You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by la...@apache.org on 2025/10/17 17:21:07 UTC
(airavata-custos) 23/26: included AMIE account reactivate handler and its test class
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 <la...@gmail.com>
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"
+ ]
+ }
+}