from __future__ import annotations import io from pathlib import Path import pytest from yuxi.services import conversation_service as cs 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_attachment_storage_path_uses_thread_local_uploads_dir(tmp_path: Path, monkeypatch) -> None: monkeypatch.setattr(cs.app_config, "save_dir", str(tmp_path)) virtual_path, host_path = cs._build_attachment_storage_path( user_id="u-1", thread_id="t-1", file_name="demo.txt", ) assert virtual_path == "/home/gem/user-data/uploads/attachments/demo.md" assert host_path == tmp_path / "threads" / "t-1" / "user-data" / "uploads" / "attachments" / "demo.md" def test_serialize_attachment_includes_original_file_fields() -> None: serialized = cs.serialize_attachment( { "file_id": "f-1", "file_name": "demo.txt", "file_type": "text/plain", "file_size": 5, "status": "parsed", "uploaded_at": "2026-03-25T00:00:00+00:00", "path": "/home/gem/user-data/uploads/attachments/demo.md", "artifact_url": "/api/chat/thread/t-1/artifacts/home/gem/user-data/uploads/attachments/demo.md", "original_path": "/home/gem/user-data/uploads/demo.txt", "original_artifact_url": "/api/chat/thread/t-1/artifacts/home/gem/user-data/uploads/demo.txt", "minio_url": None, } ) assert serialized["path"] == "/home/gem/user-data/uploads/attachments/demo.md" assert serialized["original_path"] == "/home/gem/user-data/uploads/demo.txt" assert serialized["original_artifact_url"] == "/api/chat/thread/t-1/artifacts/home/gem/user-data/uploads/demo.txt" @pytest.mark.asyncio async def test_materialize_attachment_files_keeps_original_file_when_markdown_conversion_unsupported( tmp_path: Path, monkeypatch, ) -> None: monkeypatch.setattr(cs.app_config, "save_dir", str(tmp_path)) upload = _DummyUpload(filename="demo.pdf", content_type="application/pdf", data=b"%PDF-test") result = await cs._materialize_attachment_files( thread_id="t-1", upload=upload, file_name="demo.pdf", file_content=b"%PDF-test", ) assert result["status"] == "uploaded" assert result["path"] == "/home/gem/user-data/uploads/demo.pdf" assert result["original_path"] == "/home/gem/user-data/uploads/demo.pdf" assert ( tmp_path / "threads" / "t-1" / "user-data" / "uploads" / "demo.pdf" ).read_bytes() == b"%PDF-test" @pytest.mark.asyncio async def test_materialize_attachment_files_writes_markdown_copy_when_conversion_succeeds( tmp_path: Path, monkeypatch, ) -> None: monkeypatch.setattr(cs.app_config, "save_dir", str(tmp_path)) async def _fake_convert(_upload): return cs.ConversionResult( file_id="f-1", file_name="demo.txt", file_type="text/plain", file_size=5, markdown="hello\nworld", truncated=False, ) monkeypatch.setattr(cs, "_convert_upload_to_markdown", _fake_convert) upload = _DummyUpload(filename="demo.txt", content_type="text/plain", data=b"hello") result = await cs._materialize_attachment_files( thread_id="t-1", upload=upload, file_name="demo.txt", file_content=b"hello", ) assert result["status"] == "parsed" assert result["path"] == "/home/gem/user-data/uploads/attachments/demo.md" assert result["original_path"] == "/home/gem/user-data/uploads/demo.txt" assert result["file_path"] == "/home/gem/user-data/uploads/attachments/demo.md" assert result["markdown"] == "hello\nworld" assert (tmp_path / "threads" / "t-1" / "user-data" / "uploads" / "demo.txt").read_bytes() == b"hello" assert ( tmp_path / "threads" / "t-1" / "user-data" / "uploads" / "attachments" / "demo.md" ).read_text(encoding="utf-8") == "hello\nworld"