Files
Octop/tests/unit/api/test_attachments.py
HUANG Cheng 99b3c173e0 Merge pull request #1938 from huangcheng/feature/1345-wps-kb-chat-anydoc
feat(knowledge): support .wps/.doc via firecrawl-anydoc (#1345)
2026-10-13 10:24:22 +08:00

274 lines
11 KiB
Python

"""Unit tests for chat attachment workspace paths."""
from __future__ import annotations
import re
import tempfile
import pytest
from deepagents.backends.local_shell import LocalShellBackend
from octop_gateway.models import FileContent, InboundMessage
from octop_harness.backends.workspace import BackendWorkspace
from octop.api.common.attachments import dashboard_inbound_preview_url, save_attachment
from octop.infra.errors import ErrorCode, OctopError
from octop.infra.gateway.media.attachment_hints import (
hints_from_content_parts,
sniff_image_media_type,
)
from octop.infra.gateway.media.inbound_store import (
MAX_INBOUND_BYTES,
inbound_extension,
validate_inbound_media_type,
validate_inbound_size,
)
from octop.infra.gateway.media.ingress import AgentBackedMediaBackend
from octop.infra.gateway.process.harness_request import build_content_from_message
def _workspace(root: str) -> BackendWorkspace:
backend = LocalShellBackend(root_dir=root, virtual_mode=False)
return BackendWorkspace(backend, root)
def test_inbound_extension_from_filename() -> None:
assert inbound_extension("report.pdf", "application/pdf") == ".pdf"
assert inbound_extension("data.PDF", "application/pdf") == ".pdf"
def test_inbound_extension_from_media_type() -> None:
assert inbound_extension("upload", "application/pdf") == ".pdf"
def test_inbound_extension_fallback_bin() -> None:
assert inbound_extension("upload", "application/octet-stream") == ".bin"
def test_sniff_png_from_clipboard_blob() -> None:
from octop.api.routers.uploads import _resolve_media_type
png = b"\x89PNG\r\n\x1a\n" + b"fake-png-body"
assert sniff_image_media_type(png) == "image/png"
assert _resolve_media_type("blob", "application/octet-stream", png) == "image/png"
def test_unknown_mime_falls_back_to_extension() -> None:
assert (
validate_inbound_media_type("application/x-zip-compressed", "bundle.zip")
== "application/zip"
)
assert validate_inbound_media_type("application/x-empty", "data.csv") == "text/csv"
assert validate_inbound_media_type("image/x-png", "photo.png") == "image/png"
def test_programmer_extensions_are_accepted() -> None:
assert validate_inbound_media_type("video/mp2t", "app.ts") == "text/x-typescript"
assert validate_inbound_media_type("application/x-sh", "setup.sh") == "text/x-shellscript"
assert validate_inbound_media_type("text/x-python", "main.py") == "text/x-python"
assert validate_inbound_media_type("foo/bar", "query.sql") == "application/sql"
def test_office_image_and_archive_extensions_are_accepted() -> None:
assert validate_inbound_media_type("foo/bar", "notes.doc") == "application/msword"
assert validate_inbound_media_type("foo/bar", "notes.wps") == "application/msword"
assert (
validate_inbound_media_type("application/wps-office.wps", "notes.wps")
== "application/msword"
)
assert validate_inbound_media_type("application/kswps", "blob") == "application/msword"
assert validate_inbound_media_type("foo/bar", "notes.rtf") == "application/rtf"
assert validate_inbound_media_type("foo/bar", "book.epub") == "application/epub+zip"
assert validate_inbound_media_type("foo/bar", "map.xmind") == "application/vnd.xmind.workbook"
assert validate_inbound_media_type("foo/bar", "photo.heic") == "image/heic"
assert validate_inbound_media_type("foo/bar", "photo.bmp") == "image/bmp"
assert validate_inbound_media_type("foo/bar", "bundle.7z") == "application/x-7z-compressed"
assert validate_inbound_media_type("foo/bar", "bundle.rar") == "application/vnd.rar"
assert validate_inbound_media_type("foo/bar", "bundle.tgz") == "application/gzip"
def test_video_audio_and_executable_extensions_are_accepted() -> None:
assert validate_inbound_media_type("foo/bar", "clip.mp4") == "video/mp4"
assert validate_inbound_media_type("foo/bar", "clip.webm") == "video/webm"
assert validate_inbound_media_type("foo/bar", "clip.mov") == "video/quicktime"
assert validate_inbound_media_type("foo/bar", "clip.mkv") == "video/x-matroska"
assert validate_inbound_media_type("foo/bar", "track.mp3") == "audio/mpeg"
assert validate_inbound_media_type("foo/bar", "track.wav") == "audio/wav"
assert validate_inbound_media_type("foo/bar", "track.aac") == "audio/aac"
assert validate_inbound_media_type("foo/bar", "track.flac") == "audio/flac"
assert validate_inbound_media_type("foo/bar", "setup.exe") == "application/x-msdownload"
assert validate_inbound_media_type("foo/bar", "disk.dmg") == "application/x-apple-diskimage"
assert (
validate_inbound_media_type("foo/bar", "app.apk")
== "application/vnd.android.package-archive"
)
def test_video_audio_and_executable_mime_types_are_accepted() -> None:
assert validate_inbound_media_type("video/mp4", "blob") == "video/mp4"
assert validate_inbound_media_type("video/webm", "blob") == "video/webm"
assert validate_inbound_media_type("video/quicktime", "blob") == "video/quicktime"
assert validate_inbound_media_type("audio/mpeg", "blob") == "audio/mpeg"
assert validate_inbound_media_type("audio/x-wav", "blob") == "audio/wav"
assert validate_inbound_media_type("application/x-msdownload", "setup") == (
"application/x-msdownload"
)
assert (
validate_inbound_media_type(
"application/vnd.android.package-archive",
"app",
)
== "application/vnd.android.package-archive"
)
def test_media_type_aliases_are_canonicalized() -> None:
assert validate_inbound_media_type("audio/mp3", "blob") == "audio/mpeg"
assert validate_inbound_media_type("application/javascript", "app.js") == "text/javascript"
assert validate_inbound_media_type("application/x-zip-compressed", "bundle.zip") == (
"application/zip"
)
def test_octet_stream_with_known_extension_uses_extension_mime() -> None:
assert validate_inbound_media_type("application/octet-stream", "clip.mp4") == "video/mp4"
assert validate_inbound_media_type("application/octet-stream", "blob") == (
"application/octet-stream"
)
def test_av_mime_prefixes_are_accepted() -> None:
assert validate_inbound_media_type("video/x-msvideo", "blob") == "video/x-msvideo"
assert validate_inbound_media_type("audio/ogg", "blob") == "audio/ogg"
assert validate_inbound_media_type("video/mp2t", "app.ts") == "text/x-typescript"
def test_dashboard_preview_url_uses_media_preview_for_av() -> None:
image = dashboard_inbound_preview_url("a1", "inbound/x.png", media_type="image/png")
audio = dashboard_inbound_preview_url("a1", "inbound/x.mp3", media_type="audio/mpeg")
video = dashboard_inbound_preview_url("a1", "inbound/x.mp4", media_type="video/mp4")
pdf = dashboard_inbound_preview_url("a1", "inbound/x.pdf", media_type="application/pdf")
assert "/media/preview?" in image
assert "/media/preview?" in audio
assert "/media/preview?" in video
assert "/workspace/download?" in pdf
def test_unknown_mime_without_known_extension_is_rejected() -> None:
with pytest.raises(OctopError) as exc_info:
validate_inbound_media_type("application/x-zip-compressed", "bundle")
assert exc_info.value.code is ErrorCode.ATTACHMENT_UNSUPPORTED_TYPE
# ``details.reason`` survives i18n localization; the raw message does not.
assert "application/x-zip-compressed" in str(exc_info.value.details["reason"])
envelope = exc_info.value.to_envelope(locale="zh")
assert envelope["error"]["details"]["reason"] == exc_info.value.details["reason"]
def test_allowed_mime_is_kept_even_if_extension_differs() -> None:
assert validate_inbound_media_type("application/pdf", "notes.txt") == "application/pdf"
def test_inbound_over_size_limit_is_rejected() -> None:
limit = 1024
with pytest.raises(OctopError) as exc_info:
validate_inbound_size(b"x" * (limit + 1), max_bytes=limit)
assert exc_info.value.code is ErrorCode.ATTACHMENT_TOO_LARGE
assert exc_info.value.status == 413
assert exc_info.value.details["max_mb"] == 1
envelope = exc_info.value.to_envelope(locale="zh")
assert "1" in envelope["error"]["message"]
assert "{max_mb}" not in envelope["error"]["message"]
def test_default_inbound_limit_is_100_mib() -> None:
assert MAX_INBOUND_BYTES == 100 * 1024 * 1024
@pytest.mark.asyncio
async def test_save_attachment_zip_from_windows_browser() -> None:
with tempfile.TemporaryDirectory() as ws_dir:
workspace = _workspace(ws_dir)
stored = await save_attachment(
workspace,
owner_id=1,
filename="bundle.zip",
media_type="application/x-zip-compressed",
data=b"PK\x03\x04",
)
assert stored.media_type == "application/zip"
assert stored.data_path.endswith(".zip")
@pytest.mark.asyncio
async def test_save_attachment_pdf_uses_extension() -> None:
with tempfile.TemporaryDirectory() as ws_dir:
workspace = _workspace(ws_dir)
stored = await save_attachment(
workspace,
owner_id=1,
filename="report.pdf",
media_type="application/pdf",
data=b"%PDF-1.4",
)
assert re.fullmatch(r"inbound/\d{10,}_report\.pdf", stored.data_path)
assert stored.filename == "report.pdf"
assert stored.data_path.endswith(".pdf")
on_disk = await workspace.adownload_bytes(stored.data_path)
assert on_disk == b"%PDF-1.4"
@pytest.mark.asyncio
async def test_build_content_from_file_part_uses_resolved_path() -> None:
from octop_gateway.models import TextContent
with tempfile.TemporaryDirectory() as ws_dir:
workspace = _workspace(ws_dir)
backend = AgentBackedMediaBackend(workspace)
msg = InboundMessage(
channel_id="c",
channel_type="dashboard",
content=[
TextContent(text="summarize"),
FileContent(
filename="report.pdf",
mime_type="application/pdf",
local_path="inbound/01JTEST.pdf",
),
],
)
out = await build_content_from_message(msg, media_backend=backend)
assert isinstance(out, str)
from octop.infra.gateway.media.inbound_store import resolve_inbound_attachment_path
resolved = resolve_inbound_attachment_path(workspace, "inbound/01JTEST.pdf")
assert resolved in out
assert "pdf" in out.lower()
@pytest.mark.asyncio
async def test_hints_from_saved_attachment_path() -> None:
with tempfile.TemporaryDirectory() as ws_dir:
workspace = _workspace(ws_dir)
stored = await save_attachment(
workspace,
owner_id=1,
filename="report.pdf",
media_type="application/pdf",
data=b"%PDF-1.4",
)
hints = hints_from_content_parts(
[
FileContent(
filename="report.pdf",
mime_type="application/pdf",
local_path=stored.data_path,
)
],
workspace=workspace,
)
assert len(hints) == 1
from octop.infra.gateway.media.inbound_store import resolve_inbound_attachment_path
resolved = resolve_inbound_attachment_path(workspace, stored.data_path)
assert resolved in hints[0]