Exemplo n.º 1
0
def run_app(port: int, endpoints: List[Endpoint], spec: str,
            framework: str) -> None:
    if spec == "graphql":
        app = _graphql.create_app()
        app.run(port=port)
    else:
        if endpoints is not None:
            if "__all__" in endpoints:
                prepared_endpoints = tuple(endpoint.name
                                           for endpoint in Endpoint)
            else:
                prepared_endpoints = tuple(endpoint.name
                                           for endpoint in endpoints)
        else:
            prepared_endpoints = tuple(
                endpoint.name for endpoint in Endpoint
                if endpoint.name not in INVALID_ENDPOINTS)
        version = {
            "openapi2": OpenAPIVersion("2.0"),
            "openapi3": OpenAPIVersion("3.0")
        }[spec]
        if framework == "aiohttp":
            app = _aiohttp.create_openapi_app(prepared_endpoints, version)
            web.run_app(app, port=port)
        elif framework == "flask":
            app = _flask.create_openapi_app(prepared_endpoints, version)
            app.run(port=port)
Exemplo n.º 2
0
def run_app(port: int, operations: List[Operation], spec: str,
            framework: str) -> None:
    if spec == "graphql":
        app = _graphql.create_app()
        app.run(port=port)
    else:
        if operations is not None:
            prepared_operations = tuple(operation.name
                                        for operation in operations)
            if "all" in prepared_operations:
                prepared_operations = tuple(operation.name
                                            for operation in Operation
                                            if operation.name != "all")
        else:
            prepared_operations = tuple(
                operation.name for operation in Operation
                if operation.name not in INVALID_OPERATIONS
                and operation.name != "all")
        version = {
            "openapi2": OpenAPIVersion("2.0"),
            "openapi3": OpenAPIVersion("3.0")
        }[spec]
        click.secho(
            f"Schemathesis test server is running!\n\n"
            f"API Schema is available at: http://0.0.0.0:{port}/schema.yaml\n",
            bold=True,
        )
        if framework == "aiohttp":
            app = _aiohttp.create_openapi_app(prepared_operations, version)
            web.run_app(app, port=port)
        elif framework == "flask":
            app = _flask.create_openapi_app(prepared_operations, version)
            app.run(port=port)
Exemplo n.º 3
0
def create_openapi_app(operations: Tuple[str, ...] = ("success", "failure"),
                       version: OpenAPIVersion = OpenAPIVersion(
                           "2.0")) -> web.Application:
    """Factory for aioHTTP app.

    Each handler except the one for schema saves requests in the list shared in the app instance and could be
    used to verify generated requests.

    >>> def test_something(app, server):
    >>>     # make some request to the app here
    >>>     assert app["incoming_requests"][0].method == "GET"
    """
    incoming_requests = []
    schema_requests = []

    async def schema(request: web.Request) -> web.Response:
        schema_data = request.app["config"]["schema_data"]
        content = yaml.dump(schema_data)
        schema_requests.append(request)
        return web.Response(body=content)

    async def set_cookies(request: web.Request) -> web.Response:
        response = web.Response()
        response.set_cookie("foo", "bar")
        response.set_cookie("baz", "spam")
        return response

    def wrapper(handler_name: str) -> Callable:

        handler = getattr(handlers, handler_name)

        @wraps(handler)
        async def inner(request: web.Request) -> web.Response:
            await request.read()  # to introspect the payload in tests
            incoming_requests.append(request)
            return await handler(request)

        return inner

    app = web.Application()
    app.add_routes([
        web.get("/schema.yaml", schema),
        web.get("/api/cookies", set_cookies)
    ] + [
        web.route(item.value[0], item.value[1], wrapper(item.name))
        for item in Operation if item.name != "all"
    ])

    async def answer(request: web.Request) -> web.Response:
        return web.json_response(42)

    app.add_routes([web.get("/answer.json", answer)])
    app["users"] = {}
    app["incoming_requests"] = incoming_requests
    app["schema_requests"] = schema_requests
    app["config"] = {
        "should_fail": True,
        "schema_data": make_openapi_schema(operations, version)
    }
    return app
Exemplo n.º 4
0
def reset_app(
    app: web.Application,
    endpoints: Tuple[str, ...] = ("success", "failure"),
    version: OpenAPIVersion = OpenAPIVersion("2.0"),
) -> None:
    """Clean up all internal containers of the application and resets its config."""
    app["users"].clear()
    app["requests_history"].clear()
    app["incoming_requests"][:] = []
    app["schema_requests"][:] = []
    app["config"].update({"should_fail": True, "schema_data": make_openapi_schema(endpoints, version)})
Exemplo n.º 5
0
def create_openapi_app(endpoints: Tuple[str, ...] = ("success", "failure"),
                       version: OpenAPIVersion = OpenAPIVersion(
                           "2.0")) -> Flask:
    app = Flask("test_app")
    app.config["should_fail"] = True
    app.config["schema_data"] = make_openapi_schema(endpoints, version)
    app.config["incoming_requests"] = []
    app.config["schema_requests"] = []
    app.config["internal_exception"] = False
    app.config["users"] = {}

    @app.before_request
    def store_request():
        current_request = _request_ctx_stack.top.request
        if request.path == "/schema.yaml":
            app.config["schema_requests"].append(current_request)
        else:
            app.config["incoming_requests"].append(current_request)

    @app.route("/schema.yaml")
    def schema():
        schema_data = app.config["schema_data"]
        content = yaml.dump(schema_data)
        return Response(content, content_type="text/plain")

    @app.route("/api/success", methods=["GET"])
    def success():
        if app.config["internal_exception"]:
            1 / 0
        return jsonify({"success": True})

    @app.route("/api/recursive", methods=["GET"])
    def recursive():
        return jsonify({"children": [{"children": [{"children": []}]}]})

    @app.route("/api/payload", methods=["POST"])
    def payload():
        return jsonify(request.json)

    @app.route("/api/get_payload", methods=["GET"])
    def get_payload():
        return jsonify(request.json)

    @app.route("/api/headers", methods=["GET"])
    def headers():
        values = dict(request.headers)
        return Response(json.dumps(values),
                        content_type="application/json",
                        headers=values)

    @app.route("/api/failure", methods=["GET"])
    def failure():
        raise InternalServerError

    @app.route("/api/multiple_failures", methods=["GET"])
    def multiple_failures():
        id_value = int(request.args["id"])
        if id_value == 0:
            raise InternalServerError
        if id_value > 0:
            raise GatewayTimeout
        return jsonify({"result": "OK"})

    @app.route("/api/slow", methods=["GET"])
    def slow():
        sleep(0.1)
        return jsonify({"slow": True})

    @app.route("/api/path_variable/<key>", methods=["GET"])
    def path_variable(key):
        return jsonify({"success": True})

    @app.route("/api/unsatisfiable", methods=["POST"])
    def unsatisfiable():
        return jsonify({"result": "IMPOSSIBLE!"})

    @app.route("/api/invalid", methods=["POST"])
    def invalid():
        return jsonify({"success": True})

    @app.route("/api/flaky", methods=["GET"])
    def flaky():
        if app.config["should_fail"]:
            app.config["should_fail"] = False
            raise InternalServerError
        return jsonify({"result": "flaky!"})

    @app.route("/api/multipart", methods=["POST"])
    def multipart():
        files = {
            name: value.stream.read().decode()
            for name, value in request.files.items()
        }
        return jsonify(**files, **request.form.to_dict())

    @app.route("/api/upload_file", methods=["POST"])
    def upload_file():
        return jsonify({"size": request.content_length})

    @app.route("/api/form", methods=["POST"])
    def form():
        if request.headers[
                "Content-Type"] != "application/x-www-form-urlencoded":
            raise InternalServerError("Not an urlencoded request!")
        return jsonify({"size": request.content_length})

    @app.route("/api/teapot", methods=["POST"])
    def teapot():
        return jsonify({"success": True}), 418

    @app.route("/api/text", methods=["GET"])
    def text():
        return Response("Text response", content_type="text/plain")

    @app.route("/api/malformed_json", methods=["GET"])
    def malformed_json():
        return Response("{malformed}", content_type="application/json")

    @app.route("/api/invalid_response", methods=["GET"])
    def invalid_response():
        return jsonify({"random": "key"})

    @app.route("/api/custom_format", methods=["GET"])
    def custom_format():
        return jsonify({"value": request.args["id"]})

    @app.route("/api/invalid_path_parameter/<id>", methods=["GET"])
    def invalid_path_parameter(id):
        return jsonify({"success": True})

    @app.route("/api/users/", methods=["POST"])
    def create_user():
        data = request.json
        user_id = len(app.config["users"]) + 1
        app.config["users"][user_id] = {**data, "id": user_id}
        return jsonify({"id": user_id}), 201

    @app.route("/api/users/<int:user_id>", methods=["GET"])
    def get_user(user_id):
        try:
            user = app.config["users"][user_id]
            return jsonify(user)
        except KeyError:
            return jsonify({"message": "Not found"}), 404

    @app.route("/api/users/<int:user_id>", methods=["PATCH"])
    def update_user(user_id):
        try:
            user = app.config["users"][user_id]
            user["username"] = request.json["username"]
            return jsonify(user)
        except KeyError:
            return jsonify({"message": "Not found"}), 404

    return app
Exemplo n.º 6
0
def create_openapi_app(operations: Tuple[str, ...] = ("success", "failure"),
                       version: OpenAPIVersion = OpenAPIVersion(
                           "2.0")) -> Flask:
    app = Flask("test_app")
    app.config["should_fail"] = True
    app.config["schema_data"] = make_openapi_schema(operations, version)
    app.config["incoming_requests"] = []
    app.config["schema_requests"] = []
    app.config["internal_exception"] = False
    app.config["users"] = {}

    @app.before_request
    def store_request():
        current_request = _request_ctx_stack.top.request
        if request.path == "/schema.yaml":
            app.config["schema_requests"].append(current_request)
        else:
            app.config["incoming_requests"].append(current_request)

    @app.route("/schema.yaml")
    def schema():
        schema_data = app.config["schema_data"]
        content = yaml.dump(schema_data)
        return Response(content, content_type="text/plain")

    @app.route("/api/success", methods=["GET"])
    def success():
        if app.config["internal_exception"]:
            1 / 0
        return jsonify({"success": True})

    @app.route("/api/foo:bar", methods=["GET"])
    def reserved():
        return jsonify({"success": True})

    @app.route("/api/recursive", methods=["GET"])
    def recursive():
        return jsonify({"children": [{"children": [{"children": []}]}]})

    @app.route("/api/payload", methods=["POST"])
    def payload():
        try:
            data = request.json
            try:
                PAYLOAD_VALIDATOR.validate(data)
            except jsonschema.ValidationError:
                return jsonify({"detail": "Validation error"}), 400
        except BadRequest:
            data = {"name": "Nothing!"}
        return jsonify(data)

    @app.route("/api/get_payload", methods=["GET"])
    def get_payload():
        return jsonify(request.json)

    @app.route("/api/basic", methods=["GET"])
    def basic():
        if "Authorization" in request.headers and request.headers[
                "Authorization"] == "Basic dGVzdDp0ZXN0":
            return jsonify({"secret": 42})
        return {"detail": "Unauthorized"}, 401

    @app.route("/api/empty", methods=["GET"])
    def empty():
        return Response(status=204)

    @app.route("/api/empty_string", methods=["GET"])
    def empty_string():
        return Response(response="")

    @app.route("/api/headers", methods=["GET"])
    def headers():
        values = dict(request.headers)
        return Response(json.dumps(values),
                        content_type="application/json",
                        headers=values)

    @app.route("/api/failure", methods=["GET"])
    def failure():
        raise InternalServerError

    @app.route("/api/multiple_failures", methods=["GET"])
    def multiple_failures():
        try:
            id_value = int(request.args["id"])
        except KeyError:
            return jsonify({"detail": "Missing `id`"}), 400
        except ValueError:
            return jsonify({"detail": "Invalid `id`"}), 400
        if id_value == 0:
            raise InternalServerError
        if id_value > 0:
            raise GatewayTimeout
        return jsonify({"result": "OK"})

    @app.route("/api/slow", methods=["GET"])
    def slow():
        sleep(0.1)
        return jsonify({"success": True})

    @app.route("/api/path_variable/<key>", methods=["GET"])
    def path_variable(key):
        return jsonify({"success": True})

    @app.route("/api/unsatisfiable", methods=["POST"])
    def unsatisfiable():
        return jsonify({"result": "IMPOSSIBLE!"})

    @app.route("/api/invalid", methods=["POST"])
    def invalid():
        return jsonify({"success": True})

    @app.route("/api/performance", methods=["POST"])
    def performance():
        data = request.json
        number = str(data).count("0")
        if number > 0:
            sleep(0.01 * number)
        if number > 10:
            raise InternalServerError
        return jsonify({"success": True})

    @app.route("/api/flaky", methods=["GET"])
    def flaky():
        if app.config["should_fail"]:
            app.config["should_fail"] = False
            raise InternalServerError
        return jsonify({"result": "flaky!"})

    @app.route("/api/multipart", methods=["POST"])
    def multipart():
        files = {
            name: value.stream.read().decode()
            for name, value in request.files.items()
        }
        return jsonify(**files, **request.form.to_dict())

    @app.route("/api/upload_file", methods=["POST"])
    def upload_file():
        return jsonify({"size": request.content_length})

    @app.route("/api/form", methods=["POST"])
    def form():
        expect_content_type("application/x-www-form-urlencoded")
        data = request.form
        for field in ("first_name", "last_name"):
            if field not in data:
                return jsonify({"detail": f"Missing `{field}`"}), 400
            if not isinstance(data[field], str):
                return jsonify({"detail": f"Invalid `{field}`"}), 400
        return jsonify({"size": request.content_length})

    @app.route("/api/csv", methods=["POST"])
    def csv_payload():
        expect_content_type("text/csv")
        data = request.get_data(as_text=True)
        if data:
            reader = csv.DictReader(data.splitlines())
            data = list(reader)
        else:
            data = []
        return jsonify(data)

    @app.route("/api/teapot", methods=["POST"])
    def teapot():
        return jsonify({"success": True}), 418

    @app.route("/api/text", methods=["GET"])
    def text():
        return Response("Text response", content_type="text/plain")

    @app.route("/api/cp866", methods=["GET"])
    def cp866():
        # NOTE. Setting `Response.charset` don't have effect in test client as it re-wraps this response with the
        # default one where `charset` is `utf-8`
        return Response("Тест".encode("cp866"),
                        content_type="text/plain;charset=cp866")

    @app.route("/api/text", methods=["POST"])
    def plain_text_body():
        expect_content_type("text/plain")
        return Response(request.data, content_type="text/plain")

    @app.route("/api/malformed_json", methods=["GET"])
    def malformed_json():
        return Response("{malformed}", content_type="application/json")

    @app.route("/api/invalid_response", methods=["GET"])
    def invalid_response():
        return jsonify({"random": "key"})

    @app.route("/api/custom_format", methods=["GET"])
    def custom_format():
        if "id" not in request.args:
            return jsonify({"detail": "Missing `id`"}), 400
        if not request.args["id"].isdigit():
            return jsonify({"detail": "Invalid `id`"}), 400
        return jsonify({"value": request.args["id"]})

    @app.route("/api/invalid_path_parameter/<id>", methods=["GET"])
    def invalid_path_parameter(id):
        return jsonify({"success": True})

    @app.route("/api/users/", methods=["POST"])
    def create_user():
        data = request.json
        for field in ("first_name", "last_name"):
            if field not in data:
                return jsonify({"detail": f"Missing `{field}`"}), 400
            if not isinstance(data[field], str):
                return jsonify({"detail": f"Invalid `{field}`"}), 400
        user_id = str(uuid4())
        app.config["users"][user_id] = {**data, "id": user_id}
        return jsonify({"id": user_id}), 201

    @app.route("/api/users/<user_id>", methods=["GET"])
    def get_user(user_id):
        try:
            user = app.config["users"][user_id]
            # The full name is done specifically via concatenation to trigger a bug when the last name is `None`
            full_name = user["first_name"] + " " + user["last_name"]
            return jsonify({"id": user["id"], "full_name": full_name})
        except KeyError:
            return jsonify({"message": "Not found"}), 404

    @app.route("/api/users/<user_id>", methods=["PATCH"])
    def update_user(user_id):
        try:
            user = app.config["users"][user_id]
            data = request.json
            for field in ("first_name", "last_name"):
                if field not in data:
                    return jsonify({"detail": f"Missing `{field}`"}), 400
                # Here we don't check the input value type to emulate a bug in another operation
                user[field] = data[field]
            return jsonify(user)
        except KeyError:
            return jsonify({"message": "Not found"}), 404

    return app
Exemplo n.º 7
0
def create_openapi_app(
    endpoints: Tuple[str, ...] = ("success", "failure"), version: OpenAPIVersion = OpenAPIVersion("2.0")
) -> Flask:
    app = Flask("test_app")
    app.config["should_fail"] = True
    app.config["schema_data"] = make_openapi_schema(endpoints, version)
    app.config["incoming_requests"] = []
    app.config["schema_requests"] = []
    app.config["internal_exception"] = False
    app.config["users"] = {}
    app.config["requests_history"] = defaultdict(list)

    @app.before_request
    def store_request():
        current_request = _request_ctx_stack.top.request
        if request.path == "/schema.yaml":
            app.config["schema_requests"].append(current_request)
        else:
            app.config["incoming_requests"].append(current_request)

    @app.route("/schema.yaml")
    def schema():
        schema_data = app.config["schema_data"]
        content = yaml.dump(schema_data)
        return Response(content, content_type="text/plain")

    @app.route("/api/success", methods=["GET"])
    def success():
        if app.config["internal_exception"]:
            1 / 0
        return jsonify({"success": True})

    @app.route("/api/recursive", methods=["GET"])
    def recursive():
        return jsonify({"children": [{"children": [{"children": []}]}]})

    @app.route("/api/payload", methods=["POST"])
    def payload():
        try:
            data = request.json
            try:
                PAYLOAD_VALIDATOR.validate(data)
            except jsonschema.ValidationError:
                return jsonify({"detail": "Validation error"}), 400
        except BadRequest:
            data = {"name": "Nothing!"}
        return jsonify(data)

    @app.route("/api/get_payload", methods=["GET"])
    def get_payload():
        return jsonify(request.json)

    @app.route("/api/headers", methods=["GET"])
    def headers():
        values = dict(request.headers)
        return Response(json.dumps(values), content_type="application/json", headers=values)

    @app.route("/api/failure", methods=["GET"])
    def failure():
        raise InternalServerError

    @app.route("/api/multiple_failures", methods=["GET"])
    def multiple_failures():
        try:
            id_value = int(request.args["id"])
        except KeyError:
            return jsonify({"detail": "Missing `id`"}), 400
        except ValueError:
            return jsonify({"detail": "Invalid `id`"}), 400
        if id_value == 0:
            raise InternalServerError
        if id_value > 0:
            raise GatewayTimeout
        return jsonify({"result": "OK"})

    @app.route("/api/slow", methods=["GET"])
    def slow():
        sleep(0.1)
        return jsonify({"success": True})

    @app.route("/api/path_variable/<key>", methods=["GET"])
    def path_variable(key):
        return jsonify({"success": True})

    @app.route("/api/unsatisfiable", methods=["POST"])
    def unsatisfiable():
        return jsonify({"result": "IMPOSSIBLE!"})

    @app.route("/api/invalid", methods=["POST"])
    def invalid():
        return jsonify({"success": True})

    @app.route("/api/performance", methods=["POST"])
    def performance():
        data = request.json
        number = str(data).count("0")
        if number > 0:
            sleep(0.01 * number)
        if number > 10:
            raise InternalServerError
        return jsonify({"success": True})

    @app.route("/api/flaky", methods=["GET"])
    def flaky():
        if app.config["should_fail"]:
            app.config["should_fail"] = False
            raise InternalServerError
        return jsonify({"result": "flaky!"})

    @app.route("/api/multipart", methods=["POST"])
    def multipart():
        files = {name: value.stream.read().decode() for name, value in request.files.items()}
        return jsonify(**files, **request.form.to_dict())

    @app.route("/api/upload_file", methods=["POST"])
    def upload_file():
        return jsonify({"size": request.content_length})

    @app.route("/api/form", methods=["POST"])
    def form():
        expect_content_type("application/x-www-form-urlencoded")
        data = request.form
        for field in ("first_name", "last_name"):
            if field not in data:
                return jsonify({"detail": f"Missing `{field}`"}), 400
            if not isinstance(data[field], str):
                return jsonify({"detail": f"Invalid `{field}`"}), 400
        return jsonify({"size": request.content_length})

    @app.route("/api/csv", methods=["POST"])
    def csv_payload():
        expect_content_type("text/csv")
        data = request.get_data(as_text=True)
        if data:
            reader = csv.DictReader(data.splitlines())
            data = list(reader)
        else:
            data = []
        return jsonify(data)

    @app.route("/api/teapot", methods=["POST"])
    def teapot():
        return jsonify({"success": True}), 418

    @app.route("/api/text", methods=["GET"])
    def text():
        return Response("Text response", content_type="text/plain")

    @app.route("/api/text", methods=["POST"])
    def plain_text_body():
        expect_content_type("text/plain")
        return Response(request.data, content_type="text/plain")

    @app.route("/api/malformed_json", methods=["GET"])
    def malformed_json():
        return Response("{malformed}", content_type="application/json")

    @app.route("/api/invalid_response", methods=["GET"])
    def invalid_response():
        return jsonify({"random": "key"})

    @app.route("/api/custom_format", methods=["GET"])
    def custom_format():
        if "id" not in request.args:
            return jsonify({"detail": "Missing `id`"}), 400
        if not request.args["id"].isdigit():
            return jsonify({"detail": "Invalid `id`"}), 400
        return jsonify({"value": request.args["id"]})

    @app.route("/api/invalid_path_parameter/<id>", methods=["GET"])
    def invalid_path_parameter(id):
        return jsonify({"success": True})

    @app.route("/api/users/", methods=["POST"])
    def create_user():
        data = request.json
        if "username" not in data:
            return jsonify({"detail": "Missing `username`"}), 400
        if not isinstance(data["username"], str):
            return jsonify({"detail": "Invalid `username`"}), 400
        user_id = len(app.config["users"]) + 1
        app.config["users"][user_id] = {**data, "id": user_id}
        app.config["requests_history"][user_id].append("POST")
        return jsonify({"id": user_id}), 201

    @app.route("/api/users/<int:user_id>", methods=["GET"])
    def get_user(user_id):
        try:
            user = app.config["users"][user_id]
            app.config["requests_history"][user_id].append("GET")
            return jsonify(user)
        except KeyError:
            return jsonify({"message": "Not found"}), 404

    @app.route("/api/users/<int:user_id>", methods=["PATCH"])
    def update_user(user_id):
        try:
            user = app.config["users"][user_id]
            history = app.config["requests_history"][user_id]
            history.append("PATCH")
            if history == ["POST", "GET", "PATCH", "GET", "PATCH"]:
                raise InternalServerError("We got a problem!")
            data = request.json
            if "username" not in data:
                return jsonify({"detail": "Missing `username`"}), 400
            if not isinstance(data["username"], str):
                return jsonify({"detail": "Invalid `username`"}), 400
            user["username"] = data["username"]
            return jsonify(user)
        except KeyError:
            return jsonify({"message": "Not found"}), 404

    return app