"""Upload dispatch over FastAPI multipart — files bind into Upload fields and the declarative `File(...)` constraints are enforced.""" from __future__ import annotations import json from typing import Annotated import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel from mizan_core.client.function import client from mizan_core.registry import clear_registry, register from mizan_fastapi import File, MizanError, Upload, mizan_exception_handler, router as mizan_router class AvatarOut(BaseModel): ok: bool size: int name: str | None = None @pytest.fixture def app(): clear_registry() @client def set_avatar( request, user_id: int, avatar: Annotated[Upload, File(max_size="1MB", content_types=["image/png"])], ) -> AvatarOut: return AvatarOut(ok=True, size=avatar.size, name=avatar.filename) register(set_avatar, "set_avatar") fastapi_app = FastAPI() fastapi_app.include_router(mizan_router, prefix="/api/mizan") fastapi_app.add_exception_handler(MizanError, mizan_exception_handler) return fastapi_app def _post(test_client: TestClient, args: dict, file_tuple: tuple): return test_client.post( "/api/mizan/call/", data={"fn": "set_avatar", "args": json.dumps(args)}, files={"avatar": file_tuple}, ) def test_upload_binds_and_executes(app): resp = _post(TestClient(app), {"user_id": 5}, ("a.png", b"\x89PNG" + b"x" * 100, "image/png")) assert resp.status_code == 200, resp.text result = resp.json()["result"] assert result["ok"] is True assert result["name"] == "a.png" assert result["size"] == 104 def test_max_size_rejected(app): resp = _post(TestClient(app), {"user_id": 5}, ("b.png", b"x" * (2 * 1024 * 1024), "image/png")) assert resp.status_code == 400 assert "max size" in resp.text def test_content_type_rejected(app): resp = _post(TestClient(app), {"user_id": 5}, ("c.gif", b"GIF89a", "image/gif")) assert resp.status_code == 400 assert "content-type" in resp.text