ForcePilot/backend/test/test_conversation_service_attachment_state.py

154 lines
4.7 KiB
Python

from __future__ import annotations
import io
from types import SimpleNamespace
import pytest
from yuxi.services import chat_service as chat_svc
from yuxi.services import conversation_service as svc
class _DummyUpload:
def __init__(self, *, filename: str, content_type: str | None, data: bytes):
self.filename = filename
self.content_type = content_type
self._buffer = io.BytesIO(data)
async def read(self, size: int = -1) -> bytes:
return self._buffer.read(size)
async def seek(self, offset: int) -> int:
return self._buffer.seek(offset)
def test_build_state_files_only_parsed_and_with_content():
attachments = [
{
"status": "parsed",
"file_path": "/attachments/a.md",
"markdown": "line1\nline2",
"uploaded_at": "2026-02-20T00:00:00+00:00",
},
{
"status": "pending",
"file_path": "/attachments/b.md",
"markdown": "ignored",
},
{
"status": "parsed",
"file_path": "/attachments/c.md",
"markdown": "",
},
]
files = chat_svc._build_state_files(attachments)
assert list(files.keys()) == ["/attachments/a.md"]
assert files["/attachments/a.md"]["content"] == ["line1", "line2"]
assert files["/attachments/a.md"]["created_at"] == "2026-02-20T00:00:00+00:00"
@pytest.mark.asyncio
async def test_sync_thread_attachment_state_updates_graph(monkeypatch: pytest.MonkeyPatch):
captured: dict = {}
class FakeGraph:
async def aupdate_state(self, *, config, values):
captured["write_config"] = config
captured["write_values"] = values
class FakeAgent:
async def get_graph(self):
return FakeGraph()
monkeypatch.setattr(svc.agent_manager, "get_agent", lambda _agent_id: FakeAgent())
attachments = [
{
"status": "parsed",
"path": "/home/gem/user-data/uploads/attachments/resume.md",
"file_name": "resume.md",
"uploaded_at": "2026-02-20T00:00:00+00:00",
}
]
await svc._sync_thread_upload_state(
thread_id="thread-1",
user_id="u1",
agent_id="ChatbotAgent",
attachments=attachments,
)
assert captured["write_config"] == {"configurable": {"thread_id": "thread-1", "user_id": "u1"}}
assert captured["write_values"] == {"uploads": svc._build_state_uploads(attachments)}
@pytest.mark.asyncio
async def test_sync_thread_attachment_state_skips_when_agent_missing(monkeypatch: pytest.MonkeyPatch):
warnings: list[str] = []
fake_logger = SimpleNamespace(
warning=lambda message: warnings.append(message),
)
monkeypatch.setattr(svc, "logger", fake_logger)
monkeypatch.setattr(svc.agent_manager, "get_agent", lambda _agent_id: None)
await svc._sync_thread_upload_state(
thread_id="thread-1",
user_id="u1",
agent_id="MissingAgent",
attachments=[],
)
assert any("agent not found" in msg for msg in warnings)
@pytest.mark.asyncio
async def test_convert_upload_to_markdown_returns_conversion_result(
tmp_path,
monkeypatch: pytest.MonkeyPatch,
):
async def _fake_aparse(source: str, params=None) -> str:
return "converted markdown"
monkeypatch.setattr(svc, "_ensure_workdir", lambda: tmp_path)
monkeypatch.setattr(svc.Parser, "aparse", _fake_aparse)
payload = b"hello attachment"
upload = _DummyUpload(filename="note.txt", content_type="text/plain", data=payload)
result = await svc._convert_upload_to_markdown(upload)
assert result.file_name == "note.txt"
assert result.file_type == "text/plain"
assert result.file_size == len(payload)
assert result.markdown == "converted markdown"
assert result.truncated is False
@pytest.mark.asyncio
async def test_convert_upload_to_markdown_truncates_content(
tmp_path,
monkeypatch: pytest.MonkeyPatch,
):
async def _fake_aparse(source: str, params=None) -> str:
return "x" * (svc.MAX_ATTACHMENT_MARKDOWN_CHARS + 200)
monkeypatch.setattr(svc, "_ensure_workdir", lambda: tmp_path)
monkeypatch.setattr(svc.Parser, "aparse", _fake_aparse)
upload = _DummyUpload(filename="note.md", content_type="text/markdown", data=b"hello")
result = await svc._convert_upload_to_markdown(upload)
assert result.truncated is True
assert f"超出 {svc.MAX_ATTACHMENT_MARKDOWN_CHARS} 字符限制" in result.markdown
@pytest.mark.asyncio
async def test_convert_upload_to_markdown_rejects_unsupported_extension():
upload = _DummyUpload(filename="note.pdf", content_type="application/pdf", data=b"pdf")
with pytest.raises(ValueError, match="不支持的文件类型"):
await svc._convert_upload_to_markdown(upload)