"""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