import uuid from typing import Any from django.core.exceptions import ValidationError from django.db.models import QuerySet from django.test import TestCase from apps.reviews.models import ApproverGroup from apps.reviews.selectors import ( approver_group_get_by_experimenter, approver_group_get_by_experimenter_id, approver_group_get_by_id, approver_group_list, approver_group_list_for_approver, ) from apps.reviews.services import ( approver_group_add_approver, approver_group_create, approver_group_delete, approver_group_remove_approver, approver_group_update, ) from apps.users.models import User from ._helpers import ( _make_admin, _make_approver, _make_experimenter, _make_viewer, ) class ApproverGroupModelTest(TestCase): def setUp(self) -> None: self.experimenter: User = _make_experimenter("_model") self.approver: User = _make_approver("_model") def test_create_group(self) -> None: group: ApproverGroup = approver_group_create( experimenter=self.experimenter, approver_ids=[str(self.approver.pk)], min_approvals=1, ) self.assertIsNotNone(group.pk) self.assertEqual(group.experimenter, self.experimenter) self.assertEqual(group.min_approvals, 1) self.assertEqual(group.approvers.count(), 1) def test_str_representation(self) -> None: group: ApproverGroup = approver_group_create( experimenter=self.experimenter, min_approvals=2, ) result = str(group) self.assertIn("ApproverGroup", result) self.assertIn("min_approvals=2", result) def test_can_approve_true(self) -> None: group: ApproverGroup = approver_group_create( experimenter=self.experimenter, approver_ids=[str(self.approver.pk)], ) self.assertTrue(group.can_approve(self.approver)) def test_can_approve_false_not_in_group(self) -> None: other_approver: User = _make_approver("_other") group: ApproverGroup = approver_group_create( experimenter=self.experimenter, approver_ids=[str(self.approver.pk)], ) self.assertFalse(group.can_approve(other_approver)) def test_one_to_one_constraint(self) -> None: approver_group_create(experimenter=self.experimenter) with self.assertRaises(ValidationError): approver_group_create( experimenter=self.experimenter, min_approvals=1, ) class ApproverGroupCreateServiceTest(TestCase): def setUp(self) -> None: self.experimenter: User = _make_experimenter("_create") self.approver1: User = _make_approver("_create1") self.approver2: User = _make_approver("_create2") def test_create_with_approvers(self) -> None: group: ApproverGroup = approver_group_create( experimenter=self.experimenter, approver_ids=[str(self.approver1.pk), str(self.approver2.pk)], min_approvals=2, ) self.assertEqual(group.approvers.count(), 2) self.assertEqual(group.min_approvals, 2) def test_create_without_approvers(self) -> None: group: ApproverGroup = approver_group_create( experimenter=self.experimenter, min_approvals=1, ) self.assertEqual(group.approvers.count(), 0) self.assertEqual(group.min_approvals, 1) def test_create_default_min_approvals(self) -> None: group: ApproverGroup = approver_group_create( experimenter=self.experimenter ) self.assertEqual(group.min_approvals, 1) def test_create_rejects_non_experimenter_user(self) -> None: admin: User = _make_admin("_cre_admin") with self.assertRaises(ValidationError): approver_group_create(experimenter=admin) def test_create_rejects_viewer_as_experimenter(self) -> None: viewer: User = _make_viewer("_cre_viewer") with self.assertRaises(ValidationError): approver_group_create(experimenter=viewer) def test_create_rejects_approver_as_experimenter(self) -> None: approver: User = _make_approver("_cre_as_exp") with self.assertRaises(ValidationError): approver_group_create(experimenter=approver) def test_create_rejects_inactive_experimenter(self) -> None: self.experimenter.is_active = False self.experimenter.save() with self.assertRaises(ValidationError): approver_group_create(experimenter=self.experimenter) def test_create_rejects_non_approver_in_approver_list(self) -> None: viewer: User = _make_viewer("_cre_bad_appr") with self.assertRaises(ValidationError): approver_group_create( experimenter=self.experimenter, approver_ids=[str(viewer.pk)], ) def test_create_rejects_missing_approver_ids(self) -> None: with self.assertRaises(ValidationError): approver_group_create( experimenter=self.experimenter, approver_ids=[str(uuid.uuid4())], ) def test_create_rejects_duplicate_group(self) -> None: approver_group_create(experimenter=self.experimenter) with self.assertRaises(ValidationError): approver_group_create(experimenter=self.experimenter) def test_create_min_approvals_zero_raises(self) -> None: with self.assertRaises(ValidationError): approver_group_create( experimenter=self.experimenter, min_approvals=0, ) def test_create_min_approvals_exceeds_approvers_raises(self) -> None: with self.assertRaises(ValidationError): approver_group_create( experimenter=self.experimenter, approver_ids=[str(self.approver1.pk)], min_approvals=3, ) class ApproverGroupUpdateServiceTest(TestCase): def setUp(self) -> None: self.experimenter: User = _make_experimenter("_upd") self.approver1: User = _make_approver("_upd1") self.approver2: User = _make_approver("_upd2") self.approver3: User = _make_approver("_upd3") self.group: ApproverGroup = approver_group_create( experimenter=self.experimenter, approver_ids=[str(self.approver1.pk), str(self.approver2.pk)], min_approvals=1, ) def test_update_min_approvals(self) -> None: updated: ApproverGroup = approver_group_update( group=self.group, min_approvals=2 ) self.assertEqual(updated.min_approvals, 2) def test_update_approver_ids_replaces_set(self) -> None: updated: ApproverGroup = approver_group_update( group=self.group, approver_ids=[str(self.approver3.pk)], ) approver_pks: set[Any] = set( updated.approvers.values_list("pk", flat=True) ) self.assertEqual(approver_pks, {self.approver3.pk}) def test_update_both_fields(self) -> None: updated: ApproverGroup = approver_group_update( group=self.group, approver_ids=[ str(self.approver1.pk), str(self.approver2.pk), str(self.approver3.pk), ], min_approvals=3, ) self.assertEqual(updated.approvers.count(), 3) self.assertEqual(updated.min_approvals, 3) def test_update_no_op(self) -> None: updated: ApproverGroup = approver_group_update(group=self.group) self.assertEqual(updated.min_approvals, self.group.min_approvals) def test_update_min_approvals_exceeds_new_approver_count_raises( self, ) -> None: with self.assertRaises(ValidationError): approver_group_update( group=self.group, approver_ids=[str(self.approver1.pk)], min_approvals=5, ) def test_update_min_approvals_exceeds_existing_count_raises(self) -> None: with self.assertRaises(ValidationError): approver_group_update(group=self.group, min_approvals=10) def test_update_rejects_non_approver_role(self) -> None: viewer: User = _make_viewer("_upd_bad") with self.assertRaises(ValidationError): approver_group_update( group=self.group, approver_ids=[str(viewer.pk)], ) def test_update_rejects_missing_user(self) -> None: with self.assertRaises(ValidationError): approver_group_update( group=self.group, approver_ids=[str(uuid.uuid4())], ) class ApproverGroupDeleteServiceTest(TestCase): def test_delete_removes_group(self) -> None: exp: User = _make_experimenter("_del") group: ApproverGroup = approver_group_create(experimenter=exp) pk = group.pk approver_group_delete(group=group) self.assertFalse(ApproverGroup.objects.filter(pk=pk).exists()) def test_delete_allows_recreating_group(self) -> None: exp: User = _make_experimenter("_del2") group: ApproverGroup = approver_group_create(experimenter=exp) approver_group_delete(group=group) new_group: ApproverGroup = approver_group_create(experimenter=exp) self.assertIsNotNone(new_group.pk) class ApproverGroupAddRemoveServiceTest(TestCase): def setUp(self) -> None: self.experimenter: User = _make_experimenter("_ar") self.approver1: User = _make_approver("_ar1") self.approver2: User = _make_approver("_ar2") self.group: ApproverGroup = approver_group_create( experimenter=self.experimenter, approver_ids=[str(self.approver1.pk)], min_approvals=1, ) def test_add_approver(self) -> None: approver_group_add_approver(group=self.group, approver=self.approver2) self.assertEqual(self.group.approvers.count(), 2) self.assertTrue( self.group.approvers.filter(pk=self.approver2.pk).exists() ) def test_add_approver_rejects_non_approver_role(self) -> None: viewer: User = _make_viewer("_ar_bad") with self.assertRaises(ValidationError): approver_group_add_approver(group=self.group, approver=viewer) def test_add_approver_rejects_duplicate(self) -> None: with self.assertRaises(ValidationError): approver_group_add_approver( group=self.group, approver=self.approver1 ) def test_remove_approver(self) -> None: approver_group_add_approver(group=self.group, approver=self.approver2) approver_group_remove_approver( group=self.group, approver=self.approver1 ) self.assertEqual(self.group.approvers.count(), 1) self.assertFalse( self.group.approvers.filter(pk=self.approver1.pk).exists() ) def test_remove_approver_not_in_group_raises(self) -> None: with self.assertRaises(ValidationError): approver_group_remove_approver( group=self.group, approver=self.approver2 ) def test_remove_approver_below_min_raises(self) -> None: with self.assertRaises(ValidationError): approver_group_remove_approver( group=self.group, approver=self.approver1 ) class ApproverGroupSelectorsTest(TestCase): def setUp(self) -> None: self.exp1: User = _make_experimenter("_sel1") self.exp2: User = _make_experimenter("_sel2") self.appr1: User = _make_approver("_sel1") self.appr2: User = _make_approver("_sel2") self.group1: ApproverGroup = approver_group_create( experimenter=self.exp1, approver_ids=[str(self.appr1.pk)], min_approvals=1, ) self.group2: ApproverGroup = approver_group_create( experimenter=self.exp2, approver_ids=[str(self.appr1.pk), str(self.appr2.pk)], min_approvals=2, ) def test_get_by_id(self) -> None: found: ApproverGroup | None = approver_group_get_by_id( str(self.group1.pk) ) self.assertEqual(found, self.group1) def test_get_by_id_invalid_uuid(self) -> None: self.assertIsNone(approver_group_get_by_id("not-a-uuid")) def test_get_by_id_nonexistent(self) -> None: self.assertIsNone(approver_group_get_by_id(str(uuid.uuid4()))) def test_get_by_experimenter(self) -> None: found: ApproverGroup | None = approver_group_get_by_experimenter( self.exp1 ) self.assertEqual(found, self.group1) def test_get_by_experimenter_no_group(self) -> None: exp3: User = _make_experimenter("_sel3") self.assertIsNone(approver_group_get_by_experimenter(exp3)) def test_get_by_experimenter_id(self) -> None: found: ApproverGroup | None = approver_group_get_by_experimenter_id( str(self.exp2.pk) ) self.assertEqual(found, self.group2) def test_get_by_experimenter_id_invalid(self) -> None: self.assertIsNone(approver_group_get_by_experimenter_id("bad")) def test_get_by_experimenter_id_nonexistent(self) -> None: self.assertIsNone( approver_group_get_by_experimenter_id(str(uuid.uuid4())) ) def test_list_all(self) -> None: qs: QuerySet[ApproverGroup] = approver_group_list() self.assertEqual(qs.count(), 2) def test_list_for_approver(self) -> None: qs: QuerySet[ApproverGroup] = approver_group_list_for_approver( self.appr1 ) self.assertEqual(qs.count(), 2) qs2: QuerySet[ApproverGroup] = approver_group_list_for_approver( self.appr2 ) self.assertEqual(qs2.count(), 1)