Files
ai-exchange/tests/unit/test_training_case_engine_m1.py
T

167 lines
4.4 KiB
Python

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()