diff --git a/build.gradle b/build.gradle index bd09e8581..e5a81ac92 100644 --- a/build.gradle +++ b/build.gradle @@ -169,6 +169,7 @@ dependencies { implementation 'com.diffplug.spotless:spotless-plugin-gradle:6.17.0' implementation 'org.mifos:openapi-java-client:2.0.3-SNAPSHOT' testImplementation 'org.hamcrest:hamcrest:2.2' + testImplementation 'org.apache.commons:commons-csv:1.5' } tasks.named('test') { diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index a69c6203e..c77de8445 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -25,7 +25,7 @@ paybill: settlement: "/confirmation" bulk-processor: - contactpoint: "https://bulk-connector.sandbox.fynarfin.io" + contactpoint: "https://localhost:8443" endpoints: bulk-transactions: "/batchtransactions" simulate: "/simulate" @@ -167,6 +167,10 @@ keycloak: secret: "toCozdqKQut8dpVi7WiXXGVeEqKrrTjp" grant_type: "password" +config: + completion-threshold-check: + completion-threshold: 90 + payerFundTransfer: tenant: payer: "gorilla" diff --git a/src/test/java/org/mifos/integrationtest/cucumber/stepdef/BatchApiStepDef.java b/src/test/java/org/mifos/integrationtest/cucumber/stepdef/BatchApiStepDef.java index 6a2154107..9ce611ca8 100644 --- a/src/test/java/org/mifos/integrationtest/cucumber/stepdef/BatchApiStepDef.java +++ b/src/test/java/org/mifos/integrationtest/cucumber/stepdef/BatchApiStepDef.java @@ -362,7 +362,7 @@ public void callBatchTransactionsRawEndpoint(int expectedStatus) { @And("I can mock the Batch Transaction Request DTO without payer info") public void mockBatchTransactionRequestDTOWithoutPayer() throws JsonProcessingException { - BatchRequestDTO batchRequestDTO = mockBatchTransactionRequestDTO(); + BatchRequestDTO batchRequestDTO = mockBatchTransactionRequestDTO("mojaloop"); batchRequestDTO = setCreditPartyInMockBatchTransactionRequestDTO(batchRequestDTO); batchRequestDTO = setDebitPartyInMockBatchTransactionRequestDTO(batchRequestDTO); assertThat(batchRequestDTO).isNotNull(); @@ -446,11 +446,11 @@ public void batchTearDown() { BaseStepDef.response = null; } - public BatchRequestDTO mockBatchTransactionRequestDTO() { + public BatchRequestDTO mockBatchTransactionRequestDTO(String paymentMode) { BatchRequestDTO batchRequestDTO = new BatchRequestDTO(); batchRequestDTO.setAmount("100"); batchRequestDTO.setCurrency("USD"); - batchRequestDTO.setSubType("mojaloop"); + batchRequestDTO.setSubType(paymentMode); batchRequestDTO.setDescriptionText("Integration test"); return batchRequestDTO; } @@ -648,4 +648,21 @@ public void iShouldAssertTotalTxnCountAndSuccessfulTxnCountInPaymentBatchDetailR assertThat(BaseStepDef.paymentBatchDetail).isNotNull(); assertThat(BaseStepDef.paymentBatchDetail.getInstructionList().size()).isEqualTo(3); } + @And("I can mock the Batch Transaction Request DTO without closed loop") + public void iCanMockTheBatchTransactionRequestDTOWithoutClosedLoop() throws JsonProcessingException { + BatchRequestDTO batchRequestDTO = mockBatchTransactionRequestDTO("closedloop"); + batchRequestDTO = setCreditPartyInMockBatchTransactionRequestDTO(batchRequestDTO); + batchRequestDTO = setDebitPartyInMockBatchTransactionRequestDTO(batchRequestDTO); + assertThat(batchRequestDTO).isNotNull(); + assertThat(batchRequestDTO.getCurrency()).isNotEmpty(); + assertThat(batchRequestDTO.getAmount()).isNotEmpty(); + assertThat(batchRequestDTO.getSubType()).isNotEmpty(); + assertThat(batchRequestDTO.getCreditParty()).isNotEmpty(); + BaseStepDef.batchRequestDTO = batchRequestDTO; + + List batchRequestDTOS = new ArrayList<>(); + batchRequestDTOS.add(batchRequestDTO); + BaseStepDef.batchRawRequest = objectMapper.writeValueAsString(batchRequestDTOS); + assertThat(BaseStepDef.batchRawRequest).isNotEmpty(); + } } diff --git a/src/test/java/org/mifos/integrationtest/cucumber/stepdef/BulkPaymentStepDef.java b/src/test/java/org/mifos/integrationtest/cucumber/stepdef/BulkPaymentStepDef.java new file mode 100644 index 000000000..9d0da6d86 --- /dev/null +++ b/src/test/java/org/mifos/integrationtest/cucumber/stepdef/BulkPaymentStepDef.java @@ -0,0 +1,162 @@ +package org.mifos.integrationtest.cucumber.stepdef; + +import com.google.gson.Gson; +import io.cucumber.java.en.And; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import io.restassured.RestAssured; +import io.restassured.builder.MultiPartSpecBuilder; +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.builder.ResponseSpecBuilder; +import io.restassured.specification.MultiPartSpecification; +import io.restassured.specification.RequestSpecification; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.mifos.integrationtest.common.BatchSummaryResponse; +import org.mifos.integrationtest.common.Utils; +import org.mifos.integrationtest.config.BulkProcessorConfig; +import org.mifos.integrationtest.config.OperationsAppConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; + +import static com.google.common.truth.Truth.assertThat; + +public class BulkPaymentStepDef extends BaseStepDef { + + private String batchId; + + private int completionPercent; + + @Value("${config.completion-threshold-check.completion-threshold}") + private int thresholdPercent; + + @Autowired + BulkProcessorConfig bulkProcessorConfig; + + @Autowired + OperationsAppConfig operationsAppConfig; + + @Given("the CSV file is available") + public boolean isCsvFileAvailable(){ + String fileName = "bulk-payment.csv"; + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + URL resource = classLoader.getResource(fileName); + return resource != null && resource.getPath().endsWith(".csv"); + } + + @When("initiate the batch transaction API with the input CSV file with tenant as {string}") + public void initiateTheBatchTransactionAPIWithTheInputCSVFileWithTenantAs(String tenant) { + Map headers = new HashMap<>(); + headers.put("Purpose", "test payment"); + headers.put("filename", "bulk_payment.csv"); + headers.put("X-CorrelationID", "12345678-6897-6798-6798-098765432134"); + headers.put("Platform-TenantId", tenant); + String fileContent = getFileContent("bulk-payment.csv"); + logger.info("file content: " + fileContent); + RequestSpecification requestSpec = getDefaultSpec(); + String response = RestAssured.given(requestSpec) + .baseUri(bulkProcessorConfig.bulkProcessorContactPoint) + .multiPart(getMultiPart(fileContent)) + .queryParam("type", "csv") + .headers(headers) + .expect() + .spec(new ResponseSpecBuilder().expectStatusCode(200).build()) + .when() + .post(bulkProcessorConfig.bulkTransactionEndpoint) + .andReturn().asString(); + batchId = fetchBatchId(response); + logger.info(batchId); + logger.info("Batch transaction API response: " + response); + } + + @Given("the batch ID for the submitted CSV file") + public void isBatchIdAvailable(){ + assertThat(batchId).isNotEmpty(); + } + + @And("poll the batch summary API using the batch ID and tenant as {string}") + public void pollTheBatchSummaryAPIUsingTheBatchIDAndTenantAs(String tenant) { + int retries = 5; + int intervalInSeconds = 30; + + Map headers = new HashMap<>(); + headers.put("Platform-TenantId", tenant); + RequestSpecification requestSpec = getDefaultSpec(); + + for(int index = 0; index < retries; index++) { + String response = RestAssured.given(requestSpec) + .baseUri(operationsAppConfig.operationAppContactPoint) + .param("batchId", batchId) + .headers(headers) + .expect() + .spec(new ResponseSpecBuilder().expectStatusCode(200).build()) + .when() + .get(operationsAppConfig.batchSummaryEndpoint) + .andReturn().asString(); + Gson gson = new Gson(); + BatchSummaryResponse batchSummaryResponse = gson.fromJson(response, BatchSummaryResponse.class); + assertThat(batchSummaryResponse).isNotNull(); + + if(batchSummaryResponse.getTotal() != 0){ + completionPercent = (int) (batchSummaryResponse.getSuccessful()/ batchSummaryResponse.getTotal() * 100); + } + Utils.sleep(intervalInSeconds); + } + } + + @Then("successful transactions percentage should be greater than or equal to minimum threshold") + public void batchSummarySuccessful(){ + assertThat(completionPercent).isNotNull(); + assertThat(completionPercent).isGreaterThan(thresholdPercent); + } + + private static RequestSpecification getDefaultSpec() { + RequestSpecification requestSpec = new RequestSpecBuilder().build(); + requestSpec.relaxedHTTPSValidation(); + return requestSpec; + } + + private MultiPartSpecification getMultiPart(String fileContent) { + return new MultiPartSpecBuilder(fileContent.getBytes()). + fileName("test.csv"). + controlName("file"). + mimeType("text/plain"). + build(); + } + + private String getFileContent(String filePath) { + File file = new File(filePath); + Reader reader; + CSVFormat csvFormat; + CSVParser csvParser = null; + try { + reader = new FileReader(file); + csvFormat = CSVFormat.DEFAULT.withDelimiter(','); + csvParser = new CSVParser(reader, csvFormat); + } catch (IOException e) { + throw new RuntimeException(e); + } + StringJoiner stringJoiner = new StringJoiner("\n"); + + for (CSVRecord csvRecord : csvParser) { + stringJoiner.add(csvRecord.toString()); + } + return stringJoiner.toString(); + } + + private String fetchBatchId(String response) { + String[] split = response.split(","); + return split[0].substring(31); + } +} \ No newline at end of file diff --git a/src/test/java/resources/batch_demo_csv/bulk_payment.csv b/src/test/java/resources/batch_demo_csv/bulk_payment.csv new file mode 100644 index 000000000..4346bfabb --- /dev/null +++ b/src/test/java/resources/batch_demo_csv/bulk_payment.csv @@ -0,0 +1,11 @@ +id,request_id,payment_mode,payer_identifier_type,payer_identifier,payee_identifier_type,payee_identifier,amount,currency,note +0,f1e22fe3-9740-4fba-97b6-78f43bfa7f2f,closedloop,msisdn,835322416,msisdn,277138039112,10,USD,Moja bulk test +1,39f6ac4d052e-72aa3ea4-e6f6-4880-877f,closedloop,msisdn,835322416,msisdn,277138039122,10,USD,Moja bulk test +2,a27631f6-6dd4-4d69-b4fc-8932bd721913,closedloop,msisdn,835322416,msisdn,277138039123,10,USD,Moja bulk test +3,3d21e6ea-c583-44ed-b94f-af909fa7616e,closedloop,msisdn,835322416,msisdn,277138039124,10,USD,Moja bulk test +4,15f9a0b0-2299-436d-8433-da564140ba66,closedloop,msisdn,835322416,msisdn,277138039512,10,USD,Moja bulk test +5,f1e22fe3-9740-4fba-97b6-78f49bfa7f2f,closedloop,msisdn,835322416,msisdn,277138039612,10,USD,Moja bulk test +6,39f6ac4d052e-72aa3ea4-e6f6-4380-877f,closedloop,msisdn,835322416,msisdn,277138039172,10,USD,Moja bulk test +7,a27631f6-6dd4-4d69-b4fc-8452bd721913,closedloop,msisdn,835322416,msisdn,277138039182,10,USD,Moja bulk test +8,3d21e6ea-c583-44ed-b94f-af909fu7616e,closedloop,msisdn,835322416,msisdn,277138039192,10,USD,Moja bulk test +9,15f9a0b0-2299-436d-8433-da667140ba66,closedloop,msisdn,835322416,msisdn,277138039120,10,USD,Moja bulk test \ No newline at end of file diff --git a/src/test/java/resources/bulkPayment.feature b/src/test/java/resources/bulkPayment.feature new file mode 100644 index 000000000..0eb059d39 --- /dev/null +++ b/src/test/java/resources/bulkPayment.feature @@ -0,0 +1,19 @@ +@gov +Feature: Test ability to make payment to individual with bank account + + Scenario: Input CSV file using the batch transaction API and poll batch summary API till we get completed status + Given I have tenant as "gorilla" + And I have the demo csv file "bulk_payment.csv" + And I create a new clientCorrelationId + And I have private key + And I generate signature + When I call the batch transactions endpoint with expected status of 202 + Then I should get non empty response + And I am able to parse batch transactions response + And I fetch batch ID from batch transaction API's response + Then I will sleep for 10000 millisecond + Given I have tenant as "gorilla" + When I call the batch summary API with expected status of 200 + Then I am able to parse batch summary response + And Status of transaction is "COMPLETED" + And I should have matching total txn count and successful txn count in response \ No newline at end of file