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

1from django.urls import reverse 

2from rest_framework.test import APIClient 

3from rest_framework_api_key.permissions import HasAPIKey 

4 

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 

10 

11 

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) 

19 

20 def setup(self): 

21 error_msg = "Setup function not implemented" 

22 raise NotImplementedError(error_msg) 

23 

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()]) 

31 

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) 

40 

41 def authenticate(self): 

42 self.headers["Authorization"] = f"Api-Key {self.key}" 

43 

44 def logout(self): 

45 self.headers.pop("Authorization") 

46 

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) 

62 

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) 

71 

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) 

76 

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) 

83 

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]) 

94 

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}) 

105 

106 if HasAPIKey in permissions: 

107 self.check_auth(name="HasAPIKey with no key", mode=mode, error_list=error_list) 

108 

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) 

114 

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) 

123 

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) 

135 

136 self.build_key(permissions) 

137 self.authenticate() 

138 

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) 

147 

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) 

155 

156 if len(error_list) > 0: 

157 error_msg = f"Errors in {mode} mode" 

158 raise ExceptionGroup(error_msg, error_list) 

159 

160 

161class ViewTest: 

162 def __init__(self, testcase_instance: APITestCase, *args, **kwargs): 

163 self.testcase_instance = testcase_instance 

164 

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 ) 

173 

174 return _setup 

175 

176 def run(self) -> list[dict[str, any]]: 

177 raise NotImplementedError