| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- from flask import Blueprint, Flask
- from flask_restx import Resource
- from werkzeug.exceptions import BadRequest, Unauthorized
-
- from core.errors.error import AppInvokeQuotaExceededError
- from libs.external_api import ExternalApi
-
-
- def _create_api_app():
- app = Flask(__name__)
- bp = Blueprint("t", __name__)
- api = ExternalApi(bp)
-
- @api.route("/bad-request")
- class Bad(Resource): # type: ignore
- def get(self): # type: ignore
- raise BadRequest("invalid input")
-
- @api.route("/unauth")
- class Unauth(Resource): # type: ignore
- def get(self): # type: ignore
- raise Unauthorized("auth required")
-
- @api.route("/value-error")
- class ValErr(Resource): # type: ignore
- def get(self): # type: ignore
- raise ValueError("boom")
-
- @api.route("/quota")
- class Quota(Resource): # type: ignore
- def get(self): # type: ignore
- raise AppInvokeQuotaExceededError("quota exceeded")
-
- @api.route("/general")
- class Gen(Resource): # type: ignore
- def get(self): # type: ignore
- raise RuntimeError("oops")
-
- # Note: We avoid altering default_mediatype to keep normal error paths
-
- # Special 400 message rewrite
- @api.route("/json-empty")
- class JsonEmpty(Resource): # type: ignore
- def get(self): # type: ignore
- e = BadRequest()
- # Force the specific message the handler rewrites
- e.description = "Failed to decode JSON object: Expecting value: line 1 column 1 (char 0)"
- raise e
-
- # 400 mapping payload path
- @api.route("/param-errors")
- class ParamErrors(Resource): # type: ignore
- def get(self): # type: ignore
- e = BadRequest()
- # Coerce a mapping description to trigger param error shaping
- e.description = {"field": "is required"} # type: ignore[assignment]
- raise e
-
- app.register_blueprint(bp, url_prefix="/api")
- return app
-
-
- def test_external_api_error_handlers_basic_paths():
- app = _create_api_app()
- client = app.test_client()
-
- # 400
- res = client.get("/api/bad-request")
- assert res.status_code == 400
- data = res.get_json()
- assert data["code"] == "bad_request"
- assert data["status"] == 400
-
- # 401
- res = client.get("/api/unauth")
- assert res.status_code == 401
- assert "WWW-Authenticate" in res.headers
-
- # 400 ValueError
- res = client.get("/api/value-error")
- assert res.status_code == 400
- assert res.get_json()["code"] == "invalid_param"
-
- # 500 general
- res = client.get("/api/general")
- assert res.status_code == 500
- assert res.get_json()["status"] == 500
-
-
- def test_external_api_json_message_and_bad_request_rewrite():
- app = _create_api_app()
- client = app.test_client()
-
- # JSON empty special rewrite
- res = client.get("/api/json-empty")
- assert res.status_code == 400
- assert res.get_json()["message"] == "Invalid JSON payload received or JSON payload is empty."
-
-
- def test_external_api_param_mapping_and_quota_and_exc_info_none():
- # Force exc_info() to return (None,None,None) only during request
- import libs.external_api as ext
-
- orig_exc_info = ext.sys.exc_info
- try:
- ext.sys.exc_info = lambda: (None, None, None) # type: ignore[assignment]
-
- app = _create_api_app()
- client = app.test_client()
-
- # Param errors mapping payload path
- res = client.get("/api/param-errors")
- assert res.status_code == 400
- data = res.get_json()
- assert data["code"] == "invalid_param"
- assert data["params"] == "field"
-
- # Quota path — depending on Flask-RESTX internals it may be handled
- res = client.get("/api/quota")
- assert res.status_code in (400, 429)
- finally:
- ext.sys.exc_info = orig_exc_info # type: ignore[assignment]
|