From 2461a923be7ff93b0d63496f41a9c14061e0e69b Mon Sep 17 00:00:00 2001 From: 1445043649 <> Date: Sun, 28 Jun 2026 19:49:05 +0800 Subject: [PATCH] feat(gateway): dual-mount legacy routers on /api and /api/v1 (Stage 1 PR5) Strip /api prefix from 13 legacy router APIRouter() declarations and dual-mount each on prefix="/api" (backward compat) and prefix="/api/v1" (versioned surface) in app.py. Auth, service-accounts, api-keys, assistants-compat remain single-mount. Update 10 test files to pass prefix="/api" when directly including stripped routers. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/app/gateway/app.py | 76 +++++++++++-------- backend/app/gateway/routers/agents.py | 2 +- backend/app/gateway/routers/artifacts.py | 2 +- backend/app/gateway/routers/channels.py | 2 +- backend/app/gateway/routers/feedback.py | 2 +- backend/app/gateway/routers/mcp.py | 2 +- backend/app/gateway/routers/memory.py | 2 +- backend/app/gateway/routers/models.py | 2 +- backend/app/gateway/routers/runs.py | 2 +- backend/app/gateway/routers/skills.py | 2 +- backend/app/gateway/routers/suggestions.py | 2 +- backend/app/gateway/routers/thread_runs.py | 2 +- backend/app/gateway/routers/threads.py | 2 +- backend/app/gateway/routers/uploads.py | 2 +- backend/tests/test_api_v1_dual_mount.py | 51 +++++++++++++ backend/tests/test_artifacts_router.py | 4 +- backend/tests/test_custom_agent.py | 2 +- backend/tests/test_memory_router.py | 24 +++--- backend/tests/test_runs_api_endpoints.py | 2 +- backend/tests/test_skills_custom_router.py | 2 +- .../test_thread_run_messages_pagination.py | 2 +- backend/tests/test_threads_post_workspace.py | 2 +- backend/tests/test_threads_router.py | 8 +- backend/tests/test_uploads_router.py | 2 +- .../test_workspace_isolation_boundary.py | 2 +- 25 files changed, 135 insertions(+), 68 deletions(-) create mode 100644 backend/tests/test_api_v1_dual_mount.py diff --git a/backend/app/gateway/app.py b/backend/app/gateway/app.py index c1afb214..04739490 100644 --- a/backend/app/gateway/app.py +++ b/backend/app/gateway/app.py @@ -381,56 +381,72 @@ This gateway provides custom endpoints for models, MCP configuration, skills, an ) # Include routers - # Models API is mounted at /api/models - app.include_router(models.router) + # Legacy routers are dual-mounted on /api (backward compat) and /api/v1 (versioned). + # The deprecation middleware (Task 5.1) stamps X-API-Deprecated on /api responses. - # MCP API is mounted at /api/mcp - app.include_router(mcp.router) + # Models API — /api/models and /api/v1/models + app.include_router(models.router, prefix="/api") + app.include_router(models.router, prefix="/api/v1") - # Memory API is mounted at /api/memory - app.include_router(memory.router) + # MCP API — /api/mcp and /api/v1/mcp + app.include_router(mcp.router, prefix="/api") + app.include_router(mcp.router, prefix="/api/v1") - # Skills API is mounted at /api/skills - app.include_router(skills.router) + # Memory API — /api/memory and /api/v1/memory + app.include_router(memory.router, prefix="/api") + app.include_router(memory.router, prefix="/api/v1") - # Artifacts API is mounted at /api/threads/{thread_id}/artifacts - app.include_router(artifacts.router) + # Skills API — /api/skills and /api/v1/skills + app.include_router(skills.router, prefix="/api") + app.include_router(skills.router, prefix="/api/v1") - # Uploads API is mounted at /api/threads/{thread_id}/uploads - app.include_router(uploads.router) + # Artifacts API — /api/threads/{thread_id}/artifacts and /api/v1/threads/{thread_id}/artifacts + app.include_router(artifacts.router, prefix="/api") + app.include_router(artifacts.router, prefix="/api/v1") - # Thread cleanup API is mounted at /api/threads/{thread_id} - app.include_router(threads.router) + # Uploads API — /api/threads/{thread_id}/uploads and /api/v1/threads/{thread_id}/uploads + app.include_router(uploads.router, prefix="/api") + app.include_router(uploads.router, prefix="/api/v1") - # Agents API is mounted at /api/agents - app.include_router(agents.router) + # Threads API — /api/threads/{thread_id} and /api/v1/threads/{thread_id} + app.include_router(threads.router, prefix="/api") + app.include_router(threads.router, prefix="/api/v1") - # Suggestions API is mounted at /api/threads/{thread_id}/suggestions - app.include_router(suggestions.router) + # Agents API — /api/agents and /api/v1/agents + app.include_router(agents.router, prefix="/api") + app.include_router(agents.router, prefix="/api/v1") - # Channels API is mounted at /api/channels - app.include_router(channels.router) + # Suggestions API — /api/threads/{thread_id}/suggestions and /api/v1/threads/{thread_id}/suggestions + app.include_router(suggestions.router, prefix="/api") + app.include_router(suggestions.router, prefix="/api/v1") - # Assistants compatibility API (LangGraph Platform stub) + # Channels API — /api/channels and /api/v1/channels + app.include_router(channels.router, prefix="/api") + app.include_router(channels.router, prefix="/api/v1") + + # Assistants compatibility API (LangGraph Platform stub) — intentionally NOT dual-mounted app.include_router(assistants_compat.router) - # Auth API is mounted at /api/v1/auth + # Auth API — /api/v1/auth only (already versioned; must NOT get an /api/auth twin) app.include_router(auth.router) - # Service Accounts API is mounted at /api/v1/service-accounts + # Service Accounts API — /api/v1/service-accounts only (already versioned) app.include_router(service_accounts.router) - # API Keys API is mounted at /api/v1/api-keys + # API Keys API — /api/v1/api-keys only (already versioned) app.include_router(api_keys.router) - # Feedback API is mounted at /api/threads/{thread_id}/runs/{run_id}/feedback - app.include_router(feedback.router) + # Feedback API — /api/threads/{thread_id}/runs/{run_id}/feedback and /api/v1/... twin + app.include_router(feedback.router, prefix="/api") + app.include_router(feedback.router, prefix="/api/v1") - # Thread Runs API (LangGraph Platform-compatible runs lifecycle) - app.include_router(thread_runs.router) + # Thread Runs API — /api/threads/{thread_id}/runs and /api/v1/... twin + app.include_router(thread_runs.router, prefix="/api") + app.include_router(thread_runs.router, prefix="/api/v1") - # Stateless Runs API (stream/wait without a pre-existing thread) - app.include_router(runs.router) + # Stateless Runs API — /api/runs and /api/v1/runs + app.include_router(runs.router, prefix="/api") + app.include_router(runs.router, prefix="/api/v1") @app.get("/health", tags=["health"]) async def health_check() -> dict: diff --git a/backend/app/gateway/routers/agents.py b/backend/app/gateway/routers/agents.py index 8769e983..c001e8fb 100644 --- a/backend/app/gateway/routers/agents.py +++ b/backend/app/gateway/routers/agents.py @@ -14,7 +14,7 @@ from deerflow.config.paths import get_paths from deerflow.runtime.user_context import get_effective_user_id logger = logging.getLogger(__name__) -router = APIRouter(prefix="/api", tags=["agents"]) +router = APIRouter(tags=["agents"]) AGENT_NAME_PATTERN = re.compile(r"^[A-Za-z0-9-]+$") diff --git a/backend/app/gateway/routers/artifacts.py b/backend/app/gateway/routers/artifacts.py index 78ea5fa0..f0b65bdd 100644 --- a/backend/app/gateway/routers/artifacts.py +++ b/backend/app/gateway/routers/artifacts.py @@ -12,7 +12,7 @@ from app.gateway.path_utils import resolve_thread_virtual_path logger = logging.getLogger(__name__) -router = APIRouter(prefix="/api", tags=["artifacts"]) +router = APIRouter(tags=["artifacts"]) ACTIVE_CONTENT_MIME_TYPES = { "text/html", diff --git a/backend/app/gateway/routers/channels.py b/backend/app/gateway/routers/channels.py index abd8aa15..9510840b 100644 --- a/backend/app/gateway/routers/channels.py +++ b/backend/app/gateway/routers/channels.py @@ -9,7 +9,7 @@ from pydantic import BaseModel logger = logging.getLogger(__name__) -router = APIRouter(prefix="/api/channels", tags=["channels"]) +router = APIRouter(prefix="/channels", tags=["channels"]) class ChannelStatusResponse(BaseModel): diff --git a/backend/app/gateway/routers/feedback.py b/backend/app/gateway/routers/feedback.py index ca5c1d40..24579623 100644 --- a/backend/app/gateway/routers/feedback.py +++ b/backend/app/gateway/routers/feedback.py @@ -16,7 +16,7 @@ from app.gateway.authz import require_permission from app.gateway.deps import get_current_user, get_feedback_repo, get_run_store logger = logging.getLogger(__name__) -router = APIRouter(prefix="/api/threads", tags=["feedback"]) +router = APIRouter(prefix="/threads", tags=["feedback"]) # --------------------------------------------------------------------------- diff --git a/backend/app/gateway/routers/mcp.py b/backend/app/gateway/routers/mcp.py index 386fc13c..0de33788 100644 --- a/backend/app/gateway/routers/mcp.py +++ b/backend/app/gateway/routers/mcp.py @@ -9,7 +9,7 @@ from pydantic import BaseModel, Field from deerflow.config.extensions_config import ExtensionsConfig, get_extensions_config, reload_extensions_config logger = logging.getLogger(__name__) -router = APIRouter(prefix="/api", tags=["mcp"]) +router = APIRouter(tags=["mcp"]) class McpOAuthConfigResponse(BaseModel): diff --git a/backend/app/gateway/routers/memory.py b/backend/app/gateway/routers/memory.py index ca9e5f5e..f45f902d 100644 --- a/backend/app/gateway/routers/memory.py +++ b/backend/app/gateway/routers/memory.py @@ -15,7 +15,7 @@ from deerflow.agents.memory.updater import ( from deerflow.config.memory_config import get_memory_config from deerflow.runtime.user_context import get_effective_user_id -router = APIRouter(prefix="/api", tags=["memory"]) +router = APIRouter(tags=["memory"]) class ContextSection(BaseModel): diff --git a/backend/app/gateway/routers/models.py b/backend/app/gateway/routers/models.py index a36ece92..6b13b8c3 100644 --- a/backend/app/gateway/routers/models.py +++ b/backend/app/gateway/routers/models.py @@ -4,7 +4,7 @@ from pydantic import BaseModel, Field from app.gateway.deps import get_config from deerflow.config.app_config import AppConfig -router = APIRouter(prefix="/api", tags=["models"]) +router = APIRouter(tags=["models"]) class ModelResponse(BaseModel): diff --git a/backend/app/gateway/routers/runs.py b/backend/app/gateway/routers/runs.py index f2775466..624fd0a8 100644 --- a/backend/app/gateway/routers/runs.py +++ b/backend/app/gateway/routers/runs.py @@ -21,7 +21,7 @@ from app.gateway.services import sse_consumer, start_run from deerflow.runtime import serialize_channel_values logger = logging.getLogger(__name__) -router = APIRouter(prefix="/api/runs", tags=["runs"]) +router = APIRouter(prefix="/runs", tags=["runs"]) def _resolve_thread_id(body: RunCreateRequest) -> str: diff --git a/backend/app/gateway/routers/skills.py b/backend/app/gateway/routers/skills.py index 78462ae0..d1ffd117 100644 --- a/backend/app/gateway/routers/skills.py +++ b/backend/app/gateway/routers/skills.py @@ -18,7 +18,7 @@ from deerflow.skills.types import SKILL_MD_FILE, SkillCategory logger = logging.getLogger(__name__) -router = APIRouter(prefix="/api", tags=["skills"]) +router = APIRouter(tags=["skills"]) class SkillResponse(BaseModel): diff --git a/backend/app/gateway/routers/suggestions.py b/backend/app/gateway/routers/suggestions.py index 56d99b9d..af0bbcd8 100644 --- a/backend/app/gateway/routers/suggestions.py +++ b/backend/app/gateway/routers/suggestions.py @@ -12,7 +12,7 @@ from deerflow.models import create_chat_model logger = logging.getLogger(__name__) -router = APIRouter(prefix="/api", tags=["suggestions"]) +router = APIRouter(tags=["suggestions"]) class SuggestionMessage(BaseModel): diff --git a/backend/app/gateway/routers/thread_runs.py b/backend/app/gateway/routers/thread_runs.py index e6847c50..4a7f9230 100644 --- a/backend/app/gateway/routers/thread_runs.py +++ b/backend/app/gateway/routers/thread_runs.py @@ -25,7 +25,7 @@ from app.gateway.services import sse_consumer, start_run from deerflow.runtime import RunRecord, serialize_channel_values logger = logging.getLogger(__name__) -router = APIRouter(prefix="/api/threads", tags=["runs"]) +router = APIRouter(prefix="/threads", tags=["runs"]) # --------------------------------------------------------------------------- diff --git a/backend/app/gateway/routers/threads.py b/backend/app/gateway/routers/threads.py index cb048152..7ce60d89 100644 --- a/backend/app/gateway/routers/threads.py +++ b/backend/app/gateway/routers/threads.py @@ -29,7 +29,7 @@ from deerflow.runtime.user_context import get_effective_user_id from deerflow.utils.time import coerce_iso, now_iso logger = logging.getLogger(__name__) -router = APIRouter(prefix="/api/threads", tags=["threads"]) +router = APIRouter(prefix="/threads", tags=["threads"]) # Metadata keys that the server controls; clients are not allowed to set diff --git a/backend/app/gateway/routers/uploads.py b/backend/app/gateway/routers/uploads.py index a4267f72..0d2f39af 100644 --- a/backend/app/gateway/routers/uploads.py +++ b/backend/app/gateway/routers/uploads.py @@ -30,7 +30,7 @@ from deerflow.utils.file_conversion import CONVERTIBLE_EXTENSIONS, convert_file_ logger = logging.getLogger(__name__) -router = APIRouter(prefix="/api/threads/{thread_id}/uploads", tags=["uploads"]) +router = APIRouter(prefix="/threads/{thread_id}/uploads", tags=["uploads"]) UPLOAD_CHUNK_SIZE = 8192 DEFAULT_MAX_FILES = 10 diff --git a/backend/tests/test_api_v1_dual_mount.py b/backend/tests/test_api_v1_dual_mount.py new file mode 100644 index 00000000..7f14878c --- /dev/null +++ b/backend/tests/test_api_v1_dual_mount.py @@ -0,0 +1,51 @@ +"""Dual-mount /api + /api/v1 tests (Stage 1 PR5). + +Every migrated legacy router must answer on BOTH /api/ and /api/v1/. +Asserts route presence on the OpenAPI schema (independent of per-route auth). +""" + +from __future__ import annotations + +from app.gateway.app import create_app + + +def _paths(): + return set(create_app().openapi()["paths"].keys()) + + +def test_models_dual_mounted(): + paths = _paths() + assert "/api/models" in paths + assert "/api/v1/models" in paths + + +def test_runs_dual_mounted(): + paths = _paths() + assert "/api/runs/stream" in paths + assert "/api/v1/runs/stream" in paths + + +def test_threads_dual_mounted(): + paths = _paths() + # at least one threads sub-path must exist on both surfaces + assert any(p.startswith("/api/threads/") for p in paths) + assert any(p.startswith("/api/v1/threads/") for p in paths) + + +def test_uploads_dual_mounted(): + paths = _paths() + assert any(p.startswith("/api/threads/") and "/uploads" in p for p in paths) + assert any(p.startswith("/api/v1/threads/") and "/uploads" in p for p in paths) + + +def test_auth_only_v1_not_dual(): + # auth stays v1-only — must NOT acquire an /api/auth twin. + paths = _paths() + assert "/api/v1/auth/me" in paths + assert "/api/auth/me" not in paths + + +def test_no_v1_langgraph_twins(): + paths = _paths() + assert not any(p.startswith("/api/v1/langgraph") for p in paths) + assert not any(p.startswith("/api/v1/assistants") for p in paths) diff --git a/backend/tests/test_artifacts_router.py b/backend/tests/test_artifacts_router.py index df32e45d..47b7c86c 100644 --- a/backend/tests/test_artifacts_router.py +++ b/backend/tests/test_artifacts_router.py @@ -76,7 +76,7 @@ def test_get_artifact_download_false_does_not_force_attachment(tmp_path, monkeyp monkeypatch.setattr(artifacts_router, "resolve_thread_virtual_path", lambda _thread_id, _path: artifact_path) app = make_authed_test_app() - app.include_router(artifacts_router.router) + app.include_router(artifacts_router.router, prefix="/api") with TestClient(app) as client: response = client.get("/api/threads/thread-1/artifacts/mnt/user-data/outputs/note.txt?download=false") @@ -94,7 +94,7 @@ def test_get_artifact_download_true_forces_attachment_for_skill_archive(tmp_path monkeypatch.setattr(artifacts_router, "resolve_thread_virtual_path", lambda _thread_id, _path: skill_path) app = make_authed_test_app() - app.include_router(artifacts_router.router) + app.include_router(artifacts_router.router, prefix="/api") with TestClient(app) as client: response = client.get("/api/threads/thread-1/artifacts/mnt/user-data/outputs/sample.skill/notes.txt?download=true") diff --git a/backend/tests/test_custom_agent.py b/backend/tests/test_custom_agent.py index 28490808..b64efb18 100644 --- a/backend/tests/test_custom_agent.py +++ b/backend/tests/test_custom_agent.py @@ -382,7 +382,7 @@ def _make_test_app(tmp_path: Path): from app.gateway.routers.agents import router app = FastAPI() - app.include_router(router) + app.include_router(router, prefix="/api") return app diff --git a/backend/tests/test_memory_router.py b/backend/tests/test_memory_router.py index 91fd1d66..d23a98bc 100644 --- a/backend/tests/test_memory_router.py +++ b/backend/tests/test_memory_router.py @@ -26,7 +26,7 @@ def _sample_memory(facts: list[dict] | None = None) -> dict: def test_export_memory_route_returns_current_memory() -> None: app = FastAPI() - app.include_router(memory.router) + app.include_router(memory.router, prefix="/api") exported_memory = _sample_memory( facts=[ { @@ -50,7 +50,7 @@ def test_export_memory_route_returns_current_memory() -> None: def test_import_memory_route_returns_imported_memory() -> None: app = FastAPI() - app.include_router(memory.router) + app.include_router(memory.router, prefix="/api") imported_memory = _sample_memory( facts=[ { @@ -74,7 +74,7 @@ def test_import_memory_route_returns_imported_memory() -> None: def test_export_memory_route_preserves_source_error() -> None: app = FastAPI() - app.include_router(memory.router) + app.include_router(memory.router, prefix="/api") exported_memory = _sample_memory( facts=[ { @@ -99,7 +99,7 @@ def test_export_memory_route_preserves_source_error() -> None: def test_import_memory_route_preserves_source_error() -> None: app = FastAPI() - app.include_router(memory.router) + app.include_router(memory.router, prefix="/api") imported_memory = _sample_memory( facts=[ { @@ -124,7 +124,7 @@ def test_import_memory_route_preserves_source_error() -> None: def test_clear_memory_route_returns_cleared_memory() -> None: app = FastAPI() - app.include_router(memory.router) + app.include_router(memory.router, prefix="/api") with patch("app.gateway.routers.memory.clear_memory_data", return_value=_sample_memory()): with TestClient(app) as client: @@ -136,7 +136,7 @@ def test_clear_memory_route_returns_cleared_memory() -> None: def test_create_memory_fact_route_returns_updated_memory() -> None: app = FastAPI() - app.include_router(memory.router) + app.include_router(memory.router, prefix="/api") updated_memory = _sample_memory( facts=[ { @@ -167,7 +167,7 @@ def test_create_memory_fact_route_returns_updated_memory() -> None: def test_delete_memory_fact_route_returns_updated_memory() -> None: app = FastAPI() - app.include_router(memory.router) + app.include_router(memory.router, prefix="/api") updated_memory = _sample_memory( facts=[ { @@ -191,7 +191,7 @@ def test_delete_memory_fact_route_returns_updated_memory() -> None: def test_delete_memory_fact_route_returns_404_for_missing_fact() -> None: app = FastAPI() - app.include_router(memory.router) + app.include_router(memory.router, prefix="/api") with patch("app.gateway.routers.memory.delete_memory_fact", side_effect=KeyError("fact_missing")): with TestClient(app) as client: @@ -203,7 +203,7 @@ def test_delete_memory_fact_route_returns_404_for_missing_fact() -> None: def test_update_memory_fact_route_returns_updated_memory() -> None: app = FastAPI() - app.include_router(memory.router) + app.include_router(memory.router, prefix="/api") updated_memory = _sample_memory( facts=[ { @@ -234,7 +234,7 @@ def test_update_memory_fact_route_returns_updated_memory() -> None: def test_update_memory_fact_route_preserves_omitted_fields() -> None: app = FastAPI() - app.include_router(memory.router) + app.include_router(memory.router, prefix="/api") updated_memory = _sample_memory( facts=[ { @@ -270,7 +270,7 @@ def test_update_memory_fact_route_preserves_omitted_fields() -> None: def test_update_memory_fact_route_returns_404_for_missing_fact() -> None: app = FastAPI() - app.include_router(memory.router) + app.include_router(memory.router, prefix="/api") with patch("app.gateway.routers.memory.update_memory_fact", side_effect=KeyError("fact_missing")): with TestClient(app) as client: @@ -289,7 +289,7 @@ def test_update_memory_fact_route_returns_404_for_missing_fact() -> None: def test_update_memory_fact_route_returns_specific_error_for_invalid_confidence() -> None: app = FastAPI() - app.include_router(memory.router) + app.include_router(memory.router, prefix="/api") with patch("app.gateway.routers.memory.update_memory_fact", side_effect=ValueError("confidence")): with TestClient(app) as client: diff --git a/backend/tests/test_runs_api_endpoints.py b/backend/tests/test_runs_api_endpoints.py index 1826e4d8..7737f723 100644 --- a/backend/tests/test_runs_api_endpoints.py +++ b/backend/tests/test_runs_api_endpoints.py @@ -17,7 +17,7 @@ from app.gateway.routers import runs def _make_app(run_store=None, event_store=None, feedback_repo=None): """Build a test FastAPI app with stub auth and mocked state.""" app = make_authed_test_app() - app.include_router(runs.router) + app.include_router(runs.router, prefix="/api") if run_store is not None: app.state.run_store = run_store diff --git a/backend/tests/test_skills_custom_router.py b/backend/tests/test_skills_custom_router.py index ed93e551..6902db2d 100644 --- a/backend/tests/test_skills_custom_router.py +++ b/backend/tests/test_skills_custom_router.py @@ -39,7 +39,7 @@ def _make_skill(name: str, *, enabled: bool) -> Skill: def _make_test_app(config) -> FastAPI: app = FastAPI() app.state.config = config - app.include_router(skills_router.router) + app.include_router(skills_router.router, prefix="/api") return app diff --git a/backend/tests/test_thread_run_messages_pagination.py b/backend/tests/test_thread_run_messages_pagination.py index 00e354a3..88627977 100644 --- a/backend/tests/test_thread_run_messages_pagination.py +++ b/backend/tests/test_thread_run_messages_pagination.py @@ -17,7 +17,7 @@ from app.gateway.routers import thread_runs def _make_app(event_store=None): """Build a test FastAPI app with stub auth and mocked state.""" app = make_authed_test_app() - app.include_router(thread_runs.router) + app.include_router(thread_runs.router, prefix="/api") if event_store is not None: app.state.run_event_store = event_store diff --git a/backend/tests/test_threads_post_workspace.py b/backend/tests/test_threads_post_workspace.py index 18f60bf8..5d773f30 100644 --- a/backend/tests/test_threads_post_workspace.py +++ b/backend/tests/test_threads_post_workspace.py @@ -37,7 +37,7 @@ def _build_app(workspace_id: str): app.state.store = store app.state.checkpointer = checkpointer app.state.thread_store = MemoryThreadMetaStore(store) - app.include_router(threads.router) + app.include_router(threads.router, prefix="/api") return app, store diff --git a/backend/tests/test_threads_router.py b/backend/tests/test_threads_router.py index c13fb1e6..aa5f0aec 100644 --- a/backend/tests/test_threads_router.py +++ b/backend/tests/test_threads_router.py @@ -59,7 +59,7 @@ def _build_thread_app() -> tuple[FastAPI, InMemoryStore, InMemorySaver]: app.state.store = store app.state.checkpointer = checkpointer app.state.thread_store = _PermissiveThreadMetaStore(store) - app.include_router(threads.router) + app.include_router(threads.router, prefix="/api") return app, store, checkpointer @@ -113,7 +113,7 @@ def test_delete_thread_route_cleans_thread_directory(tmp_path): (paths.sandbox_work_dir("thread-route", user_id=user_id) / "notes.txt").write_text("hello", encoding="utf-8") app = make_authed_test_app() - app.include_router(threads.router) + app.include_router(threads.router, prefix="/api") with patch("app.gateway.routers.threads.get_paths", return_value=paths): with TestClient(app) as client: @@ -128,7 +128,7 @@ def test_delete_thread_route_rejects_invalid_thread_id(tmp_path): paths = Paths(tmp_path) app = make_authed_test_app() - app.include_router(threads.router) + app.include_router(threads.router, prefix="/api") with patch("app.gateway.routers.threads.get_paths", return_value=paths): with TestClient(app) as client: @@ -141,7 +141,7 @@ def test_delete_thread_route_returns_422_for_route_safe_invalid_id(tmp_path): paths = Paths(tmp_path) app = make_authed_test_app() - app.include_router(threads.router) + app.include_router(threads.router, prefix="/api") with patch("app.gateway.routers.threads.get_paths", return_value=paths): with TestClient(app) as client: diff --git a/backend/tests/test_uploads_router.py b/backend/tests/test_uploads_router.py index 4a778345..3c80c242 100644 --- a/backend/tests/test_uploads_router.py +++ b/backend/tests/test_uploads_router.py @@ -598,7 +598,7 @@ def test_upload_limits_endpoint_requires_thread_access(): cfg.uploads = {} app = make_authed_test_app(owner_check_passes=False) app.state.config = cfg - app.include_router(uploads.router) + app.include_router(uploads.router, prefix="/api") with TestClient(app) as client: response = client.get("/api/threads/thread-local/uploads/limits") diff --git a/backend/tests/test_workspace_isolation_boundary.py b/backend/tests/test_workspace_isolation_boundary.py index ba2c905f..2ff285b3 100644 --- a/backend/tests/test_workspace_isolation_boundary.py +++ b/backend/tests/test_workspace_isolation_boundary.py @@ -80,7 +80,7 @@ def _build_app(*, user_id: str, workspace_id: str): app.state.store = store app.state.checkpointer = InMemorySaver() app.state.thread_store = MemoryThreadMetaStore(store) - app.include_router(threads.router) + app.include_router(threads.router, prefix="/api") return app, store