from __future__ import annotations import ast import sys import unittest from pathlib import Path from unittest.mock import patch from backend.bootstrap import runtime class BackgroundLifecycleTests(unittest.TestCase): def test_service_construction_does_not_start_the_scheduler(self) -> None: source = (Path(__file__).parents[1] / "backend" / "application.py").read_text( encoding="utf-8" ) tree = ast.parse(source) service_class = next( node for node in tree.body if isinstance(node, ast.ClassDef) and node.name == "DashboardService" ) constructor = next( node for node in service_class.body if isinstance(node, ast.FunctionDef) and node.name == "__init__" ) called_methods = { node.func.attr for node in ast.walk(constructor) if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute) } self.assertNotIn("start_scheduler", called_methods) def test_runtime_starts_jobs_after_bind_and_stops_before_close(self) -> None: events: list[str] = [] class Service: def start_background_jobs(self) -> None: events.append("start-jobs") def stop_background_jobs(self) -> None: events.append("stop-jobs") class Server: def __init__(self, address: tuple[str, int], handler: object) -> None: events.append("bind") def serve_forever(self) -> None: events.append("serve") raise KeyboardInterrupt def server_close(self) -> None: events.append("close") with ( patch.object(runtime, "ThreadingHTTPServer", Server), patch.object(sys, "argv", ["server.py", "--port", "8797"]), ): runtime.main(object, Service()) self.assertEqual( events, ["bind", "start-jobs", "serve", "stop-jobs", "close"] ) if __name__ == "__main__": unittest.main()