import inspect import os import re import unittest from unittest.mock import patch os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:") from scripts import run_migration_static_checks from src.db.base import Base TRAINING_REQUIRED_SCHEMA = { "training_cases": ( "id", "case_type", "skill_tag", "instrument_id", "timeframe", "start_ts", "decision_ts", "end_ts", "source_event_refs", "answer_key", "status", "created_at", "updated_at", ), "training_case_tasks": ( "id", "case_id", "task_type", "skill_tag", "prompt", "required_annotation_types", "max_score", "status", "created_at", "updated_at", ), "training_attempts": ( "id", "case_id", "user_id", "status", "started_at", "submitted_at", "total_score", "created_at", "updated_at", ), "training_annotations": ( "id", "attempt_id", "user_id", "annotation_type", "payload", "created_at", "updated_at", ), "training_scores": ( "id", "attempt_id", "user_id", "category", "score", "max_score", "feedback", "created_at", "updated_at", ), "training_progress": ( "id", "user_id", "skill_tag", "attempt_count", "avg_score", "last_attempt_at", "weakness_tags", "created_at", "updated_at", ), } class _Result: def __init__(self, rows=None, scalar=1): self._rows = rows or [] self._scalar = scalar def mappings(self): return self def all(self): return self._rows def first(self): return self._rows[0] if self._rows else None def scalar_one(self): return self._scalar class _ReadSession: def __enter__(self): return self def __exit__(self, exc_type, exc, traceback): return False def execute(self, sql, params=None): sql_text = str(sql).lower() case_row = { "id": 7, "case_type": "sweep", "skill_tag": "liquidity_pool", "instrument_id": 1, "timeframe": "15m", "start_ts": "2026-04-01T00:00:00Z", "decision_ts": "2026-04-01T02:00:00Z", "end_ts": "2026-04-01T04:00:00Z", "source_event_refs": [{"table": "sweep_events", "id": 5}], "status": "published", "created_at": "2026-04-01T00:00:00Z", "updated_at": "2026-04-01T00:00:00Z", } if "answer_key" in sql_text: case_row["answer_key"] = {"side": "buy_side"} return _Result(rows=[case_row]) class TrainingCaseEngineM1Tests(unittest.TestCase): def test_migration_static_checks_can_identify_training_tables(self) -> None: result = run_migration_static_checks.run_migration_static_checks(required_schema=TRAINING_REQUIRED_SCHEMA) self.assertTrue(result["passed"], result["errors"]) def test_model_metadata_contains_training_tables(self) -> None: import src.models # noqa: F401 for table_name in TRAINING_REQUIRED_SCHEMA: self.assertIn(table_name, Base.metadata.tables) def test_repository_source_does_not_write_to_trading_chain_tables(self) -> None: from src.repositories.training_case_repository import TrainingCaseRepository source = inspect.getsource(TrainingCaseRepository).lower() for table_name in ("trade_signals", "trade_executions", "backtest_results"): forbidden = re.compile(rf"\b(insert\s+into|update|delete\s+from|truncate\s+table)\s+{table_name}\b") self.assertIsNone(forbidden.search(source), f"repository writes to {table_name}") def test_list_cases_does_not_expose_answer_key_by_default(self) -> None: from src.repositories import training_case_repository as repository_module repository = repository_module.TrainingCaseRepository() with patch.object(repository_module, "SessionLocal", return_value=_ReadSession()): cases = repository.list_cases() self.assertEqual(len(cases), 1) self.assertNotIn("answer_key", cases[0]) if __name__ == "__main__": unittest.main()