diff --git a/src/aipass/api/.seedgo/bypass.json b/src/aipass/api/.seedgo/bypass.json index 5bb1f5ac..260cc656 100644 --- a/src/aipass/api/.seedgo/bypass.json +++ b/src/aipass/api/.seedgo/bypass.json @@ -89,6 +89,26 @@ "file": "tests/test_integrations.py", "standard": "architecture", "reason": "Test file — lives in tests/ by convention, not in the 3-layer app structure. Test files are exempt from layer architecture standard." + }, + { + "file": "apps/integrations/broken_driver/driver.py", + "standard": "architecture", + "reason": "Integration driver test fixture in gitignored apps/integrations/ — intentionally outside 3-layer structure per DPLAN-0133 architecture. Loaded via importlib.util.spec_from_file_location, not standard package import." + }, + { + "file": "apps/integrations/stress_test/driver.py", + "standard": "architecture", + "reason": "Integration driver test fixture in gitignored apps/integrations/ — intentionally outside 3-layer structure per DPLAN-0133 architecture. Loaded via importlib.util.spec_from_file_location, not standard package import." + }, + { + "file": "apps/integrations/no_register/driver.py", + "standard": "architecture", + "reason": "Integration driver test fixture in gitignored apps/integrations/ — intentionally outside 3-layer structure per DPLAN-0133 architecture. Loaded via importlib.util.spec_from_file_location, not standard package import." + }, + { + "file": "apps/modules/integrations_manager.py", + "standard": "unused_function", + "reason": "fetch_contracts() and call_contract() are test API hooks called from tests/test_integrations.py. The unused_function checker excludes test dirs from its search corpus, so these appear unused despite being actively called. Encapsulation standard requires tests to go through modules, not import handlers directly — these functions serve that purpose." } ], "notes": { diff --git a/src/aipass/api/apps/modules/bridge.py b/src/aipass/api/apps/modules/bridge.py index 47e74a0c..29f7812e 100644 --- a/src/aipass/api/apps/modules/bridge.py +++ b/src/aipass/api/apps/modules/bridge.py @@ -63,6 +63,9 @@ def clear() -> None: def handle_command(command: str, args: list) -> bool: """Bridge is a utility module — no drone commands. Always returns False.""" + if args and args[0] in ("--help", "-h", "help"): + print_introspection() + return False if not args: print_introspection() return False diff --git a/src/aipass/api/apps/modules/integrations_manager.py b/src/aipass/api/apps/modules/integrations_manager.py index c947c931..722a09b2 100644 --- a/src/aipass/api/apps/modules/integrations_manager.py +++ b/src/aipass/api/apps/modules/integrations_manager.py @@ -101,6 +101,19 @@ def _run_call(contract_name: str, args: List[str]) -> int: return 0 +def fetch_contracts() -> dict: + """Return contract listing result dict. Called from tests/test_integrations.py.""" + return get_contracts(list_contracts()) + + +def call_contract(contract_name: str, args: list) -> dict: + """Resolve and invoke a contract driver. Called from tests/test_integrations.py.""" + driver_fn = resolve(contract_name) + if driver_fn is None: + return {"result": None, "success": False, "error": f"contract '{contract_name}' not registered"} + return invoke(driver_fn, contract_name, args) + + def handle_command(command: str, args: List[str]) -> bool: """ Handle integrations subcommands. diff --git a/src/aipass/api/apps/modules/registry.py b/src/aipass/api/apps/modules/registry.py index b1bd543c..1f5a4abd 100644 --- a/src/aipass/api/apps/modules/registry.py +++ b/src/aipass/api/apps/modules/registry.py @@ -106,6 +106,9 @@ def _import_driver(driver_path: Path, project_name: str) -> None: def handle_command(command: str, args: list) -> bool: """Registry is a utility module — no drone commands. Always returns False.""" + if args and args[0] in ("--help", "-h", "help"): + print_introspection() + return False if not args: print_introspection() return False diff --git a/src/aipass/api/tests/test_integrations.py b/src/aipass/api/tests/test_integrations.py index b4121c9b..31d0ca01 100644 --- a/src/aipass/api/tests/test_integrations.py +++ b/src/aipass/api/tests/test_integrations.py @@ -11,16 +11,14 @@ Tests for DPLAN-0133 Phase 2: bridge + registry + handlers. Groups: TestBridge — contract registration, resolve, list, clear TestRegistry — auto-discovery walk, empty dir, missing driver, broken import - TestListHandler — get_contracts() happy path and empty - TestCallHandler — invoke() happy path, args forwarding, exception handling + TestFetchContracts — fetch_contracts() happy path and empty + TestCallContract — call_contract() happy path, unregistered, args forwarding, exception """ import pytest from aipass.api.apps.modules import bridge, registry -from aipass.api.apps.modules.bridge import list_contracts, resolve -from aipass.api.apps.handlers.integrations.list import get_contracts -from aipass.api.apps.handlers.integrations.call import invoke +from aipass.api.apps.modules.integrations_manager import fetch_contracts, call_contract # --------------------------------------------------------------------------- @@ -152,48 +150,50 @@ class TestRegistry: # --------------------------------------------------------------------------- -# TestListHandler +# TestFetchContracts # --------------------------------------------------------------------------- -class TestListHandler: +class TestFetchContracts: def test_empty_returns_success(self): - """get_contracts() returns success with empty list when bridge is clear.""" - result = get_contracts(list_contracts()) + """fetch_contracts() returns success with empty list when bridge is clear.""" + result = fetch_contracts() assert result["success"] is True assert result["contracts"] == [] assert result["count"] == 0 def test_returns_registered_contracts(self): - """get_contracts() returns sorted contracts from bridge.""" + """fetch_contracts() returns sorted contracts from bridge.""" bridge.register("beta", lambda: None) bridge.register("alpha", lambda: None) - result = get_contracts(list_contracts()) + result = fetch_contracts() assert result["success"] is True assert result["contracts"] == ["alpha", "beta"] assert result["count"] == 2 # --------------------------------------------------------------------------- -# TestCallHandler +# TestCallContract # --------------------------------------------------------------------------- -class TestCallHandler: - def test_invoke_registered_contract(self): - """invoke() calls registered driver and returns success.""" +class TestCallContract: + def test_call_registered_contract(self): + """call_contract() resolves and invokes registered driver.""" bridge.register("ping", lambda *a: "pong") - driver_fn = resolve("ping") - result = invoke(driver_fn, "ping", []) + result = call_contract("ping", []) assert result["success"] is True assert result["result"] == "pong" - def test_unregistered_contract_resolves_none(self): - """resolve() returns None for unregistered contract.""" - assert resolve("nope") is None + def test_call_unregistered_returns_failure(self): + """call_contract() returns failure for unregistered contract.""" + result = call_contract("nope", []) + assert result["success"] is False + assert result["error"] is not None + assert "nope" in result["error"] - def test_invoke_passes_args_to_driver(self): - """invoke() forwards args to the driver function.""" + def test_call_passes_args_to_driver(self): + """call_contract() forwards args to the driver function.""" received: list = [] def capturing_driver(*args): @@ -202,21 +202,19 @@ class TestCallHandler: return "done" bridge.register("cap", capturing_driver) - driver_fn = resolve("cap") - result = invoke(driver_fn, "cap", ["foo", "bar"]) + result = call_contract("cap", ["foo", "bar"]) assert result["success"] is True assert received == ["foo", "bar"] - def test_invoke_driver_exception_returns_failure(self): - """invoke() returns failure dict when driver raises.""" + def test_call_driver_exception_returns_failure(self): + """call_contract() returns failure dict when driver raises.""" def broken_driver(*args): """Always raises to simulate a broken driver.""" raise RuntimeError("boom") bridge.register("broken", broken_driver) - driver_fn = resolve("broken") - result = invoke(driver_fn, "broken", []) + result = call_contract("broken", []) assert result["success"] is False assert result["error"] is not None assert "boom" in result["error"]