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"); + } +}
