093d993611
Applied ruff format auto-fix to resolve line-length formatting violations in: - features/steps/langgraph_platform_remote_graph_steps.py - robot/helper_langgraph_platform_integration.py These files had multi-line assert statements that ruff format collapses to single lines when they fit within the line length limit. ISSUES CLOSED: #693
404 lines
13 KiB
Python
404 lines
13 KiB
Python
"""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()
|