import flask import pytest from json import dumps, JSONEncoder from flask import Blueprint, redirect, views from werkzeug.exceptions import HTTPException, Unauthorized, BadRequest import flask_restx as restx # Add a dummy Resource to verify that the app is properly set. class HelloWorld(restx.Resource): def get(self): return {} class APITest(object): def test_unauthorized_no_challenge_by_default(self, api, mocker): response = mocker.Mock() response.headers = {} response = api.unauthorized(response) assert "WWW-Authenticate" not in response.headers @pytest.mark.api(serve_challenge_on_401=True) def test_unauthorized(self, api, mocker): response = mocker.Mock() response.headers = {} response = api.unauthorized(response) assert response.headers["WWW-Authenticate"] == 'Basic realm="flask-restx"' @pytest.mark.options(HTTP_BASIC_AUTH_REALM="Foo") @pytest.mark.api(serve_challenge_on_401=True) def test_unauthorized_custom_realm(self, api, mocker): response = mocker.Mock() response.headers = {} response = api.unauthorized(response) assert response.headers["WWW-Authenticate"] == 'Basic realm="Foo"' def test_handle_error_401_no_challenge_by_default(self, api): resp = api.handle_error(Unauthorized()) assert resp.status_code == 401 assert "WWW-Autheneticate" not in resp.headers @pytest.mark.api(serve_challenge_on_401=True) def test_handle_error_401_sends_challege_default_realm(self, api): exception = HTTPException() exception.code = 401 exception.data = {"foo": "bar"} resp = api.handle_error(exception) assert resp.status_code == 401 assert resp.headers["WWW-Authenticate"] == 'Basic realm="flask-restx"' @pytest.mark.api(serve_challenge_on_401=True) @pytest.mark.options(HTTP_BASIC_AUTH_REALM="test-realm") def test_handle_error_401_sends_challege_configured_realm(self, api): resp = api.handle_error(Unauthorized()) assert resp.status_code == 401 assert resp.headers["WWW-Authenticate"] == 'Basic realm="test-realm"' def test_handle_error_does_not_swallow_exceptions(self, api): exception = BadRequest("x") resp = api.handle_error(exception) assert resp.status_code == 400 assert resp.get_data() == b'{"message": "x"}\n' def test_api_representation(self, api): @api.representation("foo") def foo(): pass assert api.representations["foo"] == foo def test_api_base(self, app): api = restx.Api(app) assert api.urls == {} assert api.prefix == "" assert api.default_mediatype == "application/json" def test_api_delayed_initialization(self, app, client): api = restx.Api() api.add_resource(HelloWorld, "/", endpoint="hello") api.init_app(app) assert client.get("/").status_code == 200 def test_api_prefix(self, app): api = restx.Api(app, prefix="/foo") assert api.prefix == "/foo" @pytest.mark.api(serve_challenge_on_401=True) def test_handle_auth(self, api): resp = api.handle_error(Unauthorized()) assert resp.status_code == 401 expected_data = dumps({"message": Unauthorized.description}) + "\n" assert resp.data.decode() == expected_data assert "WWW-Authenticate" in resp.headers def test_media_types(self, app): api = restx.Api(app) with app.test_request_context("/foo", headers={"Accept": "application/json"}): assert api.mediatypes() == ["application/json"] def test_media_types_method(self, app, mocker): api = restx.Api(app) with app.test_request_context( "/foo", headers={"Accept": "application/xml; q=0.5"} ): assert api.mediatypes_method()(mocker.Mock()) == [ "application/xml", "application/json", ] def test_media_types_q(self, app): api = restx.Api(app) with app.test_request_context( "/foo", headers={"Accept": "application/json; q=1.0, application/xml; q=0.5"}, ): assert api.mediatypes() == ["application/json", "application/xml"] def test_decorator(self, mocker, mock_app): def return_zero(func): return 0 view = mocker.Mock() api = restx.Api(mock_app) api.decorators.append(return_zero) api.output = mocker.Mock() api.add_resource(view, "/foo", endpoint="bar") mock_app.add_url_rule.assert_called_with("/foo", view_func=0) def test_add_resource_endpoint(self, app, mocker): view = mocker.Mock(**{"as_view.return_value.__name__": str("test_view")}) api = restx.Api(app) api.add_resource(view, "/foo", endpoint="bar") view.as_view.assert_called_with("bar", api) def test_add_two_conflicting_resources_on_same_endpoint(self, app): api = restx.Api(app) class Foo1(restx.Resource): def get(self): return "foo1" class Foo2(restx.Resource): def get(self): return "foo2" api.add_resource(Foo1, "/foo", endpoint="bar") with pytest.raises(ValueError): api.add_resource(Foo2, "/foo/toto", endpoint="bar") def test_add_the_same_resource_on_same_endpoint(self, app): api = restx.Api(app) class Foo1(restx.Resource): def get(self): return "foo1" api.add_resource(Foo1, "/foo", endpoint="bar") api.add_resource(Foo1, "/foo/toto", endpoint="blah") with app.test_client() as client: foo1 = client.get("/foo") assert foo1.data == b'"foo1"\n' foo2 = client.get("/foo/toto") assert foo2.data == b'"foo1"\n' 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_add_resource_kwargs(self, mocker, mock_app): api = restx.Api(mock_app) api.output = mocker.Mock() api.add_resource(views.MethodView, "/foo", defaults={"bar": "baz"}) mock_app.add_url_rule.assert_called_with( "/foo", view_func=api.output(), defaults={"bar": "baz"} ) def test_add_resource_forward_resource_class_parameters(self, app, client): api = restx.Api(app) class Foo(restx.Resource): def __init__(self, api, *args, **kwargs): self.one = args[0] self.two = kwargs["secret_state"] super(Foo, self).__init__(api, *args, **kwargs) def get(self): return "{0} {1}".format(self.one, self.two) api.add_resource( Foo, "/foo", resource_class_args=("wonderful",), resource_class_kwargs={"secret_state": "slurm"}, ) foo = client.get("/foo") assert foo.data == b'"wonderful slurm"\n' def test_output_unpack(self, app): def make_empty_response(): return {"foo": "bar"} api = restx.Api(app) with app.test_request_context("/foo"): wrapper = api.output(make_empty_response) resp = wrapper() assert resp.status_code == 200 assert resp.data.decode() == '{"foo": "bar"}\n' def test_output_func(self, app): def make_empty_resposne(): return flask.make_response("") api = restx.Api(app) with app.test_request_context("/foo"): wrapper = api.output(make_empty_resposne) resp = wrapper() assert resp.status_code == 200 assert resp.data.decode() == "" def test_resource(self, app, mocker): resource = restx.Resource() resource.get = mocker.Mock() with app.test_request_context("/foo"): resource.dispatch_request() def test_resource_resp(self, app, mocker): resource = restx.Resource() resource.get = mocker.Mock() with app.test_request_context("/foo"): resource.get.return_value = flask.make_response("") resource.dispatch_request() def test_resource_text_plain(self, app): def text(data, code, headers=None): return flask.make_response(str(data)) class Foo(restx.Resource): representations = { "text/plain": text, } def get(self): return "hello" with app.test_request_context("/foo", headers={"Accept": "text/plain"}): resource = Foo(None) resp = resource.dispatch_request() assert resp.data.decode() == "hello" @pytest.mark.request_context("/foo") def test_resource_error(self, app): resource = restx.Resource() with pytest.raises(AssertionError): resource.dispatch_request() @pytest.mark.request_context("/foo", method="HEAD") def test_resource_head(self, app): resource = restx.Resource() with pytest.raises(AssertionError): resource.dispatch_request() def test_endpoints(self, app): api = restx.Api(app) api.add_resource(HelloWorld, "/ids/", endpoint="hello") with app.test_request_context("/foo"): assert api._has_fr_route() is False with app.test_request_context("/ids/3"): assert api._has_fr_route() is True def test_url_for(self, app): api = restx.Api(app) api.add_resource(HelloWorld, "/ids/") with app.test_request_context("/foo"): assert api.url_for(HelloWorld, id=123) == "/ids/123" def test_url_for_with_blueprint(self, app): """Verify that url_for works when an Api object is mounted on a Blueprint. """ api_bp = Blueprint("api", __name__) api = restx.Api(api_bp) api.add_resource(HelloWorld, "/foo/") app.register_blueprint(api_bp) with app.test_request_context("/foo"): assert api.url_for(HelloWorld, bar="baz") == "/foo/baz" def test_exception_header_forwarding_doesnt_duplicate_headers(self, api): """Test that HTTPException's headers do not add a duplicate Content-Length header https://github.com/flask-restful/flask-restful/issues/534 """ r = api.handle_error(BadRequest()) assert len(r.headers.getlist("Content-Length")) == 1 def test_read_json_settings_from_config(self, app, client): class TestConfig(object): RESTX_JSON = {"indent": 2, "sort_keys": True, "separators": (", ", ": ")} app.config.from_object(TestConfig) api = restx.Api(app) class Foo(restx.Resource): def get(self): return {"foo": "bar", "baz": "qux"} api.add_resource(Foo, "/foo") data = client.get("/foo").data expected = b'{\n "baz": "qux", \n "foo": "bar"\n}\n' assert data == expected def test_use_custom_jsonencoder(self, app, client): class CabageEncoder(JSONEncoder): def default(self, obj): return "cabbage" class TestConfig(object): RESTX_JSON = {"cls": CabageEncoder} app.config.from_object(TestConfig) api = restx.Api(app) class Cabbage(restx.Resource): def get(self): return {"frob": object()} api.add_resource(Cabbage, "/cabbage") data = client.get("/cabbage").data expected = b'{"frob": "cabbage"}\n' assert data == expected def test_json_with_no_settings(self, api, client): class Foo(restx.Resource): def get(self): return {"foo": "bar"} api.add_resource(Foo, "/foo") data = client.get("/foo").data expected = b'{"foo": "bar"}\n' assert data == expected def test_redirect(self, api, client): class FooResource(restx.Resource): def get(self): return redirect("/") api.add_resource(FooResource, "/api") resp = client.get("/api") assert resp.status_code == 302 # FIXME: The behavior changed somewhere between Flask 2.0.3 and 2.2.x assert resp.headers["Location"].endswith("/") def test_calling_owns_endpoint_before_api_init(self): api = restx.Api() api.owns_endpoint("endpoint") # with pytest.raises(AttributeError): # try: # except AttributeError as ae: # self.fail(ae.message)