From 23c86b8dfbaf91207b47b4dbc594539cef0e30ca Mon Sep 17 00:00:00 2001 From: NikshetSteh <88205797+NikshetSteh@users.noreply.github.com> Date: Sun, 22 Jun 2025 15:10:09 +0500 Subject: [PATCH] feat: add pagination support to get realm roles (#659) --- src/keycloak/keycloak_admin.py | 34 +++++++++++++---------- tests/test_keycloak_admin.py | 51 ++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py index 42fb668..6135d4d 100644 --- a/src/keycloak/keycloak_admin.py +++ b/src/keycloak/keycloak_admin.py @@ -2910,7 +2910,9 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) - def get_realm_roles(self, brief_representation: bool = True, search_text: str = "") -> list: + def get_realm_roles( + self, brief_representation: bool = True, search_text: str = "", query: dict | None = None + ) -> list: """ Get all roles for the realm or client. @@ -2921,20 +2923,23 @@ class KeycloakAdmin: :type brief_representation: bool :param search_text: optional search text to limit the returned result. :type search_text: str + :param query: Query parameters (optional) + :type query: dict :return: Keycloak server response (RoleRepresentation) :rtype: list """ + query = query or {} params_path = {"realm-name": self.connection.realm_name} params = {"briefRepresentation": brief_representation} + url = urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path) if search_text is not None and search_text.strip() != "": params["search"] = search_text - data_raw = self.connection.raw_get( - urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), - **params, - ) - return raise_error_from_response(data_raw, KeycloakGetError) + if "first" in query and "max" in query: + return self.__fetch_paginated(url, query) + + return self.__fetch_all(url, params) def get_realm_role_groups( self, @@ -8176,9 +8181,7 @@ class KeycloakAdmin: return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[HTTP_OK]) async def a_get_realm_roles( - self, - brief_representation: bool = True, - search_text: str = "", + self, brief_representation: bool = True, search_text: str = "", query: dict | None = None ) -> list: """ Get all roles for the realm or client asynchronously. @@ -8190,20 +8193,23 @@ class KeycloakAdmin: :type brief_representation: bool :param search_text: optional search text to limit the returned result. :type search_text: str + :param query: Query parameters (optional) + :type query: dict :return: Keycloak server response (RoleRepresentation) :rtype: list """ + query = query or {} params_path = {"realm-name": self.connection.realm_name} params = {"briefRepresentation": brief_representation} + url = urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path) if search_text is not None and search_text.strip() != "": params["search"] = search_text - data_raw = await self.connection.a_raw_get( - urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), - **params, - ) - return raise_error_from_response(data_raw, KeycloakGetError) + if "first" in query and "max" in query: + return await self.a___fetch_paginated(url, query) + + return await self.a___fetch_all(url, params) async def a_get_realm_role_groups( self, diff --git a/tests/test_keycloak_admin.py b/tests/test_keycloak_admin.py index 5e01291..914d7eb 100644 --- a/tests/test_keycloak_admin.py +++ b/tests/test_keycloak_admin.py @@ -1662,6 +1662,31 @@ def test_realm_roles(admin: KeycloakAdmin, realm: str) -> None: assert err.match(COULD_NOT_FIND_ROLE_REGEX) +def test_realm_roles_pagination(admin: KeycloakAdmin, realm: str) -> None: + """ + Test realm roles pagination. + + :param admin: Keycloak admin + :type admin: KeycloakAdmin + :param realm: Keycloak realm + :type realm: str + """ + admin.change_current_realm(realm) + + for ind in range(admin.PAGE_SIZE + 50 - 3): + role_name = f"role_{ind:03}" + admin.create_realm_role(payload={"name": role_name}) + + roles = admin.get_realm_roles() + assert len(roles) == admin.PAGE_SIZE + 50, len(roles) + + roles = admin.get_realm_roles(query={"first": 100, "max": 20}) + assert len(roles) == 20, len(roles) + + roles = admin.get_realm_roles(query={"first": 120, "max": 50}) + assert len(roles) == 30, len(roles) + + @pytest.mark.parametrize( ("testcase", "arg_brief_repr", "includes_attributes"), [ @@ -5194,6 +5219,32 @@ async def test_a_realm_roles(admin: KeycloakAdmin, realm: str) -> None: assert err.match(COULD_NOT_FIND_ROLE_REGEX) +@pytest.mark.asyncio +async def test_a_realm_roles_pagination(admin: KeycloakAdmin, realm: str) -> None: + """ + Test realm roles pagination. + + :param admin: Keycloak admin + :type admin: KeycloakAdmin + :param realm: Keycloak realm + :type realm: str + """ + admin.change_current_realm(realm) + + for ind in range(admin.PAGE_SIZE + 50 - 3): + role_name = f"role_{ind:03}" + admin.create_realm_role(payload={"name": role_name}) + + roles = await admin.a_get_realm_roles() + assert len(roles) == admin.PAGE_SIZE + 50, len(roles) + + roles = await admin.a_get_realm_roles(query={"first": 100, "max": 20}) + assert len(roles) == 20, len(roles) + + roles = await admin.a_get_realm_roles(query={"first": 120, "max": 50}) + assert len(roles) == 30, len(roles) + + @pytest.mark.asyncio @pytest.mark.parametrize( ("testcase", "arg_brief_repr", "includes_attributes"),