From 756b5997d580dfa67c067832d8207bb9bed47f76 Mon Sep 17 00:00:00 2001 From: Oluwayhemisi Date: Mon, 10 Aug 2026 14:18:55 +0100 Subject: [PATCH 1/8] Implement CI/CD Pipeline --- .github/workflows/build.yml | 29 ++++++++++++++++++++++++ .github/workflows/publish.yml | 42 +++++++++++++++++++++++++++++++++++ README.md | 2 +- pom.xml | 36 +++++++++++++++++------------- 4 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6b5fad8 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,29 @@ +name: Build and Test + +on: + push: + branches: + - main + + pull_request: + branches: + - main + +jobs: + build: + name: Build and Test + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Java 11 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '11' + cache: maven + + - name: Build and test + run: mvn --batch-mode clean verify \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..bf7ed69 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,42 @@ +name: Publish to Maven Central + +on: + release: + types: [published] + +jobs: + publish: + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '11' + cache: maven + + server-id: central + server-username: CENTRAL_USERNAME + server-password: CENTRAL_PASSWORD + + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + + - name: Publish + run: mvn --batch-mode clean deploy -DskipTests + env: + CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }} + GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + diff --git a/README.md b/README.md index 00d8816..ab30f47 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Add the following dependency to your `pom.xml` if using Maven: com.monnify monnify-java-sdk - 1.1.0 + 1.2.0 ``` diff --git a/pom.xml b/pom.xml index 5b3593d..33616ae 100644 --- a/pom.xml +++ b/pom.xml @@ -101,22 +101,6 @@ - - - org.apache.maven.plugins - maven-gpg-plugin - 3.1.0 - - - sign-artifacts - verify - - sign - - - - - org.sonatype.central central-publishing-maven-plugin @@ -175,6 +159,26 @@ ${maven.compiler.target} + + org.apache.maven.plugins + maven-gpg-plugin + 3.1.0 + + + ${gpg.keyname} + ${gpg.passphrase} + + + + + sign-artifacts + verify + + sign + + + + From ecc62ef55e290c6ab7b59595d0057291f7606e07 Mon Sep 17 00:00:00 2001 From: Oluwayhemisi Date: Mon, 10 Aug 2026 14:29:45 +0100 Subject: [PATCH 2/8] Implement CI/CD Pipeline --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab30f47..00d8816 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Add the following dependency to your `pom.xml` if using Maven: com.monnify monnify-java-sdk - 1.2.0 + 1.1.0 ``` From 029574768cec0df3ce4d9755436a04ed9bf43a88 Mon Sep 17 00:00:00 2001 From: Oluwayhemisi Date: Tue, 11 Aug 2026 20:05:03 +0100 Subject: [PATCH 3/8] Implement CI/CD Pipeline --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b5fad8..5385fb5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,11 +3,12 @@ name: Build and Test on: push: branches: - - main + - 'main' pull_request: branches: - - main + - 'main' + - 'feature/**' jobs: build: From 2f6ec47866bae39b1fd08d02d0ad3cfda1a5b042 Mon Sep 17 00:00:00 2001 From: Oluwayhemisi Date: Tue, 11 Aug 2026 20:25:09 +0100 Subject: [PATCH 4/8] Added environment variables --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5385fb5..de5a7dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,10 @@ jobs: build: name: Build and Test runs-on: ubuntu-latest + env: + apiKey: ${{ secrets.API_KEY }} + secretKey: ${{secrets.SECRET_KEY}} + contractCode: ${{secrets.CONTRACT_CODE}} steps: - name: Checkout repository From 83c9c87aec5022d283013efcb1c62ecb83c80475 Mon Sep 17 00:00:00 2001 From: Oluwayhemisi Date: Tue, 18 Aug 2026 19:29:09 +0100 Subject: [PATCH 5/8] Added environment variables --- pom.xml | 30 + src/main/java/com/monnify/Monnify.java | 16 + .../monnify/services/auth/AuthService.java | 9 + .../java/com/monnify/BaseMonnifyMockTest.java | 71 ++ .../mocktests/AuthServiceMockTest.java | 110 +++ .../mocktests/BankServiceMockTest.java | 175 +++++ .../mocktests/DirectDebitServiceMockTest.java | 488 ++++++++++++ .../mocktests/DisbursementMockTest.java | 727 ++++++++++++++++++ .../mocktests/InvoiceServiceMockTest.java | 537 +++++++++++++ .../LimitProfileServiceMockTest.java | 471 ++++++++++++ .../mocktests/PaycodeServiceMockTest.java | 320 ++++++++ .../mocktests/RefundServiceMockTest.java | 259 +++++++ .../mocktests/ReservedAccountServiceTest.java | 462 +++++++++++ .../mocktests/SettlementServiceMockTest.java | 410 ++++++++++ .../mocktests/SubAccountServiceTest.java | 271 +++++++ .../mocktests/TransactionServiceMockTest.java | 502 ++++++++++++ .../VerificationServiceMockTest.java | 371 +++++++++ .../mocktests/WebhookServiceMockTest.java | 403 ++++++++++ .../com/monnify/services/TestEnvironment.java | 22 + .../directdebit/DirectDebitServiceTest.java | 1 - .../disbursement/DisbursementServiceTest.java | 36 +- .../services/invoice/InvoiceServiceTest.java | 1 - .../services/paycode/PaycodeServiceTest.java | 1 - .../settlement/SettlementServiceTest.java | 1 - .../services/webhook/WebhookServiceTest.java | 2 - 25 files changed, 5672 insertions(+), 24 deletions(-) create mode 100644 src/test/java/com/monnify/BaseMonnifyMockTest.java create mode 100644 src/test/java/com/monnify/mocktests/AuthServiceMockTest.java create mode 100644 src/test/java/com/monnify/mocktests/BankServiceMockTest.java create mode 100644 src/test/java/com/monnify/mocktests/DirectDebitServiceMockTest.java create mode 100644 src/test/java/com/monnify/mocktests/DisbursementMockTest.java create mode 100644 src/test/java/com/monnify/mocktests/InvoiceServiceMockTest.java create mode 100644 src/test/java/com/monnify/mocktests/LimitProfileServiceMockTest.java create mode 100644 src/test/java/com/monnify/mocktests/PaycodeServiceMockTest.java create mode 100644 src/test/java/com/monnify/mocktests/RefundServiceMockTest.java create mode 100644 src/test/java/com/monnify/mocktests/ReservedAccountServiceTest.java create mode 100644 src/test/java/com/monnify/mocktests/SettlementServiceMockTest.java create mode 100644 src/test/java/com/monnify/mocktests/SubAccountServiceTest.java create mode 100644 src/test/java/com/monnify/mocktests/TransactionServiceMockTest.java create mode 100644 src/test/java/com/monnify/mocktests/VerificationServiceMockTest.java create mode 100644 src/test/java/com/monnify/mocktests/WebhookServiceMockTest.java create mode 100644 src/test/java/com/monnify/services/TestEnvironment.java diff --git a/pom.xml b/pom.xml index 33616ae..9fbc1ca 100644 --- a/pom.xml +++ b/pom.xml @@ -55,6 +55,12 @@ 1.18.30 provided + + org.wiremock + wiremock + 3.13.1 + test + com.squareup.okhttp3 okhttp @@ -180,6 +186,30 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + + + **/*MockTest.java + + + + + diff --git a/src/main/java/com/monnify/Monnify.java b/src/main/java/com/monnify/Monnify.java index 482680e..7c7d7dd 100644 --- a/src/main/java/com/monnify/Monnify.java +++ b/src/main/java/com/monnify/Monnify.java @@ -69,6 +69,22 @@ public static String getBaseUrl() { ensureInitialized(); return BASE_URL; } + /** + * Allows tests to override the API base URL. + * + * This method is package-private so it is not exposed as part + * of the public SDK API. + */ + static void setBaseUrlForTesting(String baseUrl) { + if (!initialized) { + throw new MonnifyException( + "Monnify must be initialized before setting the test base URL." + ); + } + + BASE_URL = baseUrl; + } + private static void ensureInitialized() { if (!initialized) { diff --git a/src/main/java/com/monnify/services/auth/AuthService.java b/src/main/java/com/monnify/services/auth/AuthService.java index 6ad18ec..37d91ea 100644 --- a/src/main/java/com/monnify/services/auth/AuthService.java +++ b/src/main/java/com/monnify/services/auth/AuthService.java @@ -73,4 +73,13 @@ public static synchronized String getToken() { private static String encodeToBase64(String apiKey, String secretKey) { return Base64.getEncoder().encodeToString(String.format("%s:%s", apiKey, secretKey).getBytes(StandardCharsets.UTF_8)); } + /** + * Resets cached authentication state. + * + * This method is intended for testing only. + */ + public static void resetForTesting() { + authToken = null; + expiryTime = 0; + } } diff --git a/src/test/java/com/monnify/BaseMonnifyMockTest.java b/src/test/java/com/monnify/BaseMonnifyMockTest.java new file mode 100644 index 0000000..f4270fe --- /dev/null +++ b/src/test/java/com/monnify/BaseMonnifyMockTest.java @@ -0,0 +1,71 @@ +package com.monnify; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.google.gson.Gson; +import com.monnify.services.auth.AuthService; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; + +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.reset; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.configureFor; + +public abstract class BaseMonnifyMockTest { + + protected final Gson gson = new Gson(); + + protected static WireMockServer wireMockServer; + + protected static final String API_KEY = "MK_TEST_API_KEY"; + protected static final String SECRET_KEY = "test-secret-key"; + protected static final String ACCESS_TOKEN = "mock-access-token"; + + @BeforeAll + static void setUpWireMock() { + wireMockServer = new WireMockServer(8089); + wireMockServer.start(); + configureFor("localhost", 8089); + Monnify.initialize(API_KEY, SECRET_KEY); + Monnify.setBaseUrlForTesting("http://localhost:8089"); + } + + @BeforeEach + void resetWireMock() { + reset(); + AuthService.resetForTesting(); + mockAuthentication(); + + } + protected void mockAuthentication() { + + stubFor( + post(urlPathEqualTo("/api/v1/auth/login")) + .withHeader("Authorization", matching("Basic .*")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\": true," + + "\"responseMessage\": \"success\"," + + "\"responseCode\": \"0\"," + + "\"responseBody\": {" + + "\"accessToken\": \"mock-access-token\"," + + "\"expiresIn\": 3600" + + "}" + + "}"))); + } + + @AfterAll + static void tearDownWireMock() { + + if (wireMockServer != null) { + wireMockServer.stop(); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/monnify/mocktests/AuthServiceMockTest.java b/src/test/java/com/monnify/mocktests/AuthServiceMockTest.java new file mode 100644 index 0000000..b37df0c --- /dev/null +++ b/src/test/java/com/monnify/mocktests/AuthServiceMockTest.java @@ -0,0 +1,110 @@ +package com.monnify.mocktests; + +import com.monnify.BaseMonnifyMockTest; +import com.monnify.services.auth.AuthService; +import org.junit.jupiter.api.Test; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +class AuthServiceMockTest extends BaseMonnifyMockTest { + @Test + void shouldReturnAccessTokenWhenApiReturnsSuccess() { + + stubFor( + post(urlPathEqualTo("/api/v1/auth/login")) + .withHeader("Authorization", matching("Basic .*")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\": true," + + "\"responseMessage\": \"success\"," + + "\"responseCode\": \"0\"," + + "\"responseBody\": {" + + "\"accessToken\": \"mock-access-token\"," + + "\"expiresIn\": 3600" + + "}" + + "}" + ) + ) + ); + + String token = AuthService.getToken(); + assertNotNull(token); + assertEquals(ACCESS_TOKEN, token); + verify(1, postRequestedFor(urlPathEqualTo("/api/v1/auth/login")) + ); + } + @Test + void shouldSendCorrectAuthorizationHeader() { + stubFor( + post(urlPathEqualTo("/api/v1/auth/login")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader( + "Content-Type", + "application/json" + ) + .withBody( + "{" + + "\"requestSuccessful\": true," + + "\"responseMessage\": \"success\"," + + "\"responseCode\": \"0\"," + + "\"responseBody\": {" + + "\"accessToken\": \"mock-access-token\"," + + "\"expiresIn\": 3600" + + "}" + + "}" + ) + ) + ); + + + AuthService.getToken(); + String credentials = API_KEY + ":" + SECRET_KEY; + + String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8)); + verify(postRequestedFor(urlPathEqualTo("/api/v1/auth/login")) + .withHeader("Authorization", equalTo("Basic " + encodedCredentials))); + } + + + @Test + void shouldUsePostMethodForAuthentication() { + + stubFor( + post(urlPathEqualTo("/api/v1/auth/login")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader( + "Content-Type", + "application/json" + ) + .withBody( + "{" + + "\"requestSuccessful\": true," + + "\"responseMessage\": \"success\"," + + "\"responseCode\": \"0\"," + + "\"responseBody\": {" + + "\"accessToken\": \"mock-access-token\"," + + "\"expiresIn\": 3600" + + "}" + + "}" + ) + ) + ); + + + AuthService.getToken(); + verify(1, postRequestedFor(urlPathEqualTo("/api/v1/auth/login"))); + verify(0, getRequestedFor(urlPathEqualTo("/api/v1/auth/login"))); + } + + +} \ No newline at end of file diff --git a/src/test/java/com/monnify/mocktests/BankServiceMockTest.java b/src/test/java/com/monnify/mocktests/BankServiceMockTest.java new file mode 100644 index 0000000..c334201 --- /dev/null +++ b/src/test/java/com/monnify/mocktests/BankServiceMockTest.java @@ -0,0 +1,175 @@ +package com.monnify.mocktests; + + +import com.monnify.BaseMonnifyMockTest; +import com.monnify.models.MonnifyBaseResponse; +import com.monnify.models.bank.BankResponse; +import com.monnify.services.bank.BankService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BankServiceMockTest extends BaseMonnifyMockTest { + + private BankService bankService; + + @BeforeEach + void setUp() { + bankService = new BankService(); + } + + @Test + void shouldReturnBanksWhenApiReturnsSuccess() { + + stubFor(get(urlPathEqualTo("/api/v1/banks")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + "\"requestSuccessful\": true," + + "\"responseMessage\": \"success\"," + + "\"responseCode\": \"0\"," + + "\"responseBody\": [" + + "{" + + "\"code\": \"044\"," + + "\"name\": \"Access Bank\"," + + "\"ussdTemplate\": \"*901*\"," + + "\"baseUssdCode\": \"*901#\"," + + "\"transferUssdTemplate\": \"*901*AMOUNT*ACCOUNT#\"" + + "}," + + "{" + + "\"code\": \"058\"," + + "\"name\": \"Guaranty Trust Bank\"," + + "\"ussdTemplate\": \"*737*\"," + + "\"baseUssdCode\": \"*737#\"," + + "\"transferUssdTemplate\": \"*737*AMOUNT*ACCOUNT#\"" + + "}" + + "]" + + "}"))); + + MonnifyBaseResponse> response = bankService.getBanks(); + assertNotNull(response); + assertEquals("0", response.getResponseCode()); + assertEquals("success", response.getResponseMessage()); + assertNotNull(response.getResponseBody()); + assertEquals(2, response.getResponseBody().size()); + BankResponse firstBank = response.getResponseBody().get(0); + assertEquals("044", firstBank.code); + assertEquals("Access Bank", firstBank.getName()); + assertEquals("*901*", firstBank.getUssdTemplate()); + assertEquals("*901#", firstBank.getBaseUssdCode()); + assertEquals("*901*AMOUNT*ACCOUNT#", firstBank.getTransferUssdTemplate()); + BankResponse secondBank = response.getResponseBody().get(1); + assertEquals("058", secondBank.code); + + assertEquals("Guaranty Trust Bank", secondBank.getName()); + + verify( + 1, + getRequestedFor( + urlPathEqualTo("/api/v1/banks") + ) + ); + } + + + @Test + void shouldReturnEmptyListWhenApiReturnsNoBanks() { + + stubFor(get(urlPathEqualTo("/api/v1/banks")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{" + + "\"requestSuccessful\": true," + + "\"responseMessage\": \"success\"," + + "\"responseCode\": \"0\"," + + "\"responseBody\": []" + + "}"))); + + MonnifyBaseResponse> response = bankService.getBanks(); + assertNotNull(response); + assertEquals("0", response.getResponseCode()); + assertNotNull(response.getResponseBody()); + assertTrue(response.getResponseBody().isEmpty()); + verify(1, getRequestedFor(urlPathEqualTo("/api/v1/banks"))); + } + + + @Test + void shouldReturnBanksWithUssdShortCode() { + + stubFor( + get(urlPathEqualTo("/api/v1/sdk/transactions/banks")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\": true," + + "\"responseMessage\": \"success\"," + + "\"responseCode\": \"0\"," + + "\"responseBody\": [" + + "{" + + "\"code\": \"044\"," + + "\"name\": \"Access Bank\"," + + "\"ussdTemplate\": \"*901*\"," + + "\"baseUssdCode\": \"*901#\"," + + "\"transferUssdTemplate\": \"*901*AMOUNT*ACCOUNT#\"" + + "}" + + "]" + + "}" + ) + ) + ); + + MonnifyBaseResponse> response = bankService.getBanksWithUssdShortCode(); + assertNotNull(response); + assertEquals("0", response.getResponseCode()); + assertNotNull(response.getResponseBody()); + assertEquals(1, response.getResponseBody().size()); + BankResponse bank = response.getResponseBody().get(0); + assertEquals("044", bank.code); + assertEquals("Access Bank", bank.getName()); + assertEquals("*901*", bank.getUssdTemplate()); + assertEquals("*901#", bank.getBaseUssdCode()); + assertEquals("*901*AMOUNT*ACCOUNT#", bank.getTransferUssdTemplate()); + + verify(1, getRequestedFor(urlPathEqualTo("/api/v1/sdk/transactions/banks"))); + } + + + @Test + void shouldReturnEmptyListForBanksWithUssdShortCode() { + + stubFor(get(urlPathEqualTo("/api/v1/sdk/transactions/banks")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\": true," + + "\"responseMessage\": \"success\"," + + "\"responseCode\": \"0\"," + + "\"responseBody\": []" + + "}"))); + + MonnifyBaseResponse> response = bankService.getBanksWithUssdShortCode(); + assertNotNull(response); + assertNotNull(response.getResponseBody()); + assertTrue(response.getResponseBody().isEmpty()); + verify(1, getRequestedFor(urlPathEqualTo("/api/v1/sdk/transactions/banks"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/monnify/mocktests/DirectDebitServiceMockTest.java b/src/test/java/com/monnify/mocktests/DirectDebitServiceMockTest.java new file mode 100644 index 0000000..778d1d1 --- /dev/null +++ b/src/test/java/com/monnify/mocktests/DirectDebitServiceMockTest.java @@ -0,0 +1,488 @@ +package com.monnify.mocktests; + +import com.monnify.BaseMonnifyMockTest; +import com.monnify.exceptions.MonnifyValidationException; +import com.monnify.models.MonnifyBaseResponse; +import com.monnify.models.directdebit.*; +import com.monnify.services.directdebit.DirectDebitService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.List; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.*; + +public class DirectDebitServiceMockTest extends BaseMonnifyMockTest { + + private DirectDebitService directDebitService; + + @BeforeEach + void init() { + directDebitService = new DirectDebitService(); + } + private MandateRequest createValidMandateRequest() { + return MandateRequest.builder() + .contractCode("CONTRACT123") + .mandateReference("MANDATE-REF-123") + .autoRenew(true) + .customerCancellation(true) + .customerName("Halima Ismail") + .customerPhoneNumber("08012345678") + .customerEmailAddress("halima@example.com") + .customerAddress("Lagos, Nigeria") + .customerAccountName("Halima Ismail") + .customerAccountNumber("1234567890") + .customerAccountBankCode("044") + .mandateDescription("Monthly subscription payment") + .mandateStartDate("2026-08-13T00:00:00") + .mandateEndDate("2027-08-13T00:00:00") + .mandateAmount(new BigDecimal("10000.00")) + .debitAmount(new BigDecimal("1000.00")) + .build(); + } + + @Test + void shouldCreateMandateSuccessfully() { + + stubFor(post(urlPathEqualTo("/api/v1/direct-debit/mandate/create")) + .withHeader("Authorization", equalTo("Bearer mock-access-token")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"mandateCode\":\"MANDATE123\"," + + "\"status\":\"PENDING_AUTHORIZATION\"" + + "}" + + "}" + ))); + + MandateRequest request = createValidMandateRequest(); + MonnifyBaseResponse result = directDebitService.createMandate(request); + + assertNotNull(result); + assertEquals("0", result.getResponseCode()); + assertTrue(result.isRequestSuccessful()); + + verify(1, postRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/create" + ))); + } + + + @Test + void shouldUsePostForCreateMandate() { + + stubFor(post(urlPathEqualTo("/api/v1/direct-debit/mandate/create")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader( + "Content-Type", + "application/json" + ) + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{}" + + "}" + ) + ) + ); + + + MandateRequest request = createValidMandateRequest(); + + + directDebitService.createMandate(request); + + verify(1, postRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/create"))); + verify(0, getRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/create"))); + } + + @Test + void shouldGetMandateSuccessfully() { + + stubFor( + get(urlPathEqualTo( + "/api/v1/direct-debit/mandate/" + )) + .withQueryParam( + "mandateReferences", + equalTo("MANDATE123") + ) + .withHeader( + "Authorization", + equalTo("Bearer mock-access-token") + ) + .willReturn( + aResponse() + .withStatus(200) + .withHeader( + "Content-Type", + "application/json" + ) + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":[" + + "{" + + "\"mandateCode\":\"MANDATE123\"," + + "\"status\":\"ACTIVE\"" + + "}" + + "]" + + "}" + ) + ) + ); + + MonnifyBaseResponse> result = directDebitService.getMandate("MANDATE123"); + + assertNotNull(result); + assertEquals("0", result.getResponseCode()); + assertTrue(result.isRequestSuccessful()); + + assertNotNull(result.getResponseBody()); + assertEquals(1, result.getResponseBody().size()); + + verify(1, getRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/")) + .withQueryParam("mandateReferences", equalTo("MANDATE123"))); + } + + @Test + void shouldRejectEmptyMandateReference() { + assertThrows(MonnifyValidationException.class, () -> directDebitService.getMandate("")); + verify(0, getRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/"))); + } + + @Test + void shouldRejectNullMandateReference() { + assertThrows(MonnifyValidationException.class, () -> directDebitService.getMandate(null)); + } + @Test + void shouldDebitMandateSuccessfully() { + + /* + * Mock the debit mandate endpoint. + */ + stubFor( + post(urlPathEqualTo("/api/v1/direct-debit/mandate/debit")) + .withHeader("Authorization", equalTo("Bearer mock-access-token")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"transactionStatus\":\"SUCCESS\"," + + "\"responseMessage\":\"Debit successful\"," + + "\"transactionReference\":\"TXN123456\"," + + "\"paymentReference\":\"PAY123\"," + + "\"debitAmount\":1000.00," + + "\"narration\":\"Payment for subscription\"," + + "\"mandateCode\":\"MANDATE123\"" + + "}" + + "}" + ) + ) + ); + MandateDebitRequest request = new MandateDebitRequest(); + request.setPaymentReference("PAY123"); + request.setMandateCode("MANDATE123"); + request.setDebitAmount(new BigDecimal("1000.00")); + request.setNarration("Payment for subscription"); + request.setCustomerEmail("customer@example.com"); + MonnifyBaseResponse result = directDebitService.debitMandate(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertEquals("success", result.getResponseMessage()); + + assertNotNull(result.getResponseBody()); + + MandateDebitResponse response = result.getResponseBody(); + + assertEquals("SUCCESS", response.getTransactionStatus()); + assertEquals("Debit successful", response.getResponseMessage()); + assertEquals("TXN123456", response.getTransactionReference()); + assertEquals("PAY123", response.getPaymentReference()); + assertEquals(new BigDecimal("1000.00"), response.getDebitAmount()); + assertEquals("Payment for subscription", response.getNarration()); + assertEquals("MANDATE123", response.getMandateCode()); + verify(1, postRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/debit"))); + verify(postRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/debit")).withHeader("Authorization", equalTo("Bearer mock-access-token"))); + verify(0, getRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/debit"))); + } + + @Test + void shouldGetDebitStatusSuccessfully() { + + stubFor( + get(urlPathEqualTo("/api/v1/direct-debit/mandate/debit-status")) + .withQueryParam("paymentReference", equalTo("PAY123")) + .withHeader("Authorization", equalTo("Bearer mock-access-token")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"transactionStatus\":\"SUCCESS\"," + + "\"responseMessage\":\"Debit successful\"," + + "\"transactionReference\":\"TXN123456\"," + + "\"paymentReference\":\"PAY123\"," + + "\"debitAmount\":1000.00," + + "\"narration\":\"Payment for subscription\"," + + "\"mandateCode\":\"MANDATE123\"" + + "}" + + "}" + ) + ) + ); + + MonnifyBaseResponse result = directDebitService.getDebitStatus("PAY123"); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertEquals("success", result.getResponseMessage()); + assertNotNull(result.getResponseBody()); + + MandateDebitResponse response = result.getResponseBody(); + + assertEquals("SUCCESS", response.getTransactionStatus()); + assertEquals("Debit successful", response.getResponseMessage()); + assertEquals("TXN123456", response.getTransactionReference()); + assertEquals("PAY123", response.getPaymentReference()); + assertEquals(new BigDecimal("1000.00"), response.getDebitAmount()); + assertEquals("Payment for subscription", response.getNarration()); + assertEquals("MANDATE123", response.getMandateCode()); + + verify( + 1, + getRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/debit-status")) + .withQueryParam("paymentReference", equalTo("PAY123")) + ); + + verify( + getRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/debit-status")) + .withQueryParam("paymentReference", equalTo("PAY123")) + .withHeader("Authorization", equalTo("Bearer mock-access-token")) + ); + } + @Test + void shouldEncodePaymentReference() { + + String paymentReference = "PAY 123"; + + stubFor( + get(urlPathEqualTo("/api/v1/direct-debit/mandate/debit-status")) + .withQueryParam("paymentReference", equalTo("PAY 123")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"paymentReference\":\"PAY 123\"" + + "}" + + "}" + ) + ) + ); + + MonnifyBaseResponse result = + directDebitService.getDebitStatus(paymentReference); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + + verify( + 1, + getRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/debit-status")) + .withQueryParam("paymentReference", equalTo("PAY 123")) + ); + } + + @Test + void shouldRejectEmptyPaymentReference() { + + assertThrows( + MonnifyValidationException.class, + () -> directDebitService.getDebitStatus("") + ); + + verify( + 0, + getRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/debit-status")) + ); + } + + @Test + void shouldRejectNullPaymentReference() { + + assertThrows( + MonnifyValidationException.class, + () -> directDebitService.getDebitStatus(null) + ); + + verify( + 0, + getRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/debit-status")) + ); + } + +// ============================================================ +// CANCEL MANDATE +// ============================================================ + + @Test + void shouldCancelMandateSuccessfully() { + + stubFor( + patch(urlPathEqualTo("/api/v1/direct-debit/mandate/cancel-mandate/MANDATE123")) + .withHeader("Authorization", equalTo("Bearer mock-access-token")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"mandateCode\":\"MANDATE123\"" + + "}" + + "}" + ) + ) + ); + + MonnifyBaseResponse result = + directDebitService.cancelMandate("MANDATE123"); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertEquals("success", result.getResponseMessage()); + + verify( + 1, + patchRequestedFor( + urlPathEqualTo("/api/v1/direct-debit/mandate/cancel-mandate/MANDATE123") + ) + ); + + verify( + patchRequestedFor( + urlPathEqualTo("/api/v1/direct-debit/mandate/cancel-mandate/MANDATE123") + ) + .withHeader("Authorization", equalTo("Bearer mock-access-token")) + ); + } + + @Test + void shouldRejectEmptyMandateCode() { + + assertThrows(MonnifyValidationException.class, () -> directDebitService.cancelMandate("")); + + verify(0, patchRequestedFor( + urlPathMatching("/api/v1/direct-debit/mandate/cancel-mandate/.*") + ) + ); + } + + @Test + void shouldRejectNullMandateCode() { + + assertThrows( + MonnifyValidationException.class, + () -> directDebitService.cancelMandate(null) + ); + + verify( + 0, + patchRequestedFor( + urlPathMatching("/api/v1/direct-debit/mandate/cancel-mandate/.*") + ) + ); + } + + @Test + void shouldReturnErrorResponseWhenCreateMandateFails() { + + stubFor( + post(urlPathEqualTo("/api/v1/direct-debit/mandate/create")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":false," + + "\"responseMessage\":\"Invalid mandate\"," + + "\"responseCode\":\"99\"," + + "\"responseBody\":null" + + "}" + ) + ) + ); + + MandateRequest request = new MandateRequest(); + + request.setContractCode("CONTRACT123"); + request.setMandateReference("MANDATE-REF-123"); + request.setCustomerName("Test Customer"); + request.setCustomerPhoneNumber("08012345678"); + request.setCustomerEmailAddress("customer@example.com"); + request.setCustomerAddress("123 Test Street"); + request.setCustomerAccountName("Test Customer"); + request.setCustomerAccountNumber("0123456789"); + request.setCustomerAccountBankCode("058"); + request.setMandateDescription("Subscription payment"); + request.setMandateStartDate("2026-08-13T00:00:00"); + request.setMandateEndDate("2027-08-13T00:00:00"); + request.setMandateAmount(new BigDecimal("1000.00")); + request.setDebitAmount(new BigDecimal("1000.00")); + request.setAutoRenew(false); + request.setCustomerCancellation(false); + + MonnifyBaseResponse result = + directDebitService.createMandate(request); + + assertNotNull(result); + assertFalse(result.isRequestSuccessful()); + assertEquals("99", result.getResponseCode()); + assertEquals("Invalid mandate", result.getResponseMessage()); + + verify( + 1, + postRequestedFor(urlPathEqualTo("/api/v1/direct-debit/mandate/create")) + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/monnify/mocktests/DisbursementMockTest.java b/src/test/java/com/monnify/mocktests/DisbursementMockTest.java new file mode 100644 index 0000000..ad75a5f --- /dev/null +++ b/src/test/java/com/monnify/mocktests/DisbursementMockTest.java @@ -0,0 +1,727 @@ +package com.monnify.mocktests; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.monnify.BaseMonnifyMockTest; +import com.monnify.exceptions.MonnifyValidationException; +import com.monnify.models.MonnifyBaseResponse; +import com.monnify.models.SearchResponse; +import com.monnify.models.disbursement.BatchDisbursementRequest; +import com.monnify.models.disbursement.SingleDisbursementRequest; +import com.monnify.models.disbursement.SearchTransactionsRequest; +import com.monnify.models.disbursement.OnValidationFailure; +import com.monnify.models.disbursement.SingleDisbursementResponse; +import com.monnify.models.disbursement.DisbursementTransaction; +import com.monnify.models.disbursement.BatchDisbursementResponse; +import com.monnify.models.disbursement.TransferDetails; +import com.monnify.models.disbursement.DisbursementSummaryResponse; + +import com.monnify.models.wallet.WalletBalanceResponse; +import com.monnify.services.disbursement.DisbursementService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +public class DisbursementMockTest extends BaseMonnifyMockTest { + private DisbursementService disbursementService; + + @BeforeEach + void setUpService() { + disbursementService = new DisbursementService(); + } + + @Test + void shouldDisburseSingleSuccessfully() { + + stubFor( + post(urlPathEqualTo("/api/v2/disbursements/single")) + .withHeader("Authorization", equalTo("Bearer mock-access-token")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"amount\":1000.00," + + "\"reference\":\"DISB123\"," + + "\"status\":\"SUCCESS\"," + + "\"totalFee\":10.00," + + "\"destinationAccountName\":\"Test Customer\"," + + "\"destinationBankName\":\"Test Bank\"," + + "\"destinationAccountNumber\":\"0123456789\"," + + "\"destinationBankCode\":\"058\"" + + "}" + + "}" + ) + ) + ); + + SingleDisbursementRequest request = new SingleDisbursementRequest(); + request.setAmount(new BigDecimal("1000.00")); + request.setReference("DISB123"); + request.setNarration("Test disbursement"); + request.setDestinationBankCode("058"); + request.setDestinationAccountNumber("0123456789"); + request.setCurrency("NGN"); + request.setSourceAccountNumber("1234567890"); + request.setDestinationAccountName("Test Customer"); + request.setAsync(false); + + MonnifyBaseResponse result = + disbursementService.disburseSingle(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertEquals("success", result.getResponseMessage()); + assertNotNull(result.getResponseBody()); + assertEquals(new BigDecimal("1000.00"), result.getResponseBody().getAmount()); + assertEquals("DISB123", result.getResponseBody().getReference()); + assertEquals("SUCCESS", result.getResponseBody().getStatus()); + assertEquals(new BigDecimal("10.00"), result.getResponseBody().getTotalFee()); + assertEquals("Test Customer", result.getResponseBody().getDestinationAccountName()); + assertEquals("Test Bank", result.getResponseBody().getDestinationBankName()); + assertEquals("0123456789", result.getResponseBody().getDestinationAccountNumber()); + assertEquals("058", result.getResponseBody().getDestinationBankCode()); + + verify( + 1, + postRequestedFor(urlPathEqualTo("/api/v2/disbursements/single")) + ); + + verify( + postRequestedFor(urlPathEqualTo("/api/v2/disbursements/single")) + .withHeader("Authorization", equalTo("Bearer mock-access-token")) + ); + + verify( + 0, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/single")) + ); + } + + @Test + void shouldRejectInvalidSingleDisbursementRequest() { + + SingleDisbursementRequest request = new SingleDisbursementRequest(); + + assertThrows( + Exception.class, + () -> disbursementService.disburseSingle(request) + ); + + verify( + 0, + postRequestedFor(urlPathEqualTo("/api/v2/disbursements/single")) + ); + } + + @Test + void shouldReturnErrorResponseWhenSingleDisbursementFails() { + + stubFor( + post(urlPathEqualTo("/api/v2/disbursements/single")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":false," + + "\"responseMessage\":\"Insufficient balance\"," + + "\"responseCode\":\"99\"," + + "\"responseBody\":null" + + "}" + ) + ) + ); + + SingleDisbursementRequest request = new SingleDisbursementRequest(); + request.setAmount(new BigDecimal("1000.00")); + request.setReference("DISB123"); + request.setNarration("Test disbursement"); + request.setDestinationBankCode("058"); + request.setDestinationAccountNumber("0123456789"); + request.setCurrency("NGN"); + request.setSourceAccountNumber("1234567890"); + request.setDestinationAccountName("Test Customer"); + request.setAsync(false); + + MonnifyBaseResponse result = + disbursementService.disburseSingle(request); + + assertNotNull(result); + assertFalse(result.isRequestSuccessful()); + assertEquals("99", result.getResponseCode()); + assertEquals("Insufficient balance", result.getResponseMessage()); + + verify( + 1, + postRequestedFor(urlPathEqualTo("/api/v2/disbursements/single")) + ); + } + + @Test + void shouldDisburseBatchSuccessfully() { + + stubFor( + post(urlPathEqualTo("/api/v2/disbursements/batch")) + .withHeader("Authorization", equalTo("Bearer mock-access-token")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"totalAmount\":2000.00," + + "\"totalFee\":20.00," + + "\"batchReference\":\"BATCH123\"," + + "\"batchStatus\":\"SUCCESS\"," + + "\"totalTransactionsCount\":2" + + "}" + + "}" + ) + ) + ); + + DisbursementTransaction transaction1 = new DisbursementTransaction(); + transaction1.setAmount(new BigDecimal("1000.00")); + transaction1.setReference("TXN001"); + transaction1.setNarration("Payment one"); + transaction1.setDestinationBankCode("058"); + transaction1.setDestinationAccountNumber("0123456789"); + transaction1.setCurrency("NGN"); + + DisbursementTransaction transaction2 = new DisbursementTransaction(); + transaction2.setAmount(new BigDecimal("1000.00")); + transaction2.setReference("TXN002"); + transaction2.setNarration("Payment two"); + transaction2.setDestinationBankCode("058"); + transaction2.setDestinationAccountNumber("9876543210"); + transaction2.setCurrency("NGN"); + + BatchDisbursementRequest request = new BatchDisbursementRequest(); + request.setTitle("Test Batch"); + request.setBatchReference("BATCH123"); + request.setNarration("Batch disbursement"); + request.setSourceAccountNumber("1234567890"); + request.setOnValidationFailure(OnValidationFailure.values()[0]); + request.setTransactionList(java.util.Arrays.asList(transaction1, transaction2)); + + MonnifyBaseResponse result = + disbursementService.disburseBatch(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertEquals("success", result.getResponseMessage()); + assertNotNull(result.getResponseBody()); + assertEquals(new BigDecimal("2000.00"), result.getResponseBody().getTotalAmount()); + assertEquals(new BigDecimal("20.00"), result.getResponseBody().getTotalFee()); + assertEquals("BATCH123", result.getResponseBody().getBatchReference()); + assertEquals("SUCCESS", result.getResponseBody().getBatchStatus()); + assertEquals(2, result.getResponseBody().getTotalTransactionsCount()); + + verify( + 1, + postRequestedFor(urlPathEqualTo("/api/v2/disbursements/batch")) + ); + + verify( + postRequestedFor(urlPathEqualTo("/api/v2/disbursements/batch")) + .withHeader("Authorization", equalTo("Bearer mock-access-token")) + ); + } + + @Test + void shouldRejectInvalidBatchDisbursementRequest() { + + BatchDisbursementRequest request = new BatchDisbursementRequest(); + + assertThrows( + Exception.class, + () -> disbursementService.disburseBatch(request) + ); + + verify( + 0, + postRequestedFor(urlPathEqualTo("/api/v2/disbursements/batch")) + ); + } + + @Test + void shouldGetDisbursementSummarySuccessfully() { + + stubFor( + get(urlPathEqualTo("/api/v2/disbursements/single/summary")) + .withQueryParam("reference", equalTo("DISB123")) + .withHeader("Authorization", equalTo("Bearer mock-access-token")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{}" + + "}" + ) + ) + ); + + MonnifyBaseResponse result = + disbursementService.getDisbursementSummary("DISB123"); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertEquals("success", result.getResponseMessage()); + + verify( + 1, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/single/summary")) + .withQueryParam("reference", equalTo("DISB123")) + ); + } + + @Test + void shouldRejectEmptyDisbursementReference() { + + assertThrows( + MonnifyValidationException.class, + () -> disbursementService.getDisbursementSummary("") + ); + + verify( + 0, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/single/summary")) + ); + } + + @Test + void shouldRejectNullDisbursementReference() { + + assertThrows( + MonnifyValidationException.class, + () -> disbursementService.getDisbursementSummary(null) + ); + + verify( + 0, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/single/summary")) + ); + } + + @Test + void shouldGetAllSingleDisbursementsSuccessfully() { + + stubFor( + get(urlPathEqualTo("/api/v2/disbursements/single/transactions")) + .withQueryParam("pageNo", equalTo("0")) + .withQueryParam("pageSize", equalTo("10")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{}" + + "}" + ) + ) + ); + + MonnifyBaseResponse> result = + disbursementService.getAllSingleDisbursements(10, 0); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + + verify( + 1, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/single/transactions")) + .withQueryParam("pageNo", equalTo("0")) + .withQueryParam("pageSize", equalTo("10")) + ); + } + + @Test + void shouldGetAllBulkDisbursementsSuccessfully() { + + stubFor( + get(urlPathEqualTo("/api/v2/disbursements/bulk/transactions")) + .withQueryParam("pageNo", equalTo("0")) + .withQueryParam("pageSize", equalTo("10")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{}" + + "}" + ) + ) + ); + + MonnifyBaseResponse> result = + disbursementService.getAllBulkDisbursements(10, 0); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + + verify( + 1, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/bulk/transactions")) + .withQueryParam("pageNo", equalTo("0")) + .withQueryParam("pageSize", equalTo("10")) + ); + } + + @Test + void shouldGetBulkTransferTransactionsSuccessfully() { + + stubFor( + get(urlPathEqualTo("/api/v2/disbursements/bulk/BATCH123/transactions")) + .withQueryParam("pageNo", equalTo("0")) + .withQueryParam("pageSize", equalTo("10")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{}" + + "}" + ) + ) + ); + + MonnifyBaseResponse> result = + disbursementService.getBulkTransferTransactions("BATCH123", 10, 0); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + + verify( + 1, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/bulk/BATCH123/transactions")) + .withQueryParam("pageNo", equalTo("0")) + .withQueryParam("pageSize", equalTo("10")) + ); + } + + @Test + void shouldRejectEmptyBatchReferenceForBulkTransactions() { + + assertThrows( + MonnifyValidationException.class, + () -> disbursementService.getBulkTransferTransactions("", 10, 0) + ); + + verify( + 0, + getRequestedFor(urlPathMatching("/api/v2/disbursements/bulk/.*/transactions")) + ); + } + + @Test + void shouldRejectNullBatchReferenceForBulkTransactions() { + + assertThrows( + MonnifyValidationException.class, + () -> disbursementService.getBulkTransferTransactions(null, 10, 0) + ); + + verify( + 0, + getRequestedFor(urlPathMatching("/api/v2/disbursements/bulk/.*/transactions")) + ); + } + + @Test + void shouldEncodeBatchReference() { + + String batchReference = "BATCH 123"; + + stubFor( + get(urlPathMatching("/api/v2/disbursements/bulk/BATCH\\+123/transactions")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{}" + + "}" + ) + ) + ); + + MonnifyBaseResponse> result = + disbursementService.getBulkTransferTransactions(batchReference, null, null); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + + verify( + 1, + getRequestedFor(urlPathMatching("/api/v2/disbursements/bulk/.*/transactions")) + ); + } + + @Test + void shouldGetBulkTransferStatusSuccessfully() { + + stubFor( + get(urlPathEqualTo("/api/v2/disbursements/batch/summary")) + .withQueryParam("reference", equalTo("BATCH123")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{}" + + "}" + ) + ) + ); + + MonnifyBaseResponse result = + disbursementService.getBulkTransferStatus("BATCH123"); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + + verify( + 1, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/batch/summary")) + .withQueryParam("reference", equalTo("BATCH123")) + ); + } + + @Test + void shouldRejectEmptyBatchReferenceForBulkStatus() { + + assertThrows( + MonnifyValidationException.class, + () -> disbursementService.getBulkTransferStatus("") + ); + + verify( + 0, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/batch/summary")) + ); + } + + @Test + void shouldRejectNullBatchReferenceForBulkStatus() { + + assertThrows( + MonnifyValidationException.class, + () -> disbursementService.getBulkTransferStatus(null) + ); + + verify( + 0, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/batch/summary")) + ); + } + + @Test + void shouldSearchTransactionsSuccessfully() { + + stubFor( + get(urlPathEqualTo("/api/v2/disbursements/search-transactions")) + .withQueryParam("sourceAccountNumber", equalTo("1234567890")) + .withQueryParam("pageSize", equalTo("10")) + .withQueryParam("pageNo", equalTo("0")) + .withQueryParam("startDate", equalTo("2026-08-01")) + .withQueryParam("endDate", equalTo("2026-08-13")) + .withQueryParam("amountFrom", equalTo("100")) + .withQueryParam("amountTo", equalTo("10000")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{}" + + "}" + ) + ) + ); + + SearchTransactionsRequest request = new SearchTransactionsRequest(); + request.setSourceAccountNumber("1234567890"); + request.setPageSize(10); + request.setPageNo(0); + request.setStartDate("2026-08-01"); + request.setEndDate("2026-08-13"); + request.setAmountFrom("100"); + request.setAmountTo("10000"); + + MonnifyBaseResponse> result = disbursementService.searchTransactions(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + + verify( + 1, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/search-transactions")) + .withQueryParam("sourceAccountNumber", equalTo("1234567890")) + .withQueryParam("pageSize", equalTo("10")) + .withQueryParam("pageNo", equalTo("0")) + .withQueryParam("startDate", equalTo("2026-08-01")) + .withQueryParam("endDate", equalTo("2026-08-13")) + .withQueryParam("amountFrom", equalTo("100")) + .withQueryParam("amountTo", equalTo("10000")) + ); + } + + @Test + void shouldSearchTransactionsWithoutOptionalParameters() { + + stubFor( + get(urlPathEqualTo("/api/v2/disbursements/search-transactions")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{}" + + "}" + ) + ) + ); + + SearchTransactionsRequest request = new SearchTransactionsRequest(); + + MonnifyBaseResponse> result = + disbursementService.searchTransactions(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + + verify( + 1, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/search-transactions")) + ); + } + + @Test + void shouldGetWalletBalanceSuccessfully() { + + stubFor( + get(urlPathEqualTo("/api/v2/disbursements/wallet-balance")) + .withQueryParam("accountNumber", equalTo("1234567890")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{}" + + "}" + ) + ) + ); + + MonnifyBaseResponse result = + disbursementService.getWalletBalance("1234567890"); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + + verify( + 1, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/wallet-balance")) + .withQueryParam("accountNumber", equalTo("1234567890")) + ); + } + + @Test + void shouldRejectEmptyWalletAccountNumber() { + + assertThrows( + MonnifyValidationException.class, + () -> disbursementService.getWalletBalance("") + ); + + verify( + 0, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/wallet-balance")) + ); + } + + @Test + void shouldRejectNullWalletAccountNumber() { + + assertThrows( + MonnifyValidationException.class, + () -> disbursementService.getWalletBalance(null) + ); + + verify( + 0, + getRequestedFor(urlPathEqualTo("/api/v2/disbursements/wallet-balance")) + ); + } +} diff --git a/src/test/java/com/monnify/mocktests/InvoiceServiceMockTest.java b/src/test/java/com/monnify/mocktests/InvoiceServiceMockTest.java new file mode 100644 index 0000000..494d78a --- /dev/null +++ b/src/test/java/com/monnify/mocktests/InvoiceServiceMockTest.java @@ -0,0 +1,537 @@ +package com.monnify.mocktests; + +import com.monnify.BaseMonnifyMockTest; +import com.monnify.exceptions.MonnifyValidationException; +import com.monnify.models.MonnifyBaseResponse; +import com.monnify.models.SearchResponse; +import com.monnify.models.invoice.InvoiceRequest; +import com.monnify.models.invoice.InvoiceResponse; +import com.monnify.services.invoice.InvoiceService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.delete; +import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class InvoiceServiceMockTest extends BaseMonnifyMockTest { + + private InvoiceService invoiceService; + + @BeforeEach + void setUp() { + invoiceService = new InvoiceService(); + } + + @Test + void shouldCreateInvoiceSuccessfully() { + + stubFor( + post(urlPathEqualTo("/api/v1/invoice/create")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"amount\":5000," + + "\"invoiceReference\":\"INV123\"," + + "\"invoiceStatus\":\"PENDING\"," + + "\"description\":\"Test invoice\"," + + "\"contractCode\":\"CONTRACT123\"," + + "\"customerEmail\":\"customer@example.com\"," + + "\"customerName\":\"Test Customer\"," + + "\"expiryDate\":\"2026-08-20T23:59:59\"," + + "\"createdBy\":\"test-user\"," + + "\"createdOn\":\"2026-08-13T10:00:00\"," + + "\"checkoutUrl\":\"https://checkout.example.com/INV123\"," + + "\"accountNumber\":\"1234567890\"," + + "\"accountName\":\"Test Customer\"," + + "\"bankName\":\"Test Bank\"," + + "\"bankCode\":\"058\"," + + "\"redirectUrl\":\"https://example.com/callback\"," + + "\"transactionReference\":\"TXN123\"" + + "}" + + "}" + ) + ) + ); + + InvoiceRequest request = createValidInvoiceRequest(); + + MonnifyBaseResponse result = + invoiceService.createInvoice(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertEquals("success", result.getResponseMessage()); + + assertNotNull(result.getResponseBody()); + + assertEquals( + new BigDecimal("5000"), + result.getResponseBody().getAmount() + ); + + assertEquals( + "INV123", + result.getResponseBody().getInvoiceReference() + ); + + assertEquals( + "PENDING", + result.getResponseBody().getInvoiceStatus() + ); + + assertEquals( + "customer@example.com", + result.getResponseBody().getCustomerEmail() + ); + + assertEquals( + "Test Customer", + result.getResponseBody().getCustomerName() + ); + + assertEquals( + "1234567890", + result.getResponseBody().getAccountNumber() + ); + + assertEquals( + "058", + result.getResponseBody().getBankCode() + ); + + assertEquals( + "TXN123", + result.getResponseBody().getTransactionReference() + ); + + verify( + 1, + postRequestedFor( + urlPathEqualTo("/api/v1/invoice/create") + ) + ); + } + + @Test + void shouldRejectInvalidCreateInvoiceRequest() { + + InvoiceRequest request = new InvoiceRequest(); + + assertThrows( + Exception.class, + () -> invoiceService.createInvoice(request) + ); + + verify( + 0, + postRequestedFor( + urlPathEqualTo("/api/v1/invoice/create") + ) + ); + } + + @Test + void shouldAttachInvoiceToReservedAccountSuccessfully() { + + stubFor( + post(urlPathEqualTo("/api/v1/invoice/create")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"amount\":5000," + + "\"invoiceReference\":\"INV123\"," + + "\"invoiceStatus\":\"PENDING\"," + + "\"accountReference\":\"ACC123\"," + + "\"accountNumber\":\"1234567890\"," + + "\"accountName\":\"Test Customer\"," + + "\"bankName\":\"Test Bank\"," + + "\"bankCode\":\"058\"" + + "}" + + "}" + ) + ) + ); + + InvoiceRequest request = createValidInvoiceRequest(); + request.setAccountReference("ACC123"); + + MonnifyBaseResponse result = + invoiceService.attachInvoiceToReservedAccount(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + + assertNotNull(result.getResponseBody()); + + assertEquals( + "INV123", + result.getResponseBody().getInvoiceReference() + ); + + assertEquals( + "1234567890", + result.getResponseBody().getAccountNumber() + ); + + verify( + 1, + postRequestedFor( + urlPathEqualTo("/api/v1/invoice/create") + ) + ); + } + + @Test + void shouldRejectInvoiceWithoutAccountReference() { + + InvoiceRequest request = createValidInvoiceRequest(); + request.setAccountReference(null); + + assertThrows( + MonnifyValidationException.class, + () -> invoiceService.attachInvoiceToReservedAccount(request) + ); + + verify( + 0, + postRequestedFor( + urlPathEqualTo("/api/v1/invoice/create") + ) + ); + } + + @Test + void shouldRejectEmptyAccountReference() { + + InvoiceRequest request = createValidInvoiceRequest(); + request.setAccountReference(""); + + assertThrows( + MonnifyValidationException.class, + () -> invoiceService.attachInvoiceToReservedAccount(request) + ); + + verify( + 0, + postRequestedFor( + urlPathEqualTo("/api/v1/invoice/create") + ) + ); + } + + @Test + void shouldViewInvoiceDetailsSuccessfully() { + + stubFor( + get(urlPathEqualTo("/api/v1/invoice/INV123/details")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"amount\":5000," + + "\"invoiceReference\":\"INV123\"," + + "\"invoiceStatus\":\"PENDING\"," + + "\"description\":\"Test invoice\"," + + "\"contractCode\":\"CONTRACT123\"," + + "\"customerEmail\":\"customer@example.com\"," + + "\"customerName\":\"Test Customer\"," + + "\"transactionReference\":\"TXN123\"" + + "}" + + "}" + ) + ) + ); + + MonnifyBaseResponse result = + invoiceService.viewInvoiceDetails("INV123"); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + + assertNotNull(result.getResponseBody()); + + assertEquals( + "INV123", + result.getResponseBody().getInvoiceReference() + ); + + assertEquals( + "PENDING", + result.getResponseBody().getInvoiceStatus() + ); + + assertEquals( + "TXN123", + result.getResponseBody().getTransactionReference() + ); + + verify( + 1, + getRequestedFor( + urlPathEqualTo("/api/v1/invoice/INV123/details") + ) + ); + } + + + @Test + void shouldRejectEmptyInvoiceReferenceForViewDetails() { + + assertThrows( + MonnifyValidationException.class, + () -> invoiceService.viewInvoiceDetails("") + ); + + verify( + 0, + getRequestedFor( + urlPathMatching("/api/v1/invoice/.*/details") + ) + ); + } + + @Test + void shouldRejectNullInvoiceReferenceForViewDetails() { + + assertThrows( + MonnifyValidationException.class, + () -> invoiceService.viewInvoiceDetails(null) + ); + + verify( + 0, + getRequestedFor( + urlPathMatching("/api/v1/invoice/.*/details") + ) + ); + } + + @Test + void shouldGetAllInvoicesSuccessfully() { + + stubFor( + get(urlPathEqualTo("/api/v1/invoice/all")) + .withQueryParam("page", equalTo("0")) + .withQueryParam("size", equalTo("10")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"content\":[]," + + "\"pageNumber\":0," + + "\"pageSize\":10," + + "\"totalElements\":0," + + "\"totalPages\":0" + + "}" + + "}" + ) + ) + ); + + MonnifyBaseResponse> result = + invoiceService.getAllInvoices(0, 10); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + + verify( + 1, + getRequestedFor( + urlPathEqualTo("/api/v1/invoice/all") + ) + .withQueryParam("page", equalTo("0")) + .withQueryParam("size", equalTo("10")) + ); + } + + @Test + void shouldCancelInvoiceSuccessfully() { + + stubFor( + delete(urlPathEqualTo("/api/v1/invoice/INV123/cancel")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"amount\":5000," + + "\"invoiceReference\":\"INV123\"," + + "\"invoiceStatus\":\"CANCELLED\"," + + "\"customerName\":\"Test Customer\"" + + "}" + + "}" + ) + ) + ); + + MonnifyBaseResponse result = + invoiceService.cancelInvoice("INV123"); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + + assertNotNull(result.getResponseBody()); + + assertEquals( + "INV123", + result.getResponseBody().getInvoiceReference() + ); + + assertEquals( + "CANCELLED", + result.getResponseBody().getInvoiceStatus() + ); + + verify( + 1, + deleteRequestedFor( + urlPathEqualTo("/api/v1/invoice/INV123/cancel") + ) + ); + } + + @Test + void shouldRejectEmptyInvoiceReferenceWhenCancelling() { + + assertThrows( + MonnifyValidationException.class, + () -> invoiceService.cancelInvoice("") + ); + + verify( + 0, + deleteRequestedFor( + urlPathMatching("/api/v1/invoice/.*/cancel") + ) + ); + } + + @Test + void shouldRejectNullInvoiceReferenceWhenCancelling() { + + assertThrows( + MonnifyValidationException.class, + () -> invoiceService.cancelInvoice(null) + ); + + verify( + 0, + deleteRequestedFor( + urlPathMatching("/api/v1/invoice/.*/cancel") + ) + ); + } + + @Test + void shouldReturnErrorResponseWhenCreateInvoiceFails() { + + stubFor( + post(urlPathEqualTo("/api/v1/invoice/create")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":false," + + "\"responseMessage\":\"Invalid invoice\"," + + "\"responseCode\":\"99\"," + + "\"responseBody\":null" + + "}" + ) + ) + ); + + InvoiceRequest request = createValidInvoiceRequest(); + + MonnifyBaseResponse result = + invoiceService.createInvoice(request); + + assertNotNull(result); + assertFalse(result.isRequestSuccessful()); + assertEquals("99", result.getResponseCode()); + assertEquals( + "Invalid invoice", + result.getResponseMessage() + ); + + verify( + 1, + postRequestedFor( + urlPathEqualTo("/api/v1/invoice/create") + ) + ); + } + + private InvoiceRequest createValidInvoiceRequest() { + + InvoiceRequest request = new InvoiceRequest(); + + request.setAmount(new BigDecimal("5000.00")); + request.setInvoiceReference("INV123"); + request.setDescription("Test invoice"); + request.setCurrencyCode("NGN"); + request.setContractCode("CONTRACT123"); + request.setCustomerEmail("customer@example.com"); + request.setCustomerName("Test Customer"); + request.setExpiryDate("2026-08-20T23:59:59"); + request.setRedirectUrl("https://example.com/callback"); + + return request; + } +} \ No newline at end of file diff --git a/src/test/java/com/monnify/mocktests/LimitProfileServiceMockTest.java b/src/test/java/com/monnify/mocktests/LimitProfileServiceMockTest.java new file mode 100644 index 0000000..55db20a --- /dev/null +++ b/src/test/java/com/monnify/mocktests/LimitProfileServiceMockTest.java @@ -0,0 +1,471 @@ +package com.monnify.mocktests; + +import com.monnify.BaseMonnifyMockTest; +import com.monnify.exceptions.MonnifyValidationException; +import com.monnify.models.MonnifyBaseResponse; +import com.monnify.models.SearchResponse; +import com.monnify.models.limitprofile.LimitProfileRequest; +import com.monnify.models.limitprofile.LimitProfileResponse; +import com.monnify.services.limitprofile.LimitProfileService; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.*; + +class LimitProfileServiceMockTest extends BaseMonnifyMockTest { + + private final LimitProfileService limitProfileService = + new LimitProfileService(); + + @Test + void shouldCreateLimitProfile() { + + String requestBody = + "{" + + "\"limitProfileName\":\"Test Profile\"," + + "\"singleTransactionValue\":100000.00," + + "\"dailyTransactionVolume\":10," + + "\"dailyTransactionValue\":500000.00" + + "}"; + + stubFor( + post(urlPathEqualTo("/api/v1/limit-profile")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"limitProfileCode\":\"LP001\"," + + "\"limitProfileName\":\"Test Profile\"," + + "\"singleTransactionValue\":100000.00," + + "\"dailyTransactionVolume\":10," + + "\"dailyTransactionValue\":500000.00," + + "\"dateCreated\":\"2026-08-14T10:00:00\"," + + "\"lastModified\":\"2026-08-14T10:00:00\"" + + "}" + + "}" + ) + ) + ); + + LimitProfileRequest request = + LimitProfileRequest.builder() + .limitProfileName("Test Profile") + .singleTransactionValue(new BigDecimal("100000.00")) + .dailyTransactionVolume(10) + .dailyTransactionValue(new BigDecimal("500000.00")) + .build(); + + MonnifyBaseResponse result = + limitProfileService.createLimitProfile(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("success", result.getResponseMessage()); + assertEquals("0", result.getResponseCode()); + + assertNotNull(result.getResponseBody()); + + assertEquals( + "LP001", + result.getResponseBody().getLimitProfileCode() + ); + + assertEquals( + "Test Profile", + result.getResponseBody().getLimitProfileName() + ); + + assertEquals( + new BigDecimal("100000.00"), + result.getResponseBody().getSingleTransactionValue() + ); + + assertEquals( + 10, + result.getResponseBody().getDailyTransactionVolume() + ); + + assertEquals( + new BigDecimal("500000.00"), + result.getResponseBody().getDailyTransactionValue() + ); + + verify( + 1, + postRequestedFor( + urlPathEqualTo("/api/v1/limit-profile") + ).withRequestBody( + equalToJson(requestBody) + ) + ); + } + + @Test + void shouldGetLimitProfiles() { + + stubFor( + get(urlPathEqualTo("/api/v1/limit-profile")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"content\":[" + + "{" + + "\"limitProfileCode\":\"LP001\"," + + "\"limitProfileName\":\"Test Profile\"," + + "\"singleTransactionValue\":100000.00," + + "\"dailyTransactionVolume\":10," + + "\"dailyTransactionValue\":500000.00," + + "\"dateCreated\":\"2026-08-14T10:00:00\"," + + "\"lastModified\":\"2026-08-14T10:00:00\"" + + "}" + + "]," + + "\"pageNumber\":0," + + "\"pageSize\":10," + + "\"totalPages\":1," + + "\"totalElements\":1" + + "}" + + "}" + ) + ) + ); + + MonnifyBaseResponse> result = + limitProfileService.getLimitProfiles(); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("success", result.getResponseMessage()); + assertEquals("0", result.getResponseCode()); + + assertNotNull(result.getResponseBody()); + + assertNotNull(result.getResponseBody().getContent()); + assertEquals(1, result.getResponseBody().getContent().size()); + + LimitProfileResponse profile = + result.getResponseBody().getContent().get(0); + + assertEquals( + "LP001", + profile.getLimitProfileCode() + ); + + assertEquals( + "Test Profile", + profile.getLimitProfileName() + ); + + assertEquals( + new BigDecimal("100000.00"), + profile.getSingleTransactionValue() + ); + + assertEquals( + 10, + profile.getDailyTransactionVolume() + ); + + assertEquals( + new BigDecimal("500000.00"), + profile.getDailyTransactionValue() + ); + + verify( + 1, + getRequestedFor( + urlPathEqualTo("/api/v1/limit-profile") + ) + ); + } + + @Test + void shouldUpdateLimitProfile() { + + String limitProfileCode = "LP001"; + + String requestBody = + "{" + + "\"limitProfileName\":\"Updated Profile\"," + + "\"singleTransactionValue\":200000.00," + + "\"dailyTransactionVolume\":20," + + "\"dailyTransactionValue\":1000000.00" + + "}"; + + stubFor( + put(urlPathEqualTo("/api/v1/limit-profile/LP001")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"limitProfileCode\":\"LP001\"," + + "\"limitProfileName\":\"Updated Profile\"," + + "\"singleTransactionValue\":200000.00," + + "\"dailyTransactionVolume\":20," + + "\"dailyTransactionValue\":1000000.00," + + "\"dateCreated\":\"2026-08-14T10:00:00\"," + + "\"lastModified\":\"2026-08-14T11:00:00\"" + + "}" + + "}" + ) + ) + ); + + LimitProfileRequest request = + LimitProfileRequest.builder() + .limitProfileName("Updated Profile") + .singleTransactionValue(new BigDecimal("200000.00")) + .dailyTransactionVolume(20) + .dailyTransactionValue(new BigDecimal("1000000.00")) + .build(); + + MonnifyBaseResponse result = + limitProfileService.updateLimitProfile( + limitProfileCode, + request + ); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("success", result.getResponseMessage()); + assertEquals("0", result.getResponseCode()); + + assertNotNull(result.getResponseBody()); + + assertEquals( + "LP001", + result.getResponseBody().getLimitProfileCode() + ); + + assertEquals( + "Updated Profile", + result.getResponseBody().getLimitProfileName() + ); + + assertEquals( + new BigDecimal("200000.00"), + result.getResponseBody().getSingleTransactionValue() + ); + + assertEquals( + 20, + result.getResponseBody().getDailyTransactionVolume() + ); + + assertEquals( + new BigDecimal("1000000.00"), + result.getResponseBody().getDailyTransactionValue() + ); + + verify( + 1, + putRequestedFor( + urlPathEqualTo("/api/v1/limit-profile/LP001") + ).withRequestBody( + equalToJson(requestBody) + ) + ); + } + + @Test + void shouldEncodeLimitProfileCodeInUrlPath() { + + String limitProfileCode = "LP 001"; + + stubFor( + put(urlPathEqualTo("/api/v1/limit-profile/LP+001")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"limitProfileCode\":\"LP 001\"," + + "\"limitProfileName\":\"Test Profile\"," + + "\"singleTransactionValue\":100000.00," + + "\"dailyTransactionVolume\":10," + + "\"dailyTransactionValue\":500000.00," + + "\"dateCreated\":\"2026-08-14T10:00:00\"," + + "\"lastModified\":\"2026-08-14T10:00:00\"" + + "}" + + "}" + ) + ) + ); + + LimitProfileRequest request = + LimitProfileRequest.builder() + .limitProfileName("Test Profile") + .singleTransactionValue(new BigDecimal("100000.00")) + .dailyTransactionVolume(10) + .dailyTransactionValue(new BigDecimal("500000.00")) + .build(); + + MonnifyBaseResponse result = + limitProfileService.updateLimitProfile( + limitProfileCode, + request + ); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + + assertNotNull(result.getResponseBody()); + + assertEquals( + "LP 001", + result.getResponseBody().getLimitProfileCode() + ); + + verify( + 1, + putRequestedFor( + urlPathEqualTo("/api/v1/limit-profile/LP+001") + ) + ); + } + + @Test + void shouldThrowExceptionWhenLimitProfileCodeIsEmpty() { + + LimitProfileRequest request = + LimitProfileRequest.builder() + .limitProfileName("Test Profile") + .singleTransactionValue(new BigDecimal("100000.00")) + .dailyTransactionVolume(10) + .dailyTransactionValue(new BigDecimal("500000.00")) + .build(); + + MonnifyValidationException exception = + assertThrows( + MonnifyValidationException.class, + () -> limitProfileService.updateLimitProfile( + "", + request + ) + ); + + assertEquals( + "Limit profile code is empty", + exception.getMessage() + ); + } + + @Test + void shouldThrowExceptionWhenLimitProfileCodeIsNull() { + + LimitProfileRequest request = + LimitProfileRequest.builder() + .limitProfileName("Test Profile") + .singleTransactionValue(new BigDecimal("100000.00")) + .dailyTransactionVolume(10) + .dailyTransactionValue(new BigDecimal("500000.00")) + .build(); + + MonnifyValidationException exception = + assertThrows( + MonnifyValidationException.class, + () -> limitProfileService.updateLimitProfile( + null, + request + ) + ); + + assertEquals( + "Limit profile code is empty", + exception.getMessage() + ); + } + + @Test + void shouldValidateLimitProfileName() { + + LimitProfileRequest request = + LimitProfileRequest.builder() + .limitProfileName("") + .singleTransactionValue(new BigDecimal("100000.00")) + .dailyTransactionVolume(10) + .dailyTransactionValue(new BigDecimal("500000.00")) + .build(); + + assertThrows( + MonnifyValidationException.class, + () -> limitProfileService.createLimitProfile(request) + ); + } + + @Test + void shouldValidateSingleTransactionValue() { + + LimitProfileRequest request = + LimitProfileRequest.builder() + .limitProfileName("Test Profile") + .singleTransactionValue(null) + .dailyTransactionVolume(10) + .dailyTransactionValue(new BigDecimal("500000.00")) + .build(); + + assertThrows( + MonnifyValidationException.class, + () -> limitProfileService.createLimitProfile(request) + ); + } + + @Test + void shouldValidateDailyTransactionVolume() { + + LimitProfileRequest request = + LimitProfileRequest.builder() + .limitProfileName("Test Profile") + .singleTransactionValue(new BigDecimal("100000.00")) + .dailyTransactionVolume(null) + .dailyTransactionValue(new BigDecimal("500000.00")) + .build(); + + assertThrows( + MonnifyValidationException.class, + () -> limitProfileService.createLimitProfile(request) + ); + } + + @Test + void shouldValidateDailyTransactionValue() { + + LimitProfileRequest request = + LimitProfileRequest.builder() + .limitProfileName("Test Profile") + .singleTransactionValue(new BigDecimal("100000.00")) + .dailyTransactionVolume(10) + .dailyTransactionValue(null) + .build(); + + assertThrows( + MonnifyValidationException.class, + () -> limitProfileService.createLimitProfile(request) + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/monnify/mocktests/PaycodeServiceMockTest.java b/src/test/java/com/monnify/mocktests/PaycodeServiceMockTest.java new file mode 100644 index 0000000..3061027 --- /dev/null +++ b/src/test/java/com/monnify/mocktests/PaycodeServiceMockTest.java @@ -0,0 +1,320 @@ +package com.monnify.mocktests; + +import com.monnify.BaseMonnifyMockTest; +import com.monnify.exceptions.MonnifyValidationException; +import com.monnify.models.MonnifyBaseResponse; +import com.monnify.models.SearchResponse; +import com.monnify.models.paycode.PaycodeRequest; +import com.monnify.models.paycode.PaycodeResponse; +import com.monnify.services.paycode.PaycodeService; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.*; + +public class PaycodeServiceMockTest extends BaseMonnifyMockTest { + + private final PaycodeService paycodeService = new PaycodeService(); + + @Test + void shouldCreatePaycode() { + stubFor(post(urlEqualTo("/api/v1/paycode")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"paycode\":\"123456\"," + + "\"transactionReference\":\"MNFY|123456\"," + + "\"paycodeReference\":\"PAY-123\"," + + "\"beneficiaryName\":\"John Doe\"," + + "\"amount\":1000," + + "\"fee\":10," + + "\"transactionStatus\":\"PENDING\"," + + "\"expiryDate\":\"2026-08-20T12:00:00\"," + + "\"createdOn\":\"2026-08-14T10:00:00\"," + + "\"createdBy\":\"test\"," + + "\"modifiedBy\":\"test\"" + + "}" + + "}"))); + + PaycodeRequest request = PaycodeRequest.builder() + .beneficiaryName("John Doe") + .amount(BigDecimal.valueOf(1000)) + .paycodeReference("PAY-123") + .expiryDate("2026-08-20") + .clientId("client-123") + .build(); + + MonnifyBaseResponse response = + paycodeService.createPaycode(request); + + assertTrue(response.isRequestSuccessful()); + assertEquals("0", response.getResponseCode()); + assertNotNull(response.getResponseBody()); + assertEquals("123456", response.getResponseBody().getPaycode()); + assertEquals("PAY-123", response.getResponseBody().getPaycodeReference()); + assertEquals("John Doe", response.getResponseBody().getBeneficiaryName()); + assertEquals(BigDecimal.valueOf(1000), response.getResponseBody().getAmount()); + } + + @Test + void shouldRejectInvalidPaycodeRequest() { + PaycodeRequest request = PaycodeRequest.builder().build(); + + assertThrows( + MonnifyValidationException.class, + () -> paycodeService.createPaycode(request) + ); + } + + @Test + void shouldGetPaycode() { + stubFor(get(urlEqualTo("/api/v1/paycode/PAY-123")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"paycode\":\"123456\"," + + "\"transactionReference\":\"MNFY|123456\"," + + "\"paycodeReference\":\"PAY-123\"," + + "\"beneficiaryName\":\"John Doe\"," + + "\"amount\":1000," + + "\"fee\":10," + + "\"transactionStatus\":\"PENDING\"" + + "}" + + "}"))); + + MonnifyBaseResponse response = + paycodeService.getPaycode("PAY-123"); + + assertTrue(response.isRequestSuccessful()); + assertEquals("0", response.getResponseCode()); + assertNotNull(response.getResponseBody()); + assertEquals("123456", response.getResponseBody().getPaycode()); + assertEquals("PAY-123", response.getResponseBody().getPaycodeReference()); + } + + @Test + void shouldEncodePaycodeReferenceInUrlPath() { + stubFor(get(urlEqualTo("/api/v1/paycode/PAY%2B123")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"paycode\":\"123456\"," + + "\"paycodeReference\":\"PAY+123\"," + + "\"beneficiaryName\":\"John Doe\"," + + "\"amount\":1000," + + "\"transactionStatus\":\"PENDING\"" + + "}" + + "}"))); + + MonnifyBaseResponse response = + paycodeService.getPaycode("PAY+123"); + + assertTrue(response.isRequestSuccessful()); + assertNotNull(response.getResponseBody()); + assertEquals("PAY+123", response.getResponseBody().getPaycodeReference()); + + verify(getRequestedFor(urlEqualTo("/api/v1/paycode/PAY%2B123"))); + } + + @Test + void shouldRejectEmptyPaycodeReferenceWhenGettingPaycode() { + assertThrows( + MonnifyValidationException.class, + () -> paycodeService.getPaycode("") + ); + } + + @Test + void shouldRejectNullPaycodeReferenceWhenGettingPaycode() { + assertThrows( + MonnifyValidationException.class, + () -> paycodeService.getPaycode(null) + ); + } + + @Test + void shouldGetClearPaycode() { + stubFor(get(urlEqualTo("/api/v1/paycode/PAY-123/authorize")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"paycode\":\"123456\"," + + "\"paycodeReference\":\"PAY-123\"," + + "\"beneficiaryName\":\"John Doe\"," + + "\"amount\":1000," + + "\"transactionStatus\":\"AUTHORIZED\"" + + "}" + + "}"))); + + MonnifyBaseResponse response = + paycodeService.getClearPaycode("PAY-123"); + + assertTrue(response.isRequestSuccessful()); + assertEquals("0", response.getResponseCode()); + assertNotNull(response.getResponseBody()); + assertEquals("123456", response.getResponseBody().getPaycode()); + assertEquals("AUTHORIZED", response.getResponseBody().getTransactionStatus()); + } + + @Test + void shouldRejectEmptyPaycodeReferenceWhenClearingPaycode() { + assertThrows( + MonnifyValidationException.class, + () -> paycodeService.getClearPaycode("") + ); + } + + @Test + void shouldFetchPaycodes() { + stubFor(get(urlPathEqualTo("/api/v1/paycode")) + .withQueryParam("transactionReference", equalTo("MNFY|123456")) + .withQueryParam("beneficiaryName", equalTo("John Doe")) + .withQueryParam("transactionStatus", equalTo("PENDING")) + .withQueryParam("from", equalTo("100")) + .withQueryParam("to", equalTo("200")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"content\":[{" + + "\"paycode\":\"123456\"," + + "\"transactionReference\":\"MNFY|123456\"," + + "\"paycodeReference\":\"PAY-123\"," + + "\"beneficiaryName\":\"John Doe\"," + + "\"amount\":1000," + + "\"fee\":10," + + "\"transactionStatus\":\"PENDING\"" + + "}]," + + "\"pageNumber\":0," + + "\"pageSize\":10," + + "\"totalElements\":1," + + "\"totalPages\":1" + + "}" + + "}"))); + + MonnifyBaseResponse> response = + paycodeService.fetchPaycodes( + "MNFY|123456", + "John Doe", + "PENDING", + 100, + 200 + ); + + assertTrue(response.isRequestSuccessful()); + assertEquals("0", response.getResponseCode()); + assertNotNull(response.getResponseBody()); + assertNotNull(response.getResponseBody().getContent()); + assertEquals(1, response.getResponseBody().getContent().size()); + assertEquals( + "PAY-123", + response.getResponseBody().getContent().get(0).getPaycodeReference() + ); + } + + @Test + void shouldFetchPaycodesWithoutOptionalParameters() { + stubFor(get(urlPathEqualTo("/api/v1/paycode")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"content\":[]," + + "\"pageNumber\":0," + + "\"pageSize\":10," + + "\"totalElements\":0," + + "\"totalPages\":0" + + "}" + + "}"))); + + MonnifyBaseResponse> response = + paycodeService.fetchPaycodes(null, null, null, 0, 0); + + assertTrue(response.isRequestSuccessful()); + assertNotNull(response.getResponseBody()); + assertNotNull(response.getResponseBody().getContent()); + assertTrue(response.getResponseBody().getContent().isEmpty()); + } + + @Test + void shouldDeletePaycode() { + stubFor(delete(urlEqualTo("/api/v1/paycode/PAY-123")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"paycode\":\"123456\"," + + "\"paycodeReference\":\"PAY-123\"," + + "\"beneficiaryName\":\"John Doe\"," + + "\"amount\":1000," + + "\"transactionStatus\":\"DELETED\"" + + "}" + + "}"))); + + MonnifyBaseResponse response = + paycodeService.deletePaycode("PAY-123"); + + assertTrue(response.isRequestSuccessful()); + assertEquals("0", response.getResponseCode()); + assertNotNull(response.getResponseBody()); + assertEquals("PAY-123", response.getResponseBody().getPaycodeReference()); + assertEquals("DELETED", response.getResponseBody().getTransactionStatus()); + } + + @Test + void shouldRejectEmptyPaycodeReferenceWhenDeletingPaycode() { + assertThrows( + MonnifyValidationException.class, + () -> paycodeService.deletePaycode("") + ); + } + + @Test + void shouldRejectNullPaycodeReferenceWhenDeletingPaycode() { + assertThrows( + MonnifyValidationException.class, + () -> paycodeService.deletePaycode(null) + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/monnify/mocktests/RefundServiceMockTest.java b/src/test/java/com/monnify/mocktests/RefundServiceMockTest.java new file mode 100644 index 0000000..5908862 --- /dev/null +++ b/src/test/java/com/monnify/mocktests/RefundServiceMockTest.java @@ -0,0 +1,259 @@ +package com.monnify.mocktests; + +import com.monnify.BaseMonnifyMockTest; +import com.monnify.models.MonnifyBaseResponse; +import com.monnify.models.SearchResponse; +import com.monnify.models.refund.RefundRequest; +import com.monnify.models.refund.RefundResponse; +import com.monnify.services.refund.RefundService; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.*; + +class RefundServiceMockTest extends BaseMonnifyMockTest { + + private final RefundService refundService = new RefundService(); + + @Test + void shouldInitiateRefund() { + + stubFor( + post(urlPathEqualTo("/api/v1/refunds/initiate-refund")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"Refund initiated successfully\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"refundReference\":\"REF-123456\"," + + "\"transactionReference\":\"TXN-123456\"," + + "\"refundAmount\":1000.00," + + "\"refundStatus\":\"PENDING\"" + + "}" + + "}" + ) + ) + ); + + RefundRequest request = RefundRequest.builder() + .transactionReference("TXN-123456") + .refundReference("REF-123456") + .refundAmount(new BigDecimal("1000.00")) + .refundReason("Customer requested refund") + .customerNote("Refund for failed transaction") + .build(); + + MonnifyBaseResponse response = + refundService.initiateRefund(request); + + assertNotNull(response); + assertTrue(response.isRequestSuccessful()); + assertEquals("0", response.getResponseCode()); + assertNotNull(response.getResponseBody()); + assertEquals("REF-123456", + response.getResponseBody().getRefundReference()); + assertEquals("TXN-123456", + response.getResponseBody().getTransactionReference()); + + verify( + postRequestedFor( + urlPathEqualTo("/api/v1/refunds/initiate-refund") + ) + ); + } + + @Test + void shouldGetRefundStatus() { + + stubFor( + get(urlPathEqualTo("/api/v1/refunds/REF-123456")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"Refund status retrieved successfully\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"refundReference\":\"REF-123456\"," + + "\"transactionReference\":\"TXN-123456\"," + + "\"refundAmount\":1000.00," + + "\"refundStatus\":\"COMPLETED\"" + + "}" + + "}" + ) + ) + ); + + MonnifyBaseResponse response = + refundService.getRefundStatus("REF-123456"); + + assertNotNull(response); + assertTrue(response.isRequestSuccessful()); + assertEquals("0", response.getResponseCode()); + assertNotNull(response.getResponseBody()); + assertEquals("REF-123456", + response.getResponseBody().getRefundReference()); + assertEquals("COMPLETED", + response.getResponseBody().getRefundStatus()); + + verify( + getRequestedFor( + urlPathEqualTo("/api/v1/refunds/REF-123456") + ) + ); + } + + @Test + void shouldGetRefunds() { + + stubFor( + get(urlPathEqualTo("/api/v1/refunds")) + .withQueryParam("page", equalTo("0")) + .withQueryParam("size", equalTo("10")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"Refunds retrieved successfully\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"content\":[" + + "{" + + "\"refundReference\":\"REF-123456\"," + + "\"transactionReference\":\"TXN-123456\"," + + "\"refundAmount\":1000.00," + + "\"refundStatus\":\"COMPLETED\"" + + "}" + + "]," + + "\"pageSize\":10," + + "\"pageNumber\":0," + + "\"totalPages\":1," + + "\"totalElements\":1" + + "}" + + "}" + ) + ) + ); + + MonnifyBaseResponse> response = + refundService.getRefunds(0, 10); + + assertNotNull(response); + assertTrue(response.isRequestSuccessful()); + assertEquals("0", response.getResponseCode()); + assertNotNull(response.getResponseBody()); + assertNotNull(response.getResponseBody().getContent()); + assertEquals(1, response.getResponseBody().getContent().size()); + + RefundResponse refund = + response.getResponseBody().getContent().get(0); + + assertEquals("REF-123456", refund.getRefundReference()); + assertEquals("TXN-123456", refund.getTransactionReference()); + assertEquals(new BigDecimal("1000.00"), refund.getRefundAmount()); + assertEquals("COMPLETED", refund.getRefundStatus()); + + verify( + getRequestedFor( + urlPathEqualTo("/api/v1/refunds") + ) + .withQueryParam("page", equalTo("0")) + .withQueryParam("size", equalTo("10")) + ); + } + + @Test + void shouldGetRefundsWithoutPagination() { + + stubFor( + get(urlPathEqualTo("/api/v1/refunds")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"Refunds retrieved successfully\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"content\":[]," + + "\"pageSize\":0," + + "\"pageNumber\":0," + + "\"totalPages\":0," + + "\"totalElements\":0" + + "}" + + "}" + ) + ) + ); + + MonnifyBaseResponse> response = + refundService.getRefunds(null, null); + + assertNotNull(response); + assertTrue(response.isRequestSuccessful()); + assertNotNull(response.getResponseBody()); + assertNotNull(response.getResponseBody().getContent()); + assertTrue(response.getResponseBody().getContent().isEmpty()); + + verify( + getRequestedFor( + urlPathEqualTo("/api/v1/refunds") + ) + ); + } + + @Test + void shouldThrowExceptionWhenRefundReferenceIsEmpty() { + + assertThrows( + Exception.class, + () -> refundService.getRefundStatus("") + ); + + verify( + 0, + getRequestedFor( + urlPathMatching("/api/v1/refunds/.*") + ) + ); + } + + @Test + void shouldThrowExceptionWhenRefundRequestIsInvalid() { + + RefundRequest request = RefundRequest.builder() + .transactionReference("") + .refundReference("") + .refundAmount(null) + .refundReason("") + .customerNote("") + .build(); + + assertThrows( + Exception.class, + () -> refundService.initiateRefund(request) + ); + + verify( + 0, + postRequestedFor( + urlPathEqualTo("/api/v1/refunds/initiate-refund") + ) + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/monnify/mocktests/ReservedAccountServiceTest.java b/src/test/java/com/monnify/mocktests/ReservedAccountServiceTest.java new file mode 100644 index 0000000..db41445 --- /dev/null +++ b/src/test/java/com/monnify/mocktests/ReservedAccountServiceTest.java @@ -0,0 +1,462 @@ +package com.monnify.mocktests; +import com.monnify.BaseMonnifyMockTest; +import com.monnify.exceptions.MonnifyValidationException; +import com.monnify.models.MonnifyBaseResponse; +import com.monnify.models.SearchResponse; +import com.monnify.models.account.*; +import com.monnify.models.transaction.TransactionRecord; +import com.monnify.services.reservedaccount.ReservedAccountService; +import org.junit.jupiter.api.Test; +import java.util.ArrayList; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.*; + +public class ReservedAccountServiceTest extends BaseMonnifyMockTest { + + private final ReservedAccountService reservedAccountService = + new ReservedAccountService(); + + @Test + void shouldCreateReservedAccount() { + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"Reserved account created successfully\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"contractCode\":\"MK_TEST\"," + + "\"accountReference\":\"reserved-account-001\"," + + "\"accountName\":\"Test Customer\"," + + "\"currencyCode\":\"NGN\"," + + "\"customerEmail\":\"test@example.com\"," + + "\"customerName\":\"Test Customer\"," + + "\"status\":\"ACTIVE\"" + + "}" + + "}"; + + stubFor(post(urlEqualTo("/api/v2/bank-transfer/reserved-accounts")) + .willReturn(okJson(response))); + + ReservedAccountRequest request = new ReservedAccountRequest(); + request.setAccountReference("reserved-account-001"); + request.setAccountName("Test Customer"); + request.setCurrencyCode("NGN"); + request.setContractCode("MK_TEST"); + request.setCustomerEmail("test@example.com"); + request.setCustomerName("Test Customer"); + request.setGetAllAvailableBanks(true); + + MonnifyBaseResponse result = + reservedAccountService.createReservedAccount(request); + + assertNotNull(result); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + assertEquals( + "reserved-account-001", + result.getResponseBody().getAccountReference() + ); + + verify(postRequestedFor( + urlEqualTo("/api/v2/bank-transfer/reserved-accounts") + )); + } + + @Test + void shouldRejectReservedAccountWhenPreferredBanksAreRequired() { + + ReservedAccountRequest request = new ReservedAccountRequest(); + request.setAccountReference("reserved-account-001"); + request.setAccountName("Test Customer"); + request.setCurrencyCode("NGN"); + request.setContractCode("MK_TEST"); + request.setCustomerEmail("test@example.com"); + request.setCustomerName("Test Customer"); + request.setGetAllAvailableBanks(false); + request.setPreferredBanks(new ArrayList()); + + assertThrows( + MonnifyValidationException.class, + () -> reservedAccountService.createReservedAccount(request) + ); + } + + @Test + void shouldCreateInvoiceReservedAccount() { + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"Reserved account created successfully\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"accountReference\":\"invoice-account-001\"," + + "\"accountName\":\"Invoice Customer\"," + + "\"currencyCode\":\"NGN\"," + + "\"status\":\"ACTIVE\"" + + "}" + + "}"; + + stubFor(post(urlEqualTo("/api/v1/bank-transfer/reserved-accounts")) + .willReturn(okJson(response))); + + ReservedAccountRequest request = new ReservedAccountRequest(); + request.setAccountReference("invoice-account-001"); + request.setAccountName("Invoice Customer"); + request.setCurrencyCode("NGN"); + request.setContractCode("MK_TEST"); + request.setCustomerEmail("invoice@example.com"); + request.setCustomerName("Invoice Customer"); + request.setGetAllAvailableBanks(true); + + MonnifyBaseResponse result = + reservedAccountService.createReservedAccountInvoice(request); + + assertNotNull(result); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + assertEquals( + "invoice-account-001", + result.getResponseBody().getAccountReference() + ); + + verify(postRequestedFor( + urlEqualTo("/api/v1/bank-transfer/reserved-accounts") + )); + } + + @Test + void shouldCreateReservedAccountWithLimit() { + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"Reserved account created successfully\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"accountReference\":\"limit-account-001\"," + + "\"accountName\":\"Limit Customer\"," + + "\"currencyCode\":\"NGN\"," + + "\"status\":\"ACTIVE\"" + + "}" + + "}"; + + stubFor(post(urlEqualTo( + "/api/v2/bank-transfer/reserved-accounts/limit" + )).willReturn(okJson(response))); + + ReservedAccountRequest request = new ReservedAccountRequest(); + request.setAccountReference("limit-account-001"); + request.setAccountName("Limit Customer"); + request.setCurrencyCode("NGN"); + request.setContractCode("MK_TEST"); + request.setCustomerEmail("limit@example.com"); + request.setCustomerName("Limit Customer"); + request.setLimitProfileCode("LIMIT_PROFILE_001"); + request.setGetAllAvailableBanks(true); + + MonnifyBaseResponse result = + reservedAccountService.createReservedAccountWithLimit(request); + + assertNotNull(result); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + assertEquals( + "limit-account-001", + result.getResponseBody().getAccountReference() + ); + + verify(postRequestedFor( + urlEqualTo("/api/v2/bank-transfer/reserved-accounts/limit") + )); + } + + @Test + void shouldUpdateReservedAccountLimit() { + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"Limit profile updated successfully\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"accountReference\":\"reserved-account-001\"," + + "\"status\":\"ACTIVE\"" + + "}" + + "}"; + + stubFor(put(urlEqualTo( + "/api/v2/bank-transfer/reserved-accounts/limit" + )).willReturn(okJson(response))); + + UpdateReservedAccountLimitRequest request = + new UpdateReservedAccountLimitRequest(); + + request.setAccountReference("reserved-account-001"); + request.setLimitProfileCode("LIMIT_PROFILE_001"); + + MonnifyBaseResponse result = + reservedAccountService.updateReservedAccountLimit(request); + + assertNotNull(result); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + assertEquals( + "reserved-account-001", + result.getResponseBody().getAccountReference() + ); + + verify(putRequestedFor( + urlEqualTo("/api/v2/bank-transfer/reserved-accounts/limit") + )); + } + + @Test + void shouldRejectUpdateReservedAccountLimitWithMissingAccountReference() { + + UpdateReservedAccountLimitRequest request = + new UpdateReservedAccountLimitRequest(); + + request.setLimitProfileCode("LIMIT_PROFILE_001"); + + assertThrows( + MonnifyValidationException.class, + () -> reservedAccountService.updateReservedAccountLimit(request) + ); + } + + @Test + void shouldGetReservedAccountDetails() { + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"Reserved account retrieved successfully\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"accountReference\":\"reserved-account-001\"," + + "\"accountName\":\"Test Customer\"," + + "\"currencyCode\":\"NGN\"," + + "\"customerEmail\":\"test@example.com\"," + + "\"status\":\"ACTIVE\"" + + "}" + + "}"; + + stubFor(get(urlEqualTo( + "/api/v2/bank-transfer/reserved-accounts/reserved-account-001" + )).willReturn(okJson(response))); + + MonnifyBaseResponse result = + reservedAccountService.getReservedAccountDetails( + "reserved-account-001" + ); + + assertNotNull(result); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + assertEquals( + "reserved-account-001", + result.getResponseBody().getAccountReference() + ); + + verify(getRequestedFor( + urlEqualTo( + "/api/v2/bank-transfer/reserved-accounts/reserved-account-001" + ) + )); + } + + @Test + void shouldRejectGetReservedAccountDetailsWithoutReference() { + + assertThrows( + MonnifyValidationException.class, + () -> reservedAccountService.getReservedAccountDetails("") + ); + } + + @Test + void shouldDeallocateReservedAccount() { + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"Reserved account deallocated successfully\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"accountReference\":\"reserved-account-001\"," + + "\"status\":\"DEALLOCATED\"" + + "}" + + "}"; + + stubFor(delete(urlEqualTo( + "/api/v1/bank-transfer/reserved-accounts/reference/reserved-account-001" + )).willReturn(okJson(response))); + + MonnifyBaseResponse result = + reservedAccountService.deallocateReservedAccount( + "reserved-account-001" + ); + + assertNotNull(result); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + assertEquals( + "reserved-account-001", + result.getResponseBody().getAccountReference() + ); + + verify(deleteRequestedFor( + urlEqualTo( + "/api/v1/bank-transfer/reserved-accounts/reference/reserved-account-001" + ) + )); + } + + @Test + void shouldRejectDeallocateReservedAccountWithoutReference() { + + assertThrows( + MonnifyValidationException.class, + () -> reservedAccountService.deallocateReservedAccount(null) + ); + } + + @Test + void shouldUpdateKycInfoWithBvn() { + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"KYC information updated successfully\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"accountReference\":\"reserved-account-001\"," + + "\"accountName\":\"Test Customer\"," + + "\"customerEmail\":\"test@example.com\"," + + "\"customerName\":\"Test Customer\"," + + "\"bvn\":\"12345678901\"" + + "}" + + "}"; + + stubFor(put(urlEqualTo( + "/api/v1/bank-transfer/reserved-accounts/" + + "reserved-account-001/kyc-info" + )).willReturn(okJson(response))); + + KycInfoRequest request = new KycInfoRequest(); + request.setBvn("12345678901"); + + MonnifyBaseResponse result = + reservedAccountService.updateKycInfo( + request, + "reserved-account-001" + ); + + assertNotNull(result); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + assertEquals( + "reserved-account-001", + result.getResponseBody().getAccountReference() + ); + assertEquals( + "12345678901", + result.getResponseBody().getBvn() + ); + + verify(putRequestedFor( + urlEqualTo( + "/api/v1/bank-transfer/reserved-accounts/" + + "reserved-account-001/kyc-info" + ) + )); + } + + @Test + void shouldRejectKycUpdateWithoutBvnOrNin() { + + KycInfoRequest request = new KycInfoRequest(); + + assertThrows( + MonnifyValidationException.class, + () -> reservedAccountService.updateKycInfo( + request, + "reserved-account-001" + ) + ); + } + + @Test + void shouldRejectKycUpdateWithoutAccountReference() { + + KycInfoRequest request = new KycInfoRequest(); + request.setBvn("12345678901"); + + assertThrows( + MonnifyValidationException.class, + () -> reservedAccountService.updateKycInfo( + request, + "" + ) + ); + } + + @Test + void shouldGetReservedAccountTransactions() { + + stubFor(get(urlPathEqualTo( + "/api/v1/bank-transfer/reserved-accounts/transactions")) + .withQueryParam("accountReference", + equalTo("reserved-account-001")) + .withQueryParam("page", + equalTo("0")) + .withQueryParam("size", + equalTo("10")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{\n" + + " \"requestSuccessful\": true,\n" + + " \"responseMessage\": \"success\",\n" + + " \"responseCode\": \"0\",\n" + + " \"responseBody\": {\n" + + " \"content\": [],\n" + + " \"pageable\": {\n" + + " \"pageNumber\": 0,\n" + + " \"pageSize\": 10\n" + + " },\n" + + " \"totalElements\": 0,\n" + + " \"totalPages\": 0,\n" + + " \"last\": true,\n" + + " \"first\": true,\n" + + " \"numberOfElements\": 0,\n" + + " \"size\": 10,\n" + + " \"number\": 0\n" + + " }\n" + + "}"))); + + MonnifyBaseResponse> response = + reservedAccountService.getReservedAccountTransactions( + "reserved-account-001", + 0, + 10); + + assertNotNull(response); + assertTrue(response.isRequestSuccessful()); + assertEquals("success", response.getResponseMessage()); + assertEquals("0", response.getResponseCode()); + assertNotNull(response.getResponseBody()); + assertNotNull(response.getResponseBody().getContent()); + assertTrue(response.getResponseBody().getContent().isEmpty()); + } + + @Test + void shouldRejectReservedAccountTransactionsWithoutReference() { + + assertThrows( + MonnifyValidationException.class, + () -> reservedAccountService.getReservedAccountTransactions( + "", + 0, + 10 + ) + ); + } +} diff --git a/src/test/java/com/monnify/mocktests/SettlementServiceMockTest.java b/src/test/java/com/monnify/mocktests/SettlementServiceMockTest.java new file mode 100644 index 0000000..5d114b3 --- /dev/null +++ b/src/test/java/com/monnify/mocktests/SettlementServiceMockTest.java @@ -0,0 +1,410 @@ +package com.monnify.mocktests; + +import com.monnify.BaseMonnifyMockTest; +import com.monnify.exceptions.MonnifyValidationException; +import com.monnify.models.MonnifyBaseResponse; +import com.monnify.models.SearchResponse; +import com.monnify.models.settlement.SettlementResponse; +import com.monnify.models.transaction.TransactionStatusResponse; +import com.monnify.services.settlement.SettlementService; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Mock tests for {@link SettlementService}. + */ +public class SettlementServiceMockTest extends BaseMonnifyMockTest { + + private final SettlementService settlementService = new SettlementService(); + + @Test + void shouldGetTransactionsBySettlementReference() { + + stubFor(get(urlPathEqualTo( + "/api/v1/transactions/find-by-settlement-reference" + )) + .withQueryParam( + "reference", + equalTo("settlement-reference-001") + ) + .withQueryParam( + "page", + equalTo("0") + ) + .withQueryParam( + "size", + equalTo("10") + ) + .willReturn( + aResponse() + .withStatus(200) + .withHeader( + "Content-Type", + "application/json" + ) + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"content\":[" + + "{" + + "\"transactionReference\":\"MNFY|123456\"," + + "\"paymentReference\":\"PAY|123456\"," + + "\"amountPaid\":10000.00," + + "\"totalPayable\":10000.00," + + "\"settlementAmount\":9900.00," + + "\"paymentStatus\":\"PAID\"," + + "\"paymentDescription\":\"Test settlement\"," + + "\"currency\":\"NGN\"," + + "\"paymentMethod\":\"ACCOUNT_TRANSFER\"" + + "}" + + "]," + + "\"pageable\":{" + + "\"pageNumber\":0," + + "\"pageSize\":10" + + "}," + + "\"totalElements\":1," + + "\"totalPages\":1" + + "}" + + "}" + ) + ) + ); + + MonnifyBaseResponse> response = + settlementService.getTransactionsBySettlementReference( + "settlement-reference-001", + 0, + 10 + ); + + assertNotNull(response); + assertTrue(response.isRequestSuccessful()); + assertNotNull(response.getResponseBody()); + + assertEquals( + 1, + response.getResponseBody().getContent().size() + ); + + TransactionStatusResponse transaction = + response.getResponseBody() + .getContent() + .get(0); + + assertEquals( + "MNFY|123456", + transaction.getTransactionReference() + ); + + assertEquals( + "PAY|123456", + transaction.getPaymentReference() + ); + + assertEquals( + new BigDecimal("10000.00"), + transaction.getAmountPaid() + ); + + assertEquals( + new BigDecimal("9900.00"), + transaction.getSettlementAmount() + ); + + assertEquals( + "PAID", + transaction.getPaymentStatus() + ); + + assertEquals( + "NGN", + transaction.getCurrency() + ); + + verify( + getRequestedFor( + urlPathEqualTo( + "/api/v1/transactions/find-by-settlement-reference" + ) + ) + .withQueryParam( + "reference", + equalTo("settlement-reference-001") + ) + .withQueryParam( + "page", + equalTo("0") + ) + .withQueryParam( + "size", + equalTo("10") + ) + ); + } + + @Test + void shouldThrowExceptionWhenReferenceIsNull() { + + MonnifyValidationException exception = + assertThrows( + MonnifyValidationException.class, + () -> settlementService + .getTransactionsBySettlementReference( + null, + 0, + 10 + ) + ); + + assertEquals( + "Reference is required, size must be greater than 0", + exception.getMessage() + ); + } + + @Test + void shouldThrowExceptionWhenReferenceIsEmpty() { + + MonnifyValidationException exception = + assertThrows( + MonnifyValidationException.class, + () -> settlementService + .getTransactionsBySettlementReference( + "", + 0, + 10 + ) + ); + + assertEquals( + "Reference is required, size must be greater than 0", + exception.getMessage() + ); + } + + @Test + void shouldThrowExceptionWhenSizeIsZero() { + + MonnifyValidationException exception = + assertThrows( + MonnifyValidationException.class, + () -> settlementService + .getTransactionsBySettlementReference( + "settlement-reference-001", + 0, + 0 + ) + ); + + assertEquals( + "Reference is required, size must be greater than 0", + exception.getMessage() + ); + } + + @Test + void shouldThrowExceptionWhenSizeIsNegative() { + + MonnifyValidationException exception = + assertThrows( + MonnifyValidationException.class, + () -> settlementService + .getTransactionsBySettlementReference( + "settlement-reference-001", + 0, + -1 + ) + ); + + assertEquals( + "Reference is required, size must be greater than 0", + exception.getMessage() + ); + } + + @Test + void shouldGetSettlementInformation() { + + stubFor(get(urlPathEqualTo( + "/api/v1/settlement-detail" + )) + .withQueryParam( + "transactionReference", + equalTo("MNFY|123456") + ) + .willReturn( + aResponse() + .withStatus(200) + .withHeader( + "Content-Type", + "application/json" + ) + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"transaction\":{" + + "\"transactionReference\":\"MNFY|123456\"," + + "\"paymentReference\":\"PAY|123456\"," + + "\"amountPaid\":10000.00," + + "\"totalPayable\":10000.00," + + "\"settlementAmount\":9900.00," + + "\"paymentStatus\":\"PAID\"," + + "\"paymentDescription\":\"Test payment\"," + + "\"transactionHash\":\"transaction-hash-001\"," + + "\"currency\":\"NGN\"," + + "\"paymentMethod\":\"ACCOUNT_TRANSFER\"" + + "}," + + "\"beneficiaries\":[]" + + "}" + + "}" + ) + ) + ); + + MonnifyBaseResponse response = + settlementService.getSettlementInformation( + "MNFY|123456" + ); + + assertNotNull(response); + assertTrue(response.isRequestSuccessful()); + assertNotNull(response.getResponseBody()); + + SettlementResponse settlement = + response.getResponseBody(); + + assertNotNull(settlement.getTransaction()); + + TransactionStatusResponse transaction = + settlement.getTransaction(); + + assertEquals( + "MNFY|123456", + transaction.getTransactionReference() + ); + + assertEquals( + "PAY|123456", + transaction.getPaymentReference() + ); + + assertEquals( + new BigDecimal("10000.00"), + transaction.getAmountPaid() + ); + + assertEquals( + new BigDecimal("10000.00"), + transaction.getTotalPayable() + ); + + assertEquals( + new BigDecimal("9900.00"), + transaction.getSettlementAmount() + ); + + assertEquals( + "PAID", + transaction.getPaymentStatus() + ); + + assertEquals( + "NGN", + transaction.getCurrency() + ); + + assertEquals( + "ACCOUNT_TRANSFER", + transaction.getPaymentMethod() + ); + + assertNotNull( + settlement.getBeneficiaries() + ); + + assertTrue( + settlement.getBeneficiaries().isEmpty() + ); + + verify( + getRequestedFor( + urlPathEqualTo( + "/api/v1/settlement-detail" + ) + ) + .withQueryParam( + "transactionReference", + equalTo("MNFY|123456") + ) + ); + } + + @Test + void shouldGetSettlementInformationWithEncodedReference() { + + String transactionReference = + "transaction/reference 001"; + + stubFor(get(urlPathEqualTo( + "/api/v1/settlement-detail" + )) + .withQueryParam( + "transactionReference", + equalTo(transactionReference) + ) + .willReturn( + aResponse() + .withStatus(200) + .withHeader( + "Content-Type", + "application/json" + ) + .withBody( + "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"transaction\":{" + + "\"transactionReference\":\"transaction/reference 001\"," + + "\"paymentReference\":\"PAY-001\"," + + "\"amountPaid\":5000.00," + + "\"totalPayable\":5000.00," + + "\"settlementAmount\":4900.00," + + "\"paymentStatus\":\"PAID\"," + + "\"currency\":\"NGN\"" + + "}," + + "\"beneficiaries\":[]" + + "}" + + "}" + ) + ) + ); + + MonnifyBaseResponse response = + settlementService.getSettlementInformation( + transactionReference + ); + + assertNotNull(response); + assertTrue(response.isRequestSuccessful()); + assertNotNull(response.getResponseBody()); + + assertEquals( + transactionReference, + response.getResponseBody() + .getTransaction() + .getTransactionReference() + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/monnify/mocktests/SubAccountServiceTest.java b/src/test/java/com/monnify/mocktests/SubAccountServiceTest.java new file mode 100644 index 0000000..403f5c6 --- /dev/null +++ b/src/test/java/com/monnify/mocktests/SubAccountServiceTest.java @@ -0,0 +1,271 @@ +package com.monnify.mocktests; + +import com.monnify.BaseMonnifyMockTest; +import com.monnify.exceptions.MonnifyValidationException; +import com.monnify.models.MonnifyBaseResponse; +import com.monnify.models.subaccount.SubAccountRequest; +import com.monnify.models.subaccount.SubAccountResponse; +import com.monnify.models.subaccount.UpdateSubAccountRequest; +import com.monnify.services.subaccount.SubAccountService; +import org.junit.jupiter.api.Test; +import java.util.Arrays; +import java.util.List; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.*; + +public class SubAccountServiceTest extends BaseMonnifyMockTest { + + private final SubAccountService subAccountService = new SubAccountService(); + + @Test + void shouldCreateSubAccounts() { + + stubFor(post(urlEqualTo("/api/v1/sub-accounts")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{\n" + + " \"requestSuccessful\": true,\n" + + " \"responseMessage\": \"success\",\n" + + " \"responseCode\": \"0\",\n" + + " \"responseBody\": [\n" + + " {\n" + + " \"subAccountCode\": \"MFY_SUB_001\",\n" + + " \"accountNumber\": \"1234567890\",\n" + + " \"accountName\": \"Test Sub Account\",\n" + + " \"currencyCode\": \"NGN\",\n" + + " \"email\": \"test@example.com\",\n" + + " \"bankCode\": \"058\",\n" + + " \"bankName\": \"GTBank\",\n" + + " \"defaultSplitPercentage\": 50.0,\n" + + " \"settlementProfileCode\": \"SETTLEMENT_001\",\n" + + " \"settlementReportEmails\": [\n" + + " \"test@example.com\"\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}" + ))); + + SubAccountRequest request = SubAccountRequest.builder() + .accountNumber("1234567890") + .currencyCode("NGN") + .email("test@example.com") + .bankCode("058") + .defaultSplitPercentage(50.0) + .build(); + + List requests = Arrays.asList(request); + + MonnifyBaseResponse> response = + subAccountService.createSubAccounts(requests); + + assertNotNull(response); + assertTrue(response.isRequestSuccessful()); + assertNotNull(response.getResponseBody()); + assertEquals(1, response.getResponseBody().size()); + + SubAccountResponse subAccount = + response.getResponseBody().get(0); + + assertEquals("MFY_SUB_001", subAccount.getSubAccountCode()); + assertEquals("1234567890", subAccount.getAccountNumber()); + assertEquals("Test Sub Account", subAccount.getAccountName()); + assertEquals("NGN", subAccount.getCurrencyCode()); + assertEquals("test@example.com", subAccount.getEmail()); + assertEquals("058", subAccount.getBankCode()); + assertEquals("GTBank", subAccount.getBankName()); + assertEquals(50.0, subAccount.getDefaultSplitPercentage()); + + verify(postRequestedFor(urlEqualTo("/api/v1/sub-accounts"))); + } + + @Test + void shouldDeleteSubAccount() { + + stubFor(delete(urlEqualTo("/api/v1/sub-accounts/MFY_SUB_001")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{\n" + + " \"requestSuccessful\": true,\n" + + " \"responseMessage\": \"success\",\n" + + " \"responseCode\": \"0\",\n" + + " \"responseBody\": null\n" + + "}" + ))); + + MonnifyBaseResponse response = + subAccountService.deleteSubAccount("MFY_SUB_001"); + + assertNotNull(response); + assertTrue(response.isRequestSuccessful()); + assertEquals("0", response.getResponseCode()); + + verify(deleteRequestedFor( + urlEqualTo("/api/v1/sub-accounts/MFY_SUB_001") + )); + } + + @Test + void shouldThrowExceptionWhenDeletingSubAccountWithEmptyCode() { + + MonnifyValidationException exception = + assertThrows( + MonnifyValidationException.class, + () -> subAccountService.deleteSubAccount("") + ); + + assertEquals("subAccountCode is empty", exception.getMessage()); + + verify(0, deleteRequestedFor(anyUrl())); + } + + @Test + void shouldThrowExceptionWhenDeletingSubAccountWithNullCode() { + + MonnifyValidationException exception = + assertThrows( + MonnifyValidationException.class, + () -> subAccountService.deleteSubAccount(null) + ); + + assertEquals("subAccountCode is empty", exception.getMessage()); + + verify(0, deleteRequestedFor(anyUrl())); + } + + @Test + void shouldGetSubAccounts() { + + stubFor(get(urlEqualTo("/api/v1/sub-accounts")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{\n" + + " \"requestSuccessful\": true,\n" + + " \"responseMessage\": \"success\",\n" + + " \"responseCode\": \"0\",\n" + + " \"responseBody\": [\n" + + " {\n" + + " \"subAccountCode\": \"MFY_SUB_001\",\n" + + " \"accountNumber\": \"1234567890\",\n" + + " \"accountName\": \"Test Sub Account\",\n" + + " \"currencyCode\": \"NGN\",\n" + + " \"email\": \"test@example.com\",\n" + + " \"bankCode\": \"058\",\n" + + " \"bankName\": \"GTBank\",\n" + + " \"defaultSplitPercentage\": 50.0,\n" + + " \"settlementProfileCode\": \"SETTLEMENT_001\",\n" + + " \"settlementReportEmails\": [\n" + + " \"test@example.com\"\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"subAccountCode\": \"MFY_SUB_002\",\n" + + " \"accountNumber\": \"0987654321\",\n" + + " \"accountName\": \"Second Sub Account\",\n" + + " \"currencyCode\": \"NGN\",\n" + + " \"email\": \"second@example.com\",\n" + + " \"bankCode\": \"044\",\n" + + " \"bankName\": \"Access Bank\",\n" + + " \"defaultSplitPercentage\": 30.0,\n" + + " \"settlementProfileCode\": \"SETTLEMENT_002\",\n" + + " \"settlementReportEmails\": [\n" + + " \"second@example.com\"\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}" + ))); + + MonnifyBaseResponse> response = + subAccountService.getSubAccounts(); + + assertNotNull(response); + assertTrue(response.isRequestSuccessful()); + assertNotNull(response.getResponseBody()); + assertEquals(2, response.getResponseBody().size()); + + SubAccountResponse first = + response.getResponseBody().get(0); + + assertEquals("MFY_SUB_001", first.getSubAccountCode()); + assertEquals("1234567890", first.getAccountNumber()); + assertEquals("Test Sub Account", first.getAccountName()); + assertEquals("NGN", first.getCurrencyCode()); + assertEquals("058", first.getBankCode()); + assertEquals("GTBank", first.getBankName()); + + SubAccountResponse second = + response.getResponseBody().get(1); + + assertEquals("MFY_SUB_002", second.getSubAccountCode()); + assertEquals("0987654321", second.getAccountNumber()); + assertEquals("Second Sub Account", second.getAccountName()); + + verify(getRequestedFor(urlEqualTo("/api/v1/sub-accounts"))); + } + + @Test + void shouldUpdateSubAccount() { + + stubFor(put(urlEqualTo("/api/v1/sub-accounts")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody( + "{\n" + + " \"requestSuccessful\": true,\n" + + " \"responseMessage\": \"success\",\n" + + " \"responseCode\": \"0\",\n" + + " \"responseBody\": {\n" + + " \"subAccountCode\": \"MFY_SUB_001\",\n" + + " \"accountNumber\": \"1234567890\",\n" + + " \"accountName\": \"Updated Sub Account\",\n" + + " \"currencyCode\": \"NGN\",\n" + + " \"email\": \"updated@example.com\",\n" + + " \"bankCode\": \"058\",\n" + + " \"bankName\": \"GTBank\",\n" + + " \"defaultSplitPercentage\": 70.0,\n" + + " \"settlementProfileCode\": \"SETTLEMENT_001\",\n" + + " \"settlementReportEmails\": [\n" + + " \"updated@example.com\"\n" + + " ]\n" + + " }\n" + + "}" + ))); + + UpdateSubAccountRequest request = + UpdateSubAccountRequest.builder() + .subAccountCode("MFY_SUB_001") + .currencyCode("NGN") + .bankCode("058") + .accountNumber("1234567890") + .email("updated@example.com") + .defaultSplitPercentage(70.0) + .build(); + + MonnifyBaseResponse response = + subAccountService.updateSubAccount(request); + + assertNotNull(response); + assertTrue(response.isRequestSuccessful()); + assertNotNull(response.getResponseBody()); + + SubAccountResponse subAccount = + response.getResponseBody(); + + assertEquals("MFY_SUB_001", subAccount.getSubAccountCode()); + assertEquals("1234567890", subAccount.getAccountNumber()); + assertEquals("Updated Sub Account", subAccount.getAccountName()); + assertEquals("updated@example.com", subAccount.getEmail()); + assertEquals(70.0, subAccount.getDefaultSplitPercentage()); + + verify(putRequestedFor(urlEqualTo("/api/v1/sub-accounts"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/monnify/mocktests/TransactionServiceMockTest.java b/src/test/java/com/monnify/mocktests/TransactionServiceMockTest.java new file mode 100644 index 0000000..a2d08b6 --- /dev/null +++ b/src/test/java/com/monnify/mocktests/TransactionServiceMockTest.java @@ -0,0 +1,502 @@ +package com.monnify.mocktests; +import com.google.gson.Gson; +import com.monnify.BaseMonnifyMockTest; +import com.monnify.exceptions.MonnifyValidationException; +import com.monnify.models.MonnifyBaseResponse; +import com.monnify.models.SearchResponse; +import com.monnify.models.transaction.*; +import com.monnify.services.transaction.TransactionService; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.*; + +public class TransactionServiceMockTest extends BaseMonnifyMockTest { + + private final TransactionService transactionService = new TransactionService(); + private final Gson gson = new Gson(); + + @Test + void shouldInitializeTransaction() { + + TransactionRequest request = TransactionRequest.builder() + .amount(new BigDecimal("1000.00")) + .customerName("John Doe") + .customerEmail("john.doe@example.com") + .paymentReference("PAY-001") + .paymentDescription("Test payment") + .currencyCode("NGN") + .contractCode("1234567890") + .redirectUrl("https://example.com/callback") + .build(); + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"transactionReference\":\"MNFY-TRANS-001\"," + + "\"paymentReference\":\"PAY-001\"," + + "\"amount\":1000.00," + + "\"currencyCode\":\"NGN\"," + + "\"paymentStatus\":\"PENDING\"" + + "}" + + "}"; + + stubFor(post(urlEqualTo("/api/v1/merchant/transactions/init-transaction")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(response))); + + MonnifyBaseResponse result = + transactionService.initializeTransaction(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + + verify(postRequestedFor( + urlEqualTo("/api/v1/merchant/transactions/init-transaction"))); + } + + @Test + void shouldPayWithBankTransfer() { + + BankTransferRequest request = BankTransferRequest.builder() + .transactionReference("MNFY-TRANS-001") + .bankCode("058") + .build(); + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"transactionReference\":\"MNFY-TRANS-001\"," + + "\"paymentReference\":\"PAY-001\"," + + "\"accountNumber\":\"1234567890\"," + + "\"accountName\":\"John Doe\"," + + "\"bankName\":\"GTBank\"," + + "\"bankCode\":\"058\"" + + "}" + + "}"; + + stubFor(post(urlEqualTo("/api/v1/merchant/bank-transfer/init-payment")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(response))); + + MonnifyBaseResponse result = + transactionService.payWithBankTransfer(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + + verify(postRequestedFor( + urlEqualTo("/api/v1/merchant/bank-transfer/init-payment"))); + } + + @Test + void shouldChargeCard() { + + ChargeCardRequest.CardDetails card = + new ChargeCardRequest.CardDetails(); + + card.setNumber("4111111111111111"); + card.setExpiryMonth("12"); + card.setExpiryYear("30"); + card.setPin("1234"); + card.setCvv("123"); + + ChargeCardRequest request = ChargeCardRequest.builder() + .transactionReference("MNFY-TRANS-001") + .collectionChannel("CARD") + .card(card) + .build(); + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"authorizedAmount\":1000.00," + + "\"paymentReference\":\"PAY-001\"," + + "\"transactionReference\":\"MNFY-TRANS-001\"," + + "\"status\":\"SUCCESS\"," + + "\"message\":\"Card charged successfully\"" + + "}" + + "}"; + + stubFor(post(urlEqualTo("/api/v1/merchant/cards/charge")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(response))); + + MonnifyBaseResponse result = + transactionService.chargeCard(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + + assertEquals( + "PAY-001", + result.getResponseBody().getPaymentReference() + ); + + assertEquals( + "SUCCESS", + result.getResponseBody().getStatus() + ); + + verify(postRequestedFor( + urlEqualTo("/api/v1/merchant/cards/charge"))); + } + + @Test + void shouldChargeCardToken() { + + ChargeCardTokenRequest request = ChargeCardTokenRequest.builder() + .cardToken("card-token-001") + .amount(new BigDecimal("1000.00")) + .customerName("John Doe") + .customerEmail("john.doe@example.com") + .paymentReference("PAY-002") + .paymentDescription("Token card payment") + .currencyCode("NGN") + .contractCode("1234567890") + .apiKey("test-api-key") + .build(); + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"transactionReference\":\"MNFY-TRANS-002\"," + + "\"paymentReference\":\"PAY-002\"," + + "\"paymentStatus\":\"PAID\"," + + "\"amountPaid\":1000.00," + + "\"currency\":\"NGN\"" + + "}" + + "}"; + + stubFor(post(urlEqualTo("/api/v1/merchant/cards/charge-card-token")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(response))); + + MonnifyBaseResponse result = + transactionService.chargeCardToken(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + + assertEquals( + "MNFY-TRANS-002", + result.getResponseBody().getTransactionReference() + ); + + verify(postRequestedFor( + urlEqualTo("/api/v1/merchant/cards/charge-card-token"))); + } + + @Test + void shouldAuthorizeCardOtp() { + + AuthorizeOtpRequest request = AuthorizeOtpRequest.builder() + .transactionReference("MNFY-TRANS-001") + .collectionChannel("CARD") + .tokenId("otp-token-001") + .token("123456") + .build(); + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"authorizedAmount\":1000.00," + + "\"paymentReference\":\"PAY-001\"," + + "\"transactionReference\":\"MNFY-TRANS-001\"," + + "\"status\":\"SUCCESS\"," + + "\"message\":\"OTP authorization successful\"" + + "}" + + "}"; + + stubFor(post(urlEqualTo("/api/v1/merchant/cards/otp/authorize")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(response))); + + MonnifyBaseResponse result = + transactionService.authorizeCardOtp(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + + assertEquals( + "SUCCESS", + result.getResponseBody().getStatus() + ); + + verify(postRequestedFor( + urlEqualTo("/api/v1/merchant/cards/otp/authorize"))); + } + + @Test + void shouldSearchTransactions() { + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"content\":[" + + "{" + + "\"transactionReference\":\"MNFY-TRANS-001\"," + + "\"paymentReference\":\"PAY-001\"," + + "\"amountPaid\":1000.00," + + "\"paymentStatus\":\"PAID\"" + + "}" + + "]," + + "\"pageable\":{" + + "\"pageNumber\":0," + + "\"pageSize\":10" + + "}," + + "\"totalElements\":1," + + "\"totalPages\":1" + + "}" + + "}"; + + stubFor(get(urlPathEqualTo("/api/v1/transactions/search")) + .withQueryParam("pageNo", equalTo("0")) + .withQueryParam("pageSize", equalTo("10")) + .withQueryParam("paymentReference", equalTo("PAY-001")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(response))); + + MonnifyBaseResponse> result = + transactionService.searchTransactions( + 0, + 10, + "PAY-001", + null, + null, + null, + null, + null, + null, + "PAID", + null, + null + ); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + + verify(getRequestedFor( + urlPathEqualTo("/api/v1/transactions/search")) + .withQueryParam("pageNo", equalTo("0")) + .withQueryParam("pageSize", equalTo("10")) + .withQueryParam("paymentReference", equalTo("PAY-001"))); + } + + @Test + void shouldGetTransactionStatus() { + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"transactionReference\":\"MNFY-TRANS-001\"," + + "\"paymentReference\":\"PAY-001\"," + + "\"amountPaid\":1000.00," + + "\"totalPayable\":1000.00," + + "\"paymentStatus\":\"PAID\"," + + "\"currency\":\"NGN\"" + + "}" + + "}"; + + stubFor(get(urlEqualTo("/api/v2/transactions/MNFY-TRANS-001")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(response))); + + MonnifyBaseResponse result = + transactionService.getStatus("MNFY-TRANS-001"); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + + assertEquals( + "MNFY-TRANS-001", + result.getResponseBody().getTransactionReference() + ); + + assertEquals( + "PAID", + result.getResponseBody().getPaymentStatus() + ); + + verify(getRequestedFor( + urlEqualTo("/api/v2/transactions/MNFY-TRANS-001"))); + } + + @Test + void shouldGetStatusByPaymentReference() { + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"transactionReference\":\"MNFY-TRANS-001\"," + + "\"paymentReference\":\"PAY-001\"," + + "\"paymentStatus\":\"PAID\"," + + "\"amountPaid\":1000.00," + + "\"currency\":\"NGN\"" + + "}" + + "}"; + + stubFor(get(urlPathEqualTo("/api/v2/merchant/transactions/query")) + .withQueryParam("paymentReference", equalTo("PAY-001")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(response))); + + MonnifyBaseResponse result = + transactionService.getStatusByReference( + "PAY-001", + null + ); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + + assertEquals( + "PAY-001", + result.getResponseBody().getPaymentReference() + ); + + assertEquals( + "PAID", + result.getResponseBody().getPaymentStatus() + ); + + verify(getRequestedFor( + urlPathEqualTo("/api/v2/merchant/transactions/query")) + .withQueryParam( + "paymentReference", + equalTo("PAY-001") + )); + } + + @Test + void shouldGetStatusByTransactionReference() { + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"transactionReference\":\"MNFY-TRANS-001\"," + + "\"paymentReference\":\"PAY-001\"," + + "\"paymentStatus\":\"PAID\"," + + "\"amountPaid\":1000.00" + + "}" + + "}"; + + stubFor(get(urlPathEqualTo("/api/v2/merchant/transactions/query")) + .withQueryParam( + "transactionReference", + equalTo("MNFY-TRANS-001") + ) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(response))); + + MonnifyBaseResponse result = + transactionService.getStatusByReference( + null, + "MNFY-TRANS-001" + ); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + + assertEquals( + "MNFY-TRANS-001", + result.getResponseBody().getTransactionReference() + ); + + verify(getRequestedFor( + urlPathEqualTo("/api/v2/merchant/transactions/query")) + .withQueryParam( + "transactionReference", + equalTo("MNFY-TRANS-001") + )); + } + + @Test + void shouldThrowExceptionWhenTransactionReferenceIsEmpty() { + + assertThrows( + MonnifyValidationException.class, + () -> transactionService.getStatus("") + ); + } + + @Test + void shouldThrowExceptionWhenBothReferencesAreEmpty() { + + assertThrows( + MonnifyValidationException.class, + () -> transactionService.getStatusByReference( + null, + null + ) + ); + } + + @Test + void shouldThrowExceptionWhenBothReferencesAreProvided() { + + assertThrows( + MonnifyValidationException.class, + () -> transactionService.getStatusByReference( + "PAY-001", + "MNFY-TRANS-001" + ) + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/monnify/mocktests/VerificationServiceMockTest.java b/src/test/java/com/monnify/mocktests/VerificationServiceMockTest.java new file mode 100644 index 0000000..dce2237 --- /dev/null +++ b/src/test/java/com/monnify/mocktests/VerificationServiceMockTest.java @@ -0,0 +1,371 @@ +package com.monnify.mocktests; +import com.monnify.BaseMonnifyMockTest; +import com.monnify.exceptions.MonnifyValidationException; +import com.monnify.models.MonnifyBaseResponse; +import com.monnify.models.verification.*; +import com.monnify.services.verification.VerificationService; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.*; + + +public class VerificationServiceMockTest extends BaseMonnifyMockTest { + + private final VerificationService verificationService = + new VerificationService(); + + @Test + void shouldValidateBankAccount() { + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"accountNumber\":\"0123456789\"," + + "\"accountName\":\"JOHN DOE\"," + + "\"bankCode\":\"058\"" + + "}" + + "}"; + + stubFor(get(urlPathEqualTo( + "/api/v2/disbursements/account/validate")) + .withQueryParam( + "accountNumber", + equalTo("0123456789")) + .withQueryParam( + "bankCode", + equalTo("058")) + .willReturn(aResponse() + .withStatus(200) + .withHeader( + "Content-Type", + "application/json") + .withBody(response))); + + MonnifyBaseResponse result = + verificationService.validateBankAccount( + "0123456789", + "058" + ); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + + assertEquals( + "0123456789", + result.getResponseBody().getAccountNumber() + ); + + assertEquals( + "JOHN DOE", + result.getResponseBody().getAccountName() + ); + + assertEquals( + "058", + result.getResponseBody().getBankCode() + ); + + verify(getRequestedFor( + urlPathEqualTo( + "/api/v2/disbursements/account/validate")) + .withQueryParam( + "accountNumber", + equalTo("0123456789")) + .withQueryParam( + "bankCode", + equalTo("058")) + ); + } + + @Test + void shouldThrowExceptionWhenAccountNumberIsEmpty() { + + assertThrows( + MonnifyValidationException.class, + () -> verificationService.validateBankAccount( + "", + "058" + ) + ); + } + + @Test + void shouldThrowExceptionWhenBankCodeIsEmpty() { + + assertThrows( + MonnifyValidationException.class, + () -> verificationService.validateBankAccount( + "0123456789", + "" + ) + ); + } + + @Test + void shouldVerifyBVNInformation() { + + String response = "{" + + "\"requestSuccessful\": true," + + "\"responseMessage\": \"success\"," + + "\"responseCode\": \"0\"," + + "\"responseBody\": {" + + "\"bvn\": \"22222222222\"," + + "\"name\": {" + + "\"matchStatus\": \"FULL_MATCH\"," + + "\"matchPercentage\": 100.0" + + "}," + + "\"dateOfBirth\": \"1990-01-01\"," + + "\"mobileNo\": \"08012345678\"" + + "}" + + "}"; + + stubFor(post(urlEqualTo("/api/v1/vas/bvn-details-match")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(response))); + + BVNVerificationRequest request = BVNVerificationRequest.builder() + .bvn("22222222222") + .name("John Doe") + .dateOfBirth(LocalDate.of(1990, 1, 1)) + .mobileNo("08012345678") + .build(); + + MonnifyBaseResponse result = + verificationService.verifyBVNInformation(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertEquals("success", result.getResponseMessage()); + + assertNotNull(result.getResponseBody()); + + BVNVerificationResponse responseBody = result.getResponseBody(); + + assertEquals("22222222222", responseBody.getBvn()); + assertEquals("1990-01-01", responseBody.getDateOfBirth()); + assertEquals("08012345678", responseBody.getMobileNo()); + + assertNotNull(responseBody.getName()); + assertEquals("FULL_MATCH", responseBody.getName().getMatchStatus()); + assertEquals( + 100.0, + responseBody.getName().getMatchPercentage(), + 0.001 + ); + + verify(postRequestedFor( + urlEqualTo("/api/v1/vas/bvn-details-match") + )); + } + + @Test + void shouldBVNAccountNameMatch() { + + BVNAccountMatchRequest request = + BVNAccountMatchRequest.builder() + .bankCode("058") + .accountNumber("0123456789") + .bvn("22222222222") + .build(); + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"accountNumber\":\"0123456789\"," + + "\"accountName\":\"JOHN DOE\"," + + "\"bvn\":\"22222222222\"," + + "\"matchStatus\":\"FULL_MATCH\"," + + "\"matchPercentage\":100.0" + + "}" + + "}"; + + stubFor(post(urlEqualTo( + "/api/v1/vas/bvn-account-match")) + .willReturn(aResponse() + .withStatus(200) + .withHeader( + "Content-Type", + "application/json") + .withBody(response))); + + MonnifyBaseResponse result = + verificationService.bvnAccountNameMatch(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + + verify(postRequestedFor( + urlEqualTo( + "/api/v1/vas/bvn-account-match"))); + } + + @Test + void shouldVerifyNIN() { + + NINVerificationRequest request = + NINVerificationRequest.builder() + .nin("12345678901") + .build(); + + String response = "{" + + "\"requestSuccessful\":true," + + "\"responseMessage\":\"success\"," + + "\"responseCode\":\"0\"," + + "\"responseBody\":{" + + "\"nin\":\"12345678901\"," + + "\"lastName\":\"DOE\"," + + "\"firstName\":\"JOHN\"," + + "\"middleName\":\"MICHAEL\"," + + "\"dateOfBirth\":\"1995-01-01\"," + + "\"gender\":\"MALE\"," + + "\"mobileNumber\":\"08012345678\"" + + "}" + + "}"; + + stubFor(post(urlEqualTo( + "/api/v1/vas/nin-details")) + .willReturn(aResponse() + .withStatus(200) + .withHeader( + "Content-Type", + "application/json") + .withBody(response))); + + MonnifyBaseResponse result = + verificationService.verifyNIN(request); + + assertNotNull(result); + assertTrue(result.isRequestSuccessful()); + assertEquals("0", result.getResponseCode()); + assertNotNull(result.getResponseBody()); + + assertEquals( + "12345678901", + result.getResponseBody().getNin() + ); + + assertEquals( + "DOE", + result.getResponseBody().getLastName() + ); + + assertEquals( + "JOHN", + result.getResponseBody().getFirstName() + ); + + assertEquals( + "MICHAEL", + result.getResponseBody().getMiddleName() + ); + + assertEquals( + "1995-01-01", + result.getResponseBody().getDateOfBirth() + ); + + assertEquals( + "MALE", + result.getResponseBody().getGender() + ); + + assertEquals( + "08012345678", + result.getResponseBody().getMobileNumber() + ); + + verify(postRequestedFor( + urlEqualTo( + "/api/v1/vas/nin-details"))); + } + + @Test + void shouldThrowExceptionWhenBVNIsInvalid() { + + BVNAccountMatchRequest request = + BVNAccountMatchRequest.builder() + .bankCode("058") + .accountNumber("0123456789") + .bvn("12345") + .build(); + + assertThrows( + MonnifyValidationException.class, + () -> verificationService.bvnAccountNameMatch(request) + ); + } + + @Test + void shouldThrowExceptionWhenBVNAccountNumberIsEmpty() { + + BVNAccountMatchRequest request = + BVNAccountMatchRequest.builder() + .bankCode("058") + .accountNumber("") + .bvn("22222222222") + .build(); + + assertThrows( + MonnifyValidationException.class, + () -> verificationService.bvnAccountNameMatch(request) + ); + } + + @Test + void shouldThrowExceptionWhenBVNBankCodeIsEmpty() { + + BVNAccountMatchRequest request = + BVNAccountMatchRequest.builder() + .bankCode("") + .accountNumber("0123456789") + .bvn("22222222222") + .build(); + + assertThrows( + MonnifyValidationException.class, + () -> verificationService.bvnAccountNameMatch(request) + ); + } + + @Test + void shouldThrowExceptionWhenNINIsInvalid() { + + NINVerificationRequest request = + NINVerificationRequest.builder() + .nin("12345") + .build(); + + assertThrows( + MonnifyValidationException.class, + () -> verificationService.verifyNIN(request) + ); + } + + @Test + void shouldThrowExceptionWhenNINIsEmpty() { + + NINVerificationRequest request = + NINVerificationRequest.builder() + .nin("") + .build(); + + assertThrows( + MonnifyValidationException.class, + () -> verificationService.verifyNIN(request) + ); + } +} diff --git a/src/test/java/com/monnify/mocktests/WebhookServiceMockTest.java b/src/test/java/com/monnify/mocktests/WebhookServiceMockTest.java new file mode 100644 index 0000000..59f2622 --- /dev/null +++ b/src/test/java/com/monnify/mocktests/WebhookServiceMockTest.java @@ -0,0 +1,403 @@ +package com.monnify.mocktests; +import com.monnify.exceptions.MonnifyException; +import com.monnify.services.webhook.WebhookService; +import org.junit.jupiter.api.Test; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +public class WebhookServiceMockTest { + + private final WebhookService webhookService = new WebhookService(); + + @Test + void shouldCalculateHMAC512TransactionHash() throws Exception { + String data = "test-data"; + String merchantClientSecret = "test-secret"; + + String result = webhookService.calculateHMAC512TransactionHash( + data, + merchantClientSecret + ); + + assertNotNull(result); + assertEquals(128, result.length()); + } + + @Test + void shouldReturnTrueWhenSignatureIsValid() throws Exception { + String data = "test-data"; + String merchantClientSecret = "test-secret"; + + String signature = webhookService.calculateHMAC512TransactionHash( + data, + merchantClientSecret + ); + + boolean result = webhookService.isSignatureValid( + signature, + data, + merchantClientSecret + ); + + assertTrue(result); + } + + @Test + void shouldReturnFalseWhenSignatureIsInvalid() throws Exception { + String data = "test-data"; + String merchantClientSecret = "test-secret"; + + boolean result = webhookService.isSignatureValid( + "invalid-signature", + data, + merchantClientSecret + ); + + assertFalse(result); + } + + @Test + void shouldResolveSuccessfulTransactionWebhook() throws Exception { + Map webhookPayload = new HashMap<>(); + + webhookPayload.put("eventType", "SUCCESSFUL_TRANSACTION"); + webhookPayload.put("eventData", new HashMap()); + + String secret = "test-secret"; + + String payload = new com.google.gson.Gson().toJson(webhookPayload); + + String signature = webhookService.calculateHMAC512TransactionHash( + payload, + secret + ); + + Object result = webhookService.resolveMonnifyWebhook( + signature, + webhookPayload, + secret + ); + + assertNotNull(result); + assertEquals( + "SUCCESSFUL_TRANSACTION", + ((com.monnify.models.webhook.BaseWebhookResponse) result).getEventType() + ); + } + + @Test + void shouldResolveSuccessfulDisbursementWebhook() throws Exception { + Map webhookPayload = new HashMap<>(); + + webhookPayload.put("eventType", "SUCCESSFUL_DISBURSEMENT"); + webhookPayload.put("eventData", new HashMap()); + + String secret = "test-secret"; + + String payload = new com.google.gson.Gson().toJson(webhookPayload); + + String signature = webhookService.calculateHMAC512TransactionHash( + payload, + secret + ); + + Object result = webhookService.resolveMonnifyWebhook( + signature, + webhookPayload, + secret + ); + + assertNotNull(result); + assertEquals( + "SUCCESSFUL_DISBURSEMENT", + ((com.monnify.models.webhook.BaseWebhookResponse) result).getEventType() + ); + } + + @Test + void shouldResolveFailedDisbursementWebhook() throws Exception { + Map webhookPayload = new HashMap<>(); + + webhookPayload.put("eventType", "FAILED_DISBURSEMENT"); + webhookPayload.put("eventData", new HashMap()); + + String secret = "test-secret"; + + String payload = new com.google.gson.Gson().toJson(webhookPayload); + + String signature = webhookService.calculateHMAC512TransactionHash( + payload, + secret + ); + + Object result = webhookService.resolveMonnifyWebhook( + signature, + webhookPayload, + secret + ); + + assertNotNull(result); + assertEquals( + "FAILED_DISBURSEMENT", + ((com.monnify.models.webhook.BaseWebhookResponse) result).getEventType() + ); + } + + @Test + void shouldResolveReversedDisbursementWebhook() throws Exception { + Map webhookPayload = new HashMap<>(); + + webhookPayload.put("eventType", "REVERSED_DISBURSEMENT"); + webhookPayload.put("eventData", new HashMap()); + + String secret = "test-secret"; + + String payload = new com.google.gson.Gson().toJson(webhookPayload); + + String signature = webhookService.calculateHMAC512TransactionHash( + payload, + secret + ); + + Object result = webhookService.resolveMonnifyWebhook( + signature, + webhookPayload, + secret + ); + + assertNotNull(result); + assertEquals( + "REVERSED_DISBURSEMENT", + ((com.monnify.models.webhook.BaseWebhookResponse) result).getEventType() + ); + } + + @Test + void shouldResolveSuccessfulRefundWebhook() throws Exception { + Map webhookPayload = new HashMap<>(); + + webhookPayload.put("eventType", "SUCCESSFUL_REFUND"); + webhookPayload.put("eventData", new HashMap()); + + String secret = "test-secret"; + + String payload = new com.google.gson.Gson().toJson(webhookPayload); + + String signature = webhookService.calculateHMAC512TransactionHash( + payload, + secret + ); + + Object result = webhookService.resolveMonnifyWebhook( + signature, + webhookPayload, + secret + ); + + assertNotNull(result); + assertEquals( + "SUCCESSFUL_REFUND", + ((com.monnify.models.webhook.BaseWebhookResponse) result).getEventType() + ); + } + + @Test + void shouldResolveFailedRefundWebhook() throws Exception { + Map webhookPayload = new HashMap<>(); + + webhookPayload.put("eventType", "FAILED_REFUND"); + webhookPayload.put("eventData", new HashMap()); + + String secret = "test-secret"; + + String payload = new com.google.gson.Gson().toJson(webhookPayload); + + String signature = webhookService.calculateHMAC512TransactionHash( + payload, + secret + ); + + Object result = webhookService.resolveMonnifyWebhook( + signature, + webhookPayload, + secret + ); + + assertNotNull(result); + assertEquals( + "FAILED_REFUND", + ((com.monnify.models.webhook.BaseWebhookResponse) result).getEventType() + ); + } + + @Test + void shouldResolveSettlementWebhook() throws Exception { + Map webhookPayload = new HashMap<>(); + + webhookPayload.put("eventType", "SETTLEMENT"); + webhookPayload.put("eventData", new HashMap()); + + String secret = "test-secret"; + + String payload = new com.google.gson.Gson().toJson(webhookPayload); + + String signature = webhookService.calculateHMAC512TransactionHash( + payload, + secret + ); + + Object result = webhookService.resolveMonnifyWebhook( + signature, + webhookPayload, + secret + ); + + assertNotNull(result); + assertEquals( + "SETTLEMENT", + ((com.monnify.models.webhook.BaseWebhookResponse) result).getEventType() + ); + } + + @Test + void shouldResolveRejectedPaymentWebhook() throws Exception { + Map webhookPayload = new HashMap<>(); + + webhookPayload.put("eventType", "REJECTED_PAYMENT"); + webhookPayload.put("eventData", new HashMap()); + + String secret = "test-secret"; + + String payload = new com.google.gson.Gson().toJson(webhookPayload); + + String signature = webhookService.calculateHMAC512TransactionHash( + payload, + secret + ); + + Object result = webhookService.resolveMonnifyWebhook( + signature, + webhookPayload, + secret + ); + + assertNotNull(result); + assertEquals( + "REJECTED_PAYMENT", + ((com.monnify.models.webhook.BaseWebhookResponse) result).getEventType() + ); + } + + @Test + void shouldResolveMandateUpdateWebhook() throws Exception { + Map webhookPayload = new HashMap<>(); + + webhookPayload.put("eventType", "MANDATE_UPDATE"); + webhookPayload.put("eventData", new HashMap()); + + String secret = "test-secret"; + + String payload = new com.google.gson.Gson().toJson(webhookPayload); + + String signature = webhookService.calculateHMAC512TransactionHash( + payload, + secret + ); + + Object result = webhookService.resolveMonnifyWebhook( + signature, + webhookPayload, + secret + ); + + assertNotNull(result); + assertEquals( + "MANDATE_UPDATE", + ((com.monnify.models.webhook.BaseWebhookResponse) result).getEventType() + ); + } + + @Test + void shouldResolveAccountActivityWebhook() throws Exception { + Map webhookPayload = new HashMap<>(); + + webhookPayload.put("eventType", "ACCOUNT_ACTIVITY"); + webhookPayload.put("eventData", new HashMap()); + + String secret = "test-secret"; + + String payload = new com.google.gson.Gson().toJson(webhookPayload); + + String signature = webhookService.calculateHMAC512TransactionHash( + payload, + secret + ); + + Object result = webhookService.resolveMonnifyWebhook( + signature, + webhookPayload, + secret + ); + + assertNotNull(result); + assertEquals( + "ACCOUNT_ACTIVITY", + ((com.monnify.models.webhook.BaseWebhookResponse) result).getEventType() + ); + } + + @Test + void shouldThrowExceptionWhenWebhookSignatureIsInvalid() { + Map webhookPayload = new HashMap<>(); + + webhookPayload.put("eventType", "SETTLEMENT"); + webhookPayload.put("eventData", new HashMap()); + + String secret = "test-secret"; + + MonnifyException exception = assertThrows( + MonnifyException.class, + () -> webhookService.resolveMonnifyWebhook( + "invalid-signature", + webhookPayload, + secret + ) + ); + + assertEquals("Invalid signature received", exception.getMessage()); + } + + @Test + void shouldThrowExceptionForInvalidEventType() throws Exception { + Map webhookPayload = new HashMap<>(); + + webhookPayload.put("eventType", "INVALID_EVENT"); + webhookPayload.put("eventData", new HashMap()); + + String secret = "test-secret"; + + String payload = new com.google.gson.Gson().toJson(webhookPayload); + + String signature = webhookService.calculateHMAC512TransactionHash( + payload, + secret + ); + + MonnifyException exception = assertThrows( + MonnifyException.class, + () -> webhookService.resolveMonnifyWebhook( + signature, + webhookPayload, + secret + ) + ); + + assertEquals( + "Invalid event type: INVALID_EVENT", + exception.getMessage() + ); + } + +} diff --git a/src/test/java/com/monnify/services/TestEnvironment.java b/src/test/java/com/monnify/services/TestEnvironment.java new file mode 100644 index 0000000..2a250eb --- /dev/null +++ b/src/test/java/com/monnify/services/TestEnvironment.java @@ -0,0 +1,22 @@ +package com.monnify.services; + +import io.github.cdimascio.dotenv.Dotenv; + +public class TestEnvironment { + private static final Dotenv dotenv = Dotenv.configure() + .ignoreIfMissing() + .load(); + + public static String get(String key) { + String value = dotenv.get(key); + + if (value == null || value.isBlank()) { + throw new IllegalStateException( + "Environment variable '" + key + "' is not defined" + ); + } + + return value; + } + +} diff --git a/src/test/java/com/monnify/services/directdebit/DirectDebitServiceTest.java b/src/test/java/com/monnify/services/directdebit/DirectDebitServiceTest.java index f7f6f22..01a1288 100644 --- a/src/test/java/com/monnify/services/directdebit/DirectDebitServiceTest.java +++ b/src/test/java/com/monnify/services/directdebit/DirectDebitServiceTest.java @@ -1,6 +1,5 @@ package com.monnify.services.directdebit; -import com.google.gson.Gson; import com.monnify.Monnify; import com.monnify.models.MonnifyBaseResponse; import com.monnify.models.directdebit.MandateRequest; diff --git a/src/test/java/com/monnify/services/disbursement/DisbursementServiceTest.java b/src/test/java/com/monnify/services/disbursement/DisbursementServiceTest.java index 3801b22..82dc600 100644 --- a/src/test/java/com/monnify/services/disbursement/DisbursementServiceTest.java +++ b/src/test/java/com/monnify/services/disbursement/DisbursementServiceTest.java @@ -4,10 +4,10 @@ import com.monnify.models.MonnifyBaseResponse; import com.monnify.models.SearchResponse; import com.monnify.models.disbursement.*; +import com.monnify.services.TestEnvironment; import org.junit.jupiter.api.*; import java.math.BigDecimal; -import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.UUID; @@ -20,10 +20,10 @@ class DisbursementServiceTest { private final DisbursementService disbursementService = new DisbursementService(); @BeforeAll static void setUp() { - assertNotNull(System.getenv("apiKey")); - assertNotNull(System.getenv("secretKey")); - assertNotNull(System.getenv("contractCode")); - Monnify.initialize(System.getenv("apiKey"), System.getenv("secretKey")); + assertNotNull(TestEnvironment.get("apiKey")); + assertNotNull(TestEnvironment.get("secretKey")); + assertNotNull(TestEnvironment.get("contractCode")); + Monnify.initialize(TestEnvironment.get("apiKey"), TestEnvironment.get("secretKey")); } private static final String reference = UUID.randomUUID().toString(); @@ -35,10 +35,10 @@ void disburseSingle() { int randomNumber = new Random().nextInt(1000); MonnifyBaseResponse response = disbursementService.disburseSingle(SingleDisbursementRequest.builder() .amount(BigDecimal.valueOf(randomNumber)) - .sourceAccountNumber("7853297429") + .sourceAccountNumber("7216487825") .currency("NGN") - .destinationBankCode("044") - .destinationAccountNumber("0104430292") + .destinationBankCode("058") + .destinationAccountNumber("7062396366") .destinationAccountName("Tester Test") .reference(reference) .narration("Test Single trnx" + randomNumber) @@ -56,29 +56,29 @@ void disburseBatch() { .narration("test batch on sdk" + randomNumber) .notificationInterval(25) .onValidationFailure(OnValidationFailure.CONTINUE) - .sourceAccountNumber("7853297429") + .sourceAccountNumber("8144157072") .transactionList(List.of(DisbursementTransaction.builder() - .amount(BigDecimal.valueOf(1000)) + .amount(BigDecimal.valueOf(10)) .currency("NGN") .narration("batch dis" + randomNumber) .reference(UUID.randomUUID().toString()) - .destinationAccountNumber("0104430292") - .destinationBankCode("044") + .destinationAccountNumber("7062396366") + .destinationBankCode("305") .build() ,DisbursementTransaction.builder() - .amount(BigDecimal.valueOf(500)) + .amount(BigDecimal.valueOf(50)) .currency("NGN") .narration("batch dis" + randomNumber) .reference(UUID.randomUUID().toString()) - .destinationAccountNumber("0104430292") - .destinationBankCode("044") + .destinationAccountNumber("7062396366") + .destinationBankCode("305") .build(),DisbursementTransaction.builder() - .amount(BigDecimal.valueOf(250)) + .amount(BigDecimal.valueOf(25)) .currency("NGN") .narration("batch dis" + randomNumber) .reference(UUID.randomUUID().toString()) - .destinationAccountNumber("0104430292") - .destinationBankCode("044") + .destinationAccountNumber("7062396366") + .destinationBankCode("305") .build() )).build()); assertSuccess(response); diff --git a/src/test/java/com/monnify/services/invoice/InvoiceServiceTest.java b/src/test/java/com/monnify/services/invoice/InvoiceServiceTest.java index 2cce47a..112371f 100644 --- a/src/test/java/com/monnify/services/invoice/InvoiceServiceTest.java +++ b/src/test/java/com/monnify/services/invoice/InvoiceServiceTest.java @@ -1,7 +1,6 @@ package com.monnify.services.invoice; import com.google.gson.Gson; -import com.monnify.MainTest; import com.monnify.Monnify; import com.monnify.models.MonnifyBaseResponse; import com.monnify.models.SearchResponse; diff --git a/src/test/java/com/monnify/services/paycode/PaycodeServiceTest.java b/src/test/java/com/monnify/services/paycode/PaycodeServiceTest.java index da0cc2c..d7f2f5f 100644 --- a/src/test/java/com/monnify/services/paycode/PaycodeServiceTest.java +++ b/src/test/java/com/monnify/services/paycode/PaycodeServiceTest.java @@ -8,7 +8,6 @@ import org.junit.jupiter.api.*; import java.math.BigDecimal; -import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; diff --git a/src/test/java/com/monnify/services/settlement/SettlementServiceTest.java b/src/test/java/com/monnify/services/settlement/SettlementServiceTest.java index 16aba47..7024028 100644 --- a/src/test/java/com/monnify/services/settlement/SettlementServiceTest.java +++ b/src/test/java/com/monnify/services/settlement/SettlementServiceTest.java @@ -1,6 +1,5 @@ package com.monnify.services.settlement; -import com.google.gson.Gson; import com.monnify.Monnify; import com.monnify.models.MonnifyBaseResponse; import com.monnify.models.SearchResponse; diff --git a/src/test/java/com/monnify/services/webhook/WebhookServiceTest.java b/src/test/java/com/monnify/services/webhook/WebhookServiceTest.java index d208bcb..25b0e08 100644 --- a/src/test/java/com/monnify/services/webhook/WebhookServiceTest.java +++ b/src/test/java/com/monnify/services/webhook/WebhookServiceTest.java @@ -1,7 +1,5 @@ package com.monnify.services.webhook; -import static org.junit.jupiter.api.Assertions.*; - class WebhookServiceTest { } \ No newline at end of file From 344f5b5cb3f74eceb30dae51c3e0c7bbafb50659 Mon Sep 17 00:00:00 2001 From: Oluwayhemisi Date: Tue, 18 Aug 2026 19:38:35 +0100 Subject: [PATCH 6/8] Added environment variables --- .../com/monnify/services/TestEnvironment.java | 22 ------------------- .../disbursement/DisbursementServiceTest.java | 9 ++++---- 2 files changed, 4 insertions(+), 27 deletions(-) delete mode 100644 src/test/java/com/monnify/services/TestEnvironment.java diff --git a/src/test/java/com/monnify/services/TestEnvironment.java b/src/test/java/com/monnify/services/TestEnvironment.java deleted file mode 100644 index 2a250eb..0000000 --- a/src/test/java/com/monnify/services/TestEnvironment.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.monnify.services; - -import io.github.cdimascio.dotenv.Dotenv; - -public class TestEnvironment { - private static final Dotenv dotenv = Dotenv.configure() - .ignoreIfMissing() - .load(); - - public static String get(String key) { - String value = dotenv.get(key); - - if (value == null || value.isBlank()) { - throw new IllegalStateException( - "Environment variable '" + key + "' is not defined" - ); - } - - return value; - } - -} diff --git a/src/test/java/com/monnify/services/disbursement/DisbursementServiceTest.java b/src/test/java/com/monnify/services/disbursement/DisbursementServiceTest.java index 82dc600..c95326b 100644 --- a/src/test/java/com/monnify/services/disbursement/DisbursementServiceTest.java +++ b/src/test/java/com/monnify/services/disbursement/DisbursementServiceTest.java @@ -4,7 +4,6 @@ import com.monnify.models.MonnifyBaseResponse; import com.monnify.models.SearchResponse; import com.monnify.models.disbursement.*; -import com.monnify.services.TestEnvironment; import org.junit.jupiter.api.*; import java.math.BigDecimal; @@ -20,10 +19,10 @@ class DisbursementServiceTest { private final DisbursementService disbursementService = new DisbursementService(); @BeforeAll static void setUp() { - assertNotNull(TestEnvironment.get("apiKey")); - assertNotNull(TestEnvironment.get("secretKey")); - assertNotNull(TestEnvironment.get("contractCode")); - Monnify.initialize(TestEnvironment.get("apiKey"), TestEnvironment.get("secretKey")); + assertNotNull(System.getenv("apiKey")); + assertNotNull(System.getenv("secretKey")); + assertNotNull(System.getenv("contractCode")); + Monnify.initialize(System.getenv("apiKey"), System.getenv("secretKey")); } private static final String reference = UUID.randomUUID().toString(); From 1189e90b2dbd5e06b91528b1ba5933dd23f2fb68 Mon Sep 17 00:00:00 2001 From: Oluwayhemisi Date: Tue, 18 Aug 2026 20:16:44 +0100 Subject: [PATCH 7/8] Added environment variables --- src/main/java/com/monnify/Monnify.java | 2 +- .../java/com/monnify/BaseMonnifyMockTest.java | 45 ++++++++++--------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/monnify/Monnify.java b/src/main/java/com/monnify/Monnify.java index 7c7d7dd..ac3337f 100644 --- a/src/main/java/com/monnify/Monnify.java +++ b/src/main/java/com/monnify/Monnify.java @@ -75,7 +75,7 @@ public static String getBaseUrl() { * This method is package-private so it is not exposed as part * of the public SDK API. */ - static void setBaseUrlForTesting(String baseUrl) { + public static void setBaseUrlForTesting(String baseUrl) { if (!initialized) { throw new MonnifyException( "Monnify must be initialized before setting the test base URL." diff --git a/src/test/java/com/monnify/BaseMonnifyMockTest.java b/src/test/java/com/monnify/BaseMonnifyMockTest.java index f4270fe..1fc23d7 100644 --- a/src/test/java/com/monnify/BaseMonnifyMockTest.java +++ b/src/test/java/com/monnify/BaseMonnifyMockTest.java @@ -1,36 +1,39 @@ package com.monnify; + import com.github.tomakehurst.wiremock.WireMockServer; import com.google.gson.Gson; import com.monnify.services.auth.AuthService; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.post; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.reset; -import static com.github.tomakehurst.wiremock.client.WireMock.matching; -import static com.github.tomakehurst.wiremock.client.WireMock.configureFor; +import static com.github.tomakehurst.wiremock.client.WireMock.*; public abstract class BaseMonnifyMockTest { - protected final Gson gson = new Gson(); - protected static WireMockServer wireMockServer; protected static final String API_KEY = "MK_TEST_API_KEY"; protected static final String SECRET_KEY = "test-secret-key"; protected static final String ACCESS_TOKEN = "mock-access-token"; + private static volatile boolean initialized = false; + @BeforeAll - static void setUpWireMock() { + static synchronized void setUpWireMock() { + + if (initialized) { + return; + } + wireMockServer = new WireMockServer(8089); wireMockServer.start(); + configureFor("localhost", 8089); + Monnify.initialize(API_KEY, SECRET_KEY); Monnify.setBaseUrlForTesting("http://localhost:8089"); + + initialized = true; } @BeforeEach @@ -38,8 +41,8 @@ void resetWireMock() { reset(); AuthService.resetForTesting(); mockAuthentication(); - } + protected void mockAuthentication() { stubFor( @@ -48,7 +51,10 @@ protected void mockAuthentication() { .willReturn( aResponse() .withStatus(200) - .withHeader("Content-Type", "application/json") + .withHeader( + "Content-Type", + "application/json" + ) .withBody( "{" + "\"requestSuccessful\": true," @@ -58,14 +64,9 @@ protected void mockAuthentication() { + "\"accessToken\": \"mock-access-token\"," + "\"expiresIn\": 3600" + "}" - + "}"))); - } - - @AfterAll - static void tearDownWireMock() { - - if (wireMockServer != null) { - wireMockServer.stop(); - } + + "}" + ) + ) + ); } } \ No newline at end of file From 9c459c1fe41b233b15885702e8c49f1a23223411 Mon Sep 17 00:00:00 2001 From: Oluwayhemisi Date: Tue, 18 Aug 2026 20:47:43 +0100 Subject: [PATCH 8/8] Added environment variables --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9fbc1ca..93f1269 100644 --- a/pom.xml +++ b/pom.xml @@ -178,7 +178,7 @@ sign-artifacts - verify + deploy sign