def test_numeric_status_codes(empty_open_api_3_schema): # When the API schema contains a numeric status code, which is not allowed by the spec empty_open_api_3_schema["paths"] = { "/foo": { "parameters": [], "get": { "responses": { 200: { "description": "OK" } }, }, "post": { "responses": { 201: { "description": "OK" } }, }, }, } # And schema validation is enabled # Then Schemathesis reports an error about numeric status codes with pytest.raises(SchemaLoadingError, match=NUMERIC_STATUS_CODES_MESSAGE) as exc: schemathesis.from_dict(empty_open_api_3_schema) # And shows all locations of these keys assert " - 200 at schema['paths']['/foo']['get']['responses']" in exc.value.args[ 0] assert " - 201 at schema['paths']['/foo']['post']['responses']" in exc.value.args[ 0]
def test_invalid_code_sample_style(empty_open_api_3_schema): with pytest.raises( ValueError, match= "Invalid value for code sample style: ruby. Available styles: python, curl" ): schemathesis.from_dict(empty_open_api_3_schema, code_sample_style="ruby")
def api_schema(request, openapi_version): if openapi_version.is_openapi_2: schema = request.getfixturevalue("empty_open_api_2_schema") schema["paths"] = { "/payload": { "post": { "parameters": [{ "in": "body", "required": True, "name": "payload", "schema": { "type": "boolean", "x-nullable": True }, }], "responses": { "200": { "description": "OK" } }, } } } jsonschema.validate(schema, SWAGGER_20) else: schema = request.getfixturevalue("empty_open_api_3_schema") schema["paths"] = { "/payload": { "post": { "requestBody": { "required": True, "content": { "application/json": { "schema": { "type": "boolean", "nullable": True } } }, }, "responses": { "200": { "description": "OK" } }, } } } jsonschema.validate(schema, OPENAPI_30) if request.param == "aiohttp": base_url = request.getfixturevalue("base_url") return schemathesis.from_dict(schema, base_url=base_url) app = request.getfixturevalue("flask_app") return schemathesis.from_dict(schema, app=app, base_url="/api")
def test_date_format(data): raw_schema = { "openapi": "3.0.2", "info": {"title": "Test", "description": "Test", "version": "0.1.0"}, "paths": { "/data": { "post": { "requestBody": { "content": { "application/json": { "schema": { "format": "date", "type": "string", }, } }, "required": True, }, "responses": {"200": {"description": "OK"}}, } } }, } schema = schemathesis.from_dict(raw_schema) strategy = schema["/data"]["POST"].as_strategy() case = data.draw(strategy) datetime.datetime.strptime(case.body, "%Y-%m-%d")
def test_parameters_jsonified(empty_open_api_3_schema, expected): # See GH-1166 # When `None` or `True` / `False` are generated in path or query empty_open_api_3_schema["paths"] = { "/foo/{param_path}": { "get": { "parameters": [ { "name": f"param_{location}", "in": location, "required": True, "schema": {"type": "boolean", "nullable": True}, } for location in ("path", "query") ], "responses": {"200": {"description": "OK"}}, } } } schema = schemathesis.from_dict(empty_open_api_3_schema) strategy = schema["/foo/{param_path}"]["GET"].as_strategy() @given(case=strategy) @settings(deadline=None, max_examples=1) def test(case): # Then they should be converted to their JSON equivalents assume(case.path_parameters["param_path"] == expected) assume(case.query["param_query"] == expected) test()
def test_iter_parameters(empty_open_api_3_schema): empty_open_api_3_schema["paths"] = { "/data": { "post": { "parameters": [ { "name": "X-id", "in": "header", "required": True, "schema": { "type": "string" }, }, { "name": "q", "in": "query", "required": True, "schema": { "type": "string" }, }, ], "responses": { "200": { "description": "OK" } }, }, }, } schema = schemathesis.from_dict(empty_open_api_3_schema) params = list(schema["/data"]["POST"].iter_parameters()) assert len(params) == 2 assert params[0].name == "X-id" assert params[1].name == "q"
def test_case_insensitive_headers(empty_open_api_3_schema): empty_open_api_3_schema["paths"] = { "/data": { "post": { "parameters": [{ "name": "X-id", "in": "header", "required": True, "schema": { "type": "string" }, }], "responses": { "200": { "description": "OK" } }, }, }, } # When headers are generated schema = schemathesis.from_dict(empty_open_api_3_schema) @given(case=schema["/data"]["POST"].as_strategy()) @settings(max_examples=1) def test(case): assert "X-id" in case.headers # Then they are case-insensitive case.headers["x-ID"] = "foo" assert len(case.headers) == 1 assert case.headers["X-id"] == "foo" test()
def test_partial_examples(empty_open_api_3_schema): # When the API schema contains multiple parameters in the same location # And some of them don't have explicit examples and others do empty_open_api_3_schema["paths"] = { "/test/{foo}/{bar}/": { "post": { "parameters": [ {"name": "foo", "in": "path", "required": True, "schema": {"type": "string", "enum": ["A"]}}, { "name": "bar", "in": "path", "required": True, "schema": {"type": "string", "example": "bar-example"}, }, ], "responses": {"default": {"description": "OK"}}, } } } schema = schemathesis.from_dict(empty_open_api_3_schema) operation = schema["/test/{foo}/{bar}/"]["POST"] strategy = operation.get_strategies_from_examples()[0] # Then all generated examples should have those missing parts generated according to the API schema example = get_single_example(strategy) parameters_schema = parameters_to_json_schema(operation.path_parameters) jsonschema.validate(example.path_parameters, parameters_schema)
def test_ref_field(): # When the schema contains "$ref" field, that is not a reference (which is supported by the JSON Schema spec) raw_schema = { "openapi": "3.0.2", "info": {"title": "Test", "description": "Test", "version": "0.1.0"}, "paths": { "/body": { "post": { "requestBody": { "content": { "application/json": { "schema": { "properties": {"$ref": {"type": "string"}}, "required": ["$ref"], "type": "object", } } }, "required": True, }, "responses": {"200": {"description": "OK"}}, } } }, } schema = schemathesis.from_dict(raw_schema) @given(case=schema["/body"]["POST"].as_strategy()) @settings(max_examples=5) def test(case): assert isinstance(case.body["$ref"], str) # Then "$ref" field should be generated test()
def test_link_override(empty_open_api_3_schema, schema_code, link_code): # See GH-1022 # When the schema contains response codes as integers empty_open_api_3_schema["paths"] = { "/foo": { "get": { "parameters": [ { "in": "query", "name": "key", "schema": { "type": "integer" }, "required": True }, ], "responses": { schema_code: { "description": "OK", "schema": { "type": "object" } } }, } }, } schema = schemathesis.from_dict(empty_open_api_3_schema, validate_schema=False) schema.add_link(source=schema["/foo"]["GET"], target=schema["/foo"]["GET"], status_code=link_code, parameters={"key": "42"}) assert "links" in schema.raw_schema["paths"]["/foo"]["get"]["responses"][ schema_code]
def test_binary_body(mocker, flask_app): # When an endpoint accepts a binary input schema = schemathesis.from_dict( { "openapi": "3.0.2", "info": {"title": "Test", "description": "Test", "version": "0.1.0"}, "paths": { "/api/upload_file": { "post": { "requestBody": { "content": {"application/octet-stream": {"schema": {"format": "binary", "type": "string"}}} }, "responses": {"200": {"description": "OK"}}, } } }, }, app=flask_app, ) strategy = schema.endpoints["/api/upload_file"]["POST"].as_strategy() @given(case=strategy) @settings(max_examples=3, suppress_health_check=[HealthCheck.filter_too_much], deadline=None) def test(case): response = case.call_wsgi() assert response.status_code == 200 assert response.json == {"size": mocker.ANY} # Then it should be sent correctly test()
def test_binary_data(empty_open_api_3_schema, media_type): empty_open_api_3_schema["paths"] = { "/test": { "post": { "requestBody": { "required": True, "content": { media_type: { "schema": {}, "examples": {"answer": {"externalValue": f"http://127.0.0.1:1/answer.json"}}, } }, }, "responses": {"200": {"description": "OK"}}, }, }, } schema = schemathesis.from_dict(empty_open_api_3_schema) operation = schema["/test"]["POST"] # When an explicit bytes value is passed as body (it happens with `externalValue`) body = b"\x92\x42" case = operation.make_case(body=body, media_type=media_type) # Then it should be used as is requests_kwargs = case.as_requests_kwargs() assert requests_kwargs["data"] == body werkzeug_kwargs = case.as_werkzeug_kwargs() assert werkzeug_kwargs["data"] == body if media_type != "multipart/form-data": # Don't know the proper header for raw multipart content assert requests_kwargs["headers"]["Content-Type"] == media_type assert werkzeug_kwargs["headers"]["Content-Type"] == media_type # And it is OK to send it over the network assert_requests_call(case)
def test_make_case_missing_media_type(empty_open_api_3_schema): # When there are multiple available media types empty_open_api_3_schema["paths"] = { "/data": { "post": { "requestBody": { "required": True, "content": { "text/plain": { "schema": { "type": "string" } }, "application/json": { "schema": { "type": "array" } }, }, }, "responses": { "200": { "description": "OK" } }, }, }, } schema = schemathesis.from_dict(empty_open_api_3_schema) # And the `media_type` argument is not passed to `make_case` # Then there should be a usage error with pytest.raises(UsageError): schema["/data"]["POST"].make_case(body="foo")
def test_multipart_examples(): # Regression after parameters refactoring # When the schema contains examples for multipart forms raw_schema = { "openapi": "3.0.0", "info": {"title": "Sample API", "description": "API description in Markdown.", "version": "1.0.0"}, "paths": { "/test": { "post": { "description": "Test", "requestBody": { "content": { "multipart/form-data": { "schema": { "properties": { "key": { "example": "test", "type": "string", }, }, "type": "object", } } } }, "responses": {"default": {"description": "OK"}}, }, }, }, } schema = schemathesis.from_dict(raw_schema) # Then examples should be correctly generated strategies = schema["/test"]["POST"].get_strategies_from_examples() assert len(strategies) == 1 assert find(strategies[0], lambda case: case.body == {"key": "test"})
def test_path_serialization_styles_openapi3(schema, style, explode, expected): raw_schema = { "openapi": "3.0.2", "info": {"title": "Test", "description": "Test", "version": "0.1.0"}, "paths": { "/teapot/{color}": { "get": { "summary": "Test", "parameters": [ { "name": "color", "in": "path", "required": True, "schema": schema, "style": style, "explode": explode, } ], "responses": {"200": {"description": "OK"}}, } } }, } schema = schemathesis.from_dict(raw_schema) @given(case=schema["/teapot/{color}"]["GET"].as_strategy()) def test(case): assert case.path_parameters == expected test()
def test_invalid_body_in_get_disable_validation(simple_schema): schema = schemathesis.from_dict(simple_schema, validate_schema=False) endpoint = Endpoint( path="/foo", method="GET", definition=EndpointDefinition({}, {}, "foo"), schema=schema, body={ "required": ["foo"], "type": "object", "properties": { "foo": { "type": "string" } } }, ) strategy = get_case_strategy(endpoint) @given(strategy) @settings(max_examples=1) def test(case): assert case.body is not None test()
def _assert_parameter(schema, schema_spec, location, expected=None): # When security definition is defined as "apiKey" schema = schemathesis.from_dict(schema) if schema_spec == "swagger": operation = schema["/users"]["get"] expected = (expected if expected is not None else [{ "in": location, "name": "api_key", "type": "string", "required": True }]) else: operation = schema["/query"]["get"] expected = (expected if expected is not None else [{ "in": location, "name": "api_key", "schema": { "type": "string" }, "required": True }]) parameters = schema.security.get_security_definitions_as_parameters( schema.raw_schema, operation, schema.resolver, location) # Then it should be presented as a "string" parameter assert parameters == expected
def test_invalid_x_examples(empty_open_api_2_schema): # See GH-982 # When an Open API 2.0 schema contains a non-object type in `x-examples` empty_open_api_2_schema["paths"] = { "/test": { "post": { "parameters": [{ "name": "body", "in": "body", "schema": { "type": "integer" }, "x-examples": { "foo": "value" } }], "responses": { "default": { "description": "OK" } }, } } } schema = schemathesis.from_dict(empty_open_api_2_schema) # Then such examples should be skipped as invalid (there should be an object) assert schema["/test"]["POST"].get_strategies_from_examples() == []
def test_operation_id(operation_id): parameters = {"responses": {"200": {"description": "OK"}}} raw = make_schema( "simple_openapi.yaml", paths={ "/foo": { "get": { **parameters, "operationId": "foo_get" } }, "/bar": { "get": { **parameters, "operationId": "bar_get" }, "post": { **parameters, "operationId": "bar_post" }, "put": parameters, }, }, ) schema = schemathesis.from_dict(raw, operation_id=operation_id) assert schema.operation_id == operation_id assert len(list(schema.get_all_operations())) == 1 for operation in schema.get_all_operations(): assert operation.ok().definition.raw["operationId"] == operation_id
def test_optional_form_data(openapi3_base_url, empty_open_api_3_schema): empty_open_api_3_schema["paths"]["/form"] = { "post": { "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "string", }, } } }, "responses": {"200": {"description": "OK"}}, } } # When the multipart form is optional # Note, this test is similar to the one above, but has a simplified schema & conditions # It is done mostly due to performance reasons schema = schemathesis.from_dict(empty_open_api_3_schema, base_url=openapi3_base_url) @given(case=schema["/form"]["POST"].as_strategy()) @settings(deadline=None, suppress_health_check=[HealthCheck.too_slow, HealthCheck.filter_too_much], max_examples=1) def inner(case): assume(case.body is NOT_SET) case.call() # Then payload can be absent inner()
def test_missing_content_and_schema(empty_open_api_3_schema, location): # When an Open API 3 parameter is missing `schema` & `content` empty_open_api_3_schema["paths"] = { "/foo": { "get": { "parameters": [{ "in": location, "name": "X-Foo", "required": True }] } } } schema = schemathesis.from_dict(empty_open_api_3_schema, validate_schema=False) @given(schema["/foo"]["GET"].as_strategy()) @settings(max_examples=1) def test(case): pass # Then the proper error should be shown with pytest.raises( InvalidSchema, match=f'Can not generate data for {location} parameter "X-Foo"! ' "It should have either `schema` or `content` keywords defined", ): test()
def test_data_generation_method_is_available(method, empty_open_api_3_schema): # When a new case is generated empty_open_api_3_schema["paths"] = { "/data": { "post": { "requestBody": { "required": True, "content": { "text/plain": { "schema": { "type": "string" } } }, }, "responses": { "200": { "description": "OK" } }, }, }, } api_schema = schemathesis.from_dict(empty_open_api_3_schema) @given(case=api_schema["/data"]["POST"].as_strategy( data_generation_method=method)) @settings(max_examples=1) def test(case): # Then its data generation method should be available assert case.data_generation_method == method test()
def test_loose_multipart_definition(): # When the schema of "multipart/form-data" content does not define "object" type raw_schema = { "openapi": "3.0.2", "info": {"title": "Test", "description": "Test", "version": "0.1.0"}, "paths": { "/body": { "post": { "requestBody": { "content": {"multipart/form-data": {"schema": {"properties": {"foo": {"type": "string"}}}}}, "required": True, }, "responses": {"200": {"description": "OK"}}, } } }, } schema = schemathesis.from_dict(raw_schema) # Then non-object data should be excluded during generation @given(case=schema["/body"]["POST"].as_strategy()) @settings(max_examples=5) def test(case): assert isinstance(case.body, dict) # And the resulting values should be valid test()
def test_invalid_body_in_get_disable_validation(simple_schema): schema = schemathesis.from_dict(simple_schema, validate_schema=False) endpoint = Endpoint( path="/foo", method="GET", definition=EndpointDefinition({}, {}, "foo", []), schema=schema, body=PayloadAlternatives( [ OpenAPI20Body( { "name": "attributes", "in": "body", "required": True, "schema": {"required": ["foo"], "type": "object", "properties": {"foo": {"type": "string"}}}, }, media_type="application/json", ) ] ), ) strategy = get_case_strategy(endpoint) @given(strategy) @settings(max_examples=1) def test(case): assert case.body is not None test()
def test_call_asgi_and_validate(fastapi_app): api_schema = schemathesis.from_dict(fastapi_app.openapi()) @given(case=api_schema["/users"]["GET"].as_strategy()) @settings(max_examples=1) def test(case): with pytest.raises(RuntimeError, match="The URL should be absolute"): case.call_and_validate()
def assert_generates(raw_schema, expected, parameter): schema = schemathesis.from_dict(raw_schema) @given(case=schema["/teapot"]["GET"].as_strategy()) def test(case): assert getattr(case, "path_parameters" if parameter == "path" else parameter) == expected test()
def test_force_open_api_version(version, expected): schema = { # Invalid schema, but it happens in real applications "swagger": "2.0", "openapi": "3.0.0", } loaded = schemathesis.from_dict(schema, force_schema_version=version, validate_schema=False) assert isinstance(loaded, expected)
def test_not_recoverable_schema_error(simple_schema, validate_schema, expected_exception): # When there is an error in the API schema that leads to inability to generate any tests del simple_schema["paths"] # Then it is an explicit exception either during schema loading or processing API operations with pytest.raises(expected_exception): schema = schemathesis.from_dict(simple_schema, validate_schema=validate_schema) list(schema.get_all_operations())
def assert_content_type_conformance(raw_schema, content_type, is_error): schema = schemathesis.from_dict(raw_schema) endpoint = schema.endpoints["/users"]["get"] case = models.Case(endpoint) response = make_response(content_type=content_type) if not is_error: assert content_type_conformance(response, case) is None else: with pytest.raises(AssertionError): content_type_conformance(response, case)
def test_getitem(simple_schema, mocker): swagger = schemathesis.from_dict(simple_schema) mocked = mocker.patch("schemathesis.schemas.endpoints_to_dict", wraps=endpoints_to_dict) assert "_endpoints" not in swagger.__dict__ assert isinstance(swagger["/users"], CaseInsensitiveDict) assert mocked.call_count == 1 # Check cached access assert "_endpoints" in swagger.__dict__ assert isinstance(swagger["/users"], CaseInsensitiveDict) assert mocked.call_count == 1