feat: extend operational investigation workflows
Generic Container CI/CD / test-build-publish (push) Successful in 3m53s
Server CI/CD v2 / build-test-publish-and-deploy (push) Successful in 3m52s

This commit is contained in:
2026-10-30 17:30:19 +08:00
parent 63af998045
commit 05183aff93
82 changed files with 4644 additions and 330 deletions
+48 -3
View File
@@ -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"]