feat: extend operational investigation workflows
This commit is contained in:
@@ -53,11 +53,12 @@ def test_calculation_submission_returns_receipt_without_running_native_code(runt
|
||||
assert response["result"]["structuredContent"]["semantics"] == "task"
|
||||
app.executor.assert_not_awaited()
|
||||
assert submit.await_args.args[3].timeout_seconds == 2
|
||||
assert submit.await_args.kwargs["job_timeout_seconds"] == 3600
|
||||
|
||||
|
||||
def test_calculation_limit_is_advertised_and_enforced_before_execution(runtime):
|
||||
app, client, calls, _ = runtime
|
||||
app.settings.max_calculation_timeout_seconds = 120
|
||||
app.settings.max_request_timeout_seconds = 120
|
||||
tools = request(client).json()["result"]["tools"]
|
||||
schema = next(tool["inputSchema"] for tool in tools if tool["name"] == "run_simulation")
|
||||
assert schema["properties"]["timeout_seconds"]["maximum"] == 120
|
||||
@@ -83,8 +84,19 @@ def test_queue_submission_errors_preserve_mcp_error_mapping(runtime, monkeypatch
|
||||
assert response["result"]["structuredContent"]["error"]["code"] == "QUERY_CONTEXT_STALE"
|
||||
|
||||
|
||||
def test_burst_location_default_inputs_pass_protocol_validation(runtime, monkeypatch):
|
||||
def test_expired_calculation_result_has_distinct_mcp_error(runtime):
|
||||
app, client, _, _ = runtime
|
||||
app.executor = AsyncMock(side_effect=HTTPException(410, "Calculation result retention period has expired"))
|
||||
response = request(client, "tools/call", {"name": "get_calculation_result", "arguments": {
|
||||
"context_handle": binding().context_handle, "job_id": str(uuid4()),
|
||||
}}).json()
|
||||
assert response["result"]["structuredContent"]["error"]["code"] == "RESULT_EXPIRED"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("maximum,expected", [(240, 120), (120, 120)])
|
||||
def test_burst_location_default_inputs_pass_protocol_validation(runtime, monkeypatch, maximum, expected):
|
||||
app, client, calls, _ = runtime
|
||||
app.settings.max_request_timeout_seconds = maximum
|
||||
from app.mcp import calculation_jobs
|
||||
executor = AsyncMock(return_value=calculation_jobs.envelope("run_burst_location", {"job_id": str(uuid4()), "status": "queued"}))
|
||||
monkeypatch.setattr(calculation_jobs, "submit_calculation", executor)
|
||||
@@ -92,6 +104,7 @@ def test_burst_location_default_inputs_pass_protocol_validation(runtime, monkeyp
|
||||
schema = next(tool["inputSchema"] for tool in request(client).json()["result"]["tools"]
|
||||
if tool["name"] == "run_burst_location")
|
||||
assert "pressure_scada_ids" not in schema["required"]
|
||||
assert schema["properties"]["timeout_seconds"]["default"] == expected
|
||||
response = request(client, "tools/call", {"name": "run_burst_location", "arguments": {
|
||||
"context_handle": binding().context_handle, "burst_leakage": 0.0274,
|
||||
"scada_burst_start": "2026-10-08T12:00:00+08:00",
|
||||
@@ -99,6 +112,7 @@ def test_burst_location_default_inputs_pass_protocol_validation(runtime, monkeyp
|
||||
}}).json()
|
||||
assert response["result"].get("isError") is not True
|
||||
assert calls == []
|
||||
assert executor.await_args.args[3].timeout_seconds == expected
|
||||
|
||||
|
||||
def settings():
|
||||
@@ -286,6 +300,37 @@ def binding(task=False):
|
||||
return value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("seconds", [30, 120, 240])
|
||||
def test_all_tools_advertise_the_effective_host_request_budget(runtime, seconds):
|
||||
app, client, _, _ = runtime
|
||||
app.settings.query_timeout_seconds = seconds
|
||||
for tool in request(client).json()["result"]["tools"]:
|
||||
assert tool["_meta"]["io.tjwater/request-timeout"] == {
|
||||
"defaultSeconds": seconds, "maxSeconds": 240,
|
||||
}
|
||||
timeout = tool["inputSchema"]["properties"].get("timeout_seconds")
|
||||
if timeout:
|
||||
assert timeout["default"] == seconds
|
||||
|
||||
|
||||
def test_omitted_calculation_http_budget_uses_server_configuration(runtime, monkeypatch):
|
||||
from app.mcp import calculation_jobs
|
||||
app, client, _, _ = runtime
|
||||
app.settings.query_timeout_seconds = 240
|
||||
monkeypatch.setattr(application, "authorize_query", AsyncMock())
|
||||
submit = AsyncMock(return_value=calculation_jobs.envelope(
|
||||
"run_simulation", {"job_id": str(uuid4()), "status": "queued"},
|
||||
))
|
||||
monkeypatch.setattr(calculation_jobs, "submit_calculation", submit)
|
||||
response = request(client, "tools/call", {"name": "run_simulation", "arguments": {
|
||||
"context_handle": binding().context_handle, "start_time": "2026-10-08T12:00:00+08:00",
|
||||
"duration": 3600, "output_node_ids": ["J1"],
|
||||
}}).json()
|
||||
assert response["result"].get("isError") is not True
|
||||
assert submit.await_args.args[3].timeout_seconds == 240
|
||||
assert submit.await_args.kwargs["job_timeout_seconds"] == 3600
|
||||
|
||||
|
||||
def test_modern_discovery_catalog_and_metadata(runtime):
|
||||
_, client, _, _ = runtime
|
||||
result = request(client, "server/discover").json()
|
||||
@@ -298,7 +343,7 @@ def test_modern_discovery_catalog_and_metadata(runtime):
|
||||
assert (timeout is not None) == (TOOLS[tool["name"]].computation or TOOLS[tool["name"]].mutating)
|
||||
if timeout:
|
||||
assert timeout["default"] == 120
|
||||
assert timeout["maximum"] == 3600
|
||||
assert timeout["maximum"] == 240
|
||||
assert listed["ttlMs"] == 300000 and listed["cacheScope"] == "public"
|
||||
assert all(
|
||||
tool["inputSchema"]["additionalProperties"] is False for tool in listed["tools"]
|
||||
|
||||
Reference in New Issue
Block a user