Files
lda-wf/tests/wf_config/test_config_models.py
T

541 lines
18 KiB
Python

from __future__ import annotations
import json
from pathlib import Path
import pytest
from pydantic import ValidationError
from wf_config import (
FilesystemStoreConfig,
HttpSourceTransportConfig,
LocalTargetConfig,
McpSourceConfig,
RpcHttpTargetConfig,
RpcHttpTransportConfig,
SchedulerConfig,
StdioSourceTransportConfig,
StdlibSourceConfig,
WorkflowConfigFile,
load_workflow_config,
)
def test_workflow_config_parses_local_target_and_filesystem_store() -> None:
config = WorkflowConfigFile.model_validate(
{
"version": 1,
"client": {"target": {"kind": "local"}},
"server": {
"store": {"kind": "filesystem", "root": ".wf_store"},
"sources": [{"kind": "stdlib", "id": "wf.std"}],
},
}
)
assert isinstance(config.client.target, LocalTargetConfig)
assert isinstance(config.server.store, FilesystemStoreConfig)
assert config.server.store.root.as_posix() == ".wf_store"
assert isinstance(config.server.sources[0], StdlibSourceConfig)
assert config.server.sources[0].id == "wf.std"
def test_workflow_config_parses_rpc_http_target_and_transport() -> None:
config = WorkflowConfigFile.model_validate(
{
"version": 1,
"client": {
"target": {
"kind": "rpc_http",
"url": "http://127.0.0.1:8765/rpc",
"timeout_seconds": 12,
}
},
"server": {
"transports": [
{
"kind": "rpc_http",
"host": "0.0.0.0",
"port": 9999,
"path": "/rpc",
}
]
},
}
)
assert isinstance(config.client.target, RpcHttpTargetConfig)
assert str(config.client.target.url) == "http://127.0.0.1:8765/rpc"
assert config.client.target.timeout_seconds == 12
assert isinstance(config.server.transports[0], RpcHttpTransportConfig)
assert config.server.transports[0].host == "0.0.0.0"
assert config.server.transports[0].port == 9999
def test_workflow_config_rejects_duplicate_source_ids() -> None:
with pytest.raises(ValidationError, match="duplicate source id"):
WorkflowConfigFile.model_validate(
{
"version": 1,
"server": {
"sources": [
{"kind": "stdlib", "id": "wf.std"},
{"kind": "stdlib", "id": "wf.std"},
]
},
}
)
def test_workflow_config_rejects_unknown_target_kind() -> None:
with pytest.raises(ValidationError):
WorkflowConfigFile.model_validate(
{
"version": 1,
"client": {"target": {"kind": "mcp"}},
}
)
def test_workflow_config_rejects_invalid_rpc_http_url() -> None:
with pytest.raises(ValidationError):
WorkflowConfigFile.model_validate(
{
"version": 1,
"client": {"target": {"kind": "rpc_http", "url": "not-a-url"}},
}
)
def test_workflow_config_rejects_unwired_stdlib_source_id() -> None:
with pytest.raises(ValidationError):
WorkflowConfigFile.model_validate(
{
"version": 1,
"server": {"sources": [{"kind": "stdlib", "id": "custom.id"}]},
}
)
def test_load_workflow_config_resolves_filesystem_store_relative_to_config(
tmp_path: Path,
) -> None:
config_path = tmp_path / "wf.json"
config_path.write_text(
json.dumps(
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": ".wf_store"},
},
}
),
encoding="utf-8",
)
config = load_workflow_config(config_path)
assert config.server.store.root == (tmp_path / ".wf_store").resolve()
def test_load_workflow_config_preserves_absolute_filesystem_store(
tmp_path: Path,
) -> None:
absolute_root = (tmp_path / "absolute-store").resolve()
config_path = tmp_path / "wf.json"
config_path.write_text(
json.dumps(
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": str(absolute_root)},
},
}
),
encoding="utf-8",
)
config = load_workflow_config(config_path)
assert config.server.store.root == absolute_root
def test_workflow_config_parses_mcp_stdio_source() -> None:
config = WorkflowConfigFile.model_validate(
{
"version": 1,
"server": {
"sources": [
{
"kind": "mcp",
"id": "everything.default",
"enabled": True,
"provider": "everything",
"account": "default",
"profile": "dev",
"ownership": "seed",
"transport": {
"kind": "stdio",
"command": "uvx",
"args": ["mcp-server-everything"],
"env": {"DEBUG": "1"},
},
"auth_ref": "auth.everything.default",
"metadata": {"description": "Everything test server"},
}
]
},
}
)
source = config.server.sources[0]
assert isinstance(source, McpSourceConfig)
assert source.id == "everything.default"
assert source.enabled is True
assert source.provider == "everything"
assert source.account == "default"
assert source.profile == "dev"
assert source.ownership == "seed"
assert isinstance(source.transport, StdioSourceTransportConfig)
assert source.transport.command == "uvx"
assert source.transport.args == ("mcp-server-everything",)
assert source.transport.env == {"DEBUG": "1"}
assert source.auth_ref == "auth.everything.default"
assert source.metadata["description"] == "Everything test server"
def test_workflow_config_parses_mcp_http_source() -> None:
config = WorkflowConfigFile.model_validate(
{
"version": 1,
"server": {
"sources": [
{
"kind": "mcp",
"id": "context7.default",
"provider": "context7",
"account": "default",
"transport": {
"kind": "http",
"url": "http://127.0.0.1:3000/mcp",
"headers": {"X-Test": "yes"},
},
}
]
},
}
)
source = config.server.sources[0]
assert isinstance(source, McpSourceConfig)
assert source.enabled is True
assert source.ownership == "locked"
assert isinstance(source.transport, HttpSourceTransportConfig)
assert str(source.transport.url) == "http://127.0.0.1:3000/mcp"
assert source.transport.headers == {"X-Test": "yes"}
def test_workflow_config_rejects_mcp_source_without_provider_account_shape() -> None:
with pytest.raises(ValidationError, match="source id must look like"):
WorkflowConfigFile.model_validate(
{
"version": 1,
"server": {
"sources": [
{
"kind": "mcp",
"id": "everything",
"provider": "everything",
"account": "default",
"transport": {"kind": "stdio", "command": "uvx"},
}
]
},
}
)
def test_workflow_config_rejects_unsafe_mcp_source_id() -> None:
with pytest.raises(ValidationError, match="source id must start"):
WorkflowConfigFile.model_validate(
{
"version": 1,
"server": {
"sources": [
{
"kind": "mcp",
"id": ".hidden.default",
"provider": "hidden",
"account": "default",
"transport": {"kind": "stdio", "command": "uvx"},
}
]
},
}
)
def test_workflow_config_parses_role_specific_store_overrides() -> None:
config = WorkflowConfigFile.model_validate(
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": ".wf_store"},
"stores": {
"workflow": {"kind": "filesystem", "root": ".wf_workflow"},
"auth": {"kind": "filesystem", "root": ".wf_auth"},
"source_registry": {
"kind": "filesystem",
"root": ".wf_sources",
},
"catalog_cache": {
"kind": "filesystem",
"root": ".wf_catalog",
},
},
},
}
)
assert isinstance(config.server.stores.workflow, FilesystemStoreConfig)
assert config.server.stores.workflow.root.as_posix() == ".wf_workflow"
assert isinstance(config.server.stores.auth, FilesystemStoreConfig)
assert config.server.stores.auth.root.as_posix() == ".wf_auth"
assert isinstance(config.server.stores.source_registry, FilesystemStoreConfig)
assert config.server.stores.source_registry.root.as_posix() == ".wf_sources"
assert isinstance(config.server.stores.catalog_cache, FilesystemStoreConfig)
assert config.server.stores.catalog_cache.root.as_posix() == ".wf_catalog"
def test_workflow_config_rejects_duplicate_source_ids_across_kinds() -> None:
with pytest.raises(ValidationError, match="duplicate source id"):
WorkflowConfigFile.model_validate(
{
"version": 1,
"server": {
"sources": [
{"kind": "stdlib", "id": "wf.std"},
{
"kind": "mcp",
"id": "wf.std",
"provider": "wf",
"account": "std",
"transport": {"kind": "stdio", "command": "uvx"},
},
]
},
}
)
def test_load_workflow_config_resolves_role_store_paths_relative_to_config(
tmp_path: Path,
) -> None:
config_path = tmp_path / "nested" / "wf.json"
config_path.parent.mkdir()
config_path.write_text(
json.dumps(
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": ".default_store"},
"stores": {
"workflow": {
"kind": "filesystem",
"root": ".workflow_store",
},
"auth": {
"kind": "filesystem",
"root": ".auth_store",
},
"source_registry": {
"kind": "filesystem",
"root": ".source_store",
},
"catalog_cache": {
"kind": "filesystem",
"root": ".catalog_store",
},
},
},
}
),
encoding="utf-8",
)
config = load_workflow_config(config_path)
assert config.server.store.root == (config_path.parent / ".default_store").resolve()
assert config.server.stores.workflow is not None
assert (
config.server.stores.workflow.root
== (config_path.parent / ".workflow_store").resolve()
)
assert config.server.stores.auth is not None
assert (
config.server.stores.auth.root == (config_path.parent / ".auth_store").resolve()
)
assert config.server.stores.source_registry is not None
assert (
config.server.stores.source_registry.root
== (config_path.parent / ".source_store").resolve()
)
assert config.server.stores.catalog_cache is not None
assert (
config.server.stores.catalog_cache.root
== (config_path.parent / ".catalog_store").resolve()
)
def test_workflow_config_parses_python_source() -> None:
config = WorkflowConfigFile.model_validate(
{
"version": 1,
"server": {
"sources": [
{
"kind": "python",
"id": "local.ops",
"module": "tests.fixtures.python_source_ops",
"registry": "registry",
}
]
},
}
)
source = config.server.sources[0]
assert source.kind == "python"
assert source.id == "local.ops"
assert source.path == Path(".")
assert source.module == "tests.fixtures.python_source_ops"
assert source.registry == "registry"
def test_load_workflow_config_resolves_python_source_path(tmp_path: Path) -> None:
config_path = tmp_path / "wf.json"
config_path.write_text(
json.dumps(
{
"version": 1,
"server": {
"sources": [
{
"kind": "python",
"id": "local.ops",
"path": "src",
"module": "project_ops",
}
]
},
}
),
encoding="utf-8",
)
config = load_workflow_config(config_path)
source = config.server.sources[0]
assert source.kind == "python"
assert source.path == (tmp_path / "src").resolve()
def test_server_config_resolves_missing_role_stores_to_default_store() -> None:
config = WorkflowConfigFile.model_validate(
{
"version": 1,
"server": {
"store": {"kind": "filesystem", "root": ".default"},
"stores": {
"auth": {"kind": "filesystem", "root": ".auth"},
},
},
}
)
assert config.server.workflow_store.root.as_posix() == ".default"
assert config.server.auth_store.root.as_posix() == ".auth"
assert config.server.source_registry_store.root.as_posix() == ".default"
assert config.server.catalog_cache_store.root.as_posix() == ".default"
def test_workflow_config_parses_oauth_provider_profile() -> None:
config = WorkflowConfigFile.model_validate(
{
"auth": {
"providers": {
"google": {
"kind": "oauth_authorization_code_pkce",
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"client_id_env": "GOOGLE_OAUTH_CLIENT_ID",
"client_secret_env": "GOOGLE_OAUTH_CLIENT_SECRET",
"scopes": [
"https://www.googleapis.com/auth/drive.readonly",
],
"extra_authorize_params": {
"access_type": "offline",
"prompt": "consent",
},
}
}
}
}
)
provider = config.auth.providers["google"]
assert provider.kind == "oauth_authorization_code_pkce"
assert provider.client_id_env == "GOOGLE_OAUTH_CLIENT_ID"
assert provider.scopes == ("https://www.googleapis.com/auth/drive.readonly",)
assert provider.extra_authorize_params == {
"access_type": "offline",
"prompt": "consent",
}
def test_server_config_scheduler_absent_is_none() -> None:
config = WorkflowConfigFile.model_validate({"version": 1})
assert config.server.scheduler is None
def test_server_config_parses_scheduler_section_with_values() -> None:
config = WorkflowConfigFile.model_validate(
{
"version": 1,
"server": {
"scheduler": {
"enabled": True,
"poll_interval_s": 2.5,
"max_concurrent_runs": 8,
"drain_grace_s": 60.0,
},
},
}
)
assert isinstance(config.server.scheduler, SchedulerConfig)
assert config.server.scheduler.enabled is True
assert config.server.scheduler.poll_interval_s == 2.5
assert config.server.scheduler.max_concurrent_runs == 8
assert config.server.scheduler.drain_grace_s == 60.0
def test_server_config_scheduler_defaults_to_disabled() -> None:
config = WorkflowConfigFile.model_validate(
{"version": 1, "server": {"scheduler": {}}}
)
assert config.server.scheduler is not None
assert config.server.scheduler.enabled is False
assert config.server.scheduler.poll_interval_s == 1.0
assert config.server.scheduler.max_concurrent_runs == 4
assert config.server.scheduler.drain_grace_s == 30.0
def test_server_config_rejects_unknown_scheduler_key() -> None:
with pytest.raises(ValidationError):
WorkflowConfigFile.model_validate(
{"version": 1, "server": {"scheduler": {"bogus_key": 1}}}
)