1- """Tests for staticware."""
1+ """Tests for staticware.
2+
3+ Async test detection:
4+ pytest-asyncio is configured with asyncio_mode = "auto" in pyproject.toml.
5+ This means any test written as ``async def`` automatically runs on an event
6+ loop. Regular ``def`` tests run normally without one.
7+
8+ Use ``async def`` for tests that call ASGI apps (they are async callables).
9+ Use plain ``def`` for tests that only exercise sync APIs like StaticFiles()
10+ construction, url(), and file_map lookups.
11+
12+ Do NOT write ``async def`` for a test that has no await in its body. It will
13+ still pass, but it runs on an event loop for no reason and misleads readers
14+ into thinking the test exercises async behavior.
15+ """
216
317import hashlib
418from pathlib import Path
@@ -154,7 +168,6 @@ def test_multi_dot_filename(tmp_path: Path) -> None:
154168# ── StaticFiles: ASGI serving ───────────────────────────────────────────
155169
156170
157- @pytest .mark .asyncio
158171async def test_serve_original_filename (static : StaticFiles , static_dir : Path ) -> None :
159172 resp = ResponseCollector ()
160173 await static (make_scope ("/static/styles.css" ), receive , resp )
@@ -163,7 +176,6 @@ async def test_serve_original_filename(static: StaticFiles, static_dir: Path) ->
163176 assert b"cache-control" not in resp .headers
164177
165178
166- @pytest .mark .asyncio
167179async def test_serve_hashed_filename_with_immutable_cache (static : StaticFiles ) -> None :
168180 hashed_name = static .file_map ["styles.css" ]
169181 resp = ResponseCollector ()
@@ -173,44 +185,38 @@ async def test_serve_hashed_filename_with_immutable_cache(static: StaticFiles) -
173185 assert resp .headers [b"cache-control" ] == b"public, max-age=31536000, immutable"
174186
175187
176- @pytest .mark .asyncio
177188async def test_serve_404_for_missing_file (static : StaticFiles ) -> None :
178189 resp = ResponseCollector ()
179190 await static (make_scope ("/static/nope.css" ), receive , resp )
180191 assert resp .status == 404
181192
182193
183- @pytest .mark .asyncio
184194async def test_serve_404_outside_prefix (static : StaticFiles ) -> None :
185195 resp = ResponseCollector ()
186196 await static (make_scope ("/other/styles.css" ), receive , resp )
187197 assert resp .status == 404
188198
189199
190- @pytest .mark .asyncio
191200async def test_path_traversal_rejected (static : StaticFiles ) -> None :
192201 resp = ResponseCollector ()
193202 await static (make_scope ("/static/../../etc/passwd" ), receive , resp )
194203 assert resp .status == 404
195204
196205
197- @pytest .mark .asyncio
198206async def test_non_http_scope_ignored (static : StaticFiles ) -> None :
199207 """WebSocket and lifespan scopes should be silently ignored."""
200208 resp = ResponseCollector ()
201209 await static ({"type" : "websocket" , "path" : "/static/styles.css" }, receive , resp )
202210 assert resp .status == 0 # send was never called
203211
204212
205- @pytest .mark .asyncio
206213async def test_serve_subdirectory_file (static : StaticFiles ) -> None :
207214 resp = ResponseCollector ()
208215 await static (make_scope ("/static/images/logo.png" ), receive , resp )
209216 assert resp .status == 200
210217 assert resp .body == b"\x89 PNG fake image data"
211218
212219
213- @pytest .mark .asyncio
214220async def test_content_type_header (static : StaticFiles ) -> None :
215221 resp = ResponseCollector ()
216222 await static (make_scope ("/static/styles.css" ), receive , resp )
@@ -255,7 +261,6 @@ async def app(scope: dict, receive: Any, send: Any) -> None:
255261 return app
256262
257263
258- @pytest .mark .asyncio
259264async def test_rewrite_html_response (static : StaticFiles ) -> None :
260265 html = '<link href="/static/styles.css">'
261266 app = StaticRewriteMiddleware (make_html_app (html ), static = static )
@@ -267,7 +272,6 @@ async def test_rewrite_html_response(static: StaticFiles) -> None:
267272 assert "/static/styles.css" not in resp .text
268273
269274
270- @pytest .mark .asyncio
271275async def test_rewrite_updates_content_length (static : StaticFiles ) -> None :
272276 html = '<link href="/static/styles.css">'
273277 app = StaticRewriteMiddleware (make_html_app (html ), static = static )
@@ -278,7 +282,6 @@ async def test_rewrite_updates_content_length(static: StaticFiles) -> None:
278282 assert declared_length == len (resp .body )
279283
280284
281- @pytest .mark .asyncio
282285async def test_rewrite_leaves_unknown_paths_alone (static : StaticFiles ) -> None :
283286 html = '<script src="/static/app.js"></script>'
284287 app = StaticRewriteMiddleware (make_html_app (html ), static = static )
@@ -287,7 +290,6 @@ async def test_rewrite_leaves_unknown_paths_alone(static: StaticFiles) -> None:
287290 assert "/static/app.js" in resp .text
288291
289292
290- @pytest .mark .asyncio
291293async def test_non_html_passes_through (static : StaticFiles ) -> None :
292294 data = b'{"path": "/static/styles.css"}'
293295 app = StaticRewriteMiddleware (make_json_app (data ), static = static )
@@ -296,7 +298,6 @@ async def test_non_html_passes_through(static: StaticFiles) -> None:
296298 assert resp .body == data
297299
298300
299- @pytest .mark .asyncio
300301async def test_rewrite_multiple_paths (static : StaticFiles ) -> None :
301302 html = '<link href="/static/styles.css"><img src="/static/images/logo.png">'
302303 app = StaticRewriteMiddleware (make_html_app (html ), static = static )
@@ -307,7 +308,6 @@ async def test_rewrite_multiple_paths(static: StaticFiles) -> None:
307308 assert f"/static/{ static .file_map ['images/logo.png' ]} " in resp .text
308309
309310
310- @pytest .mark .asyncio
311311async def test_rewrite_non_http_passes_through (static : StaticFiles ) -> None :
312312 """Non-HTTP scopes are forwarded to the wrapped app without rewriting."""
313313 calls : list [str ] = []
@@ -320,7 +320,6 @@ async def ws_app(scope: dict, receive: Any, send: Any) -> None:
320320 assert calls == ["websocket" ]
321321
322322
323- @pytest .mark .asyncio
324323async def test_rewrite_raises_runtime_error_on_body_before_start (
325324 static : StaticFiles ,
326325) -> None :
@@ -344,7 +343,6 @@ async def broken_app(scope: dict, receive: Any, send: Any) -> None:
344343 await app (make_scope ("/" ), receive , ResponseCollector ())
345344
346345
347- @pytest .mark .asyncio
348346async def test_rewrite_streaming_html_response (static : StaticFiles ) -> None :
349347 """Middleware rewrites static paths even when the body arrives in multiple chunks."""
350348 chunk1 = b'<link href="/static/'
@@ -372,7 +370,6 @@ async def streaming_app(scope: dict, receive: Any, send: Any) -> None:
372370 assert "/static/styles.css" not in resp .text
373371
374372
375- @pytest .mark .asyncio
376373async def test_serve_prefix_only_returns_404 (static : StaticFiles ) -> None :
377374 """Requesting /static or /static/ with no filename returns 404."""
378375 # /static with no trailing slash
@@ -386,7 +383,6 @@ async def test_serve_prefix_only_returns_404(static: StaticFiles) -> None:
386383 assert resp_slash .status == 404
387384
388385
389- @pytest .mark .asyncio
390386async def test_rewrite_non_utf8_html_passes_through (static : StaticFiles ) -> None :
391387 """HTML response with non-UTF-8 bytes passes through unchanged."""
392388 raw_body = b"<html>\x80 \x81 \x82 not valid utf-8</html>"
0 commit comments