Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Build and Test

on:
push:
branches:
- 'main'

pull_request:
branches:
- 'main'
- 'feature/**'

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
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
42 changes: 42 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -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 }}

66 changes: 50 additions & 16 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>3.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
Expand Down Expand Up @@ -101,22 +107,6 @@

<build>
<plugins>
<!-- GPG Signing Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
Expand Down Expand Up @@ -175,6 +165,50 @@
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.1.0</version>

<configuration>
<keyname>${gpg.keyname}</keyname>
<passphrase>${gpg.passphrase}</passphrase>
</configuration>

<executions>
<execution>
<id>sign-artifacts</id>
<phase>deploy</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>

<!--
Default test suite.

Only MockTest classes are executed during normal builds.

Run:
mvn clean verify

API integration tests are executed explicitly using:
mvn clean verify -Papi-tests
-->
<includes>
<include>**/*MockTest.java</include>
</includes>

</configuration>
</plugin>

</plugins>
</build>
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/monnify/Monnify.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
public 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) {
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/monnify/services/auth/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
72 changes: 72 additions & 0 deletions src/test/java/com/monnify/BaseMonnifyMockTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
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.BeforeAll;
import org.junit.jupiter.api.BeforeEach;

import static com.github.tomakehurst.wiremock.client.WireMock.*;

public abstract class BaseMonnifyMockTest {

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 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
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"
+ "}"
+ "}"
)
)
);
}
}
110 changes: 110 additions & 0 deletions src/test/java/com/monnify/mocktests/AuthServiceMockTest.java
Original file line number Diff line number Diff line change
@@ -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")));
}


}
Loading
Loading