Skip to content

Commit 9518c72

Browse files
committed
Added feature tests
1 parent 5400588 commit 9518c72

File tree

7 files changed

+469
-12
lines changed

7 files changed

+469
-12
lines changed

tests/test_async_auth.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import django
2+
import pytest
3+
from ninja.testing import TestAsyncClient
4+
5+
from ninja_extra import NinjaExtraAPI
6+
from ninja_extra.security import (
7+
AsyncAPIKeyCookie,
8+
AsyncAPIKeyHeader,
9+
AsyncAPIKeyQuery,
10+
AsyncHttpBasicAuth,
11+
AsyncHttpBearer,
12+
async_django_auth,
13+
)
14+
15+
16+
async def callable_auth(request):
17+
return request.GET.get("auth")
18+
19+
20+
class KeyQuery(AsyncAPIKeyQuery):
21+
async def authenticate(self, request, key):
22+
if key == "keyquerysecret":
23+
return key
24+
25+
26+
class KeyHeader(AsyncAPIKeyHeader):
27+
async def authenticate(self, request, key):
28+
if key == "keyheadersecret":
29+
return key
30+
31+
32+
class KeyCookie(AsyncAPIKeyCookie):
33+
async def authenticate(self, request, key):
34+
if key == "keycookiersecret":
35+
return key
36+
37+
38+
class BasicAuth(AsyncHttpBasicAuth):
39+
async def authenticate(self, request, username, password):
40+
if username == "admin" and password == "secret":
41+
return username
42+
43+
44+
class BearerAuth(AsyncHttpBearer):
45+
async def authenticate(self, request, token):
46+
if token == "bearertoken":
47+
return token
48+
49+
50+
async def demo_operation(request):
51+
return {"auth": request.auth}
52+
53+
54+
api = NinjaExtraAPI(csrf=True, urls_namespace="async_auth")
55+
56+
for path, auth in [
57+
("django_auth", async_django_auth),
58+
("callable", callable_auth),
59+
("apikeyquery", KeyQuery()),
60+
("apikeyheader", KeyHeader()),
61+
("apikeycookie", KeyCookie()),
62+
("basic", BasicAuth()),
63+
("bearer", BearerAuth()),
64+
]:
65+
api.get(f"/{path}", auth=auth, operation_id=path)(demo_operation)
66+
67+
68+
client = TestAsyncClient(api)
69+
70+
71+
class MockUser(str):
72+
is_authenticated = True
73+
74+
75+
BODY_UNAUTHORIZED_DEFAULT = dict(detail="Unauthorized")
76+
77+
78+
@pytest.mark.skipif(django.VERSION < (3, 1), reason="requires django 3.1 or higher")
79+
@pytest.mark.asyncio
80+
@pytest.mark.parametrize(
81+
"path,kwargs,expected_code,expected_body",
82+
[
83+
("/django_auth", {}, 401, BODY_UNAUTHORIZED_DEFAULT),
84+
("/django_auth", dict(user=MockUser("admin")), 200, dict(auth="admin")),
85+
("/callable", {}, 401, BODY_UNAUTHORIZED_DEFAULT),
86+
("/callable?auth=demo", {}, 200, dict(auth="demo")),
87+
("/apikeyquery", {}, 401, BODY_UNAUTHORIZED_DEFAULT),
88+
("/apikeyquery?key=keyquerysecret", {}, 200, dict(auth="keyquerysecret")),
89+
("/apikeyheader", {}, 401, BODY_UNAUTHORIZED_DEFAULT),
90+
(
91+
"/apikeyheader",
92+
dict(headers={"key": "keyheadersecret"}),
93+
200,
94+
dict(auth="keyheadersecret"),
95+
),
96+
("/apikeycookie", {}, 401, BODY_UNAUTHORIZED_DEFAULT),
97+
(
98+
"/apikeycookie",
99+
dict(COOKIES={"key": "keycookiersecret"}),
100+
200,
101+
dict(auth="keycookiersecret"),
102+
),
103+
("/basic", {}, 401, BODY_UNAUTHORIZED_DEFAULT),
104+
(
105+
"/basic",
106+
dict(headers={"Authorization": "Basic YWRtaW46c2VjcmV0"}),
107+
200,
108+
dict(auth="admin"),
109+
),
110+
(
111+
"/basic",
112+
dict(headers={"Authorization": "YWRtaW46c2VjcmV0"}),
113+
200,
114+
dict(auth="admin"),
115+
),
116+
(
117+
"/basic",
118+
dict(headers={"Authorization": "Basic invalid"}),
119+
401,
120+
BODY_UNAUTHORIZED_DEFAULT,
121+
),
122+
(
123+
"/basic",
124+
dict(headers={"Authorization": "some invalid value"}),
125+
401,
126+
BODY_UNAUTHORIZED_DEFAULT,
127+
),
128+
("/bearer", {}, 401, BODY_UNAUTHORIZED_DEFAULT),
129+
(
130+
"/bearer",
131+
dict(headers={"Authorization": "Bearer bearertoken"}),
132+
200,
133+
dict(auth="bearertoken"),
134+
),
135+
(
136+
"/bearer",
137+
dict(headers={"Authorization": "Invalid bearertoken"}),
138+
401,
139+
BODY_UNAUTHORIZED_DEFAULT,
140+
),
141+
],
142+
)
143+
async def test_auth(path, kwargs, expected_code, expected_body, settings):
144+
for debug in (False, True):
145+
settings.DEBUG = debug
146+
response = await client.get(path, **kwargs)
147+
assert response.status_code == expected_code
148+
assert response.json() == expected_body

tests/test_controller.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
from unittest.mock import Mock, patch
22

3+
import django
34
import pytest
45
from django.contrib.auth.models import Group
56

67
from ninja_extra import NinjaExtraAPI, api_controller, exceptions, http_get, testing
78
from ninja_extra.controllers import ControllerBase, RouteContext, RouteFunction
89
from ninja_extra.controllers.base import (
910
APIController,
10-
MissingAPIControllerDecoratorException,
1111
compute_api_route_function,
1212
get_route_functions,
1313
)
1414
from ninja_extra.controllers.response import Detail, Id, Ok
1515
from ninja_extra.permissions.common import AllowAny
1616

17+
from .utils import AsyncFakeAuth, FakeAuth
18+
1719

1820
@api_controller
1921
class SomeController:
@@ -57,11 +59,16 @@ class DisableAutoImportController:
5759

5860
class TestAPIController:
5961
def test_api_controller_as_decorator(self):
60-
api_controller_instance = api_controller("prefix", tags="new_tag")
62+
api_controller_instance = api_controller(
63+
"prefix", tags="new_tag", auth=FakeAuth()
64+
)
6165

66+
assert not api_controller_instance.has_auth_async
67+
assert not api_controller_instance._prefix_has_route_param
6268
assert api_controller_instance.prefix == "prefix"
6369
assert api_controller_instance.tags == ["new_tag"]
6470
assert api_controller_instance.permission_classes == [AllowAny]
71+
6572
api_controller_instance = api_controller()
6673
assert api_controller_instance.prefix == ""
6774
assert api_controller_instance.tags is None
@@ -183,6 +190,35 @@ def test_controller_base_get_object_or_none_works(self):
183190
assert isinstance(ex, exceptions.PermissionDenied)
184191

185192

193+
@pytest.mark.skipif(django.VERSION < (3, 1), reason="requires django 3.1 or higher")
194+
def test_async_controller():
195+
api_controller_instance = api_controller(
196+
"prefix", tags="any_Tag", auth=AsyncFakeAuth()
197+
)
198+
assert api_controller_instance.has_auth_async
199+
200+
with pytest.raises(Exception) as ex:
201+
202+
@api_controller_instance
203+
class NonAsyncRouteInControllerWithAsyncAuth:
204+
@http_get("/example")
205+
def example(self):
206+
pass
207+
208+
assert "NonAsyncRouteInControllerWithAsyncAuth" in str(ex) and "example" in str(ex)
209+
210+
@api_controller_instance
211+
class AsyncRouteInControllerWithAsyncAuth:
212+
@http_get("/example")
213+
async def example(self):
214+
pass
215+
216+
assert isinstance(
217+
AsyncRouteInControllerWithAsyncAuth.example.operation.auth_callbacks[0],
218+
AsyncFakeAuth,
219+
)
220+
221+
186222
class TestAPIControllerResponse:
187223
ok_response = Ok("OK")
188224
id_response = Id("ID")

tests/test_django_ninja_router.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import pytest
2+
from ninja.testing import TestClient
3+
4+
from ninja_extra import NinjaExtraAPI, Router
5+
from ninja_extra.operation import PathView
6+
7+
api = NinjaExtraAPI(urls_namespace="ninja_router")
8+
9+
10+
@api.get("/endpoint")
11+
# view->api
12+
def global_op(request):
13+
return "global"
14+
15+
16+
first_router = Router()
17+
18+
19+
@first_router.get("/endpoint_1")
20+
# view->router, router->api
21+
def router_op1(request):
22+
return "first 1"
23+
24+
25+
second_router_one = Router()
26+
27+
28+
@second_router_one.get("endpoint_1")
29+
# view->router2, router2->router1, router1->api
30+
def router_op2(request):
31+
return "second 1"
32+
33+
34+
second_router_two = Router()
35+
36+
37+
@second_router_two.get("endpoint_2")
38+
# view->router2, router2->router1, router1->api
39+
def router2_op3(request):
40+
return "second 2"
41+
42+
43+
first_router.add_router("/second", second_router_one, tags=["one"])
44+
first_router.add_router("/second", second_router_two, tags=["two"])
45+
api.add_router("/first", first_router, tags=["global"])
46+
47+
48+
@first_router.get("endpoint_2")
49+
# router->api, view->router
50+
def router1_op1(request):
51+
return "first 2"
52+
53+
54+
@second_router_one.get("endpoint_3")
55+
# router2->router1, router1->api, view->router2
56+
def router21_op3(request, path_param: int = None):
57+
return "second 3" if path_param is None else f"second 3: {path_param}"
58+
59+
60+
second_router_three = Router()
61+
62+
63+
@second_router_three.get("endpoint_4")
64+
# router1->api, view->router2, router2->router1
65+
def router_op3(request, path_param: int = None):
66+
return "second 4" if path_param is None else f"second 4: {path_param}"
67+
68+
69+
first_router.add_router("/second", second_router_three, tags=["three"])
70+
71+
72+
client = TestClient(api)
73+
74+
75+
@pytest.mark.parametrize(
76+
"path,expected_status,expected_response",
77+
[
78+
("/endpoint", 200, "global"),
79+
("/first/endpoint_1", 200, "first 1"),
80+
("/first/endpoint_2", 200, "first 2"),
81+
("/first/second/endpoint_1", 200, "second 1"),
82+
("/first/second/endpoint_2", 200, "second 2"),
83+
("/first/second/endpoint_3", 200, "second 3"),
84+
("/first/second/endpoint_4", 200, "second 4"),
85+
],
86+
)
87+
def test_inheritance_responses(path, expected_status, expected_response):
88+
response = client.get(path)
89+
assert response.status_code == expected_status, response.content
90+
assert response.json() == expected_response
91+
92+
93+
def test_router_path_view():
94+
router_op1_path_view = first_router.path_operations.get("/endpoint_1")
95+
assert router_op1_path_view
96+
assert isinstance(router_op1_path_view, PathView)
97+
98+
global_op_path_view = api.default_router.path_operations.get("/endpoint")
99+
assert router_op1_path_view
100+
assert isinstance(global_op_path_view, PathView)

tests/test_operation.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
import pytest
33

44
from ninja_extra import api_controller, route
5+
from ninja_extra.controllers import AsyncRouteFunction, RouteFunction
6+
from ninja_extra.operation import AsyncControllerOperation, ControllerOperation
57
from ninja_extra.testing import TestAsyncClient, TestClient
68

7-
from .utils import mock_log_call, mock_signal_call
9+
from .utils import AsyncFakeAuth, FakeAuth, mock_log_call, mock_signal_call
810

911

1012
class TestOperation:
@@ -35,6 +37,40 @@ def test_route_operation_execution_should_log_execution(self):
3537
client.get(str(self.SomeTestController.example_exception))
3638

3739

40+
@pytest.mark.skipif(django.VERSION < (3, 1), reason="requires django 3.1 or higher")
41+
def test_operation_auth_configs():
42+
api_controller_instance = api_controller("prefix", tags="any_Tag")
43+
44+
async def async_endpoint(self, request):
45+
pass
46+
47+
def sync_endpoint(self, request):
48+
pass
49+
50+
sync_auth_http_get = route.get("/example", auth=[FakeAuth()])
51+
async_auth_http_get = route.get("/example/async", auth=[AsyncFakeAuth()])
52+
53+
route_function = sync_auth_http_get(async_endpoint)
54+
assert isinstance(route_function, AsyncRouteFunction)
55+
async_route_function = async_auth_http_get(async_endpoint)
56+
57+
api_controller_instance.add_operation_from_route_function(route_function)
58+
assert isinstance(route_function.operation, AsyncControllerOperation)
59+
api_controller_instance.add_operation_from_route_function(async_route_function)
60+
assert isinstance(async_route_function.operation, AsyncControllerOperation)
61+
62+
sync_route_function = sync_auth_http_get(sync_endpoint)
63+
api_controller_instance.add_operation_from_route_function(sync_route_function)
64+
assert isinstance(sync_route_function.operation, ControllerOperation)
65+
assert isinstance(sync_route_function, RouteFunction)
66+
67+
with pytest.raises(Exception) as ex:
68+
api_controller_instance.add_operation_from_route_function(
69+
async_auth_http_get(sync_endpoint)
70+
)
71+
assert "sync_endpoint" in str(ex) and "AsyncFakeAuth" in str(ex)
72+
73+
3874
@pytest.mark.skipif(django.VERSION < (3, 1), reason="requires django 3.1 or higher")
3975
@pytest.mark.asyncio
4076
class TestAsyncOperations:

0 commit comments

Comments
 (0)