diff --git a/backend/tests/integration/test_auth_routes.py b/backend/tests/integration/test_auth_routes.py index 9069ca7..a270fa1 100644 --- a/backend/tests/integration/test_auth_routes.py +++ b/backend/tests/integration/test_auth_routes.py @@ -167,7 +167,11 @@ def test_send_otp_phone_rate_limited_after_too_many_attempts() -> None: 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( FakeAuthService(_token_response()) ) diff --git a/backend/tests/integration/test_schedule_items_routes.py b/backend/tests/integration/test_schedule_items_routes.py index 57d24f0..3f0674d 100644 --- a/backend/tests/integration/test_schedule_items_routes.py +++ b/backend/tests/integration/test_schedule_items_routes.py @@ -92,6 +92,7 @@ def test_create_schedule_item_returns_201() -> None: json={ "title": "Test Event", "start_at": "2026-02-28T16:00:00Z", + "timezone": "UTC", }, ) assert response.status_code == 201 diff --git a/backend/tests/integration/v1/agent/test_sse_flow_live.py b/backend/tests/integration/v1/agent/test_sse_flow_live.py index 9339cf6..a676db3 100644 --- a/backend/tests/integration/v1/agent/test_sse_flow_live.py +++ b/backend/tests/integration/v1/agent/test_sse_flow_live.py @@ -1,15 +1,14 @@ from __future__ import annotations -import base64 from pathlib import Path from uuid import UUID, uuid4 import httpx import pytest from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from core.config.settings import config -from core.db.session import AsyncSessionLocal from models.agent_chat_message import AgentChatMessage from models.agent_chat_session import AgentChatSession 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: phone = config.test.phone if not phone: @@ -82,7 +89,7 @@ async def test_agent_sse_closed_loop_live() -> None: thread_id = str(accepted["threadId"]) 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] = [] async with client.stream( "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_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)) assert session_row is not None 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"}: 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: token = await _live_access_token(client) headers = {"Authorization": f"Bearer {token}"} 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( f"{BASE_URL}/api/v1/agent/runs", headers=headers, @@ -141,7 +159,7 @@ async def test_agent_runs_events_history_live_with_image_input() -> None: {"type": "text", "text": "请描述图片里的内容"}, { "type": "binary", - "data": image_data, + "url": image_url, "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 - 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] = [] async with client.stream( "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 history = history_resp.json() - assert history.get("type") == "STATE_SNAPSHOT" - snapshot = history.get("snapshot", {}) - assert snapshot.get("scope") == "history_day" - messages = snapshot.get("messages", []) + assert history.get("scope") == "history_day" + messages = history.get("messages", []) user_messages = [ item for item in messages if isinstance(item, dict) and item.get("role") == "user" ] assert user_messages - metadata = user_messages[0].get("metadata") - assert isinstance(metadata, dict) - user_attachments = metadata.get("user_message_attachments") + user_attachments = user_messages[0].get("attachments") assert isinstance(user_attachments, list) assert user_attachments 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)) assert session_row is not None 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}" ) - async with AsyncSessionLocal() as session: + async with _make_session() as session: rows = await session.execute( select(AgentChatMessage).where( AgentChatMessage.session_id == UUID(thread_id), diff --git a/backend/tests/unit/core/agentscope/test_agent_prompt.py b/backend/tests/unit/core/agentscope/test_agent_prompt.py index 0524edd..047eee3 100644 --- a/backend/tests/unit/core/agentscope/test_agent_prompt.py +++ b/backend/tests/unit/core/agentscope/test_agent_prompt.py @@ -42,14 +42,12 @@ def test_build_agent_prompt_for_router_contains_identity_and_config() -> None: assert "- type: router" 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 "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 "- 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 "context_messages.mode=day" 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( router_output=RouterAgentOutput( 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 diff --git a/backend/tests/unit/core/agentscope/tools/test_toolkit.py b/backend/tests/unit/core/agentscope/tools/test_toolkit.py index c8805eb..bc9cb08 100644 --- a/backend/tests/unit/core/agentscope/tools/test_toolkit.py +++ b/backend/tests/unit/core/agentscope/tools/test_toolkit.py @@ -105,7 +105,8 @@ def test_view_skill_file_reads_calendar_action_card() -> None: block = response.content[0] text = block["text"] if isinstance(block, dict) else block.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 diff --git a/backend/tests/unit/services/test_llm_pricing_service.py b/backend/tests/unit/services/test_llm_pricing_service.py index ad1e9ce..9a329f1 100644 --- a/backend/tests/unit/services/test_llm_pricing_service.py +++ b/backend/tests/unit/services/test_llm_pricing_service.py @@ -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"