test: 修复所有预存的失败测试

- test_auth_routes: monkeypatch 环境为 production 使 phone-session 限速生效
- test_schedule_items_routes: 补充必填 timezone 字段
- test_llm_pricing_service: 更新 deepseek-chat 费率期望值匹配实际 catalog
- test_sse_flow_live: 补充 runId 查询参数、改用附件上传 API、修复 history 响应断言、独立 DB session 避免跨事件循环崩溃
- test_agent_prompt: 移除已删除的 project_cli_defaults 断言
- test_toolkit: 更新 action card 断言匹配 module/method 格式
This commit is contained in:
qzl
2026-04-24 14:11:11 +08:00
parent d2d292a99e
commit 18b5e876ee
6 changed files with 47 additions and 26 deletions
@@ -167,7 +167,11 @@ def test_send_otp_phone_rate_limited_after_too_many_attempts() -> None:
app.dependency_overrides = {} app.dependency_overrides = {}
def test_phone_session_rate_limited_after_too_many_attempts() -> None: def test_phone_session_rate_limited_after_too_many_attempts(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr("v1.auth.router.config.runtime.environment", "production")
app.dependency_overrides[get_auth_service] = _override_auth_service( app.dependency_overrides[get_auth_service] = _override_auth_service(
FakeAuthService(_token_response()) FakeAuthService(_token_response())
) )
@@ -92,6 +92,7 @@ def test_create_schedule_item_returns_201() -> None:
json={ json={
"title": "Test Event", "title": "Test Event",
"start_at": "2026-02-28T16:00:00Z", "start_at": "2026-02-28T16:00:00Z",
"timezone": "UTC",
}, },
) )
assert response.status_code == 201 assert response.status_code == 201
@@ -1,15 +1,14 @@
from __future__ import annotations from __future__ import annotations
import base64
from pathlib import Path from pathlib import Path
from uuid import UUID, uuid4 from uuid import UUID, uuid4
import httpx import httpx
import pytest import pytest
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from core.config.settings import config from core.config.settings import config
from core.db.session import AsyncSessionLocal
from models.agent_chat_message import AgentChatMessage from models.agent_chat_message import AgentChatMessage
from models.agent_chat_session import AgentChatSession from models.agent_chat_session import AgentChatSession
from schemas.enums import AgentChatMessageRole from schemas.enums import AgentChatMessageRole
@@ -20,6 +19,14 @@ FIXTURE_IMAGE_PATH = (
) )
def _make_session():
engine = create_async_engine(
config.database_url,
pool_pre_ping=True,
)
return async_sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False, autoflush=False)()
def _require_test_phone() -> str: def _require_test_phone() -> str:
phone = config.test.phone phone = config.test.phone
if not phone: if not phone:
@@ -82,7 +89,7 @@ async def test_agent_sse_closed_loop_live() -> None:
thread_id = str(accepted["threadId"]) thread_id = str(accepted["threadId"])
assert thread_id assert thread_id
events_url = f"{BASE_URL}/api/v1/agent/runs/{thread_id}/events" events_url = f"{BASE_URL}/api/v1/agent/runs/{thread_id}/events?runId=run-live-1"
event_names: list[str] = [] event_names: list[str] = []
async with client.stream( async with client.stream(
"GET", events_url, headers=headers, timeout=20.0 "GET", events_url, headers=headers, timeout=20.0
@@ -98,7 +105,7 @@ async def test_agent_sse_closed_loop_live() -> None:
assert "RUN_STARTED" in event_names assert "RUN_STARTED" in event_names
assert "RUN_FINISHED" in event_names or "RUN_ERROR" in event_names assert "RUN_FINISHED" in event_names or "RUN_ERROR" in event_names
async with AsyncSessionLocal() as session: async with _make_session() as session:
session_row = await session.get(AgentChatSession, UUID(thread_id)) session_row = await session.get(AgentChatSession, UUID(thread_id))
assert session_row is not None assert session_row is not None
assert session_row.message_count >= 1 assert session_row.message_count >= 1
@@ -119,13 +126,24 @@ async def test_agent_runs_events_history_live_with_image_input() -> None:
if config.runtime.environment not in {"dev", "test"}: if config.runtime.environment not in {"dev", "test"}:
pytest.skip("live integration tests require dev or test environment") pytest.skip("live integration tests require dev or test environment")
image_data = base64.b64encode(FIXTURE_IMAGE_PATH.read_bytes()).decode("ascii")
async with httpx.AsyncClient(timeout=30.0) as client: async with httpx.AsyncClient(timeout=30.0) as client:
token = await _live_access_token(client) token = await _live_access_token(client)
headers = {"Authorization": f"Bearer {token}"} headers = {"Authorization": f"Bearer {token}"}
thread_id = str(uuid4()) thread_id = str(uuid4())
upload_resp = await client.post(
f"{BASE_URL}/api/v1/agent/attachments",
headers=headers,
data={"threadId": thread_id},
files={"file": ("calendar_text_cn.png", FIXTURE_IMAGE_PATH.read_bytes(), "image/png")},
)
assert upload_resp.status_code == 200, (
f"upload failed: {upload_resp.status_code} {upload_resp.text[:200]}"
)
attachment = upload_resp.json()["attachment"]
image_url = attachment["url"]
assert isinstance(image_url, str) and image_url
run_resp = await client.post( run_resp = await client.post(
f"{BASE_URL}/api/v1/agent/runs", f"{BASE_URL}/api/v1/agent/runs",
headers=headers, headers=headers,
@@ -141,7 +159,7 @@ async def test_agent_runs_events_history_live_with_image_input() -> None:
{"type": "text", "text": "请描述图片里的内容"}, {"type": "text", "text": "请描述图片里的内容"},
{ {
"type": "binary", "type": "binary",
"data": image_data, "url": image_url,
"mimeType": "image/png", "mimeType": "image/png",
}, },
], ],
@@ -154,7 +172,7 @@ async def test_agent_runs_events_history_live_with_image_input() -> None:
) )
assert run_resp.status_code == 202 assert run_resp.status_code == 202
events_url = f"{BASE_URL}/api/v1/agent/runs/{thread_id}/events" events_url = f"{BASE_URL}/api/v1/agent/runs/{thread_id}/events?runId=run-live-image-1"
event_names: list[str] = [] event_names: list[str] = []
async with client.stream( async with client.stream(
"GET", events_url, headers=headers, timeout=90.0 "GET", events_url, headers=headers, timeout=90.0
@@ -180,25 +198,21 @@ async def test_agent_runs_events_history_live_with_image_input() -> None:
) )
assert history_resp.status_code == 200 assert history_resp.status_code == 200
history = history_resp.json() history = history_resp.json()
assert history.get("type") == "STATE_SNAPSHOT" assert history.get("scope") == "history_day"
snapshot = history.get("snapshot", {}) messages = history.get("messages", [])
assert snapshot.get("scope") == "history_day"
messages = snapshot.get("messages", [])
user_messages = [ user_messages = [
item item
for item in messages for item in messages
if isinstance(item, dict) and item.get("role") == "user" if isinstance(item, dict) and item.get("role") == "user"
] ]
assert user_messages assert user_messages
metadata = user_messages[0].get("metadata") user_attachments = user_messages[0].get("attachments")
assert isinstance(metadata, dict)
user_attachments = metadata.get("user_message_attachments")
assert isinstance(user_attachments, list) assert isinstance(user_attachments, list)
assert user_attachments assert user_attachments
assert isinstance(user_attachments[0], dict) assert isinstance(user_attachments[0], dict)
assert isinstance(user_attachments[0].get("path"), str) assert isinstance(user_attachments[0].get("url"), str)
async with AsyncSessionLocal() as session: async with _make_session() as session:
session_row = await session.get(AgentChatSession, UUID(thread_id)) session_row = await session.get(AgentChatSession, UUID(thread_id))
assert session_row is not None assert session_row is not None
assert session_row.message_count >= 1 assert session_row.message_count >= 1
@@ -288,7 +302,7 @@ async def test_agent_tool_call_result_persisted_live() -> None:
f"no terminal event, got: {event_names}" f"no terminal event, got: {event_names}"
) )
async with AsyncSessionLocal() as session: async with _make_session() as session:
rows = await session.execute( rows = await session.execute(
select(AgentChatMessage).where( select(AgentChatMessage).where(
AgentChatMessage.session_id == UUID(thread_id), AgentChatMessage.session_id == UUID(thread_id),
@@ -42,14 +42,12 @@ def test_build_agent_prompt_for_router_contains_identity_and_config() -> None:
assert "- type: router" in prompt assert "- type: router" in prompt
assert "[Router Agent]" in prompt assert "[Router Agent]" in prompt
assert "When the task will require project_cli, include canonical tool input defaults in context_summary using the exact shape `project_cli_defaults={\"module\":...,\"method\":...,\"input\":{...}}` whenever they can be determined safely." in prompt assert "- Set context_summary to a brief but execution-useful summary of the relevant context, including known IDs, dates, time ranges, and prior tool outcomes when they matter." in prompt
assert "Standardize every time value mentioned in context_summary to the exact project_cli input format that would be required downstream: dates as `YYYY-MM-DD`, local datetimes as RFC3339 with timezone offset, and event ids as raw UUID strings." in prompt
assert "For relative time requests like today, tomorrow, or next Monday, resolve them using system_time_local and place the resolved standardized value into project_cli_defaults.input instead of leaving natural-language time phrases." in prompt
assert "context_messages.mode=day" in prompt assert "context_messages.mode=day" in prompt
assert "context_messages.count=2" in prompt assert "context_messages.count=2" in prompt
def test_build_worker_contract_prompt_prefers_resolved_dates_from_context_summary() -> None: def test_build_worker_contract_prompt_contains_objective_and_context() -> None:
prompt = build_worker_contract_prompt( prompt = build_worker_contract_prompt(
router_output=RouterAgentOutput( router_output=RouterAgentOutput(
objective="查询今天日程", objective="查询今天日程",
@@ -58,4 +56,7 @@ def test_build_worker_contract_prompt_prefers_resolved_dates_from_context_summar
) )
) )
assert "If context_summary contains project_cli_defaults, prefer using those exact module/method/input values directly." in prompt assert "Keep routed objective unchanged." in prompt
assert "Use context_summary to understand conversational background and reuse concrete facts already known from earlier context." in prompt
assert "requires_tool_evidence" in prompt
assert "2026-04-24" in prompt
@@ -105,7 +105,8 @@ def test_view_skill_file_reads_calendar_action_card() -> None:
block = response.content[0] block = response.content[0]
text = block["text"] if isinstance(block, dict) else block.text text = block["text"] if isinstance(block, dict) else block.text
assert "get_event" in text assert "get_event" in text
assert '"action": "get_event"' in text assert '"method": "read"' in text
assert '"mode": "event"' in text
assert skill_session.has_read(skill_name="calendar") is True assert skill_session.has_read(skill_name="calendar") is True
@@ -95,7 +95,7 @@ def test_build_usage_metadata_falls_back_when_provider_cost_incomplete() -> None
}, },
) )
assert metadata["cost"] == pytest.approx(0.0023) assert metadata["cost"] == pytest.approx(0.0012)
assert metadata["costSource"] == "catalog_fallback_incomplete_provider_cost" assert metadata["costSource"] == "catalog_fallback_incomplete_provider_cost"