Files
ai-exchange/tests/unit/test_visual_review_service.py
T
2026-05-12 08:11:16 +08:00

188 lines
7.9 KiB
Python

import unittest
from src.services.visual_watch import (
VISUAL_TRIGGER_CANDIDATE_ACTIONABLE_WATCH,
VISUAL_TRIGGER_MARKET_ANOMALY_REVIEW,
)
from src.services.visual_watch.visual_review_service import VisualReviewService
class StubVisualSnapshotRepository:
def __init__(self) -> None:
self.watch_runs = {}
self.snapshots = {}
self.latest_snapshots_by_candidate = {}
self.snapshot_calls = []
self.review_calls = []
self.completed_calls = []
self.pending_watch_runs = []
def fetch_watch_run_by_id(self, watch_run_id: int):
return self.watch_runs.get(watch_run_id)
def fetch_snapshot_by_id(self, snapshot_id: int):
return self.snapshots.get(snapshot_id)
def fetch_latest_snapshot_for_candidate(self, candidate_id: int):
return self.latest_snapshots_by_candidate.get(candidate_id)
def insert_snapshot(self, **kwargs):
self.snapshot_calls.append(kwargs)
snapshot_id = len(self.snapshot_calls) + 332
self.snapshots[snapshot_id] = {
"id": snapshot_id,
"watch_run_id": kwargs["watch_run_id"],
"source_name": kwargs["source_name"],
"image_path": kwargs["image_path"],
"captured_ts": kwargs["captured_ts"],
"status": kwargs["status"],
"payload": kwargs["payload"],
}
return snapshot_id
def insert_visual_review(self, **kwargs):
self.review_calls.append(kwargs)
return len(self.review_calls) + 443
def mark_visual_watch_run_completed(self, **kwargs):
self.completed_calls.append(kwargs)
def fetch_pending_watch_runs(self, **kwargs):
return list(self.pending_watch_runs)
class StubVisualSnapshotCaptureService:
def __init__(self) -> None:
self.calls = []
def capture_watch_run_snapshot(self, *, watch_run: dict, source_name: str | None = None, output_dir=None):
self.calls.append({"watch_run": watch_run, "source_name": source_name, "output_dir": output_dir})
return {
"status": "captured",
"watch_run_id": watch_run["id"],
"instrument_id": watch_run["instrument_id"],
"source_name": source_name or "auto_visual_watch_capture",
"image_path": f"captures/watch-{watch_run['id']}.html",
"artifact_path": f"captures/watch-{watch_run['id']}.json",
"captured_ts": "2026-05-11T02:00:00+00:00",
"capture_mode": "review_packet_html",
"payload": {
"capture_mode": "review_packet_html",
"captured_ts": "2026-05-11T02:00:00+00:00",
"source_name": source_name or "auto_visual_watch_capture",
"image_path": f"captures/watch-{watch_run['id']}.html",
"artifact_path": f"captures/watch-{watch_run['id']}.json",
"watch_run": {"id": watch_run["id"]},
"review_packet_status": "captured",
"review_packet_summary": {"present": True},
"review_packet": {"decision_packet": {"id": watch_run["decision_packet_id"]}},
},
}
class VisualReviewServiceTests(unittest.TestCase):
def test_review_watch_run_with_image_path_creates_snapshot_and_review(self) -> None:
repository = StubVisualSnapshotRepository()
repository.watch_runs[11] = {
"id": 11,
"trigger_type": VISUAL_TRIGGER_CANDIDATE_ACTIONABLE_WATCH,
"source_name": "reserved_for_visual_watch",
"instrument_id": 1,
"candidate_id": 22,
"signal_id": 33,
"decision_packet_id": 44,
"symbol": "BTC-USDT",
"timeframe": "1m",
"status": "reserved",
"payload": {
"trigger_type": VISUAL_TRIGGER_CANDIDATE_ACTIONABLE_WATCH,
"visual_trigger_policy": {"status": "triggered", "reason": "high_value_actionable_candidate"},
"visual_trigger_reason": "high_value_actionable_candidate",
},
}
service = VisualReviewService(repository=repository)
result = service.review_watch_run(
watch_run_id=11,
image_path="captures/btc.png",
source_name="capture_bot",
)
self.assertEqual(result["status"], "reviewed")
self.assertEqual(result["watch_run_id"], 11)
self.assertEqual(result["visual_state"], "confirm")
self.assertEqual(result["snapshot_id"], 333)
self.assertEqual(repository.snapshot_calls[0]["image_path"], "captures/btc.png")
self.assertEqual(repository.review_calls[0]["visual_state"], "confirm")
self.assertEqual(repository.review_calls[0]["payload"]["payload"]["trigger_policy"]["status"], "triggered")
self.assertEqual(repository.completed_calls[0]["status"], "reviewed")
def test_review_pending_watch_runs_uses_existing_snapshot(self) -> None:
repository = StubVisualSnapshotRepository()
repository.watch_runs[21] = {
"id": 21,
"trigger_type": VISUAL_TRIGGER_MARKET_ANOMALY_REVIEW,
"source_name": "reserved_for_visual_watch",
"instrument_id": 1,
"candidate_id": 55,
"signal_id": 66,
"decision_packet_id": 77,
"symbol": "ETH-USDT",
"timeframe": "5m",
"status": "reserved",
"payload": {"trigger_type": VISUAL_TRIGGER_MARKET_ANOMALY_REVIEW},
}
repository.latest_snapshots_by_candidate[55] = {
"id": 444,
"watch_run_id": 99,
"source_name": "manual_capture",
"image_path": "captures/eth.png",
"captured_ts": "2026-05-11T01:00:00+00:00",
"status": "captured",
"payload": {},
}
repository.pending_watch_runs = [repository.watch_runs[21]]
service = VisualReviewService(repository=repository)
result = service.review_pending_watch_runs(limit=1, instrument_id=1)
self.assertEqual(result["status"], "completed")
self.assertEqual(result["attempted"], 1)
self.assertEqual(result["reviewed"], 1)
self.assertEqual(result["items"][0]["visual_state"], "mismatch")
self.assertEqual(result["items"][0]["snapshot_id"], 444)
self.assertEqual(result["trigger_type_counts"][VISUAL_TRIGGER_MARKET_ANOMALY_REVIEW], 1)
self.assertEqual(result["review_state_counts"]["mismatch"], 1)
self.assertEqual(repository.completed_calls[0]["payload"]["review_status"], "reviewed")
def test_review_watch_run_auto_captures_when_no_snapshot_exists(self) -> None:
repository = StubVisualSnapshotRepository()
repository.watch_runs[31] = {
"id": 31,
"trigger_type": VISUAL_TRIGGER_CANDIDATE_ACTIONABLE_WATCH,
"source_name": "reserved_for_visual_watch",
"instrument_id": 1,
"candidate_id": None,
"signal_id": 88,
"decision_packet_id": 99,
"symbol": "SOL-USDT",
"timeframe": "1m",
"status": "reserved",
"payload": {"trigger_type": VISUAL_TRIGGER_CANDIDATE_ACTIONABLE_WATCH},
}
capture_service = StubVisualSnapshotCaptureService()
service = VisualReviewService(repository=repository, snapshot_capture_service=capture_service)
result = service.review_watch_run(watch_run_id=31)
self.assertEqual(result["status"], "reviewed")
self.assertEqual(result["snapshot_image_path"], "captures/watch-31.html")
self.assertEqual(result["snapshot_artifact_path"], "captures/watch-31.json")
self.assertEqual(capture_service.calls[0]["source_name"], "auto_visual_watch_capture")
self.assertEqual(repository.snapshot_calls[0]["source_name"], "auto_visual_watch_capture")
self.assertEqual(repository.snapshot_calls[0]["payload"]["payload"]["capture_mode"], "review_packet_html")
if __name__ == "__main__":
unittest.main()