from __future__ import annotations import ast import hashlib import unittest from pathlib import Path APP_ROOT = Path(__file__).resolve().parents[1] ORIGINAL_ROOT = APP_ROOT.parent ROTATION_METHODS = { "rotation_history", "rotation_sector_members", } LADDER_ROTATION_BUILDERS = { "_build_ladders", "_build_sector_rotation", } def class_methods(path: Path, class_name: str) -> dict[str, str]: tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) owner = next( node for node in tree.body if isinstance(node, ast.ClassDef) and node.name == class_name ) return { node.name: ast.dump(node, include_attributes=False) for node in owner.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) } def top_level_functions(path: Path) -> dict[str, str]: tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) return { node.name: ast.dump(node, include_attributes=False) for node in tree.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name in LADDER_ROTATION_BUILDERS } def sha256(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() class LadderRotationSliceSourceEquivalenceTests(unittest.TestCase): def test_rotation_service_methods_are_exact_original_ast(self) -> None: original = class_methods(ORIGINAL_ROOT / "server.py", "DashboardService") migrated = class_methods( APP_ROOT / "backend" / "features" / "rotation" / "service.py", "RotationServiceMixin", ) self.assertEqual(set(migrated), ROTATION_METHODS) for name in sorted(ROTATION_METHODS): self.assertEqual(migrated[name], original[name], name) def test_dashboard_service_no_longer_duplicates_rotation_methods(self) -> None: remaining = class_methods( APP_ROOT / "backend" / "application.py", "DashboardService" ) self.assertTrue(ROTATION_METHODS.isdisjoint(remaining)) def test_ladder_and_rotation_builders_are_exact_original_ast(self) -> None: self.assertEqual( top_level_functions(ORIGINAL_ROOT / "tushare_client.py"), top_level_functions( APP_ROOT / "backend" / "data" / "providers" / "tushare_client.py" ), ) def test_api_and_frontend_assets_are_unchanged(self) -> None: for relative in ( "config/api.config.json", "static/index.html", "static/app.js", "static/styles.css", "static/pages/ladder/page.js", "static/pages/rotation/page.js", ): self.assertEqual( sha256(APP_ROOT / relative), sha256(ORIGINAL_ROOT / relative), relative, ) if __name__ == "__main__": unittest.main()