test_http.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. from __future__ import annotations
  2. from uuid import UUID, uuid4
  3. def test_dms_blueprint_is_registered(flask_app):
  4. rules = {rule.rule for rule in flask_app.url_map.iter_rules()}
  5. assert "/api/v1/health" in rules
  6. def test_only_q2b_dms_routes_are_registered(flask_app):
  7. dms_rules = {
  8. rule.rule for rule in flask_app.url_map.iter_rules()
  9. if rule.rule.startswith("/api/v1")
  10. }
  11. assert dms_rules == {
  12. "/api/v1/health",
  13. "/api/v1/auth/login",
  14. "/api/v1/auth/logout",
  15. "/api/v1/auth/captcha",
  16. "/api/v1/auth/reauth",
  17. "/api/v1/auth/portal/verify",
  18. "/api/v1/auth/portal/setup-password",
  19. "/api/v1/users/me",
  20. "/api/v1/users/me/ui-theme",
  21. "/api/v1/users",
  22. "/api/v1/users/<id>",
  23. "/api/v1/users/<id>/reset-password",
  24. "/api/v1/users/reorder",
  25. "/api/v1/organizations/tree",
  26. "/api/v1/organizations",
  27. "/api/v1/organizations/<id>",
  28. "/api/v1/organizations/<id>/ip-assignments",
  29. "/api/v1/organizations/reorder",
  30. "/api/v1/config/ui-dictionaries",
  31. "/api/v1/config/login-background",
  32. "/api/v1/categories/tree",
  33. "/api/v1/categories",
  34. "/api/v1/categories/<id>",
  35. "/api/v1/categories/reorder",
  36. "/api/v1/documents",
  37. "/api/v1/documents/<id>",
  38. "/api/v1/documents/batch-import",
  39. "/api/v1/documents/<id>/preview",
  40. "/api/v1/documents/<id>/re-extract",
  41. "/api/v1/documents/<id>/move",
  42. "/api/v1/documents/<id>/download",
  43. "/api/v1/documents/<id>/description",
  44. "/api/v1/documents/<id>/password",
  45. "/api/v1/documents/<id>/relations",
  46. "/api/v1/documents/relation-presets",
  47. "/api/v1/documents/reorder",
  48. "/api/v1/documents/tags",
  49. "/api/v1/main-plans/<id>/sub-plans",
  50. "/api/v1/main-plans/<id>/sub-plans/bind",
  51. "/api/v1/main-plans/<id>/sub-plans/<subPlanId>",
  52. "/api/v1/attachments",
  53. "/api/v1/attachments/<id>",
  54. "/api/v1/attachments/batch-import",
  55. "/api/v1/attachments/<id>/main-plans",
  56. "/api/v1/main-plans/<id>/attachments",
  57. "/api/v1/main-plans/<id>/attachments/bind",
  58. "/api/v1/main-plans/<id>/attachments/reorder",
  59. "/api/v1/main-plans/<id>/attachments/<attachmentId>",
  60. "/api/v1/main-plans/<id>/attachments/<bindingId>/move",
  61. "/api/v1/documents/<id>/permissions",
  62. "/api/v1/documents/<id>/permissions/entries/<entryId>/voucher",
  63. "/api/v1/roles",
  64. "/api/v1/roles/<id>",
  65. "/api/v1/roles/<id>/permissions",
  66. "/api/v1/roles/<id>/users",
  67. "/api/v1/roles/reorder",
  68. "/api/v1/notifications",
  69. "/api/v1/notifications/unread-count",
  70. "/api/v1/notifications/read-all",
  71. "/api/v1/notifications/<id>/handle",
  72. "/api/v1/ip-entries",
  73. "/api/v1/ip-entries/<id>",
  74. "/api/v1/backup/config",
  75. "/api/v1/backup/jobs",
  76. "/api/v1/backup/jobs/current",
  77. "/api/v1/backup/jobs/current/cancel",
  78. "/api/v1/backup/test-target",
  79. "/api/v1/system/metrics/current",
  80. "/api/v1/system/metrics/history",
  81. "/api/v1/system/process",
  82. "/api/v1/system/storage-usage",
  83. "/api/v1/system/converter",
  84. "/api/v1/system/health",
  85. "/api/v1/system/info",
  86. "/api/v1/system/runtime-config",
  87. "/api/v1/system/logs",
  88. "/api/v1/system/logs/download",
  89. "/api/v1/system/maintenance/preview-cache",
  90. "/api/v1/system/users/<user_id>/force-offline",
  91. "/api/v1/audit/logs",
  92. "/api/v1/statistics/documents",
  93. "/api/v1/audit/statistics/trend",
  94. "/api/v1/audit/statistics/actions",
  95. "/api/v1/audit/statistics/users",
  96. "/api/v1/recycle-bin/documents",
  97. "/api/v1/recycle-bin/documents/<id>/restore",
  98. }
  99. def test_legacy_health_response_is_unchanged(client):
  100. response = client.get("/api/health")
  101. assert response.status_code == 200
  102. assert response.get_json() == {"status": "ok"}
  103. assert "requestId" not in response.get_json()
  104. def test_dms_health_uses_contract_response(client):
  105. response = client.get("/api/v1/health")
  106. payload = response.get_json()
  107. assert response.status_code == 200
  108. assert payload == {
  109. "code": "OK",
  110. "message": "success",
  111. "data": {"service": "dms", "status": "UP"},
  112. "requestId": payload["requestId"],
  113. }
  114. UUID(payload["requestId"])
  115. assert response.headers["X-Request-Id"] == payload["requestId"]
  116. def test_request_id_is_passed_through(client):
  117. request_id = str(uuid4())
  118. response = client.get(
  119. "/api/v1/health",
  120. headers={"X-Request-Id": request_id},
  121. )
  122. assert response.get_json()["requestId"] == request_id
  123. assert response.headers["X-Request-Id"] == request_id
  124. def test_request_id_is_generated_when_absent(client):
  125. response = client.get("/api/v1/health")
  126. UUID(response.get_json()["requestId"])
  127. def test_invalid_request_id_uses_uniform_error_response(client):
  128. response = client.get(
  129. "/api/v1/health",
  130. headers={"X-Request-Id": "not-a-uuid"},
  131. )
  132. payload = response.get_json()
  133. assert response.status_code == 400
  134. assert payload["code"] == "INVALID_ARGUMENT"
  135. assert payload["details"] is None
  136. assert set(payload) == {"code", "message", "details", "requestId"}
  137. assert response.headers["X-Request-Id"] == payload["requestId"]
  138. UUID(payload["requestId"])
  139. def test_unknown_dms_route_uses_uniform_not_found(client):
  140. request_id = str(uuid4())
  141. response = client.get(
  142. "/api/v1/not-implemented",
  143. headers={"X-Request-Id": request_id},
  144. )
  145. assert response.status_code == 404
  146. assert response.get_json() == {
  147. "code": "RESOURCE_NOT_FOUND",
  148. "message": "请求的资源不存在",
  149. "details": None,
  150. "requestId": request_id,
  151. }
  152. assert response.headers["X-Request-Id"] == request_id