Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions tests/unit/api/test_llm_route_coercion.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# -*- coding: utf-8 -*-
"""/llm 路由边界 ID 归一:user_id / config_id 字符串 → int,非法值 → 422
"""/llm 路由边界行为:user_id / config_id 归一(M2/M3)+ 缺配置 → 404

锁定 M2/M3 修复:弱类型 ID 不再下沉到 SQL 靠驱动隐式转换,路由层显式校验。
锁定 M2/M3 修复:弱类型 ID 不再下沉到 SQL 靠驱动隐式转换,路由层显式校验;
并验证统一解析未命中(含系统兜底)时翻成 HTTP 404,保持原有对外契约。
"""
from __future__ import annotations

from unittest.mock import AsyncMock

import pytest
from fastapi import HTTPException

from src.api.routes.llm import _coerce_int
from src.api.routes.llm import _coerce_int, _resolve_provider
from src.core.llm.exceptions import UserModelConfigMissingError


def test_coerce_int_valid():
Expand All @@ -27,3 +31,17 @@ def test_coerce_int_rejects_empty():
_coerce_int("", "config_id")
assert exc.value.status_code == 422
assert "config_id" in exc.value.detail


@pytest.mark.asyncio
async def test_resolve_provider_missing_config_maps_to_404(monkeypatch):
"""统一解析未命中(含系统兜底)抛 UserModelConfigMissingError → HTTP 404,
保持 /llm 端点原有对外行为。"""
async def _raise(**kwargs):
raise UserModelConfigMissingError("EMBEDDING", 123)

monkeypatch.setattr("src.api.routes.llm.aresolve_user_model", _raise)
with pytest.raises(HTTPException) as exc:
await _resolve_provider(db=AsyncMock(), user_id="123", capability="EMBEDDING")
assert exc.value.status_code == 404
assert "EMBEDDING" in exc.value.detail
20 changes: 20 additions & 0 deletions tests/unit/api/test_recall_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from src.config import settings
from src.core.pipeline.recall import (
RecallError,
RecallFatalError,
RecallHit,
RecallResponse,
)
Expand Down Expand Up @@ -295,6 +296,25 @@ def test_all_sources_failed_emits_sse_error(client):
app.dependency_overrides.pop(get_recall_pipeline, None)


def test_embedding_config_missing_emits_sse_error(client):
"""发起用户无默认 EMBEDDING 配置 → pipeline 抛 RecallFatalError →
SSE error 事件返回 RECALL_EMBEDDING_CONFIG_MISSING(硬失败,不降级)。"""
fake = FakePipeline(exc=RecallFatalError("user embedding config missing"))
app.dependency_overrides[get_recall_pipeline] = lambda: fake
try:
resp = _post(TestClient(app), make_token(), {"query": "q", "user_id": 123, "dataset_ids": [1]})
assert resp.status_code == 200
assert resp.headers["content-type"].startswith("text/event-stream")
import json
name, data = _parse_sse(resp.text)[0]
assert name == "error"
payload = json.loads(data)
assert payload["code"] == "RECALL_EMBEDDING_CONFIG_MISSING"
assert "Traceback" not in payload["message"]
finally:
app.dependency_overrides.pop(get_recall_pipeline, None)


def test_timeout_emits_sse_error(client, monkeypatch):
monkeypatch.setattr(settings, "RECALL_STREAM_TIMEOUT_MS", 10)
fake = FakePipeline(response=_ok_response(), delay=0.5)
Expand Down
Loading