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.
129 lines
4.2 KiB
129 lines
4.2 KiB
import base64
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from sqlalchemy import select
|
|
|
|
from app.models.entities import Backend, BlobReplica
|
|
|
|
|
|
def _encode_metadata(value: str) -> str:
|
|
return base64.b64encode(value.encode("utf-8")).decode("utf-8")
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_tus_create_head_patch_and_finalize(api_client, session_factory, runtime_paths) -> None:
|
|
metadata_header = f"filename {_encode_metadata('movie.mp4')}"
|
|
create_response = await api_client.post(
|
|
"/files",
|
|
headers={
|
|
"Tus-Resumable": "1.0.0",
|
|
"Upload-Length": "11",
|
|
"Upload-Metadata": metadata_header,
|
|
},
|
|
)
|
|
|
|
assert create_response.status_code == 201
|
|
upload_location = create_response.headers["Location"]
|
|
upload_id = upload_location.rsplit("/", 1)[-1]
|
|
|
|
head_response = await api_client.head(
|
|
f"/files/{upload_id}",
|
|
headers={"Tus-Resumable": "1.0.0"},
|
|
)
|
|
assert head_response.status_code == 200
|
|
assert head_response.headers["Upload-Offset"] == "0"
|
|
assert head_response.headers["Upload-Length"] == "11"
|
|
|
|
patch_response = await api_client.patch(
|
|
f"/files/{upload_id}",
|
|
headers={
|
|
"Tus-Resumable": "1.0.0",
|
|
"Upload-Offset": "0",
|
|
"Content-Type": "application/offset+octet-stream",
|
|
},
|
|
content=b"hello world",
|
|
)
|
|
assert patch_response.status_code == 204
|
|
assert patch_response.headers["Upload-Offset"] == "11"
|
|
|
|
finalize_response = await api_client.post(
|
|
f"/api/uploads/{upload_id}/finalize",
|
|
json={"directory_id": "dir_root", "filename": "movie.mp4"},
|
|
)
|
|
assert finalize_response.status_code == 200
|
|
payload = finalize_response.json()
|
|
assert payload["upload"]["status"] == "finalized"
|
|
assert payload["file"]["name"] == "movie.mp4"
|
|
assert payload["file"]["size_bytes"] == 11
|
|
|
|
detail_response = await api_client.get(f"/api/files/{payload['file']['id']}")
|
|
assert detail_response.status_code == 200
|
|
assert detail_response.json()["file"]["name"] == "movie.mp4"
|
|
|
|
async with session_factory() as session:
|
|
backend = (
|
|
await session.execute(select(Backend).where(Backend.name == "local-default"))
|
|
).scalar_one()
|
|
replica = (await session.execute(select(BlobReplica).where(BlobReplica.backend_id == backend.id))).scalar_one()
|
|
|
|
assert replica.storage_key.startswith("objects/file/sha256/")
|
|
assert replica.storage_key.endswith(".blob")
|
|
stored_path = Path(runtime_paths["local_storage_dir"]) / replica.storage_key
|
|
assert stored_path.exists()
|
|
assert stored_path.read_bytes() == b"hello world"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_tus_patch_rejects_wrong_offset(api_client) -> None:
|
|
create_response = await api_client.post(
|
|
"/files",
|
|
headers={
|
|
"Tus-Resumable": "1.0.0",
|
|
"Upload-Length": "5",
|
|
},
|
|
)
|
|
upload_id = create_response.headers["Location"].rsplit("/", 1)[-1]
|
|
|
|
patch_response = await api_client.patch(
|
|
f"/files/{upload_id}",
|
|
headers={
|
|
"Tus-Resumable": "1.0.0",
|
|
"Upload-Offset": "3",
|
|
"Content-Type": "application/offset+octet-stream",
|
|
},
|
|
content=b"hello",
|
|
)
|
|
|
|
assert patch_response.status_code == 409
|
|
assert patch_response.json()["error"]["code"] == "upload_conflict"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_finalize_requires_complete_upload(api_client) -> None:
|
|
create_response = await api_client.post(
|
|
"/files",
|
|
headers={
|
|
"Tus-Resumable": "1.0.0",
|
|
"Upload-Length": "10",
|
|
},
|
|
)
|
|
upload_id = create_response.headers["Location"].rsplit("/", 1)[-1]
|
|
|
|
patch_response = await api_client.patch(
|
|
f"/files/{upload_id}",
|
|
headers={
|
|
"Tus-Resumable": "1.0.0",
|
|
"Upload-Offset": "0",
|
|
"Content-Type": "application/offset+octet-stream",
|
|
},
|
|
content=b"abc",
|
|
)
|
|
assert patch_response.status_code == 204
|
|
|
|
finalize_response = await api_client.post(
|
|
f"/api/uploads/{upload_id}/finalize",
|
|
json={"directory_id": "dir_root", "filename": "partial.bin"},
|
|
)
|
|
assert finalize_response.status_code == 409
|
|
assert finalize_response.json()["error"]["code"] == "upload_conflict"
|
|
|