async def test_aiohttp_output_stream__ok__nominal_case(self, aiohttp_client, loop): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) class AsyncGenerator: def __init__(self): self._iterator = iter([{"name": "Hello, bob"}, {"name": "Hello, franck"}]) async def __aiter__(self): return self async def __anext__(self): return next(self._iterator) class OuputStreamItemSchema(marshmallow.Schema): name = marshmallow.fields.String() @hapic.output_stream(OuputStreamItemSchema()) async def hello(request): return AsyncGenerator() app = web.Application(debug=True) app.router.add_get("/", hello) hapic.set_context( AiohttpContext(app, default_error_builder=MarshmallowDefaultErrorBuilder()) ) client = await aiohttp_client(app) resp = await client.get("/") assert resp.status == 200 line = await resp.content.readline() assert b'{"name": "Hello, bob"}\n' == line line = await resp.content.readline() assert b'{"name": "Hello, franck"}\n' == line
def test_func_schema_in_doc__ok__additionals_fields__file(self): hapic = Hapic(processor_class=MarshmallowProcessor) app = AgnosticApp() hapic.set_context(AgnosticContext(app=app)) class MySchema(marshmallow.Schema): category = marshmallow.fields.Raw(required=True, description="a description", example="00010") @hapic.with_api_doc() @hapic.input_files(MySchema()) def my_controller(): return app.route("/upload", method="POST", callback=my_controller) doc = hapic.generate_doc() assert doc assert "/upload" in doc["paths"] assert "consumes" in doc["paths"]["/upload"]["post"] assert "multipart/form-data" in doc["paths"]["/upload"]["post"][ "consumes"] assert doc.get("paths").get("/upload").get("post").get("parameters")[0] field = doc.get("paths").get("/upload").get("post").get( "parameters")[0] assert field[ "description"] == "a description\n\n*example value: 00010*" # INFO - G.M - 01-06-2018 - Field example not allowed here, # added in description instead assert "example" not in field assert field["in"] == "formData" assert field["type"] == "file" assert field["required"] is True
async def test_unit__input_error__err__hook_called(self, aiohttp_client): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) class InputPathSchema(marshmallow.Schema): user_id = marshmallow.fields.Int(required=True) class MyContext(AiohttpContext): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.hook_called = None def input_validation_error_caught( self, request_parameters: RequestParameters, process_exception: ProcessException) -> None: self.hook_called = request_parameters @hapic.with_api_doc() @hapic.input_path(InputPathSchema()) async def user(): pass app = web.Application(debug=True) context = MyContext(app) hapic.set_context(context) app.router.add_get("/user", user) client = await aiohttp_client(app) assert not context.hook_called await client.get("/user?foo=bar") assert context.hook_called.query_parameters.get("foo") == "bar"
def test_func__input_files_doc__ok__one_file_and_text(self): hapic = Hapic() hapic.set_context(MyContext()) app = bottle.Bottle() class MySchema(marshmallow.Schema): name = marshmallow.fields.String(required=True) class MyFilesSchema(marshmallow.Schema): file_abc = marshmallow.fields.Raw(required=True) @hapic.with_api_doc() @hapic.input_files(MyFilesSchema()) @hapic.input_body(MySchema()) def my_controller(hapic_data=None): assert hapic_data assert hapic_data.files app.route('/upload', method='POST', callback=my_controller) doc = hapic.generate_doc(app) assert doc assert '/upload' in doc['paths'] assert 'consume' in doc['paths']['/upload']['post'] assert 'multipart/form-data' in doc['paths']['/upload']['post'][ 'consume'] assert 'parameters' in doc['paths']['/upload']['post'] assert { 'name': 'file_abc', 'required': True, 'in': 'formData', 'type': 'file', } in doc['paths']['/upload']['post']['parameters']
async def test_unit__output_error__err__hook_called(self, aiohttp_client): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) class OutputBodySchema(marshmallow.Schema): user_id = marshmallow.fields.Int(required=True) class MyContext(AiohttpContext): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.hook_called = False def output_validation_error_caught( self, hapic_data: HapicData, process_exception: ProcessException) -> None: self.hook_called = True @hapic.with_api_doc() @hapic.output_body(OutputBodySchema()) async def user(request): return {} app = web.Application(debug=True) context = MyContext(app) hapic.set_context(context) app.router.add_get("/user", user) client = await aiohttp_client(app) assert not context.hook_called await client.get("/user") assert context.hook_called
async def test_aiohttp_input_body__error__incorrect_input_body( self, aiohttp_client, loop): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) class InputBodySchema(marshmallow.Schema): i = marshmallow.fields.Integer() @hapic.input_body(InputBodySchema()) async def hello(request, hapic_data: HapicData): i = hapic_data.body.get("i") return web.Response(text="integer, {}".format(i)) app = web.Application(debug=True) app.router.add_post("/", hello) hapic.set_context( AiohttpContext( app, default_error_builder=MarshmallowDefaultErrorBuilder())) client = await aiohttp_client(app) resp = await client.post("/", data={"i": "bob"}) # NOTE: should be int assert resp.status == 400 error = await resp.json() assert "Validation error of input data" in error.get("message") assert {"i": ["Not a valid integer."]} == error.get("details")
async def test_aiohttp_output_body__error__incorrect_output_body( self, aiohttp_client, loop): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) class OuputBodySchema(marshmallow.Schema): i = marshmallow.fields.Integer(required=True) @hapic.output_body(OuputBodySchema()) async def hello(request): return {"i": "bob"} # NOTE: should be integer app = web.Application(debug=True) app.router.add_get("/", hello) hapic.set_context( AiohttpContext( app, default_error_builder=MarshmallowDefaultErrorBuilder())) client = await aiohttp_client(app) resp = await client.get("/") assert resp.status == 500 data = await resp.text() data = await resp.json() assert "Validation error of output data" == data.get("message") assert { "i": ["Missing data for required field."] } == data.get("details")
def test_unit__generate_output_stream_doc__ok__nominal_case( self, aiohttp_client, loop): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) class OuputStreamItemSchema(marshmallow.Schema): name = marshmallow.fields.String(required=True) @hapic.with_api_doc() @hapic.output_stream(OuputStreamItemSchema()) async def get_users(request, hapic_data): pass app = web.Application(debug=True) app.router.add_get("/", get_users) hapic.set_context( AiohttpContext( app, default_error_builder=MarshmallowDefaultErrorBuilder())) doc = hapic.generate_doc("aiohttp", "testing") # INFO BS 2019-04-15: Prevent keep of OrderedDict doc = json.loads(json.dumps(doc)) assert "/" in doc.get("paths") assert "get" in doc["paths"]["/"] assert "200" in doc["paths"]["/"]["get"].get("responses", {}) assert { "items": { "$ref": "#/definitions/OuputStreamItemSchema" }, "type": "array" } == doc["paths"]["/"]["get"]["responses"]["200"]["schema"]
def test_func__catch_one_exception__ok__pyramid(self): from pyramid.config import Configurator configurator = Configurator(autocommit=True) context = PyramidContext( configurator, default_error_builder=MarshmallowDefaultErrorBuilder()) hapic = Hapic(processor_class=MarshmallowProcessor) hapic.set_context(context) def my_view(context, request): raise ZeroDivisionError("An exception message") configurator.add_route("my_view", "/my-view", request_method="GET") configurator.add_view(my_view, route_name="my_view", renderer="json") # FIXME - G.M - 17-05-2018 - Verify if: # - other view that work/raise an other exception do not go # into this code for handle this exceptions. # - response come clearly from there, not from web framework: # Check not only http code, but also body. # see issue #158 (https://github.com/algoo/hapic/issues/158) context.handle_exception(ZeroDivisionError, http_code=400) app = configurator.make_wsgi_app() test_app = TestApp(app) response = test_app.get("/my-view", status="*") assert 400 == response.status_code
async def test_aiohttp_input_path__error_wrong_input_parameter( self, aiohttp_client, loop): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) class InputPathSchema(marshmallow.Schema): i = marshmallow.fields.Integer() @hapic.input_path(InputPathSchema()) async def hello(request, hapic_data: HapicData): i = hapic_data.path.get("i") return web.Response(text="integer: {}".format(str(i))) app = web.Application(debug=True) app.router.add_get("/{i}", hello) hapic.set_context( AiohttpContext( app, default_error_builder=MarshmallowDefaultErrorBuilder())) client = await aiohttp_client(app) resp = await client.get("/bob") # NOTE: should be integer here assert resp.status == 400 error = await resp.json() assert "Validation error of input data" in error.get("message") assert {"i": ["Not a valid integer."]} == error.get("details")
async def test_unit__general_exception_handling__ok__nominal_case( self, aiohttp_client, loop): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) @hapic.with_api_doc() async def hello(request): return 1 / 0 app = web.Application(debug=True) app.router.add_get("/", hello) context = AiohttpContext( app, default_error_builder=MarshmallowDefaultErrorBuilder()) context.handle_exception(ZeroDivisionError, 400) hapic.set_context(context) client = await aiohttp_client(app) resp = await client.get("/") json = await resp.json() assert resp.status == 400 assert { "details": { "error_detail": {} }, "message": "division by zero", "code": None, } == json
def test_func__schema_in_doc__ok__many_case(self): hapic = Hapic(processor_class=MarshmallowProcessor) app = AgnosticApp() hapic.set_context(AgnosticContext(app=app)) class MySchema(marshmallow.Schema): name = marshmallow.fields.String(required=True) @hapic.with_api_doc() @hapic.input_body(MySchema(many=True)) def my_controller(): return {"name": "test"} app.route("/paper", method="POST", callback=my_controller) doc = hapic.generate_doc() assert doc.get("definitions", {}).get("MySchema", {}) schema_def = doc.get("definitions", {}).get("MySchema", {}) assert schema_def.get("properties", {}).get("name", {}).get("type") assert doc.get("paths").get("/paper").get("post").get("parameters")[0] schema_ref = doc.get("paths").get("/paper").get("post").get( "parameters")[0] assert schema_ref.get("in") == "body" assert schema_ref.get("name") == "body" assert schema_ref["schema"] == { "items": { "$ref": "#/definitions/MySchema" }, "type": "array", }
async def test_unit__global_exception__ok__hook_called( self, aiohttp_client): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) class MyContext(AiohttpContext): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.hook_called = False def global_exception_caught(self, exc: Exception, *args, **kwargs) -> None: self.hook_called = True @hapic.with_api_doc() async def divide_by_zero(request): raise ZeroDivisionError() app = web.Application(debug=True) context = MyContext(app) hapic.set_context(context) context.handle_exception(ZeroDivisionError, http_code=HTTPStatus.BAD_REQUEST) app.router.add_get("/", divide_by_zero) client = await aiohttp_client(app) assert not context.hook_called await client.get("/") assert context.hook_called
def test_func__errors__http_status_as_int_description(self): hapic = Hapic(processor_class=MarshmallowProcessor) app = AgnosticApp() hapic.set_context(AgnosticContext(app=app)) class MyException(Exception): pass @hapic.with_api_doc() @hapic.handle_exception(MyException, http_code=400) def my_controller(hapic_data=None): assert hapic_data app.route("/upload", method="POST", callback=my_controller) doc = hapic.generate_doc() assert doc.get("paths") assert "/upload" in doc["paths"] assert "post" in doc["paths"]["/upload"] assert "responses" in doc["paths"]["/upload"]["post"] assert "400" in doc["paths"]["/upload"]["post"]["responses"] assert { "description": "400", "schema": { "$ref": "#/definitions/DefaultErrorSchema" }, } == doc["paths"]["/upload"]["post"]["responses"]["400"]
async def test_unit__post_file__ok__put_success(self, aiohttp_client, loop): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) class InputFilesSchema(marshmallow.Schema): avatar = marshmallow.fields.Raw() @hapic.with_api_doc() @hapic.input_files(InputFilesSchema()) async def update_avatar(request: Request, hapic_data: HapicData): avatar = hapic_data.files["avatar"] assert isinstance(avatar, FileField) assert b"text content of file" == avatar.file.read() return Response(body="ok") app = web.Application(debug=True) app.router.add_put("/avatar", update_avatar) hapic.set_context( AiohttpContext( app, default_error_builder=MarshmallowDefaultErrorBuilder())) client = await aiohttp_client(app) resp = await client.put( "/avatar", data={"avatar": io.StringIO("text content of file")}) assert resp.status == 200
def test_func__input_files_doc__ok__one_file(self): hapic = Hapic(processor_class=MarshmallowProcessor) app = AgnosticApp() hapic.set_context(AgnosticContext(app=app)) class MySchema(marshmallow.Schema): file_abc = marshmallow.fields.Raw(required=True) @hapic.with_api_doc() @hapic.input_files(MySchema()) def my_controller(hapic_data=None): assert hapic_data assert hapic_data.files app.route("/upload", method="POST", callback=my_controller) doc = hapic.generate_doc() assert doc assert "/upload" in doc["paths"] assert "consumes" in doc["paths"]["/upload"]["post"] assert "multipart/form-data" in doc["paths"]["/upload"]["post"][ "consumes"] assert "parameters" in doc["paths"]["/upload"]["post"] assert { "name": "file_abc", "required": True, "in": "formData", "type": "file" } in doc["paths"]["/upload"]["post"]["parameters"]
async def test_unit__post_file__ok__missing_file(self, aiohttp_client, loop): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) class InputFilesSchema(marshmallow.Schema): avatar = marshmallow.fields.Raw(required=True) @hapic.with_api_doc() @hapic.input_files(InputFilesSchema()) async def update_avatar(request: Request, hapic_data: HapicData): raise AssertionError("Test should no pass here") app = web.Application(debug=True) app.router.add_put("/avatar", update_avatar) hapic.set_context( AiohttpContext( app, default_error_builder=MarshmallowDefaultErrorBuilder())) client = await aiohttp_client(app) resp = await client.put("/avatar") assert resp.status == 400 json_ = await resp.json() assert { "details": { "avatar": ["Missing data for required field"] }, "message": "Validation error of input data", "code": None, } == json_
def test_unit__input_files__ok__file_is_not_present(self): hapic = Hapic(processor_class=MarshmallowProcessor) hapic.set_context( AgnosticContext( app=None, files_parameters={ # No file here }, ) ) class MySchema(marshmallow.Schema): file_abc = marshmallow.fields.Raw(required=True) @hapic.input_files(MySchema()) def my_controller(hapic_data=None): assert hapic_data assert hapic_data.files return "OK" result = my_controller() assert HTTPStatus.BAD_REQUEST == result.status_code assert { "http_code": 400, "original_error": { "details": {"file_abc": ["Missing data for required field"]}, "message": "Validation error of input data", }, } == json.loads(result.body)
async def test_aiohttp_output_stream__error__interrupt_py37( self, aiohttp_client, loop): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) class OuputStreamItemSchema(marshmallow.Schema): name = marshmallow.fields.String(required=True) from .py37.stream import get_func_with_output_stream_and_error hello = get_func_with_output_stream_and_error(hapic, OuputStreamItemSchema, ignore_on_error=False) app = web.Application(debug=True) app.router.add_get("/", hello) hapic.set_context( AiohttpContext( app, default_error_builder=MarshmallowDefaultErrorBuilder())) client = await aiohttp_client(app) resp = await client.get("/") assert resp.status == 200 line = await resp.content.readline() assert b'{"name": "Hello, bob"}\n' == line line = await resp.content.readline() assert b"" == line
def test_func__schema_with_many__ok__with_exclude(self): hapic = Hapic(processor_class=MarshmallowProcessor) app = AgnosticApp() hapic.set_context(AgnosticContext(app=app)) class MySchema(marshmallow.Schema): first_name = marshmallow.fields.String(required=True) last_name = marshmallow.fields.String(required=False) @hapic.with_api_doc() @hapic.output_body(MySchema(many=True, exclude=("last_name", ))) def my_controller(hapic_data=None): pass app.route("/", method="GET", callback=my_controller) doc = hapic.generate_doc() assert { "MySchema_without_last_name": { "type": "object", "properties": { "first_name": { "type": "string" } }, "required": ["first_name"], } } == doc["definitions"]
def test_unit__generate_doc__ok__nominal_case(self, aiohttp_client, loop): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) class InputPathSchema(marshmallow.Schema): username = marshmallow.fields.String(required=True) class InputQuerySchema(marshmallow.Schema): show_deleted = marshmallow.fields.Boolean(required=False) class UserSchema(marshmallow.Schema): name = marshmallow.fields.String(required=True) @hapic.with_api_doc() @hapic.input_path(InputPathSchema()) @hapic.input_query(InputQuerySchema()) @hapic.output_body(UserSchema()) async def get_user(request, hapic_data): pass @hapic.with_api_doc() @hapic.input_path(InputPathSchema()) @hapic.output_body(UserSchema()) async def post_user(request, hapic_data): pass app = web.Application(debug=True) app.router.add_get("/{username}", get_user) app.router.add_post("/{username}", post_user) hapic.set_context( AiohttpContext(app, default_error_builder=MarshmallowDefaultErrorBuilder()) ) doc = hapic.generate_doc("aiohttp", "testing") # INFO BS 2019-04-15: Prevent keep of OrderedDict doc = json.loads(json.dumps(doc)) assert "UserSchema" in doc.get("definitions") assert {"name": {"type": "string"}} == doc["definitions"]["UserSchema"].get("properties") assert "/{username}" in doc.get("paths") assert "get" in doc["paths"]["/{username}"] assert "post" in doc["paths"]["/{username}"] assert [ {"name": "username", "in": "path", "required": True, "type": "string"}, {"name": "show_deleted", "in": "query", "required": False, "type": "boolean"}, ] == doc["paths"]["/{username}"]["get"]["parameters"] assert { "200": {"schema": {"$ref": "#/definitions/UserSchema"}, "description": "200"} } == doc["paths"]["/{username}"]["get"]["responses"] assert [{"name": "username", "in": "path", "required": True, "type": "string"}] == doc[ "paths" ]["/{username}"]["post"]["parameters"] assert { "200": {"schema": {"$ref": "#/definitions/UserSchema"}, "description": "200"} } == doc["paths"]["/{username}"]["get"]["responses"]
def test_func__errors__multiple_same_http_status_description(self): hapic = Hapic(processor_class=MarshmallowProcessor) app = AgnosticApp() hapic.set_context(AgnosticContext(app=app)) class MyFirstException(Exception): pass class MySecondException(Exception): "Just a docstring" class MyThirdException(Exception): "Docstring not used" pass class MyFourthException(Exception): pass @hapic.with_api_doc() @hapic.handle_exception(MyFirstException, http_code=HTTPStatus.BAD_REQUEST) @hapic.handle_exception(MySecondException, http_code=HTTPStatus.BAD_REQUEST) @hapic.handle_exception(MyThirdException, http_code=HTTPStatus.BAD_REQUEST, description="explicit description") @hapic.handle_exception(MyFourthException, http_code=400) def my_controller(hapic_data=None): assert hapic_data app.route("/upload", method="POST", callback=my_controller) doc = hapic.generate_doc() assert doc.get("paths") assert "/upload" in doc["paths"] assert "post" in doc["paths"]["/upload"] assert "responses" in doc["paths"]["/upload"]["post"] assert "400" in doc["paths"]["/upload"]["post"]["responses"] assert "description" assert doc["paths"]["/upload"]["post"]["responses"]["400"][ "description"] descriptions = doc["paths"]["/upload"]["post"]["responses"]["400"][ "description"].split("\n\n") assert "BAD_REQUEST: Bad request syntax or unsupported method" in descriptions assert "explicit description" in descriptions assert "400" in descriptions assert "Just a docstring" in descriptions assert "Docstring not used" not in descriptions assert doc["paths"]["/upload"]["post"]["responses"]["400"]["schema"] assert { "$ref": "#/definitions/DefaultErrorSchema" } == doc["paths"]["/upload"]["post"]["responses"]["400"]["schema"]
async def test_unit__handle_exception__ok__nominal_case(self, aiohttp_client): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) @hapic.with_api_doc() @hapic.handle_exception(ZeroDivisionError, http_code=HTTPStatus.BAD_REQUEST) async def divide_by_zero(request): raise ZeroDivisionError() app = web.Application(debug=True) hapic.set_context(AiohttpContext(app)) app.router.add_get("/", divide_by_zero) client = await aiohttp_client(app) response = await client.get("/") assert 400 == response.status
def test_unit__input_files__ok__file_is_present(self): hapic = Hapic(processor_class=MarshmallowProcessor) hapic.set_context(AgnosticContext(app=None, files_parameters={"file_abc": "10101010101"})) class MySchema(marshmallow.Schema): file_abc = marshmallow.fields.Raw(required=True) @hapic.input_files(MySchema()) def my_controller(hapic_data=None): assert hapic_data assert hapic_data.files return "OK" result = my_controller() assert "OK" == result
def test_unit__decoration__ok__method(self): hapic = Hapic() class MyControllers(object): @hapic.with_api_doc() def controller_a(self): pass my_controllers = MyControllers() class_method_token = getattr( MyControllers.controller_a, DECORATION_ATTRIBUTE_NAME, None, ) assert class_method_token instance_method_token = getattr( my_controllers.controller_a, DECORATION_ATTRIBUTE_NAME, None, ) assert instance_method_token assert hapic.controllers assert 1 == len(hapic.controllers) reference = hapic.controllers[0].reference assert class_method_token == reference.token assert instance_method_token == reference.token assert MyControllers.controller_a == reference.wrapper assert MyControllers.controller_a != reference.wrapped assert my_controllers.controller_a != reference.wrapper assert my_controllers.controller_a != reference.wrapped
def test_func_schema_in_doc__ok__additionals_fields__forms__string(self): hapic = Hapic(processor_class=MarshmallowProcessor) app = AgnosticApp() hapic.set_context(AgnosticContext(app=app)) class MySchema(marshmallow.Schema): category = marshmallow.fields.String( required=True, description="a description", example="00010", format="binary", enum=["01000", "11111"], maxLength=5, minLength=5, # Theses none string specific parameters should disappear # in query/path maximum=400, # exclusiveMaximun=False, # minimum=0, # exclusiveMinimum=True, # multipleOf=1, ) @hapic.with_api_doc() @hapic.input_forms(MySchema()) def my_controller(): return app.route("/paper", method="POST", callback=my_controller) doc = hapic.generate_doc() assert "multipart/form-data" in doc["paths"]["/paper"]["post"][ "consumes"] assert doc.get("paths").get("/paper").get("post").get("parameters")[0] field = doc.get("paths").get("/paper").get("post").get("parameters")[0] assert field[ "description"] == "a description\n\n*example value: 00010*" # INFO - G.M - 01-06-2018 - Field example not allowed here, # added in description instead assert "example" not in field assert field["format"] == "binary" assert field["in"] == "formData" assert field["type"] == "string" assert field["maxLength"] == 5 assert field["minLength"] == 5 assert field["required"] is True assert field["enum"] == ["01000", "11111"] assert "maximum" not in field
def test_func__tags__ok__nominal_case(self): hapic = Hapic(processor_class=MarshmallowProcessor) app = AgnosticApp() hapic.set_context(AgnosticContext(app=app)) @hapic.with_api_doc(tags=["foo", "bar"]) def my_controller(hapic_data=None): assert hapic_data assert hapic_data.files app.route("/upload", method="POST", callback=my_controller) doc = hapic.generate_doc() assert doc.get("paths") assert "/upload" in doc["paths"] assert "post" in doc["paths"]["/upload"] assert "tags" in doc["paths"]["/upload"]["post"] assert ["foo", "bar"] == doc["paths"]["/upload"]["post"]["tags"]
def test_func__output_file_doc__ok__nominal_case(self): hapic = Hapic(processor_class=MarshmallowProcessor) app = AgnosticApp() hapic.set_context(AgnosticContext(app=app)) @hapic.with_api_doc() @hapic.output_file(["image/jpeg"]) def my_controller(): return b"101010100101" app.route("/avatar", method="GET", callback=my_controller) doc = hapic.generate_doc() assert doc assert "/avatar" in doc["paths"] assert "produces" in doc["paths"]["/avatar"]["get"] assert "image/jpeg" in doc["paths"]["/avatar"]["get"]["produces"] assert "200" in doc["paths"]["/avatar"]["get"]["responses"]
def test_func__output_file_doc__ok__nominal_case(self): hapic = Hapic() hapic.set_context(MyContext()) app = bottle.Bottle() @hapic.with_api_doc() @hapic.output_file('image/jpeg') def my_controller(): return b'101010100101' app.route('/avatar', method='GET', callback=my_controller) doc = hapic.generate_doc(app) assert doc assert '/avatar' in doc['paths'] assert 'produce' in doc['paths']['/avatar']['get'] assert 'image/jpeg' in doc['paths']['/avatar']['get']['produce'] assert 200 in doc['paths']['/avatar']['get']['responses']
async def test_request_header__ok__lowercase_key(self, aiohttp_client): hapic = Hapic(async_=True, processor_class=MarshmallowProcessor) class HeadersSchema(marshmallow.Schema): foo = marshmallow.fields.String(required=True) @hapic.with_api_doc() @hapic.input_headers(HeadersSchema()) async def hello(request, hapic_data: HapicData): return web.json_response(hapic_data.headers) app = web.Application(debug=True) hapic.set_context(AiohttpContext(app)) app.router.add_get("/", hello) client = await aiohttp_client(app) response = await client.get("/", headers={"FOO": "bar"}) assert 200 == response.status json_ = await response.json() assert {"foo": "bar"} == json_