Operation Ollama-First v5.0 / Phase 19 — 補完戰役紀律 tests/test_caller_registry.py (7 tests) - registry 含 30+ 核心 caller (ADR-028 對齊) - is_known_caller / assert_known_caller strict=False/True 行為 - ai_call_logger 整合:未知 caller log warning 不阻擋 - frozenset 不可變動 tests/test_deepseek_service.py (6 tests) - is_available() 需 KEY + flag 雙條件 - generate flag OFF / 200 success / 500 / timeout - usage tokens 解析(prompt_tokens / completion_tokens) tests/test_ppt_vision_service.py (6 tests) - flag OFF 不打 HTTP / 檔不存在 - ✅ 無視覺異常 / ⚠️ marker 解析 - HTTP 500 觸發 mark_unhealthy / timeout fail-safe tests/test_low_quality_response_v2.py (8 tests) - 規則 5 純英文回應 (中文 < 30%) - 規則 6 thinking-mode 漏洞 <think>...</think> - 規則 7 重複迴圈 (前 50 字 ≥ 3 次) - 規則 8 佔位符 ({{var}} / [TODO] / <待填>) - 合法繁中商業文字應通過 8 條規則 regression: 全戰役 unit test 累計 241 tests - Phase 1: 52 (logger + report) - Phase 2: 14 (ollama_resolve) - Phase 3: 36 (qa/golden/nemotron/daily) - Phase 7: 23 (anthropic + code_review) - Phase 11: 70 (rag + learning + promotion) - Phase 10.5: 8 (mcp_router) - Phase 13: 10 (retry chain) - Phase 19: 27 (caller_registry + deepseek + ppt_vision + lq_v2) ⭐ 新 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
119 lines
3.9 KiB
Python
119 lines
3.9 KiB
Python
"""
|
||
tests/test_deepseek_service.py
|
||
─────────────────────────────────────────────────────────────────
|
||
Operation Ollama-First v5.0 / Phase 15 — DeepSeek 直連 service 驗證
|
||
"""
|
||
|
||
from unittest.mock import patch, MagicMock
|
||
|
||
import pytest
|
||
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def _reset_env(monkeypatch):
|
||
"""每 test 清 env"""
|
||
monkeypatch.delenv('DEEPSEEK_DIRECT_ENABLED', raising=False)
|
||
monkeypatch.delenv('DEEPSEEK_API_KEY', raising=False)
|
||
yield
|
||
|
||
|
||
def test_is_available_requires_key_and_flag(monkeypatch):
|
||
from services.deepseek_service import DeepSeekService
|
||
|
||
svc = DeepSeekService()
|
||
# 無 key 無 flag → False
|
||
assert svc.is_available() is False
|
||
|
||
# 只有 flag → False
|
||
monkeypatch.setenv('DEEPSEEK_DIRECT_ENABLED', 'true')
|
||
assert svc.is_available() is False
|
||
|
||
# flag + key → True(需 reload module 取新 env)
|
||
monkeypatch.setenv('DEEPSEEK_API_KEY', 'sk-test')
|
||
import importlib
|
||
import services.deepseek_service as ds
|
||
importlib.reload(ds)
|
||
assert ds.deepseek_service.is_available() is True
|
||
|
||
|
||
def test_generate_returns_failure_when_unavailable(monkeypatch):
|
||
"""flag OFF 時 generate 直接 return failure,不打 HTTP"""
|
||
monkeypatch.setenv('DEEPSEEK_DIRECT_ENABLED', 'false')
|
||
from services.deepseek_service import DeepSeekService
|
||
|
||
svc = DeepSeekService()
|
||
with patch('services.deepseek_service.requests.post') as mock_post:
|
||
resp = svc.generate('test prompt')
|
||
|
||
assert resp.success is False
|
||
assert 'DEEPSEEK_DIRECT_ENABLED=false' in (resp.error or '') or \
|
||
'API_KEY 未設' in (resp.error or '')
|
||
mock_post.assert_not_called()
|
||
|
||
|
||
def test_generate_success_parses_usage(monkeypatch):
|
||
"""正常 200 回應應解 usage tokens"""
|
||
monkeypatch.setenv('DEEPSEEK_DIRECT_ENABLED', 'true')
|
||
monkeypatch.setenv('DEEPSEEK_API_KEY', 'sk-test')
|
||
|
||
import importlib
|
||
import services.deepseek_service as ds
|
||
importlib.reload(ds)
|
||
|
||
fake_resp = MagicMock(status_code=200)
|
||
fake_resp.json.return_value = {
|
||
'model': 'deepseek-chat',
|
||
'choices': [{'message': {'content': 'Hello from DeepSeek'}}],
|
||
'usage': {'prompt_tokens': 100, 'completion_tokens': 50},
|
||
}
|
||
with patch('services.deepseek_service.requests.post', return_value=fake_resp):
|
||
resp = ds.deepseek_service.generate('hi', system_prompt='you are helpful')
|
||
|
||
assert resp.success is True
|
||
assert resp.content == 'Hello from DeepSeek'
|
||
assert resp.input_tokens == 100
|
||
assert resp.output_tokens == 50
|
||
assert resp.model == 'deepseek-chat'
|
||
|
||
|
||
def test_generate_http_500_returns_failure(monkeypatch):
|
||
monkeypatch.setenv('DEEPSEEK_DIRECT_ENABLED', 'true')
|
||
monkeypatch.setenv('DEEPSEEK_API_KEY', 'sk-test')
|
||
|
||
import importlib
|
||
import services.deepseek_service as ds
|
||
importlib.reload(ds)
|
||
|
||
fake_resp = MagicMock(status_code=500)
|
||
fake_resp.text = 'Internal Server Error'
|
||
with patch('services.deepseek_service.requests.post', return_value=fake_resp):
|
||
resp = ds.deepseek_service.generate('test')
|
||
|
||
assert resp.success is False
|
||
assert 'HTTP 500' in (resp.error or '')
|
||
|
||
|
||
def test_generate_timeout_returns_failure(monkeypatch):
|
||
monkeypatch.setenv('DEEPSEEK_DIRECT_ENABLED', 'true')
|
||
monkeypatch.setenv('DEEPSEEK_API_KEY', 'sk-test')
|
||
|
||
import importlib
|
||
import services.deepseek_service as ds
|
||
importlib.reload(ds)
|
||
|
||
import requests
|
||
with patch('services.deepseek_service.requests.post',
|
||
side_effect=requests.Timeout('60s')):
|
||
resp = ds.deepseek_service.generate('test')
|
||
|
||
assert resp.success is False
|
||
assert 'timeout' in (resp.error or '').lower()
|
||
|
||
|
||
def test_check_connection_when_unavailable():
|
||
from services.deepseek_service import DeepSeekService
|
||
|
||
svc = DeepSeekService()
|
||
# 無 key 無 flag
|
||
assert svc.check_connection() is False
|