Files
ai.allucanget.biz/backend/tests/test_images.py
T

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