def test_non_blueprint_rest_error_routing(self, app, mocker): blueprint = Blueprint('test', __name__) api = restx.Api(blueprint) api.add_resource(HelloWorld, '/hi', endpoint="hello") api.add_resource(GoodbyeWorld(404), '/bye', endpoint="bye") app.register_blueprint(blueprint, url_prefix='/blueprint') api2 = restx.Api(app) api2.add_resource(HelloWorld(api), '/hi', endpoint="hello") api2.add_resource(GoodbyeWorld(404), '/bye', endpoint="bye") with app.test_request_context('/hi', method='POST'): assert api._should_use_fr_error_handler() is False assert api2._should_use_fr_error_handler() is True assert api._has_fr_route() is False assert api2._has_fr_route() is True with app.test_request_context('/blueprint/hi', method='POST'): assert api._should_use_fr_error_handler() is True assert api2._should_use_fr_error_handler() is False assert api._has_fr_route() is True assert api2._has_fr_route() is False api._should_use_fr_error_handler = mocker.Mock(return_value=False) api2._should_use_fr_error_handler = mocker.Mock(return_value=False) with app.test_request_context('/bye'): assert api._has_fr_route() is False assert api2._has_fr_route() is True with app.test_request_context('/blueprint/bye'): assert api._has_fr_route() is True assert api2._has_fr_route() is False
def test_registration_prefix_overrides_blueprint_prefix(self, app): blueprint = Blueprint('test', __name__, url_prefix='/bp') api = restx.Api(blueprint) api.add_resource(HelloWorld, '/hi', endpoint='hello') app.register_blueprint(blueprint, url_prefix='/reg') with app.test_request_context('/reg/hi'): assert request.endpoint == 'test.hello'
def test_accept_default_override_accept(self, app, client): api = restx.Api(app) api.add_resource(Foo, '/test/') res = client.get('/test/', headers=[('Accept', 'text/plain')]) assert res.status_code == 200 assert res.content_type == 'application/json'
def test_add_resource(self, mocker, mock_app): api = restx.Api(mock_app) api.output = mocker.Mock() api.add_resource(views.MethodView, "/foo") mock_app.add_url_rule.assert_called_with("/foo", view_func=api.output())
def test_accept_default_override_accept(self, app, client): api = restx.Api(app) api.add_resource(Foo, "/test/") res = client.get("/test/", headers=[("Accept", "text/plain")]) assert res.status_code == 200 assert res.content_type == "application/json"
def app(controller: layabase.CRUDController): application = flask.Flask(__name__) application.testing = True api = flask_restx.Api(application) namespace = api.namespace("Test", path="/") controller.flask_restx.init_models(namespace) @namespace.route("/test") class TestResource(flask_restx.Resource): @namespace.expect(controller.flask_restx.query_get_parser) @namespace.marshal_with(controller.flask_restx.get_response_model) def get(self): return [] @namespace.expect(controller.flask_restx.json_post_model) def post(self): return [] @namespace.expect(controller.flask_restx.json_put_model) def put(self): return [] @namespace.expect(controller.flask_restx.query_delete_parser) def delete(self): return [] return application
def test_namespace_loggers_log_to_flask_app_logger(self, app, client, caplog): # capture Flask app logs caplog.set_level(logging.INFO, logger=app.logger.name) api = restx.Api(app) ns1 = api.namespace("ns1", path="/ns1") ns2 = api.namespace("ns2", path="/ns2") @ns1.route("/") class Ns1(restx.Resource): def get(self): ns1.logger.info("hello from ns1") pass @ns2.route("/") class Ns2(restx.Resource): def get(self): ns2.logger.info("hello from ns2") pass # debug log not shown client.get("/ns1/") matching = [r for r in caplog.records if r.message == "hello from ns1"] assert len(matching) == 1 # info log shown client.get("/ns2/") matching = [r for r in caplog.records if r.message == "hello from ns2"] assert len(matching) == 1
def test_validation_on_list(self, app, client): '''It should perform validation on lists''' api = restx.Api(app, validate=True) person = api.model( 'Person', { 'name': restx.fields.String(required=True), 'age': restx.fields.Integer(required=True), }) family = api.model( 'Family', { 'name': restx.fields.String(required=True), 'members': restx.fields.List(restx.fields.Nested(person)) }) @api.route('/validation/') class List(restx.Resource): @api.expect(family) def post(self): return {} self.assert_errors(client, '/validation/', { 'name': 'Doe', 'members': [{ 'name': 'Jonn' }, { 'age': 42 }] }, 'members.0.age', 'members.1.name')
def test_url_variables_enabled(self, app): api = restx.Api(app) parser = api.parser() parser.add_argument('int', type=int) parser.add_argument('default', type=int, default=5) parser.add_argument('str', type=str) @api.route('/test/') class Test(restx.Resource): @api.expect(parser) def get(self): pass data = api.as_postman(urlvars=True) validate(data, schema) assert len(data['requests']) == 1 request = data['requests'][0] qs = parse_qs(urlparse(request['url']).query, keep_blank_values=True) assert 'int' in qs assert qs['int'][0] == '0' assert 'default' in qs assert qs['default'][0] == '5' assert 'str' in qs assert qs['str'][0] == ''
def test_api_payload_strict_verification(self, app, client): api = restx.Api(app, validate=True) ns = restx.Namespace("apples") api.add_namespace(ns) fields = ns.model( "Person", { "name": restx.fields.String(required=True), "age": restx.fields.Integer, "birthdate": restx.fields.DateTime, }, strict=True, ) @ns.route("/validation/") class Payload(restx.Resource): payload = None @ns.expect(fields) def post(self): Payload.payload = ns.payload return {} data = { "name": "John Doe", "agge": 15, # typo } resp = client.post_json("/apples/validation/", data, status=400) assert re.match( "Additional properties are not allowed \(u*'agge' was unexpected\)", resp["errors"][""])
def test_api_payload(self, app, client): api = restx.Api(app, validate=True) ns = restx.Namespace("apples") api.add_namespace(ns) fields = ns.model( "Person", { "name": restx.fields.String(required=True), "age": restx.fields.Integer, "birthdate": restx.fields.DateTime, }, ) @ns.route("/validation/") class Payload(restx.Resource): payload = None @ns.expect(fields) def post(self): Payload.payload = ns.payload return {} data = { "name": "John Doe", "age": 15, } client.post_json("/apples/validation/", data) assert Payload.payload == data
def test_namespace_errorhandler_with_propagate_true(self, app, client): """Exceptions with errorhandler on a namespace should not be returned to client, even if PROPAGATE_EXCEPTIONS is set.""" app.config["PROPAGATE_EXCEPTIONS"] = True api = restx.Api(app) namespace = restx.Namespace('test_namespace') api.add_namespace(namespace) @namespace.route("/test/", endpoint="test") class TestResource(restx.Resource): def get(self): raise RuntimeError("error") @namespace.errorhandler(RuntimeError) def handle_custom_exception(error): return {"message": str(error), "test": "value"}, 400 response = client.get("/test_namespace/test/") assert response.status_code == 400 assert response.content_type == "application/json" data = json.loads(response.data.decode("utf8")) assert data == { "message": "error", "test": "value", }
def test_handle_smart_errors(self, app): api = restx.Api(app) view = restx.Resource api.add_resource(view, "/foo", endpoint="bor") api.add_resource(view, "/fee", endpoint="bir") api.add_resource(view, "/fii", endpoint="ber") with app.test_request_context("/faaaaa"): response = api.handle_error(NotFound()) assert response.status_code == 404 assert json.loads(response.data.decode()) == { "message": NotFound.description, } with app.test_request_context("/fOo"): response = api.handle_error(NotFound()) assert response.status_code == 404 assert "did you mean /foo ?" in response.data.decode() app.config["RESTX_ERROR_404_HELP"] = False response = api.handle_error(NotFound()) assert response.status_code == 404 assert json.loads(response.data.decode()) == { "message": NotFound.description }
def test_errorhandler_lazy(self, app, client): api = restx.Api() class CustomException(RuntimeError): pass @api.route("/test/", endpoint="test") class TestResource(restx.Resource): def get(self): raise CustomException("error") @api.errorhandler(CustomException) def handle_custom_exception(error): return {"message": str(error), "test": "value"}, 400 api.init_app(app) response = client.get("/test/") assert response.status_code == 400 assert response.content_type == "application/json" data = json.loads(response.data.decode("utf8")) assert data == { "message": "error", "test": "value", }
def test_url_with_api_and_blueprint_prefix(self, app): blueprint = Blueprint('test', __name__, url_prefix='/bp') api = restx.Api(blueprint, prefix='/api') api.add_resource(HelloWorld, '/hi', endpoint='hello') app.register_blueprint(blueprint) with app.test_request_context('/bp/api/hi'): assert request.endpoint == 'test.hello'
def test_root_endpoint(self, app): api = restx.Api(app, version='1.0') with app.test_request_context(): url = url_for('root') assert url == '/' assert api.base_url == 'http://localhost/'
def test_validation_with_inheritance(self, app, client): '''It should perform validation with inheritance (allOf/$ref)''' api = restx.Api(app, validate=True) fields = api.model('Parent', { 'name': restx.fields.String(required=True), }) child_fields = api.inherit('Child', fields, { 'age': restx.fields.Integer, }) @api.route('/validation/') class Inheritance(restx.Resource): @api.expect(child_fields) def post(self): return {} client.post_json('/validation/', { 'name': 'John Doe', 'age': 15, }) self.assert_errors(client, '/validation/', { 'age': '15', }, 'name', 'age')
def test_method_security_headers(self, app): api = restx.Api(app, authorizations={ 'apikey': { 'type': 'apiKey', 'in': 'header', 'name': 'X-API' } }) @api.route('/secure/') class Secure(restx.Resource): @api.doc('secure', security='apikey') def get(self): pass @api.route('/unsecure/') class Unsecure(restx.Resource): @api.doc('unsecure') def get(self): pass data = api.as_postman() validate(data, schema) requests = dict((r['name'], r['headers']) for r in data['requests']) assert requests['unsecure'] == '' assert requests['secure'] == 'X-API:'
def test_api_payload(self, app, client): api = restx.Api(app, validate=True) ns = restx.Namespace('apples') api.add_namespace(ns) fields = ns.model('Person', { 'name': restx.fields.String(required=True), 'age': restx.fields.Integer, 'birthdate': restx.fields.DateTime, }) @ns.route('/validation/') class Payload(restx.Resource): payload = None @ns.expect(fields) def post(self): Payload.payload = ns.payload return {} data = { 'name': 'John Doe', 'age': 15, } client.post_json('/apples/validation/', data) assert Payload.payload == data
def test_export_with_namespace(self, app): api = restx.Api(app) ns = api.namespace('test', 'A test namespace') @ns.route('/test') class Test(restx.Resource): @api.doc('test_post') def post(self): '''A test post''' pass data = api.as_postman() validate(data, schema) assert len(data['requests']) == 1 request = data['requests'][0] assert request['name'] == 'test_post' assert request['description'] == 'A test post' assert len(data['folders']) == 1 folder = data['folders'][0] assert folder['name'] == 'test' assert folder['description'] == 'A test namespace' assert request['folder'] == folder['id']
def test_override_app_level(self, app, client, caplog): caplog.set_level(logging.INFO, logger=app.logger.name) api = restx.Api(app) ns1 = api.namespace("ns1", path="/ns1") ns1.logger.setLevel(logging.DEBUG) ns2 = api.namespace("ns2", path="/ns2") @ns1.route("/") class Ns1(restx.Resource): def get(self): ns1.logger.debug("hello from ns1") pass @ns2.route("/") class Ns2(restx.Resource): def get(self): ns2.logger.debug("hello from ns2") pass # debug log shown from ns1 client.get("/ns1/") matching = [r for r in caplog.records if r.message == "hello from ns1"] assert len(matching) == 1 # debug not shown from ns2 client.get("/ns2/") matching = [r for r in caplog.records if r.message == "hello from ns2"] assert len(matching) == 0
def test_root_endpoint(self, app): api = restx.Api(app, version="1.0") with app.test_request_context(): url = url_for("root") assert url == "/" assert api.base_url == "http://localhost/"
def test_accept_default_application_json(self, app, client): api = restx.Api(app) api.add_resource(Foo, "/test/") res = client.get("/test/", headers={"Accept": None}) assert res.status_code == 200 assert res.content_type == "application/json"
def test_defaults_to_app_level(self, app, client, caplog): caplog.set_level(logging.INFO, logger=app.logger.name) api = restx.Api(app) ns1 = api.namespace('ns1', path='/ns1') ns2 = api.namespace('ns2', path='/ns2') @ns1.route('/') class Ns1(restx.Resource): def get(self): ns1.logger.debug("hello from ns1") pass @ns2.route('/') class Ns2(restx.Resource): def get(self): ns2.logger.info("hello from ns2") pass # debug log not shown client.get("/ns1/") matching = [r for r in caplog.records if r.message == "hello from ns1"] assert len(matching) == 0 # info log shown client.get("/ns2/") matching = [r for r in caplog.records if r.message == "hello from ns2"] assert len(matching) == 1
def test_accept_no_default_no_match_not_acceptable(self, app, client): api = restx.Api(app, default_mediatype=None) api.add_resource(Foo, "/test/") res = client.get("/test/", headers=[("Accept", "text/plain")]) assert res.status_code == 406 assert res.content_type == "application/json"
def test_blunder_in_errorhandler_is_not_suppressed_in_logs( self, app, client, caplog): api = restx.Api(app) class CustomException(RuntimeError): pass class ProgrammingBlunder(Exception): pass @api.route('/test/', endpoint="test") class TestResource(restx.Resource): def get(self): raise CustomException('error') @api.errorhandler(CustomException) def handle_custom_exception(error): raise ProgrammingBlunder( "This exception needs to be logged, not suppressed, then cause 500" ) with caplog.at_level(logging.ERROR): response = client.get('/test/') exc_type, value, traceback = caplog.records[0].exc_info assert exc_type is ProgrammingBlunder assert response.status_code == 500
def test_accept_default_application_json(self, app, client): api = restx.Api(app) api.add_resource(Foo, '/test/') res = client.get('/test/', headers={'Accept': None}) assert res.status_code == 200 assert res.content_type == 'application/json'
def test_errorhandler_lazy(self, app, client): api = restx.Api() class CustomException(RuntimeError): pass @api.route('/test/', endpoint='test') class TestResource(restx.Resource): def get(self): raise CustomException('error') @api.errorhandler(CustomException) def handle_custom_exception(error): return {'message': str(error), 'test': 'value'}, 400 api.init_app(app) response = client.get('/test/') assert response.status_code == 400 assert response.content_type == 'application/json' data = json.loads(response.data.decode('utf8')) assert data == { 'message': 'error', 'test': 'value', }
def test_accept_no_default_no_match_not_acceptable(self, app, client): api = restx.Api(app, default_mediatype=None) api.add_resource(Foo, '/test/') res = client.get('/test/', headers=[('Accept', 'text/plain')]) assert res.status_code == 406 assert res.content_type == 'application/json'
def test_handle_smart_errors(self, app): api = restx.Api(app) view = restx.Resource api.add_resource(view, '/foo', endpoint='bor') api.add_resource(view, '/fee', endpoint='bir') api.add_resource(view, '/fii', endpoint='ber') with app.test_request_context("/faaaaa"): response = api.handle_error(NotFound()) assert response.status_code == 404 assert json.loads(response.data.decode()) == { 'message': NotFound.description, } with app.test_request_context("/fOo"): response = api.handle_error(NotFound()) assert response.status_code == 404 assert 'did you mean /foo ?' in response.data.decode() app.config['ERROR_404_HELP'] = False response = api.handle_error(NotFound()) assert response.status_code == 404 assert json.loads(response.data.decode()) == { 'message': NotFound.description }