|
4 | 4 | import time
|
5 | 5 | from http.cookies import SimpleCookie
|
6 | 6 | from pathlib import Path
|
7 |
| -from typing import AsyncIterator, Iterator |
| 7 | +from typing import Any, AsyncIterator, Iterator |
8 | 8 |
|
9 | 9 | import anyio
|
10 | 10 | import pytest
|
@@ -682,3 +682,58 @@ def test_file_response_insert_ranges(file_response_client: TestClient) -> None:
|
682 | 682 | "",
|
683 | 683 | f"--{boundary}--",
|
684 | 684 | ]
|
| 685 | + |
| 686 | + |
| 687 | +@pytest.mark.anyio |
| 688 | +async def test_file_response_multi_small_chunk_size(readme_file: Path) -> None: |
| 689 | + class SmallChunkSizeFileResponse(FileResponse): |
| 690 | + chunk_size = 10 |
| 691 | + |
| 692 | + app = SmallChunkSizeFileResponse(path=str(readme_file)) |
| 693 | + |
| 694 | + received_chunks: list[bytes] = [] |
| 695 | + start_message: dict[str, Any] = {} |
| 696 | + |
| 697 | + async def receive() -> Message: |
| 698 | + raise NotImplementedError("Should not be called!") |
| 699 | + |
| 700 | + async def send(message: Message) -> None: |
| 701 | + if message["type"] == "http.response.start": |
| 702 | + start_message.update(message) |
| 703 | + elif message["type"] == "http.response.body": |
| 704 | + received_chunks.append(message["body"]) |
| 705 | + |
| 706 | + await app({"type": "http", "method": "get", "headers": [(b"range", b"bytes=0-15,20-35,35-50")]}, receive, send) |
| 707 | + assert start_message["status"] == 206 |
| 708 | + |
| 709 | + headers = Headers(raw=start_message["headers"]) |
| 710 | + assert headers.get("content-type") == "text/plain; charset=utf-8" |
| 711 | + assert headers.get("accept-ranges") == "bytes" |
| 712 | + assert "content-length" in headers |
| 713 | + assert "last-modified" in headers |
| 714 | + assert "etag" in headers |
| 715 | + assert headers["content-range"].startswith("multipart/byteranges; boundary=") |
| 716 | + boundary = headers["content-range"].split("boundary=")[1] |
| 717 | + |
| 718 | + assert received_chunks == [ |
| 719 | + # Send the part headers. |
| 720 | + f"--{boundary}\nContent-Type: text/plain; charset=utf-8\nContent-Range: bytes 0-15/526\n\n".encode(), |
| 721 | + # Send the first chunk (10 bytes). |
| 722 | + b"# B\xc3\xa1iZ\xc3\xa9\n", |
| 723 | + # Send the second chunk (6 bytes). |
| 724 | + b"\nPower", |
| 725 | + # Send the new line to separate the parts. |
| 726 | + b"\n", |
| 727 | + # Send the part headers. We merge the ranges 20-35 and 35-50 into a single part. |
| 728 | + f"--{boundary}\nContent-Type: text/plain; charset=utf-8\nContent-Range: bytes 20-50/526\n\n".encode(), |
| 729 | + # Send the first chunk (10 bytes). |
| 730 | + b"and exquis", |
| 731 | + # Send the second chunk (10 bytes). |
| 732 | + b"ite WSGI/A", |
| 733 | + # Send the third chunk (10 bytes). |
| 734 | + b"SGI framew", |
| 735 | + # Send the last chunk (1 byte). |
| 736 | + b"o", |
| 737 | + b"\n", |
| 738 | + f"\n--{boundary}--\n".encode(), |
| 739 | + ] |
0 commit comments