168 lines
5.7 KiB
Python
168 lines
5.7 KiB
Python
from __future__ import annotations
|
|
|
|
import ast
|
|
import hashlib
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
import heaven_agent
|
|
import heaven_engine
|
|
from backend.features.heaven import agent as canonical_agent
|
|
from backend.features.heaven import engine as canonical_engine
|
|
|
|
|
|
APP_ROOT = Path(__file__).resolve().parents[1]
|
|
ORIGINAL_ROOT = APP_ROOT.parent
|
|
|
|
HEAVEN_SERVICE_METHODS = {
|
|
"_heaven_manual_schema",
|
|
"_validate_heaven_manual_data",
|
|
"_apply_heaven_manual_data",
|
|
"_heaven_line_checks",
|
|
"_resolve_heaven_stock_code",
|
|
"heaven_setup",
|
|
"_heaven_stock_context",
|
|
"_heaven_market_mode",
|
|
"_heaven_trend_sources",
|
|
"_heaven_trend_quality_issues",
|
|
"heaven_personal",
|
|
"heaven_hexagram",
|
|
"heaven_readings",
|
|
"_heaven_reading_identity",
|
|
"heaven_interpret",
|
|
"_legacy_truncated_heaven_reading",
|
|
"_call_heaven_agent",
|
|
"_heaven_index_context",
|
|
"_aggregate_index_context",
|
|
"_heaven_sector_context",
|
|
}
|
|
|
|
HEAVEN_REPOSITORY_METHODS = {
|
|
"list_sector_phase_overrides",
|
|
"save_sector_phase_override",
|
|
"delete_sector_phase_override",
|
|
"_heaven_reading_dict",
|
|
"save_heaven_reading",
|
|
"list_heaven_readings",
|
|
"latest_heaven_reading",
|
|
"delete_heaven_reading",
|
|
}
|
|
|
|
HEAVEN_HTTP_METHODS = {"heaven_hexagram", "heaven_personal", "heaven_interpret"}
|
|
|
|
|
|
def sha256(path: Path) -> str:
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
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_definitions(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, ast.ClassDef))
|
|
}
|
|
|
|
|
|
class HeavenSliceSourceEquivalenceTests(unittest.TestCase):
|
|
def assert_methods_equal(
|
|
self,
|
|
original_path: Path,
|
|
original_class: str,
|
|
migrated_path: Path,
|
|
migrated_class: str,
|
|
expected: set[str],
|
|
adapted: set[str] | None = None,
|
|
) -> None:
|
|
original = class_methods(original_path, original_class)
|
|
migrated = class_methods(migrated_path, migrated_class)
|
|
self.assertEqual(set(migrated), expected)
|
|
for name in sorted(expected - (adapted or set())):
|
|
self.assertEqual(migrated[name], original[name], name)
|
|
|
|
def test_heaven_agent_uses_shared_transport(self) -> None:
|
|
source = (
|
|
APP_ROOT / "backend" / "features" / "heaven" / "agent.py"
|
|
).read_text(encoding="utf-8")
|
|
self.assertIn("llm_transport.chat_completion", source)
|
|
self.assertNotIn("urllib.request", source)
|
|
|
|
def test_heaven_engine_definitions_are_exact_original_ast(self) -> None:
|
|
self.assertEqual(
|
|
top_level_definitions(ORIGINAL_ROOT / "heaven_engine.py"),
|
|
top_level_definitions(
|
|
APP_ROOT / "backend" / "features" / "heaven" / "engine.py"
|
|
),
|
|
)
|
|
|
|
def test_compatibility_modules_are_canonical_module_objects(self) -> None:
|
|
self.assertIs(heaven_agent, canonical_agent)
|
|
self.assertIs(heaven_engine, canonical_engine)
|
|
|
|
def test_heaven_service_methods_are_exact_original_ast(self) -> None:
|
|
self.assert_methods_equal(
|
|
ORIGINAL_ROOT / "server.py",
|
|
"DashboardService",
|
|
APP_ROOT / "backend" / "features" / "heaven" / "service.py",
|
|
"HeavenServiceMixin",
|
|
HEAVEN_SERVICE_METHODS,
|
|
{"_heaven_reading_identity"},
|
|
)
|
|
source = (
|
|
APP_ROOT / "backend" / "features" / "heaven" / "service.py"
|
|
).read_text(encoding="utf-8")
|
|
self.assertIn(
|
|
"MarketServiceMixin._display_compact_date(context_date)", source
|
|
)
|
|
|
|
def test_heaven_repository_methods_are_exact_original_ast(self) -> None:
|
|
self.assert_methods_equal(
|
|
ORIGINAL_ROOT / "database.py",
|
|
"ReviewDatabase",
|
|
APP_ROOT / "backend" / "features" / "heaven" / "repository.py",
|
|
"HeavenRepositoryMixin",
|
|
HEAVEN_REPOSITORY_METHODS,
|
|
)
|
|
|
|
def test_original_classes_no_longer_duplicate_moved_methods(self) -> None:
|
|
remaining_service = class_methods(
|
|
APP_ROOT / "backend" / "application.py", "DashboardService"
|
|
)
|
|
remaining_database = class_methods(APP_ROOT / "database.py", "ReviewDatabase")
|
|
remaining_http = class_methods(
|
|
APP_ROOT / "backend" / "application.py", "RequestHandler"
|
|
)
|
|
self.assertTrue(HEAVEN_SERVICE_METHODS.isdisjoint(remaining_service))
|
|
self.assertTrue(HEAVEN_REPOSITORY_METHODS.isdisjoint(remaining_database))
|
|
self.assertTrue(HEAVEN_HTTP_METHODS.isdisjoint(remaining_http))
|
|
|
|
def test_http_mixin_preserves_all_heaven_endpoints(self) -> None:
|
|
methods = class_methods(
|
|
APP_ROOT / "backend" / "features" / "heaven" / "http.py",
|
|
"HeavenHttpMixin",
|
|
)
|
|
self.assertEqual(set(methods), HEAVEN_HTTP_METHODS)
|
|
source = (
|
|
APP_ROOT / "backend" / "features" / "heaven" / "http.py"
|
|
).read_text(encoding="utf-8")
|
|
self.assertNotIn("SERVICE.", source)
|
|
self.assertEqual(source.count("self.application_service.heaven_"), 3)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|