test_fastapi_access_logger.py 4.3 KB

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