chore: no changes needed for calendar message card
This commit is contained in:
@@ -100,3 +100,105 @@ async def test_agent_sse_closed_loop_live() -> None:
|
||||
)
|
||||
)
|
||||
assert len(list(rows.scalars().all())) >= 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.live
|
||||
async def test_agent_runs_events_history_live_with_image_input() -> None:
|
||||
if os.getenv("AGENT_LIVE_INTEGRATION") != "1":
|
||||
pytest.skip("set AGENT_LIVE_INTEGRATION=1 to run live integration test")
|
||||
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
token = await _live_access_token(client)
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
thread_id = str(uuid4())
|
||||
|
||||
run_resp = await client.post(
|
||||
f"{BASE_URL}/api/v1/agent/runs",
|
||||
headers=headers,
|
||||
json={
|
||||
"threadId": thread_id,
|
||||
"runId": "run-live-image-1",
|
||||
"state": {},
|
||||
"messages": [
|
||||
{
|
||||
"id": "u1",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "请描述图片里的内容"},
|
||||
{
|
||||
"type": "binary",
|
||||
"data": "aGVsbG8=",
|
||||
"mimeType": "image/png",
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
"tools": [],
|
||||
"context": [],
|
||||
"forwardedProps": {},
|
||||
},
|
||||
)
|
||||
assert run_resp.status_code == 202
|
||||
|
||||
events_url = f"{BASE_URL}/api/v1/agent/runs/{thread_id}/events"
|
||||
event_names: list[str] = []
|
||||
async with client.stream(
|
||||
"GET", events_url, headers=headers, timeout=20.0
|
||||
) as sse_resp:
|
||||
assert sse_resp.status_code == 200
|
||||
assert sse_resp.headers.get("content-type", "").startswith(
|
||||
"text/event-stream"
|
||||
)
|
||||
async for line in sse_resp.aiter_lines():
|
||||
if line.startswith("event:"):
|
||||
event_name = line.split(":", 1)[1].strip()
|
||||
event_names.append(event_name)
|
||||
if event_name in {"RUN_FINISHED", "RUN_ERROR"}:
|
||||
break
|
||||
|
||||
assert "RUN_STARTED" in event_names
|
||||
assert "RUN_FINISHED" in event_names or "RUN_ERROR" in event_names
|
||||
|
||||
history_resp = await client.get(
|
||||
f"{BASE_URL}/api/v1/agent/runs/{thread_id}/history",
|
||||
headers=headers,
|
||||
)
|
||||
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", [])
|
||||
user_messages = [
|
||||
item
|
||||
for item in messages
|
||||
if isinstance(item, dict) and item.get("role") == "user"
|
||||
]
|
||||
assert user_messages
|
||||
attachments = user_messages[0].get("attachments")
|
||||
assert isinstance(attachments, list)
|
||||
assert attachments and isinstance(attachments[0], dict)
|
||||
assert isinstance(attachments[0].get("path"), str)
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
session_row = await session.get(AgentChatSession, UUID(thread_id))
|
||||
assert session_row is not None
|
||||
assert session_row.message_count >= 1
|
||||
assert session_row.total_tokens >= 0
|
||||
assert session_row.total_cost >= 0
|
||||
|
||||
rows = await session.execute(
|
||||
select(AgentChatMessage).where(
|
||||
AgentChatMessage.session_id == UUID(thread_id)
|
||||
)
|
||||
)
|
||||
all_messages = list(rows.scalars().all())
|
||||
assert all_messages
|
||||
user_rows = [row for row in all_messages if str(row.role) == "user"]
|
||||
assert user_rows
|
||||
metadata = user_rows[0].metadata_json or {}
|
||||
attachments = metadata.get("attachments")
|
||||
assert isinstance(attachments, list)
|
||||
assert attachments and isinstance(attachments[0], dict)
|
||||
assert isinstance(attachments[0].get("path"), str)
|
||||
|
||||
@@ -70,3 +70,37 @@ async def test_tool_message_keeps_inline_content_when_storage_payload_missing()
|
||||
|
||||
assert payload["toolCallId"] == "call-2"
|
||||
assert payload["content"] == "inline-tool-content"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_message_snapshot_includes_renderable_attachments() -> None:
|
||||
repository = AgentRepository(
|
||||
session=SimpleNamespace(), # type: ignore[arg-type]
|
||||
)
|
||||
message = SimpleNamespace(
|
||||
id=uuid4(),
|
||||
role=AgentChatMessageRole.USER,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
content="请分析这张图",
|
||||
metadata_json={
|
||||
"attachments": [
|
||||
{
|
||||
"bucket": "agent-chat-attachments",
|
||||
"path": "agent-inputs/u1/t1/r1/m1/att-1.png",
|
||||
"mimeType": "image/png",
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
payload = await repository._to_snapshot_message(message) # type: ignore[arg-type]
|
||||
|
||||
assert payload["role"] == "user"
|
||||
assert payload["content"] == "请分析这张图"
|
||||
assert payload["attachments"] == [
|
||||
{
|
||||
"bucket": "agent-chat-attachments",
|
||||
"path": "agent-inputs/u1/t1/r1/m1/att-1.png",
|
||||
"mimeType": "image/png",
|
||||
}
|
||||
]
|
||||
|
||||
@@ -18,6 +18,7 @@ class _FakeRepository:
|
||||
self.rolled_back = False
|
||||
self.deleted_session_id: str | None = None
|
||||
self.created_with_session_id: str | None = None
|
||||
self.persisted_user_messages: list[dict[str, object]] = []
|
||||
|
||||
async def get_session_owner(self, *, session_id: str) -> str:
|
||||
if session_id == "00000000-0000-0000-0000-000000000001":
|
||||
@@ -56,6 +57,23 @@ class _FakeRepository:
|
||||
del user_id
|
||||
return "00000000-0000-0000-0000-000000000001"
|
||||
|
||||
async def persist_user_message(
|
||||
self,
|
||||
*,
|
||||
session_id: str,
|
||||
run_id: str,
|
||||
content_text: str,
|
||||
metadata: dict[str, object] | None,
|
||||
) -> None:
|
||||
self.persisted_user_messages.append(
|
||||
{
|
||||
"session_id": session_id,
|
||||
"run_id": run_id,
|
||||
"content_text": content_text,
|
||||
"metadata": metadata,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class _FakeQueue:
|
||||
async def enqueue(
|
||||
@@ -83,6 +101,29 @@ class _FakeStream:
|
||||
]
|
||||
|
||||
|
||||
class _FakeAttachmentStorage:
|
||||
def __init__(self) -> None:
|
||||
self.calls: list[dict[str, object]] = []
|
||||
|
||||
async def upload_bytes(
|
||||
self,
|
||||
*,
|
||||
bucket: str,
|
||||
path: str,
|
||||
content: bytes,
|
||||
content_type: str,
|
||||
) -> str:
|
||||
self.calls.append(
|
||||
{
|
||||
"bucket": bucket,
|
||||
"path": path,
|
||||
"content": content,
|
||||
"content_type": content_type,
|
||||
}
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
def _user() -> CurrentUser:
|
||||
return CurrentUser(
|
||||
id=UUID("00000000-0000-0000-0000-000000000001"),
|
||||
@@ -216,6 +257,66 @@ async def test_enqueue_run_handles_session_create_race() -> None:
|
||||
assert repository.rolled_back is True
|
||||
|
||||
|
||||
async def test_enqueue_run_uploads_user_image_to_supabase_and_injects_metadata(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr(
|
||||
agent_service_module.config.storage, "bucket", "agent-test-bucket"
|
||||
)
|
||||
repository = _FakeRepository()
|
||||
attachment_storage = _FakeAttachmentStorage()
|
||||
service = AgentService(
|
||||
repository=repository,
|
||||
queue=_FakeQueue(),
|
||||
stream=_FakeStream(),
|
||||
attachment_storage=attachment_storage,
|
||||
)
|
||||
run_input = RunAgentInput.model_validate(
|
||||
{
|
||||
"threadId": "00000000-0000-0000-0000-000000000001",
|
||||
"runId": "run-with-image",
|
||||
"state": {},
|
||||
"messages": [
|
||||
{
|
||||
"id": "u1",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "帮我看下这张图"},
|
||||
{
|
||||
"type": "binary",
|
||||
"data": "aGVsbG8=",
|
||||
"mimeType": "image/png",
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
"tools": [],
|
||||
"context": [],
|
||||
"forwardedProps": {},
|
||||
}
|
||||
)
|
||||
|
||||
accepted = await service.enqueue_run(run_input=run_input, current_user=_user())
|
||||
|
||||
assert accepted.task_id == "task-1"
|
||||
assert len(attachment_storage.calls) == 1
|
||||
upload = attachment_storage.calls[0]
|
||||
assert upload["bucket"] == "agent-test-bucket"
|
||||
assert upload["content"] == b"hello"
|
||||
assert upload["content_type"] == "image/png"
|
||||
assert repository.persisted_user_messages
|
||||
persisted = repository.persisted_user_messages[0]
|
||||
assert persisted["session_id"] == "00000000-0000-0000-0000-000000000001"
|
||||
assert persisted["run_id"] == "run-with-image"
|
||||
metadata = persisted["metadata"]
|
||||
assert isinstance(metadata, dict)
|
||||
attachments = metadata.get("attachments")
|
||||
assert isinstance(attachments, list)
|
||||
assert attachments and isinstance(attachments[0], dict)
|
||||
assert attachments[0]["bucket"] == "agent-test-bucket"
|
||||
assert isinstance(attachments[0]["path"], str)
|
||||
|
||||
|
||||
async def test_get_history_snapshot_wraps_history_day_as_state_snapshot_event() -> None:
|
||||
service = AgentService(
|
||||
repository=_FakeRepository(),
|
||||
|
||||
Reference in New Issue
Block a user