""" Tests for the access control app — Phase 3. Covers role/permission CRUD, role↔permission binding, user↔role assignment, permission checking, and seeding. """ from django.test import TestCase from django.urls import reverse from apps.accounts.services import register_user from apps.access.models import Permission, Role, RolePermission, UserRole from apps.access.services import ( assign_permission_to_role, assign_role, check_access, create_permission, create_role, delete_role, get_role, get_role_permissions, get_user_permissions, get_user_roles, has_permission, has_role, list_roles, revoke_permission_from_role, revoke_role, seed_roles_and_permissions, ) from core.exceptions import NotFoundError, ValidationError # ─── Role Tests ────────────────────────────────────────────────────────────── class RoleTests(TestCase): """Test role CRUD operations.""" def test_create_role(self): """Create a new role.""" role = create_role("test_admin", Role.Scope.PLATFORM, "Test admin role.") self.assertEqual(role.name, "test_admin") self.assertEqual(role.scope, Role.Scope.PLATFORM) def test_create_duplicate_role_fails(self): """Cannot create a role with duplicate name.""" create_role("admin") with self.assertRaises(ValidationError): create_role("admin") def test_get_role(self): """Retrieve a role by name.""" create_role("viewer") role = get_role("viewer") self.assertEqual(role.name, "viewer") def test_get_nonexistent_role_fails(self): """Getting a non-existent role raises NotFoundError.""" with self.assertRaises(NotFoundError): get_role("nonexistent") def test_delete_role(self): """Delete a role removes it from the database.""" create_role("temp") delete_role("temp") with self.assertRaises(NotFoundError): get_role("temp") def test_list_roles_by_scope(self): """List roles filtered by scope.""" create_role("platform_a", Role.Scope.PLATFORM) create_role("org_a", Role.Scope.ORG) platform_roles = list_roles(scope=Role.Scope.PLATFORM) self.assertTrue(all(r.scope == Role.Scope.PLATFORM for r in platform_roles)) # ─── Permission Tests ─────────────────────────────────────────────────────── class PermissionTests(TestCase): """Test permission CRUD.""" def test_create_permission(self): """Create a new permission.""" perm = create_permission("test_users", "read", "View users") self.assertEqual(perm.codename, "test_users:read") def test_create_duplicate_permission_fails(self): """Cannot create duplicate permission.""" create_permission("test_users", "read") with self.assertRaises(ValidationError): create_permission("test_users", "read") # ─── Role ↔ Permission Tests ──────────────────────────────────────────────── class RolePermissionTests(TestCase): """Test role-permission binding.""" def setUp(self): self.role = create_role("editor", Role.Scope.ORG) self.perm1 = create_permission("articles", "read") self.perm2 = create_permission("articles", "write") def test_assign_permission_to_role(self): """Assign a permission to a role.""" assign_permission_to_role("editor", "articles", "read") perms = get_role_permissions("editor") self.assertIn("articles:read", perms) def test_revoke_permission_from_role(self): """Revoke a permission from a role.""" assign_permission_to_role("editor", "articles", "read") revoke_permission_from_role("editor", "articles", "read") perms = get_role_permissions("editor") self.assertNotIn("articles:read", perms) def test_multiple_permissions_on_role(self): """Role can have multiple permissions.""" assign_permission_to_role("editor", "articles", "read") assign_permission_to_role("editor", "articles", "write") perms = get_role_permissions("editor") self.assertEqual(len(perms), 2) # ─── User ↔ Role Tests ────────────────────────────────────────────────────── class UserRoleTests(TestCase): """Test user-role assignment and permission checking.""" def setUp(self): self.user = register_user("rbac@example.com", "StrongPass123!", org_name="Test Org", business_type="SaaS") self.role = create_role("test_platform_admin", Role.Scope.PLATFORM) create_permission("test_usersxyz", "read") create_permission("test_usersxyz", "write") assign_permission_to_role("test_platform_admin", "test_usersxyz", "read") assign_permission_to_role("test_platform_admin", "test_usersxyz", "write") def test_assign_role_to_user(self): """Assign a role to a user.""" assign_role(self.user, "test_platform_admin") self.assertTrue(has_role(self.user, "test_platform_admin")) def test_revoke_role_from_user(self): """Revoke a role from a user.""" assign_role(self.user, "test_platform_admin") revoke_role(self.user, "test_platform_admin") self.assertFalse(has_role(self.user, "test_platform_admin")) def test_get_user_roles(self): """Get all roles for a user.""" assign_role(self.user, "test_platform_admin") roles = get_user_roles(self.user) self.assertIn("test_platform_admin", roles) def test_has_permission_via_role(self): """User has permission through assigned role.""" assign_role(self.user, "test_platform_admin") self.assertTrue(has_permission(self.user, "test_usersxyz", "read")) self.assertTrue(has_permission(self.user, "test_usersxyz", "write")) def test_no_permission_without_role(self): """User without role has no permissions.""" self.assertFalse(has_permission(self.user, "test_usersxyz", "read")) def test_check_access_codename_format(self): """check_access works with 'resource:action' format.""" assign_role(self.user, "test_platform_admin") self.assertTrue(check_access(self.user, "test_usersxyz:read")) self.assertFalse(check_access(self.user, "billing:read")) def test_get_user_permissions_set(self): """get_user_permissions returns full set.""" assign_role(self.user, "test_platform_admin") perms = get_user_permissions(self.user) self.assertIn("test_usersxyz:read", perms) self.assertIn("test_usersxyz:write", perms) # ─── Seeding Tests ─────────────────────────────────────────────────────────── class SeedTests(TestCase): """Test role/permission seeding.""" def test_seed_creates_defaults(self): """Seeding creates default roles and permissions.""" seed_roles_and_permissions() from apps.access.models import Role, Permission self.assertGreater(Role.objects.count(), 0) self.assertGreater(Permission.objects.count(), 0) def test_seed_is_idempotent(self): """Running seed twice doesn't duplicate records.""" seed_roles_and_permissions() result = seed_roles_and_permissions() self.assertEqual(result["roles_created"], 0) self.assertEqual(result["permissions_created"], 0) # ─── URL Tests ─────────────────────────────────────────────────────────────── class AccessURLTests(TestCase): """Test URL routing.""" def test_roles_list_url(self): url = reverse("access:roles-list") self.assertEqual(url, "/access/roles/") def test_permissions_list_url(self): url = reverse("access:permissions-list") self.assertEqual(url, "/access/permissions/") def test_my_roles_url(self): url = reverse("access:my-roles") self.assertEqual(url, "/access/my-roles/")