refactor: establish standalone application boundary
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from backend.application import (
|
||||
AUTHENTICATED_POST_HANDLERS,
|
||||
@@ -9,6 +11,23 @@ from backend.application import (
|
||||
)
|
||||
|
||||
|
||||
APP_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def class_methods(path: Path, class_name: str) -> set[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
|
||||
for node in owner.body
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
||||
}
|
||||
|
||||
|
||||
class HttpDispatchContractTests(unittest.TestCase):
|
||||
@staticmethod
|
||||
def handler(path: str, calls: list[str]) -> RequestHandler:
|
||||
@@ -78,6 +97,49 @@ class HttpDispatchContractTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual(calls, ["auth", "csrf", f"access:POST:{path}"])
|
||||
|
||||
def test_application_composition_and_route_ownership_stay_narrow(self) -> None:
|
||||
application = APP_ROOT / "backend" / "application.py"
|
||||
dispatch = APP_ROOT / "backend" / "http" / "dispatch.py"
|
||||
self.assertLessEqual(len(application.read_text(encoding="utf-8").splitlines()), 220)
|
||||
self.assertLessEqual(len(dispatch.read_text(encoding="utf-8").splitlines()), 150)
|
||||
self.assertEqual(class_methods(application, "DashboardService"), {"__init__"})
|
||||
self.assertEqual(class_methods(application, "RequestHandler"), set())
|
||||
self.assertEqual(
|
||||
class_methods(dispatch, "ApplicationHttpDispatchMixin"),
|
||||
{"_dispatch_named_handler", "do_GET", "do_POST", "do_DELETE"},
|
||||
)
|
||||
|
||||
expected_route_owners = {
|
||||
"accounts", "alerts", "auction", "dragon_tiger", "heaven", "market",
|
||||
"mentor", "pools", "popularity", "review", "rotation", "screener",
|
||||
"sentiment", "system", "themes",
|
||||
}
|
||||
route_files = {
|
||||
path.parent.name: path
|
||||
for path in (APP_ROOT / "backend" / "features").glob("*/routes.py")
|
||||
}
|
||||
self.assertEqual(set(route_files), expected_route_owners)
|
||||
for path in route_files.values():
|
||||
source = path.read_text(encoding="utf-8")
|
||||
self.assertLessEqual(len(source.splitlines()), 120, path.name)
|
||||
self.assertNotIn("backend.application", source)
|
||||
self.assertNotRegex(source, r"\bSERVICE\b")
|
||||
|
||||
def test_application_service_methods_have_single_domain_owners(self) -> None:
|
||||
owners = (
|
||||
("features/system/service.py", "SystemServiceMixin", 300),
|
||||
("features/accounts/application.py", "AccountApplicationMixin", 100),
|
||||
("jobs/service.py", "JobServiceMixin", 80),
|
||||
)
|
||||
claimed: set[str] = set()
|
||||
for relative, class_name, line_limit in owners:
|
||||
path = APP_ROOT / "backend" / relative
|
||||
methods = class_methods(path, class_name)
|
||||
self.assertTrue(claimed.isdisjoint(methods))
|
||||
claimed.update(methods)
|
||||
self.assertLessEqual(len(path.read_text(encoding="utf-8").splitlines()), line_limit)
|
||||
self.assertEqual(len(claimed), 27)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user