test_fastapi_access_logger.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. from unittest import mock
  2. import pytest
  3. from fastapi.routing import APIRoute
  4. from starlette.requests import Request
  5. from starlette.responses import JSONResponse
  6. from starlette.responses import StreamingResponse
  7. from clean_python import InMemoryGateway
  8. from clean_python.fastapi import FastAPIAccessLogger
  9. @pytest.fixture
  10. def fastapi_access_logger():
  11. return FastAPIAccessLogger(hostname="myhost", gateway_override=InMemoryGateway([]))
  12. @pytest.fixture
  13. def req():
  14. # a copy-paste from a local session, with some values removed / shortened
  15. scope = {
  16. "type": "http",
  17. "asgi": {"version": "3.0", "spec_version": "2.3"},
  18. "http_version": "1.1",
  19. "server": ("172.20.0.6", 80),
  20. "client": ("172.20.0.1", 45584),
  21. "scheme": "http",
  22. "root_path": "/v1-beta",
  23. "headers": [
  24. (b"host", b"localhost:8000"),
  25. (b"connection", b"keep-alive"),
  26. (b"accept", b"application/json"),
  27. (b"authorization", b"..."),
  28. (b"user-agent", b"Mozilla/5.0 ..."),
  29. (b"referer", b"http://localhost:8000/v1-beta/docs"),
  30. (b"accept-encoding", b"gzip, deflate, br"),
  31. (b"accept-language", b"en-US,en;q=0.9"),
  32. (b"cookie", b"..."),
  33. ],
  34. "state": {},
  35. "method": "GET",
  36. "path": "/rasters",
  37. "raw_path": b"/v1-beta/rasters",
  38. "query_string": b"limit=50&offset=0&order_by=id",
  39. "path_params": {},
  40. "app_root_path": "",
  41. "route": APIRoute(
  42. endpoint=lambda x: x,
  43. path="/rasters",
  44. name="v1-beta/raster_list",
  45. methods=["GET"],
  46. ),
  47. }
  48. return Request(scope)
  49. @pytest.fixture
  50. def response():
  51. return JSONResponse({"foo": "bar"})
  52. @pytest.fixture
  53. def call_next(response):
  54. async def func(request):
  55. return response
  56. return func
  57. @mock.patch("time.time", return_value=0.0)
  58. async def test_logging(time, fastapi_access_logger, req, response, call_next):
  59. await fastapi_access_logger(req, call_next)
  60. assert len(fastapi_access_logger.gateway.data) == 0
  61. await response.background()
  62. (actual,) = fastapi_access_logger.gateway.data.values()
  63. actual.pop("id")
  64. assert actual == {
  65. "tag_suffix": "access_log",
  66. "remote_address": "172.20.0.1",
  67. "method": "GET",
  68. "path": "/v1-beta/rasters",
  69. "portal": "localhost:8000",
  70. "referer": "http://localhost:8000/v1-beta/docs",
  71. "user_agent": "Mozilla/5.0 ...",
  72. "query_params": "limit=50&offset=0&order_by=id",
  73. "view_name": "v1-beta/raster_list",
  74. "status": 200,
  75. "content_type": "application/json",
  76. "content_length": 13,
  77. "time": "1970-01-01T00:00:00Z",
  78. "request_time": 0.0,
  79. }
  80. @pytest.fixture
  81. def req_minimal():
  82. # https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope
  83. scope = {
  84. "type": "http",
  85. "asgi": {"version": "3.0"},
  86. "http_version": "1.1",
  87. "method": "GET",
  88. "scheme": "http",
  89. "path": "/",
  90. "query_string": "",
  91. "headers": [],
  92. }
  93. return Request(scope)
  94. @pytest.fixture
  95. def streaming_response():
  96. async def numbers(minimum, maximum):
  97. yield ("<html><body><ul>")
  98. for number in range(minimum, maximum + 1):
  99. yield "<li>%d</li>" % number
  100. yield ("</ul></body></html>")
  101. return StreamingResponse(numbers(1, 3), media_type="text/html")
  102. @pytest.fixture
  103. def call_next_streaming(streaming_response):
  104. async def func(request):
  105. return streaming_response
  106. return func
  107. @mock.patch("time.time", return_value=0.0)
  108. async def test_logging_minimal(
  109. time, fastapi_access_logger, req_minimal, streaming_response, call_next_streaming
  110. ):
  111. await fastapi_access_logger(req_minimal, call_next_streaming)
  112. assert len(fastapi_access_logger.gateway.data) == 0
  113. await streaming_response.background()
  114. (actual,) = fastapi_access_logger.gateway.data.values()
  115. actual.pop("id")
  116. assert actual == {
  117. "tag_suffix": "access_log",
  118. "remote_address": None,
  119. "method": "GET",
  120. "path": "/",
  121. "portal": "",
  122. "referer": None,
  123. "user_agent": None,
  124. "query_params": "",
  125. "view_name": None,
  126. "status": 200,
  127. "content_type": "text/html; charset=utf-8",
  128. "content_length": None,
  129. "time": "1970-01-01T00:00:00Z",
  130. "request_time": 0.0,
  131. }