"""Helper script for langgraph_platform_integration.robot tests. Each subcommand is a self-contained check that prints a sentinel on success. Validates the LangGraph Platform RemoteGraph integration per ADR-048. """ from __future__ import annotations import logging import sys from collections.abc import Callable from pathlib import Path # Keep helper stderr focused on assertion failures and tracebacks. logging.disable(logging.CRITICAL) # Ensure local source tree is importable _SRC = str(Path(__file__).resolve().parents[1] / "src") if _SRC not in sys.path: sys.path.insert(0, _SRC) from cleveragents.infrastructure.database.postgresql import ( # noqa: E402 PostgreSQLConnectionConfig, build_postgresql_url, is_postgresql_url, ) from cleveragents.langgraph.remote_graph import ( # noqa: E402 RemoteGraphConfig, RemoteGraphManager, RemoteGraphNotAvailableError, ) # --------------------------------------------------------------------------- # Subcommands # --------------------------------------------------------------------------- def manager_not_configured() -> None: """Verify RemoteGraphManager reports unavailable when no platform URL given.""" manager = RemoteGraphManager() assert not manager.is_available, ( "Expected manager.is_available=False when no platform URL given" ) assert manager.platform_url is None, ( f"Expected platform_url=None, got {manager.platform_url!r}" ) # All operations should raise RemoteGraphNotAvailableError config = RemoteGraphConfig( graph_id="test-actor", platform_url="https://langgraph.example.com", ) try: manager.register_graph(config) raise AssertionError( "Expected RemoteGraphNotAvailableError from register_graph" ) except RemoteGraphNotAvailableError: pass try: manager.list_graphs() raise AssertionError("Expected RemoteGraphNotAvailableError from list_graphs") except RemoteGraphNotAvailableError: pass try: manager.invoke("test-actor", {}) raise AssertionError("Expected RemoteGraphNotAvailableError from invoke") except RemoteGraphNotAvailableError: pass # health_check returns False (no exception) result = manager.health_check() assert result is False, f"Expected health_check=False, got {result!r}" print("remote-graph-manager-not-configured-ok") def manager_configured() -> None: """Verify RemoteGraphManager is available when platform URL is given.""" platform_url = "https://langgraph.example.com" manager = RemoteGraphManager(platform_url=platform_url) assert manager.is_available, ( "Expected manager.is_available=True when platform URL is given" ) assert manager.platform_url == platform_url, ( f"Expected platform_url={platform_url!r}, got {manager.platform_url!r}" ) # Initially no graphs registered graphs = manager.list_graphs() assert graphs == [], f"Expected empty graph list, got {graphs!r}" print("remote-graph-manager-configured-ok") def graph_registration() -> None: """Verify graph registration and listing lifecycle.""" platform_url = "https://langgraph.example.com" manager = RemoteGraphManager(platform_url=platform_url) # Register multiple graphs for graph_id in ("strategy-actor", "execution-actor", "estimation-actor"): config = RemoteGraphConfig( graph_id=graph_id, platform_url=platform_url, ) manager.register_graph(config) # Verify all are listed in sorted order graphs = manager.list_graphs() expected = sorted(["strategy-actor", "execution-actor", "estimation-actor"]) assert graphs == expected, f"Expected {expected!r}, got {graphs!r}" # Verify get_graph_config returns correct config config = manager.get_graph_config("strategy-actor") assert config.graph_id == "strategy-actor", ( f"Expected graph_id='strategy-actor', got {config.graph_id!r}" ) assert config.platform_url == platform_url, ( f"Expected platform_url={platform_url!r}, got {config.platform_url!r}" ) # Verify unregister removes the graph manager.unregister_graph("estimation-actor") graphs_after = manager.list_graphs() assert "estimation-actor" not in graphs_after, ( f"Expected 'estimation-actor' to be removed, but found in {graphs_after!r}" ) assert len(graphs_after) == 2, ( f"Expected 2 graphs after unregister, got {len(graphs_after)}" ) # Verify get_graph_config raises for unknown graph try: manager.get_graph_config("unknown-actor") raise AssertionError("Expected KeyError for unknown graph") except KeyError: pass # Verify unregister raises for unknown graph try: manager.unregister_graph("ghost-actor") raise AssertionError("Expected KeyError for unregistered graph") except KeyError: pass print("remote-graph-registration-ok") def invoke_stub() -> None: """Verify invoke raises RemoteGraphNotAvailableError for stub implementation.""" platform_url = "https://langgraph.example.com" manager = RemoteGraphManager(platform_url=platform_url) # Register a graph config = RemoteGraphConfig( graph_id="strategy-actor", platform_url=platform_url, ) manager.register_graph(config) # Invoke should raise RemoteGraphNotAvailableError (stub) try: manager.invoke("strategy-actor", {"messages": []}) raise AssertionError("Expected RemoteGraphNotAvailableError from invoke") except RemoteGraphNotAvailableError as exc: assert "strategy-actor" in str(exc.details), ( f"Expected graph_id in error details, got {exc.details!r}" ) # Invoke unregistered graph should raise KeyError try: manager.invoke("missing-actor", {}) raise AssertionError("Expected KeyError for unregistered graph") except KeyError: pass # Invoke with thread_id should also raise RemoteGraphNotAvailableError try: manager.invoke("strategy-actor", {}, thread_id="thread-001") raise AssertionError( "Expected RemoteGraphNotAvailableError from invoke with thread_id" ) except RemoteGraphNotAvailableError as exc: assert exc.details.get("thread_id") == "thread-001", ( f"Expected thread_id in error details, got {exc.details!r}" ) print("remote-graph-invoke-stub-ok") def health_check() -> None: """Verify health_check returns False for stub implementation.""" # Not configured manager_unconfigured = RemoteGraphManager() result = manager_unconfigured.health_check() assert result is False, ( f"Expected health_check=False for unconfigured manager, got {result!r}" ) # Configured (stub still returns False) manager_configured = RemoteGraphManager( platform_url="https://langgraph.example.com" ) result = manager_configured.health_check() assert result is False, ( f"Expected health_check=False for stub configured manager, got {result!r}" ) print("remote-graph-health-check-ok") def postgresql_config() -> None: """Verify PostgreSQLConnectionConfig validation and URL building.""" # Valid config config = PostgreSQLConnectionConfig( host="db.example.com", database="cleveragents", username="app", password="secret", ) assert config.host == "db.example.com", ( f"Expected host='db.example.com', got {config.host!r}" ) assert config.database == "cleveragents", ( f"Expected database='cleveragents', got {config.database!r}" ) assert config.username == "app", f"Expected username='app', got {config.username!r}" assert config.port == 5432, f"Expected port=5432, got {config.port}" assert config.ssl_mode == "prefer", ( f"Expected ssl_mode='prefer', got {config.ssl_mode!r}" ) assert config.pool_size == 5, f"Expected pool_size=5, got {config.pool_size}" assert config.max_overflow == 10, ( f"Expected max_overflow=10, got {config.max_overflow}" ) # Async URL async_url = config.to_url(async_driver=True) assert async_url.startswith("postgresql+asyncpg://"), ( f"Expected async URL to start with 'postgresql+asyncpg://', got {async_url!r}" ) assert "db.example.com" in async_url, f"Expected host in URL, got {async_url!r}" assert "cleveragents" in async_url, f"Expected database in URL, got {async_url!r}" # Sync URL sync_url = config.to_url(async_driver=False) assert sync_url.startswith("postgresql://"), ( f"Expected sync URL to start with 'postgresql://', got {sync_url!r}" ) # Custom port config_custom_port = PostgreSQLConnectionConfig( host="db.example.com", database="cleveragents", username="app", password="secret", port=5433, ) assert config_custom_port.port == 5433, ( f"Expected port=5433, got {config_custom_port.port}" ) # build_postgresql_url helper url = build_postgresql_url( host="db.example.com", database="mydb", username="user", password="pass", ) assert url.startswith("postgresql+asyncpg://"), ( f"Expected async URL from build_postgresql_url, got {url!r}" ) assert "db.example.com" in url, f"Expected host in URL, got {url!r}" assert "mydb" in url, f"Expected database in URL, got {url!r}" # Validation errors try: PostgreSQLConnectionConfig( host="", database="mydb", username="user", password="pass", ) raise AssertionError("Expected validation error for empty host") except AssertionError: raise except Exception: pass try: PostgreSQLConnectionConfig( host="db.example.com", database="mydb", username="user", password="pass", port=0, ) raise AssertionError("Expected validation error for port=0") except AssertionError: raise except Exception: pass try: PostgreSQLConnectionConfig( host="db.example.com", database="mydb", username="user", password="pass", pool_size=0, ) raise AssertionError("Expected validation error for pool_size=0") except AssertionError: raise except Exception: pass print("postgresql-config-ok") def postgresql_url_detection() -> None: """Verify is_postgresql_url correctly identifies PostgreSQL URLs.""" # PostgreSQL URLs assert is_postgresql_url("postgresql://user:pass@host/db") is True, ( "Expected True for postgresql:// URL" ) assert is_postgresql_url("postgresql+asyncpg://user:pass@host/db") is True, ( "Expected True for postgresql+asyncpg:// URL" ) assert is_postgresql_url("POSTGRESQL://user:pass@host/db") is True, ( "Expected True for uppercase POSTGRESQL:// URL" ) # Non-PostgreSQL URLs assert is_postgresql_url("sqlite:///path/to/db.sqlite") is False, ( "Expected False for sqlite:// URL" ) assert is_postgresql_url("mysql://user:pass@host/db") is False, ( "Expected False for mysql:// URL" ) assert is_postgresql_url("") is False, "Expected False for empty string" print("postgresql-url-detection-ok") def module_exports() -> None: """Verify RemoteGraph classes are exported from langgraph package.""" from cleveragents.infrastructure.database import build_postgresql_url as bpu from cleveragents.infrastructure.database import is_postgresql_url as ipgu from cleveragents.langgraph import RemoteGraphConfig as RGC from cleveragents.langgraph import RemoteGraphManager as RGM from cleveragents.langgraph import RemoteGraphNotAvailableError as RGNAE # Verify classes are importable and functional manager = RGM() assert not manager.is_available, "Expected manager.is_available=False" config = RGC( graph_id="test", platform_url="https://langgraph.example.com", ) assert config.graph_id == "test", ( f"Expected graph_id='test', got {config.graph_id!r}" ) # Verify error class err = RGNAE("test error") assert str(err) == "test error", f"Expected 'test error', got {str(err)!r}" # Verify database exports assert callable(bpu), "Expected build_postgresql_url to be callable" assert callable(ipgu), "Expected is_postgresql_url to be callable" print("module-exports-ok") # --------------------------------------------------------------------------- # Dispatcher # --------------------------------------------------------------------------- _COMMANDS: dict[str, Callable[[], None]] = { "manager-not-configured": manager_not_configured, "manager-configured": manager_configured, "graph-registration": graph_registration, "invoke-stub": invoke_stub, "health-check": health_check, "postgresql-config": postgresql_config, "postgresql-url-detection": postgresql_url_detection, "module-exports": module_exports, } if __name__ == "__main__": if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS: print(f"Usage: {sys.argv[0]} <{'|'.join(_COMMANDS)}>", file=sys.stderr) sys.exit(2) fn = _COMMANDS[sys.argv[1]] fn()