"""Public attachment upload (local storage).""" import pytest from app.core.config import get_settings PNG = b"\x89PNG\r\n\x1a\n" + b"\x00" * 8 PDF = b"%PDF-1.4" + b"\x00" * 16 @pytest.fixture def local_storage_env(tmp_path, monkeypatch): monkeypatch.setenv("STORAGE_TYPE", "local") monkeypatch.setenv("LOCAL_STORAGE_DIR", str(tmp_path)) get_settings.cache_clear() yield tmp_path get_settings.cache_clear() def test_public_upload(client, local_storage_env): files = {"file": ("a.png", PNG, "image/png")} r = client.post( "/api/public/attachment/upload", files=files, data={"folder": "feedback"}, ) assert r.status_code == 200 data = r.json()["data"] assert data["url"] assert data["id"] assert data["fileName"] == "a.png" assert data["size"] == len(PNG) assert data["url"].startswith("/uploads/feedback/") def test_public_upload_pdf(client, local_storage_env): files = {"file": ("report.pdf", PDF, "application/pdf")} r = client.post( "/api/public/attachment/upload", files=files, data={"folder": "suggestion"}, ) assert r.status_code == 200 data = r.json()["data"] assert data["fileName"] == "report.pdf" assert data["url"].endswith(".pdf") or ".pdf" in data["url"] def test_public_upload_rejects_disallowed_extension(client, local_storage_env): files = {"file": ("malware.exe", b"MZ", "application/octet-stream")} r = client.post( "/api/public/attachment/upload", files=files, data={"folder": "feedback"}, ) assert r.status_code == 400 body = r.json() assert body["code"] == 400 assert "类型" in body["message"] or "格式" in body["message"] or "不支持" in body["message"] def test_public_upload_allows_large_file(client, local_storage_env): # 原 20MB 限制已取消;略超原阈值仍应成功 big = b"%PDF-1.4" + b"x" * (20 * 1024 * 1024) files = {"file": ("big.pdf", big, "application/pdf")} r = client.post( "/api/public/attachment/upload", files=files, data={"folder": "feedback"}, ) assert r.status_code == 200 data = r.json()["data"] assert data["fileName"] == "big.pdf" assert data["size"] == len(big) def test_public_upload_rejects_path_traversal(client, local_storage_env): base = local_storage_env outside = (base / ".." / "outside").resolve() assert not outside.exists() files = {"file": ("evil.png", PNG, "image/png")} r = client.post( "/api/public/attachment/upload", files=files, data={"folder": "../outside"}, ) assert r.status_code == 400 body = r.json() assert body["code"] == 400 assert "message" in body assert "detail" not in body assert not outside.exists() assert not any(base.rglob("*")) def test_public_upload_rejects_qrcode_folder(client, local_storage_env): files = {"file": ("a.png", PNG, "image/png")} r = client.post( "/api/public/attachment/upload", files=files, data={"folder": "qrcode"}, ) assert r.status_code == 400 assert r.json()["code"] == 400 def test_public_upload_allows_feedback_folder(client, local_storage_env): files = {"file": ("a.png", PNG, "image/png")} r = client.post( "/api/public/attachment/upload", files=files, data={"folder": "feedback"}, ) assert r.status_code == 200 assert "/feedback/" in r.json()["data"]["url"]