diff --git a/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java b/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java index 01e6b6d25..914d2014f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java +++ b/fineract-core/src/main/java/org/apache/fineract/batch/command/CommandStrategyProvider.java @@ -149,6 +149,8 @@ public class CommandStrategyProvider { commandStrategies.put(CommandContext .resource("v1\\/savingsaccounts\\/" + NUMBER_REGEX + "\\/transactions\\/" + NUMBER_REGEX + OPTIONAL_COMMAND_PARAM_REGEX) .method(POST).build(), "savingsAccountAdjustTransactionCommandStrategy"); + commandStrategies.put(CommandContext.resource("v1\\/savingsaccounts\\/" + NUMBER_REGEX + "\\/charges").method(POST).build(), + "createSavingsAccountChargeCommandStrategy"); commandStrategies.put(CommandContext.resource("v1\\/loans\\/" + NUMBER_REGEX + "\\/charges").method(POST).build(), "createChargeCommandStrategy"); commandStrategies.put( diff --git a/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategy.java b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategy.java new file mode 100644 index 000000000..fc8b49170 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategy.java @@ -0,0 +1,71 @@ +/** + * 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.fineract.batch.command.internal; + +import static org.apache.fineract.batch.command.CommandStrategyUtils.relativeUrlWithoutVersion; + +import com.google.common.base.Splitter; +import jakarta.ws.rs.core.UriInfo; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.batch.command.CommandStrategy; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.portfolio.savings.api.SavingsAccountChargesApiResource; +import org.apache.http.HttpStatus; +import org.springframework.stereotype.Component; + +/** + * Implements {@link CommandStrategy} and Create Charge for a Savings Account. It passes the contents of the body from + * the BatchRequest to {@link SavingsAccountChargesApiResource} and gets back the response. This class will also catch + * any errors raised by {@link SavingsAccountChargesApiResource} and map those errors to appropriate status codes in + * BatchResponse. + * + * @see CommandStrategy + * @see BatchRequest + * @see BatchResponse + */ +@Component +@RequiredArgsConstructor +public class CreateSavingsAccountChargeCommandStrategy implements CommandStrategy { + + private final SavingsAccountChargesApiResource savingsAccountChargesApiResource; + + @Override + public BatchResponse execute(BatchRequest request, @SuppressWarnings("unused") UriInfo uriInfo) { + + final BatchResponse response = new BatchResponse(); + final String responseBody; + + response.setRequestId(request.getRequestId()); + response.setHeaders(request.getHeaders()); + + final List pathParameters = Splitter.on('/').splitToList(relativeUrlWithoutVersion(request)); + final Long savingsAccountId = Long.parseLong(pathParameters.get(1)); + + // Create a new charge for a savings account + responseBody = savingsAccountChargesApiResource.addSavingsAccountCharge(savingsAccountId, request.getBody()); + + response.setStatusCode(HttpStatus.SC_OK); + // Set the body of the response after Charge has been successfully created + response.setBody(responseBody); + + return response; + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java b/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java index 004869974..16b3c2487 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/batch/command/CommandStrategyProviderTest.java @@ -40,6 +40,7 @@ import org.apache.fineract.batch.command.internal.CreateChargeCommandStrategy; import org.apache.fineract.batch.command.internal.CreateClientCommandStrategy; import org.apache.fineract.batch.command.internal.CreateDatatableEntryCommandStrategy; import org.apache.fineract.batch.command.internal.CreateLoanRescheduleRequestCommandStrategy; +import org.apache.fineract.batch.command.internal.CreateSavingsAccountChargeCommandStrategy; import org.apache.fineract.batch.command.internal.CreateTransactionByLoanExternalIdCommandStrategy; import org.apache.fineract.batch.command.internal.CreateTransactionLoanCommandStrategy; import org.apache.fineract.batch.command.internal.DisburseLoanCommandStrategy; @@ -91,6 +92,8 @@ public class CommandStrategyProviderTest { Arguments.of("loans/external-id/8dfad438-2319-48ce-8520-10a62801e9a1?associations=all&exclude=guarantors", HttpMethod.GET, "getLoanByExternalIdCommandStrategy", mock(GetLoanByExternalIdCommandStrategy.class)), Arguments.of("savingsaccounts", HttpMethod.POST, "applySavingsCommandStrategy", mock(ApplySavingsCommandStrategy.class)), + Arguments.of("savingsaccounts/123/charges", HttpMethod.POST, "createSavingsAccountChargeCommandStrategy", + mock(CreateSavingsAccountChargeCommandStrategy.class)), Arguments.of("loans/123/charges", HttpMethod.POST, "createChargeCommandStrategy", mock(CreateChargeCommandStrategy.class)), Arguments.of("loans/external-id/8dfad438-2319-48ce-8520-10a62801e9a1/charges", HttpMethod.POST, "createChargeByLoanExternalIdCommandStrategy", mock(CreateChargeByLoanExternalIdCommandStrategy.class)), diff --git a/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategyTest.java b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategyTest.java new file mode 100644 index 000000000..a5469118d --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/batch/command/internal/CreateSavingsAccountChargeCommandStrategyTest.java @@ -0,0 +1,116 @@ +/** + * 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.fineract.batch.command.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.core.UriInfo; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.fineract.batch.domain.BatchRequest; +import org.apache.fineract.batch.domain.BatchResponse; +import org.apache.fineract.portfolio.savings.api.SavingsAccountChargesApiResource; +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for {@link CreateSavingsAccountChargeCommandStrategy}. + */ +public class CreateSavingsAccountChargeCommandStrategyTest { + + /** + * Test {@link CreateSavingsAccountChargeCommandStrategy#execute} happy path scenario. + */ + @Test + public void testExecuteSuccessScenario() { + final TestContext testContext = new TestContext(); + final Long savingsAccountId = Long.valueOf(RandomStringUtils.randomNumeric(4)); + final BatchRequest batchRequest = getBatchRequest(savingsAccountId); + final String responseBody = "myResponseBody"; + + when(testContext.savingsAccountChargesApiResource.addSavingsAccountCharge(savingsAccountId, batchRequest.getBody())) + .thenReturn(responseBody); + + BatchResponse batchResponse = testContext.subjectToTest.execute(batchRequest, testContext.uriInfo); + + assertEquals(HttpStatus.SC_OK, batchResponse.getStatusCode()); + assertSame(responseBody, batchResponse.getBody()); + assertEquals(batchRequest.getRequestId(), batchResponse.getRequestId()); + assertEquals(batchRequest.getHeaders(), batchResponse.getHeaders()); + + verify(testContext.savingsAccountChargesApiResource).addSavingsAccountCharge(savingsAccountId, batchRequest.getBody()); + } + + /** + * Creates and returns a request with the given savings account id. + * + * @param savingsAccountId + * the savings account id + * @return BatchRequest + */ + private BatchRequest getBatchRequest(final Long savingsAccountId) { + + final BatchRequest br = new BatchRequest(); + String relativeUrl = "savingsaccounts/" + savingsAccountId + "/charges"; + + br.setRequestId(Long.valueOf(RandomStringUtils.randomNumeric(5))); + br.setRelativeUrl(relativeUrl); + br.setMethod(HttpMethod.POST); + br.setReference(Long.valueOf(RandomStringUtils.randomNumeric(5))); + br.setBody("{\"chargeId\":\"1\",\"amount\":\"100\"}"); + + return br; + } + + /** + * Private test context class used since testng runs in parallel to avoid state between tests + */ + private static class TestContext { + + /** + * Mock URI info. + */ + @Mock + private UriInfo uriInfo; + + /** + * Mock savings account charges API resource. + */ + @Mock + private SavingsAccountChargesApiResource savingsAccountChargesApiResource; + + /** + * The {@link CreateSavingsAccountChargeCommandStrategy} under test. + */ + private final CreateSavingsAccountChargeCommandStrategy subjectToTest; + + /** + * Constructor. + */ + TestContext() { + MockitoAnnotations.openMocks(this); + subjectToTest = new CreateSavingsAccountChargeCommandStrategy(savingsAccountChargesApiResource); + } + } +}