| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 | from http import HTTPStatusfrom unittest import mockimport pytestfrom aiohttp import ClientSessionfrom clean_python import Conflictfrom clean_python import ctxfrom clean_python import Tenantfrom clean_python.api_client import ApiExceptionfrom clean_python.api_client import ApiProviderMODULE = "clean_python.api_client.api_provider"async def fake_token():    return {"Authorization": f"Bearer tenant-{ctx.tenant.id}"}async def no_token():    return {}@pytest.fixturedef tenant() -> Tenant:    ctx.tenant = Tenant(id=2, name="")    yield ctx.tenant    ctx.tenant = None@pytest.fixturedef response():    # this mocks the aiohttp.ClientResponse:    response = mock.Mock()    response.status = int(HTTPStatus.OK)    response.headers = {"Content-Type": "application/json"}    response.json = mock.AsyncMock(return_value={"foo": 2})    response.read = mock.AsyncMock()    return response@pytest.fixturedef api_provider_no_mock() -> mock.AsyncMock:    return ApiProvider(        url="http://testserver/foo/",        headers_factory=fake_token,    )@pytest.fixturedef request_m() -> mock.AsyncMock:    request = mock.AsyncMock()    with mock.patch.object(ClientSession, "request", new=request):        yield request@pytest.fixturedef api_provider(api_provider_no_mock, tenant, response, request_m) -> ApiProvider:    request_m.return_value = response    return api_provider_no_mockasync def test_get(api_provider: ApiProvider, request_m):    actual = await api_provider.request("GET", "")    assert request_m.call_count == 1    assert request_m.call_args[1] == dict(        method="GET",        url="http://testserver/foo",        headers={"Authorization": "Bearer tenant-2"},        timeout=5.0,        data=None,        json=None,    )    assert actual == {"foo": 2}async def test_post_json(api_provider: ApiProvider, response, request_m):    response.status == int(HTTPStatus.CREATED)    request_m.return_value = response    actual = await api_provider.request("POST", "bar", json={"foo": 2})    assert request_m.call_count == 1    assert request_m.call_args[1] == dict(        method="POST",        url="http://testserver/foo/bar",        data=None,        json={"foo": 2},        headers={            "Authorization": "Bearer tenant-2",        },        timeout=5.0,    )    assert actual == {"foo": 2}@pytest.mark.parametrize(    "path,params,expected_url",    [        ("", None, "http://testserver/foo"),        ("bar", None, "http://testserver/foo/bar"),        ("bar/", None, "http://testserver/foo/bar"),        ("", {"a": 2}, "http://testserver/foo?a=2"),        ("bar", {"a": 2}, "http://testserver/foo/bar?a=2"),        ("bar/", {"a": 2}, "http://testserver/foo/bar?a=2"),        ("", {"a": [1, 2]}, "http://testserver/foo?a=1&a=2"),        ("", {"a": 1, "b": "foo"}, "http://testserver/foo?a=1&b=foo"),    ],)async def test_url(api_provider: ApiProvider, path, params, expected_url, request_m):    await api_provider.request("GET", path, params=params)    assert request_m.call_args[1]["url"] == expected_urlasync def test_timeout(api_provider: ApiProvider, request_m):    await api_provider.request("POST", "bar", timeout=2.1)    assert request_m.call_args[1]["timeout"] == 2.1@pytest.mark.parametrize(    "status", [HTTPStatus.OK, HTTPStatus.NOT_FOUND, HTTPStatus.INTERNAL_SERVER_ERROR])async def test_unexpected_content_type(api_provider: ApiProvider, response, status):    response.status = int(status)    response.headers["Content-Type"] = "text/plain"    with pytest.raises(ApiException) as e:        await api_provider.request("GET", "bar")    assert e.value.status is status    assert str(e.value) == f"{status}: Unexpected content type 'text/plain'"async def test_json_variant_content_type(api_provider: ApiProvider, response):    response.headers["Content-Type"] = "application/something+json"    actual = await api_provider.request("GET", "bar")    assert actual == {"foo": 2}async def test_no_content(api_provider: ApiProvider, response):    response.status = int(HTTPStatus.NO_CONTENT)    response.headers = {}    actual = await api_provider.request("DELETE", "bar/2")    assert actual is None@pytest.mark.parametrize("status", [HTTPStatus.BAD_REQUEST, HTTPStatus.NOT_FOUND])async def test_error_response(api_provider: ApiProvider, response, status):    response.status = int(status)    with pytest.raises(ApiException) as e:        await api_provider.request("GET", "bar")    assert e.value.status is status    assert str(e.value) == str(int(status)) + ": {'foo': 2}"async def test_no_token(api_provider: ApiProvider, request_m):    api_provider._headers_factory = no_token    await api_provider.request("GET", "")    assert request_m.call_args[1]["headers"] == {}@pytest.mark.parametrize(    "path,trailing_slash,expected",    [        ("bar", False, "bar"),        ("bar", True, "bar/"),        ("bar/", False, "bar"),        ("bar/", True, "bar/"),    ],)async def test_trailing_slash(    api_provider: ApiProvider, path, trailing_slash, expected, request_m):    api_provider._trailing_slash = trailing_slash    await api_provider.request("GET", path)    assert request_m.call_args[1]["url"] == "http://testserver/foo/" + expectedasync def test_conflict(api_provider: ApiProvider, response):    response.status = HTTPStatus.CONFLICT    with pytest.raises(Conflict):        await api_provider.request("GET", "bar")async def test_conflict_with_message(api_provider: ApiProvider, response):    response.status = HTTPStatus.CONFLICT    response.json.return_value = {"message": "foo"}    with pytest.raises(Conflict, match="foo"):        await api_provider.request("GET", "bar")async def test_custom_header(api_provider: ApiProvider, request_m):    await api_provider.request("POST", "bar", headers={"foo": "bar"})    assert request_m.call_args[1]["headers"] == {        "foo": "bar",        **(await api_provider._headers_factory()),    }async def test_custom_header_precedes(api_provider: ApiProvider, request_m):    await api_provider.request("POST", "bar", headers={"Authorization": "bar"})    assert request_m.call_args[1]["headers"]["Authorization"] == "bar"async def test_session_closed(api_provider: ApiProvider, request_m):    with mock.patch.object(        ClientSession, "close", new_callable=mock.AsyncMock    ) as close_m:        await api_provider.request("GET", "")    close_m.assert_awaited_once()
 |