mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
1358121d52
Mechanical updates across the test suite to swap legacy
/v1/api/{send,approve,cancel,events,workstreams/close} URLs for the
path-keyed equivalents under /v1/api/workstreams/{ws_id}/<verb>, and
to drop ws_id from request bodies (the path provides it now).
Per file:
- test_session_routes.py: deletes test_close_legacy_mounts_when_handler_provided
(the close_legacy slot is gone); test_send_mounts_post_and_delete_when_dequeue_provided
(added in PR commit 1) stays.
- test_openapi.py: expected-paths set swaps to path-keyed shape;
test_send_endpoint_has_request_body now asserts the OpenAPI for
/v1/api/workstreams/{ws_id}/send.
- test_auth.py / test_auth_identity.py: required_scope and
check_request fixtures swap to path-keyed shape; new tests cover
write/approve/read scope assignment for the path-keyed verbs +
the /node/* proxy mirror.
- test_sdk_server.py / test_sdk_console.py: mock-transport URL keys
swap; bodies drop ws_id.
- test_server_attachments_endpoints.py: ~17 send sites migrated to
/v1/api/workstreams/<ws>/send (a small Python script ran the bulk
rewrite — body ws_id stripped, URL rebuilt).
- test_server_authz.py: cross-tenant approve/close/cancel/events
tests retargeted to path-keyed URLs;
test_events_legacy_query_keyed_url_still_resolves_to_404_for_unknown_ws
renamed to test_events_path_keyed_url_resolves_to_404_for_unknown_ws
with the docstring updated to note the legacy adapter is gone.
- test_close_reason_persistence.py: 7 close sites all swap.
- test_console_routing_proxy.py: route-proxy tests swap to
/v1/api/route/workstreams/{ws_id}/<verb>; the upstream-URL
assertion now reads from .request (route_proxy uses
client.request(method, url, ...) for method passthrough); _wire_proxy
helper installs both .post and .request mocks for compatibility.
- test_route_proxy_audit.py: parametrized URLs migrated;
_make_proxy now also exposes a .request side-effect that delegates
to .post for the same compatibility surface.
- test_api_versioning.py: openapi.json path assertion swaps to the
path-keyed shape.
4557 passing under -m "not live"; ruff + mypy clean.
193 lines
7.2 KiB
Python
193 lines
7.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}/close",
|
|
"/v1/api/workstreams/{ws_id}/events",
|
|
"/v1/api/dashboard",
|
|
"/v1/api/workstreams/saved",
|
|
"/v1/api/plan",
|
|
"/v1/api/command",
|
|
"/v1/api/events/global",
|
|
"/v1/api/workstreams/new",
|
|
"/v1/api/auth/login",
|
|
"/v1/api/auth/logout",
|
|
"/health",
|
|
}
|
|
assert expected.issubset(paths), f"Missing: {expected - paths}"
|
|
|
|
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}/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')})"
|
|
)
|