revert check fix, add test cov

This commit is contained in:
lda
2026-07-30 02:03:29 +07:00 Verified
parent 981edd3cf5
commit 005a96c9ff
7 changed files with 115 additions and 28 deletions
+2 -2
View File
@@ -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:
+16 -17
View File
@@ -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
View File
@@ -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"]
+6 -6
View File
@@ -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"),
) )
+6 -1
View File
@@ -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__,
+1 -1
View File
@@ -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
+75
View File
@@ -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)