mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
110d44b07e
`man` and `math` duplicated capabilities already reachable through `bash`; `plan_agent` is better expressed as a `task_agent` running a planning skill, and carried a large amount of special-case machinery (plan-review gate, refinement loop, per-kind model routing). Removing all three shrinks the tool surface and cuts per-call token cost. Also removed, as dead-once-the-tools-are-gone: - the `math` sandbox executor (`turnstone.core.sandbox`) and its `[sandbox]` extra; the eval analyst now runs bash-only - the read-only `AGENT_TOOLS` sub-agent tool set and the `agent` tool-metadata key (`task_agent`/`TASK_AGENT_TOOLS` retained) - the plan-review protocol end to end: the `on_plan_review` UI hook, `resolve_plan`, `POST /v1/api/plan` + `POST /v1/api/route/plan`, the `plan_review`/`plan_resolved` SSE events, and their Python SDK / TypeScript SDK / OpenAPI / frontend / Discord+Slack bindings - the `model.plan_alias` / `model.plan_effort` settings and the registry `plan_model` / `plan_effort` routing fields TOOLS 31->28, TASK_AGENT_TOOLS 13->11; COORDINATOR_TOOLS unchanged. BREAKING CHANGE: removes the `man`, `math`, `plan_agent` tools, the plan-review SSE/HTTP/SDK surface, and the plan_* model-routing settings from the experimental 1.6 line.
213 lines
8.2 KiB
Python
213 lines
8.2 KiB
Python
"""Tests for OpenAPI spec generation."""
|
|
|
|
import json
|
|
|
|
|
|
class TestServerSpec:
|
|
"""Validate the generated server OpenAPI spec."""
|
|
|
|
def test_valid_openapi_version(self):
|
|
from turnstone.api.server_spec import build_server_spec
|
|
|
|
spec = build_server_spec()
|
|
assert spec["openapi"] == "3.1.0"
|
|
|
|
def test_has_info(self):
|
|
from turnstone.api.server_spec import build_server_spec
|
|
|
|
spec = build_server_spec()
|
|
assert "title" in spec["info"]
|
|
assert "version" in spec["info"]
|
|
|
|
def test_has_all_api_endpoints(self):
|
|
from turnstone.api.server_spec import build_server_spec
|
|
|
|
spec = build_server_spec()
|
|
paths = set(spec["paths"].keys())
|
|
expected = {
|
|
"/v1/api/workstreams",
|
|
"/v1/api/workstreams/{ws_id}",
|
|
"/v1/api/workstreams/{ws_id}/history",
|
|
"/v1/api/workstreams/{ws_id}/send",
|
|
"/v1/api/workstreams/{ws_id}/approve",
|
|
"/v1/api/workstreams/{ws_id}/cancel",
|
|
"/v1/api/workstreams/{ws_id}/rewind",
|
|
"/v1/api/workstreams/{ws_id}/retry",
|
|
"/v1/api/workstreams/{ws_id}/close",
|
|
"/v1/api/workstreams/{ws_id}/events",
|
|
"/v1/api/dashboard",
|
|
"/v1/api/workstreams/saved",
|
|
"/v1/api/command",
|
|
"/v1/api/events/global",
|
|
"/v1/api/workstreams/new",
|
|
"/v1/api/workstreams/{ws_id}/speech-to-text",
|
|
"/v1/api/tts",
|
|
"/v1/api/auth/login",
|
|
"/v1/api/auth/logout",
|
|
"/health",
|
|
}
|
|
assert expected.issubset(paths), f"Missing: {expected - paths}"
|
|
|
|
def test_voice_endpoints_documented(self):
|
|
from turnstone.api.server_spec import build_server_spec
|
|
|
|
spec = build_server_spec()
|
|
stt = spec["paths"]["/v1/api/workstreams/{ws_id}/speech-to-text"]["post"]
|
|
tts = spec["paths"]["/v1/api/tts"]["post"]
|
|
assert "responses" in stt
|
|
assert "requestBody" in tts
|
|
assert "application/json" in tts["requestBody"]["content"]
|
|
schemas = spec["components"]["schemas"]
|
|
assert "capabilities" in schemas["AvailableModelInfo"]["properties"]
|
|
models_props = schemas["ListAvailableModelsResponse"]["properties"]
|
|
assert "stt_default_alias" in models_props
|
|
assert "tts_default_alias" in models_props
|
|
|
|
def test_workstream_history_has_limit_query_param(self):
|
|
"""Mirror of the coord-side history limit param test — server now
|
|
exposes the same endpoint via the lifted factory."""
|
|
from turnstone.api.server_spec import build_server_spec
|
|
|
|
spec = build_server_spec()
|
|
op = spec["paths"]["/v1/api/workstreams/{ws_id}/history"]["get"]
|
|
param_names = [p["name"] for p in op.get("parameters", [])]
|
|
assert "ws_id" in param_names
|
|
assert "limit" in param_names
|
|
|
|
def test_schemas_not_empty(self):
|
|
from turnstone.api.server_spec import build_server_spec
|
|
|
|
spec = build_server_spec()
|
|
assert len(spec["components"]["schemas"]) > 0
|
|
|
|
def test_json_serializable(self):
|
|
from turnstone.api.server_spec import build_server_spec
|
|
|
|
spec = build_server_spec()
|
|
result = json.dumps(spec)
|
|
assert len(result) > 100
|
|
|
|
def test_send_endpoint_has_request_body(self):
|
|
from turnstone.api.server_spec import build_server_spec
|
|
|
|
spec = build_server_spec()
|
|
send = spec["paths"]["/v1/api/workstreams/{ws_id}/send"]["post"]
|
|
assert "requestBody" in send
|
|
assert "application/json" in send["requestBody"]["content"]
|
|
|
|
def test_health_endpoint_not_versioned(self):
|
|
from turnstone.api.server_spec import build_server_spec
|
|
|
|
spec = build_server_spec()
|
|
assert "/health" in spec["paths"]
|
|
assert "/v1/health" not in spec["paths"]
|
|
|
|
|
|
class TestConsoleSpec:
|
|
"""Validate the generated console OpenAPI spec."""
|
|
|
|
def test_valid_openapi_version(self):
|
|
from turnstone.api.console_spec import build_console_spec
|
|
|
|
spec = build_console_spec()
|
|
assert spec["openapi"] == "3.1.0"
|
|
|
|
def test_has_cluster_endpoints(self):
|
|
from turnstone.api.console_spec import build_console_spec
|
|
|
|
spec = build_console_spec()
|
|
paths = set(spec["paths"].keys())
|
|
expected = {
|
|
"/v1/api/cluster/overview",
|
|
"/v1/api/cluster/nodes",
|
|
"/v1/api/cluster/workstreams",
|
|
"/v1/api/cluster/node/{node_id}",
|
|
"/v1/api/cluster/workstreams/new",
|
|
"/v1/api/cluster/events",
|
|
}
|
|
assert expected.issubset(paths), f"Missing: {expected - paths}"
|
|
|
|
def test_json_serializable(self):
|
|
from turnstone.api.console_spec import build_console_spec
|
|
|
|
spec = build_console_spec()
|
|
result = json.dumps(spec)
|
|
assert len(result) > 100
|
|
|
|
def test_nodes_endpoint_has_query_params(self):
|
|
from turnstone.api.console_spec import build_console_spec
|
|
|
|
spec = build_console_spec()
|
|
nodes = spec["paths"]["/v1/api/cluster/nodes"]["get"]
|
|
assert "parameters" in nodes
|
|
param_names = [p["name"] for p in nodes["parameters"]]
|
|
assert "sort" in param_names
|
|
assert "limit" in param_names
|
|
|
|
def test_has_coordinator_endpoints(self):
|
|
"""Phase 1-3 coordinator routes must appear in the OpenAPI catalog —
|
|
the spec was missing every coordinator endpoint except ``/open``,
|
|
so SDK consumers and operators couldn't discover the surface
|
|
from /docs. Pin the full set so a future regression that drops
|
|
one fails loudly."""
|
|
from turnstone.api.console_spec import build_console_spec
|
|
|
|
spec = build_console_spec()
|
|
paths = set(spec["paths"].keys())
|
|
expected = {
|
|
"/v1/api/workstreams/new",
|
|
"/v1/api/workstreams",
|
|
"/v1/api/workstreams/{ws_id}",
|
|
"/v1/api/workstreams/{ws_id}/open",
|
|
"/v1/api/workstreams/{ws_id}/send",
|
|
"/v1/api/workstreams/{ws_id}/approve",
|
|
"/v1/api/workstreams/{ws_id}/cancel",
|
|
"/v1/api/workstreams/{ws_id}/rewind",
|
|
"/v1/api/workstreams/{ws_id}/retry",
|
|
"/v1/api/workstreams/{ws_id}/close",
|
|
"/v1/api/workstreams/{ws_id}/events",
|
|
"/v1/api/workstreams/{ws_id}/history",
|
|
"/v1/api/workstreams/{ws_id}/children",
|
|
"/v1/api/workstreams/{ws_id}/tasks",
|
|
"/v1/api/cluster/ws/{ws_id}/detail",
|
|
}
|
|
assert expected.issubset(paths), f"Missing: {expected - paths}"
|
|
|
|
def test_coordinator_create_has_request_body_and_200(self):
|
|
"""Coordinator create returns 200 and accepts a body.
|
|
|
|
Pre-1.5.0 this returned 201 (REST-strict for create); the lifted
|
|
``make_create_handler`` factory converges on 200 across both
|
|
kinds for response-shape parity with every other shared verb.
|
|
"""
|
|
from turnstone.api.console_spec import build_console_spec
|
|
|
|
spec = build_console_spec()
|
|
op = spec["paths"]["/v1/api/workstreams/new"]["post"]
|
|
assert "requestBody" in op
|
|
assert "application/json" in op["requestBody"]["content"]
|
|
assert "200" in op["responses"]
|
|
|
|
def test_coordinator_history_has_limit_query_param(self):
|
|
from turnstone.api.console_spec import build_console_spec
|
|
|
|
spec = build_console_spec()
|
|
op = spec["paths"]["/v1/api/workstreams/{ws_id}/history"]["get"]
|
|
param_names = [p["name"] for p in op.get("parameters", [])]
|
|
assert "ws_id" in param_names # auto-added from path
|
|
assert "limit" in param_names
|
|
|
|
def test_coordinator_endpoints_share_tag(self):
|
|
"""All coordinator endpoints (including the cluster-inspect one)
|
|
live under the same OpenAPI tag so /docs groups them together."""
|
|
from turnstone.api.console_spec import build_console_spec
|
|
|
|
spec = build_console_spec()
|
|
coord_paths = [p for p in spec["paths"] if "/coordinator" in p]
|
|
coord_paths.append("/v1/api/cluster/ws/{ws_id}/detail")
|
|
for path in coord_paths:
|
|
for op in spec["paths"][path].values():
|
|
assert "Coordinator" in op.get("tags", []), (
|
|
f"{path} missing Coordinator tag (tags={op.get('tags')})"
|
|
)
|