This is an automated email from the ASF dual-hosted git repository.

yasith pushed a commit to branch feat/airavata-service-layer
in repository https://gitbox.apache.org/repos/asf/airavata.git

commit 71730e0deab2a9923b6e182c1f224394ed9475f1
Author: yasithdev <[email protected]>
AuthorDate: Thu Mar 26 12:54:02 2026 -0500

    feat: add integration test infrastructure with Testcontainers MariaDB
    
    - AbstractIntegrationTest base class with @Tag("integration") and MariaDB 
container
    - SharingServiceIntegrationTest: rewrites monolithic sharing test into 13 
ordered tests
    - ExperimentRepositoryIntegrationTest: template for repository integration 
tests
    - Surefire configured to exclude integration tests by default (run with 
-Dgroups=integration)
---
 airavata-api/pom.xml                               |   3 +
 .../integration/AbstractIntegrationTest.java       |  60 +++
 .../ExperimentRepositoryIntegrationTest.java       | 229 +++++++++++
 .../integration/SharingServiceIntegrationTest.java | 425 +++++++++++++++++++++
 4 files changed, 717 insertions(+)

diff --git a/airavata-api/pom.xml b/airavata-api/pom.xml
index 3957ed7acd..1d67f3e3b8 100644
--- a/airavata-api/pom.xml
+++ b/airavata-api/pom.xml
@@ -517,6 +517,9 @@ under the License.
             <exclude>**/DAOBaseTestCase.java</exclude>
             <exclude>**/MappingDAOTest.java</exclude>
           </excludes>
+          <!-- Exclude @Tag("integration") tests by default — they require 
Docker.
+               Run with: mvn test -Dgroups=integration -DexcludedGroups="" -->
+          <excludedGroups>integration</excludedGroups>
         </configuration>
       </plugin>
 
diff --git 
a/airavata-api/src/test/java/org/apache/airavata/integration/AbstractIntegrationTest.java
 
b/airavata-api/src/test/java/org/apache/airavata/integration/AbstractIntegrationTest.java
new file mode 100644
index 0000000000..6f2db5a166
--- /dev/null
+++ 
b/airavata-api/src/test/java/org/apache/airavata/integration/AbstractIntegrationTest.java
@@ -0,0 +1,60 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF 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.apache.airavata.integration;
+
+import org.junit.jupiter.api.Tag;
+import org.testcontainers.containers.MariaDBContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+/**
+ * Base class for integration tests. Uses Testcontainers MariaDB.
+ *
+ * <p>Tag: "integration" — can be filtered in CI with {@code 
-Dgroups=integration} or excluded
+ * with {@code -DexcludedGroups=integration}.
+ *
+ * <p>Usage: {@code mvn test -pl airavata-api -Dgroups=integration 
-DexcludedGroups=""}
+ */
+@Tag("integration")
+@Testcontainers
+public abstract class AbstractIntegrationTest {
+
+    @Container
+    protected static final MariaDBContainer<?> mariadb = new 
MariaDBContainer<>("mariadb:11")
+            .withDatabaseName("airavata_test")
+            .withUsername("airavata")
+            .withPassword("airavata");
+
+    protected static String getJdbcUrl() {
+        return mariadb.getJdbcUrl();
+    }
+
+    protected static String getUsername() {
+        return mariadb.getUsername();
+    }
+
+    protected static String getPassword() {
+        return mariadb.getPassword();
+    }
+
+    protected static String getJdbcDriver() {
+        return "org.mariadb.jdbc.Driver";
+    }
+}
diff --git 
a/airavata-api/src/test/java/org/apache/airavata/integration/ExperimentRepositoryIntegrationTest.java
 
b/airavata-api/src/test/java/org/apache/airavata/integration/ExperimentRepositoryIntegrationTest.java
new file mode 100644
index 0000000000..81de8c4683
--- /dev/null
+++ 
b/airavata-api/src/test/java/org/apache/airavata/integration/ExperimentRepositoryIntegrationTest.java
@@ -0,0 +1,229 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF 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.apache.airavata.integration;
+
+import org.apache.airavata.common.utils.DBInitializer;
+import org.apache.airavata.model.experiment.ExperimentModel;
+import org.apache.airavata.model.experiment.ExperimentType;
+import org.apache.airavata.model.experiment.UserConfigurationDataModel;
+import 
org.apache.airavata.model.scheduling.ComputationalResourceSchedulingModel;
+import org.apache.airavata.model.status.ExperimentState;
+import org.apache.airavata.model.workspace.Gateway;
+import org.apache.airavata.model.workspace.Project;
+import 
org.apache.airavata.registry.core.repositories.expcatalog.ExperimentRepository;
+import 
org.apache.airavata.registry.core.repositories.expcatalog.GatewayRepository;
+import 
org.apache.airavata.registry.core.repositories.expcatalog.ProjectRepository;
+import org.apache.airavata.registry.core.utils.ExpCatalogDBInitConfig;
+import org.junit.jupiter.api.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Integration test template for ExperimentRepository. Demonstrates the 
recommended pattern for
+ * writing new repository integration tests against a real MariaDB instance.
+ *
+ * <p>Key patterns shown here:
+ * <ul>
+ *   <li>Extend {@link AbstractIntegrationTest} — gets the shared 
Testcontainers MariaDB container</li>
+ *   <li>Use {@code @BeforeAll} to initialize the DB schema and seed 
prerequisite data</li>
+ *   <li>Use {@code @AfterAll} to clean up system property overrides</li>
+ *   <li>Each {@code @Test} method tests a single behavior with a {@code 
@DisplayName}</li>
+ * </ul>
+ *
+ * <p>Run with: {@code mvn test -pl airavata-api -Dgroups=integration 
-DexcludedGroups=""}
+ */
+@Tag("integration")
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class ExperimentRepositoryIntegrationTest extends 
AbstractIntegrationTest {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(ExperimentRepositoryIntegrationTest.class);
+
+    private static GatewayRepository gatewayRepository;
+    private static ProjectRepository projectRepository;
+    private static ExperimentRepository experimentRepository;
+
+    private static String gatewayId;
+    private static String projectId;
+
+    @BeforeAll
+    static void setUpAll() throws Exception {
+        // Route ExpCatalogJDBCConfig to the Testcontainers instance.
+        // ApplicationSettings.getSettingImpl checks System.getProperties() 
first.
+        System.setProperty("airavata.jdbc.url", getJdbcUrl());
+        System.setProperty("airavata.jdbc.user", getUsername());
+        System.setProperty("airavata.jdbc.password", getPassword());
+        System.setProperty("airavata.jdbc.driver", getJdbcDriver());
+        System.setProperty("airavata.jdbc.validationQuery", "SELECT 1");
+
+        ExpCatalogDBInitConfig config = new 
ExpCatalogDBInitConfig().setDbInitScriptPrefix("expcatalog");
+        DBInitializer.initializeDB(config);
+
+        gatewayRepository = new GatewayRepository();
+        projectRepository = new ProjectRepository();
+        experimentRepository = new ExperimentRepository();
+
+        // Seed: gateway and project are prerequisites for experiments
+        Gateway gateway = new Gateway();
+        gateway.setGatewayId("integration-test-gateway");
+        gateway.setDomain("INTEGRATION");
+        gateway.setEmailAddress("[email protected]");
+        gatewayId = gatewayRepository.addGateway(gateway);
+
+        Project project = new Project();
+        project.setName("Integration Test Project");
+        project.setOwner("integration-user");
+        project.setGatewayId(gatewayId);
+        projectId = projectRepository.addProject(project, gatewayId);
+
+        logger.info("Test DB initialized. gatewayId={}, projectId={}", 
gatewayId, projectId);
+    }
+
+    @AfterAll
+    static void tearDownAll() {
+        System.clearProperty("airavata.jdbc.url");
+        System.clearProperty("airavata.jdbc.user");
+        System.clearProperty("airavata.jdbc.password");
+        System.clearProperty("airavata.jdbc.driver");
+        System.clearProperty("airavata.jdbc.validationQuery");
+    }
+
+    // --- Helper ---
+
+    private ExperimentModel buildExperiment(String name) {
+        ExperimentModel experiment = new ExperimentModel();
+        experiment.setProjectId(projectId);
+        experiment.setGatewayId(gatewayId);
+        experiment.setExperimentType(ExperimentType.SINGLE_APPLICATION);
+        experiment.setUserName("integration-user");
+        experiment.setExperimentName(name);
+        experiment.setGatewayInstanceId("gw-instance-1");
+        return experiment;
+    }
+
+    // --- Tests ---
+
+    @Test
+    @Order(1)
+    @DisplayName("Create experiment and retrieve it by id")
+    void createAndRetrieveExperiment() throws Exception {
+        ExperimentModel experiment = buildExperiment("create-test-experiment");
+
+        String experimentId = experimentRepository.addExperiment(experiment);
+        assertNotNull(experimentId, "addExperiment should return a non-null 
id");
+
+        ExperimentModel retrieved = 
experimentRepository.getExperiment(experimentId);
+        assertNotNull(retrieved, "getExperiment should return the created 
experiment");
+        assertEquals(gatewayId, retrieved.getGatewayId());
+        assertEquals("integration-user", retrieved.getUserName());
+        assertEquals("gw-instance-1", retrieved.getGatewayInstanceId());
+        assertEquals(ExperimentType.SINGLE_APPLICATION, 
retrieved.getExperimentType());
+        assertEquals(1, retrieved.getExperimentStatusSize(), "initial status 
should be set");
+        assertEquals(ExperimentState.CREATED,
+                retrieved.getExperimentStatus().get(0).getState(), "initial 
state should be CREATED");
+
+        // Cleanup
+        experimentRepository.removeExperiment(experimentId);
+        assertFalse(experimentRepository.isExperimentExist(experimentId), 
"experiment should be deleted");
+    }
+
+    @Test
+    @Order(2)
+    @DisplayName("Update experiment description and email addresses")
+    void updateExperimentFields() throws Exception {
+        ExperimentModel experiment = buildExperiment("update-test-experiment");
+        String experimentId = experimentRepository.addExperiment(experiment);
+
+        assertEquals(0, 
experimentRepository.getExperiment(experimentId).getEmailAddressesSize(),
+                "freshly created experiment should have no email addresses");
+
+        experiment.setDescription("updated description");
+        experiment.addToEmailAddresses("[email protected]");
+        experiment.addToEmailAddresses("[email protected]");
+        experimentRepository.updateExperiment(experiment, experimentId);
+
+        ExperimentModel updated = 
experimentRepository.getExperiment(experimentId);
+        assertEquals("updated description", updated.getDescription());
+        assertEquals(2, updated.getEmailAddressesSize());
+        assertEquals("[email protected]", updated.getEmailAddresses().get(0));
+        assertEquals("[email protected]", updated.getEmailAddresses().get(1));
+
+        experimentRepository.removeExperiment(experimentId);
+    }
+
+    @Test
+    @Order(3)
+    @DisplayName("Add and update user configuration data")
+    void addAndUpdateUserConfigurationData() throws Exception {
+        ExperimentModel experiment = buildExperiment("config-test-experiment");
+        String experimentId = experimentRepository.addExperiment(experiment);
+
+        ComputationalResourceSchedulingModel scheduling = new 
ComputationalResourceSchedulingModel();
+        scheduling.setResourceHostId("host-1");
+        scheduling.setTotalCPUCount(8);
+        scheduling.setNodeCount(2);
+        scheduling.setQueueName("batch");
+        scheduling.setWallTimeLimit(60);
+
+        UserConfigurationDataModel config = new UserConfigurationDataModel();
+        config.setAiravataAutoSchedule(true);
+        config.setOverrideManualScheduledParams(false);
+        config.setComputationalResourceScheduling(scheduling);
+
+        assertEquals(experimentId, 
experimentRepository.addUserConfigurationData(config, experimentId),
+                "addUserConfigurationData should return the experimentId");
+
+        config.setInputStorageResourceId("storage-in");
+        config.setOutputStorageResourceId("storage-out");
+        experimentRepository.updateUserConfigurationData(config, experimentId);
+
+        UserConfigurationDataModel retrieved = 
experimentRepository.getUserConfigurationData(experimentId);
+        assertEquals("storage-in", retrieved.getInputStorageResourceId());
+        assertEquals("storage-out", retrieved.getOutputStorageResourceId());
+
+        ComputationalResourceSchedulingModel retrievedScheduling = 
retrieved.getComputationalResourceScheduling();
+        assertNotNull(retrievedScheduling);
+        assertEquals("host-1", retrievedScheduling.getResourceHostId());
+        assertEquals(8, retrievedScheduling.getTotalCPUCount());
+        assertEquals(2, retrievedScheduling.getNodeCount());
+        assertEquals("batch", retrievedScheduling.getQueueName());
+        assertEquals(60, retrievedScheduling.getWallTimeLimit());
+
+        experimentRepository.removeExperiment(experimentId);
+    }
+
+    @Test
+    @Order(4)
+    @DisplayName("Slashes in experiment name are replaced with underscores in 
generated id")
+    void slashesInNameAreNormalized() throws Exception {
+        ExperimentModel experiment = buildExperiment("name/forward-slash//a");
+        String experimentId = experimentRepository.addExperiment(experiment);
+        assertTrue(experimentId.startsWith("name_forward-slash__a"),
+                "forward slashes should be replaced with underscores");
+        experimentRepository.removeExperiment(experimentId);
+
+        experiment = buildExperiment("name\\backward-slash\\\\a");
+        experimentId = experimentRepository.addExperiment(experiment);
+        assertTrue(experimentId.startsWith("name_backward-slash__a"),
+                "backward slashes should be replaced with underscores");
+        experimentRepository.removeExperiment(experimentId);
+    }
+}
diff --git 
a/airavata-api/src/test/java/org/apache/airavata/integration/SharingServiceIntegrationTest.java
 
b/airavata-api/src/test/java/org/apache/airavata/integration/SharingServiceIntegrationTest.java
new file mode 100644
index 0000000000..ee657e6ffa
--- /dev/null
+++ 
b/airavata-api/src/test/java/org/apache/airavata/integration/SharingServiceIntegrationTest.java
@@ -0,0 +1,425 @@
+/**
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF 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.apache.airavata.integration;
+
+import java.util.Arrays;
+import java.util.List;
+import 
org.apache.airavata.sharing.registry.db.utils.SharingRegistryDBInitConfig;
+import org.apache.airavata.sharing.registry.models.*;
+import 
org.apache.airavata.sharing.registry.server.SharingRegistryServerHandler;
+import org.junit.jupiter.api.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Integration test for the sharing registry lifecycle. Extends {@link 
AbstractIntegrationTest}
+ * to use a real MariaDB via Testcontainers.
+ *
+ * <p>Run with: {@code mvn test -pl airavata-api -Dgroups=integration 
-DexcludedGroups=""}
+ */
+@Tag("integration")
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class SharingServiceIntegrationTest extends AbstractIntegrationTest {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(SharingServiceIntegrationTest.class);
+
+    private static SharingRegistryServerHandler handler;
+
+    // Domain
+    private static String domainId;
+
+    // Users
+    private static String userId1, userId2, userId3, userId7;
+    private static User user3, user7;
+
+    // Groups
+    private static String groupId1, groupId2;
+
+    // Permission types
+    private static String permissionTypeId1, permissionTypeId2;
+
+    // Entity types
+    private static String entityTypeId1, entityTypeId2, entityTypeId3, 
entityTypeId4;
+
+    // Entities
+    private static String entityId1, entityId2, entityId3, entityId4, 
entityId5, entityId6;
+
+    @BeforeAll
+    static void setUpAll() throws Exception {
+        // Point SharingRegistryJDBCConfig at the Testcontainers MariaDB 
instance.
+        // ApplicationSettings.getSettingImpl checks System.getProperties() 
first.
+        System.setProperty("airavata.jdbc.url", getJdbcUrl());
+        System.setProperty("airavata.jdbc.user", getUsername());
+        System.setProperty("airavata.jdbc.password", getPassword());
+        System.setProperty("airavata.jdbc.driver", getJdbcDriver());
+        System.setProperty("airavata.jdbc.validationQuery", "SELECT 1");
+
+        SharingRegistryDBInitConfig config = new SharingRegistryDBInitConfig();
+        config.setDBInitScriptPrefix("sharing-registry");
+        handler = new SharingRegistryServerHandler(config);
+
+        // Create domain
+        domainId = "test-domain." + System.currentTimeMillis();
+        Domain domain = new Domain();
+        domain.setDomainId(domainId);
+        domain.setName(domainId);
+        domain.setDescription("integration test domain");
+        domain.setCreatedTime(System.currentTimeMillis());
+        domain.setUpdatedTime(System.currentTimeMillis());
+        assertNotNull(handler.createDomain(domain), "domain creation should 
return id");
+
+        // Create users
+        userId1 = createUser("test-user-1");
+        userId2 = createUser("test-user-2");
+        userId3 = createUser("test-user-3");
+        userId7 = createUser("test-user-7");
+        user3 = handler.getUser(domainId, userId3);
+        user7 = handler.getUser(domainId, userId7);
+
+        // Create groups
+        groupId1 = createGroup("test-group-1", userId1);
+        groupId2 = createGroup("test-group-2", userId2);
+
+        // Create permission types
+        permissionTypeId1 = createPermissionType("READ", "READ description");
+        permissionTypeId2 = createPermissionType("WRITE", "WRITE description");
+
+        // Create entity types
+        entityTypeId1 = createEntityType("Project", "project type");
+        entityTypeId2 = createEntityType("Experiment", "experiment type");
+        entityTypeId3 = createEntityType("FileInput", "file input type");
+        entityTypeId4 = createEntityType("Application-Deployment", "app 
deployment type");
+    }
+
+    @AfterAll
+    static void tearDownAll() {
+        // Remove system property overrides so other tests are not affected
+        System.clearProperty("airavata.jdbc.url");
+        System.clearProperty("airavata.jdbc.user");
+        System.clearProperty("airavata.jdbc.password");
+        System.clearProperty("airavata.jdbc.driver");
+        System.clearProperty("airavata.jdbc.validationQuery");
+    }
+
+    // --- Helpers ---
+
+    private static String createUser(String namePrefix) throws Exception {
+        long ts = System.currentTimeMillis();
+        String userName = namePrefix + "." + ts;
+        String userId = domainId + ":" + userName;
+        User user = new User();
+        user.setUserId(userId);
+        user.setUserName(userName);
+        user.setDomainId(domainId);
+        user.setCreatedTime(ts);
+        user.setUpdatedTime(ts);
+        assertNotNull(handler.createUser(user), "user creation should return 
id");
+        return userId;
+    }
+
+    private static String createGroup(String namePrefix, String ownerId) 
throws Exception {
+        long ts = System.currentTimeMillis();
+        String groupName = namePrefix + "." + ts;
+        String groupId = domainId + ":" + groupName;
+        UserGroup group = new UserGroup();
+        group.setGroupId(groupId);
+        group.setDomainId(domainId);
+        group.setName(groupName);
+        group.setDescription("test group");
+        group.setOwnerId(ownerId);
+        group.setGroupType(GroupType.USER_LEVEL_GROUP);
+        group.setGroupCardinality(GroupCardinality.MULTI_USER);
+        group.setCreatedTime(ts);
+        group.setUpdatedTime(ts);
+        assertNotNull(handler.createGroup(group), "group creation should 
return id");
+        return groupId;
+    }
+
+    private static String createPermissionType(String name, String 
description) throws Exception {
+        long ts = System.currentTimeMillis();
+        PermissionType pt = new PermissionType();
+        pt.setPermissionTypeId(domainId + ":" + name);
+        pt.setDomainId(domainId);
+        pt.setName(name);
+        pt.setDescription(description);
+        pt.setCreatedTime(ts);
+        pt.setUpdatedTime(ts);
+        String id = handler.createPermissionType(pt);
+        assertNotNull(id, "permission type creation should return id");
+        return id;
+    }
+
+    private static String createEntityType(String name, String description) 
throws Exception {
+        long ts = System.currentTimeMillis();
+        EntityType et = new EntityType();
+        et.setEntityTypeId(domainId + ":" + name);
+        et.setDomainId(domainId);
+        et.setName(name);
+        et.setDescription(description);
+        et.setCreatedTime(ts);
+        et.setUpdatedTime(ts);
+        String id = handler.createEntityType(et);
+        assertNotNull(id, "entity type creation should return id");
+        return id;
+    }
+
+    private static String createEntity(String entitySuffix, String 
entityTypeId, String ownerId, String name,
+            String description, String parentEntityId) throws Exception {
+        long ts = System.currentTimeMillis();
+        Entity entity = new Entity();
+        entity.setEntityId(domainId + ":" + entitySuffix);
+        entity.setDomainId(domainId);
+        entity.setEntityTypeId(entityTypeId);
+        entity.setOwnerId(ownerId);
+        entity.setName(name);
+        entity.setDescription(description);
+        entity.setFullText(name + " " + description);
+        entity.setCreatedTime(ts);
+        entity.setUpdatedTime(ts);
+        if (parentEntityId != null) {
+            entity.setParentEntityId(parentEntityId);
+        }
+        String id = handler.createEntity(entity);
+        assertNotNull(id, "entity creation should return id");
+        return id;
+    }
+
+    // --- Tests ---
+
+    @Test
+    @Order(1)
+    @DisplayName("Domain is listed after creation")
+    void domainIsListed() throws Exception {
+        assertTrue(handler.getDomains(0, 10).size() > 0, "getDomains should 
return at least the test domain");
+    }
+
+    @Test
+    @Order(2)
+    @DisplayName("Users are listed in domain")
+    void usersAreListed() throws Exception {
+        assertTrue(handler.getUsers(domainId, 0, 10).size() > 0, "getUsers 
should return users in domain");
+    }
+
+    @Test
+    @Order(3)
+    @DisplayName("Group owner has owner access")
+    void groupOwnerHasOwnerAccess() throws Exception {
+        assertTrue(handler.hasOwnerAccess(domainId, groupId1, userId1), "group 
creator should have owner access");
+    }
+
+    @Test
+    @Order(4)
+    @DisplayName("Add and remove group admin")
+    void addAndRemoveGroupAdmin() throws Exception {
+        assertTrue(handler.addGroupAdmins(domainId, groupId1, 
Arrays.asList(userId7)), "addGroupAdmins should return true");
+        assertTrue(handler.hasAdminAccess(domainId, groupId1, userId7), 
"userId7 should have admin access after being added");
+        assertTrue(handler.removeGroupAdmins(domainId, groupId1, 
Arrays.asList(userId7)), "removeGroupAdmins should return true");
+        assertFalse(handler.hasAdminAccess(domainId, groupId1, userId7), 
"userId7 should not have admin access after removal");
+    }
+
+    @Test
+    @Order(5)
+    @DisplayName("Transfer group ownership")
+    void transferGroupOwnership() throws Exception {
+        handler.addUsersToGroup(domainId, Arrays.asList(userId2), groupId1);
+        assertTrue(handler.transferGroupOwnership(domainId, groupId1, 
userId2), "ownership transfer should return true");
+        assertTrue(handler.hasOwnerAccess(domainId, groupId1, userId2), 
"userId2 should be the new owner");
+        // Transfer back
+        assertTrue(handler.transferGroupOwnership(domainId, groupId1, 
userId1), "ownership transfer back should return true");
+        assertFalse(handler.hasOwnerAccess(domainId, groupId1, userId2), 
"userId2 should no longer be owner");
+    }
+
+    @Test
+    @Order(6)
+    @DisplayName("Group membership: users and child groups are reflected 
correctly")
+    void groupMembership() throws Exception {
+        handler.addUsersToGroup(domainId, Arrays.asList(userId1), groupId1);
+        handler.addUsersToGroup(domainId, Arrays.asList(userId7), groupId1);
+        handler.addUsersToGroup(domainId, Arrays.asList(userId2, userId3), 
groupId2);
+        handler.addChildGroupsToParentGroup(domainId, Arrays.asList(groupId2), 
groupId1);
+
+        assertEquals(1, handler.getGroupMembersOfTypeGroup(domainId, groupId1, 
0, 10).size(),
+                "groupId1 should have one child group (groupId2)");
+        assertEquals(2, handler.getGroupMembersOfTypeUser(domainId, groupId2, 
0, 10).size(),
+                "groupId2 should have two direct users");
+        assertEquals(1, handler.getAllMemberGroupsForUser(domainId, 
userId3).size(),
+                "userId3 should be a member of groupId2");
+    }
+
+    @Test
+    @Order(7)
+    @DisplayName("Entity sharing via users propagates to children")
+    void entitySharingPropagatesViaUsers() throws Exception {
+        entityId1 = createEntity("Entity1", entityTypeId1, userId1, "Project 
name 1", "Project description", null);
+        entityId2 = createEntity("Entity2", entityTypeId2, userId1, 
"Experiment name", "Experiment description", entityId1);
+        entityId3 = createEntity("Entity3", entityTypeId2, userId1, 
"Experiment name", "Experiment description", entityId1);
+
+        handler.shareEntityWithUsers(domainId, entityId1, 
Arrays.asList(userId2), permissionTypeId1, true);
+        handler.shareEntityWithGroups(domainId, entityId3, 
Arrays.asList(groupId2), permissionTypeId1, true);
+
+        entityId4 = createEntity("Entity4", entityTypeId3, userId3, "Input 
name", "Input file description", entityId3);
+
+        assertTrue(handler.userHasAccess(domainId, userId3, entityId4, 
permissionTypeId1),
+                "userId3 (group member) should have access to child entity via 
group share");
+        assertTrue(handler.userHasAccess(domainId, userId2, entityId4, 
permissionTypeId1),
+                "userId2 should have access to child entity via parent group 
share");
+        assertTrue(handler.userHasAccess(domainId, userId1, entityId4, 
permissionTypeId1),
+                "owner userId1 should have access");
+        assertFalse(handler.userHasAccess(domainId, userId3, entityId1, 
permissionTypeId1),
+                "userId3 should not have access to entityId1 (shared only with 
userId2)");
+    }
+
+    @Test
+    @Order(8)
+    @DisplayName("Entity sharing with multiple permission types")
+    void entitySharingWithMultiplePermissions() throws Exception {
+        entityId5 = createEntity("Entity5", entityTypeId4, userId1, "App 
deployment name", "App deployment description", null);
+        handler.shareEntityWithUsers(domainId, entityId5, 
Arrays.asList(userId2), permissionTypeId1, true);
+        handler.shareEntityWithGroups(domainId, entityId5, 
Arrays.asList(groupId2), permissionTypeId2, true);
+
+        assertTrue(handler.userHasAccess(domainId, userId3, entityId5, 
permissionTypeId2),
+                "userId3 (group member) should have WRITE access via group");
+        assertTrue(handler.userHasAccess(domainId, userId2, entityId5, 
permissionTypeId1),
+                "userId2 should have READ access via direct share");
+        assertFalse(handler.userHasAccess(domainId, userId3, entityId5, 
permissionTypeId1),
+                "userId3 should not have READ access (only WRITE via group)");
+    }
+
+    @Test
+    @Order(9)
+    @DisplayName("Search entities by name and type")
+    void searchEntitiesByNameAndType() throws Exception {
+        java.util.ArrayList<SearchCriteria> filters = new 
java.util.ArrayList<>();
+        SearchCriteria nameCriteria = new SearchCriteria();
+        nameCriteria.setSearchCondition(SearchCondition.LIKE);
+        nameCriteria.setValue("Input");
+        nameCriteria.setSearchField(EntitySearchField.NAME);
+        filters.add(nameCriteria);
+
+        SearchCriteria typeCriteria = new SearchCriteria();
+        typeCriteria.setSearchCondition(SearchCondition.EQUAL);
+        typeCriteria.setValue(entityTypeId3);
+        typeCriteria.setSearchField(EntitySearchField.ENTITY_TYPE_ID);
+        filters.add(typeCriteria);
+
+        assertTrue(handler.searchEntities(domainId, userId1, filters, 0, 
-1).size() > 0,
+                "search should return at least entityId4 (FileInput entity 
with 'Input' in name)");
+    }
+
+    @Test
+    @Order(10)
+    @DisplayName("Get shared users and groups for entity")
+    void getSharedUsersAndGroups() throws Exception {
+        assertNotNull(handler.getListOfSharedUsers(domainId, entityId1, 
permissionTypeId1),
+                "getListOfSharedUsers should not return null");
+        assertNotNull(handler.getListOfSharedGroups(domainId, entityId1, 
permissionTypeId1),
+                "getListOfSharedGroups should not return null");
+        assertEquals(1,
+                handler.getListOfSharedUsers(domainId, entityId1, domainId + 
":OWNER").size(),
+                "entityId1 should have exactly one owner");
+    }
+
+    @Test
+    @Order(11)
+    @DisplayName("Changing parent entity removes old cascading permissions and 
applies new ones")
+    void changingParentEntityUpdatesPermissions() throws Exception {
+        // Setup: entityId2 parent is entityId1; entityId1 is shared with 
userId2
+        assertTrue(handler.userHasAccess(domainId, userId2, entityId1, 
permissionTypeId1));
+        assertTrue(handler.userHasAccess(domainId, userId2, entityId2, 
permissionTypeId1),
+                "userId2 should have access to entityId2 via parent 
entityId1");
+        assertFalse(handler.userHasAccess(domainId, userId3, entityId2, 
permissionTypeId1),
+                "userId3 should not yet have access to entityId2");
+
+        // Create a second parent entity shared with userId3
+        entityId6 = createEntity("Entity6", entityTypeId1, userId1, "Project 
name 2", "Project description", null);
+        handler.shareEntityWithUsers(domainId, entityId6, 
Arrays.asList(userId3), permissionTypeId1, true);
+        assertTrue(handler.userHasAccess(domainId, userId3, entityId6, 
permissionTypeId1));
+
+        // Directly share entityId2 with userId7 before reparenting
+        assertFalse(handler.userHasAccess(domainId, userId7, entityId2, 
permissionTypeId1));
+        handler.shareEntityWithUsers(domainId, entityId2, 
Arrays.asList(userId7), permissionTypeId1, true);
+        assertTrue(handler.userHasAccess(domainId, userId7, entityId2, 
permissionTypeId1));
+
+        // Reparent entityId2 to entityId6
+        Entity entity2 = handler.getEntity(domainId, entityId2);
+        entity2.setParentEntityId(entityId6);
+        assertTrue(handler.updateEntity(entity2), "updateEntity should return 
true");
+
+        Entity entity2Updated = handler.getEntity(domainId, entityId2);
+        assertEquals(entityId6, entity2Updated.getParentEntityId(), "parent 
should be updated to entityId6");
+
+        // userId2's access should be removed (was from old parent)
+        assertFalse(handler.userHasAccess(domainId, userId2, entityId2, 
permissionTypeId1),
+                "userId2 should lose access after parent change");
+        // userId3 now has access via new parent
+        assertTrue(handler.userHasAccess(domainId, userId3, entityId2, 
permissionTypeId1),
+                "userId3 should gain access via new parent entityId6");
+        // userId7's direct share is preserved
+        assertTrue(handler.userHasAccess(domainId, userId7, entityId2, 
permissionTypeId1),
+                "userId7's direct share should be preserved after parent 
change");
+    }
+
+    @Test
+    @Order(12)
+    @DisplayName("getListOfDirectlySharedUsers returns only direct shares")
+    void directlySharedUsersDoNotIncludeCascading() throws Exception {
+        assertEquals(Arrays.asList(user3),
+                handler.getListOfDirectlySharedUsers(domainId, entityId6, 
permissionTypeId1),
+                "entityId6 should be directly shared with user3 only");
+        assertEquals(Arrays.asList(user7),
+                handler.getListOfDirectlySharedUsers(domainId, entityId2, 
permissionTypeId1),
+                "entityId2 should be directly shared with user7 only");
+
+        List<org.apache.airavata.sharing.registry.models.User> 
entityId2SharedUsers =
+                handler.getListOfSharedUsers(domainId, entityId2, 
permissionTypeId1);
+        assertEquals(2, entityId2SharedUsers.size(), "entityId2 should have 2 
shared users total (user3 cascaded + user7 direct)");
+        assertTrue(entityId2SharedUsers.contains(user3) && 
entityId2SharedUsers.contains(user7),
+                "shared users should contain both user3 and user7");
+
+        assertEquals(1,
+                handler.getListOfDirectlySharedGroups(domainId, entityId3, 
permissionTypeId1).size(),
+                "entityId3 should have one directly shared group");
+        assertEquals(groupId2,
+                handler.getListOfDirectlySharedGroups(domainId, entityId3, 
permissionTypeId1).get(0).getGroupId(),
+                "the directly shared group for entityId3 should be groupId2");
+    }
+
+    @Test
+    @Order(13)
+    @DisplayName("New users are automatically added to initialUserGroupId when 
configured")
+    void newUserAddedToInitialUserGroup() throws Exception {
+        String initialUserGroupId = createGroup("initial-user-group", userId1);
+
+        Domain domain = handler.getDomain(domainId);
+        domain.setInitialUserGroupId(initialUserGroupId);
+        assertTrue(handler.updateDomain(domain), "updateDomain should return 
true");
+        assertEquals(initialUserGroupId, 
handler.getDomain(domainId).getInitialUserGroupId(),
+                "domain should have initialUserGroupId set");
+
+        String userId8 = createUser("test-user-8");
+        List<UserGroup> user8Groups = 
handler.getAllMemberGroupsForUser(domainId, userId8);
+        assertFalse(user8Groups.isEmpty(), "new user should be added to at 
least one group");
+        assertEquals(1, user8Groups.size(), "new user should be in exactly one 
group");
+        assertEquals(initialUserGroupId, user8Groups.get(0).getGroupId(),
+                "new user should be in the initialUserGroup");
+    }
+}


Reply via email to