@@ -1582,6 +1582,174 @@ def test_org_filter_combined_with_search(self):
15821582 self .assertIn (self .COURSE_ORG1 , external_keys )
15831583 self .assertNotIn (self .COURSE_ORG2 , external_keys )
15841584
1585+ # ------------------------------------------------------------------ #
1586+ # Orgs filter #
1587+ # ------------------------------------------------------------------ #
1588+
1589+ def test_orgs_filter_staff_courses (self ):
1590+ """Staff user with orgs param sees only courses from that org."""
1591+ self .build_qs_patcher .stop ()
1592+
1593+ response = self .client .get (self .url , {"orgs" : "Org1" , "scope_type" : "course" })
1594+
1595+ self .build_qs_patcher .start ()
1596+ self .assertEqual (response .status_code , status .HTTP_200_OK )
1597+ for item in response .data ["results" ]:
1598+ self .assertIn ("Org1" , item ["external_key" ])
1599+ # Org2 course should not appear
1600+ external_keys = [item ["external_key" ] for item in response .data ["results" ]]
1601+ self .assertNotIn (self .COURSE_ORG2 , external_keys )
1602+
1603+ def test_orgs_filter_staff_libraries (self ):
1604+ """Staff user with orgs param sees only libraries from that org."""
1605+ self .build_qs_patcher .stop ()
1606+
1607+ response = self .client .get (self .url , {"orgs" : "Org2" , "scope_type" : "library" })
1608+
1609+ self .build_qs_patcher .start ()
1610+ self .assertEqual (response .status_code , status .HTTP_200_OK )
1611+ external_keys = [item ["external_key" ] for item in response .data ["results" ]]
1612+ self .assertIn (self .LIBRARY_ORG2 , external_keys )
1613+ self .assertNotIn (self .LIBRARY_ORG1 , external_keys )
1614+
1615+ def test_orgs_filter_staff_no_match (self ):
1616+ """Staff user with orgs param for a non-existent org gets empty results."""
1617+ self .build_qs_patcher .stop ()
1618+
1619+ response = self .client .get (self .url , {"orgs" : "NonExistentOrg" , "scope_type" : "course" })
1620+
1621+ self .build_qs_patcher .start ()
1622+ self .assertEqual (response .status_code , status .HTTP_200_OK )
1623+ self .assertEqual (response .data ["count" ], 0 )
1624+
1625+ def test_orgs_filter_non_staff_with_permission (self ):
1626+ """Non-staff user with orgs param sees scopes only if they have permission for that org."""
1627+ # regular_1 has LIBRARY_USER on lib:Org1:LIB1 → VIEW_LIBRARY_TEAM granted
1628+ user = User .objects .get (username = "regular_1" )
1629+ self .client .force_authenticate (user = user )
1630+ self .build_qs_patcher .stop ()
1631+
1632+ response = self .client .get (self .url , {"orgs" : "Org1" , "scope_type" : "library" })
1633+
1634+ self .build_qs_patcher .start ()
1635+ self .assertEqual (response .status_code , status .HTTP_200_OK )
1636+ external_keys = [item ["external_key" ] for item in response .data ["results" ]]
1637+ self .assertIn (self .LIBRARY_ORG1 , external_keys )
1638+
1639+ def test_orgs_filter_non_staff_without_permission (self ):
1640+ """Non-staff user with org param for an org they have no permission for gets empty results."""
1641+ # regular_1 has no permissions on Org2
1642+ user = User .objects .get (username = "regular_1" )
1643+ self .client .force_authenticate (user = user )
1644+ self .build_qs_patcher .stop ()
1645+
1646+ response = self .client .get (self .url , {"orgs" : "Org2" , "scope_type" : "library" })
1647+
1648+ self .build_qs_patcher .start ()
1649+ self .assertEqual (response .status_code , status .HTTP_200_OK )
1650+ self .assertEqual (response .data ["count" ], 0 )
1651+
1652+ def test_orgs_filter_non_staff_courses (self ):
1653+ """Non-staff user with orgs param sees only courses from that org if they have permission."""
1654+ # regular_9 has COURSE_STAFF on COURSE_ORG1 → VIEW_COURSE_TEAM granted
1655+ user = User .objects .get (username = "regular_9" )
1656+ self .client .force_authenticate (user = user )
1657+ self .build_qs_patcher .stop ()
1658+
1659+ response = self .client .get (self .url , {"orgs" : "Org1" , "scope_type" : "course" })
1660+
1661+ self .build_qs_patcher .start ()
1662+ self .assertEqual (response .status_code , status .HTTP_200_OK )
1663+ external_keys = [item ["external_key" ] for item in response .data ["results" ]]
1664+ self .assertIn (self .COURSE_ORG1 , external_keys )
1665+
1666+ def test_orgs_filter_non_staff_courses_no_permission (self ):
1667+ """Non-staff user with orgs param for an org they have no course permission for gets empty results."""
1668+ # regular_9 has no course permissions on Org2
1669+ user = User .objects .get (username = "regular_9" )
1670+ self .client .force_authenticate (user = user )
1671+ self .build_qs_patcher .stop ()
1672+
1673+ response = self .client .get (self .url , {"orgs" : "Org2" , "scope_type" : "course" })
1674+
1675+ self .build_qs_patcher .start ()
1676+ self .assertEqual (response .status_code , status .HTTP_200_OK )
1677+ self .assertEqual (response .data ["count" ], 0 )
1678+
1679+ def test_orgs_filter_with_glob_permission (self ):
1680+ """Non-staff user with orgs glob permission and org filter sees only that org's scopes."""
1681+ user = User .objects .get (username = "regular_1" )
1682+ self .client .force_authenticate (user = user )
1683+ self .build_qs_patcher .stop ()
1684+
1685+ glob_scope = OrgContentLibraryGlobData (external_key = "lib:Org1:*" )
1686+ with patch (
1687+ "openedx_authz.rest_api.v1.views.get_scopes_for_user_and_permission" ,
1688+ return_value = [glob_scope ],
1689+ ):
1690+ response = self .client .get (self .url , {"orgs" : "Org1" , "scope_type" : "library" })
1691+
1692+ self .build_qs_patcher .start ()
1693+ self .assertEqual (response .status_code , status .HTTP_200_OK )
1694+ external_keys = [item ["external_key" ] for item in response .data ["results" ]]
1695+ self .assertIn (self .LIBRARY_ORG1 , external_keys )
1696+ self .assertNotIn (self .LIBRARY_ORG2 , external_keys )
1697+
1698+ def test_orgs_filter_with_glob_permission_wrong_org (self ):
1699+ """Non-staff user with org glob for Org1 but filtering by Org2 gets empty results."""
1700+ user = User .objects .get (username = "regular_1" )
1701+ self .client .force_authenticate (user = user )
1702+ self .build_qs_patcher .stop ()
1703+
1704+ glob_scope = OrgContentLibraryGlobData (external_key = "lib:Org1:*" )
1705+ with patch (
1706+ "openedx_authz.rest_api.v1.views.get_scopes_for_user_and_permission" ,
1707+ return_value = [glob_scope ],
1708+ ):
1709+ response = self .client .get (self .url , {"orgs" : "Org2" , "scope_type" : "library" })
1710+
1711+ self .build_qs_patcher .start ()
1712+ self .assertEqual (response .status_code , status .HTTP_200_OK )
1713+ self .assertEqual (response .data ["count" ], 0 )
1714+
1715+ def test_orgs_filter_combined_with_search (self ):
1716+ """Orgs filter works together with search filter."""
1717+ self .build_qs_patcher .stop ()
1718+
1719+ response = self .client .get (self .url , {"orgs" : "Org1" , "search" : "Course" , "scope_type" : "course" })
1720+
1721+ self .build_qs_patcher .start ()
1722+ self .assertEqual (response .status_code , status .HTTP_200_OK )
1723+ external_keys = [item ["external_key" ] for item in response .data ["results" ]]
1724+ self .assertIn (self .COURSE_ORG1 , external_keys )
1725+ self .assertNotIn (self .COURSE_ORG2 , external_keys )
1726+
1727+ def test_orgs_filter_combined_with_org (self ):
1728+ """Orgs filter works together with the singluar org filter."""
1729+ self .build_qs_patcher .stop ()
1730+
1731+ response = self .client .get (
1732+ self .url , {"org" : "Org2" , "orgs" : "Org1" , "search" : "Course" , "scope_type" : "course" }
1733+ )
1734+
1735+ self .build_qs_patcher .start ()
1736+ self .assertEqual (response .status_code , status .HTTP_200_OK )
1737+ external_keys = [item ["external_key" ] for item in response .data ["results" ]]
1738+ self .assertIn (self .COURSE_ORG1 , external_keys )
1739+ self .assertIn (self .COURSE_ORG2 , external_keys )
1740+
1741+ def test_orgs_filter_with_multiple_orgs (self ):
1742+ """Orgs filter with multiple orgs returns scopes from both orgs."""
1743+ self .build_qs_patcher .stop ()
1744+
1745+ response = self .client .get (self .url , {"orgs" : "Org1,Org2" , "search" : "Course" , "scope_type" : "course" })
1746+
1747+ self .build_qs_patcher .start ()
1748+ self .assertEqual (response .status_code , status .HTTP_200_OK )
1749+ external_keys = [item ["external_key" ] for item in response .data ["results" ]]
1750+ self .assertIn (self .COURSE_ORG1 , external_keys )
1751+ self .assertIn (self .COURSE_ORG2 , external_keys )
1752+
15851753
15861754@ddt
15871755class TestAdminConsoleOrgsAPIView (ViewTestMixin ):
0 commit comments