Coverage for django_napse/utils/api_test_case.py: 39%
136 statements
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-12 13:49 +0000
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-12 13:49 +0000
1from django.urls import reverse
2from rest_framework.test import APIClient
3from rest_framework_api_key.permissions import HasAPIKey
5from django_napse.api.custom_permissions import HasAdminPermission, HasFullAccessPermission, HasMasterKey, HasReadPermission, HasSpace
6from django_napse.auth.models import KeyPermission
7from django_napse.auth.models.keys.key import NapseAPIKey
8from django_napse.utils.constants import PERMISSION_TYPES
9from django_napse.utils.custom_test_case import CustomTestCase
12class APITestCase(CustomTestCase):
13 def setUp(self):
14 self.client = APIClient()
15 self.modes = self.setup()
16 if self.modes == {} or not isinstance(self.modes, dict):
17 error_msg = "Modes not defined"
18 raise ValueError(error_msg)
20 def setup(self):
21 error_msg = "Setup function not implemented"
22 raise NotImplementedError(error_msg)
24 def build_url(self, kwargs=None):
25 if kwargs and kwargs.get("pk", None):
26 self.url = reverse(viewname=self.url_name, kwargs={"pk": kwargs.pop("pk")})
27 else:
28 self.url = reverse(viewname=self.url_name)
29 if kwargs:
30 self.url += "?" + "&".join([f"{key}={value}" for key, value in kwargs.items()])
32 def build_key(self, permissions):
33 key_obj, self.key = NapseAPIKey.objects.create_key(name="test")
34 if HasReadPermission in permissions:
35 KeyPermission.objects.create(key=key_obj, space=self.space, permission_type=PERMISSION_TYPES.READ_ONLY, approved=True)
36 if HasFullAccessPermission in permissions:
37 KeyPermission.objects.create(key=key_obj, space=self.space, permission_type=PERMISSION_TYPES.FULL_ACCESS, approved=True)
38 if HasAdminPermission in permissions:
39 KeyPermission.objects.create(key=key_obj, space=self.space, permission_type=PERMISSION_TYPES.ADMIN, approved=True)
41 def authenticate(self):
42 self.headers["Authorization"] = f"Api-Key {self.key}"
44 def logout(self):
45 self.headers.pop("Authorization")
47 def request(self, method):
48 match method:
49 case "GET":
50 return self.client.get(path=self.url, data=self.data, headers=self.headers)
51 case "POST":
52 return self.client.post(path=self.url, data=self.data, headers=self.headers)
53 case "PUT":
54 return self.client.put(path=self.url, data=self.data, headers=self.headers)
55 case "PATCH":
56 return self.client.patch(path=self.url, data=self.data, headers=self.headers)
57 case "DELETE":
58 return self.client.delete(path=self.url, data=self.data, headers=self.headers)
59 case _:
60 error_msg = f"Unknown method: {method}"
61 raise ValueError(error_msg)
63 def check_auth(self, name: str, mode: str, error_list: list, divider: int = 1, expected: int = 403) -> None:
64 response = self.request(self.modes[mode]["method"])
65 try:
66 self.assertEqual(response.status_code // divider, expected)
67 except AssertionError as e:
68 e.add_note(f"{name} test failed (no permissions didn't return 403)")
69 e.add_note(str(response.data))
70 error_list.append(e)
72 def run_tests(self, mode):
73 if mode not in self.modes:
74 error_msg = f"Unknown mode: {mode}"
75 raise ValueError(error_msg)
77 error_list = []
78 self.kwargs = self.modes[mode].get("kwargs", {})
79 self.data = self.modes[mode].get("data", {})
80 self.headers = self.modes[mode].get("headers", {})
81 self.url_name = self.modes[mode]["url_name"]
82 self.build_url(kwargs={"pk": 1} if self.modes[mode]["method"] in ["PATCH", "PUT", "DELETE"] and not self.kwargs else self.kwargs)
84 permissions = self.modes[mode]["view"].permission_classes
85 permission_importance = {
86 HasSpace: 0,
87 HasAPIKey: 1,
88 HasReadPermission: 2,
89 HasFullAccessPermission: 3,
90 HasAdminPermission: 4,
91 HasMasterKey: 5,
92 }
93 permissions.sort(key=lambda x: permission_importance[x])
95 if permissions == []:
96 self.check_auth(name="No permissions", mode=mode, error_list=error_list, divider=100, expected=2)
97 else:
98 if HasSpace in permissions:
99 self.check_auth(name="HasSpace with no key", mode=mode, error_list=error_list, expected=400)
100 self.build_url(kwargs={"space": str("random uuid"), **self.kwargs})
101 self.check_auth(name="HasSpace with no key", mode=mode, error_list=error_list, expected=400)
102 self.build_url(kwargs={"space": str("7aafc68d-f619-4874-aaf5-c123a176e303"), **self.kwargs})
103 self.check_auth(name="HasSpace with no key", mode=mode, error_list=error_list, expected=400)
104 self.build_url(kwargs={"space": str(self.space.uuid), **self.kwargs})
106 if HasAPIKey in permissions:
107 self.check_auth(name="HasAPIKey with no key", mode=mode, error_list=error_list)
109 if HasReadPermission in permissions:
110 self.check_auth(name="HasReadPermission with no key", mode=mode, error_list=error_list)
111 self.build_key([])
112 self.authenticate()
113 self.check_auth(name="HasReadPermission with no permissions", mode=mode, error_list=error_list)
115 if HasFullAccessPermission in permissions:
116 self.check_auth(name="HasFullAccessPermission with no key", mode=mode, error_list=error_list)
117 self.build_key([])
118 self.authenticate()
119 self.check_auth(name="HasFullAccessPermission with no permissions", mode=mode, error_list=error_list)
120 self.build_key([HasReadPermission])
121 self.authenticate()
122 self.check_auth(name="HasFullAccessPermission with read permissions", mode=mode, error_list=error_list)
124 if HasAdminPermission in permissions:
125 self.check_auth(name="HasAdminPermission with no key", mode=mode, error_list=error_list)
126 self.build_key([])
127 self.authenticate()
128 self.check_auth(name="HasAdminPermission with no permissions", mode=mode, error_list=error_list)
129 self.build_key([HasReadPermission])
130 self.authenticate()
131 self.check_auth(name="HasAdminPermission with read permissions", mode=mode, error_list=error_list)
132 self.build_key([HasReadPermission, HasFullAccessPermission])
133 self.authenticate()
134 self.check_auth(name="HasAdminPermission with read and full access permissions", mode=mode, error_list=error_list)
136 self.build_key(permissions)
137 self.authenticate()
139 for test in self.modes[mode]["tests"]:
140 response = test["setup"]()
141 try:
142 self.assertEqual(test["status_code"], response.status_code)
143 except AssertionError as e:
144 e.add_note(f"Test: {test['name']} | {test['setup'].__name__} | status code")
145 e.add_note(str(response.data))
146 error_list.append(e)
148 for check in test["checks"]:
149 try:
150 self.assertTrue(check(response))
151 except Exception as e:
152 e.add_note(f"Test: {test['name']} | {test['setup'].__name__} | check {check.__name__}")
153 e.add_note(str(response.data))
154 error_list.append(e)
156 if len(error_list) > 0:
157 error_msg = f"Errors in {mode} mode"
158 raise ExceptionGroup(error_msg, error_list)
161class ViewTest:
162 def __init__(self, testcase_instance: APITestCase, *args, **kwargs):
163 self.testcase_instance = testcase_instance
165 def setup(self, data: dict | None = None):
166 def _setup(data=data):
167 # TODO: Add other methods (post, put, patch, delete)
168 return self.testcase_instance.client.get(
169 path=self.testcase_instance.url,
170 data=data,
171 headers=self.testcase_instance.headers,
172 )
174 return _setup
176 def run(self) -> list[dict[str, any]]:
177 raise NotImplementedError