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) <noreply@anthropic.com>
This commit is contained in:
+46
-30
@@ -381,56 +381,72 @@ This gateway provides custom endpoints for models, MCP configuration, skills, an
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Include routers
|
# Include routers
|
||||||
# Models API is mounted at /api/models
|
# Legacy routers are dual-mounted on /api (backward compat) and /api/v1 (versioned).
|
||||||
app.include_router(models.router)
|
# The deprecation middleware (Task 5.1) stamps X-API-Deprecated on /api responses.
|
||||||
|
|
||||||
# MCP API is mounted at /api/mcp
|
# Models API — /api/models and /api/v1/models
|
||||||
app.include_router(mcp.router)
|
app.include_router(models.router, prefix="/api")
|
||||||
|
app.include_router(models.router, prefix="/api/v1")
|
||||||
|
|
||||||
# Memory API is mounted at /api/memory
|
# MCP API — /api/mcp and /api/v1/mcp
|
||||||
app.include_router(memory.router)
|
app.include_router(mcp.router, prefix="/api")
|
||||||
|
app.include_router(mcp.router, prefix="/api/v1")
|
||||||
|
|
||||||
# Skills API is mounted at /api/skills
|
# Memory API — /api/memory and /api/v1/memory
|
||||||
app.include_router(skills.router)
|
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
|
# Skills API — /api/skills and /api/v1/skills
|
||||||
app.include_router(artifacts.router)
|
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
|
# Artifacts API — /api/threads/{thread_id}/artifacts and /api/v1/threads/{thread_id}/artifacts
|
||||||
app.include_router(uploads.router)
|
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}
|
# Uploads API — /api/threads/{thread_id}/uploads and /api/v1/threads/{thread_id}/uploads
|
||||||
app.include_router(threads.router)
|
app.include_router(uploads.router, prefix="/api")
|
||||||
|
app.include_router(uploads.router, prefix="/api/v1")
|
||||||
|
|
||||||
# Agents API is mounted at /api/agents
|
# Threads API — /api/threads/{thread_id} and /api/v1/threads/{thread_id}
|
||||||
app.include_router(agents.router)
|
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
|
# Agents API — /api/agents and /api/v1/agents
|
||||||
app.include_router(suggestions.router)
|
app.include_router(agents.router, prefix="/api")
|
||||||
|
app.include_router(agents.router, prefix="/api/v1")
|
||||||
|
|
||||||
# Channels API is mounted at /api/channels
|
# Suggestions API — /api/threads/{thread_id}/suggestions and /api/v1/threads/{thread_id}/suggestions
|
||||||
app.include_router(channels.router)
|
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)
|
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)
|
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)
|
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)
|
app.include_router(api_keys.router)
|
||||||
|
|
||||||
# Feedback API is mounted at /api/threads/{thread_id}/runs/{run_id}/feedback
|
# Feedback API — /api/threads/{thread_id}/runs/{run_id}/feedback and /api/v1/... twin
|
||||||
app.include_router(feedback.router)
|
app.include_router(feedback.router, prefix="/api")
|
||||||
|
app.include_router(feedback.router, prefix="/api/v1")
|
||||||
|
|
||||||
# Thread Runs API (LangGraph Platform-compatible runs lifecycle)
|
# Thread Runs API — /api/threads/{thread_id}/runs and /api/v1/... twin
|
||||||
app.include_router(thread_runs.router)
|
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)
|
# Stateless Runs API — /api/runs and /api/v1/runs
|
||||||
app.include_router(runs.router)
|
app.include_router(runs.router, prefix="/api")
|
||||||
|
app.include_router(runs.router, prefix="/api/v1")
|
||||||
|
|
||||||
@app.get("/health", tags=["health"])
|
@app.get("/health", tags=["health"])
|
||||||
async def health_check() -> dict:
|
async def health_check() -> dict:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from deerflow.config.paths import get_paths
|
|||||||
from deerflow.runtime.user_context import get_effective_user_id
|
from deerflow.runtime.user_context import get_effective_user_id
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter(prefix="/api", tags=["agents"])
|
router = APIRouter(tags=["agents"])
|
||||||
|
|
||||||
AGENT_NAME_PATTERN = re.compile(r"^[A-Za-z0-9-]+$")
|
AGENT_NAME_PATTERN = re.compile(r"^[A-Za-z0-9-]+$")
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from app.gateway.path_utils import resolve_thread_virtual_path
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter(prefix="/api", tags=["artifacts"])
|
router = APIRouter(tags=["artifacts"])
|
||||||
|
|
||||||
ACTIVE_CONTENT_MIME_TYPES = {
|
ACTIVE_CONTENT_MIME_TYPES = {
|
||||||
"text/html",
|
"text/html",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/channels", tags=["channels"])
|
router = APIRouter(prefix="/channels", tags=["channels"])
|
||||||
|
|
||||||
|
|
||||||
class ChannelStatusResponse(BaseModel):
|
class ChannelStatusResponse(BaseModel):
|
||||||
|
|||||||
@@ -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
|
from app.gateway.deps import get_current_user, get_feedback_repo, get_run_store
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter(prefix="/api/threads", tags=["feedback"])
|
router = APIRouter(prefix="/threads", tags=["feedback"])
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from pydantic import BaseModel, Field
|
|||||||
from deerflow.config.extensions_config import ExtensionsConfig, get_extensions_config, reload_extensions_config
|
from deerflow.config.extensions_config import ExtensionsConfig, get_extensions_config, reload_extensions_config
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter(prefix="/api", tags=["mcp"])
|
router = APIRouter(tags=["mcp"])
|
||||||
|
|
||||||
|
|
||||||
class McpOAuthConfigResponse(BaseModel):
|
class McpOAuthConfigResponse(BaseModel):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from deerflow.agents.memory.updater import (
|
|||||||
from deerflow.config.memory_config import get_memory_config
|
from deerflow.config.memory_config import get_memory_config
|
||||||
from deerflow.runtime.user_context import get_effective_user_id
|
from deerflow.runtime.user_context import get_effective_user_id
|
||||||
|
|
||||||
router = APIRouter(prefix="/api", tags=["memory"])
|
router = APIRouter(tags=["memory"])
|
||||||
|
|
||||||
|
|
||||||
class ContextSection(BaseModel):
|
class ContextSection(BaseModel):
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from pydantic import BaseModel, Field
|
|||||||
from app.gateway.deps import get_config
|
from app.gateway.deps import get_config
|
||||||
from deerflow.config.app_config import AppConfig
|
from deerflow.config.app_config import AppConfig
|
||||||
|
|
||||||
router = APIRouter(prefix="/api", tags=["models"])
|
router = APIRouter(tags=["models"])
|
||||||
|
|
||||||
|
|
||||||
class ModelResponse(BaseModel):
|
class ModelResponse(BaseModel):
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from app.gateway.services import sse_consumer, start_run
|
|||||||
from deerflow.runtime import serialize_channel_values
|
from deerflow.runtime import serialize_channel_values
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter(prefix="/api/runs", tags=["runs"])
|
router = APIRouter(prefix="/runs", tags=["runs"])
|
||||||
|
|
||||||
|
|
||||||
def _resolve_thread_id(body: RunCreateRequest) -> str:
|
def _resolve_thread_id(body: RunCreateRequest) -> str:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from deerflow.skills.types import SKILL_MD_FILE, SkillCategory
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter(prefix="/api", tags=["skills"])
|
router = APIRouter(tags=["skills"])
|
||||||
|
|
||||||
|
|
||||||
class SkillResponse(BaseModel):
|
class SkillResponse(BaseModel):
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from deerflow.models import create_chat_model
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter(prefix="/api", tags=["suggestions"])
|
router = APIRouter(tags=["suggestions"])
|
||||||
|
|
||||||
|
|
||||||
class SuggestionMessage(BaseModel):
|
class SuggestionMessage(BaseModel):
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from app.gateway.services import sse_consumer, start_run
|
|||||||
from deerflow.runtime import RunRecord, serialize_channel_values
|
from deerflow.runtime import RunRecord, serialize_channel_values
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter(prefix="/api/threads", tags=["runs"])
|
router = APIRouter(prefix="/threads", tags=["runs"])
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from deerflow.runtime.user_context import get_effective_user_id
|
|||||||
from deerflow.utils.time import coerce_iso, now_iso
|
from deerflow.utils.time import coerce_iso, now_iso
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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
|
# Metadata keys that the server controls; clients are not allowed to set
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ from deerflow.utils.file_conversion import CONVERTIBLE_EXTENSIONS, convert_file_
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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
|
UPLOAD_CHUNK_SIZE = 8192
|
||||||
DEFAULT_MAX_FILES = 10
|
DEFAULT_MAX_FILES = 10
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"""Dual-mount /api + /api/v1 tests (Stage 1 PR5).
|
||||||
|
|
||||||
|
Every migrated legacy router must answer on BOTH /api/<x> and /api/v1/<x>.
|
||||||
|
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)
|
||||||
@@ -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)
|
monkeypatch.setattr(artifacts_router, "resolve_thread_virtual_path", lambda _thread_id, _path: artifact_path)
|
||||||
|
|
||||||
app = make_authed_test_app()
|
app = make_authed_test_app()
|
||||||
app.include_router(artifacts_router.router)
|
app.include_router(artifacts_router.router, prefix="/api")
|
||||||
|
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
response = client.get("/api/threads/thread-1/artifacts/mnt/user-data/outputs/note.txt?download=false")
|
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)
|
monkeypatch.setattr(artifacts_router, "resolve_thread_virtual_path", lambda _thread_id, _path: skill_path)
|
||||||
|
|
||||||
app = make_authed_test_app()
|
app = make_authed_test_app()
|
||||||
app.include_router(artifacts_router.router)
|
app.include_router(artifacts_router.router, prefix="/api")
|
||||||
|
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
response = client.get("/api/threads/thread-1/artifacts/mnt/user-data/outputs/sample.skill/notes.txt?download=true")
|
response = client.get("/api/threads/thread-1/artifacts/mnt/user-data/outputs/sample.skill/notes.txt?download=true")
|
||||||
|
|||||||
@@ -382,7 +382,7 @@ def _make_test_app(tmp_path: Path):
|
|||||||
from app.gateway.routers.agents import router
|
from app.gateway.routers.agents import router
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.include_router(router)
|
app.include_router(router, prefix="/api")
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ def _sample_memory(facts: list[dict] | None = None) -> dict:
|
|||||||
|
|
||||||
def test_export_memory_route_returns_current_memory() -> None:
|
def test_export_memory_route_returns_current_memory() -> None:
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.include_router(memory.router)
|
app.include_router(memory.router, prefix="/api")
|
||||||
exported_memory = _sample_memory(
|
exported_memory = _sample_memory(
|
||||||
facts=[
|
facts=[
|
||||||
{
|
{
|
||||||
@@ -50,7 +50,7 @@ def test_export_memory_route_returns_current_memory() -> None:
|
|||||||
|
|
||||||
def test_import_memory_route_returns_imported_memory() -> None:
|
def test_import_memory_route_returns_imported_memory() -> None:
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.include_router(memory.router)
|
app.include_router(memory.router, prefix="/api")
|
||||||
imported_memory = _sample_memory(
|
imported_memory = _sample_memory(
|
||||||
facts=[
|
facts=[
|
||||||
{
|
{
|
||||||
@@ -74,7 +74,7 @@ def test_import_memory_route_returns_imported_memory() -> None:
|
|||||||
|
|
||||||
def test_export_memory_route_preserves_source_error() -> None:
|
def test_export_memory_route_preserves_source_error() -> None:
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.include_router(memory.router)
|
app.include_router(memory.router, prefix="/api")
|
||||||
exported_memory = _sample_memory(
|
exported_memory = _sample_memory(
|
||||||
facts=[
|
facts=[
|
||||||
{
|
{
|
||||||
@@ -99,7 +99,7 @@ def test_export_memory_route_preserves_source_error() -> None:
|
|||||||
|
|
||||||
def test_import_memory_route_preserves_source_error() -> None:
|
def test_import_memory_route_preserves_source_error() -> None:
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.include_router(memory.router)
|
app.include_router(memory.router, prefix="/api")
|
||||||
imported_memory = _sample_memory(
|
imported_memory = _sample_memory(
|
||||||
facts=[
|
facts=[
|
||||||
{
|
{
|
||||||
@@ -124,7 +124,7 @@ def test_import_memory_route_preserves_source_error() -> None:
|
|||||||
|
|
||||||
def test_clear_memory_route_returns_cleared_memory() -> None:
|
def test_clear_memory_route_returns_cleared_memory() -> None:
|
||||||
app = FastAPI()
|
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 patch("app.gateway.routers.memory.clear_memory_data", return_value=_sample_memory()):
|
||||||
with TestClient(app) as client:
|
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:
|
def test_create_memory_fact_route_returns_updated_memory() -> None:
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.include_router(memory.router)
|
app.include_router(memory.router, prefix="/api")
|
||||||
updated_memory = _sample_memory(
|
updated_memory = _sample_memory(
|
||||||
facts=[
|
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:
|
def test_delete_memory_fact_route_returns_updated_memory() -> None:
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.include_router(memory.router)
|
app.include_router(memory.router, prefix="/api")
|
||||||
updated_memory = _sample_memory(
|
updated_memory = _sample_memory(
|
||||||
facts=[
|
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:
|
def test_delete_memory_fact_route_returns_404_for_missing_fact() -> None:
|
||||||
app = FastAPI()
|
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 patch("app.gateway.routers.memory.delete_memory_fact", side_effect=KeyError("fact_missing")):
|
||||||
with TestClient(app) as client:
|
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:
|
def test_update_memory_fact_route_returns_updated_memory() -> None:
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.include_router(memory.router)
|
app.include_router(memory.router, prefix="/api")
|
||||||
updated_memory = _sample_memory(
|
updated_memory = _sample_memory(
|
||||||
facts=[
|
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:
|
def test_update_memory_fact_route_preserves_omitted_fields() -> None:
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.include_router(memory.router)
|
app.include_router(memory.router, prefix="/api")
|
||||||
updated_memory = _sample_memory(
|
updated_memory = _sample_memory(
|
||||||
facts=[
|
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:
|
def test_update_memory_fact_route_returns_404_for_missing_fact() -> None:
|
||||||
app = FastAPI()
|
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 patch("app.gateway.routers.memory.update_memory_fact", side_effect=KeyError("fact_missing")):
|
||||||
with TestClient(app) as client:
|
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:
|
def test_update_memory_fact_route_returns_specific_error_for_invalid_confidence() -> None:
|
||||||
app = FastAPI()
|
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 patch("app.gateway.routers.memory.update_memory_fact", side_effect=ValueError("confidence")):
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from app.gateway.routers import runs
|
|||||||
def _make_app(run_store=None, event_store=None, feedback_repo=None):
|
def _make_app(run_store=None, event_store=None, feedback_repo=None):
|
||||||
"""Build a test FastAPI app with stub auth and mocked state."""
|
"""Build a test FastAPI app with stub auth and mocked state."""
|
||||||
app = make_authed_test_app()
|
app = make_authed_test_app()
|
||||||
app.include_router(runs.router)
|
app.include_router(runs.router, prefix="/api")
|
||||||
|
|
||||||
if run_store is not None:
|
if run_store is not None:
|
||||||
app.state.run_store = run_store
|
app.state.run_store = run_store
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ def _make_skill(name: str, *, enabled: bool) -> Skill:
|
|||||||
def _make_test_app(config) -> FastAPI:
|
def _make_test_app(config) -> FastAPI:
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
app.state.config = config
|
app.state.config = config
|
||||||
app.include_router(skills_router.router)
|
app.include_router(skills_router.router, prefix="/api")
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from app.gateway.routers import thread_runs
|
|||||||
def _make_app(event_store=None):
|
def _make_app(event_store=None):
|
||||||
"""Build a test FastAPI app with stub auth and mocked state."""
|
"""Build a test FastAPI app with stub auth and mocked state."""
|
||||||
app = make_authed_test_app()
|
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:
|
if event_store is not None:
|
||||||
app.state.run_event_store = event_store
|
app.state.run_event_store = event_store
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ def _build_app(workspace_id: str):
|
|||||||
app.state.store = store
|
app.state.store = store
|
||||||
app.state.checkpointer = checkpointer
|
app.state.checkpointer = checkpointer
|
||||||
app.state.thread_store = MemoryThreadMetaStore(store)
|
app.state.thread_store = MemoryThreadMetaStore(store)
|
||||||
app.include_router(threads.router)
|
app.include_router(threads.router, prefix="/api")
|
||||||
return app, store
|
return app, store
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ def _build_thread_app() -> tuple[FastAPI, InMemoryStore, InMemorySaver]:
|
|||||||
app.state.store = store
|
app.state.store = store
|
||||||
app.state.checkpointer = checkpointer
|
app.state.checkpointer = checkpointer
|
||||||
app.state.thread_store = _PermissiveThreadMetaStore(store)
|
app.state.thread_store = _PermissiveThreadMetaStore(store)
|
||||||
app.include_router(threads.router)
|
app.include_router(threads.router, prefix="/api")
|
||||||
return app, store, checkpointer
|
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")
|
(paths.sandbox_work_dir("thread-route", user_id=user_id) / "notes.txt").write_text("hello", encoding="utf-8")
|
||||||
|
|
||||||
app = make_authed_test_app()
|
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 patch("app.gateway.routers.threads.get_paths", return_value=paths):
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
@@ -128,7 +128,7 @@ def test_delete_thread_route_rejects_invalid_thread_id(tmp_path):
|
|||||||
paths = Paths(tmp_path)
|
paths = Paths(tmp_path)
|
||||||
|
|
||||||
app = make_authed_test_app()
|
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 patch("app.gateway.routers.threads.get_paths", return_value=paths):
|
||||||
with TestClient(app) as client:
|
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)
|
paths = Paths(tmp_path)
|
||||||
|
|
||||||
app = make_authed_test_app()
|
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 patch("app.gateway.routers.threads.get_paths", return_value=paths):
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
|
|||||||
@@ -598,7 +598,7 @@ def test_upload_limits_endpoint_requires_thread_access():
|
|||||||
cfg.uploads = {}
|
cfg.uploads = {}
|
||||||
app = make_authed_test_app(owner_check_passes=False)
|
app = make_authed_test_app(owner_check_passes=False)
|
||||||
app.state.config = cfg
|
app.state.config = cfg
|
||||||
app.include_router(uploads.router)
|
app.include_router(uploads.router, prefix="/api")
|
||||||
|
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
response = client.get("/api/threads/thread-local/uploads/limits")
|
response = client.get("/api/threads/thread-local/uploads/limits")
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ def _build_app(*, user_id: str, workspace_id: str):
|
|||||||
app.state.store = store
|
app.state.store = store
|
||||||
app.state.checkpointer = InMemorySaver()
|
app.state.checkpointer = InMemorySaver()
|
||||||
app.state.thread_store = MemoryThreadMetaStore(store)
|
app.state.thread_store = MemoryThreadMetaStore(store)
|
||||||
app.include_router(threads.router)
|
app.include_router(threads.router, prefix="/api")
|
||||||
return app, store
|
return app, store
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user