""" Comprehensive tests for /api/export endpoint ============================================= Tests cover: - Export format options (Excel, CSV) - Empty data handling - Authentication requirements - File generation """ import pytest import io from unittest.mock import patch, MagicMock class TestExportEndpoint: """Tests for /api/export endpoint""" def test_export_unauthorized(self, client): """Test that export requires authentication""" response = client.post("/api/export", json={ "format": "excel", "data": [] }) assert response.status_code == 401 def test_export_empty_data(self, client, auth_headers): """Test export with empty data""" response = client.post("/api/export", json={ "format": "excel", "data": [] }, headers=auth_headers) # Should handle empty data gracefully assert response.status_code in [200, 400, 422] def test_export_excel_format(self, client, auth_headers): """Test Excel export format""" # First create some comparison data compare_response = client.post("/api/comparisons/compare", json={ "hotel_ids": [1, 2], "check_in": "2026-02-01", "check_out": "2026-02-03", "occupancy": "couple" }, headers=auth_headers) # Now try to export response = client.post("/api/export", json={ "format": "excel", "comparison_id": 1 }, headers=auth_headers) # Check response if response.status_code == 200: # Should return binary Excel data content_type = response.headers.get("content-type", "") assert "application" in content_type or "octet-stream" in content_type def test_export_csv_format(self, client, auth_headers): """Test CSV export format""" response = client.post("/api/export", json={ "format": "csv", "data": [ {"hotel": "Test Hotel", "price": 450}, {"hotel": "Another Hotel", "price": 500} ] }, headers=auth_headers) if response.status_code == 200: content_type = response.headers.get("content-type", "") # CSV should be text/csv or similar assert "csv" in content_type.lower() or "text" in content_type.lower() or response.status_code == 200 def test_export_invalid_format(self, client, auth_headers): """Test export with invalid format""" response = client.post("/api/export", json={ "format": "invalid_format", "data": [{"test": "data"}] }, headers=auth_headers) # Should reject invalid format assert response.status_code in [400, 422] def test_export_missing_format(self, client, auth_headers): """Test export with missing format field""" response = client.post("/api/export", json={ "data": [{"test": "data"}] }, headers=auth_headers) # Should require format assert response.status_code in [200, 400, 422] # May have default def test_export_sql_injection_in_data(self, client, auth_headers): """Test that SQL injection in export data is safe""" response = client.post("/api/export", json={ "format": "excel", "data": [ {"hotel": "'; DROP TABLE hotels; --", "price": 450}, {"hotel": "Test", "price": 500} ] }, headers=auth_headers) # Should NOT crash assert response.status_code != 500 def test_export_large_dataset(self, client, auth_headers): """Test export with large dataset""" large_data = [ {"hotel": f"Hotel {i}", "price": 400 + i, "rating": 8.0 + (i % 10) / 10} for i in range(100) ] response = client.post("/api/export", json={ "format": "excel", "data": large_data }, headers=auth_headers) # Should handle large data assert response.status_code in [200, 413, 422] # 413 = payload too large def test_export_special_characters(self, client, auth_headers): """Test export with Hebrew and special characters""" response = client.post("/api/export", json={ "format": "excel", "data": [ {"hotel": "מלון ישראלי", "price": 450, "location": "תל אביב"}, {"hotel": "Hotel with émojis 🏨", "price": 500, "location": "Jerusalem"} ] }, headers=auth_headers) # Should handle Unicode properly assert response.status_code != 500 class TestExportDownload: """Tests for export download endpoint""" def test_download_unauthorized(self, client): """Test that download requires authentication""" response = client.get("/api/export/download/some-file-id") assert response.status_code in [401, 404] def test_download_nonexistent_file(self, client, auth_headers): """Test download of non-existent file""" response = client.get("/api/export/download/nonexistent-file", headers=auth_headers) assert response.status_code in [404, 400] def test_download_path_traversal(self, client, auth_headers): """Test that path traversal attacks are blocked""" malicious_paths = [ "../../../etc/passwd", "..\\..\\..\\windows\\system32\\config\\sam", "/etc/passwd", "....//....//....//etc/passwd", ] for path in malicious_paths: response = client.get(f"/api/export/download/{path}", headers=auth_headers) # Should NOT return a 200 with actual file content assert response.status_code in [400, 404, 422]