372 lines
11 KiB
Python
372 lines
11 KiB
Python
"""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
|