Private
Public Access
0
0
Files
manual_slop/tests/test_app_controller_no_top_level_fastapi.py
T
ed 3849d30441 refactor(app_controller): remove top-level fastapi imports; lift _require_warmed to shared module
Phase 4 T4.1-T4.4 of startup_speedup_20260606 track.

DEVIATION FROM ORIGINAL SPEC: spec.md said fastapi was in src/api_hooks.py
but it was actually in src/app_controller.py (lines 17, 21). api_hooks.py
uses stdlib http.server. Phase 4 target corrected to app_controller.

LIFTED _require_warmed TO SHARED MODULE: created src/module_loader.py to
avoid duplicating the lookup logic and the cross-module import smell
(app_controller -> ai_client). src/ai_client.py re-exports it so the
T3.1 test (which asserts hasattr(src.ai_client, '_require_warmed'))
continues to work.

src/app_controller.py changes:
- Added 'from __future__ import annotations' (enables lazy type annotations;
  -> FastAPI return type now a forward reference)
- Removed 'from fastapi import FastAPI, Depends, HTTPException' (line 17)
- Removed 'from fastapi.security.api_key import APIKeyHeader' (line 21)
- Added 'from src.module_loader import _require_warmed' (cross-module via
  shared utility, not via ai_client)
- create_api(): added lookups at top of function body
- 7 _api_* helper functions (_api_get_key, _api_generate, _api_stream,
  _api_confirm_action, _api_get_session, _api_delete_session,
  _api_get_context): added 'HTTPException = _require_warmed(...).HTTPException'
  at top of each function body

EFFECTIVENESS:
- import src.app_controller no longer triggers fastapi import (saves ~470ms
  in main thread; only loaded when --enable-test-hooks is set)
- When --enable-test-hooks is set, the AppController's warmup pre-loads
  fastapi on the _io_pool, so create_api()'s lookup is O(1)

TESTS:
- tests/test_app_controller_no_top_level_fastapi.py: 4/4 PASS (was 3 RED + 1 pass)
- tests/test_ai_client_no_top_level_sdk_imports.py: 9/9 still PASS (re-export works)
- tests/test_app_controller_mcp.py, test_app_controller_offloading.py: pass
- tests/test_headless_service.py: 10/11 PASS (1 pre-existing failure
  test_generate_endpoint is a circular-import issue in google.genai,
  reproduces identically on stashed pre-Phase-4 state - NOT a regression
  from this change)
- tests/test_hooks.py: pass

NEXT: Phase 5 (feature-gated GUI module imports - command palette, NERV
theme, markdown table), then Phase 6 (ad-hoc threads -> _io_pool).
2026-06-06 16:34:46 -04:00

95 lines
3.3 KiB
Python

"""Tests that src/app_controller.py has NO top-level fastapi imports.
Per spec.md:2.2 Layer 1, the main thread's import chain must not include
heavy modules. FastAPI is heavy (~470ms) and is only needed when
`--enable-test-hooks` or `--web-host` is passed. The warmup loads it
on the AppController's _io_pool; functions that need it call
`_require_warmed("fastapi")` to get the module from sys.modules.
These tests run in a fresh subprocess to ensure no warmup state leaks
from the test runner. We assert:
- `fastapi` is NOT in `sys.modules` after `import src.app_controller`
- `fastapi.security.api_key` is NOT in `sys.modules` either
- The static audit script reports NO new violation from app_controller.py
"""
import subprocess
import sys
import textwrap
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
def _run_in_subprocess(snippet: str) -> subprocess.CompletedProcess:
script = textwrap.dedent(snippet)
return subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
cwd=str(ROOT),
timeout=30,
)
def test_app_controller_does_not_import_fastapi_at_module_level() -> None:
res = _run_in_subprocess("""
import sys
import src.app_controller
print('fastapi' in sys.modules)
""")
assert res.returncode == 0, f"stderr: {res.stderr}"
assert res.stdout.strip() == "False", f"app_controller triggered fastapi import: {res.stdout}"
def test_app_controller_does_not_import_fastapi_security_at_module_level() -> None:
res = _run_in_subprocess("""
import sys
import src.app_controller
print('fastapi.security.api_key' in sys.modules)
""")
assert res.returncode == 0, f"stderr: {res.stderr}"
assert res.stdout.strip() == "False", f"app_controller triggered fastapi.security.api_key import: {res.stdout}"
def test_app_controller_create_api_still_resolvable() -> None:
"""Even without fastapi in sys.modules, the function reference must be
importable (it just returns a lazy FastAPI app on call)."""
res = _run_in_subprocess("""
import src.app_controller
print(hasattr(src.app_controller.AppController, 'create_api'))
print(callable(src.app_controller.AppController.create_api))
""")
assert res.returncode == 0, f"stderr: {res.stderr}"
assert res.stdout.strip() == "True\nTrue"
def test_audit_main_thread_imports_sees_no_new_violation_from_app_controller() -> None:
"""Run the static audit and check that src/app_controller.py contributes no
new fastapi violations (any remaining violations are pre-existing in OTHER
files; this just verifies app_controller is clean).
"""
res = _run_in_subprocess("""
import ast
from pathlib import Path
root = Path('.').resolve()
app_controller_path = root / 'src' / 'app_controller.py'
tree = ast.parse(app_controller_path.read_text(encoding='utf-8'))
heavy = ['fastapi', 'fastapi.security', 'fastapi.security.api_key']
for node in tree.body:
if isinstance(node, ast.Import):
for alias in node.names:
for h in heavy:
if alias.name == h or alias.name.startswith(h + '.'):
print('VIOLATION:', alias.name)
elif isinstance(node, ast.ImportFrom):
if node.module:
for h in heavy:
if node.module == h or node.module.startswith(h + '.'):
print('VIOLATION:', node.module)
print('OK')
""")
assert res.returncode == 0, f"stderr: {res.stderr}"
assert "OK" in res.stdout
assert "VIOLATION" not in res.stdout