|
|
@@ -3,6 +3,9 @@ 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):
|
|
|
@@ -14,15 +17,60 @@ def local_storage_env(tmp_path, monkeypatch):
|
|
|
|
|
|
|
|
|
def test_public_upload(client, local_storage_env):
|
|
|
- files = {"file": ("a.png", b"fake-png-bytes", "image/png")}
|
|
|
- r = client.post("/api/public/attachment/upload", files=files)
|
|
|
+ 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(b"fake-png-bytes")
|
|
|
- assert data["url"].startswith("/uploads/")
|
|
|
+ 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_rejects_oversized(client, local_storage_env):
|
|
|
+ # 20MB + 1 byte
|
|
|
+ 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 == 400
|
|
|
+ body = r.json()
|
|
|
+ assert body["code"] == 400
|
|
|
+ assert "大小" in body["message"] or "过大" in body["message"] or "MB" in body["message"]
|
|
|
|
|
|
|
|
|
def test_public_upload_rejects_path_traversal(client, local_storage_env):
|
|
|
@@ -30,7 +78,7 @@ def test_public_upload_rejects_path_traversal(client, local_storage_env):
|
|
|
outside = (base / ".." / "outside").resolve()
|
|
|
assert not outside.exists()
|
|
|
|
|
|
- files = {"file": ("evil.png", b"evil-bytes", "image/png")}
|
|
|
+ files = {"file": ("evil.png", PNG, "image/png")}
|
|
|
r = client.post(
|
|
|
"/api/public/attachment/upload",
|
|
|
files=files,
|
|
|
@@ -43,3 +91,25 @@ def test_public_upload_rejects_path_traversal(client, local_storage_env):
|
|
|
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"]
|