This is an automated email from the ASF dual-hosted git repository. lahirujayathilake pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
commit ff923d32ce92da8aa699ec65584f91f8471c9b10 Author: lahiruj <[email protected]> AuthorDate: Wed Oct 15 17:46:33 2025 -0400 included AMIE account reactivate handler and its test class --- .../handler/RequestAccountReactivateHandler.java | 96 +++++++++++ .../amie/service/ProjectMembershipService.java | 20 +++ amie-decoder/src/main/proto/amie_packets.proto | 20 ++- .../RequestAccountReactivateHandlerTest.java | 176 +++++++++++++++++++++ .../incoming-request.json | 19 +++ .../outgoing-notify.json | 18 +++ 6 files changed, 345 insertions(+), 4 deletions(-) diff --git a/amie-decoder/src/main/java/org/apache/custos/amie/handler/RequestAccountReactivateHandler.java b/amie-decoder/src/main/java/org/apache/custos/amie/handler/RequestAccountReactivateHandler.java new file mode 100644 index 000000000..4403cf82b --- /dev/null +++ b/amie-decoder/src/main/java/org/apache/custos/amie/handler/RequestAccountReactivateHandler.java @@ -0,0 +1,96 @@ +/* + * 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.custos.amie.handler; + +import com.fasterxml.jackson.databind.JsonNode; +import org.apache.custos.amie.client.AmieClient; +import org.apache.custos.amie.model.PacketEntity; +import org.apache.custos.amie.service.ProjectMembershipService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Handles the 'request_account_reactivate' AMIE packet. + * <p> + * This transaction asks the local site to reactivate a user's association with a specific project. + * Upon successful local processing, it sends a 'notify_account_reactivate' reply. + */ +@Component +public class RequestAccountReactivateHandler implements PacketHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(RequestAccountReactivateHandler.class); + + private final AmieClient amieClient; + private final ProjectMembershipService membershipService; + + public RequestAccountReactivateHandler(AmieClient amieClient, ProjectMembershipService membershipService) { + this.amieClient = amieClient; + this.membershipService = membershipService; + } + + @Override + public String supportsType() { + return "request_account_reactivate"; + } + + @Override + public void handle(JsonNode packetJson, PacketEntity packetEntity) { + LOGGER.info("Starting 'request_account_reactivate' handler for packet amie_id [{}].", packetEntity.getAmieId()); + + JsonNode body = packetJson.path("body"); + String projectId = body.path("ProjectID").asText(); + String personId = body.path("PersonID").asText(); + + Assert.hasText(projectId, "'ProjectID' must not be empty."); + Assert.hasText(personId, "'PersonID' must not be empty."); + LOGGER.info("Packet validated. Reactivating account for user [{}] on project [{}].", personId, projectId); + + membershipService.reactivateMembershipsByPersonAndProject(projectId, personId); + + sendSuccessReply(packetEntity.getAmieId(), body); + + LOGGER.info("Successfully completed 'request_account_reactivate' handler and sent reply for packet amie_id [{}].", packetEntity.getAmieId()); + } + + private void sendSuccessReply(long packetRecId, JsonNode body) { + Map<String, Object> reply = new HashMap<>(); + Map<String, Object> bodyContent = new HashMap<>(); + + bodyContent.put("ProjectID", body.path("ProjectID").asText()); + bodyContent.put("PersonID", body.path("PersonID").asText()); + + List<String> resourceList = new ArrayList<>(); + JsonNode rlNode = body.path("ResourceList"); + if (rlNode.isArray()) { + rlNode.forEach(node -> resourceList.add(node.asText())); + } + bodyContent.put("ResourceList", resourceList); + + reply.put("type", "notify_account_reactivate"); + reply.put("body", bodyContent); + + amieClient.replyToPacket(packetRecId, reply); + } +} diff --git a/amie-decoder/src/main/java/org/apache/custos/amie/service/ProjectMembershipService.java b/amie-decoder/src/main/java/org/apache/custos/amie/service/ProjectMembershipService.java index 9b9de3770..df2ef664b 100644 --- a/amie-decoder/src/main/java/org/apache/custos/amie/service/ProjectMembershipService.java +++ b/amie-decoder/src/main/java/org/apache/custos/amie/service/ProjectMembershipService.java @@ -136,4 +136,24 @@ public class ProjectMembershipService { LOGGER.info("Reactivated {} PI memberships for project [{}]", piMemberships.size(), projectId); } + /** + * Reactivates memberships for a specific person on a project. + * + * @param projectId The project ID. + * @param personId Person ID + */ + @Transactional + public void reactivateMembershipsByPersonAndProject(String projectId, String personId) { + List<ProjectMembershipEntity> memberships = membershipRepository.findByProjectIdAndClusterAccount_Person_Id(projectId, personId); + + if (memberships.isEmpty()) { + LOGGER.warn("No memberships found for person [{}] on project [{}]. No action taken.", personId, projectId); + return; + } + + memberships.forEach(membership -> membership.setActive(true)); + membershipRepository.saveAll(memberships); + LOGGER.info("Reactivated {} membership(s) for person [{}] on project [{}]", memberships.size(), personId, projectId); + } + } diff --git a/amie-decoder/src/main/proto/amie_packets.proto b/amie-decoder/src/main/proto/amie_packets.proto index 804c39e96..760273203 100644 --- a/amie-decoder/src/main/proto/amie_packets.proto +++ b/amie-decoder/src/main/proto/amie_packets.proto @@ -14,8 +14,9 @@ enum PacketType { REQUEST_PROJECT_REACTIVATE = 3; REQUEST_ACCOUNT_CREATE = 4; REQUEST_ACCOUNT_INACTIVATE = 5; - REQUEST_USER_MODIFY = 6; - REQUEST_PERSON_MERGE = 7; + REQUEST_ACCOUNT_REACTIVATE = 6; + REQUEST_USER_MODIFY = 7; + REQUEST_PERSON_MERGE = 8; } // Expected reply descriptor from the AMIE header @@ -114,6 +115,16 @@ message RequestAccountInactivate { google.protobuf.Struct extra_fields = 99; } +// request_account_reactivate +message RequestAccountReactivate { + string project_id = 1; // "ProjectID" + string person_id = 2; // "PersonID" + repeated string resource_list = 3; // "ResourceList" + string allocated_resource = 4; + + google.protobuf.Struct extra_fields = 99; +} + // request_user_modify message RequestUserModify { string action_type = 1; // "ActionType" (e.g., "replace" or "delete") @@ -158,8 +169,9 @@ message Packet { RequestProjectReactivate request_project_reactivate = 12; RequestAccountCreate request_account_create = 13; RequestAccountInactivate request_account_inactivate = 14; - RequestUserModify request_user_modify = 15; - RequestPersonMerge request_person_merge = 16; + RequestAccountReactivate request_account_reactivate = 15; + RequestUserModify request_user_modify = 16; + RequestPersonMerge request_person_merge = 17; // A fallback for any packet types this schema doesn't support google.protobuf.Struct unknown_body = 50; diff --git a/amie-decoder/src/test/java/org/apache/custos/amie/handler/RequestAccountReactivateHandlerTest.java b/amie-decoder/src/test/java/org/apache/custos/amie/handler/RequestAccountReactivateHandlerTest.java new file mode 100644 index 000000000..52967841e --- /dev/null +++ b/amie-decoder/src/test/java/org/apache/custos/amie/handler/RequestAccountReactivateHandlerTest.java @@ -0,0 +1,176 @@ +/* + * 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.custos.amie.handler; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.custos.amie.client.AmieClient; +import org.apache.custos.amie.model.PacketEntity; +import org.apache.custos.amie.service.ProjectMembershipService; +import org.apache.custos.amie.util.JsonTestUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +@Tag("unit") +class RequestAccountReactivateHandlerTest { + + @Mock + private AmieClient amieClient; + + @Mock + private ProjectMembershipService membershipService; + + private RequestAccountReactivateHandler handler; + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + handler = new RequestAccountReactivateHandler(amieClient, membershipService); + objectMapper = new ObjectMapper(); + } + + @Test + void supportsType_shouldReturnCorrectType() { + assertThat(handler.supportsType()).isEqualTo("request_account_reactivate"); + } + + @Test + void handle_withValidPacket_shouldProcessSuccessfully() throws Exception { + JsonNode incomingPacket = JsonTestUtils.loadMockPacket("request_account_reactivate", "incoming-request.json"); + JsonNode expectedReply = JsonTestUtils.loadMockPacket("request_account_reactivate", "outgoing-notify.json"); + + PacketEntity packetEntity = new PacketEntity(); + packetEntity.setAmieId(233497923L); + packetEntity.setType("request_account_reactivate"); + + handler.handle(incomingPacket, packetEntity); + + verify(membershipService).reactivateMembershipsByPersonAndProject("test-project-456", "test-user-person-123"); + + @SuppressWarnings("unchecked") + ArgumentCaptor<Map<String, Object>> replyCaptor = ArgumentCaptor.forClass(Map.class); + verify(amieClient).replyToPacket(eq(233497923L), replyCaptor.capture()); + + Map<String, Object> sentReply = replyCaptor.getValue(); + assertThat(sentReply).containsKey("type"); + assertThat(sentReply.get("type")).isEqualTo("notify_account_reactivate"); + assertThat(sentReply).containsKey("body"); + + @SuppressWarnings("unchecked") + Map<String, Object> sentBody = (Map<String, Object>) sentReply.get("body"); + JsonNode expectedBody = expectedReply.path("body"); + + assertThat(sentBody).containsKey("ProjectID"); + assertThat(sentBody).containsKey("PersonID"); + assertThat(sentBody).containsKey("ResourceList"); + + assertThat(sentBody.get("ProjectID")).isEqualTo(expectedBody.path("ProjectID").asText()); + assertThat(sentBody.get("PersonID")).isEqualTo(expectedBody.path("PersonID").asText()); + } + + @Test + void handle_withMissingProjectId_shouldThrowException() { + JsonNode packetJson = createPacketJsonWithMissingField("ProjectID"); + PacketEntity packetEntity = createPacketEntity(); + + assertThatThrownBy(() -> handler.handle(packetJson, packetEntity)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("'ProjectID' must not be empty"); + } + + @Test + void handle_withMissingPersonId_shouldThrowException() { + JsonNode packetJson = createPacketJsonWithMissingField("PersonID"); + PacketEntity packetEntity = createPacketEntity(); + + assertThatThrownBy(() -> handler.handle(packetJson, packetEntity)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("'PersonID' must not be empty"); + } + + @Test + void handle_withEmptyResourceList_shouldProcessSuccessfully() { + JsonNode packetJson = createValidPacketJson(); + PacketEntity packetEntity = createPacketEntity(); + + handler.handle(packetJson, packetEntity); + + verify(membershipService).reactivateMembershipsByPersonAndProject("test-project-456", "test-user-person-123"); + verify(amieClient).replyToPacket(eq(12345L), any(Map.class)); + } + + @Test + void handle_withNonEmptyResourceList_shouldProcessSuccessfully() { + JsonNode packetJson = createPacketJsonWithResourceList(); + PacketEntity packetEntity = createPacketEntity(); + + handler.handle(packetJson, packetEntity); + + verify(membershipService).reactivateMembershipsByPersonAndProject("test-project-456", "test-user-person-123"); + verify(amieClient).replyToPacket(eq(12345L), any(Map.class)); + } + + private JsonNode createValidPacketJson() { + return objectMapper.createObjectNode().set("body", objectMapper.createObjectNode() + .put("ProjectID", "test-project-456") + .put("PersonID", "test-user-person-123") + .set("ResourceList", objectMapper.createArrayNode())); + } + + private JsonNode createPacketJsonWithMissingField(String missingField) { + ObjectNode body = objectMapper.createObjectNode() + .put("ProjectID", "test-project-456") + .put("PersonID", "test-user-person-123") + .set("ResourceList", objectMapper.createArrayNode()); + + if (body.has(missingField)) { + body.remove(missingField); + } + + return objectMapper.createObjectNode().set("body", body); + } + + private JsonNode createPacketJsonWithResourceList() { + return objectMapper.createObjectNode().set("body", objectMapper.createObjectNode() + .put("ProjectID", "test-project-456") + .put("PersonID", "test-user-person-123") + .set("ResourceList", objectMapper.createArrayNode().add("test-resource1.testsite.testorg").add("test-resource2.testsite.testorg"))); + } + + private PacketEntity createPacketEntity() { + PacketEntity entity = new PacketEntity(); + entity.setAmieId(12345L); + entity.setType("request_account_reactivate"); + return entity; + } +} diff --git a/amie-decoder/src/test/resources/mock-data/request_account_reactivate/incoming-request.json b/amie-decoder/src/test/resources/mock-data/request_account_reactivate/incoming-request.json new file mode 100644 index 000000000..9a470d59e --- /dev/null +++ b/amie-decoder/src/test/resources/mock-data/request_account_reactivate/incoming-request.json @@ -0,0 +1,19 @@ +{ + "type": "request_account_reactivate", + "header": { + "packet_rec_id": 233497923, + "packet_id": "233497923", + "site_code": "TestSite", + "remote_site_name": "TestSite", + "timestamp": "2025-10-15T21:30:00Z", + "version": "1.0" + }, + "body": { + "ProjectID": "test-project-456", + "PersonID": "test-user-person-123", + "ResourceList": [ + "test-resource1.testsite.testorg" + ], + "AllocatedResource": "test-resource1.testsite.testorg" + } +} diff --git a/amie-decoder/src/test/resources/mock-data/request_account_reactivate/outgoing-notify.json b/amie-decoder/src/test/resources/mock-data/request_account_reactivate/outgoing-notify.json new file mode 100644 index 000000000..d78529c8b --- /dev/null +++ b/amie-decoder/src/test/resources/mock-data/request_account_reactivate/outgoing-notify.json @@ -0,0 +1,18 @@ +{ + "type": "notify_account_reactivate", + "header": { + "packet_rec_id": 233497923, + "packet_id": "233497923", + "site_code": "TestSite", + "remote_site_name": "TestSite", + "timestamp": "2025-10-15T21:30:00Z", + "version": "1.0" + }, + "body": { + "ProjectID": "test-project-456", + "PersonID": "test-user-person-123", + "ResourceList": [ + "test-resource1.testsite.testorg" + ] + } +}
