pythonasyncioapiasyncfastapiframeworkjsonjson-schemaopenapiopenapi3pydanticpython-typespython3redocreststarletteswaggerswagger-uiuvicornweb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
4.7 KiB
165 lines
4.7 KiB
import io
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from fastapi import FastAPI, UploadFile
|
|
from fastapi.datastructures import Default, ValidationResult
|
|
from fastapi.testclient import TestClient
|
|
from starlette.datastructures import Headers
|
|
from starlette.exceptions import HTTPException
|
|
|
|
|
|
def test_upload_file_invalid_pydantic_v2():
|
|
with pytest.raises(ValueError):
|
|
UploadFile._validate("not a Starlette UploadFile", {})
|
|
|
|
|
|
def test_default_placeholder_equals():
|
|
placeholder_1 = Default("a")
|
|
placeholder_2 = Default("a")
|
|
assert placeholder_1 == placeholder_2
|
|
assert placeholder_1.value == placeholder_2.value
|
|
|
|
|
|
def test_default_placeholder_bool():
|
|
placeholder_a = Default("a")
|
|
placeholder_b = Default("")
|
|
assert placeholder_a
|
|
assert not placeholder_b
|
|
|
|
|
|
def test_upload_file_is_closed(tmp_path: Path):
|
|
path = tmp_path / "test.txt"
|
|
path.write_bytes(b"<file content>")
|
|
app = FastAPI()
|
|
|
|
testing_file_store: list[UploadFile] = []
|
|
|
|
@app.post("/uploadfile/")
|
|
def create_upload_file(file: UploadFile):
|
|
testing_file_store.append(file)
|
|
return {"filename": file.filename}
|
|
|
|
client = TestClient(app)
|
|
with path.open("rb") as file:
|
|
response = client.post("/uploadfile/", files={"file": file})
|
|
assert response.status_code == 200, response.text
|
|
assert response.json() == {"filename": "test.txt"}
|
|
|
|
assert testing_file_store
|
|
assert testing_file_store[0].file.closed
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_upload_file():
|
|
stream = io.BytesIO(b"data")
|
|
file = UploadFile(filename="file", file=stream, size=4)
|
|
assert await file.read() == b"data"
|
|
assert file.size == 4
|
|
await file.write(b" and more data!")
|
|
assert await file.read() == b""
|
|
assert file.size == 19
|
|
await file.seek(0)
|
|
assert await file.read() == b"data and more data!"
|
|
await file.close()
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_upload_file_validate_size_pass():
|
|
stream = io.BytesIO(b"small file")
|
|
file = UploadFile(filename="test.txt", file=stream, size=10, max_size=100)
|
|
result = await file.validate()
|
|
assert result.is_valid
|
|
assert result.file_size == 10
|
|
assert result.content_type is None
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_upload_file_validate_size_fail():
|
|
stream = io.BytesIO(b"x" * 200)
|
|
file = UploadFile(filename="large.txt", file=stream, size=200, max_size=100)
|
|
with pytest.raises(HTTPException) as exc:
|
|
await file.validate()
|
|
assert exc.value.status_code == 413
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_upload_file_validate_size_none():
|
|
stream = io.BytesIO(b"x" * 999)
|
|
file = UploadFile(filename="any.txt", file=stream, size=999, max_size=None)
|
|
result = await file.validate()
|
|
assert result.is_valid
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_upload_file_validate_content_type_pass():
|
|
stream = io.BytesIO(b"some json")
|
|
file = UploadFile(
|
|
filename="data.json",
|
|
file=stream,
|
|
size=9,
|
|
headers=Headers({"content-type": "application/json"}),
|
|
allowed_content_types=["application/json", "text/plain"],
|
|
)
|
|
result = await file.validate()
|
|
assert result.is_valid
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_upload_file_validate_content_type_fail():
|
|
stream = io.BytesIO(b"some data")
|
|
file = UploadFile(
|
|
filename="data.xml",
|
|
file=stream,
|
|
size=9,
|
|
headers=Headers({"content-type": "application/xml"}),
|
|
allowed_content_types=["image/png"],
|
|
)
|
|
with pytest.raises(HTTPException) as exc:
|
|
await file.validate()
|
|
assert exc.value.status_code == 415
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_upload_file_validate_content_type_none():
|
|
stream = io.BytesIO(b"any type")
|
|
file = UploadFile(
|
|
filename="data.bin",
|
|
file=stream,
|
|
size=8,
|
|
headers=Headers({"content-type": "text/plain"}),
|
|
allowed_content_types=None,
|
|
)
|
|
result = await file.validate()
|
|
assert result.is_valid
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_upload_file_validate_both_constraints():
|
|
stream = io.BytesIO(b"ok")
|
|
file = UploadFile(
|
|
filename="ok.txt",
|
|
file=stream,
|
|
size=2,
|
|
max_size=100,
|
|
headers=Headers({"content-type": "text/plain"}),
|
|
allowed_content_types=["text/plain"],
|
|
)
|
|
result = await file.validate()
|
|
assert result.is_valid
|
|
|
|
|
|
def test_upload_file_existing_usage_unchanged():
|
|
stream = io.BytesIO(b"data")
|
|
file = UploadFile(filename="file", file=stream, size=4)
|
|
assert file.filename == "file"
|
|
assert file.size == 4
|
|
assert file.max_size is None
|
|
assert file.allowed_content_types is None
|
|
|
|
|
|
def test_validation_result_dataclass():
|
|
result = ValidationResult(is_valid=True, file_size=100, content_type="text/plain")
|
|
assert result.is_valid
|
|
assert result.file_size == 100
|
|
assert result.content_type == "text/plain"
|
|
|