fmt, fix
This commit is contained in:
@@ -145,7 +145,9 @@ def challenge_report_schema_errors(report: dict[str, Any]) -> list[str]:
|
|||||||
read = report.get("read")
|
read = report.get("read")
|
||||||
if isinstance(read, dict):
|
if isinstance(read, dict):
|
||||||
missing_read = sorted(CHALLENGE_REPORT_READ_FIELDS.difference(read))
|
missing_read = sorted(CHALLENGE_REPORT_READ_FIELDS.difference(read))
|
||||||
errors.extend(f"missing challenge_report.read.{field}" for field in missing_read)
|
errors.extend(
|
||||||
|
f"missing challenge_report.read.{field}" for field in missing_read
|
||||||
|
)
|
||||||
for field in CHALLENGE_REPORT_READ_FIELDS.intersection(read):
|
for field in CHALLENGE_REPORT_READ_FIELDS.intersection(read):
|
||||||
if not isinstance(read[field], bool):
|
if not isinstance(read[field], bool):
|
||||||
errors.append(f"challenge_report.read.{field} must be boolean")
|
errors.append(f"challenge_report.read.{field} must be boolean")
|
||||||
@@ -156,8 +158,7 @@ def challenge_report_schema_errors(report: dict[str, Any]) -> list[str]:
|
|||||||
if isinstance(attempts, dict):
|
if isinstance(attempts, dict):
|
||||||
missing_attempts = sorted(CHALLENGE_REPORT_ATTEMPT_FIELDS.difference(attempts))
|
missing_attempts = sorted(CHALLENGE_REPORT_ATTEMPT_FIELDS.difference(attempts))
|
||||||
errors.extend(
|
errors.extend(
|
||||||
f"missing challenge_report.attempts.{field}"
|
f"missing challenge_report.attempts.{field}" for field in missing_attempts
|
||||||
for field in missing_attempts
|
|
||||||
)
|
)
|
||||||
for field in CHALLENGE_REPORT_ATTEMPT_FIELDS.intersection(attempts):
|
for field in CHALLENGE_REPORT_ATTEMPT_FIELDS.intersection(attempts):
|
||||||
value = attempts[field]
|
value = attempts[field]
|
||||||
|
|||||||
@@ -467,7 +467,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
success_count = sum(
|
success_count = sum(
|
||||||
1 for item in summaries if item["classification"] == "success"
|
1 for item in summaries if item["classification"] == "success"
|
||||||
)
|
)
|
||||||
print(json.dumps({"success_count": success_count, "trial_count": len(summaries)}))
|
print(
|
||||||
|
json.dumps({"success_count": success_count, "trial_count": len(summaries)})
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
finally:
|
finally:
|
||||||
if managed_server is not None:
|
if managed_server is not None:
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import html
|
||||||
import json
|
import json
|
||||||
import threading
|
import threading
|
||||||
import uuid
|
import uuid
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import html
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||||
from typing import ClassVar
|
from typing import ClassVar
|
||||||
|
|||||||
@@ -113,8 +113,7 @@ def _render_markdown_report(payload: MarkdownInput) -> MarkdownOutput:
|
|||||||
"Actions:",
|
"Actions:",
|
||||||
]
|
]
|
||||||
lines.extend(
|
lines.extend(
|
||||||
f"- {item.owner} | {item.task} | {item.due}"
|
f"- {item.owner} | {item.task} | {item.due}" for item in report.action_items
|
||||||
for item in report.action_items
|
|
||||||
)
|
)
|
||||||
lines.extend(["", "Risks:"])
|
lines.extend(["", "Risks:"])
|
||||||
lines.extend(f"- {risk}" for risk in report.risks)
|
lines.extend(f"- {risk}" for risk in report.risks)
|
||||||
|
|||||||
@@ -24,11 +24,7 @@ def validate_deployment_dependencies(
|
|||||||
|
|
||||||
for logical_source, concrete_source in bindings.items():
|
for logical_source, concrete_source in bindings.items():
|
||||||
source = sources_by_id.get(logical_source)
|
source = sources_by_id.get(logical_source)
|
||||||
if (
|
if source is not None and source.platform and concrete_source != logical_source:
|
||||||
source is not None
|
|
||||||
and source.platform
|
|
||||||
and concrete_source != logical_source
|
|
||||||
):
|
|
||||||
diagnostics.append(
|
diagnostics.append(
|
||||||
DependencyDiagnostic(
|
DependencyDiagnostic(
|
||||||
severity=DiagnosticSeverity.ERROR,
|
severity=DiagnosticSeverity.ERROR,
|
||||||
|
|||||||
@@ -112,7 +112,9 @@ def reducer(
|
|||||||
def decorate_config(
|
def decorate_config(
|
||||||
raw: ConfigReducerCallable[ConfigT],
|
raw: ConfigReducerCallable[ConfigT],
|
||||||
) -> AuthoredReducer:
|
) -> AuthoredReducer:
|
||||||
reducer_name = name or getattr(raw, "__name__", "<anonymous config reducer>")
|
reducer_name = name or getattr(
|
||||||
|
raw, "__name__", "<anonymous config reducer>"
|
||||||
|
)
|
||||||
reducer_description = description or raw.__doc__
|
reducer_description = description or raw.__doc__
|
||||||
|
|
||||||
model_type = config_model
|
model_type = config_model
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ def _parse_map_flags(values: list[str] | None) -> dict[str, str]:
|
|||||||
parsed[source] = target
|
parsed[source] = target
|
||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
app = typer.Typer(
|
app = typer.Typer(
|
||||||
name="draft",
|
name="draft",
|
||||||
help="Create, inspect, patch, validate, and save draft workflows.",
|
help="Create, inspect, patch, validate, and save draft workflows.",
|
||||||
@@ -162,9 +163,7 @@ def set_draft_route(
|
|||||||
],
|
],
|
||||||
step_id: Annotated[str, typer.Option("--step", help="Draft step id.")],
|
step_id: Annotated[str, typer.Option("--step", help="Draft step id.")],
|
||||||
outcome: Annotated[str, typer.Option("--outcome", help="Step outcome.")],
|
outcome: Annotated[str, typer.Option("--outcome", help="Step outcome.")],
|
||||||
target: Annotated[
|
target: Annotated[str, typer.Option("--to", help="Target step id or __end__.")],
|
||||||
str, typer.Option("--to", help="Target step id or __end__.")
|
|
||||||
],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set one route: steps.<step> outcome -> target."""
|
"""Set one route: steps.<step> outcome -> target."""
|
||||||
context = load_cli_context(ctx)
|
context = load_cli_context(ctx)
|
||||||
|
|||||||
@@ -148,5 +148,7 @@ def _source_capability_names(
|
|||||||
raise ValueError("source inventory missing capabilities object")
|
raise ValueError("source inventory missing capabilities object")
|
||||||
value = capabilities.get(capability_key)
|
value = capabilities.get(capability_key)
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
raise ValueError(f"source inventory capabilities.{capability_key} must be a list")
|
raise ValueError(
|
||||||
|
f"source inventory capabilities.{capability_key} must be a list"
|
||||||
|
)
|
||||||
return [str(item) for item in value]
|
return [str(item) for item in value]
|
||||||
|
|||||||
@@ -124,7 +124,9 @@ class ContentAccessService:
|
|||||||
return _truncate_resource_payload(payload, max_chars=max_chars)
|
return _truncate_resource_payload(payload, max_chars=max_chars)
|
||||||
|
|
||||||
|
|
||||||
def _truncate_resource_payload(payload: dict[str, Any], *, max_chars: int) -> dict[str, Any]:
|
def _truncate_resource_payload(
|
||||||
|
payload: dict[str, Any], *, max_chars: int
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""Bound text content returned by source URI reads without mutating upstream payload."""
|
"""Bound text content returned by source URI reads without mutating upstream payload."""
|
||||||
|
|
||||||
contents = payload.get("contents")
|
contents = payload.get("contents")
|
||||||
|
|||||||
@@ -150,7 +150,9 @@ class SourceDiagnosticsProvider:
|
|||||||
"fetched_at_epoch_ms": None
|
"fetched_at_epoch_ms": None
|
||||||
if snapshot is None
|
if snapshot is None
|
||||||
else snapshot.fetched_at_epoch_ms,
|
else snapshot.fetched_at_epoch_ms,
|
||||||
"max_age_seconds": None if snapshot is None else snapshot.max_age_seconds,
|
"max_age_seconds": None
|
||||||
|
if snapshot is None
|
||||||
|
else snapshot.max_age_seconds,
|
||||||
"node_count": 0 if snapshot is None else len(snapshot.nodes),
|
"node_count": 0 if snapshot is None else len(snapshot.nodes),
|
||||||
"resource_count": 0 if snapshot is None else len(snapshot.resources),
|
"resource_count": 0 if snapshot is None else len(snapshot.resources),
|
||||||
"prompt_count": 0 if snapshot is None else len(snapshot.prompts),
|
"prompt_count": 0 if snapshot is None else len(snapshot.prompts),
|
||||||
|
|||||||
@@ -88,7 +88,13 @@ class WorkflowRuntimeService:
|
|||||||
deployment: WorkflowDeployment | None,
|
deployment: WorkflowDeployment | None,
|
||||||
artifact: WorkflowArtifact | None,
|
artifact: WorkflowArtifact | None,
|
||||||
saved_subgraph_tree: SavedSubgraphTree | None = None,
|
saved_subgraph_tree: SavedSubgraphTree | None = None,
|
||||||
) -> tuple[Workflow, dict[str, Any], dict[str, Any], dict[str, Any], SourceBindingPlatformContext]:
|
) -> tuple[
|
||||||
|
Workflow,
|
||||||
|
dict[str, Any],
|
||||||
|
dict[str, Any],
|
||||||
|
dict[str, Any],
|
||||||
|
SourceBindingPlatformContext,
|
||||||
|
]:
|
||||||
"""Resolve bindings once into the executable pieces core expects.
|
"""Resolve bindings once into the executable pieces core expects.
|
||||||
|
|
||||||
Saved-run resume still rebuilds prepared dependencies from the current
|
Saved-run resume still rebuilds prepared dependencies from the current
|
||||||
|
|||||||
@@ -151,7 +151,13 @@ class LocalWorkflowRuntimeRunner(WorkflowRuntimeRunner):
|
|||||||
deployment: WorkflowDeployment | None,
|
deployment: WorkflowDeployment | None,
|
||||||
artifact: WorkflowArtifact | None,
|
artifact: WorkflowArtifact | None,
|
||||||
saved_subgraph_tree: SavedSubgraphTree | None = None,
|
saved_subgraph_tree: SavedSubgraphTree | None = None,
|
||||||
) -> tuple[Workflow, dict[str, Any], dict[str, Any], dict[str, Any], SourceBindingPlatformContext]:
|
) -> tuple[
|
||||||
|
Workflow,
|
||||||
|
dict[str, Any],
|
||||||
|
dict[str, Any],
|
||||||
|
dict[str, Any],
|
||||||
|
SourceBindingPlatformContext,
|
||||||
|
]:
|
||||||
plan_node_names = [
|
plan_node_names = [
|
||||||
node.node for node in plan.nodes if isinstance(node, NodeUse)
|
node.node for node in plan.nodes if isinstance(node, NodeUse)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -186,7 +186,9 @@ class HttpxOAuthTokenRefresher:
|
|||||||
payload = response.json()
|
payload = response.json()
|
||||||
access_token = payload.get("access_token")
|
access_token = payload.get("access_token")
|
||||||
if not isinstance(access_token, str) or not access_token:
|
if not isinstance(access_token, str) or not access_token:
|
||||||
raise ValueError("OAuth token refresh response did not include access_token")
|
raise ValueError(
|
||||||
|
"OAuth token refresh response did not include access_token"
|
||||||
|
)
|
||||||
expires_in = payload.get("expires_in")
|
expires_in = payload.get("expires_in")
|
||||||
return OAuthAccessToken(
|
return OAuthAccessToken(
|
||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
@@ -232,7 +234,9 @@ class McpAuthBinder:
|
|||||||
if isinstance(auth, EnvAuth):
|
if isinstance(auth, EnvAuth):
|
||||||
raise ValueError("env auth is not supported for MCP HTTP")
|
raise ValueError("env auth is not supported for MCP HTTP")
|
||||||
if isinstance(auth, OpaqueAuth):
|
if isinstance(auth, OpaqueAuth):
|
||||||
raise ValueError(f"opaque auth scheme {auth.scheme!r} is not supported for MCP HTTP")
|
raise ValueError(
|
||||||
|
f"opaque auth scheme {auth.scheme!r} is not supported for MCP HTTP"
|
||||||
|
)
|
||||||
raise TypeError(f"unsupported auth variant {type(auth).__name__}")
|
raise TypeError(f"unsupported auth variant {type(auth).__name__}")
|
||||||
|
|
||||||
async def bind_stdio_auth(
|
async def bind_stdio_auth(
|
||||||
@@ -244,7 +248,9 @@ class McpAuthBinder:
|
|||||||
auth = record.auth
|
auth = record.auth
|
||||||
if isinstance(auth, EnvAuth):
|
if isinstance(auth, EnvAuth):
|
||||||
return BoundMcpStdioAuth(env=dict(auth.env))
|
return BoundMcpStdioAuth(env=dict(auth.env))
|
||||||
if isinstance(auth, BearerAuth | HeaderAuth | OAuthRefreshTokenAuth | OpaqueAuth):
|
if isinstance(
|
||||||
|
auth, BearerAuth | HeaderAuth | OAuthRefreshTokenAuth | OpaqueAuth
|
||||||
|
):
|
||||||
raise ValueError(f"{auth.kind} auth is not supported for MCP stdio")
|
raise ValueError(f"{auth.kind} auth is not supported for MCP stdio")
|
||||||
raise TypeError(f"unsupported auth variant {type(auth).__name__}")
|
raise TypeError(f"unsupported auth variant {type(auth).__name__}")
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ from wf_sources_mcp.connections import McpSourceConnection
|
|||||||
from wf_sources_mcp.transports import HttpSourceTransport, StdioSourceTransport
|
from wf_sources_mcp.transports import HttpSourceTransport, StdioSourceTransport
|
||||||
|
|
||||||
|
|
||||||
def _as_stored_auth(auth: AuthRecord | StoredAuthRecord | None) -> StoredAuthRecord | None:
|
def _as_stored_auth(
|
||||||
|
auth: AuthRecord | StoredAuthRecord | None,
|
||||||
|
) -> StoredAuthRecord | None:
|
||||||
if auth is None or isinstance(auth, StoredAuthRecord):
|
if auth is None or isinstance(auth, StoredAuthRecord):
|
||||||
return auth
|
return auth
|
||||||
return auth_record_from_compat(
|
return auth_record_from_compat(
|
||||||
|
|||||||
@@ -65,10 +65,10 @@ async def _list_optional_capabilities(
|
|||||||
root = _root_exception(exc)
|
root = _root_exception(exc)
|
||||||
if isinstance(root, McpError) and root.error.code == METHOD_NOT_FOUND:
|
if isinstance(root, McpError) and root.error.code == METHOD_NOT_FOUND:
|
||||||
return []
|
return []
|
||||||
if (
|
if isinstance(root, httpx.HTTPStatusError) and root.response.status_code in {
|
||||||
isinstance(root, httpx.HTTPStatusError)
|
400,
|
||||||
and root.response.status_code in {400, 404}
|
404,
|
||||||
):
|
}:
|
||||||
return []
|
return []
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ class AuthStore:
|
|||||||
def save_auth_record(self, record: NeutralAuthRecord | StoredAuthRecord) -> None:
|
def save_auth_record(self, record: NeutralAuthRecord | StoredAuthRecord) -> None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def load_auth_record(self, auth_ref: str) -> NeutralAuthRecord | StoredAuthRecord | None:
|
def load_auth_record(
|
||||||
|
self, auth_ref: str
|
||||||
|
) -> NeutralAuthRecord | StoredAuthRecord | None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def delete_auth(self, connection_id: str) -> bool:
|
def delete_auth(self, connection_id: str) -> bool:
|
||||||
@@ -139,7 +141,9 @@ class FileAuthStore(AuthStore):
|
|||||||
else:
|
else:
|
||||||
self.save_auth(mcp_auth_from_neutral(record))
|
self.save_auth(mcp_auth_from_neutral(record))
|
||||||
|
|
||||||
def load_auth_record(self, auth_ref: str) -> NeutralAuthRecord | StoredAuthRecord | None:
|
def load_auth_record(
|
||||||
|
self, auth_ref: str
|
||||||
|
) -> NeutralAuthRecord | StoredAuthRecord | None:
|
||||||
path = self._auth_path(auth_ref)
|
path = self._auth_path(auth_ref)
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return None
|
return None
|
||||||
@@ -258,7 +262,9 @@ class FileStore(Store):
|
|||||||
def save_auth_record(self, record: NeutralAuthRecord | StoredAuthRecord) -> None:
|
def save_auth_record(self, record: NeutralAuthRecord | StoredAuthRecord) -> None:
|
||||||
self._auth.save_auth_record(record)
|
self._auth.save_auth_record(record)
|
||||||
|
|
||||||
def load_auth_record(self, auth_ref: str) -> NeutralAuthRecord | StoredAuthRecord | None:
|
def load_auth_record(
|
||||||
|
self, auth_ref: str
|
||||||
|
) -> NeutralAuthRecord | StoredAuthRecord | None:
|
||||||
return self._auth.load_auth_record(auth_ref)
|
return self._auth.load_auth_record(auth_ref)
|
||||||
|
|
||||||
def delete_auth(self, connection_id: str) -> bool:
|
def delete_auth(self, connection_id: str) -> bool:
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ def register_methods(
|
|||||||
params: DiagnoseSourceParams = RpcParams(),
|
params: DiagnoseSourceParams = RpcParams(),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
return await server.source_admin.diagnose_source(
|
return await server.source_admin.diagnose_source(source_id=params.source_id)
|
||||||
source_id=params.source_id
|
|
||||||
)
|
|
||||||
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
|
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
|
||||||
raise_workflow_rpc_error(exc)
|
raise_workflow_rpc_error(exc)
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ def test_browser_click_source_human_timeout_cleans_up() -> None:
|
|||||||
assert _active_session_count() == 0
|
assert _active_session_count() == 0
|
||||||
|
|
||||||
|
|
||||||
EXAMPLE_DIR = Path(__file__).resolve().parents[2] / "examples" / "browser_click_workflow"
|
EXAMPLE_DIR = (
|
||||||
|
Path(__file__).resolve().parents[2] / "examples" / "browser_click_workflow"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -234,4 +236,3 @@ async def test_browser_click_workflow_artifact_deployment_run_path(tmp_path) ->
|
|||||||
assert run["output"]["after"]["status_text"] == "Button clicked"
|
assert run["output"]["after"]["status_text"] == "Button clicked"
|
||||||
assert run["output"]["closed"] is True
|
assert run["output"]["closed"] is True
|
||||||
assert run["trace_count"] >= 3
|
assert run["trace_count"] >= 3
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,9 @@ async def test_call_openapi_operation_maps_unexpected_status() -> None:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_call_openapi_operation_maps_invalid_request_to_validation_error() -> None:
|
async def test_call_openapi_operation_maps_invalid_request_to_validation_error() -> (
|
||||||
|
None
|
||||||
|
):
|
||||||
app = load_openapi_app(FIXTURE)
|
app = load_openapi_app(FIXTURE)
|
||||||
operation = next(
|
operation = next(
|
||||||
op for op in load_openapi_operations(FIXTURE) if op.name == "create_pet"
|
op for op in load_openapi_operations(FIXTURE) if op.name == "create_pet"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from dataclasses import replace
|
from dataclasses import replace
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
@@ -98,6 +97,7 @@ def _deployment_api(
|
|||||||
context = context_from_service(service)
|
context = context_from_service(service)
|
||||||
return WorkflowDeploymentApi(context), service
|
return WorkflowDeploymentApi(context), service
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_save_deployment_stores_and_returns_stable_fields(tmp_path: Path) -> None:
|
async def test_save_deployment_stores_and_returns_stable_fields(tmp_path: Path) -> None:
|
||||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "deploy_save")
|
artifact_store = FileWorkflowArtifactStore(tmp_path / "deploy_save")
|
||||||
@@ -108,9 +108,7 @@ async def test_save_deployment_stores_and_returns_stable_fields(tmp_path: Path)
|
|||||||
id="echo.personal",
|
id="echo.personal",
|
||||||
artifact_id="echo",
|
artifact_id="echo",
|
||||||
artifact_version=1,
|
artifact_version=1,
|
||||||
bindings=[
|
bindings=[{"logical_source": "demo", "concrete_source": "demo.personal"}],
|
||||||
{"logical_source": "demo", "concrete_source": "demo.personal"}
|
|
||||||
],
|
|
||||||
).model_dump(mode="json")
|
).model_dump(mode="json")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -119,6 +117,7 @@ async def test_save_deployment_stores_and_returns_stable_fields(tmp_path: Path)
|
|||||||
assert result["artifact_id"] == "echo"
|
assert result["artifact_id"] == "echo"
|
||||||
assert result["artifact_version"] == 1
|
assert result["artifact_version"] == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_list_deployments_returns_compact_summaries(tmp_path: Path) -> None:
|
async def test_list_deployments_returns_compact_summaries(tmp_path: Path) -> None:
|
||||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "deploy_list")
|
artifact_store = FileWorkflowArtifactStore(tmp_path / "deploy_list")
|
||||||
@@ -141,7 +140,9 @@ async def test_list_deployments_returns_compact_summaries(tmp_path: Path) -> Non
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_list_deployments_returns_empty_without_artifact_store(tmp_path: Path) -> None:
|
async def test_list_deployments_returns_empty_without_artifact_store(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "deploy_no_store")
|
artifact_store = FileWorkflowArtifactStore(tmp_path / "deploy_no_store")
|
||||||
_api, service = _deployment_api(artifact_store)
|
_api, service = _deployment_api(artifact_store)
|
||||||
context = replace(context_from_service(service), artifact_store=None)
|
context = replace(context_from_service(service), artifact_store=None)
|
||||||
@@ -173,7 +174,9 @@ async def test_delete_deployment_removes_one(tmp_path: Path) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_validate_deployment_returns_runnable_for_valid_binding(tmp_path: Path) -> None:
|
async def test_validate_deployment_returns_runnable_for_valid_binding(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "deploy_validate_runnable")
|
artifact_store = FileWorkflowArtifactStore(tmp_path / "deploy_validate_runnable")
|
||||||
api, service = _deployment_api(artifact_store, register_echo=True)
|
api, service = _deployment_api(artifact_store, register_echo=True)
|
||||||
artifact_store.save_artifact(_echo_artifact())
|
artifact_store.save_artifact(_echo_artifact())
|
||||||
@@ -186,7 +189,9 @@ async def test_validate_deployment_returns_runnable_for_valid_binding(tmp_path:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await api.validate_deployment(deployment_id="echo.personal", live_check=False)
|
result = await api.validate_deployment(
|
||||||
|
deployment_id="echo.personal", live_check=False
|
||||||
|
)
|
||||||
|
|
||||||
assert result["status"] == "runnable"
|
assert result["status"] == "runnable"
|
||||||
assert result["diagnostics"] == []
|
assert result["diagnostics"] == []
|
||||||
@@ -202,7 +207,9 @@ class FailingLivenessAdapter:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_validate_deployment_live_check_calls_live_checker(tmp_path: Path) -> None:
|
async def test_validate_deployment_live_check_calls_live_checker(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "deploy_validate_live")
|
artifact_store = FileWorkflowArtifactStore(tmp_path / "deploy_validate_live")
|
||||||
api, service = _deployment_api(artifact_store, register_echo=True)
|
api, service = _deployment_api(artifact_store, register_echo=True)
|
||||||
artifact_store.save_artifact(_echo_artifact())
|
artifact_store.save_artifact(_echo_artifact())
|
||||||
@@ -219,7 +226,9 @@ async def test_validate_deployment_live_check_calls_live_checker(tmp_path: Path)
|
|||||||
cast(BackendAdapter, FailingLivenessAdapter()),
|
cast(BackendAdapter, FailingLivenessAdapter()),
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await api.validate_deployment(deployment_id="echo.personal", live_check=True)
|
result = await api.validate_deployment(
|
||||||
|
deployment_id="echo.personal", live_check=True
|
||||||
|
)
|
||||||
|
|
||||||
assert result["status"] == "unrunnable"
|
assert result["status"] == "unrunnable"
|
||||||
assert result["diagnostics"][0]["code"] == "source_unreachable"
|
assert result["diagnostics"][0]["code"] == "source_unreachable"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ def test_platform_context_uses_identity_for_platform_sources() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_platform_context_rejects_unbound_source() -> None:
|
def test_platform_context_rejects_unbound_source() -> None:
|
||||||
context = SourceBindingPlatformContext(source_bindings={}, read_resource_handler=None)
|
context = SourceBindingPlatformContext(
|
||||||
|
source_bindings={}, read_resource_handler=None
|
||||||
|
)
|
||||||
|
|
||||||
with pytest.raises(KeyError, match="unbound logical source"):
|
with pytest.raises(KeyError, match="unbound logical source"):
|
||||||
context.resolve_source("drive")
|
context.resolve_source("drive")
|
||||||
|
|||||||
@@ -214,7 +214,9 @@ edges: []
|
|||||||
assert payload["source_bindings"] == {"local.ops": "local.ops"}
|
assert payload["source_bindings"] == {"local.ops": "local.ops"}
|
||||||
|
|
||||||
|
|
||||||
def test_artifact_create_from_plan_rejects_non_object_yaml(monkeypatch, tmp_path) -> None:
|
def test_artifact_create_from_plan_rejects_non_object_yaml(
|
||||||
|
monkeypatch, tmp_path
|
||||||
|
) -> None:
|
||||||
handlers = _ArtifactHandlers()
|
handlers = _ArtifactHandlers()
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"wf_cli.commands.artifacts.load_cli_context",
|
"wf_cli.commands.artifacts.load_cli_context",
|
||||||
|
|||||||
@@ -77,12 +77,16 @@ class _FakeOAuthClient:
|
|||||||
self.auth_kwargs: dict[str, object] = {}
|
self.auth_kwargs: dict[str, object] = {}
|
||||||
self.fetch_calls: list[str] = []
|
self.fetch_calls: list[str] = []
|
||||||
|
|
||||||
def create_authorization_url(self, auth_url: str, **kwargs: object) -> tuple[str, str]:
|
def create_authorization_url(
|
||||||
|
self, auth_url: str, **kwargs: object
|
||||||
|
) -> tuple[str, str]:
|
||||||
assert auth_url == "https://accounts.google.com/o/oauth2/v2/auth"
|
assert auth_url == "https://accounts.google.com/o/oauth2/v2/auth"
|
||||||
self.auth_kwargs = dict(kwargs)
|
self.auth_kwargs = dict(kwargs)
|
||||||
return self.authorization_url, "state-123"
|
return self.authorization_url, "state-123"
|
||||||
|
|
||||||
async def fetch_token(self, token_url: str, authorization_response: str) -> dict[str, object]:
|
async def fetch_token(
|
||||||
|
self, token_url: str, authorization_response: str
|
||||||
|
) -> dict[str, object]:
|
||||||
self.fetch_calls.append(authorization_response)
|
self.fetch_calls.append(authorization_response)
|
||||||
return {
|
return {
|
||||||
"refresh_token": "refresh",
|
"refresh_token": "refresh",
|
||||||
@@ -144,7 +148,9 @@ async def test_oauth_code_login_flow_callback_can_supply_response() -> None:
|
|||||||
assert client.fetch_calls == ["http://127.0.0.1/callback?code=abc&state=state-123"]
|
assert client.fetch_calls == ["http://127.0.0.1/callback?code=abc&state=state-123"]
|
||||||
|
|
||||||
|
|
||||||
def test_auth_oauth_login_saves_record_from_provider_profile(monkeypatch, tmp_path) -> None:
|
def test_auth_oauth_login_saves_record_from_provider_profile(
|
||||||
|
monkeypatch, tmp_path
|
||||||
|
) -> None:
|
||||||
saved: list[dict[str, object]] = []
|
saved: list[dict[str, object]] = []
|
||||||
|
|
||||||
class _FakeAdmin:
|
class _FakeAdmin:
|
||||||
@@ -178,7 +184,9 @@ def test_auth_oauth_login_saves_record_from_provider_profile(monkeypatch, tmp_pa
|
|||||||
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
|
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
|
||||||
"token_url": "https://oauth2.googleapis.com/token",
|
"token_url": "https://oauth2.googleapis.com/token",
|
||||||
"client_id_env": "GOOGLE_OAUTH_CLIENT_ID",
|
"client_id_env": "GOOGLE_OAUTH_CLIENT_ID",
|
||||||
"scopes": ["https://www.googleapis.com/auth/drive.readonly"],
|
"scopes": [
|
||||||
|
"https://www.googleapis.com/auth/drive.readonly"
|
||||||
|
],
|
||||||
"extra_authorize_params": {
|
"extra_authorize_params": {
|
||||||
"access_type": "offline",
|
"access_type": "offline",
|
||||||
"prompt": "consent",
|
"prompt": "consent",
|
||||||
|
|||||||
@@ -170,12 +170,12 @@ def test_wf_config_validate_uses_global_config_when_path_omitted(
|
|||||||
payload = json.loads(result.output)
|
payload = json.loads(result.output)
|
||||||
assert payload["valid"] is True
|
assert payload["valid"] is True
|
||||||
assert payload["path"] == str(config_path)
|
assert payload["path"] == str(config_path)
|
||||||
assert payload["sources"] == [
|
assert payload["sources"] == [{"id": "wf.std", "kind": "stdlib", "status": "ok"}]
|
||||||
{"id": "wf.std", "kind": "stdlib", "status": "ok"}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_wf_config_validate_reports_python_source_import_failure(tmp_path: Path) -> None:
|
def test_wf_config_validate_reports_python_source_import_failure(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
config_path = tmp_path / "wf.config.json"
|
config_path = tmp_path / "wf.config.json"
|
||||||
config_path.write_text(
|
config_path.write_text(
|
||||||
json.dumps(
|
json.dumps(
|
||||||
|
|||||||
@@ -268,9 +268,7 @@ async def test_content_access_uses_stateful_runtime_for_upstream_content() -> No
|
|||||||
|
|
||||||
|
|
||||||
async def test_read_resource_by_source_uri_reads_upstream() -> None:
|
async def test_read_resource_by_source_uri_reads_upstream() -> None:
|
||||||
service = WfMcpService(
|
service = WfMcpService(store=FileStore(local_temp_root() / "content_source_uri"))
|
||||||
store=FileStore(local_temp_root() / "content_source_uri")
|
|
||||||
)
|
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ def _connection(**metadata: object) -> ConnectionConfig:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _provider(tmp_path: Path, connection: ConnectionConfig) -> SourceDiagnosticsProvider:
|
def _provider(
|
||||||
|
tmp_path: Path, connection: ConnectionConfig
|
||||||
|
) -> SourceDiagnosticsProvider:
|
||||||
registry = ConnectionRegistry()
|
registry = ConnectionRegistry()
|
||||||
registry.register(connection)
|
registry.register(connection)
|
||||||
return SourceDiagnosticsProvider(
|
return SourceDiagnosticsProvider(
|
||||||
|
|||||||
@@ -100,11 +100,13 @@ def test_workflow_runtime_service_prepares_node_registry_and_reducers() -> None:
|
|||||||
emit_event=lambda event: None,
|
emit_event=lambda event: None,
|
||||||
)
|
)
|
||||||
|
|
||||||
workflow, registry, reducers, prepared_subgraphs, platform_context = runtime.prepare_workflow_runtime(
|
workflow, registry, reducers, prepared_subgraphs, platform_context = (
|
||||||
|
runtime.prepare_workflow_runtime(
|
||||||
single_echo_plan("runtime_prepare", "demo.personal.echo_tool"),
|
single_echo_plan("runtime_prepare", "demo.personal.echo_tool"),
|
||||||
deployment=None,
|
deployment=None,
|
||||||
artifact=None,
|
artifact=None,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
assert "demo.personal.echo_tool" in [nd.name for nd in workflow.node_defs]
|
assert "demo.personal.echo_tool" in [nd.name for nd in workflow.node_defs]
|
||||||
assert "demo.personal.echo_tool" in registry
|
assert "demo.personal.echo_tool" in registry
|
||||||
|
|||||||
@@ -129,7 +129,9 @@ def test_file_auth_store_writes_new_stored_auth_record_shape(tmp_path: Path) ->
|
|||||||
assert data["metadata"]["provider"] == "google"
|
assert data["metadata"]["provider"] == "google"
|
||||||
|
|
||||||
|
|
||||||
def test_file_auth_store_loads_old_format_through_load_auth_record(tmp_path: Path) -> None:
|
def test_file_auth_store_loads_old_format_through_load_auth_record(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
store = FileAuthStore(tmp_path)
|
store = FileAuthStore(tmp_path)
|
||||||
path = tmp_path / "auth" / "github.work.json"
|
path = tmp_path / "auth" / "github.work.json"
|
||||||
path.write_text(
|
path.write_text(
|
||||||
|
|||||||
@@ -449,7 +449,9 @@ async def test_rpc_client_draft_workspace_focused_edit_methods(tmp_path) -> None
|
|||||||
server = build_local_static_workflow_server(tmp_path / "store")
|
server = build_local_static_workflow_server(tmp_path / "store")
|
||||||
app = create_rpc_app(server)
|
app = create_rpc_app(server)
|
||||||
transport = httpx.ASGITransport(app=app)
|
transport = httpx.ASGITransport(app=app)
|
||||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as http_client:
|
async with httpx.AsyncClient(
|
||||||
|
transport=transport, base_url="http://test"
|
||||||
|
) as http_client:
|
||||||
client = RpcWorkflowApiClient(
|
client = RpcWorkflowApiClient(
|
||||||
url="http://test/rpc",
|
url="http://test/rpc",
|
||||||
timeout_seconds=5,
|
timeout_seconds=5,
|
||||||
@@ -503,6 +505,4 @@ async def test_rpc_client_diagnoses_source(tmp_path) -> None:
|
|||||||
payload = await Client().diagnose_source(source_id="demo.personal")
|
payload = await Client().diagnose_source(source_id="demo.personal")
|
||||||
|
|
||||||
assert payload == {"source_id": "demo.personal", "status": "ok"}
|
assert payload == {"source_id": "demo.personal", "status": "ok"}
|
||||||
assert calls == [
|
assert calls == [("workflow.sources.diagnose", {"source_id": "demo.personal"})]
|
||||||
("workflow.sources.diagnose", {"source_id": "demo.personal"})
|
|
||||||
]
|
|
||||||
|
|||||||
Reference in New Issue
Block a user