From e9667be3476a03621ecfb9c391630a6dca8558d0 Mon Sep 17 00:00:00 2001 From: dhaura Date: Fri, 22 Nov 2024 12:15:27 +0530 Subject: [PATCH] Add unit tests and improve javadoc comments. --- .../pom.xml | 11 +- .../service/OrgResourceResolverService.java | 46 +- .../OrgResourceResolverServiceImpl.java | 11 +- ...OrgResourceHierarchyTraverseConstants.java | 42 +- ...ourceHierarchyTraverseClientException.java | 12 +- ...OrgResourceHierarchyTraverseException.java | 7 +- ...ourceHierarchyTraverseServerException.java | 5 +- ...urceHierarchyTraverseServiceComponent.java | 37 +- ...rceHierarchyTraverseServiceDataHolder.java | 23 +- .../service/strategy/AggregationStrategy.java | 51 +- .../FirstFoundAggregationStrategy.java | 6 +- .../strategy/MergeAllAggregationStrategy.java | 10 +- .../OrgResourceHierarchyTraverseUtil.java | 60 +- ...gResourceResolverHierarchyServiceTest.java | 575 ++++++++++++++++++ .../impl/MockResourceManagementService.java | 105 ++++ .../resource/impl/model/MockResource.java | 143 +++++ .../src/test/resources/testng.xml | 1 + 17 files changed, 1052 insertions(+), 93 deletions(-) create mode 100644 components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverHierarchyServiceTest.java create mode 100644 components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/MockResourceManagementService.java create mode 100644 components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/model/MockResource.java diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/pom.xml b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/pom.xml index 582ed4bf8..233f0df56 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/pom.xml +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/pom.xml @@ -149,6 +149,15 @@ org.jacoco jacoco-maven-plugin ${jacoco.version} + + + org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/constant/*.class + org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/*.class + org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/*.class + org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/AggregationStrategy.class + org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverService.class + + default-prepare-agent @@ -187,7 +196,7 @@ COMPLEXITY COVEREDRATIO - + 0.60 diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverService.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverService.java index 604c83199..cb6646d5f 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverService.java +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverService.java @@ -26,19 +26,28 @@ import java.util.function.Function; /** - * Service interface for organization resource resolver. + * Provides a service interface to retrieve resources from an organization's hierarchy. + * Supports traversal of both organization and application hierarchies using customizable + * resource retrieval and aggregation strategies. + *

+ * The service is designed for extensibility, allowing clients to define their own retrieval logic + * and aggregation mechanisms via functional interfaces and strategy patterns. */ public interface OrgResourceResolverService { /** - * Get resources from the organization hierarchy. + * Retrieves resources by traversing the hierarchy of a given organization. * - * @param organizationId Organization ID. - * @param resourceRetriever Function to retrieve the resource. - * @param aggregationStrategy Aggregation strategy. - * @param Type of the resource. - * @return Resolved resources. - * @throws OrgResourceHierarchyTraverseException If an error occurs while retrieving the resources. + * @param organizationId The unique identifier of the organization. + * @param resourceRetriever A function that defines how to fetch a resource for a given organization ID. + * The function must return an {@link Optional} containing the resource if found, + * or an empty {@link Optional} if not. + * @param aggregationStrategy A strategy defining how to aggregate resources retrieved from + * different levels of the hierarchy. + * @param The type of the resource being retrieved and aggregated. + * @return An aggregated resource of type obtained from the organization hierarchy. + * @throws OrgResourceHierarchyTraverseException If any errors occur during resource retrieval + * or aggregation. */ T getResourcesFromOrgHierarchy(String organizationId, Function> resourceRetriever, @@ -46,15 +55,20 @@ T getResourcesFromOrgHierarchy(String organizationId, throws OrgResourceHierarchyTraverseException; /** - * Get resources from the organization and application hierarchy. + * Retrieves resources by traversing the hierarchy of a given organization and application. * - * @param organizationId Organization ID. - * @param applicationId Application ID. - * @param resourceRetriever Function to retrieve the resource. - * @param aggregationStrategy Aggregation strategy. - * @param Type of the resource. - * @return Resolved resources. - * @throws OrgResourceHierarchyTraverseException If an error occurs while retrieving the resources. + * @param organizationId The unique identifier of the organization. Must not be null. + * @param applicationId The unique identifier of the application within the organization. Must not be null. + * @param resourceRetriever A bi-function that defines how to fetch a resource based on the + * organization and application IDs. The function must return an + * {@link Optional} containing the resource if found, + * or an empty {@link Optional} if not. + * @param aggregationStrategy A strategy defining how to aggregate resources retrieved from + * different levels of the hierarchy. + * @param The type of the resource being retrieved and aggregated. + * @return An aggregated resource of type obtained from the organization and application hierarchy. + * @throws OrgResourceHierarchyTraverseException If any errors occur during resource retrieval + * or aggregation. */ T getResourcesFromOrgHierarchy(String organizationId, String applicationId, BiFunction> resourceRetriever, diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverServiceImpl.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverServiceImpl.java index c41fd5997..b4974135a 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverServiceImpl.java +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverServiceImpl.java @@ -22,7 +22,7 @@ import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; import org.wso2.carbon.identity.organization.management.service.OrganizationManager; -import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementServerException; import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.constant.OrgResourceHierarchyTraverseConstants; import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseException; import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.internal.OrgResourceHierarchyTraverseServiceDataHolder; @@ -37,7 +37,8 @@ import java.util.function.Function; /** - * Implementation of the OrgResourceResolverService. + * Implementation of the OrgResourceResolverService interface, responsible for resolving resources within the + * given organization/ application hierarchy. */ public class OrgResourceResolverServiceImpl implements OrgResourceResolverService { @@ -53,7 +54,7 @@ public T getResourcesFromOrgHierarchy(String organizationId, Function T getResourcesFromOrgHierarchy(String organizationId, String applicat } return aggregationStrategy.aggregate(organizationIds, ancestorAppIds, resourceRetriever); - } catch (OrganizationManagementException e) { + } catch (OrganizationManagementServerException e) { throw OrgResourceHierarchyTraverseUtil.handleServerException( OrgResourceHierarchyTraverseConstants.ErrorMessages .ERROR_CODE_SERVER_ERROR_WHILE_RESOLVING_ANCESTOR_ORGANIZATIONS, e, organizationId); @@ -92,7 +93,7 @@ public T getResourcesFromOrgHierarchy(String organizationId, String applicat } private List getAncestorOrganizationsIds(String organizationId) - throws OrganizationManagementException { + throws OrganizationManagementServerException { OrganizationManager organizationManager = OrgResourceHierarchyTraverseUtil.getOrganizationManager(); return organizationManager.getAncestorOrganizationIds(organizationId); diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/constant/OrgResourceHierarchyTraverseConstants.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/constant/OrgResourceHierarchyTraverseConstants.java index ba4b09ddf..ef91a2fd0 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/constant/OrgResourceHierarchyTraverseConstants.java +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/constant/OrgResourceHierarchyTraverseConstants.java @@ -19,18 +19,23 @@ package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.constant; /** - * Constants for organization resource hierarchy traverse service. + * Constants defined for the organization resource hierarchy traverse service. */ public class OrgResourceHierarchyTraverseConstants { private static final String ORGANIZATION_RESOURCE_HIERARCHY_TRAVERSE_ERROR_CODE_PREFIX = "ORHT-"; + /** + * Private constructor to prevent instantiation of this constant class. + */ private OrgResourceHierarchyTraverseConstants() { } /** - * Enum for error messages related to organization resource hierarchy traverse service. + * Enum which provides error codes and predefined error messages related to the traversal of + * the organization resource hierarchy. It ensures that error handling within the service is consistent + * and that detailed error descriptions are available for debugging and troubleshooting. */ public enum ErrorMessages { @@ -50,6 +55,16 @@ public enum ErrorMessages { private final String message; private final String description; + /** + * Constructor for the ErrorMessages enum. + *

+ * This constructor is used to define each error message with a unique error code, a brief message, and + * a detailed description. + * + * @param code The unique error code for the message (prefixed with "ORHT-"). + * @param message A brief message describing the error. + * @param description A detailed description of the error, often including placeholders for dynamic data. + */ ErrorMessages(String code, String message, String description) { this.code = code; @@ -57,16 +72,39 @@ public enum ErrorMessages { this.description = description; } + /** + * Gets the unique error code for the error message. + *

+ * The error code is prefixed with "ORHT-" to ensure consistency in the error code system. + * + * @return The error code prefixed with "ORHT-". + */ public String getCode() { return ORGANIZATION_RESOURCE_HIERARCHY_TRAVERSE_ERROR_CODE_PREFIX + code; } + /** + * Gets the brief message for the error. + *

+ * This message provides a short description of the error, usually without context-specific details. + * It is used for logging or displaying to the user as part of the error response. + * + * @return A brief message describing the error. + */ public String getMessage() { return message; } + /** + * Gets the detailed description for the error message. + *

+ * This description provides a more detailed explanation of the error and often contains placeholders + * for dynamic data (e.g., organization or application IDs). It is typically used for logging or debugging. + * + * @return A detailed description of the error, including placeholders for dynamic data. + */ public String getDescription() { return description; diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseClientException.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseClientException.java index 00d675930..26ac0cd0d 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseClientException.java +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseClientException.java @@ -25,14 +25,20 @@ import java.util.List; /** - * Exception class for client side errors in organization resource hierarchy traverse. + * Exception class for client-side errors during organization resource hierarchy traversal. + *

+ * This class handles exceptions where client-side errors occur, capturing error codes, messages, and descriptions + * to provide detailed context for troubleshooting. */ public class OrgResourceHierarchyTraverseClientException extends OrgResourceHierarchyTraverseException { - private static final long serialVersionUID = 559143944402014381L; - private String[] messages; + /** + * Constructs a new exception with an array of specified error messages. + * + * @param messages Detailed error messages + */ public OrgResourceHierarchyTraverseClientException(String[] messages) { super(Arrays.toString(messages)); diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseException.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseException.java index 2cc442a70..128557dc4 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseException.java +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseException.java @@ -19,14 +19,17 @@ package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception; /** - * Exception class that represents exceptions thrown upon organization resource hierarchy traverse. + * Custom exception for errors encountered during organization resource hierarchy traversal. + *

+ * This exception captures detailed error information, including error codes, messages, descriptions, + * and causes, to assist in troubleshooting issues related to resolving ancestor organizations or applications. */ public class OrgResourceHierarchyTraverseException extends Exception { private String errorCode; private String description; - private static final long serialVersionUID = -1982152066401023165L; + private static final long serialVersionUID = 5967152066669023668L; /** * Constructs a new exception with the specified message. diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseServerException.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseServerException.java index 28f2c30b9..531b834e0 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseServerException.java +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/exception/OrgResourceHierarchyTraverseServerException.java @@ -19,7 +19,10 @@ package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception; /** - * Exception class that represents server side errors in organization resource hierarchy traverse. + * Exception class for server-side errors during organization resource hierarchy traversal. + *

+ * This class handles exceptions related to server-side failures, including error codes, messages, + * descriptions, and causes, to provide detailed information for debugging. */ public class OrgResourceHierarchyTraverseServerException extends OrgResourceHierarchyTraverseException { diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceComponent.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceComponent.java index 109c26397..deec43f48 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceComponent.java +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceComponent.java @@ -33,7 +33,10 @@ import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.OrgResourceResolverServiceImpl; /** - * OSGi declarative services component of the organization resource hierarchy traverse service. + * OSGi component responsible for managing the activation and deactivation of the organization resource hierarchy + * traverse service. + *

+ * It manages dynamic references to necessary services required by {@link OrgResourceResolverService} as well. */ @Component( name = "org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service", @@ -42,6 +45,12 @@ public class OrgResourceHierarchyTraverseServiceComponent { private static final Log log = LogFactory.getLog(OrgResourceHierarchyTraverseServiceComponent.class); + /** + * Activates the OSGi component by registering the {@link OrgResourceResolverService} service. + * This method is called when the component is activated in the OSGi environment. + * + * @param context The ComponentContext instance that provides the OSGi environment context. + */ @Activate protected void activate(ComponentContext context) { @@ -57,6 +66,12 @@ protected void activate(ComponentContext context) { } } + /** + * Deactivates the OSGi component by cleaning up resources and logging the deactivation. + * This method is called when the component is deactivated in the OSGi environment. + * + * @param context The ComponentContext instance that provides the OSGi environment context. + */ @Deactivate protected void deactivate(ComponentContext context) { @@ -65,6 +80,11 @@ protected void deactivate(ComponentContext context) { } } + /** + * Sets the OrganizationManager instance in the OrgResourceHierarchyTraverseServiceDataHolder. + * + * @param organizationManager The OrganizationManager instance to be assigned. + */ @Reference(name = "org.wso2.carbon.identity.organization.management.service", service = OrganizationManager.class, cardinality = ReferenceCardinality.OPTIONAL, @@ -76,12 +96,22 @@ protected void setOrganizationManager(OrganizationManager organizationManager) { log.debug("OrganizationManager set in OrgResourceManagementServiceComponent bundle."); } + /** + * Unsets the OrganizationManager instance in the OrgResourceHierarchyTraverseServiceDataHolder. + * + * @param organizationManager The OrganizationManager instance to be removed. + */ protected void unsetOrganizationManager(OrganizationManager organizationManager) { OrgResourceHierarchyTraverseServiceDataHolder.getInstance().setOrganizationManager(null); log.debug("OrganizationManager unset in OrgResourceManagementServiceComponent bundle."); } + /** + * Sets the ApplicationManagementService instance in the OrgResourceHierarchyTraverseServiceDataHolder. + * + * @param applicationManagementService The ApplicationManagementService instance to be assigned. + */ @Reference( name = "org.wso2.carbon.identity.application.mgt", service = ApplicationManagementService.class, @@ -95,6 +125,11 @@ protected void setApplicationManagementService(ApplicationManagementService appl log.debug("ApplicationManagementService set in OrgResourceManagementServiceComponent bundle."); } + /** + * Unsets the ApplicationManagementService instance in the OrgResourceHierarchyTraverseServiceDataHolder. + * + * @param applicationManagementService The ApplicationManagementService instance to be removed. + */ protected void unsetApplicationManagementService(ApplicationManagementService applicationManagementService) { OrgResourceHierarchyTraverseServiceDataHolder.getInstance().setApplicationManagementService(null); diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceDataHolder.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceDataHolder.java index b4f1f58e3..5a5c6ba02 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceDataHolder.java +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/internal/OrgResourceHierarchyTraverseServiceDataHolder.java @@ -22,7 +22,8 @@ import org.wso2.carbon.identity.organization.management.service.OrganizationManager; /** - * This class holds the data required for the organization resource hierarchy traverse service. + * Singleton class that serves as a centralized data holder for key service instances used in the organization + * resource hierarchy traversal process. */ public class OrgResourceHierarchyTraverseServiceDataHolder { @@ -37,9 +38,9 @@ private OrgResourceHierarchyTraverseServiceDataHolder() { } /** - * Get the instance of OrgResourceManagementServiceDataHolder. + * Retires the Singleton instance of the OrgResourceHierarchyTraverseServiceDataHolder class. * - * @return OrgResourceManagementServiceDataHolder instance. + * @return The singleton instance of OrgResourceHierarchyTraverseServiceDataHolder. */ public static OrgResourceHierarchyTraverseServiceDataHolder getInstance() { @@ -47,9 +48,9 @@ public static OrgResourceHierarchyTraverseServiceDataHolder getInstance() { } /** - * Get the organization manager. + * Retrieves the current instance of the OrganizationManager. * - * @return Organization manager. + * @return The current OrganizationManager instance that manages organizational data. */ public OrganizationManager getOrganizationManager() { @@ -57,9 +58,9 @@ public OrganizationManager getOrganizationManager() { } /** - * Set the organization manager. + * Sets the OrganizationManager instance. * - * @param organizationManager Organization manager instance. + * @param organizationManager The OrganizationManager instance to be assigned. */ public void setOrganizationManager( OrganizationManager organizationManager) { @@ -68,9 +69,9 @@ public void setOrganizationManager( } /** - * Get the application management service. + * Retrieves the current instance of the ApplicationManagementService. * - * @return Application management service. + * @return The current ApplicationManagementService instance responsible for managing applications. */ public ApplicationManagementService getApplicationManagementService() { @@ -78,9 +79,9 @@ public ApplicationManagementService getApplicationManagementService() { } /** - * Set the application management service. + * Sets the ApplicationManagementService instance. * - * @param applicationManagementService Application management service instance. + * @param applicationManagementService The ApplicationManagementService instance to be set. */ public void setApplicationManagementService( ApplicationManagementService applicationManagementService) { diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/AggregationStrategy.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/AggregationStrategy.java index ef23bddde..ef0798a84 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/AggregationStrategy.java +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/AggregationStrategy.java @@ -28,19 +28,33 @@ import java.util.function.Function; /** - * Interface for aggregation strategies. + * Defines an interface for a strategy used for aggregating resources retrieved from hierarchical structures, + * such as organization and application hierarchies. This interface provides default + * methods that can be overridden to implement specific aggregation logic. + *

+ * Implementations should specify how resources are combined and may use functional + * interfaces for flexible retrieval mechanisms. * - * @param Type of the resource to be aggregated. + * @param The type of the resource to be aggregated. */ public interface AggregationStrategy { /** - * Aggregate resolved resources of the given organization hierarchy. + * Aggregates resources resolved from an organization's hierarchical structure. + *

+ * This method provides a default implementation that throws a + * {@link NotImplementedException}. Subclasses must override this method to define + * specific aggregation logic for organization hierarchies. * - * @param organizationHierarchy Organization hierarchy. - * @param resourceRetriever Function to retrieve the resource. - * @return Aggregated resource. - * @throws OrgResourceHierarchyTraverseException If an error occurs while aggregating the resources. + * @param organizationHierarchy A list representing the organization hierarchy, + * where the first element is the root organization + * and subsequent elements represent child organizations. + * @param resourceRetriever A function that retrieves a resource given an organization ID. + * Returns an {@link Optional} containing the resource, or empty + * if no resource is found for the given ID. + * @return The aggregated resource of type . + * @throws OrgResourceHierarchyTraverseException If any error occurs during resource + * retrieval or aggregation. */ default T aggregate(List organizationHierarchy, Function> resourceRetriever) throws OrgResourceHierarchyTraverseException { @@ -49,13 +63,24 @@ default T aggregate(List organizationHierarchy, Function + * This method provides a default implementation that throws a + * {@link NotImplementedException}. Subclasses must override this method to define + * specific aggregation logic for combined organization and application hierarchies. * - * @param organizationHierarchy Organization hierarchy. - * @param applicationHierarchy Application hierarchy. - * @param resourceRetriever Function to retrieve the resource. - * @return Aggregated resource. - * @throws OrgResourceHierarchyTraverseException If an error occurs while aggregating the resources. + * @param organizationHierarchy A list representing the organization hierarchy, + * where the first element is the root organization + * and subsequent elements represent child organizations. + * @param applicationHierarchy A map representing the application hierarchy, where keys + * are organization IDs, and values are application-specific + * details or IDs for each organization. + * @param resourceRetriever A bi-function that retrieves a resource based on both an + * organization ID and an application ID. Returns an {@link Optional} + * containing the resource, or empty if no resource is found. + * @return The aggregated resource of type . + * @throws OrgResourceHierarchyTraverseException If any error occurs during resource + * retrieval or aggregation. */ default T aggregate(List organizationHierarchy, Map applicationHierarchy, BiFunction> resourceRetriever) diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/FirstFoundAggregationStrategy.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/FirstFoundAggregationStrategy.java index 3ee3120f1..cfd473cc2 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/FirstFoundAggregationStrategy.java +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/FirstFoundAggregationStrategy.java @@ -29,9 +29,11 @@ import java.util.function.Function; /** - * Aggregation strategy to retrieve the first found resource in the organization hierarchy. + * Aggregation strategy that can be used to traverse the organization hierarchy and retrieve the first resource found. + * This strategy is commonly applied when multiple resources might exist at different levels of the hierarchy, + * and only the first one encountered at the bottom of the hierarchy needs to be returned. * - * @param Type of the resource. + * @param The type of the resource being retrieved from the organization/ application hierarchy. */ public class FirstFoundAggregationStrategy implements AggregationStrategy { diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/MergeAllAggregationStrategy.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/MergeAllAggregationStrategy.java index 648acb898..f911b2700 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/MergeAllAggregationStrategy.java +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/strategy/MergeAllAggregationStrategy.java @@ -29,9 +29,15 @@ import java.util.function.Function; /** - * Aggregation strategy to merge all resources in the organization hierarchy with the given resource merger function. + * Aggregation strategy to merge all resources in the organization hierarchy using the specified + * resource merger function. + *

+ * This strategy traverses the hierarchy and applies the provided function + * to combine the resources at each level. It ensures that the resources are merged according to the + * logic defined by the merger function, which could involve combining attributes, performing calculations, or + * resolving conflicts between resources. * - * @param Type of the resource. + * @param The type of the resources being merged in the organization/ application hierarchy. */ public class MergeAllAggregationStrategy implements AggregationStrategy { diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/util/OrgResourceHierarchyTraverseUtil.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/util/OrgResourceHierarchyTraverseUtil.java index 68adcc9d3..b48159dc8 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/util/OrgResourceHierarchyTraverseUtil.java +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/main/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/util/OrgResourceHierarchyTraverseUtil.java @@ -20,26 +20,31 @@ import org.apache.commons.lang.ArrayUtils; import org.wso2.carbon.identity.organization.management.service.OrganizationManager; -import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementServerException; import org.wso2.carbon.identity.organization.management.service.util.Utils; import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.constant.OrgResourceHierarchyTraverseConstants; -import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseClientException; import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseServerException; import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.internal.OrgResourceHierarchyTraverseServiceDataHolder; /** - * Utility class for organization resource hierarchy traverse service. + * Utility class for the Organization Resource Hierarchy Traverse Service. + *

+ * This class provides helper methods to interact with the organization hierarchy and manage traversal logic, + * such as verifying the hierarchy depth and handling server-side exceptions. */ public class OrgResourceHierarchyTraverseUtil { + /** + * Private constructor to prevent instantiation of the utility class. + */ private OrgResourceHierarchyTraverseUtil() { } /** - * Get the organization manager. + * Retrieve the organization manager instance. * - * @return Organization manager. + * @return The {@link OrganizationManager} instance used to manage organizations. */ public static OrganizationManager getOrganizationManager() { @@ -47,11 +52,14 @@ public static OrganizationManager getOrganizationManager() { } /** - * Check whether the minimum organization hierarchy depth is reached. + * Verify if the organization hierarchy depth has reached the minimum required level. + *

+ * This method obtains the depth of the specified organization in the hierarchy and compares it + * with the configured minimum depth. An exception is thrown if an error occurs during the depth calculation. * - * @param orgId Organization ID. - * @return True if the minimum hierarchy depth is reached. - * @throws OrgResourceHierarchyTraverseServerException If an error occurs while checking the hierarchy depth. + * @param orgId The ID of the organization to check. + * @return {@code true} if the hierarchy depth is less than the minimum required depth, {@code false} otherwise. + * @throws OrgResourceHierarchyTraverseServerException If an error occurs while retrieving the organization's depth. */ public static boolean isMinOrgHierarchyDepthReached(String orgId) throws OrgResourceHierarchyTraverseServerException { @@ -60,38 +68,22 @@ public static boolean isMinOrgHierarchyDepthReached(String orgId) throws try { int depthInHierarchy = getOrganizationManager().getOrganizationDepthInHierarchy(orgId); return depthInHierarchy < minHierarchyDepth; - } catch (OrganizationManagementException e) { + } catch (OrganizationManagementServerException e) { throw new OrgResourceHierarchyTraverseServerException( "Error occurred while getting the hierarchy depth of the organization: " + orgId, e); } } /** - * Throw an OrgResourceHierarchyTraverseClientException upon client side error while traversing organization - * resource hierarchy traverse. - * - * @param error The error enum. - * @param data The error message data. - * @return OrgResourceHierarchyTraverseClientException - */ - public static OrgResourceHierarchyTraverseClientException handleClientException( - OrgResourceHierarchyTraverseConstants.ErrorMessages error, String... data) { - - String description = error.getDescription(); - if (ArrayUtils.isNotEmpty(data)) { - description = String.format(description, data); - } - return new OrgResourceHierarchyTraverseClientException(error.getMessage(), description, error.getCode()); - } - - /** - * Throw an OrgResourceHierarchyTraverseServerException upon server side error while traversing organization - * resource hierarchy traverse. + * Create an {@link OrgResourceHierarchyTraverseServerException} to handle server-side errors. + *

+ * This method formats the error description using the provided data, if applicable, and constructs + * a custom exception for consistent error handling in the service. * - * @param error The error enum. - * @param e The error. - * @param data The error message data. - * @return OrgResourceHierarchyTraverseServerException + * @param error The error enumeration containing predefined error messages and codes. + * @param e The underlying cause of the error. + * @param data Optional data to format the error message description. + * @return An {@link OrgResourceHierarchyTraverseServerException} with the formatted error details. */ public static OrgResourceHierarchyTraverseServerException handleServerException( OrgResourceHierarchyTraverseConstants.ErrorMessages error, Throwable e, String... data) { diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverHierarchyServiceTest.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverHierarchyServiceTest.java new file mode 100644 index 000000000..1d4f38039 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/OrgResourceResolverHierarchyServiceTest.java @@ -0,0 +1,575 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service; + +import org.mockito.Mock; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; +import org.wso2.carbon.identity.application.mgt.ApplicationManagementService; +import org.wso2.carbon.identity.organization.management.service.OrganizationManager; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException; +import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementServerException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.exception.OrgResourceHierarchyTraverseServerException; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.internal.OrgResourceHierarchyTraverseServiceDataHolder; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.mock.resource.impl.MockResourceManagementService; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.mock.resource.impl.model.MockResource; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.strategy.AggregationStrategy; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.strategy.FirstFoundAggregationStrategy; +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.strategy.MergeAllAggregationStrategy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertThrows; + +/** + * Unit tests for the OrgResourceResolverHierarchyService. + */ +public class OrgResourceResolverHierarchyServiceTest { + + private static final String ROOT_ORG_ID = "10084a8d-113f-4211-a0d5-efe36b082211"; + private static final String ROOT_APP_ID = "1ee981ab-64e7-435c-ab91-e8d1e0a13b2c"; + private static final String L1_ORG_ID = "93d996f9-a5ba-4275-a52b-adaad9eba869"; + private static final String L1_APP_ID = "619d2cb1-174d-4d38-af3b-99c532dddb00"; + private static final String L2_ORG_ID = "30b701c6-e309-4241-b047-0c299c45d1a0"; + private static final String L2_APP_ID = "d04d48db-8c5a-437a-9458-4352b84db621"; + private static final String INVALID_ORG_ID = "invalid-org-id"; + private static final String INVALID_APP_ID = "invalid-app-id"; + + private OrgResourceResolverService orgResourceResolverService; + private MockResourceManagementService mockResourceManagementService; + private AggregationStrategy firstFoundAggregationStrategy; + private AggregationStrategy mergeAllAggregationStrategy; + + @Mock + OrganizationManager organizationManager; + + @Mock + ApplicationManagementService applicationManagementService; + + /** + * Initializes test data and mock services before the test class is run. + * This method sets up necessary services and aggregation strategies required for the tests. + */ + @BeforeClass + public void init() { + + // Open mock objects for the current test instance + openMocks(this); + + // Set the OrganizationManager and ApplicationManagementService to the data holder for use in tests + OrgResourceHierarchyTraverseServiceDataHolder.getInstance().setOrganizationManager(organizationManager); + OrgResourceHierarchyTraverseServiceDataHolder.getInstance() + .setApplicationManagementService(applicationManagementService); + + // Initialize the aggregation strategies with the appropriate strategy types + firstFoundAggregationStrategy = new FirstFoundAggregationStrategy<>(); + mergeAllAggregationStrategy = new MergeAllAggregationStrategy<>(this::resourceMerger); + } + + /** + * Sets up mocks for individual test cases by initializing mock services and resource management. + * This method runs before each test method to ensure the environment is ready for testing specific scenarios. + */ + @BeforeMethod + public void setUp() throws Exception { + + // Mock responses for the organization manager with different ancestor organization chains + mockAncestorOrganizationRetrieval(Arrays.asList(L2_ORG_ID, L1_ORG_ID, ROOT_ORG_ID)); + mockAncestorOrganizationRetrieval(Arrays.asList(L1_ORG_ID, ROOT_ORG_ID)); + mockAncestorOrganizationRetrieval(Collections.singletonList(ROOT_ORG_ID)); + + // Mock responses for the application management service with corresponding application ancestor data + mockAncestorApplicationRetrieval(Arrays.asList(L2_ORG_ID, L1_ORG_ID, ROOT_ORG_ID), + Arrays.asList(L2_APP_ID, L1_APP_ID, ROOT_APP_ID)); + mockAncestorApplicationRetrieval(Arrays.asList(L1_ORG_ID, ROOT_ORG_ID), + Arrays.asList(L1_APP_ID, ROOT_APP_ID)); + mockAncestorApplicationRetrieval(Collections.singletonList(ROOT_ORG_ID), + Collections.singletonList(ROOT_APP_ID)); + + // Initialize the mock resource management service used in tests + mockResourceManagementService = new MockResourceManagementService(); + + // Instantiate the OrgResourceResolverService for testing + orgResourceResolverService = new OrgResourceResolverServiceImpl(); + } + + /** + * Resets mock services after each test method to ensure a clean state for subsequent tests. + */ + @AfterMethod + public void tearDown() { + + // Reset the mock services to their default state after each test + reset(organizationManager); + reset(applicationManagementService); + } + + @DataProvider(name = "AggregationStrategyDataProvider") + public Object[][] provideAggregationStrategies() { + + return new Object[][]{ + {firstFoundAggregationStrategy}, + {mergeAllAggregationStrategy} + }; + } + + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetOrgLevelResourcesFromOrgHierarchyWhenNoResourceAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + // No resources available in the mock resource management service. + + MockResource resolvedRootResource = invokeOrgLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID); + assertNull(resolvedRootResource); + + MockResource resolvedL1Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L1_ORG_ID); + assertNull(resolvedL1Resource); + + MockResource resolvedL2Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L2_ORG_ID); + assertNull(resolvedL2Resource); + } + + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetOrgLevelResourcesFromOrgHierarchyWhenRootResourceAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + // Add org-level resource for root organization into the mock resource management service. + List createdOrgResource = addOrgResources(Collections.singletonList(ROOT_ORG_ID)); + + MockResource resolvedRootResource = invokeOrgLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID); + assertResolvedResponse(resolvedRootResource, createdOrgResource.get(0)); + + MockResource resolvedL1Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L1_ORG_ID); + assertResolvedResponse(resolvedL1Resource, createdOrgResource.get(0)); + + MockResource resolvedL2Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L2_ORG_ID); + assertResolvedResponse(resolvedL2Resource, createdOrgResource.get(0)); + } + + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetOrgLevelResourcesFromOrgHierarchyWhenL1OrgResourceAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + // Add org-level resources for L1 and root organizations into the mock resource management service. + List createdOrgResources = addOrgResources(Arrays.asList(ROOT_ORG_ID, L1_ORG_ID)); + + MockResource resolvedRootResource = invokeOrgLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID); + assertResolvedResponse(resolvedRootResource, createdOrgResources.get(0)); + + MockResource resolvedL1Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L1_ORG_ID); + assertResolvedResponse(resolvedL1Resource, createdOrgResources.get(1)); + + MockResource resolvedL2Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L2_ORG_ID); + assertResolvedResponse(resolvedL2Resource, createdOrgResources.get(1)); + } + + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetOrgLevelResourcesFromOrgHierarchyWhenL2OrgResourceAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + // Add org-level resources for L2, L1 and root organizations into the mock resource management service. + List createdOrgResources = addOrgResources(Arrays.asList(ROOT_ORG_ID, L1_ORG_ID, L2_ORG_ID)); + + MockResource resolvedRootResource = invokeOrgLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID); + assertResolvedResponse(resolvedRootResource, createdOrgResources.get(0)); + + MockResource resolvedL1Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L1_ORG_ID); + assertResolvedResponse(resolvedL1Resource, createdOrgResources.get(1)); + + MockResource resolvedL2Resource = invokeOrgLevelResourceResolver(aggregationStrategy, L2_ORG_ID); + assertResolvedResponse(resolvedL2Resource, createdOrgResources.get(2)); + } + + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetAppLevelResourcesFromOrgHierarchyWhenNoResourceAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + MockResource resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertNull(resolvedRootAppResource); + + MockResource resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertNull(resolvedL1AppResource); + + MockResource resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertNull(resolvedL2AppResource); + } + + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetAppLevelResourcesFromOrgHierarchyWhenRootResourcesAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + // Add org-level resource for root organization into the mock resource management service. + List createdOrgResource = addOrgResources(Collections.singletonList(ROOT_ORG_ID)); + + MockResource resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertResolvedResponse(resolvedRootAppResource, createdOrgResource.get(0)); + + MockResource resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertResolvedResponse(resolvedL1AppResource, createdOrgResource.get(0)); + + MockResource resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertResolvedResponse(resolvedL2AppResource, createdOrgResource.get(0)); + + // Add app-level resource for root organization into the mock resource management service. + List createdAppResource = addAppResources(Collections.singletonList(ROOT_ORG_ID), + Collections.singletonList(ROOT_APP_ID)); + + resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertResolvedResponse(resolvedRootAppResource, createdAppResource.get(0)); + + resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertResolvedResponse(resolvedL1AppResource, createdAppResource.get(0)); + + resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertResolvedResponse(resolvedL2AppResource, createdAppResource.get(0)); + } + + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetAppLevelResourcesFromOrgHierarchyWhenL1ResourcesAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + // Add org-level resources for L1 and root organizations into the mock resource management service. + List createdOrgResources = addOrgResources(Arrays.asList(ROOT_ORG_ID, L1_ORG_ID)); + // Add app-level resources for root organization into the mock resource management service. + List createdAppResources = addAppResources(Collections.singletonList(ROOT_ORG_ID), + Collections.singletonList(ROOT_APP_ID)); + + MockResource resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertResolvedResponse(resolvedRootAppResource, createdAppResources.get(0)); + + MockResource resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertResolvedResponse(resolvedL1AppResource, createdOrgResources.get(1)); + + MockResource resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertResolvedResponse(resolvedL2AppResource, createdOrgResources.get(1)); + + // Add app-level resources for L1 organization into the mock resource management service. + createdAppResources.addAll(addAppResources(Collections.singletonList(L1_ORG_ID), + Collections.singletonList(L1_APP_ID))); + + resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertResolvedResponse(resolvedRootAppResource, createdAppResources.get(0)); + + resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertResolvedResponse(resolvedL1AppResource, createdAppResources.get(1)); + + resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertResolvedResponse(resolvedL2AppResource, createdAppResources.get(1)); + } + + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetAppLevelResourcesFromOrgHierarchyWhenL2ResourcesAvailable( + AggregationStrategy aggregationStrategy) throws Exception { + + // Add org-level resources for L2, L1 and root organizations into the mock resource management service. + List createdOrgResources = addOrgResources(Arrays.asList(ROOT_ORG_ID, L1_ORG_ID, L2_ORG_ID)); + // Add app-level resources for L1 and root organizations into the mock resource management service. + List createdAppResources = addAppResources(Arrays.asList(ROOT_ORG_ID, L1_ORG_ID), + Arrays.asList(ROOT_APP_ID, L1_APP_ID)); + + MockResource resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertResolvedResponse(resolvedRootAppResource, createdAppResources.get(0)); + + MockResource resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertResolvedResponse(resolvedL1AppResource, createdAppResources.get(1)); + + MockResource resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertResolvedResponse(resolvedL2AppResource, createdOrgResources.get(2)); + + // Add app-level resources for L2 organization into the mock resource management service. + createdAppResources.addAll(addAppResources(Collections.singletonList(L2_ORG_ID), + Collections.singletonList(L2_APP_ID))); + + resolvedRootAppResource = + invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID); + assertResolvedResponse(resolvedRootAppResource, createdAppResources.get(0)); + + resolvedL1AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L1_ORG_ID, L1_APP_ID); + assertResolvedResponse(resolvedL1AppResource, createdAppResources.get(1)); + + resolvedL2AppResource = invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID); + assertResolvedResponse(resolvedL2AppResource, createdAppResources.get(2)); + } + + @DataProvider(name = "provideAggregationStrategiesForOrgLevelResolverWithInvalidInput") + public Object[][] provideAggregationStrategiesForOrgLevelResolverWithInvalidInput() { + + List invalidOrgIds = Arrays.asList(null, "", INVALID_ORG_ID); + + return new Object[][]{ + {firstFoundAggregationStrategy, invalidOrgIds}, + {mergeAllAggregationStrategy, invalidOrgIds}, + }; + } + + @Test(dataProvider = "provideAggregationStrategiesForOrgLevelResolverWithInvalidInput") + public void testGetOrgLevelResourcesFromOrgHierarchyForInvalidInput( + AggregationStrategy aggregationStrategy, List invalidOrgIds) throws Exception { + + for (String orgId : invalidOrgIds) { + MockResource resolvedResource = invokeOrgLevelResourceResolver(aggregationStrategy, orgId); + assertNull(resolvedResource); + } + } + + @DataProvider(name = "provideAggregationStrategiesForAppLevelResolverWithInvalidInput") + public Object[][] provideAggregationStrategiesForAppLevelResolverWithInvalidInput() { + + List invalidOrgIds = Arrays.asList(null, "", INVALID_ORG_ID); + List invalidAppIds = Arrays.asList(null, "", INVALID_APP_ID); + + return new Object[][]{ + {firstFoundAggregationStrategy, invalidOrgIds, invalidAppIds}, + {mergeAllAggregationStrategy, invalidOrgIds, invalidAppIds}, + }; + } + + @Test(dataProvider = "provideAggregationStrategiesForAppLevelResolverWithInvalidInput") + public void testGetAppLevelResourcesFromOrgHierarchyForInvalidInput( + AggregationStrategy aggregationStrategy, List invalidOrgIds, + List invalidAppIds) throws Exception { + + for (String invalidOrgId : invalidOrgIds) { + for (String invalidAppId : invalidAppIds) { + MockResource resolvedResource = + invokeAppLevelResourceResolver(aggregationStrategy, invalidOrgId, invalidAppId); + assertNull(resolvedResource); + } + } + } + + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetOrgLevelResourcesFromOrgHierarchyWhenServerErrorOccurs( + AggregationStrategy aggregationStrategy) throws Exception { + + when(organizationManager.getOrganizationDepthInHierarchy(anyString())) + .thenThrow(OrganizationManagementServerException.class); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeOrgLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeOrgLevelResourceResolver(aggregationStrategy, L1_ORG_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeOrgLevelResourceResolver(aggregationStrategy, L2_ORG_ID)); + + when(organizationManager.getAncestorOrganizationIds(anyString())) + .thenThrow(OrganizationManagementServerException.class); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeOrgLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeOrgLevelResourceResolver(aggregationStrategy, L1_ORG_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeOrgLevelResourceResolver(aggregationStrategy, L2_ORG_ID)); + } + + @Test(dataProvider = "AggregationStrategyDataProvider") + public void testGetAppLevelResourcesFromOrgHierarchyWhenServerErrorOccurs( + AggregationStrategy aggregationStrategy) throws Exception { + + when(applicationManagementService.getAncestorAppIds(anyString(), anyString())) + .thenThrow(IdentityApplicationManagementException.class); + + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID)); + + when(organizationManager.getAncestorOrganizationIds(anyString())) + .thenThrow(OrganizationManagementServerException.class); + + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeAppLevelResourceResolver(aggregationStrategy, ROOT_ORG_ID, ROOT_APP_ID)); + assertThrows(OrgResourceHierarchyTraverseServerException.class, + () -> invokeAppLevelResourceResolver(aggregationStrategy, L2_ORG_ID, L2_APP_ID)); + } + + /** + * Mock the retrieval of ancestor organization IDs. + * + * @param orgIds Organization IDs + * @throws OrganizationManagementException If an error occurs while retrieving ancestor organization IDs. + */ + private void mockAncestorOrganizationRetrieval(List orgIds) + throws OrganizationManagementException { + + List ancestorOrganizationIds = new ArrayList<>(orgIds); + when(organizationManager.getAncestorOrganizationIds(orgIds.get(0))).thenReturn(ancestorOrganizationIds); + } + + /** + * Mock the retrieval of ancestor application IDs. + * + * @param orgIds Organization IDs + * @param appIds Application IDs + * @throws IdentityApplicationManagementException If an error occurs while retrieving ancestor application IDs. + */ + private void mockAncestorApplicationRetrieval(List orgIds, List appIds) + throws IdentityApplicationManagementException { + + Map ancestorAppIds = new HashMap<>(); + for (String orgId : orgIds) { + ancestorAppIds.put(orgId, appIds.get(orgIds.indexOf(orgId))); + } + + when(applicationManagementService.getAncestorAppIds(appIds.get(0), orgIds.get(0))).thenReturn(ancestorAppIds); + } + + /** + * Add organization resources to the mock resource management service. + * + * @param orgIds Organization IDs + * @return List of created organization resources. + */ + private List addOrgResources(List orgIds) { + + List createdOrgResources = new ArrayList<>(); + int resourceId = 1; + for (String orgId : orgIds) { + MockResource orgResource = new MockResource(resourceId++, orgId + "Org Resource", orgId); + mockResourceManagementService.addOrgResource(orgResource); + createdOrgResources.add(orgResource); + } + return createdOrgResources; + } + + /** + * Add application resources to the mock resource management service. + * + * @param orgIds Organization IDs + * @param appIds Application IDs + * @return List of created application resources. + */ + private List addAppResources(List orgIds, List appIds) { + + List createdAppResources = new ArrayList<>(); + int resourceId = 1; + for (String orgId : orgIds) { + MockResource appResource = + new MockResource(resourceId++, orgId + "App Resource", orgId, appIds.get(orgIds.indexOf(orgId))); + mockResourceManagementService.addAppResource(appResource); + createdAppResources.add(appResource); + } + return createdAppResources; + } + + /** + * Resource resolver function used for testing MergeAllAggregationStrategy. + * + * @param aggregatedResource Aggregated resource + * @param newResource New resource + * @return Merged resource. + */ + private MockResource resourceMerger(MockResource aggregatedResource, MockResource newResource) { + + if (aggregatedResource == null) { + return newResource; + } + return aggregatedResource; + } + + /** + * Invoke the organization level resource resolver. + * + * @param aggregationStrategy Aggregation strategy + * @param organizationId Organization ID + * @return Resolved resource + * @throws OrgResourceHierarchyTraverseException If an error occurs while resolving the resource. + */ + private MockResource invokeOrgLevelResourceResolver(AggregationStrategy aggregationStrategy, + String organizationId) + throws OrgResourceHierarchyTraverseException { + + return orgResourceResolverService.getResourcesFromOrgHierarchy( + organizationId, + (orgId) -> { + MockResource resource = mockResourceManagementService.getOrgResource(orgId); + return Optional.ofNullable(resource); + }, + aggregationStrategy); + } + + /** + * Invoke the application level resource resolver. + * + * @param aggregationStrategy Aggregation strategy + * @param organizationId Organization ID + * @param applicationId Application ID + * @return Resolved resource + * @throws OrgResourceHierarchyTraverseException If an error occurs while resolving the resource. + */ + private MockResource invokeAppLevelResourceResolver(AggregationStrategy aggregationStrategy, + String organizationId, String applicationId) + throws OrgResourceHierarchyTraverseException { + + return orgResourceResolverService.getResourcesFromOrgHierarchy( + organizationId, + applicationId, + (orgId, appId) -> { + MockResource resource = mockResourceManagementService.getAppResource(orgId, appId); + return Optional.ofNullable(resource); + }, + aggregationStrategy); + } + + /** + * Assert the resolved resource with the actual resource. + * + * @param resolvedResource Resolved resource + * @param actualResource Actual resource + */ + private void assertResolvedResponse(MockResource resolvedResource, MockResource actualResource) { + + if (actualResource == null) { + assertNull(resolvedResource); + return; + } + + assertNotNull(resolvedResource); + assertEquals(resolvedResource.getId(), actualResource.getId()); + assertEquals(resolvedResource.getResourceName(), actualResource.getResourceName()); + assertEquals(resolvedResource.getOrgId(), actualResource.getOrgId()); + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/MockResourceManagementService.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/MockResourceManagementService.java new file mode 100644 index 000000000..814b010f5 --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/MockResourceManagementService.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.mock.resource.impl; + +import org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.mock.resource.impl.model.MockResource; + +import java.util.HashMap; +import java.util.Map; + +/** + * Service for managing mocked resources at both organization and application levels. + *

+ * This class maintains separate mappings for organization-level and application-level + * resources, providing methods to add and retrieve resources. These mappings simulate a database, + * offering a simple in-memory representation for resource storage and retrieval operations. + *

+ * This implementation is intended for testing or mocking purposes and simulates a resource management system. + */ +public class MockResourceManagementService { + + private final Map orgResources; + private final Map appResources; + + /** + * Initializes the mock resource management service with empty resource mappings + * for both organization-level and application-level resources. + */ + public MockResourceManagementService() { + + this.orgResources = new HashMap<>(); + this.appResources = new HashMap<>(); + } + + /** + * Adds an organization-level resource to the management service. + * + * @param orgResource The resource to be added. The resource's organization ID + * ({@link MockResource#getOrgId}) is used as the key for storage. + */ + public void addOrgResource(MockResource orgResource) { + + orgResources.put(orgResource.getOrgId(), orgResource); + } + + /** + * Retrieves an organization-level resource by its organization ID. + * + * @param orgId The unique identifier of the organization. + * @return The resource associated with the given organization ID, or {@code null} + * if no resource exists for the provided ID. + */ + public MockResource getOrgResource(String orgId) { + + return orgResources.get(orgId); + } + + /** + * Adds an application-level resource to the management service. + * + * @param appResource The resource to be added. The key for storage is derived + * by concatenating the resource's organization ID and + * application ID ({@link MockResource#getAppId}), separated by a colon. + */ + public void addAppResource(MockResource appResource) { + + appResources.put(appResource.getOrgId() + ":" + appResource.getAppId(), appResource); + } + + /** + * Retrieves an application-level resource by organization ID and application ID. + *

+ * If no application-level resource is found for the given organization and application IDs, + * the method falls back to returning the corresponding organization-level resource, if available. + * + * @param orgId The unique identifier of the organization. + * @param appId The unique identifier of the application within the organization. + * @return The application-level resource if found; otherwise, the organization-level + * resource associated with the organization ID. Returns {@code null} if no matching + * resource is available at either level. + */ + public MockResource getAppResource(String orgId, String appId) { + + MockResource appResource = appResources.get(orgId + ":" + appId); + if (appResource == null) { + return orgResources.get(orgId); + } + return appResource; + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/model/MockResource.java b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/model/MockResource.java new file mode 100644 index 000000000..9b4cbb86e --- /dev/null +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/java/org/wso2/carbon/identity/organization/resource/hierarchy/traverse/service/mock/resource/impl/model/MockResource.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service.mock.resource.impl.model; + +/** + * Represents a mocked resource in an organization or application hierarchy. + *

+ * This class models resources that are associated either at the organization level + * or at the application level within an organization. It includes identifiers and + * metadata such as resource ID, resource name, organization ID, and optionally, + * an application ID for application-specific resources. + */ +public class MockResource { + + private int id; + private String resourceName; + private String orgId; + private String appId; + + /** + * Constructs a mocked resource associated with an organization. + * + * @param id Unique identifier of the resource. + * @param resourceName Descriptive name of the resource. + * @param orgId Identifier of the organization the resource belongs to. + */ + public MockResource(int id, String resourceName, String orgId) { + + this.id = id; + this.resourceName = resourceName; + this.orgId = orgId; + } + + /** + * Constructs a mocked resource associated with a specific application within an organization. + * + * @param id Unique identifier of the resource. + * @param resourceName Descriptive name of the resource. + * @param orgId Identifier of the organization the resource belongs to. + * @param appId Identifier of the application the resource is associated with. + */ + public MockResource(int id, String resourceName, String orgId, String appId) { + + this(id, resourceName, orgId); + this.appId = appId; + } + + /** + * Retrieves the unique identifier of the resource. + * + * @return The resource ID. + */ + public int getId() { + + return id; + } + + /** + * Sets the unique identifier of the resource. + * + * @param id The resource ID to set. + */ + public void setId(int id) { + + this.id = id; + } + + /** + * Retrieves the name of the resource. + * + * @return The resource name. + */ + public String getResourceName() { + + return resourceName; + } + + /** + * Sets the name of the resource. + * + * @param resourceName The name to assign to the resource. + */ + public void setResourceName(String resourceName) { + + this.resourceName = resourceName; + } + + /** + * Retrieves the organization ID associated with the resource. + * + * @return The organization ID. + */ + public String getOrgId() { + + return orgId; + } + + /** + * Sets the organization ID associated with the resource. + * + * @param orgId The organization ID to set. + */ + public void setOrgId(String orgId) { + + this.orgId = orgId; + } + + /** + * Retrieves the application ID associated with the resource. + * + * @return The application ID, or {@code null} if the resource is not associated with a specific application. + */ + public String getAppId() { + + return appId; + } + + /** + * Sets the application ID associated with the resource. + * + * @param appId The application ID to set. + */ + public void setAppId(String appId) { + + this.appId = appId; + } +} diff --git a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/resources/testng.xml b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/resources/testng.xml index 5f83a467d..7ef88e30e 100644 --- a/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/resources/testng.xml +++ b/components/org.wso2.carbon.identity.organization.resource.hierarchy.traverse.service/src/test/resources/testng.xml @@ -21,6 +21,7 @@ +