kanban-app/backend/tests/routes/test_files.py

373 lines
11 KiB
Python
Raw Permalink Normal View History

2026-03-20 17:17:01 +00:00
"""Integration tests for file upload routes"""
import pytest
@pytest.mark.integration
def test_upload_image_to_card(client, auth_headers, test_card, test_image_file):
"""Test uploading an image to a card"""
data = {
"file": (test_image_file, "test.png", "image/png"),
}
response = client.post(
f"/api/cards/{test_card.id}/attachments",
headers=auth_headers,
data=data,
content_type="multipart/form-data",
)
assert response.status_code == 201
file_data = response.get_json()
# Only check public-facing fields
assert file_data["original_name"] == "test.png"
assert file_data["file_type"] == "image"
assert file_data["file_size"] > 0
assert file_data["mime_type"] == "image/png" # Mime type is now exposed
assert "uuid" in file_data
assert "download_url" in file_data
assert "thumbnail_url" in file_data
assert "view_url" in file_data
assert "created_at" in file_data
# Security: Verify internal details are NOT exposed
assert "id" not in file_data
assert "filename" not in file_data
assert "minio_bucket" not in file_data
assert "minio_object_name" not in file_data
assert "thumbnail_minio_bucket" not in file_data
assert "thumbnail_minio_object_name" not in file_data
assert "attachable_id" not in file_data
assert "attachable_type" not in file_data
assert "uploaded_by" not in file_data
@pytest.mark.integration
def test_upload_pdf_to_card(client, auth_headers, test_card, test_pdf_file):
"""Test uploading a PDF to a card"""
data = {
"file": (test_pdf_file, "test.pdf", "application/pdf"),
}
response = client.post(
f"/api/cards/{test_card.id}/attachments",
headers=auth_headers,
data=data,
content_type="multipart/form-data",
)
assert response.status_code == 201
file_data = response.get_json()
assert file_data["original_name"] == "test.pdf"
assert file_data["file_type"] == "pdf" # PDFProcessor returns "pdf" as file_type
# PDFs don't have thumbnails
assert file_data.get("thumbnail_url") is None
@pytest.mark.integration
def test_upload_file_to_comment(client, auth_headers, test_comment, test_image_file):
"""Test uploading a file to a comment"""
data = {
"file": (test_image_file, "comment_image.png", "image/png"),
}
response = client.post(
f"/api/comments/{test_comment.id}/attachments",
headers=auth_headers,
data=data,
content_type="multipart/form-data",
)
assert response.status_code == 201
file_data = response.get_json()
assert file_data["original_name"] == "comment_image.png"
assert file_data["file_type"] == "image"
@pytest.mark.integration
def test_get_card_attachments(client, auth_headers, test_card, test_image_file):
"""Test getting all attachments for a card"""
# Upload a file first
data = {"file": (test_image_file, "test.png", "image/png")}
client.post(
f"/api/cards/{test_card.id}/attachments",
headers=auth_headers,
data=data,
content_type="multipart/form-data",
)
# Get attachments
response = client.get(
f"/api/cards/{test_card.id}/attachments",
headers=auth_headers,
)
assert response.status_code == 200
attachments_data = response.get_json()
assert attachments_data["count"] == 1
assert len(attachments_data["files"]) == 1
assert attachments_data["files"][0]["original_name"] == "test.png"
# Verify only public fields are exposed
assert "id" not in attachments_data["files"][0]
assert "attachable_id" not in attachments_data["files"][0]
@pytest.mark.integration
def test_get_comment_attachments(client, auth_headers, test_comment, test_image_file):
"""Test getting all attachments for a comment"""
# Upload a file first
data = {"file": (test_image_file, "test.png", "image/png")}
client.post(
f"/api/comments/{test_comment.id}/attachments",
headers=auth_headers,
data=data,
content_type="multipart/form-data",
)
# Get attachments
response = client.get(
f"/api/comments/{test_comment.id}/attachments",
headers=auth_headers,
)
assert response.status_code == 200
attachments_data = response.get_json()
assert attachments_data["count"] == 1
assert len(attachments_data["files"]) == 1
@pytest.mark.integration
def test_get_file_by_uuid(client, auth_headers, test_card, test_image_file):
"""Test getting file info by UUID"""
# Upload a file first
data = {"file": (test_image_file, "test.png", "image/png")}
upload_response = client.post(
f"/api/cards/{test_card.id}/attachments",
headers=auth_headers,
data=data,
content_type="multipart/form-data",
)
file_uuid = upload_response.get_json()["uuid"]
# Get file by UUID
response = client.get(
f"/api/files/{file_uuid}",
headers=auth_headers,
)
assert response.status_code == 200
file_data = response.get_json()
assert file_data["uuid"] == file_uuid
assert file_data["original_name"] == "test.png"
assert "download_url" in file_data
# Verify only public fields
assert "id" not in file_data
assert "attachable_id" not in file_data
@pytest.mark.integration
def test_delete_file(client, auth_headers, test_card, test_image_file):
"""Test deleting a file"""
# Upload a file first
data = {"file": (test_image_file, "test.png", "image/png")}
upload_response = client.post(
f"/api/cards/{test_card.id}/attachments",
headers=auth_headers,
data=data,
content_type="multipart/form-data",
)
file_uuid = upload_response.get_json()["uuid"]
# Delete file
response = client.delete(
f"/api/files/{file_uuid}",
headers=auth_headers,
)
assert response.status_code == 200
delete_data = response.get_json()
assert delete_data["message"] == "File deleted"
# Verify file is deleted
get_response = client.get(
f"/api/files/{file_uuid}",
headers=auth_headers,
)
assert get_response.status_code == 404
@pytest.mark.integration
def test_download_file(client, auth_headers, test_card, test_image_file):
"""Test downloading a file through proxy"""
# Upload a file first
data = {"file": (test_image_file, "test.png", "image/png")}
upload_response = client.post(
f"/api/cards/{test_card.id}/attachments",
headers=auth_headers,
data=data,
content_type="multipart/form-data",
)
file_uuid = upload_response.get_json()["uuid"]
# Download file
response = client.get(
f"/api/files/{file_uuid}/download",
headers=auth_headers,
)
assert response.status_code == 200
assert response.content_type == "image/png"
assert "Content-Disposition" in response.headers
assert "test.png" in response.headers["Content-Disposition"]
@pytest.mark.integration
def test_view_file(client, auth_headers, test_card, test_image_file):
"""Test viewing a file inline through proxy"""
# Upload a file first
data = {"file": (test_image_file, "test.png", "image/png")}
upload_response = client.post(
f"/api/cards/{test_card.id}/attachments",
headers=auth_headers,
data=data,
content_type="multipart/form-data",
)
file_uuid = upload_response.get_json()["uuid"]
# View file inline
response = client.get(
f"/api/files/{file_uuid}/view",
headers=auth_headers,
)
assert response.status_code == 200
assert response.content_type == "image/png"
assert "inline" in response.headers["Content-Disposition"]
@pytest.mark.integration
def test_download_thumbnail(client, auth_headers, test_card, test_image_file):
"""Test downloading a file thumbnail"""
# Upload an image file
data = {"file": (test_image_file, "test.png", "image/png")}
upload_response = client.post(
f"/api/cards/{test_card.id}/attachments",
headers=auth_headers,
data=data,
content_type="multipart/form-data",
)
file_uuid = upload_response.get_json()["uuid"]
# Download thumbnail
response = client.get(
f"/api/files/{file_uuid}/thumbnail",
headers=auth_headers,
)
assert response.status_code == 200
assert response.content_type == "image/jpeg"
@pytest.mark.integration
def test_thumbnail_not_available_for_pdf(
client, auth_headers, test_card, test_pdf_file
):
"""Test that PDF files don't have thumbnails"""
# Upload a PDF file
data = {"file": (test_pdf_file, "test.pdf", "application/pdf")}
upload_response = client.post(
f"/api/cards/{test_card.id}/attachments",
headers=auth_headers,
data=data,
content_type="multipart/form-data",
)
file_uuid = upload_response.get_json()["uuid"]
# Try to download thumbnail
response = client.get(
f"/api/files/{file_uuid}/thumbnail",
headers=auth_headers,
)
assert response.status_code == 404
@pytest.mark.integration
def test_upload_without_authentication(client, test_card, test_image_file):
"""Test uploading a file without authentication"""
data = {"file": (test_image_file, "test.png", "image/png")}
response = client.post(
f"/api/cards/{test_card.id}/attachments",
data=data,
content_type="multipart/form-data",
)
assert response.status_code == 401
@pytest.mark.integration
def test_get_card_attachments_empty(client, auth_headers, test_card):
"""Test getting attachments when card has none"""
response = client.get(
f"/api/cards/{test_card.id}/attachments",
headers=auth_headers,
)
assert response.status_code == 200
attachments_data = response.get_json()
assert attachments_data["count"] == 0
assert len(attachments_data["files"]) == 0
@pytest.mark.integration
def test_upload_without_file(client, auth_headers, test_card):
"""Test uploading without providing a file"""
data = {}
response = client.post(
f"/api/cards/{test_card.id}/attachments",
headers=auth_headers,
data=data,
content_type="multipart/form-data",
)
assert response.status_code == 400
@pytest.mark.integration
def test_upload_with_invalid_card_id(client, auth_headers, test_image_file):
"""Test uploading to a non-existent card"""
data = {"file": (test_image_file, "test.png", "image/png")}
response = client.post(
"/api/cards/99999/attachments",
headers=auth_headers,
data=data,
content_type="multipart/form-data",
)
assert response.status_code == 404
@pytest.mark.integration
def test_get_file_with_invalid_uuid(client, auth_headers):
"""Test getting a file with an invalid UUID"""
response = client.get(
"/api/files/invalid-uuid-12345",
headers=auth_headers,
)
assert response.status_code == 404
@pytest.mark.integration
def test_delete_file_with_invalid_uuid(client, auth_headers):
"""Test deleting a file with an invalid UUID"""
response = client.delete(
"/api/files/invalid-uuid-12345",
headers=auth_headers,
)
assert response.status_code == 404