File size: 7,170 Bytes
a5784e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
"""
High-quality tests for api_utils/routers/static.py - Static file serving.

Focus: Test static file endpoints with both success and error paths.
Strategy: Mock Path.exists() to control file existence, test actual routing logic.
"""

from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
from fastapi import HTTPException


class TestReadIndex:
    """Tests for read_index endpoint."""

    @pytest.mark.asyncio
    async def test_read_index_react_exists(self):
        """
        Test scenario: React index.html exists
        Expected: Return FileResponse with React index.html
        """
        from api_utils.routers.static import read_index

        mock_logger = MagicMock()

        with patch.object(Path, "exists", return_value=True):
            response = await read_index(logger=mock_logger)

            assert response is not None

    @pytest.mark.asyncio
    async def test_read_index_not_built(self):
        """
        Test scenario: React build does not exist
        Expected: Return 503 error (Service Unavailable)
        """
        from api_utils.routers.static import read_index

        mock_logger = MagicMock()

        with patch.object(Path, "exists", return_value=False):
            with pytest.raises(HTTPException) as exc_info:
                await read_index(logger=mock_logger)

            assert exc_info.value.status_code == 503
            assert "Frontend not built" in exc_info.value.detail
            mock_logger.error.assert_called_once()


class TestServeReactAssets:
    """Tests for serve_react_assets endpoint."""

    @pytest.mark.asyncio
    async def test_serve_react_assets_js(self):
        """
        Test scenario: JS asset exists
        Expected: Return FileResponse with application/javascript media type
        """
        from api_utils.routers.static import serve_react_assets

        mock_logger = MagicMock()

        with patch.object(Path, "exists", return_value=True):
            response = await serve_react_assets("main.js", logger=mock_logger)

            assert response is not None
            assert response.media_type == "application/javascript"

    @pytest.mark.asyncio
    async def test_serve_react_assets_css(self):
        """
        Test scenario: CSS asset exists
        Expected: Return FileResponse with text/css media type
        """
        from api_utils.routers.static import serve_react_assets

        mock_logger = MagicMock()

        with patch.object(Path, "exists", return_value=True):
            response = await serve_react_assets("style.css", logger=mock_logger)

            assert response is not None
            assert response.media_type == "text/css"

    @pytest.mark.asyncio
    async def test_serve_react_assets_map(self):
        """
        Test scenario: Source map asset exists
        Expected: Return FileResponse with application/json media type
        """
        from api_utils.routers.static import serve_react_assets

        mock_logger = MagicMock()

        with patch.object(Path, "exists", return_value=True):
            response = await serve_react_assets("main.js.map", logger=mock_logger)

            assert response is not None
            assert response.media_type == "application/json"

    @pytest.mark.asyncio
    async def test_serve_react_assets_not_found(self):
        """
        Test scenario: Asset not found
        Expected: Return 404 error
        """
        from api_utils.routers.static import serve_react_assets

        mock_logger = MagicMock()

        with patch.object(Path, "exists", return_value=False):
            with pytest.raises(HTTPException) as exc_info:
                await serve_react_assets("missing.js", logger=mock_logger)

            assert exc_info.value.status_code == 404
            assert "missing.js" in exc_info.value.detail
            mock_logger.debug.assert_called_once()


class TestGetStaticFilesApp:
    """Tests for get_static_files_app factory function."""

    def test_get_static_files_app_exists(self):
        """
        Test scenario: Assets directory exists
        Expected: Return StaticFiles instance
        """
        from api_utils.routers.static import get_static_files_app

        with (
            patch("api_utils.routers.static.Path.exists", return_value=True),
            patch("os.path.isdir", return_value=True),
        ):
            result = get_static_files_app()
            assert result is not None

    def test_get_static_files_app_not_exists(self):
        """
        Test scenario: Assets directory does not exist
        Expected: Return None
        """
        from api_utils.routers.static import get_static_files_app

        with patch.object(Path, "exists", return_value=False):
            result = get_static_files_app()

            assert result is None


class TestDirectoryTraversalProtection:
    """Tests for directory traversal attack prevention."""

    @pytest.mark.asyncio
    async def test_serve_react_assets_traversal_blocked(self):
        """
        Test scenario: Attempt directory traversal attack
        Expected: Return 403 error
        """
        from api_utils.routers.static import serve_react_assets

        mock_logger = MagicMock()

        # First mock: file exists, second mock for resolve().relative_to() failure
        with patch.object(Path, "exists", return_value=True):
            # When the resolved path is outside assets dir, relative_to raises ValueError
            with patch.object(Path, "resolve") as mock_resolve:
                mock_resolved = MagicMock()
                mock_resolved.relative_to.side_effect = ValueError(
                    "Not a relative path"
                )
                mock_resolve.return_value = mock_resolved

                with pytest.raises(HTTPException) as exc_info:
                    await serve_react_assets("../../../etc/passwd", logger=mock_logger)

                assert exc_info.value.status_code == 403
                assert "Access denied" in exc_info.value.detail
                mock_logger.warning.assert_called_once()

    @pytest.mark.asyncio
    async def test_serve_react_assets_additional_mime_types(self):
        """
        Test scenario: Additional MIME type support
        Expected: Correctly identify more file types
        """
        from api_utils.routers.static import serve_react_assets

        mock_logger = MagicMock()

        test_cases = [
            ("image.svg", "image/svg+xml"),
            ("image.png", "image/png"),
            ("font.woff2", "font/woff2"),
        ]

        for filename, expected_media_type in test_cases:
            with patch.object(Path, "exists", return_value=True):
                with patch.object(Path, "resolve") as mock_resolve:
                    mock_resolved = MagicMock()
                    mock_resolved.relative_to.return_value = Path(filename)
                    mock_resolve.return_value = mock_resolved

                    response = await serve_react_assets(filename, logger=mock_logger)

                    assert response is not None
                    assert response.media_type == expected_media_type