test_sync_api_provider.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. # This module is a copy paste of test_api_provider.py
  2. from http import HTTPStatus
  3. from unittest import mock
  4. import pytest
  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 FileFormPost
  9. from clean_python.api_client import SyncApiProvider
  10. MODULE = "clean_python.api_client.sync_api_provider"
  11. @pytest.fixture
  12. def tenant() -> Tenant:
  13. ctx.tenant = Tenant(id=2, name="")
  14. yield ctx.tenant
  15. ctx.tenant = None
  16. @pytest.fixture
  17. def response():
  18. response = mock.Mock()
  19. response.status = int(HTTPStatus.OK)
  20. response.headers = {"Content-Type": "application/json"}
  21. response.data = b'{"foo": 2}'
  22. return response
  23. @pytest.fixture
  24. def api_provider(tenant, response) -> SyncApiProvider:
  25. with mock.patch(MODULE + ".PoolManager"):
  26. api_provider = SyncApiProvider(
  27. url="http://testserver/foo/",
  28. fetch_token=lambda: {"Authorization": f"Bearer tenant-{ctx.tenant.id}"},
  29. )
  30. api_provider._pool.request.return_value = response
  31. yield api_provider
  32. def test_get(api_provider: SyncApiProvider, response):
  33. actual = api_provider.request("GET", "")
  34. assert api_provider._pool.request.call_count == 1
  35. assert api_provider._pool.request.call_args[1] == dict(
  36. method="GET",
  37. url="http://testserver/foo",
  38. headers={"Authorization": "Bearer tenant-2"},
  39. timeout=5.0,
  40. )
  41. assert actual == {"foo": 2}
  42. def test_post_json(api_provider: SyncApiProvider, response):
  43. response.status == int(HTTPStatus.CREATED)
  44. api_provider._pool.request.return_value = response
  45. actual = api_provider.request("POST", "bar", json={"foo": 2})
  46. assert api_provider._pool.request.call_count == 1
  47. assert api_provider._pool.request.call_args[1] == dict(
  48. method="POST",
  49. url="http://testserver/foo/bar",
  50. body=b'{"foo": 2}',
  51. headers={
  52. "Content-Type": "application/json",
  53. "Authorization": "Bearer tenant-2",
  54. },
  55. timeout=5.0,
  56. )
  57. assert actual == {"foo": 2}
  58. @pytest.mark.parametrize(
  59. "path,params,expected_url",
  60. [
  61. ("", None, "http://testserver/foo"),
  62. ("bar", None, "http://testserver/foo/bar"),
  63. ("bar/", None, "http://testserver/foo/bar"),
  64. ("", {"a": 2}, "http://testserver/foo?a=2"),
  65. ("bar", {"a": 2}, "http://testserver/foo/bar?a=2"),
  66. ("bar/", {"a": 2}, "http://testserver/foo/bar?a=2"),
  67. ("", {"a": [1, 2]}, "http://testserver/foo?a=1&a=2"),
  68. ("", {"a": 1, "b": "foo"}, "http://testserver/foo?a=1&b=foo"),
  69. ],
  70. )
  71. def test_url(api_provider: SyncApiProvider, path, params, expected_url):
  72. api_provider.request("GET", path, params=params)
  73. assert api_provider._pool.request.call_args[1]["url"] == expected_url
  74. def test_timeout(api_provider: SyncApiProvider):
  75. api_provider.request("POST", "bar", timeout=2.1)
  76. assert api_provider._pool.request.call_args[1]["timeout"] == 2.1
  77. @pytest.mark.parametrize(
  78. "status", [HTTPStatus.OK, HTTPStatus.NOT_FOUND, HTTPStatus.INTERNAL_SERVER_ERROR]
  79. )
  80. def test_unexpected_content_type(api_provider: SyncApiProvider, response, status):
  81. response.status = int(status)
  82. response.headers["Content-Type"] = "text/plain"
  83. with pytest.raises(ApiException) as e:
  84. api_provider.request("GET", "bar")
  85. assert e.value.status is status
  86. assert str(e.value) == f"{status}: Unexpected content type 'text/plain'"
  87. def test_json_variant_content_type(api_provider: SyncApiProvider, response):
  88. response.headers["Content-Type"] = "application/something+json"
  89. actual = api_provider.request("GET", "bar")
  90. assert actual == {"foo": 2}
  91. def test_no_content(api_provider: SyncApiProvider, response):
  92. response.status = int(HTTPStatus.NO_CONTENT)
  93. response.headers = {}
  94. actual = api_provider.request("DELETE", "bar/2")
  95. assert actual is None
  96. @pytest.mark.parametrize("status", [HTTPStatus.BAD_REQUEST, HTTPStatus.NOT_FOUND])
  97. def test_error_response(api_provider: SyncApiProvider, response, status):
  98. response.status = int(status)
  99. with pytest.raises(ApiException) as e:
  100. api_provider.request("GET", "bar")
  101. assert e.value.status is status
  102. assert str(e.value) == str(int(status)) + ": {'foo': 2}"
  103. @mock.patch(MODULE + ".PoolManager", new=mock.Mock())
  104. def test_no_token(response, tenant):
  105. api_provider = SyncApiProvider(url="http://testserver/foo/", fetch_token=lambda: {})
  106. api_provider._pool.request.return_value = response
  107. api_provider.request("GET", "")
  108. assert api_provider._pool.request.call_args[1]["headers"] == {}
  109. @pytest.mark.parametrize(
  110. "path,trailing_slash,expected",
  111. [
  112. ("bar", False, "bar"),
  113. ("bar", True, "bar/"),
  114. ("bar/", False, "bar"),
  115. ("bar/", True, "bar/"),
  116. ],
  117. )
  118. def test_trailing_slash(api_provider: SyncApiProvider, path, trailing_slash, expected):
  119. api_provider._trailing_slash = trailing_slash
  120. api_provider.request("GET", path)
  121. assert (
  122. api_provider._pool.request.call_args[1]["url"]
  123. == "http://testserver/foo/" + expected
  124. )
  125. def test_post_file(api_provider: SyncApiProvider):
  126. api_provider.request(
  127. "POST",
  128. "bar",
  129. file=FileFormPost(file_name="test.zip", file=b"foo", field_name="x"),
  130. )
  131. assert api_provider._pool.request.call_count == 1
  132. assert api_provider._pool.request.call_args[1] == dict(
  133. method="POST",
  134. url="http://testserver/foo/bar",
  135. fields={"x": ("test.zip", b"foo", "application/octet-stream")},
  136. headers={
  137. "Authorization": "Bearer tenant-2",
  138. },
  139. timeout=5.0,
  140. encode_multipart=True,
  141. )
  142. def test_post_file_with_fields(api_provider: SyncApiProvider):
  143. api_provider.request(
  144. "POST",
  145. "bar",
  146. fields={"a": "b"},
  147. file=FileFormPost(file_name="test.zip", file=b"foo", field_name="x"),
  148. )
  149. assert api_provider._pool.request.call_count == 1
  150. assert api_provider._pool.request.call_args[1] == dict(
  151. method="POST",
  152. url="http://testserver/foo/bar",
  153. fields={"a": "b", "x": ("test.zip", b"foo", "application/octet-stream")},
  154. headers={
  155. "Authorization": "Bearer tenant-2",
  156. },
  157. timeout=5.0,
  158. encode_multipart=True,
  159. )