Files
codex-app-server/cloud/tests/test_handler_split_conformance.py
T
host 34a19ab9e2 code-to10 v5 rebase follow-up: keep main #275 resident working on rebased tree
- restore _audit_row in cloud/automations.py as a thin wrapper over
  cloud.auth.audit_on_conn (legacy row + ledger entry pair,
  user_id system, best-effort self-commit preserved). The stack's
  gap-4 option A deleted the direct writer; main #275 resident.py
  imports _audit_row, so the bare rebase broke server import.
  No direct audit_log INSERT is reintroduced, so the #250
  no-bypass scan stays green.
- conformance accounting 238/144 -> 243/149: main #275 added 5
  resident methods that legitimately stay in server.py; moved
  count stays 94, no duplicates.
- directional: resident x3, audit chain, conformance, automations
  x3 suites, 51 tests OK
2026-11-03 02:49:26 +00:00

165 lines
7.2 KiB
Python

"""Tests for the v2 handler mixin split (integration base 02835f6).
Pins the split contract so a future violation fails CI:
- the 94 moved methods still resolve on CloudControlPlaneHandler and
come from their mixin module (behaviour unchanged, only the defining
module moved);
- mixin/support modules never import cloud.server (no cycle), never
call sqlite3.connect and never import psycopg (narrow AST scan, same
as #238 arch_conformance.scan_imports);
- server.py re-exports the moved shared helpers as identical objects,
so `from cloud.server import _task_to_dict` keeps working;
- handler method accounting: 243 total, 149 left in server.py itself,
94 in mixins, no name defined twice. (v5 rebase: main #275 added
5 resident methods that legitimately stay in server.py, so the
stack's 238/144 becomes 243/149; moved count stays 94.)
Only the standard library; no real model, no network, no secrets.
"""
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(
os.path.dirname(os.path.abspath(__file__)))))
from cloud.arch_conformance import scan_imports # noqa: E402
from cloud.handler_support import ( # noqa: E402
_apply_default_quota, _default_quota_usd_micros, _task_to_dict)
from cloud.server import CloudControlPlaneHandler # noqa: E402
from cloud import server as _server_module # noqa: E402
MOVED = {
"cloud.handler_oauth": [
"_oauth_provider", "_oauth_login", "_oauth_clients",
"_oauth_finish", "_oauth_callback", "_oauth_error_page",
"_oauth_success_page", "_oauth_bind_success_page",
"_oauth_callback_get", "_oauth_bind_route",
"_oauth_bind_initiate", "_oauth_bindings_get",
"_oauth_bindings_delete", "_github_app_public_base",
"_github_app_error_page", "_github_app_setup_state",
"_github_app_setup_page", "_github_app_callback",
"_github_app_status"],
"cloud.handler_identity": [
"_memory_create", "_memory_list", "_memory_supersede",
"_identity_store", "_identity_close", "_identity_profile_get",
"_identity_profile_upsert", "_identity_user_memories_list",
"_identity_user_memory_create", "_identity_user_memory_supersede",
"_identity_user_memory_delete", "_identity_context",
"_auto_memory_store", "_auto_memory_close",
"_auto_memory_settings_get", "_auto_memory_settings_upsert",
"_auto_memory_log", "_auto_memory_run", "_task_auto_memory_flags",
"_project_identity_memory_create",
"_project_identity_memories_list",
"_project_identity_memory_supersede",
"_project_identity_memory_delete",
"_kb_index", "_kb_search", "_project_cost", "_task_cost",
"_relay_models", "_models_overview"],
"cloud.handler_project": [
"_fetch_environment", "_create_environment", "_list_environments",
"_get_environment", "_archive_environment", "_create_project",
"_update_project", "_github_repos_list", "_list_projects",
"_can_manage_members", "_set_member_row", "_project_members_list",
"_resolve_invite_target", "_project_member_invite",
"_project_member_patch", "_project_member_delete",
"_remove_member_row", "_project_exists", "_check_project",
"_project_push_subscription_delete"],
"cloud.handler_automation": [
"_push_create_endpoint", "_push_dispatch_endpoint",
"_automation_create", "_automation_list",
"_todos_list", "_todos_create", "_todos_get", "_todos_update",
"_todos_delete", "_automation_cancel", "_automation_get",
"_automation_bind_get", "_automation_bind", "_automation_unbind",
"_automation_heartbeat", "_automation_update", "_triage_inbox"],
"cloud.handler_fullauto": [
"_full_auto_status", "_full_auto_config_plan",
"_full_auto_validate_action", "_full_auto_merge_gate",
"_full_auto_ci_fix", "_full_auto_release_plan",
"_full_auto_rollback_plan", "_full_auto_audit_list",
"_full_auto_execute"],
}
NEW_MODULES = ["cloud/handler_support.py", "cloud/handler_oauth.py",
"cloud/handler_identity.py", "cloud/handler_project.py",
"cloud/handler_automation.py", "cloud/handler_fullauto.py"]
def _own_methods(cls):
return {name for name, value in vars(cls).items()
if callable(value)}
class TestMovedMethodsResolve(unittest.TestCase):
def test_all_moved_methods_resolve_from_handler(self):
for module, names in MOVED.items():
for name in names:
func = getattr(CloudControlPlaneHandler, name, None)
self.assertIsNotNone(
func, "%s missing on handler" % name)
self.assertEqual(
func.__module__, module,
"%s should be defined in %s" % (name, module))
def test_moved_count_is_94(self):
total = sum(len(names) for names in MOVED.values())
self.assertEqual(total, 94)
class TestNoCyclesOrDrivers(unittest.TestCase):
def test_new_modules_pass_layer_scan(self):
repo_root = os.path.dirname(os.path.dirname(
os.path.dirname(os.path.abspath(__file__))))
for rel in NEW_MODULES:
with open(os.path.join(repo_root, rel),
encoding="utf-8") as f:
scanned = scan_imports(f.read())
self.assertNotIn("server", scanned["cloud"],
"%s must not import cloud.server" % rel)
self.assertFalse(scanned["driver_connect"],
"%s must not call sqlite3.connect" % rel)
self.assertFalse(scanned["driver_import"],
"%s must not import psycopg" % rel)
class TestReexports(unittest.TestCase):
def test_shared_helpers_are_identical_objects(self):
self.assertIs(_server_module._task_to_dict, _task_to_dict)
self.assertIs(_server_module._apply_default_quota,
_apply_default_quota)
self.assertIs(_server_module._default_quota_usd_micros,
_default_quota_usd_micros)
def test_base_plumbing_stays_in_server(self):
own = _own_methods(CloudControlPlaneHandler)
for name in ["_send_json", "_send_html", "_read_body",
"_read_json", "_db", "_authenticate", "do_GET",
"do_POST", "_dispatch", "_dispatch_project",
"_dispatch_task_route"]:
self.assertIn(name, own, "%s must stay in server.py" % name)
class TestMethodAccounting(unittest.TestCase):
def test_total_243_no_duplicates(self):
seen = {}
dups = []
classes = [CloudControlPlaneHandler]
for base in CloudControlPlaneHandler.__mro__:
if base.__module__.startswith("cloud.handler_"):
classes.append(base)
total = 0
for cls in classes:
for name in _own_methods(cls):
total += 1
if name in seen:
dups.append((seen[name], cls.__name__, name))
seen[name] = cls.__name__
self.assertEqual(dups, [])
self.assertEqual(total, 243)
self.assertEqual(len(_own_methods(CloudControlPlaneHandler)),
243 - 94)
if __name__ == "__main__":
unittest.main()