Files
lda-wf/tests/wf_server/test_cli.py
T

646 lines
20 KiB
Python

from __future__ import annotations
import json
from typer.testing import CliRunner
from wf_server.cli import app
def test_rpc_server_cli_help_mentions_store_root() -> None:
result = CliRunner().invoke(app, ["--help"])
assert result.exit_code == 0
assert "--store-root" in result.output
assert "--mcp-config" in result.output
assert "--host" in result.output
assert "--port" in result.output
def test_rpc_server_main_loads_dotenv_before_invoking_app(monkeypatch) -> None:
import wf_server.cli as mod
calls: list[str] = []
monkeypatch.setattr(mod, "load_dotenv", lambda: calls.append("dotenv"))
monkeypatch.setattr(mod, "app", lambda: calls.append("app"))
mod.main()
assert calls == ["dotenv", "app"]
def test_rpc_server_cli_accepts_config_file(tmp_path) -> None:
config_path = tmp_path / "wf.json"
config_path.write_text(
json.dumps(
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": ".wf_store"},
"transports": [
{
"kind": "rpc_http",
"host": "127.0.0.1",
"port": 9999,
"path": "/rpc",
}
],
},
}
),
encoding="utf-8",
)
result = CliRunner().invoke(app, ["--config", str(config_path), "--help"])
assert result.exit_code == 0
assert "--config" in result.output
def test_rpc_server_cli_uses_configured_store_and_transport(
monkeypatch, tmp_path
) -> None:
config_path = tmp_path / "wf.json"
config_path.write_text(
json.dumps(
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": ".wf_store"},
"transports": [
{
"kind": "rpc_http",
"host": "127.0.0.2",
"port": 9999,
"path": "/workflow-rpc",
}
],
},
}
),
encoding="utf-8",
)
captured: dict[str, object] = {}
def fake_build_server(config, *, drafts=False):
captured["store_root"] = config.server.store.root
captured["drafts"] = drafts
return object()
def fake_create_rpc_app(server, *, rpc_path="/rpc", drafts=False, lifespan=None):
captured["server"] = server
captured["rpc_path"] = rpc_path
captured["drafts"] = drafts
return object()
def fake_uvicorn_run(app_obj, *, host, port, access_log):
captured["app"] = app_obj
captured["host"] = host
captured["port"] = port
captured["access_log"] = access_log
monkeypatch.setattr(
"wf_server.cli.build_workflow_server_from_workflow_config",
fake_build_server,
)
monkeypatch.setattr("wf_server.cli.create_rpc_app", fake_create_rpc_app)
monkeypatch.setattr("wf_server.cli.uvicorn.run", fake_uvicorn_run)
result = CliRunner().invoke(app, ["--config", str(config_path)])
assert result.exit_code == 0, result.output
assert captured["store_root"] == (tmp_path / ".wf_store").resolve()
assert captured["rpc_path"] == "/workflow-rpc"
assert captured["drafts"] is True
assert captured["host"] == "127.0.0.2"
assert captured["port"] == 9999
assert captured["access_log"] is False
def test_rpc_server_cli_uses_mcp_config_server(monkeypatch, tmp_path) -> None:
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
json.dumps(
{
"store_root": str(tmp_path / "store"),
"connections": [],
}
),
encoding="utf-8",
)
captured: dict[str, object] = {}
def fake_build_mcp_server(path):
captured["mcp_config_path"] = path
return object()
def fake_create_rpc_app(server, *, rpc_path="/rpc", drafts=False, lifespan=None):
captured["server"] = server
captured["rpc_path"] = rpc_path
captured["drafts"] = drafts
return object()
def fake_uvicorn_run(app_obj, *, host, port, access_log):
captured["app"] = app_obj
captured["host"] = host
captured["port"] = port
captured["access_log"] = access_log
monkeypatch.setattr(
"wf_server.cli.build_workflow_server_from_legacy_mcp_config",
fake_build_mcp_server,
)
monkeypatch.setattr("wf_server.cli.create_rpc_app", fake_create_rpc_app)
monkeypatch.setattr("wf_server.cli.uvicorn.run", fake_uvicorn_run)
result = CliRunner().invoke(
app,
[
"--mcp-config",
str(config_path),
"--host",
"127.0.0.9",
"--port",
"9988",
],
)
assert result.exit_code == 0, result.output
assert captured["mcp_config_path"] == config_path
assert captured["server"] is not None
assert captured["rpc_path"] == "/rpc"
assert captured["host"] == "127.0.0.9"
assert captured["port"] == 9988
assert captured["access_log"] is False
def test_rpc_server_cli_rejects_mcp_config_with_store_root(tmp_path) -> None:
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
json.dumps({"store_root": str(tmp_path / "store"), "connections": []}),
encoding="utf-8",
)
result = CliRunner().invoke(
app,
[
"--mcp-config",
str(config_path),
"--store-root",
str(tmp_path / "other"),
],
)
assert result.exit_code != 0
assert "--mcp-config cannot be combined with --store-root" in result.output
def test_rpc_server_cli_mcp_config_builds_registry_capable_server(
monkeypatch, tmp_path
) -> None:
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
json.dumps(
{
"store_root": str(tmp_path / "store"),
"connections": [],
}
),
encoding="utf-8",
)
captured: dict[str, object] = {}
def fake_create_rpc_app(server, *, rpc_path="/rpc", drafts=False, lifespan=None):
captured["source_registry_admin"] = server.source_registry_admin
captured["rpc_path"] = rpc_path
captured["drafts"] = drafts
return object()
def fake_uvicorn_run(app_obj, *, host, port, access_log):
captured["host"] = host
captured["port"] = port
monkeypatch.setattr("wf_server.cli.create_rpc_app", fake_create_rpc_app)
monkeypatch.setattr("wf_server.cli.uvicorn.run", fake_uvicorn_run)
result = CliRunner().invoke(app, ["--mcp-config", str(config_path)])
assert result.exit_code == 0, result.output
assert captured["source_registry_admin"] is not None
assert captured["rpc_path"] == "/rpc"
assert captured["host"] == "127.0.0.1"
assert captured["port"] == 8765
def test_rpc_server_cli_mcp_config_with_config_uses_transport_settings(
monkeypatch, tmp_path
) -> None:
mcp_config_path = tmp_path / "wf_mcp.config.json"
mcp_config_path.write_text(
json.dumps(
{
"store_root": str(tmp_path / "store"),
"connections": [],
}
),
encoding="utf-8",
)
neutral_config_path = tmp_path / "wf.json"
neutral_config_path.write_text(
json.dumps(
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": ".wf_store"},
"transports": [
{
"kind": "rpc_http",
"host": "127.0.0.3",
"port": 7777,
"path": "/custom-rpc",
}
],
},
}
),
encoding="utf-8",
)
captured: dict[str, object] = {}
def fake_create_rpc_app(server, *, rpc_path="/rpc", drafts=False, lifespan=None):
captured["server"] = server
captured["rpc_path"] = rpc_path
captured["drafts"] = drafts
return object()
def fake_uvicorn_run(app_obj, *, host, port, access_log):
captured["host"] = host
captured["port"] = port
captured["access_log"] = access_log
monkeypatch.setattr("wf_server.cli.create_rpc_app", fake_create_rpc_app)
monkeypatch.setattr("wf_server.cli.uvicorn.run", fake_uvicorn_run)
result = CliRunner().invoke(
app,
[
"--mcp-config",
str(mcp_config_path),
"--config",
str(neutral_config_path),
],
)
assert result.exit_code == 0, result.output
assert captured["server"] is not None
assert captured["rpc_path"] == "/custom-rpc"
assert captured["host"] == "127.0.0.3"
assert captured["port"] == 7777
assert captured["access_log"] is False
def test_rpc_server_cli_config_with_mcp_source_uses_mcp_builder(
monkeypatch, tmp_path
) -> None:
captured = {}
def fake_build_from_workflow_config(config, *, drafts=False):
captured["source_kinds"] = [source.kind for source in config.server.sources]
captured["build_drafts"] = drafts
return object()
def fake_create_rpc_app(server, *, rpc_path="/rpc", drafts=False, lifespan=None):
captured["server"] = server
captured["rpc_path"] = rpc_path
captured["drafts"] = drafts
return "app"
def fake_run(app, *, host, port, access_log):
captured["run"] = {
"app": app,
"host": host,
"port": port,
"access_log": access_log,
}
monkeypatch.setattr(
"wf_server.cli.build_workflow_server_from_workflow_config",
fake_build_from_workflow_config,
)
monkeypatch.setattr("wf_server.cli.create_rpc_app", fake_create_rpc_app)
monkeypatch.setattr("wf_server.cli.uvicorn.run", fake_run)
config_path = tmp_path / "wf.json"
config_path.write_text(
"""
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": ".wf_store"},
"transports": [{"kind": "rpc_http", "host": "127.0.0.1", "port": 8765}],
"sources": [
{
"kind": "mcp",
"id": "everything.default",
"provider": "everything",
"account": "default",
"transport": {"kind": "stdio", "command": "uvx"}
}
]
}
}
""",
encoding="utf-8",
)
result = CliRunner().invoke(app, ["--config", str(config_path)])
assert result.exit_code == 0, result.output
assert captured["source_kinds"] == ["mcp"]
assert captured["run"]["app"] == "app"
def test_rpc_server_cli_rejects_store_root_with_mcp_source_config(tmp_path) -> None:
config_path = tmp_path / "wf.json"
config_path.write_text(
json.dumps(
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": ".wf_store"},
"sources": [
{
"kind": "mcp",
"id": "everything.default",
"provider": "everything",
"account": "default",
"transport": {"kind": "stdio", "command": "uvx"},
}
],
},
}
),
encoding="utf-8",
)
result = CliRunner().invoke(
app,
["--config", str(config_path), "--store-root", str(tmp_path / "override")],
)
assert result.exit_code != 0
assert "--store-root cannot override MCP-source config" in result.output
def test_rpc_server_cli_config_uses_workflow_store_override(
tmp_path,
monkeypatch,
) -> None:
config_path = tmp_path / "wf.json"
config_path.write_text(
json.dumps(
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": ".default"},
"stores": {
"workflow": {
"kind": "filesystem",
"root": ".workflow",
}
},
},
}
),
encoding="utf-8",
)
captured: dict[str, object] = {}
def fake_build_server(config, *, drafts=False):
captured["workflow_store_root"] = config.server.workflow_store.root
captured["build_drafts"] = drafts
return object()
def fake_create_rpc_app(server, *, rpc_path="/rpc", drafts=False, lifespan=None):
captured["server"] = server
captured["rpc_path"] = rpc_path
captured["drafts"] = drafts
return object()
def fake_uvicorn_run(app_obj, *, host, port, access_log):
captured["app"] = app_obj
monkeypatch.setattr(
"wf_server.cli.build_workflow_server_from_workflow_config",
fake_build_server,
)
monkeypatch.setattr("wf_server.cli.create_rpc_app", fake_create_rpc_app)
monkeypatch.setattr("wf_server.cli.uvicorn.run", fake_uvicorn_run)
result = CliRunner().invoke(app, ["--config", str(config_path)])
assert result.exit_code == 0, result.output
assert captured["workflow_store_root"] == (tmp_path / ".workflow").resolve()
def test_rpc_server_cli_help_mentions_enable_scheduler() -> None:
result = CliRunner().invoke(app, ["--help"])
assert result.exit_code == 0
assert "--enable-scheduler" in result.output
def test_rpc_server_cli_scheduler_disabled_by_default(monkeypatch, tmp_path) -> None:
captured: dict[str, object] = {}
def fake_create_rpc_app(server, *, rpc_path="/rpc", drafts=False, lifespan=None):
captured["lifespan"] = lifespan
return object()
def fake_uvicorn_run(app_obj, *, host, port, access_log):
captured["app"] = app_obj
monkeypatch.setattr("wf_server.cli.create_rpc_app", fake_create_rpc_app)
monkeypatch.setattr("wf_server.cli.uvicorn.run", fake_uvicorn_run)
result = CliRunner().invoke(app, ["--store-root", str(tmp_path / "store")])
assert result.exit_code == 0, result.output
assert captured["app"] is not None
assert captured["lifespan"] is None
def test_rpc_server_cli_enable_scheduler_with_store_root_builds_app(
monkeypatch, tmp_path
) -> None:
captured: dict[str, object] = {}
def fake_create_rpc_app(server, *, rpc_path="/rpc", drafts=False, lifespan=None):
captured["server"] = server
captured["lifespan"] = lifespan
return object()
def fake_uvicorn_run(app_obj, *, host, port, access_log):
captured["app"] = app_obj
monkeypatch.setattr("wf_server.cli.create_rpc_app", fake_create_rpc_app)
monkeypatch.setattr("wf_server.cli.uvicorn.run", fake_uvicorn_run)
result = CliRunner().invoke(
app, ["--store-root", str(tmp_path / "store"), "--enable-scheduler"]
)
assert result.exit_code == 0, result.output
assert captured["server"] is not None
assert captured["app"] is not None
assert captured["lifespan"] is not None
def test_rpc_server_cli_config_scheduler_section_enables_without_flag(
monkeypatch, tmp_path
) -> None:
config_path = tmp_path / "wf.json"
config_path.write_text(
json.dumps(
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": ".wf_store"},
"scheduler": {"enabled": True},
},
}
),
encoding="utf-8",
)
captured: dict[str, object] = {}
def fake_create_rpc_app(server, *, rpc_path="/rpc", drafts=False, lifespan=None):
captured["lifespan"] = lifespan
return object()
def fake_uvicorn_run(app_obj, *, host, port, access_log):
captured["app"] = app_obj
monkeypatch.setattr("wf_server.cli.create_rpc_app", fake_create_rpc_app)
monkeypatch.setattr("wf_server.cli.uvicorn.run", fake_uvicorn_run)
result = CliRunner().invoke(app, ["--config", str(config_path)])
assert result.exit_code == 0, result.output
assert captured["lifespan"] is not None
def test_rpc_server_cli_flag_overrides_disabled_config_scheduler(
monkeypatch, tmp_path
) -> None:
config_path = tmp_path / "wf.json"
config_path.write_text(
json.dumps(
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": ".wf_store"},
"scheduler": {"enabled": False},
},
}
),
encoding="utf-8",
)
captured: dict[str, object] = {}
def fake_create_rpc_app(server, *, rpc_path="/rpc", drafts=False, lifespan=None):
captured["lifespan"] = lifespan
return object()
def fake_uvicorn_run(app_obj, *, host, port, access_log):
captured["app"] = app_obj
monkeypatch.setattr("wf_server.cli.create_rpc_app", fake_create_rpc_app)
monkeypatch.setattr("wf_server.cli.uvicorn.run", fake_uvicorn_run)
result = CliRunner().invoke(
app, ["--config", str(config_path), "--enable-scheduler"]
)
assert result.exit_code == 0, result.output
assert captured["lifespan"] is not None
def test_rpc_server_cli_enable_scheduler_rejects_mcp_backed_server(
monkeypatch, tmp_path
) -> None:
config_path = tmp_path / "wf_mcp.config.json"
config_path.write_text(
json.dumps({"store_root": str(tmp_path / "store"), "connections": []}),
encoding="utf-8",
)
def fake_build_mcp_server(path):
return object()
monkeypatch.setattr(
"wf_server.cli.build_workflow_server_from_legacy_mcp_config",
fake_build_mcp_server,
)
result = CliRunner().invoke(
app,
[
"--mcp-config",
str(config_path),
"--enable-scheduler",
],
)
assert result.exit_code != 0
assert "requires a local/static server" in result.output
def test_rpc_server_cli_config_mcp_sources_reject_scheduler(
monkeypatch, tmp_path
) -> None:
config_path = tmp_path / "wf.json"
config_path.write_text(
json.dumps(
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": ".wf_store"},
"sources": [
{
"kind": "mcp",
"id": "everything.default",
"provider": "everything",
"account": "default",
"transport": {
"kind": "stdio",
"command": "uvx",
"args": ["mcp-server-everything"],
},
}
],
"scheduler": {"enabled": True},
},
}
),
encoding="utf-8",
)
def fake_build_server(config, *, drafts=False):
return object()
def fake_create_rpc_app(server, *, rpc_path="/rpc", drafts=False, lifespan=None):
return object()
monkeypatch.setattr(
"wf_server.cli.build_workflow_server_from_workflow_config",
fake_build_server,
)
monkeypatch.setattr("wf_server.cli.create_rpc_app", fake_create_rpc_app)
result = CliRunner().invoke(app, ["--config", str(config_path)])
assert result.exit_code != 0
assert "requires a local/static server" in result.output