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>
136 lines
4.8 KiB
Python
136 lines
4.8 KiB
Python
"""
|
||
tests/test_ppt_vision_service.py
|
||
─────────────────────────────────────────────────────────────────
|
||
Operation Ollama-First v5.0 / Phase 14 — PPT vision (minicpm-v) 驗證
|
||
"""
|
||
|
||
import os
|
||
import tempfile
|
||
from unittest.mock import patch, MagicMock
|
||
|
||
import pytest
|
||
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def _reset_env(monkeypatch):
|
||
monkeypatch.delenv('PPT_VISION_ENABLED', raising=False)
|
||
yield
|
||
|
||
|
||
@pytest.fixture
|
||
def fake_image():
|
||
"""產生 1KB 假 png 檔給 test 用"""
|
||
f = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
|
||
f.write(b'\x89PNG\r\n\x1a\n' + b'\x00' * 1000) # PNG magic + 1KB padding
|
||
f.close()
|
||
yield f.name
|
||
try:
|
||
os.unlink(f.name)
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
def test_flag_off_returns_disabled_error(fake_image):
|
||
"""flag OFF 時 check_image 直接回 success=False(不打 HTTP)"""
|
||
from services.ppt_vision_service import PPTVisionService
|
||
|
||
svc = PPTVisionService()
|
||
with patch('services.ppt_vision_service.requests.post') as mock_post:
|
||
result = svc.check_image(fake_image)
|
||
|
||
assert result.success is False
|
||
assert 'PPT_VISION_ENABLED=false' in (result.error or '')
|
||
mock_post.assert_not_called()
|
||
|
||
|
||
def test_missing_image_file(monkeypatch):
|
||
monkeypatch.setenv('PPT_VISION_ENABLED', 'true')
|
||
from services.ppt_vision_service import PPTVisionService
|
||
|
||
svc = PPTVisionService()
|
||
result = svc.check_image('/tmp/this_file_does_not_exist_xyz.png')
|
||
|
||
assert result.success is False
|
||
assert 'image not found' in (result.error or '')
|
||
|
||
|
||
def test_no_issues_response(fake_image, monkeypatch):
|
||
"""minicpm-v 回「✅ 無視覺異常」→ issues_found 應為空 list"""
|
||
monkeypatch.setenv('PPT_VISION_ENABLED', 'true')
|
||
from services.ppt_vision_service import PPTVisionService
|
||
|
||
fake_resp = MagicMock(status_code=200)
|
||
fake_resp.json.return_value = {'response': '✅ 無視覺異常'}
|
||
|
||
with patch('services.ollama_service.resolve_ollama_host',
|
||
return_value='http://test:11434'), \
|
||
patch('services.ppt_vision_service.requests.post', return_value=fake_resp):
|
||
svc = PPTVisionService()
|
||
result = svc.check_image(fake_image)
|
||
|
||
assert result.success is True
|
||
assert result.issues_found == []
|
||
assert result.confidence == 1.0
|
||
|
||
|
||
def test_issues_detected(fake_image, monkeypatch):
|
||
"""minicpm-v 回多個 ⚠️ marker → issues_found 應含解析的問題"""
|
||
monkeypatch.setenv('PPT_VISION_ENABLED', 'true')
|
||
from services.ppt_vision_service import PPTVisionService
|
||
|
||
fake_resp = MagicMock(status_code=200)
|
||
fake_resp.json.return_value = {
|
||
'response': '⚠️ 圖表被切掉:右側長條圖超出邊界\n'
|
||
'⚠️ 文字溢出:商品標題被遮擋\n'
|
||
'其他無問題'
|
||
}
|
||
|
||
with patch('services.ollama_service.resolve_ollama_host',
|
||
return_value='http://test:11434'), \
|
||
patch('services.ppt_vision_service.requests.post', return_value=fake_resp):
|
||
svc = PPTVisionService()
|
||
result = svc.check_image(fake_image)
|
||
|
||
assert result.success is True
|
||
assert len(result.issues_found) == 2
|
||
assert any('圖表被切掉' in i for i in result.issues_found)
|
||
assert any('文字溢出' in i for i in result.issues_found)
|
||
assert result.confidence > 0.5
|
||
|
||
|
||
def test_http_500_marks_unhealthy(fake_image, monkeypatch):
|
||
"""HTTP 500 → success=False + mark_unhealthy 被呼叫"""
|
||
monkeypatch.setenv('PPT_VISION_ENABLED', 'true')
|
||
from services.ppt_vision_service import PPTVisionService
|
||
|
||
fake_resp = MagicMock(status_code=500)
|
||
fake_resp.text = 'oops'
|
||
|
||
with patch('services.ollama_service.resolve_ollama_host',
|
||
return_value='http://test:11434'), \
|
||
patch('services.ollama_service.mark_unhealthy') as mock_mark, \
|
||
patch('services.ppt_vision_service.requests.post', return_value=fake_resp):
|
||
svc = PPTVisionService()
|
||
result = svc.check_image(fake_image)
|
||
|
||
assert result.success is False
|
||
assert 'HTTP 500' in (result.error or '')
|
||
mock_mark.assert_called_once_with('http://test:11434')
|
||
|
||
|
||
def test_timeout_returns_failure(fake_image, monkeypatch):
|
||
monkeypatch.setenv('PPT_VISION_ENABLED', 'true')
|
||
from services.ppt_vision_service import PPTVisionService
|
||
|
||
import requests
|
||
with patch('services.ollama_service.resolve_ollama_host',
|
||
return_value='http://test:11434'), \
|
||
patch('services.ollama_service.mark_unhealthy'), \
|
||
patch('services.ppt_vision_service.requests.post',
|
||
side_effect=requests.Timeout('60s')):
|
||
svc = PPTVisionService()
|
||
result = svc.check_image(fake_image)
|
||
|
||
assert result.success is False
|
||
assert 'timeout' in (result.error or '').lower()
|