93 lines
2.9 KiB
Python
93 lines
2.9 KiB
Python
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()
|