authentik.enterprise.lifecycle.tests.test_api
1from django.contrib.contenttypes.models import ContentType 2from django.urls import reverse 3from rest_framework.test import APITestCase 4 5from authentik.core.models import Application, Group 6from authentik.core.tests.utils import create_test_admin_user, create_test_user 7from authentik.enterprise.lifecycle.models import LifecycleIteration, LifecycleRule, ReviewState 8from authentik.enterprise.reports.tests.utils import patch_license 9from authentik.lib.generators import generate_id 10 11 12@patch_license 13class TestLifecycleRuleAPI(APITestCase): 14 15 def setUp(self): 16 self.user = create_test_admin_user() 17 self.client.force_login(self.user) 18 self.app = Application.objects.create(name=generate_id(), slug=generate_id()) 19 self.content_type = ContentType.objects.get_for_model(Application) 20 self.reviewer_group = Group.objects.create(name=generate_id()) 21 22 def test_list_rules(self): 23 rule = LifecycleRule.objects.create( 24 name=generate_id(), 25 content_type=self.content_type, 26 object_id=str(self.app.pk), 27 ) 28 rule.reviewer_groups.add(self.reviewer_group) 29 30 response = self.client.get(reverse("authentik_api:lifecyclerule-list")) 31 self.assertEqual(response.status_code, 200) 32 self.assertGreaterEqual(len(response.data["results"]), 1) 33 34 def test_create_rule_with_reviewer_group(self): 35 response = self.client.post( 36 reverse("authentik_api:lifecyclerule-list"), 37 { 38 "name": generate_id(), 39 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 40 "object_id": str(self.app.pk), 41 "interval": "days=30", 42 "grace_period": "days=10", 43 "reviewer_groups": [str(self.reviewer_group.pk)], 44 "reviewers": [], 45 "min_reviewers": 1, 46 }, 47 ) 48 self.assertEqual(response.status_code, 201) 49 self.assertEqual(response.data["object_id"], str(self.app.pk)) 50 self.assertEqual(response.data["interval"], "days=30") 51 52 def test_create_rule_with_explicit_reviewer(self): 53 reviewer = create_test_user() 54 response = self.client.post( 55 reverse("authentik_api:lifecyclerule-list"), 56 { 57 "name": generate_id(), 58 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 59 "object_id": str(self.app.pk), 60 "interval": "days=60", 61 "grace_period": "days=15", 62 "reviewer_groups": [], 63 "reviewers": [str(reviewer.uuid)], 64 "min_reviewers": 1, 65 }, 66 ) 67 self.assertEqual(response.status_code, 201) 68 self.assertIn(reviewer.uuid, response.data["reviewers"]) 69 70 def test_create_rule_type_level(self): 71 response = self.client.post( 72 reverse("authentik_api:lifecyclerule-list"), 73 { 74 "name": generate_id(), 75 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 76 "object_id": None, 77 "interval": "days=90", 78 "grace_period": "days=30", 79 "reviewer_groups": [str(self.reviewer_group.pk)], 80 "reviewers": [], 81 "min_reviewers": 1, 82 }, 83 ) 84 self.assertEqual(response.status_code, 201) 85 self.assertIsNone(response.data["object_id"]) 86 87 def test_create_rule_fails_without_reviewers(self): 88 response = self.client.post( 89 reverse("authentik_api:lifecyclerule-list"), 90 { 91 "name": generate_id(), 92 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 93 "object_id": str(self.app.pk), 94 "interval": "days=30", 95 "grace_period": "days=10", 96 "reviewer_groups": [], 97 "reviewers": [], 98 "min_reviewers": 1, 99 }, 100 ) 101 self.assertEqual(response.status_code, 400) 102 103 def test_create_rule_fails_grace_period_longer_than_interval(self): 104 response = self.client.post( 105 reverse("authentik_api:lifecyclerule-list"), 106 { 107 "name": generate_id(), 108 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 109 "object_id": str(self.app.pk), 110 "interval": "days=10", 111 "grace_period": "days=30", 112 "reviewer_groups": [str(self.reviewer_group.pk)], 113 "reviewers": [], 114 "min_reviewers": 1, 115 }, 116 ) 117 self.assertEqual(response.status_code, 400) 118 self.assertIn("grace_period", response.data) 119 120 def test_create_rule_fails_invalid_object_id(self): 121 response = self.client.post( 122 reverse("authentik_api:lifecyclerule-list"), 123 { 124 "name": generate_id(), 125 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 126 "object_id": "00000000-0000-0000-0000-000000000000", 127 "interval": "days=30", 128 "grace_period": "days=10", 129 "reviewer_groups": [str(self.reviewer_group.pk)], 130 "reviewers": [], 131 "min_reviewers": 1, 132 }, 133 ) 134 self.assertEqual(response.status_code, 400) 135 self.assertIn("object_id", response.data) 136 137 def test_retrieve_rule(self): 138 rule = LifecycleRule.objects.create( 139 name=generate_id(), 140 content_type=self.content_type, 141 object_id=str(self.app.pk), 142 ) 143 rule.reviewer_groups.add(self.reviewer_group) 144 145 response = self.client.get( 146 reverse("authentik_api:lifecyclerule-detail", kwargs={"pk": rule.pk}) 147 ) 148 self.assertEqual(response.status_code, 200) 149 self.assertEqual(response.data["id"], str(rule.pk)) 150 151 def test_update_rule(self): 152 rule = LifecycleRule.objects.create( 153 name=generate_id(), 154 content_type=self.content_type, 155 object_id=str(self.app.pk), 156 interval="days=30", 157 ) 158 rule.reviewer_groups.add(self.reviewer_group) 159 160 response = self.client.patch( 161 reverse("authentik_api:lifecyclerule-detail", kwargs={"pk": rule.pk}), 162 {"interval": "days=60"}, 163 ) 164 self.assertEqual(response.status_code, 200) 165 self.assertEqual(response.data["interval"], "days=60") 166 167 def test_delete_rule(self): 168 rule = LifecycleRule.objects.create( 169 name=generate_id(), 170 content_type=self.content_type, 171 object_id=str(self.app.pk), 172 ) 173 rule.reviewer_groups.add(self.reviewer_group) 174 175 response = self.client.delete( 176 reverse("authentik_api:lifecyclerule-detail", kwargs={"pk": rule.pk}) 177 ) 178 self.assertEqual(response.status_code, 204) 179 self.assertFalse(LifecycleRule.objects.filter(pk=rule.pk).exists()) 180 181 182@patch_license 183class TestIterationAPI(APITestCase): 184 185 def setUp(self): 186 self.user = create_test_admin_user() 187 self.client.force_login(self.user) 188 self.app = Application.objects.create(name=generate_id(), slug=generate_id()) 189 self.content_type = ContentType.objects.get_for_model(Application) 190 self.reviewer_group = Group.objects.create(name=generate_id()) 191 self.reviewer_group.users.add(self.user) 192 193 def test_open_iterations(self): 194 rule = LifecycleRule.objects.create( 195 name=generate_id(), 196 content_type=self.content_type, 197 object_id=str(self.app.pk), 198 ) 199 rule.reviewer_groups.add(self.reviewer_group) 200 201 response = self.client.get(reverse("authentik_api:lifecycleiteration-open-iterations")) 202 self.assertEqual(response.status_code, 200) 203 self.assertGreaterEqual(len(response.data["results"]), 1) 204 205 for iteration in response.data["results"]: 206 self.assertEqual(iteration["state"], ReviewState.PENDING) 207 208 def test_open_iterations_filter_user_is_reviewer(self): 209 rule = LifecycleRule.objects.create( 210 name=generate_id(), 211 content_type=self.content_type, 212 object_id=str(self.app.pk), 213 ) 214 rule.reviewer_groups.add(self.reviewer_group) 215 216 response = self.client.get( 217 reverse("authentik_api:lifecycleiteration-open-iterations"), 218 {"user_is_reviewer": "true"}, 219 ) 220 self.assertEqual(response.status_code, 200) 221 # User is in reviewer_group, so should see the iteration 222 self.assertGreaterEqual(len(response.data["results"]), 1) 223 224 def test_latest_iteration(self): 225 rule = LifecycleRule.objects.create( 226 name=generate_id(), 227 content_type=self.content_type, 228 object_id=str(self.app.pk), 229 ) 230 rule.reviewer_groups.add(self.reviewer_group) 231 232 response = self.client.get( 233 reverse( 234 "authentik_api:lifecycleiteration-latest-iteration", 235 kwargs={ 236 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 237 "object_id": str(self.app.pk), 238 }, 239 ) 240 ) 241 self.assertEqual(response.status_code, 200) 242 self.assertEqual(response.data["object_id"], str(self.app.pk)) 243 244 def test_latest_iteration_not_found(self): 245 response = self.client.get( 246 reverse( 247 "authentik_api:lifecycleiteration-latest-iteration", 248 kwargs={ 249 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 250 "object_id": "00000000-0000-0000-0000-000000000000", 251 }, 252 ) 253 ) 254 self.assertEqual(response.status_code, 404) 255 256 def test_iteration_includes_user_can_review(self): 257 rule = LifecycleRule.objects.create( 258 name=generate_id(), 259 content_type=self.content_type, 260 object_id=str(self.app.pk), 261 ) 262 rule.reviewer_groups.add(self.reviewer_group) 263 264 response = self.client.get(reverse("authentik_api:lifecycleiteration-open-iterations")) 265 self.assertEqual(response.status_code, 200) 266 self.assertGreaterEqual(len(response.data["results"]), 1) 267 # user_can_review should be present 268 self.assertIn("user_can_review", response.data["results"][0]) 269 270 271@patch_license 272class TestReviewAPI(APITestCase): 273 274 def setUp(self): 275 self.user = create_test_admin_user() 276 self.client.force_login(self.user) 277 self.app = Application.objects.create(name=generate_id(), slug=generate_id()) 278 self.content_type = ContentType.objects.get_for_model(Application) 279 self.reviewer_group = Group.objects.create(name=generate_id()) 280 self.reviewer_group.users.add(self.user) 281 282 def test_create_review(self): 283 rule = LifecycleRule.objects.create( 284 name=generate_id(), 285 content_type=self.content_type, 286 object_id=str(self.app.pk), 287 min_reviewers=1, 288 ) 289 rule.reviewer_groups.add(self.reviewer_group) 290 291 # Get the auto-created iteration 292 iteration = LifecycleIteration.objects.get( 293 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 294 ) 295 296 response = self.client.post( 297 reverse("authentik_api:review-list"), 298 { 299 "iteration": str(iteration.pk), 300 "note": "Reviewed and approved", 301 }, 302 ) 303 self.assertEqual(response.status_code, 201) 304 self.assertEqual(response.data["iteration"], iteration.pk) 305 self.assertEqual(response.data["note"], "Reviewed and approved") 306 self.assertEqual(response.data["reviewer"]["pk"], self.user.pk) 307 308 def test_create_review_completes_iteration(self): 309 rule = LifecycleRule.objects.create( 310 name=generate_id(), 311 content_type=self.content_type, 312 object_id=str(self.app.pk), 313 min_reviewers=1, 314 ) 315 rule.reviewer_groups.add(self.reviewer_group) 316 317 iteration = LifecycleIteration.objects.get( 318 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 319 ) 320 self.assertEqual(iteration.state, ReviewState.PENDING) 321 322 response = self.client.post( 323 reverse("authentik_api:review-list"), 324 { 325 "iteration": str(iteration.pk), 326 }, 327 ) 328 self.assertEqual(response.status_code, 201) 329 330 iteration.refresh_from_db() 331 self.assertEqual(iteration.state, ReviewState.REVIEWED) 332 333 def test_create_review_sets_reviewer_from_request(self): 334 rule = LifecycleRule.objects.create( 335 name=generate_id(), 336 content_type=self.content_type, 337 object_id=str(self.app.pk), 338 min_reviewers=1, 339 ) 340 rule.reviewer_groups.add(self.reviewer_group) 341 342 iteration = LifecycleIteration.objects.get( 343 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 344 ) 345 346 response = self.client.post( 347 reverse("authentik_api:review-list"), 348 { 349 "iteration": str(iteration.pk), 350 }, 351 ) 352 self.assertEqual(response.status_code, 201) 353 # Reviewer should be the logged-in user 354 self.assertEqual(response.data["reviewer"]["pk"], self.user.pk) 355 356 def test_non_reviewer_cannot_review(self): 357 other_group = Group.objects.create(name=generate_id()) 358 other_user = create_test_user() 359 other_group.users.add(other_user) 360 361 rule = LifecycleRule.objects.create( 362 name=generate_id(), 363 content_type=self.content_type, 364 object_id=str(self.app.pk), 365 min_reviewers=1, 366 ) 367 rule.reviewer_groups.add(other_group) 368 369 iteration = LifecycleIteration.objects.get( 370 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 371 ) 372 373 # Current user is not in the reviewer group 374 self.assertFalse(iteration.user_can_review(self.user)) 375 376 def test_non_reviewer_review_via_api_rejected(self): 377 other_group = Group.objects.create(name=generate_id()) 378 other_user = create_test_user() 379 other_group.users.add(other_user) 380 381 rule = LifecycleRule.objects.create( 382 name=generate_id(), 383 content_type=self.content_type, 384 object_id=str(self.app.pk), 385 min_reviewers=1, 386 ) 387 rule.reviewer_groups.add(other_group) 388 389 iteration = LifecycleIteration.objects.get( 390 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 391 ) 392 393 # Current user (self.user) is NOT in the reviewer group 394 response = self.client.post( 395 reverse("authentik_api:review-list"), 396 {"iteration": str(iteration.pk)}, 397 ) 398 self.assertEqual(response.status_code, 400) 399 400 def test_duplicate_review_via_api_rejected(self): 401 rule = LifecycleRule.objects.create( 402 name=generate_id(), 403 content_type=self.content_type, 404 object_id=str(self.app.pk), 405 min_reviewers=2, 406 ) 407 rule.reviewer_groups.add(self.reviewer_group) 408 409 iteration = LifecycleIteration.objects.get( 410 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 411 ) 412 413 # First review should succeed 414 response = self.client.post( 415 reverse("authentik_api:review-list"), 416 {"iteration": str(iteration.pk)}, 417 ) 418 self.assertEqual(response.status_code, 201) 419 420 # Second review by same user should be rejected 421 response = self.client.post( 422 reverse("authentik_api:review-list"), 423 {"iteration": str(iteration.pk)}, 424 ) 425 self.assertEqual(response.status_code, 400)
13@patch_license 14class TestLifecycleRuleAPI(APITestCase): 15 16 def setUp(self): 17 self.user = create_test_admin_user() 18 self.client.force_login(self.user) 19 self.app = Application.objects.create(name=generate_id(), slug=generate_id()) 20 self.content_type = ContentType.objects.get_for_model(Application) 21 self.reviewer_group = Group.objects.create(name=generate_id()) 22 23 def test_list_rules(self): 24 rule = LifecycleRule.objects.create( 25 name=generate_id(), 26 content_type=self.content_type, 27 object_id=str(self.app.pk), 28 ) 29 rule.reviewer_groups.add(self.reviewer_group) 30 31 response = self.client.get(reverse("authentik_api:lifecyclerule-list")) 32 self.assertEqual(response.status_code, 200) 33 self.assertGreaterEqual(len(response.data["results"]), 1) 34 35 def test_create_rule_with_reviewer_group(self): 36 response = self.client.post( 37 reverse("authentik_api:lifecyclerule-list"), 38 { 39 "name": generate_id(), 40 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 41 "object_id": str(self.app.pk), 42 "interval": "days=30", 43 "grace_period": "days=10", 44 "reviewer_groups": [str(self.reviewer_group.pk)], 45 "reviewers": [], 46 "min_reviewers": 1, 47 }, 48 ) 49 self.assertEqual(response.status_code, 201) 50 self.assertEqual(response.data["object_id"], str(self.app.pk)) 51 self.assertEqual(response.data["interval"], "days=30") 52 53 def test_create_rule_with_explicit_reviewer(self): 54 reviewer = create_test_user() 55 response = self.client.post( 56 reverse("authentik_api:lifecyclerule-list"), 57 { 58 "name": generate_id(), 59 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 60 "object_id": str(self.app.pk), 61 "interval": "days=60", 62 "grace_period": "days=15", 63 "reviewer_groups": [], 64 "reviewers": [str(reviewer.uuid)], 65 "min_reviewers": 1, 66 }, 67 ) 68 self.assertEqual(response.status_code, 201) 69 self.assertIn(reviewer.uuid, response.data["reviewers"]) 70 71 def test_create_rule_type_level(self): 72 response = self.client.post( 73 reverse("authentik_api:lifecyclerule-list"), 74 { 75 "name": generate_id(), 76 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 77 "object_id": None, 78 "interval": "days=90", 79 "grace_period": "days=30", 80 "reviewer_groups": [str(self.reviewer_group.pk)], 81 "reviewers": [], 82 "min_reviewers": 1, 83 }, 84 ) 85 self.assertEqual(response.status_code, 201) 86 self.assertIsNone(response.data["object_id"]) 87 88 def test_create_rule_fails_without_reviewers(self): 89 response = self.client.post( 90 reverse("authentik_api:lifecyclerule-list"), 91 { 92 "name": generate_id(), 93 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 94 "object_id": str(self.app.pk), 95 "interval": "days=30", 96 "grace_period": "days=10", 97 "reviewer_groups": [], 98 "reviewers": [], 99 "min_reviewers": 1, 100 }, 101 ) 102 self.assertEqual(response.status_code, 400) 103 104 def test_create_rule_fails_grace_period_longer_than_interval(self): 105 response = self.client.post( 106 reverse("authentik_api:lifecyclerule-list"), 107 { 108 "name": generate_id(), 109 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 110 "object_id": str(self.app.pk), 111 "interval": "days=10", 112 "grace_period": "days=30", 113 "reviewer_groups": [str(self.reviewer_group.pk)], 114 "reviewers": [], 115 "min_reviewers": 1, 116 }, 117 ) 118 self.assertEqual(response.status_code, 400) 119 self.assertIn("grace_period", response.data) 120 121 def test_create_rule_fails_invalid_object_id(self): 122 response = self.client.post( 123 reverse("authentik_api:lifecyclerule-list"), 124 { 125 "name": generate_id(), 126 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 127 "object_id": "00000000-0000-0000-0000-000000000000", 128 "interval": "days=30", 129 "grace_period": "days=10", 130 "reviewer_groups": [str(self.reviewer_group.pk)], 131 "reviewers": [], 132 "min_reviewers": 1, 133 }, 134 ) 135 self.assertEqual(response.status_code, 400) 136 self.assertIn("object_id", response.data) 137 138 def test_retrieve_rule(self): 139 rule = LifecycleRule.objects.create( 140 name=generate_id(), 141 content_type=self.content_type, 142 object_id=str(self.app.pk), 143 ) 144 rule.reviewer_groups.add(self.reviewer_group) 145 146 response = self.client.get( 147 reverse("authentik_api:lifecyclerule-detail", kwargs={"pk": rule.pk}) 148 ) 149 self.assertEqual(response.status_code, 200) 150 self.assertEqual(response.data["id"], str(rule.pk)) 151 152 def test_update_rule(self): 153 rule = LifecycleRule.objects.create( 154 name=generate_id(), 155 content_type=self.content_type, 156 object_id=str(self.app.pk), 157 interval="days=30", 158 ) 159 rule.reviewer_groups.add(self.reviewer_group) 160 161 response = self.client.patch( 162 reverse("authentik_api:lifecyclerule-detail", kwargs={"pk": rule.pk}), 163 {"interval": "days=60"}, 164 ) 165 self.assertEqual(response.status_code, 200) 166 self.assertEqual(response.data["interval"], "days=60") 167 168 def test_delete_rule(self): 169 rule = LifecycleRule.objects.create( 170 name=generate_id(), 171 content_type=self.content_type, 172 object_id=str(self.app.pk), 173 ) 174 rule.reviewer_groups.add(self.reviewer_group) 175 176 response = self.client.delete( 177 reverse("authentik_api:lifecyclerule-detail", kwargs={"pk": rule.pk}) 178 ) 179 self.assertEqual(response.status_code, 204) 180 self.assertFalse(LifecycleRule.objects.filter(pk=rule.pk).exists())
Similar to TransactionTestCase, but use transaction.atomic() to achieve
test isolation.
In most situations, TestCase should be preferred to TransactionTestCase as it allows faster execution. However, there are some situations where using TransactionTestCase might be necessary (e.g. testing some transactional behavior).
On database backends with no transaction support, TestCase behaves as TransactionTestCase.
16 def setUp(self): 17 self.user = create_test_admin_user() 18 self.client.force_login(self.user) 19 self.app = Application.objects.create(name=generate_id(), slug=generate_id()) 20 self.content_type = ContentType.objects.get_for_model(Application) 21 self.reviewer_group = Group.objects.create(name=generate_id())
Hook method for setting up the test fixture before exercising it.
23 def test_list_rules(self): 24 rule = LifecycleRule.objects.create( 25 name=generate_id(), 26 content_type=self.content_type, 27 object_id=str(self.app.pk), 28 ) 29 rule.reviewer_groups.add(self.reviewer_group) 30 31 response = self.client.get(reverse("authentik_api:lifecyclerule-list")) 32 self.assertEqual(response.status_code, 200) 33 self.assertGreaterEqual(len(response.data["results"]), 1)
35 def test_create_rule_with_reviewer_group(self): 36 response = self.client.post( 37 reverse("authentik_api:lifecyclerule-list"), 38 { 39 "name": generate_id(), 40 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 41 "object_id": str(self.app.pk), 42 "interval": "days=30", 43 "grace_period": "days=10", 44 "reviewer_groups": [str(self.reviewer_group.pk)], 45 "reviewers": [], 46 "min_reviewers": 1, 47 }, 48 ) 49 self.assertEqual(response.status_code, 201) 50 self.assertEqual(response.data["object_id"], str(self.app.pk)) 51 self.assertEqual(response.data["interval"], "days=30")
53 def test_create_rule_with_explicit_reviewer(self): 54 reviewer = create_test_user() 55 response = self.client.post( 56 reverse("authentik_api:lifecyclerule-list"), 57 { 58 "name": generate_id(), 59 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 60 "object_id": str(self.app.pk), 61 "interval": "days=60", 62 "grace_period": "days=15", 63 "reviewer_groups": [], 64 "reviewers": [str(reviewer.uuid)], 65 "min_reviewers": 1, 66 }, 67 ) 68 self.assertEqual(response.status_code, 201) 69 self.assertIn(reviewer.uuid, response.data["reviewers"])
71 def test_create_rule_type_level(self): 72 response = self.client.post( 73 reverse("authentik_api:lifecyclerule-list"), 74 { 75 "name": generate_id(), 76 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 77 "object_id": None, 78 "interval": "days=90", 79 "grace_period": "days=30", 80 "reviewer_groups": [str(self.reviewer_group.pk)], 81 "reviewers": [], 82 "min_reviewers": 1, 83 }, 84 ) 85 self.assertEqual(response.status_code, 201) 86 self.assertIsNone(response.data["object_id"])
88 def test_create_rule_fails_without_reviewers(self): 89 response = self.client.post( 90 reverse("authentik_api:lifecyclerule-list"), 91 { 92 "name": generate_id(), 93 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 94 "object_id": str(self.app.pk), 95 "interval": "days=30", 96 "grace_period": "days=10", 97 "reviewer_groups": [], 98 "reviewers": [], 99 "min_reviewers": 1, 100 }, 101 ) 102 self.assertEqual(response.status_code, 400)
104 def test_create_rule_fails_grace_period_longer_than_interval(self): 105 response = self.client.post( 106 reverse("authentik_api:lifecyclerule-list"), 107 { 108 "name": generate_id(), 109 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 110 "object_id": str(self.app.pk), 111 "interval": "days=10", 112 "grace_period": "days=30", 113 "reviewer_groups": [str(self.reviewer_group.pk)], 114 "reviewers": [], 115 "min_reviewers": 1, 116 }, 117 ) 118 self.assertEqual(response.status_code, 400) 119 self.assertIn("grace_period", response.data)
121 def test_create_rule_fails_invalid_object_id(self): 122 response = self.client.post( 123 reverse("authentik_api:lifecyclerule-list"), 124 { 125 "name": generate_id(), 126 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 127 "object_id": "00000000-0000-0000-0000-000000000000", 128 "interval": "days=30", 129 "grace_period": "days=10", 130 "reviewer_groups": [str(self.reviewer_group.pk)], 131 "reviewers": [], 132 "min_reviewers": 1, 133 }, 134 ) 135 self.assertEqual(response.status_code, 400) 136 self.assertIn("object_id", response.data)
138 def test_retrieve_rule(self): 139 rule = LifecycleRule.objects.create( 140 name=generate_id(), 141 content_type=self.content_type, 142 object_id=str(self.app.pk), 143 ) 144 rule.reviewer_groups.add(self.reviewer_group) 145 146 response = self.client.get( 147 reverse("authentik_api:lifecyclerule-detail", kwargs={"pk": rule.pk}) 148 ) 149 self.assertEqual(response.status_code, 200) 150 self.assertEqual(response.data["id"], str(rule.pk))
152 def test_update_rule(self): 153 rule = LifecycleRule.objects.create( 154 name=generate_id(), 155 content_type=self.content_type, 156 object_id=str(self.app.pk), 157 interval="days=30", 158 ) 159 rule.reviewer_groups.add(self.reviewer_group) 160 161 response = self.client.patch( 162 reverse("authentik_api:lifecyclerule-detail", kwargs={"pk": rule.pk}), 163 {"interval": "days=60"}, 164 ) 165 self.assertEqual(response.status_code, 200) 166 self.assertEqual(response.data["interval"], "days=60")
168 def test_delete_rule(self): 169 rule = LifecycleRule.objects.create( 170 name=generate_id(), 171 content_type=self.content_type, 172 object_id=str(self.app.pk), 173 ) 174 rule.reviewer_groups.add(self.reviewer_group) 175 176 response = self.client.delete( 177 reverse("authentik_api:lifecyclerule-detail", kwargs={"pk": rule.pk}) 178 ) 179 self.assertEqual(response.status_code, 204) 180 self.assertFalse(LifecycleRule.objects.filter(pk=rule.pk).exists())
183@patch_license 184class TestIterationAPI(APITestCase): 185 186 def setUp(self): 187 self.user = create_test_admin_user() 188 self.client.force_login(self.user) 189 self.app = Application.objects.create(name=generate_id(), slug=generate_id()) 190 self.content_type = ContentType.objects.get_for_model(Application) 191 self.reviewer_group = Group.objects.create(name=generate_id()) 192 self.reviewer_group.users.add(self.user) 193 194 def test_open_iterations(self): 195 rule = LifecycleRule.objects.create( 196 name=generate_id(), 197 content_type=self.content_type, 198 object_id=str(self.app.pk), 199 ) 200 rule.reviewer_groups.add(self.reviewer_group) 201 202 response = self.client.get(reverse("authentik_api:lifecycleiteration-open-iterations")) 203 self.assertEqual(response.status_code, 200) 204 self.assertGreaterEqual(len(response.data["results"]), 1) 205 206 for iteration in response.data["results"]: 207 self.assertEqual(iteration["state"], ReviewState.PENDING) 208 209 def test_open_iterations_filter_user_is_reviewer(self): 210 rule = LifecycleRule.objects.create( 211 name=generate_id(), 212 content_type=self.content_type, 213 object_id=str(self.app.pk), 214 ) 215 rule.reviewer_groups.add(self.reviewer_group) 216 217 response = self.client.get( 218 reverse("authentik_api:lifecycleiteration-open-iterations"), 219 {"user_is_reviewer": "true"}, 220 ) 221 self.assertEqual(response.status_code, 200) 222 # User is in reviewer_group, so should see the iteration 223 self.assertGreaterEqual(len(response.data["results"]), 1) 224 225 def test_latest_iteration(self): 226 rule = LifecycleRule.objects.create( 227 name=generate_id(), 228 content_type=self.content_type, 229 object_id=str(self.app.pk), 230 ) 231 rule.reviewer_groups.add(self.reviewer_group) 232 233 response = self.client.get( 234 reverse( 235 "authentik_api:lifecycleiteration-latest-iteration", 236 kwargs={ 237 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 238 "object_id": str(self.app.pk), 239 }, 240 ) 241 ) 242 self.assertEqual(response.status_code, 200) 243 self.assertEqual(response.data["object_id"], str(self.app.pk)) 244 245 def test_latest_iteration_not_found(self): 246 response = self.client.get( 247 reverse( 248 "authentik_api:lifecycleiteration-latest-iteration", 249 kwargs={ 250 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 251 "object_id": "00000000-0000-0000-0000-000000000000", 252 }, 253 ) 254 ) 255 self.assertEqual(response.status_code, 404) 256 257 def test_iteration_includes_user_can_review(self): 258 rule = LifecycleRule.objects.create( 259 name=generate_id(), 260 content_type=self.content_type, 261 object_id=str(self.app.pk), 262 ) 263 rule.reviewer_groups.add(self.reviewer_group) 264 265 response = self.client.get(reverse("authentik_api:lifecycleiteration-open-iterations")) 266 self.assertEqual(response.status_code, 200) 267 self.assertGreaterEqual(len(response.data["results"]), 1) 268 # user_can_review should be present 269 self.assertIn("user_can_review", response.data["results"][0])
Similar to TransactionTestCase, but use transaction.atomic() to achieve
test isolation.
In most situations, TestCase should be preferred to TransactionTestCase as it allows faster execution. However, there are some situations where using TransactionTestCase might be necessary (e.g. testing some transactional behavior).
On database backends with no transaction support, TestCase behaves as TransactionTestCase.
186 def setUp(self): 187 self.user = create_test_admin_user() 188 self.client.force_login(self.user) 189 self.app = Application.objects.create(name=generate_id(), slug=generate_id()) 190 self.content_type = ContentType.objects.get_for_model(Application) 191 self.reviewer_group = Group.objects.create(name=generate_id()) 192 self.reviewer_group.users.add(self.user)
Hook method for setting up the test fixture before exercising it.
194 def test_open_iterations(self): 195 rule = LifecycleRule.objects.create( 196 name=generate_id(), 197 content_type=self.content_type, 198 object_id=str(self.app.pk), 199 ) 200 rule.reviewer_groups.add(self.reviewer_group) 201 202 response = self.client.get(reverse("authentik_api:lifecycleiteration-open-iterations")) 203 self.assertEqual(response.status_code, 200) 204 self.assertGreaterEqual(len(response.data["results"]), 1) 205 206 for iteration in response.data["results"]: 207 self.assertEqual(iteration["state"], ReviewState.PENDING)
209 def test_open_iterations_filter_user_is_reviewer(self): 210 rule = LifecycleRule.objects.create( 211 name=generate_id(), 212 content_type=self.content_type, 213 object_id=str(self.app.pk), 214 ) 215 rule.reviewer_groups.add(self.reviewer_group) 216 217 response = self.client.get( 218 reverse("authentik_api:lifecycleiteration-open-iterations"), 219 {"user_is_reviewer": "true"}, 220 ) 221 self.assertEqual(response.status_code, 200) 222 # User is in reviewer_group, so should see the iteration 223 self.assertGreaterEqual(len(response.data["results"]), 1)
225 def test_latest_iteration(self): 226 rule = LifecycleRule.objects.create( 227 name=generate_id(), 228 content_type=self.content_type, 229 object_id=str(self.app.pk), 230 ) 231 rule.reviewer_groups.add(self.reviewer_group) 232 233 response = self.client.get( 234 reverse( 235 "authentik_api:lifecycleiteration-latest-iteration", 236 kwargs={ 237 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 238 "object_id": str(self.app.pk), 239 }, 240 ) 241 ) 242 self.assertEqual(response.status_code, 200) 243 self.assertEqual(response.data["object_id"], str(self.app.pk))
245 def test_latest_iteration_not_found(self): 246 response = self.client.get( 247 reverse( 248 "authentik_api:lifecycleiteration-latest-iteration", 249 kwargs={ 250 "content_type": f"{self.content_type.app_label}.{self.content_type.model}", 251 "object_id": "00000000-0000-0000-0000-000000000000", 252 }, 253 ) 254 ) 255 self.assertEqual(response.status_code, 404)
257 def test_iteration_includes_user_can_review(self): 258 rule = LifecycleRule.objects.create( 259 name=generate_id(), 260 content_type=self.content_type, 261 object_id=str(self.app.pk), 262 ) 263 rule.reviewer_groups.add(self.reviewer_group) 264 265 response = self.client.get(reverse("authentik_api:lifecycleiteration-open-iterations")) 266 self.assertEqual(response.status_code, 200) 267 self.assertGreaterEqual(len(response.data["results"]), 1) 268 # user_can_review should be present 269 self.assertIn("user_can_review", response.data["results"][0])
272@patch_license 273class TestReviewAPI(APITestCase): 274 275 def setUp(self): 276 self.user = create_test_admin_user() 277 self.client.force_login(self.user) 278 self.app = Application.objects.create(name=generate_id(), slug=generate_id()) 279 self.content_type = ContentType.objects.get_for_model(Application) 280 self.reviewer_group = Group.objects.create(name=generate_id()) 281 self.reviewer_group.users.add(self.user) 282 283 def test_create_review(self): 284 rule = LifecycleRule.objects.create( 285 name=generate_id(), 286 content_type=self.content_type, 287 object_id=str(self.app.pk), 288 min_reviewers=1, 289 ) 290 rule.reviewer_groups.add(self.reviewer_group) 291 292 # Get the auto-created iteration 293 iteration = LifecycleIteration.objects.get( 294 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 295 ) 296 297 response = self.client.post( 298 reverse("authentik_api:review-list"), 299 { 300 "iteration": str(iteration.pk), 301 "note": "Reviewed and approved", 302 }, 303 ) 304 self.assertEqual(response.status_code, 201) 305 self.assertEqual(response.data["iteration"], iteration.pk) 306 self.assertEqual(response.data["note"], "Reviewed and approved") 307 self.assertEqual(response.data["reviewer"]["pk"], self.user.pk) 308 309 def test_create_review_completes_iteration(self): 310 rule = LifecycleRule.objects.create( 311 name=generate_id(), 312 content_type=self.content_type, 313 object_id=str(self.app.pk), 314 min_reviewers=1, 315 ) 316 rule.reviewer_groups.add(self.reviewer_group) 317 318 iteration = LifecycleIteration.objects.get( 319 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 320 ) 321 self.assertEqual(iteration.state, ReviewState.PENDING) 322 323 response = self.client.post( 324 reverse("authentik_api:review-list"), 325 { 326 "iteration": str(iteration.pk), 327 }, 328 ) 329 self.assertEqual(response.status_code, 201) 330 331 iteration.refresh_from_db() 332 self.assertEqual(iteration.state, ReviewState.REVIEWED) 333 334 def test_create_review_sets_reviewer_from_request(self): 335 rule = LifecycleRule.objects.create( 336 name=generate_id(), 337 content_type=self.content_type, 338 object_id=str(self.app.pk), 339 min_reviewers=1, 340 ) 341 rule.reviewer_groups.add(self.reviewer_group) 342 343 iteration = LifecycleIteration.objects.get( 344 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 345 ) 346 347 response = self.client.post( 348 reverse("authentik_api:review-list"), 349 { 350 "iteration": str(iteration.pk), 351 }, 352 ) 353 self.assertEqual(response.status_code, 201) 354 # Reviewer should be the logged-in user 355 self.assertEqual(response.data["reviewer"]["pk"], self.user.pk) 356 357 def test_non_reviewer_cannot_review(self): 358 other_group = Group.objects.create(name=generate_id()) 359 other_user = create_test_user() 360 other_group.users.add(other_user) 361 362 rule = LifecycleRule.objects.create( 363 name=generate_id(), 364 content_type=self.content_type, 365 object_id=str(self.app.pk), 366 min_reviewers=1, 367 ) 368 rule.reviewer_groups.add(other_group) 369 370 iteration = LifecycleIteration.objects.get( 371 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 372 ) 373 374 # Current user is not in the reviewer group 375 self.assertFalse(iteration.user_can_review(self.user)) 376 377 def test_non_reviewer_review_via_api_rejected(self): 378 other_group = Group.objects.create(name=generate_id()) 379 other_user = create_test_user() 380 other_group.users.add(other_user) 381 382 rule = LifecycleRule.objects.create( 383 name=generate_id(), 384 content_type=self.content_type, 385 object_id=str(self.app.pk), 386 min_reviewers=1, 387 ) 388 rule.reviewer_groups.add(other_group) 389 390 iteration = LifecycleIteration.objects.get( 391 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 392 ) 393 394 # Current user (self.user) is NOT in the reviewer group 395 response = self.client.post( 396 reverse("authentik_api:review-list"), 397 {"iteration": str(iteration.pk)}, 398 ) 399 self.assertEqual(response.status_code, 400) 400 401 def test_duplicate_review_via_api_rejected(self): 402 rule = LifecycleRule.objects.create( 403 name=generate_id(), 404 content_type=self.content_type, 405 object_id=str(self.app.pk), 406 min_reviewers=2, 407 ) 408 rule.reviewer_groups.add(self.reviewer_group) 409 410 iteration = LifecycleIteration.objects.get( 411 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 412 ) 413 414 # First review should succeed 415 response = self.client.post( 416 reverse("authentik_api:review-list"), 417 {"iteration": str(iteration.pk)}, 418 ) 419 self.assertEqual(response.status_code, 201) 420 421 # Second review by same user should be rejected 422 response = self.client.post( 423 reverse("authentik_api:review-list"), 424 {"iteration": str(iteration.pk)}, 425 ) 426 self.assertEqual(response.status_code, 400)
Similar to TransactionTestCase, but use transaction.atomic() to achieve
test isolation.
In most situations, TestCase should be preferred to TransactionTestCase as it allows faster execution. However, there are some situations where using TransactionTestCase might be necessary (e.g. testing some transactional behavior).
On database backends with no transaction support, TestCase behaves as TransactionTestCase.
275 def setUp(self): 276 self.user = create_test_admin_user() 277 self.client.force_login(self.user) 278 self.app = Application.objects.create(name=generate_id(), slug=generate_id()) 279 self.content_type = ContentType.objects.get_for_model(Application) 280 self.reviewer_group = Group.objects.create(name=generate_id()) 281 self.reviewer_group.users.add(self.user)
Hook method for setting up the test fixture before exercising it.
283 def test_create_review(self): 284 rule = LifecycleRule.objects.create( 285 name=generate_id(), 286 content_type=self.content_type, 287 object_id=str(self.app.pk), 288 min_reviewers=1, 289 ) 290 rule.reviewer_groups.add(self.reviewer_group) 291 292 # Get the auto-created iteration 293 iteration = LifecycleIteration.objects.get( 294 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 295 ) 296 297 response = self.client.post( 298 reverse("authentik_api:review-list"), 299 { 300 "iteration": str(iteration.pk), 301 "note": "Reviewed and approved", 302 }, 303 ) 304 self.assertEqual(response.status_code, 201) 305 self.assertEqual(response.data["iteration"], iteration.pk) 306 self.assertEqual(response.data["note"], "Reviewed and approved") 307 self.assertEqual(response.data["reviewer"]["pk"], self.user.pk)
309 def test_create_review_completes_iteration(self): 310 rule = LifecycleRule.objects.create( 311 name=generate_id(), 312 content_type=self.content_type, 313 object_id=str(self.app.pk), 314 min_reviewers=1, 315 ) 316 rule.reviewer_groups.add(self.reviewer_group) 317 318 iteration = LifecycleIteration.objects.get( 319 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 320 ) 321 self.assertEqual(iteration.state, ReviewState.PENDING) 322 323 response = self.client.post( 324 reverse("authentik_api:review-list"), 325 { 326 "iteration": str(iteration.pk), 327 }, 328 ) 329 self.assertEqual(response.status_code, 201) 330 331 iteration.refresh_from_db() 332 self.assertEqual(iteration.state, ReviewState.REVIEWED)
334 def test_create_review_sets_reviewer_from_request(self): 335 rule = LifecycleRule.objects.create( 336 name=generate_id(), 337 content_type=self.content_type, 338 object_id=str(self.app.pk), 339 min_reviewers=1, 340 ) 341 rule.reviewer_groups.add(self.reviewer_group) 342 343 iteration = LifecycleIteration.objects.get( 344 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 345 ) 346 347 response = self.client.post( 348 reverse("authentik_api:review-list"), 349 { 350 "iteration": str(iteration.pk), 351 }, 352 ) 353 self.assertEqual(response.status_code, 201) 354 # Reviewer should be the logged-in user 355 self.assertEqual(response.data["reviewer"]["pk"], self.user.pk)
357 def test_non_reviewer_cannot_review(self): 358 other_group = Group.objects.create(name=generate_id()) 359 other_user = create_test_user() 360 other_group.users.add(other_user) 361 362 rule = LifecycleRule.objects.create( 363 name=generate_id(), 364 content_type=self.content_type, 365 object_id=str(self.app.pk), 366 min_reviewers=1, 367 ) 368 rule.reviewer_groups.add(other_group) 369 370 iteration = LifecycleIteration.objects.get( 371 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 372 ) 373 374 # Current user is not in the reviewer group 375 self.assertFalse(iteration.user_can_review(self.user))
377 def test_non_reviewer_review_via_api_rejected(self): 378 other_group = Group.objects.create(name=generate_id()) 379 other_user = create_test_user() 380 other_group.users.add(other_user) 381 382 rule = LifecycleRule.objects.create( 383 name=generate_id(), 384 content_type=self.content_type, 385 object_id=str(self.app.pk), 386 min_reviewers=1, 387 ) 388 rule.reviewer_groups.add(other_group) 389 390 iteration = LifecycleIteration.objects.get( 391 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 392 ) 393 394 # Current user (self.user) is NOT in the reviewer group 395 response = self.client.post( 396 reverse("authentik_api:review-list"), 397 {"iteration": str(iteration.pk)}, 398 ) 399 self.assertEqual(response.status_code, 400)
401 def test_duplicate_review_via_api_rejected(self): 402 rule = LifecycleRule.objects.create( 403 name=generate_id(), 404 content_type=self.content_type, 405 object_id=str(self.app.pk), 406 min_reviewers=2, 407 ) 408 rule.reviewer_groups.add(self.reviewer_group) 409 410 iteration = LifecycleIteration.objects.get( 411 content_type=self.content_type, object_id=str(self.app.pk), rule=rule 412 ) 413 414 # First review should succeed 415 response = self.client.post( 416 reverse("authentik_api:review-list"), 417 {"iteration": str(iteration.pk)}, 418 ) 419 self.assertEqual(response.status_code, 201) 420 421 # Second review by same user should be rejected 422 response = self.client.post( 423 reverse("authentik_api:review-list"), 424 {"iteration": str(iteration.pk)}, 425 ) 426 self.assertEqual(response.status_code, 400)