File size: 6,001 Bytes
d77abf8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
"""
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<script>alert(1)</script>", "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]