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.
150 lines
4.6 KiB
Python
150 lines
4.6 KiB
Python
"""Integration tests for API versioning and OpenAPI/docs endpoints."""
|
|
|
|
import queue
|
|
import threading
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
# Shared test auth — JWT-based
|
|
_TEST_JWT_SECRET = "test-jwt-secret-minimum-32-chars!"
|
|
|
|
|
|
def _server_jwt() -> str:
|
|
from turnstone.core.auth import JWT_AUD_SERVER, create_jwt
|
|
|
|
return create_jwt(
|
|
user_id="test-versioning",
|
|
scopes=frozenset({"read", "write", "approve", "service"}),
|
|
source="test",
|
|
secret=_TEST_JWT_SECRET,
|
|
audience=JWT_AUD_SERVER,
|
|
)
|
|
|
|
|
|
def _console_jwt() -> str:
|
|
from turnstone.core.auth import JWT_AUD_CONSOLE, create_jwt
|
|
|
|
return create_jwt(
|
|
user_id="test-versioning",
|
|
scopes=frozenset({"read", "write", "approve", "service"}),
|
|
source="test",
|
|
secret=_TEST_JWT_SECRET,
|
|
audience=JWT_AUD_CONSOLE,
|
|
)
|
|
|
|
|
|
_SERVER_AUTH_HEADERS = {"Authorization": f"Bearer {_server_jwt()}"}
|
|
_CONSOLE_AUTH_HEADERS = {"Authorization": f"Bearer {_console_jwt()}"}
|
|
|
|
|
|
class TestServerVersioning:
|
|
"""Test /v1/ routes and OpenAPI endpoints on the server."""
|
|
|
|
@pytest.fixture()
|
|
def client(self):
|
|
from starlette.testclient import TestClient
|
|
|
|
from turnstone.server import create_app
|
|
|
|
mock_mgr = MagicMock()
|
|
mock_mgr.list_all.return_value = []
|
|
mock_mgr.max_active = 10
|
|
app = create_app(
|
|
workstreams=mock_mgr,
|
|
global_queue=queue.Queue(),
|
|
global_listeners=[],
|
|
global_listeners_lock=threading.Lock(),
|
|
skip_permissions=False,
|
|
jwt_secret=_TEST_JWT_SECRET,
|
|
)
|
|
client = TestClient(app, raise_server_exceptions=False)
|
|
yield client
|
|
client.close()
|
|
|
|
def test_v1_workstreams(self, client):
|
|
resp = client.get("/v1/api/workstreams", headers=_SERVER_AUTH_HEADERS)
|
|
assert resp.status_code == 200
|
|
assert "workstreams" in resp.json()
|
|
|
|
def test_unversioned_api_404(self, client):
|
|
resp = client.get("/api/workstreams", headers=_SERVER_AUTH_HEADERS)
|
|
assert resp.status_code == 404
|
|
|
|
def test_openapi_json(self, client):
|
|
resp = client.get("/openapi.json")
|
|
assert resp.status_code == 200
|
|
spec = resp.json()
|
|
assert spec["openapi"] == "3.1.0"
|
|
assert "/v1/api/workstreams/{ws_id}/send" in spec["paths"]
|
|
|
|
def test_docs_page(self, client):
|
|
resp = client.get("/docs")
|
|
assert resp.status_code == 200
|
|
assert "swagger-ui" in resp.text.lower()
|
|
|
|
def test_health_unversioned(self, client):
|
|
resp = client.get("/health")
|
|
assert resp.status_code == 200
|
|
assert "status" in resp.json()
|
|
|
|
def test_shared_static_unversioned(self, client):
|
|
resp = client.get("/shared/base.css")
|
|
assert resp.status_code == 200
|
|
|
|
|
|
class TestConsoleVersioning:
|
|
"""Test /v1/ routes and OpenAPI endpoints on the console."""
|
|
|
|
@pytest.fixture()
|
|
def client(self):
|
|
from starlette.testclient import TestClient
|
|
|
|
from turnstone.console.collector import ClusterCollector
|
|
from turnstone.console.server import _load_static, create_app
|
|
|
|
_load_static()
|
|
collector = MagicMock(spec=ClusterCollector)
|
|
collector.get_overview.return_value = {
|
|
"nodes": 0,
|
|
"workstreams": 0,
|
|
"states": {},
|
|
"aggregate": {},
|
|
}
|
|
app = create_app(
|
|
collector=collector,
|
|
jwt_secret=_TEST_JWT_SECRET,
|
|
)
|
|
client = TestClient(app, raise_server_exceptions=False)
|
|
yield client
|
|
client.close()
|
|
|
|
def test_v1_cluster_overview(self, client):
|
|
resp = client.get("/v1/api/cluster/overview", headers=_CONSOLE_AUTH_HEADERS)
|
|
assert resp.status_code == 200
|
|
|
|
def test_unversioned_api_404(self, client):
|
|
resp = client.get("/api/cluster/overview", headers=_CONSOLE_AUTH_HEADERS)
|
|
assert resp.status_code == 404
|
|
|
|
def test_openapi_json(self, client):
|
|
resp = client.get("/openapi.json")
|
|
assert resp.status_code == 200
|
|
spec = resp.json()
|
|
assert spec["openapi"] == "3.1.0"
|
|
assert "/v1/api/cluster/overview" in spec["paths"]
|
|
|
|
def test_docs_page(self, client):
|
|
resp = client.get("/docs")
|
|
assert resp.status_code == 200
|
|
assert "swagger-ui" in resp.text.lower()
|
|
|
|
def test_health_unversioned(self, client):
|
|
resp = client.get("/health")
|
|
assert resp.status_code == 200
|
|
|
|
def test_console_app_js_uses_v1_paths(self, client):
|
|
resp = client.get("/static/app.js")
|
|
body = resp.text
|
|
assert "/v1/api/cluster" in body
|