e1d74fe163
Co-authored-by: Copilot <copilot@github.com>
185 lines
5.9 KiB
Python
185 lines
5.9 KiB
Python
"""Tests for image upload and retrieval endpoints."""
|
|
import io
|
|
import os
|
|
import pytest
|
|
import pytest_asyncio
|
|
from httpx import AsyncClient, ASGITransport
|
|
|
|
from app.main import app
|
|
from app import db as db_module
|
|
|
|
os.environ.setdefault("JWT_SECRET", "test-secret-key-for-testing-only")
|
|
# Use a temp dir so file I/O works without polluting project data/
|
|
os.environ.setdefault("UPLOAD_DIR", "/tmp/test_uploads")
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def fresh_db():
|
|
db_module._conn = None
|
|
db_module.init_db(":memory:")
|
|
yield
|
|
db_module.close_db()
|
|
db_module._conn = None
|
|
|
|
|
|
@pytest_asyncio.fixture
|
|
async def client(fresh_db):
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://test") as ac:
|
|
yield ac
|
|
|
|
|
|
async def _user_token(client) -> str:
|
|
await client.post("/auth/register", json={"email": "user@example.com", "password": "secret123"})
|
|
resp = await client.post("/auth/login", json={"email": "user@example.com", "password": "secret123"})
|
|
return resp.json()["access_token"]
|
|
|
|
|
|
async def _other_token(client) -> str:
|
|
await client.post("/auth/register", json={"email": "other@example.com", "password": "secret123"})
|
|
resp = await client.post("/auth/login", json={"email": "other@example.com", "password": "secret123"})
|
|
return resp.json()["access_token"]
|
|
|
|
|
|
def _png_bytes() -> bytes:
|
|
"""Minimal valid 1x1 PNG."""
|
|
return (
|
|
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01"
|
|
b"\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\x0f\x00"
|
|
b"\x00\x01\x01\x00\x05\x18\xd4n\x00\x00\x00\x00IEND\xaeB`\x82"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# POST /images/upload
|
|
# ---------------------------------------------------------------------------
|
|
|
|
async def test_upload_image_success(client):
|
|
token = await _user_token(client)
|
|
resp = await client.post(
|
|
"/images/upload",
|
|
files={"file": ("test.png", io.BytesIO(_png_bytes()), "image/png")},
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
)
|
|
assert resp.status_code == 201
|
|
data = resp.json()
|
|
assert data["filename"] == "test.png"
|
|
assert data["content_type"] == "image/png"
|
|
assert "id" in data
|
|
assert data["size_bytes"] > 0
|
|
|
|
|
|
async def test_upload_image_unauthenticated(client):
|
|
resp = await client.post(
|
|
"/images/upload",
|
|
files={"file": ("test.png", io.BytesIO(_png_bytes()), "image/png")},
|
|
)
|
|
assert resp.status_code == 401
|
|
|
|
|
|
async def test_upload_image_unsupported_type(client):
|
|
token = await _user_token(client)
|
|
resp = await client.post(
|
|
"/images/upload",
|
|
files={"file": ("doc.pdf", io.BytesIO(
|
|
b"%PDF-fake"), "application/pdf")},
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
)
|
|
assert resp.status_code == 415
|
|
|
|
|
|
async def test_upload_image_too_large(client, monkeypatch):
|
|
import app.routers.images as images_mod
|
|
monkeypatch.setattr(images_mod, "MAX_SIZE_BYTES", 5)
|
|
token = await _user_token(client)
|
|
resp = await client.post(
|
|
"/images/upload",
|
|
files={"file": ("big.png", io.BytesIO(b"x" * 10), "image/png")},
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
)
|
|
assert resp.status_code == 413
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /images/
|
|
# ---------------------------------------------------------------------------
|
|
|
|
async def test_list_images_empty(client):
|
|
token = await _user_token(client)
|
|
resp = await client.get("/images/", headers={"Authorization": f"Bearer {token}"})
|
|
assert resp.status_code == 200
|
|
assert resp.json() == []
|
|
|
|
|
|
async def test_list_images_returns_own_only(client):
|
|
token = await _user_token(client)
|
|
other = await _other_token(client)
|
|
|
|
# Upload one image as user, one as other
|
|
for tok, name in [(token, "mine.png"), (other, "theirs.png")]:
|
|
await client.post(
|
|
"/images/upload",
|
|
files={"file": (name, io.BytesIO(_png_bytes()), "image/png")},
|
|
headers={"Authorization": f"Bearer {tok}"},
|
|
)
|
|
|
|
resp = await client.get("/images/", headers={"Authorization": f"Bearer {token}"})
|
|
assert resp.status_code == 200
|
|
items = resp.json()
|
|
assert len(items) == 1
|
|
assert items[0]["filename"] == "mine.png"
|
|
|
|
|
|
async def test_list_images_unauthenticated(client):
|
|
resp = await client.get("/images/")
|
|
assert resp.status_code == 401
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /images/{id}/file
|
|
# ---------------------------------------------------------------------------
|
|
|
|
async def test_serve_image_success(client):
|
|
token = await _user_token(client)
|
|
up = await client.post(
|
|
"/images/upload",
|
|
files={"file": ("pixel.png", io.BytesIO(_png_bytes()), "image/png")},
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
)
|
|
image_id = up.json()["id"]
|
|
|
|
resp = await client.get(
|
|
f"/images/{image_id}/file",
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert resp.headers["content-type"].startswith("image/png")
|
|
assert resp.content == _png_bytes()
|
|
|
|
|
|
async def test_serve_image_wrong_user(client):
|
|
token = await _user_token(client)
|
|
other = await _other_token(client)
|
|
|
|
up = await client.post(
|
|
"/images/upload",
|
|
files={"file": ("secret.png", io.BytesIO(_png_bytes()), "image/png")},
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
)
|
|
image_id = up.json()["id"]
|
|
|
|
resp = await client.get(
|
|
f"/images/{image_id}/file",
|
|
headers={"Authorization": f"Bearer {other}"},
|
|
)
|
|
assert resp.status_code == 403
|
|
|
|
|
|
async def test_serve_image_not_found(client):
|
|
token = await _user_token(client)
|
|
resp = await client.get(
|
|
"/images/00000000-0000-0000-0000-000000000000/file",
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
)
|
|
assert resp.status_code == 404
|