You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_external_api.py 3.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. from flask import Blueprint, Flask
  2. from flask_restx import Resource
  3. from werkzeug.exceptions import BadRequest, Unauthorized
  4. from core.errors.error import AppInvokeQuotaExceededError
  5. from libs.external_api import ExternalApi
  6. def _create_api_app():
  7. app = Flask(__name__)
  8. bp = Blueprint("t", __name__)
  9. api = ExternalApi(bp)
  10. @api.route("/bad-request")
  11. class Bad(Resource): # type: ignore
  12. def get(self): # type: ignore
  13. raise BadRequest("invalid input")
  14. @api.route("/unauth")
  15. class Unauth(Resource): # type: ignore
  16. def get(self): # type: ignore
  17. raise Unauthorized("auth required")
  18. @api.route("/value-error")
  19. class ValErr(Resource): # type: ignore
  20. def get(self): # type: ignore
  21. raise ValueError("boom")
  22. @api.route("/quota")
  23. class Quota(Resource): # type: ignore
  24. def get(self): # type: ignore
  25. raise AppInvokeQuotaExceededError("quota exceeded")
  26. @api.route("/general")
  27. class Gen(Resource): # type: ignore
  28. def get(self): # type: ignore
  29. raise RuntimeError("oops")
  30. # Note: We avoid altering default_mediatype to keep normal error paths
  31. # Special 400 message rewrite
  32. @api.route("/json-empty")
  33. class JsonEmpty(Resource): # type: ignore
  34. def get(self): # type: ignore
  35. e = BadRequest()
  36. # Force the specific message the handler rewrites
  37. e.description = "Failed to decode JSON object: Expecting value: line 1 column 1 (char 0)"
  38. raise e
  39. # 400 mapping payload path
  40. @api.route("/param-errors")
  41. class ParamErrors(Resource): # type: ignore
  42. def get(self): # type: ignore
  43. e = BadRequest()
  44. # Coerce a mapping description to trigger param error shaping
  45. e.description = {"field": "is required"} # type: ignore[assignment]
  46. raise e
  47. app.register_blueprint(bp, url_prefix="/api")
  48. return app
  49. def test_external_api_error_handlers_basic_paths():
  50. app = _create_api_app()
  51. client = app.test_client()
  52. # 400
  53. res = client.get("/api/bad-request")
  54. assert res.status_code == 400
  55. data = res.get_json()
  56. assert data["code"] == "bad_request"
  57. assert data["status"] == 400
  58. # 401
  59. res = client.get("/api/unauth")
  60. assert res.status_code == 401
  61. assert "WWW-Authenticate" in res.headers
  62. # 400 ValueError
  63. res = client.get("/api/value-error")
  64. assert res.status_code == 400
  65. assert res.get_json()["code"] == "invalid_param"
  66. # 500 general
  67. res = client.get("/api/general")
  68. assert res.status_code == 500
  69. assert res.get_json()["status"] == 500
  70. def test_external_api_json_message_and_bad_request_rewrite():
  71. app = _create_api_app()
  72. client = app.test_client()
  73. # JSON empty special rewrite
  74. res = client.get("/api/json-empty")
  75. assert res.status_code == 400
  76. assert res.get_json()["message"] == "Invalid JSON payload received or JSON payload is empty."
  77. def test_external_api_param_mapping_and_quota_and_exc_info_none():
  78. # Force exc_info() to return (None,None,None) only during request
  79. import libs.external_api as ext
  80. orig_exc_info = ext.sys.exc_info
  81. try:
  82. ext.sys.exc_info = lambda: (None, None, None) # type: ignore[assignment]
  83. app = _create_api_app()
  84. client = app.test_client()
  85. # Param errors mapping payload path
  86. res = client.get("/api/param-errors")
  87. assert res.status_code == 400
  88. data = res.get_json()
  89. assert data["code"] == "invalid_param"
  90. assert data["params"] == "field"
  91. # Quota path — depending on Flask-RESTX internals it may be handled
  92. res = client.get("/api/quota")
  93. assert res.status_code in (400, 429)
  94. finally:
  95. ext.sys.exc_info = orig_exc_info # type: ignore[assignment]