eggfetch-python 0.1.4

Python sync and asyncio bindings for the eggfetch HTTP engine (Rust core via PyO3; Python users install from PyPI)
"""Tests for MockTransport."""
from __future__ import annotations

import asyncio
import pytest
from eggfetch.compat.httpx import (
    Client,
    AsyncClient,
    MockTransport,
    Request,
    Response,
    _build_response,
)


class TestMockTransportSync:
    def test_basic_handler(self):
        def handler(request):
            return Response(200, content=b"Hello")

        with Client(transport=MockTransport(handler)) as client:
            resp = client.get("http://testserver/")
            assert resp.status_code == 200
            assert resp.content == b"Hello"

    def test_handler_receives_request(self):
        received = []

        def handler(request):
            received.append(request)
            return Response(200)

        with Client(transport=MockTransport(handler)) as client:
            client.post("http://testserver/data", content=b"body")

        assert len(received) == 1
        assert received[0].method == "POST"
        assert received[0].content == b"body"

    def test_response_request_attached(self):
        def handler(request):
            return Response(200, content=b"ok")

        with Client(transport=MockTransport(handler)) as client:
            resp = client.get("http://testserver/")
            assert resp.request is not None
            assert resp.request.method == "GET"

    def test_handler_exception_propagates(self):
        def handler(request):
            raise ValueError("test error")

        with Client(transport=MockTransport(handler)) as client:
            with pytest.raises(ValueError, match="test error"):
                client.get("http://testserver/")

    def test_mock_with_status_codes(self):
        def handler(request):
            if "404" in str(request.url):
                return Response(404, content=b"Not Found")
            return Response(200, content=b"OK")

        with Client(transport=MockTransport(handler)) as client:
            resp = client.get("http://testserver/404")
            assert resp.status_code == 404

    def test_close_idempotent(self):
        def handler(request):
            return Response(200)

        transport = MockTransport(handler)
        transport.close()
        transport.close()

    def test_context_manager(self):
        def handler(request):
            return Response(200)

        with MockTransport(handler) as transport:
            assert not transport._is_closed

    def test_handler_returns_json(self):
        def handler(request):
            return Response(200, json={"key": "value"})

        with Client(transport=MockTransport(handler)) as client:
            resp = client.get("http://testserver/")
            assert resp.json() == {"key": "value"}

    def test_sync_client_rejects_async_handler(self):
        async def handler(request):
            return Response(200)

        with pytest.raises(RuntimeError, match="async"):
            with Client(transport=MockTransport(handler)) as client:
                client.get("http://testserver/")

    def test_closed_transport_raises(self):
        def handler(request):
            return Response(200)

        transport = MockTransport(handler)
        transport.close()
        with pytest.raises(RuntimeError, match="closed"):
            transport.handle_request(Request("GET", "http://test/"))

    def test_streaming_response_via_handler(self):
        def handler(request):
            def body_iter():
                yield b"chunk1-"
                yield b"chunk2-"
                yield b"chunk3"

            return Response(200, stream=body_iter())

        with Client(transport=MockTransport(handler)) as client:
            with client.stream("GET", "http://testserver/") as resp:
                assert resp.status_code == 200
                content = resp.read()
                assert content == b"chunk1-chunk2-chunk3"


class TestMockTransportAsync:
    @pytest.mark.asyncio
    async def test_async_handler(self):
        async def handler(request):
            return Response(200, content=b"async")

        async with AsyncClient(
            async_transport=MockTransport(handler)
        ) as client:
            resp = await client.get("http://testserver/")
            assert resp.content == b"async"

    @pytest.mark.asyncio
    async def test_sync_handler_in_async_client(self):
        def handler(request):
            return Response(200, content=b"sync-in-async")

        async with AsyncClient(
            async_transport=MockTransport(handler)
        ) as client:
            resp = await client.get("http://testserver/")
            assert resp.content == b"sync-in-async"


class TestBuildResponse:
    def test_build_with_content(self):
        resp = _build_response(201, content=b"created")
        assert resp.status_code == 201
        assert resp.content == b"created"

    def test_build_with_text(self):
        resp = _build_response(200, text="hello")
        assert resp.text == "hello"

    def test_build_with_json(self):
        resp = _build_response(200, json={"a": 1})
        assert resp.json() == {"a": 1}

    def test_build_with_headers(self):
        resp = _build_response(200, headers={"x-test": "yes"})
        assert resp.headers["x-test"] == "yes"