Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* 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.integrationtests;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.Map;
import java.util.UUID;
import okhttp3.MediaType;
import okhttp3.ResponseBody;
import org.apache.fineract.client.models.PostClientsRequest;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansRequest;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.client.models.RunReportsResponse;
import org.apache.fineract.integrationtests.client.IntegrationTest;
import org.apache.fineract.integrationtests.common.Utils;
import org.junit.jupiter.api.Test;
import retrofit2.Response;

/**
* End-to-End Integration Test for Report Execution and Observability. Verifies that the Reporting engine correctly
* queries and outputs data for Clients and Loans.
*/
public class ReportExecutionIntegrationTest extends IntegrationTest {

@Test
public void verifyClientListingReportAndMetrics() throws IOException {
String uniqueClientName = "ReportTestClient_" + UUID.randomUUID().toString().substring(0, 8);
Long clientId = createClient(uniqueClientName);
assertThat(clientId).isNotNull();

// Run the "Client Listing" Report (Table/JSON format)
Response<RunReportsResponse> response = okR(
fineractClient().reportsRun.runReportGetData("Client Listing", Map.of("R_officeId", "1"), false));

assertThat(response.code()).isEqualTo(200);
RunReportsResponse body = response.body();
assertThat(body.getColumnHeaders()).isNotEmpty();
// Verify our created client is in the output
assertThat(body.getData().toString()).contains(uniqueClientName);

// Run the "Client Listing" Report (CSV format)
Response<ResponseBody> csvResponse = okR(
fineractClient().reportsRun.runReportGetFile("Client Listing", Map.of("R_officeId", "1", "exportCSV", "true"), false));

assertThat(csvResponse.body().contentType()).isEqualTo(MediaType.parse("text/csv"));
String csvContent = csvResponse.body().string();
assertThat(csvContent).contains("Office/Branch");
assertThat(csvContent).contains(uniqueClientName);
}

@Test
public void verifyLoanListingReport() throws IOException {
// 1. Setup Data: Client -> Loan Product -> Loan Application -> Approval -> Disbursement
String uniqueClientName = "LoanReportClient_" + UUID.randomUUID().toString().substring(0, 8);
Long clientId = createClient(uniqueClientName);

// Create a simple Loan Product
Long loanProductId = createLoanProduct();

// Apply for a Loan
Long loanId = applyForLoan(clientId, loanProductId);

// Approve the Loan
approveLoan(loanId);

// Disburse the Loan (CRITICAL: Required for "Active Loans" report)
disburseLoan(loanId, BigDecimal.valueOf(1000.0));

// 2. Execution: Run "Active Loans - Details" Report
Response<RunReportsResponse> response = okR(
fineractClient().reportsRun.runReportGetData("Active Loans - Details", Map.of("R_officeId", "1", "R_loanOfficerId", "-1",
"R_currencyId", "-1", "R_fundId", "-1", "R_loanProductId", "-1", "R_loanPurposeId", "-1"), false));

// 3. Verification
assertThat(response.code()).isEqualTo(200);
RunReportsResponse body = response.body();

// Verify the generic report structure works
assertThat(body.getColumnHeaders()).isNotEmpty();

// Verify our specific Loan ID and Client Name are present in the JSON data
String responseString = body.getData().toString();

// Assert that the list actually contains data now
assertThat(responseString).as("Report should contain the active loan ID").contains(String.valueOf(loanId));
assertThat(responseString).as("Report should contain the client name").contains(uniqueClientName);
}

// --- Helpers ---

private Long createClient(String fullName) {
return ok(fineractClient().clients.create6(new PostClientsRequest().legalFormId(1L).officeId(1L).fullname(fullName)
.dateFormat(Utils.DATE_FORMAT).locale("en_US").active(true).activationDate("01 January 2023"))).getClientId();
}

private Long applyForLoan(Long clientId, Long loanProductId) {
PostLoansResponse response = ok(fineractClient().loans.calculateLoanScheduleOrSubmitLoanApplication(
new PostLoansRequest().dateFormat(Utils.DATE_FORMAT).locale("en_US").clientId(clientId).productId(loanProductId)
.principal(BigDecimal.valueOf(1000.0)).loanTermFrequency(12).loanTermFrequencyType(2) // Months
.numberOfRepayments(12).repaymentEvery(1).repaymentFrequencyType(2) // Months
.interestRatePerPeriod(BigDecimal.valueOf(1.5)).amortizationType(1) // Equal Installments
.interestType(1) // Flat
.interestCalculationPeriodType(1) // Same as repayment period
.transactionProcessingStrategyCode("mifos-standard-strategy").expectedDisbursementDate("01 January 2023")
.submittedOnDate("01 January 2023").loanType("individual"),
"application"));
return response.getLoanId();
}

private void approveLoan(Long loanId) {
ok(fineractClient().loans.stateTransitions(loanId,
new PostLoansLoanIdRequest().approvedOnDate("01 January 2023").dateFormat(Utils.DATE_FORMAT).locale("en_US"), "approve"));
}

private void disburseLoan(Long loanId, BigDecimal amount) {
ok(fineractClient().loans.stateTransitions(loanId, new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023")
.dateFormat(Utils.DATE_FORMAT).locale("en_US").transactionAmount(amount), "disburse"));
}

private Long createLoanProduct() {
PostLoanProductsResponse response = ok(fineractClient().loanProducts.createLoanProduct(new PostLoanProductsRequest()
.name(Utils.uniqueRandomStringGenerator("Report_Product_", 6)).shortName(Utils.uniqueRandomStringGenerator("", 4))
.description("Loan Product for Report Tests").currencyCode("USD").digitsAfterDecimal(2).inMultiplesOf(1)
.installmentAmountInMultiplesOf(1).minPrincipal(100.0).principal(1000.0).maxPrincipal(10000.0).numberOfRepayments(12)
.repaymentEvery(1).repaymentFrequencyType(2L) // Months
.interestRatePerPeriod(1.5).interestRateFrequencyType(2) // Months
.amortizationType(1) // Equal installments
.interestType(1) // Flat
.interestCalculationPeriodType(1) // Same as repayment period
.transactionProcessingStrategyCode("mifos-standard-strategy").accountingRule(1) // None
.daysInYearType(1).daysInMonthType(1).isInterestRecalculationEnabled(false).dateFormat(Utils.DATE_FORMAT).locale("en_US")));
return response.getResourceId();
}
}
Loading