revert check fix, add test cov
This commit is contained in:
@@ -452,13 +452,13 @@ Do not change catalog refresh behavior yet. The adapter remains the temporary ex
|
|||||||
In `tests/wf_mcp/test_workflow_wrappers.py`, replace:
|
In `tests/wf_mcp/test_workflow_wrappers.py`, replace:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
adapter = (cast(BackendAdapter, adapter),)
|
adapter = cast(BackendAdapter, adapter)
|
||||||
```
|
```
|
||||||
|
|
||||||
with:
|
with:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
executor = (cast(ToolExecutor, adapter),)
|
executor = cast(ToolExecutor, adapter)
|
||||||
```
|
```
|
||||||
|
|
||||||
and import:
|
and import:
|
||||||
|
|||||||
@@ -13,23 +13,6 @@ from typing import Any
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[2]
|
|
||||||
|
|
||||||
|
|
||||||
def _utf8_subprocess_env() -> dict[str, str]:
|
|
||||||
"""Force child tools toward UTF-8 so captured agent output is decodable.
|
|
||||||
|
|
||||||
OpenCode emits UTF-8 JSONL, but Windows defaults Python's subprocess text
|
|
||||||
decoding to the active ANSI code page unless an encoding is supplied. The
|
|
||||||
environment nudges child Python tools too; the explicit subprocess encoding
|
|
||||||
below is the actual guard against cp1252 reader-thread crashes.
|
|
||||||
"""
|
|
||||||
env = dict(os.environ)
|
|
||||||
env.setdefault("PYTHONUTF8", "1")
|
|
||||||
env.setdefault("PYTHONIOENCODING", "utf-8")
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
from examples.agent_challenges.names import (
|
from examples.agent_challenges.names import (
|
||||||
short_challenge_name,
|
short_challenge_name,
|
||||||
short_model_name,
|
short_model_name,
|
||||||
@@ -64,6 +47,22 @@ from examples.agent_challenges.workspace import (
|
|||||||
wf_command_prefix_for_config,
|
wf_command_prefix_for_config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
||||||
|
def _utf8_subprocess_env() -> dict[str, str]:
|
||||||
|
"""Force child tools toward UTF-8 so captured agent output is decodable.
|
||||||
|
|
||||||
|
OpenCode emits UTF-8 JSONL, but Windows defaults Python's subprocess text
|
||||||
|
decoding to the active ANSI code page unless an encoding is supplied. The
|
||||||
|
environment nudges child Python tools too; the explicit subprocess encoding
|
||||||
|
below is the actual guard against cp1252 reader-thread crashes.
|
||||||
|
"""
|
||||||
|
env = dict(os.environ)
|
||||||
|
env.setdefault("PYTHONUTF8", "1")
|
||||||
|
env.setdefault("PYTHONIOENCODING", "utf-8")
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
def _opencode_trial_title(
|
def _opencode_trial_title(
|
||||||
*, challenge_id: str, model: str, profile: str, index: int
|
*, challenge_id: str, model: str, profile: str, index: int
|
||||||
|
|||||||
+9
-1
@@ -52,6 +52,14 @@ typeCheckingMode = "basic" # too many errors
|
|||||||
|
|
||||||
[tool.ruff.format]
|
[tool.ruff.format]
|
||||||
preview = false
|
preview = false
|
||||||
|
# Ruff 0.16 formats Markdown by default, but many docs contain partial Python
|
||||||
|
# call fragments that are illustrative rather than standalone programs.
|
||||||
|
exclude = ["**/*.md"]
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
extend-select = ["I"]
|
# Keep the established lint contract stable across Ruff default-rule changes.
|
||||||
|
select = ["E4", "E7", "E9", "F", "I"]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
# This generator adjusts sys.path before importing repository-local modules.
|
||||||
|
"docs/thesis/generate_agent_challenge_evaluation.py" = ["E402"]
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ def workflow_mcp_source_to_connection_config(source: object) -> ConnectionConfig
|
|||||||
for field in ("id", "provider", "account", "enabled", "ownership", "transport"):
|
for field in ("id", "provider", "account", "enabled", "ownership", "transport"):
|
||||||
if getattr(source, field, None) is None:
|
if getattr(source, field, None) is None:
|
||||||
raise ValueError(f"wf_config MCP source missing required field: {field}")
|
raise ValueError(f"wf_config MCP source missing required field: {field}")
|
||||||
transport = source.transport
|
transport = getattr(source, "transport")
|
||||||
metadata = dict(getattr(source, "metadata", {}))
|
metadata = dict(getattr(source, "metadata", {}))
|
||||||
if transport.kind == "stdio":
|
if transport.kind == "stdio":
|
||||||
metadata.update(
|
metadata.update(
|
||||||
@@ -83,12 +83,12 @@ def workflow_mcp_source_to_connection_config(source: object) -> ConnectionConfig
|
|||||||
if auth_ref is not None:
|
if auth_ref is not None:
|
||||||
metadata["auth_ref"] = auth_ref
|
metadata["auth_ref"] = auth_ref
|
||||||
return ConnectionConfig(
|
return ConnectionConfig(
|
||||||
id=source.id,
|
id=getattr(source, "id"),
|
||||||
server=source.provider,
|
server=getattr(source, "provider"),
|
||||||
account=source.account,
|
account=getattr(source, "account"),
|
||||||
enabled=source.enabled,
|
enabled=getattr(source, "enabled"),
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
source_config_ownership=source.ownership,
|
source_config_ownership=getattr(source, "ownership"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from typing import NoReturn
|
from typing import NoReturn
|
||||||
|
|
||||||
import fastapi_jsonrpc as jsonrpc
|
import fastapi_jsonrpc as jsonrpc
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict, ValidationError
|
||||||
|
|
||||||
|
|
||||||
class WorkflowRpcError(jsonrpc.BaseError):
|
class WorkflowRpcError(jsonrpc.BaseError):
|
||||||
@@ -22,6 +22,11 @@ class WorkflowRpcError(jsonrpc.BaseError):
|
|||||||
def raise_workflow_rpc_error(exc: Exception) -> NoReturn:
|
def raise_workflow_rpc_error(exc: Exception) -> NoReturn:
|
||||||
"""Map expected application exceptions without swallowing programming bugs."""
|
"""Map expected application exceptions without swallowing programming bugs."""
|
||||||
|
|
||||||
|
# DTO validation happens before handlers, while complete-document validation
|
||||||
|
# happens inside the service. Both are invalid JSON-RPC parameters.
|
||||||
|
if isinstance(exc, ValidationError):
|
||||||
|
raise jsonrpc.InvalidParams(data={"message": str(exc)}) from exc
|
||||||
|
|
||||||
raise WorkflowRpcError(
|
raise WorkflowRpcError(
|
||||||
data={
|
data={
|
||||||
"code": exc.__class__.__name__,
|
"code": exc.__class__.__name__,
|
||||||
|
|||||||
@@ -112,4 +112,4 @@ def test_context_runtime_runner_uses_workflow_runtime_service(tmp_path: Path) ->
|
|||||||
service = WfMcpService(store=FileStore(tmp_path / "context_runtime"))
|
service = WfMcpService(store=FileStore(tmp_path / "context_runtime"))
|
||||||
context = context_from_service(service)
|
context = context_from_service(service)
|
||||||
|
|
||||||
assert context.runtime.runtime is service.workflow_runtime
|
assert getattr(context.runtime, "runtime") is service.workflow_runtime
|
||||||
|
|||||||
@@ -467,6 +467,81 @@ async def test_rpc_replace_document_replaces_complete_draft_workspace(
|
|||||||
assert after_malformed["result"] == inspected["result"]
|
assert after_malformed["result"] == inspected["result"]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_rpc_replace_document_rejects_invalid_object_without_mutation(
|
||||||
|
tmp_path,
|
||||||
|
) -> None:
|
||||||
|
server = build_local_static_workflow_server(tmp_path / "store")
|
||||||
|
app = create_rpc_app(server)
|
||||||
|
transport = httpx.ASGITransport(app=app)
|
||||||
|
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||||
|
await _rpc(
|
||||||
|
client,
|
||||||
|
"workflow.draft_workspaces.create_empty",
|
||||||
|
{"workspace_id": "report", "name": "initial"},
|
||||||
|
)
|
||||||
|
before = await _rpc(
|
||||||
|
client,
|
||||||
|
"workflow.draft_workspaces.get",
|
||||||
|
{"workspace_id": "report", "include_draft": True},
|
||||||
|
)
|
||||||
|
malformed = await _rpc(
|
||||||
|
client,
|
||||||
|
"workflow.draft_workspaces.replace_document",
|
||||||
|
{
|
||||||
|
"workspace_id": "report",
|
||||||
|
"revision": 1,
|
||||||
|
"draft": {"name": "missing-required-fields"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
after = await _rpc(
|
||||||
|
client,
|
||||||
|
"workflow.draft_workspaces.get",
|
||||||
|
{"workspace_id": "report", "include_draft": True},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert malformed["error"]["code"] == -32602
|
||||||
|
assert after["result"] == before["result"]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_rpc_replace_document_persists_semantically_invalid_draft(
|
||||||
|
tmp_path,
|
||||||
|
) -> None:
|
||||||
|
server = build_local_static_workflow_server(tmp_path / "store")
|
||||||
|
app = create_rpc_app(server)
|
||||||
|
transport = httpx.ASGITransport(app=app)
|
||||||
|
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
||||||
|
await _rpc(
|
||||||
|
client,
|
||||||
|
"workflow.draft_workspaces.create_empty",
|
||||||
|
{"workspace_id": "report", "name": "initial"},
|
||||||
|
)
|
||||||
|
initial = await _rpc(
|
||||||
|
client,
|
||||||
|
"workflow.draft_workspaces.get",
|
||||||
|
{"workspace_id": "report", "include_draft": True},
|
||||||
|
)
|
||||||
|
replacement = {**initial["result"]["draft"], "start": "missing_step"}
|
||||||
|
replaced = await _rpc(
|
||||||
|
client,
|
||||||
|
"workflow.draft_workspaces.replace_document",
|
||||||
|
{
|
||||||
|
"workspace_id": "report",
|
||||||
|
"revision": 1,
|
||||||
|
"draft": replacement,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
after = await _rpc(
|
||||||
|
client,
|
||||||
|
"workflow.draft_workspaces.get",
|
||||||
|
{"workspace_id": "report", "include_draft": True},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert replaced["result"]["status"] == "invalid"
|
||||||
|
assert replaced["result"]["diagnostics"]
|
||||||
|
assert after["result"]["draft"]["start"] == "missing_step"
|
||||||
|
assert after["result"]["diagnostics"] == replaced["result"]["diagnostics"]
|
||||||
|
|
||||||
|
|
||||||
async def test_rpc_draft_workspace_lifecycle_methods(tmp_path) -> None:
|
async def test_rpc_draft_workspace_lifecycle_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)
|
||||||
|
|||||||
Reference in New Issue
Block a user