"""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()