@@ -1318,7 +1318,14 @@ class TestTeamMemberAssignmentsAPIView(ViewTestMixin):
13181318 regular_7 (library_contributor), regular_8 (library_user)
13191319
13201320 URL: /authz/v1/users/<username>/assignments
1321- Response fields per item: role, org, scope, permission_count
1321+ Response fields per item: is_superadmin, role, org, scope, permission_count
1322+
1323+ Superadmin entry:
1324+ admin_1..3 are staff/superusers. Querying any of them always prepends one
1325+ SuperAdminAssignmentData entry: role="django.superuser" (or "django.staff"),
1326+ org="*", scope="*", permission_count=None, is_superadmin=True.
1327+ This entry is always included regardless of org/role filters, since those
1328+ filters are applied only to the role assignments, not to the superadmin entry.
13221329
13231330 Visibility via filter_allowed_assignments:
13241331 - Staff/superuser: sees all assignments for any user
@@ -1344,26 +1351,30 @@ def _url(self, username: str) -> str:
13441351 # -------------------------------------------------------------------- #
13451352
13461353 @data (
1347- # Staff/superuser sees all assignments for any user
1348- ("admin_1" , "admin_1" , 1 ),
1349- ("admin_1" , "admin_2" , 1 ),
1350- ("admin_1" , "admin_3" , 1 ),
1354+ # Staff/superuser targets get 1 superadmin entry + their role assignment(s)
1355+ ("admin_1" , "admin_1" , 2 ), # superadmin entry + library_admin in Org1
1356+ ("admin_1" , "admin_2" , 2 ), # superadmin entry + library_user in Org2
1357+ ("admin_1" , "admin_3" , 2 ), # superadmin entry + library_admin in Org3
1358+ # Regular user targets get only their role assignments (no superadmin entry)
13511359 ("admin_1" , "regular_5" , 1 ),
1352- # regular_1 has VIEW_LIBRARY on Org1:LIB1 only → sees admin_1's Org1 assignment
1353- ("regular_1" , "admin_1" , 1 ),
1354- # regular_1 cannot see admin_2's Org2 assignment
1355- ("regular_1" , "admin_2" , 0 ),
1356- # regular_9 has no assignments → sees nothing for any user
1357- ("regular_9" , "admin_1" , 0 ),
1360+ # The superadmin entry is always included for superadmin targets, visible to all callers
1361+ ("regular_1" , "admin_1" , 2 ), # superadmin entry + library_admin in Org1 (visible via Org1 access)
1362+ # regular_1 cannot see admin_2's Org2 role assignment, but superadmin entry is still included
1363+ ("regular_1" , "admin_2" , 1 ), # superadmin entry only
1364+ # regular_9 has no assignments but superadmin entry is still included for admin targets
1365+ ("regular_9" , "admin_1" , 1 ), # superadmin entry only
13581366 )
13591367 @unpack
13601368 def test_visibility_limited_to_accessible_scopes (self , caller : str , target : str , expected_count : int ):
1361- """Calling user only sees assignments for scopes it has view access to.
1369+ """Calling user only sees role assignments for scopes it has view access to.
1370+
1371+ The superadmin entry is always included when the target is a superadmin,
1372+ regardless of the calling user's permissions.
13621373
13631374 Expected result:
1364- - Staff/superuser sees all assignments for any user .
1365- - Regular users only see assignments in scopes they can view .
1366- - Users with no assignments see no results for any user .
1375+ - Superadmin targets always include the superadmin entry .
1376+ - Role assignments are filtered by the calling user's permissions .
1377+ - Regular user targets return only their visible role assignments .
13671378 """
13681379 self .client .force_authenticate (user = User .objects .get (username = caller ))
13691380
@@ -1400,14 +1411,14 @@ def test_unknown_user_returns_empty(self):
14001411 # ------------------------------------------------------------------ #
14011412
14021413 @data (
1403- # admin_3 has library_admin in lib:Org3:LIB3
1404- ("admin_3" , "Org3" , 1 ),
1405- ("admin_3" , "Org1" , 0 ),
1406- # regular_5 has library_admin in lib:Org3:LIB3
1414+ # admin_3 has library_admin in lib:Org3:LIB3; superadmin entry is always included
1415+ ("admin_3" , "Org3" , 2 ), # superadmin entry + Org3 role assignment
1416+ ("admin_3" , "Org1" , 1 ), # superadmin entry only (no Org1 role assignment)
1417+ # regular_5 has library_admin in lib:Org3:LIB3 (no superadmin entry)
14071418 ("regular_5" , "Org3" , 1 ),
14081419 ("regular_5" , "Org1" , 0 ),
1409- # non-existent org returns no results
1410- ("admin_1" , "OrgX" , 0 ),
1420+ # non-existent org: superadmin entry still included for admin targets
1421+ ("admin_1" , "OrgX" , 1 ), # superadmin entry only
14111422 )
14121423 @unpack
14131424 def test_filter_by_orgs (self , target : str , orgs : str , expected_count : int ):
@@ -1442,13 +1453,14 @@ def test_filter_by_multiple_orgs(self):
14421453 # ------------------------------------------------------------------ #
14431454
14441455 @data (
1445- ("admin_1" , roles .LIBRARY_ADMIN .external_key , 1 ),
1446- ("admin_1" , roles .LIBRARY_USER .external_key , 0 ),
1456+ # role filter applies only to role assignments; superadmin entry is always included for admin targets
1457+ ("admin_1" , roles .LIBRARY_ADMIN .external_key , 2 ), # superadmin entry + library_admin
1458+ ("admin_1" , roles .LIBRARY_USER .external_key , 1 ), # superadmin entry only
14471459 ("regular_5" , roles .LIBRARY_ADMIN .external_key , 1 ),
14481460 ("regular_5" , roles .LIBRARY_USER .external_key , 0 ),
14491461 ("regular_6" , roles .LIBRARY_AUTHOR .external_key , 1 ),
14501462 ("regular_6" , roles .LIBRARY_ADMIN .external_key , 0 ),
1451- ("admin_1" , "non_existent_role" , 0 ),
1463+ ("admin_1" , "non_existent_role" , 1 ), # superadmin entry only
14521464 )
14531465 @unpack
14541466 def test_filter_by_roles (self , target : str , role_filter : str , expected_count : int ):
@@ -1463,20 +1475,20 @@ def test_filter_by_roles(self, target: str, role_filter: str, expected_count: in
14631475 self .assertEqual (response .data ["count" ], expected_count )
14641476
14651477 def test_filter_by_multiple_roles (self ):
1466- """Multiple roles are OR-combined.
1478+ """Multiple roles are OR-combined for role assignments; superadmin entry always included .
14671479
14681480 Expected result:
1469- - Returns assignments matching any of the given roles.
1481+ - Returns assignments matching any of the given roles, plus the superadmin entry .
14701482 """
1471- # regular_6 has library_author, regular_7 has library_contributor — use admin_3
1472- # who has library_admin in Org3:LIB3; filter for admin + author returns 1
1483+ # admin_3 has library_admin in Org3:LIB3; filter for admin + author returns
1484+ # 1 role assignment + 1 superadmin entry = 2
14731485 response = self .client .get (
14741486 self ._url ("admin_3" ),
14751487 {"roles" : f"{ roles .LIBRARY_ADMIN .external_key } ,{ roles .LIBRARY_AUTHOR .external_key } " },
14761488 )
14771489
14781490 self .assertEqual (response .status_code , status .HTTP_200_OK )
1479- self .assertEqual (response .data ["count" ], 1 )
1491+ self .assertEqual (response .data ["count" ], 2 )
14801492
14811493 # ------------------------------------------------------------------ #
14821494 # Sorting #
@@ -1566,24 +1578,35 @@ def test_pagination(self, query_params: dict, expected_page_count: int, has_next
15661578 def test_response_shape (self ):
15671579 """Each result item contains the expected fields.
15681580
1581+ admin_1 is a superuser, so the response contains two items:
1582+ - A superadmin entry with role="django.superuser", org="*", scope="*",
1583+ permission_count=None, is_superadmin=True
1584+ - A regular role assignment entry with concrete values and is_superadmin=False
1585+
15691586 Expected result:
15701587 - Returns 200 OK.
1571- - Each item has role, org, scope, and permission_count.
1572- - Values match the known assignment for admin_1.
1588+ - Each item has is_superadmin, role, org, scope, and permission_count.
15731589 """
15741590 response = self .client .get (self ._url ("admin_1" ))
15751591
15761592 self .assertEqual (response .status_code , status .HTTP_200_OK )
1577- self .assertEqual (response .data ["count" ], 1 )
1578- item = response .data ["results" ][0 ]
1579- self .assertIn ("role" , item )
1580- self .assertIn ("org" , item )
1581- self .assertIn ("scope" , item )
1582- self .assertIn ("permission_count" , item )
1583- self .assertEqual (item ["role" ], roles .LIBRARY_ADMIN .external_key )
1584- self .assertEqual (item ["org" ], "Org1" )
1585- self .assertEqual (item ["scope" ], "lib:Org1:LIB1" )
1586- self .assertGreater (item ["permission_count" ], 0 )
1593+ self .assertEqual (response .data ["count" ], 2 )
1594+
1595+ superadmin_item = next (item for item in response .data ["results" ] if item ["is_superadmin" ])
1596+ self .assertIn (superadmin_item ["role" ], ("django.superuser" , "django.staff" ))
1597+ self .assertEqual (superadmin_item ["org" ], "*" )
1598+ self .assertEqual (superadmin_item ["scope" ], "*" )
1599+ self .assertIsNone (superadmin_item ["permission_count" ])
1600+
1601+ role_item = next (item for item in response .data ["results" ] if not item ["is_superadmin" ])
1602+ self .assertIn ("role" , role_item )
1603+ self .assertIn ("org" , role_item )
1604+ self .assertIn ("scope" , role_item )
1605+ self .assertIn ("permission_count" , role_item )
1606+ self .assertEqual (role_item ["role" ], roles .LIBRARY_ADMIN .external_key )
1607+ self .assertEqual (role_item ["org" ], "Org1" )
1608+ self .assertEqual (role_item ["scope" ], "lib:Org1:LIB1" )
1609+ self .assertGreater (role_item ["permission_count" ], 0 )
15871610
15881611
15891612@ddt
0 commit comments