test_api_provider.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. from http import HTTPStatus
  2. from unittest import mock
  3. import pytest
  4. from aiohttp import ClientSession
  5. from clean_python import ctx
  6. from clean_python import Tenant
  7. from clean_python.api_client import ApiException
  8. from clean_python.api_client import ApiProvider
  9. MODULE = "clean_python.api_client.api_provider"
  10. async def fake_token():
  11. return {"Authorization": f"Bearer tenant-{ctx.tenant.id}"}
  12. async def no_token():
  13. return {}
  14. @pytest.fixture
  15. def tenant() -> Tenant:
  16. ctx.tenant = Tenant(id=2, name="")
  17. yield ctx.tenant
  18. ctx.tenant = None
  19. @pytest.fixture
  20. def response():
  21. # this mocks the aiohttp.ClientResponse:
  22. response = mock.Mock()
  23. response.status = int(HTTPStatus.OK)
  24. response.headers = {"Content-Type": "application/json"}
  25. response.json = mock.AsyncMock(return_value={"foo": 2})
  26. response.read = mock.AsyncMock()
  27. return response
  28. @pytest.fixture
  29. def api_provider(tenant, response) -> ApiProvider:
  30. request = mock.AsyncMock()
  31. with mock.patch.object(ClientSession, "request", new=request):
  32. api_provider = ApiProvider(
  33. url="http://testserver/foo/",
  34. fetch_token=fake_token,
  35. )
  36. api_provider._session.request.return_value = response
  37. yield api_provider
  38. async def test_get(api_provider: ApiProvider, response):
  39. actual = await api_provider.request("GET", "")
  40. assert api_provider._session.request.call_count == 1
  41. assert api_provider._session.request.call_args[1] == dict(
  42. method="GET",
  43. url="http://testserver/foo",
  44. headers={"Authorization": "Bearer tenant-2"},
  45. timeout=5.0,
  46. data=None,
  47. json=None,
  48. )
  49. assert actual == {"foo": 2}
  50. async def test_post_json(api_provider: ApiProvider, response):
  51. response.status == int(HTTPStatus.CREATED)
  52. api_provider._session.request.return_value = response
  53. actual = await api_provider.request("POST", "bar", json={"foo": 2})
  54. assert api_provider._session.request.call_count == 1
  55. assert api_provider._session.request.call_args[1] == dict(
  56. method="POST",
  57. url="http://testserver/foo/bar",
  58. data=None,
  59. json={"foo": 2},
  60. headers={
  61. "Authorization": "Bearer tenant-2",
  62. },
  63. timeout=5.0,
  64. )
  65. assert actual == {"foo": 2}
  66. @pytest.mark.parametrize(
  67. "path,params,expected_url",
  68. [
  69. ("", None, "http://testserver/foo"),
  70. ("bar", None, "http://testserver/foo/bar"),
  71. ("bar/", None, "http://testserver/foo/bar"),
  72. ("", {"a": 2}, "http://testserver/foo?a=2"),
  73. ("bar", {"a": 2}, "http://testserver/foo/bar?a=2"),
  74. ("bar/", {"a": 2}, "http://testserver/foo/bar?a=2"),
  75. ("", {"a": [1, 2]}, "http://testserver/foo?a=1&a=2"),
  76. ("", {"a": 1, "b": "foo"}, "http://testserver/foo?a=1&b=foo"),
  77. ],
  78. )
  79. async def test_url(api_provider: ApiProvider, path, params, expected_url):
  80. await api_provider.request("GET", path, params=params)
  81. assert api_provider._session.request.call_args[1]["url"] == expected_url
  82. async def test_timeout(api_provider: ApiProvider):
  83. await api_provider.request("POST", "bar", timeout=2.1)
  84. assert api_provider._session.request.call_args[1]["timeout"] == 2.1
  85. @pytest.mark.parametrize(
  86. "status", [HTTPStatus.OK, HTTPStatus.NOT_FOUND, HTTPStatus.INTERNAL_SERVER_ERROR]
  87. )
  88. async def test_unexpected_content_type(api_provider: ApiProvider, response, status):
  89. response.status = int(status)
  90. response.headers["Content-Type"] = "text/plain"
  91. with pytest.raises(ApiException) as e:
  92. await api_provider.request("GET", "bar")
  93. assert e.value.status is status
  94. assert str(e.value) == f"{status}: Unexpected content type 'text/plain'"
  95. async def test_json_variant_content_type(api_provider: ApiProvider, response):
  96. response.headers["Content-Type"] = "application/something+json"
  97. actual = await api_provider.request("GET", "bar")
  98. assert actual == {"foo": 2}
  99. async def test_no_content(api_provider: ApiProvider, response):
  100. response.status = int(HTTPStatus.NO_CONTENT)
  101. response.headers = {}
  102. actual = await api_provider.request("DELETE", "bar/2")
  103. assert actual is None
  104. @pytest.mark.parametrize("status", [HTTPStatus.BAD_REQUEST, HTTPStatus.NOT_FOUND])
  105. async def test_error_response(api_provider: ApiProvider, response, status):
  106. response.status = int(status)
  107. with pytest.raises(ApiException) as e:
  108. await api_provider.request("GET", "bar")
  109. assert e.value.status is status
  110. assert str(e.value) == str(int(status)) + ": {'foo': 2}"
  111. async def test_no_token(api_provider: ApiProvider):
  112. api_provider._fetch_token = no_token
  113. await api_provider.request("GET", "")
  114. assert api_provider._session.request.call_args[1]["headers"] == {}
  115. @pytest.mark.parametrize(
  116. "path,trailing_slash,expected",
  117. [
  118. ("bar", False, "bar"),
  119. ("bar", True, "bar/"),
  120. ("bar/", False, "bar"),
  121. ("bar/", True, "bar/"),
  122. ],
  123. )
  124. async def test_trailing_slash(
  125. api_provider: ApiProvider, path, trailing_slash, expected
  126. ):
  127. api_provider._trailing_slash = trailing_slash
  128. await api_provider.request("GET", path)
  129. assert (
  130. api_provider._session.request.call_args[1]["url"]
  131. == "http://testserver/foo/" + expected
  132. )