FINERACT-2181: Use matrix jobs via Github Actions to improve performance

This commit is contained in:
Adam Saghy
2025-07-02 10:01:37 +02:00
committed by Arnold Gálovics
parent dc2101bc21
commit 70eb2add0d
28 changed files with 924 additions and 437 deletions
+111
View File
@@ -0,0 +1,111 @@
name: Fineract Build & Cucumber tests (without E2E tests)
on: [push, pull_request]
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
include:
- task: build-core
job_type: main
- task: cucumber
job_type: main
- task: build-progressive-loan
job_type: progressive-loan
env:
TZ: Asia/Kolkata
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'zulu'
- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Setup Gradle and Validate Wrapper
uses: gradle/actions/setup-gradle@v4.4.1
with:
validate-wrappers: true
- name: Run Gradle Task
if: matrix.job_type == 'main'
run: |
case "${{ matrix.task }}" in
build-core)
./gradlew --no-daemon build -x test -x cucumber -x doc
;;
cucumber)
./gradlew --no-daemon cucumber -x :fineract-e2e-tests-runner:cucumber -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x spotlessCheck -x spotlessApply -x spotbugsMain -x spotbugsTest -x javadoc -x javadocJar -x modernizer
;;
esac
- name: Build and Test Progressive Loan
if: matrix.job_type == 'progressive-loan'
run: |
# Build the JAR
./gradlew --no-daemon --console=plain :fineract-progressive-loan-embeddable-schedule-generator:shadowJar
# Store the JAR filename in an environment variable
EMBEDDABLE_JAR_FILE=$(ls fineract-progressive-loan-embeddable-schedule-generator/build/libs/*-all.jar | head -n 1)
echo "EMBEDDABLE_JAR_FILE=$EMBEDDABLE_JAR_FILE" >> $GITHUB_ENV
echo "JAR file: $EMBEDDABLE_JAR_FILE"
# Run unit tests
./gradlew --no-daemon --console=plain :fineract-progressive-loan-embeddable-schedule-generator:test
# Build and run sample application
mkdir -p sample-app
javac -cp "$EMBEDDABLE_JAR_FILE" -d sample-app fineract-progressive-loan-embeddable-schedule-generator/misc/Main.java
java -cp "$EMBEDDABLE_JAR_FILE:sample-app" Main
java -cp "$EMBEDDABLE_JAR_FILE:sample-app" Main 25
- name: Archive test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.task }}
path: |
**/build/reports/
**/fineract-progressive-loan-embeddable-schedule-generator/build/reports/
if-no-files-found: ignore
retention-days: 5
- name: Archive Progressive Loan JAR
if: matrix.job_type == 'progressive-loan' && always()
uses: actions/upload-artifact@v4
with:
name: progressive-loan-jar
path: ${{ env.EMBEDDABLE_JAR_FILE }}
retention-days: 5
if-no-files-found: ignore
- name: Archive server logs
if: always()
uses: actions/upload-artifact@v4
with:
name: server-logs-${{ matrix.task }}
path: '**/build/cargo/'
retention-days: 5
@@ -1,31 +0,0 @@
name: Fineract Docker build - PostgreSQL
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-24.04
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
IMAGE_NAME: fineract
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- name: Build the image
run: ./gradlew --no-daemon --console=plain :fineract-provider:jibDockerBuild -Djib.to.image=$IMAGE_NAME -x test -x cucumber
- name: Start the Standalone Stack
run: docker compose -f docker-compose-postgresql.yml up -d
- name: Check the stack
run: docker ps
- name: Check health
run: curl -f -k --retry 60 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health
- name: Check info
run: (( $(curl -f -k --retry 5 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/info | wc --chars) > 100 ))
@@ -1,31 +1,53 @@
name: Fineract Docker build - MariaDB
name: Fineract Docker Builds
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-24.04
strategy:
matrix:
db_type: [mariadb, postgresql]
include:
- db_type: mariadb
compose_file: docker-compose.yml
check_worker_health: true
- db_type: postgresql
compose_file: docker-compose-postgresql.yml
check_worker_health: false
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
IMAGE_NAME: fineract
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- name: Build the image
run: ./gradlew --no-daemon --console=plain :fineract-provider:jibDockerBuild -Djib.to.image=$IMAGE_NAME -x test -x cucumber
- name: Start the stack
run: docker compose -f docker-compose.yml up -d
- name: Start the ${{ matrix.db_type }} stack
run: docker compose -f ${{ matrix.compose_file }} up -d
- name: Check the stack
run: docker ps
- name: Check health
- name: Check health Manager
run: curl -f -k --retry 60 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health
- name: Check info
- name: Check info Manager
run: (( $(curl -f -k --retry 5 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/info | wc --chars) > 100 ))
+165
View File
@@ -0,0 +1,165 @@
name: Fineract E2E Tests
on: [push, pull_request]
permissions:
contents: read
jobs:
test:
name: E2E Tests (Shard ${{ matrix.shard_index }} of ${{ matrix.total_shards }})
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
# Define the number of shards (1-based indexing)
shard_index: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
total_shards: [10]
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
IMAGE_NAME: fineract
BASE_URL: https://localhost:8443
TEST_USERNAME: mifos
TEST_PASSWORD: password
TEST_STRONG_PASSWORD: A1b2c3d4e5f$
TEST_TENANT_ID: default
INITIALIZATION_ENABLED: true
EVENT_VERIFICATION_ENABLED: true
ACTIVEMQ_BROKER_URL: tcp://localhost:61616
ACTIVEMQ_TOPIC_NAME: events
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4.4.1
- name: Make scripts executable
run: chmod +x scripts/split-features.sh
- name: Split feature files into shards
id: split-features
run: |
./scripts/split-features.sh ${{ matrix.total_shards }} ${{ matrix.shard_index }}
echo "Shard ${{ matrix.shard_index }} feature files:"
cat feature_shard_${{ matrix.shard_index }}.txt
- name: Build the image
run: ./gradlew --no-daemon --console=plain :fineract-provider:jibDockerBuild -Djib.to.image=$IMAGE_NAME -x test -x cucumber
- name: Start the Fineract stack
run: docker compose -f docker-compose-postgresql-test-activemq.yml up -d
- name: Check the stack
run: docker ps
- name: Wait for Manager to be ready
run: |
# Wait for the container to be running
echo "Waiting for Manager container to be ready..."
timeout 300 bash -c 'until docker ps --filter "health=healthy" --filter "name=fineract" --format "{{.Status}}" | grep -q "healthy"; do
if docker ps --filter "name=fineract" --format "{{.Status}}" | grep -q "unhealthy"; then
echo "Container is unhealthy. Stopping..."
docker ps -a
docker logs $(docker ps -q --filter name=fineract) || true
exit 1
fi
echo "Waiting for Manager to be ready..."
sleep 5
done'
# Check the health endpoint
echo "Checking Manager health endpoint..."
curl -f -k --retry 30 --retry-all-errors --connect-timeout 10 --retry-delay 10 \
https://localhost:8443/fineract-provider/actuator/health
- name: Execute tests for shard ${{ matrix.shard_index }}
run: |
# Read feature files from the shard file
FEATURES=$(tr '\n' ',' < "feature_shard_${{ matrix.shard_index }}.txt" | sed 's/,$//')
if [ -z "$FEATURES" ]; then
echo "No features to test in this shard. Skipping..."
exit 0
fi
# Create a directory for individual allure results
mkdir -p "allure-results-shard-${{ matrix.shard_index }}"
# Read each feature file path and run tests one by one
while IFS= read -r feature_file || [ -n "$feature_file" ]; do
# Skip empty lines
[ -z "$feature_file" ] && continue
# Create a safe filename for the results
safe_name=$(echo "$feature_file" | tr '/' '-' | tr ' ' '_')
echo "Testing feature: $feature_file"
# Run tests with individual allure results directory
./gradlew --no-daemon --console=plain \
:fineract-e2e-tests-runner:cucumber \
-Pcucumber.features="$feature_file" \
-Dallure.results.directory="allure-results-shard-${{ matrix.shard_index }}/$safe_name" \
allureReport || echo "Test failed for $feature_file, continuing with next feature..."
# Copy the results to a merged directory
if [ -d "allure-results-shard-${{ matrix.shard_index }}/$safe_name" ]; then
cp -r "allure-results-shard-${{ matrix.shard_index }}/$safe_name/." "allure-results-merged/" || true
fi
done < "feature_shard_${{ matrix.shard_index }}.txt"
# Generate individual report for this shard
if [ -d "allure-results-merged" ]; then
mkdir -p "allure-report-shard-${{ matrix.shard_index }}"
allure generate "allure-results-merged" --clean -o "allure-report-shard-${{ matrix.shard_index }}" || echo "Failed to generate Allure report for shard ${{ matrix.shard_index }}"
fi
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: allure-results-shard-${{ matrix.shard_index }}
path: |
allure-results-shard-${{ matrix.shard_index }}
allure-results-merged
**/build/allure-results
**/build/reports/tests/test
**/build/test-results/test
retention-days: 5
- name: Upload Allure Report
if: always()
uses: actions/upload-artifact@v4
with:
name: allure-report-shard-${{ matrix.shard_index }}
path: allure-report-shard-${{ matrix.shard_index }}
retention-days: 5
- name: Upload logs
if: always()
uses: actions/upload-artifact@v4
with:
name: logs-shard-${{ matrix.shard_index }}
path: |
**/build/reports/tests/
**/logs/
**/out/
retention-days: 5
- name: Clean up
if: always()
run: |
docker compose -f docker-compose-postgresql-test-activemq.yml down -v
docker system prune -f
@@ -1,47 +0,0 @@
name: Fineract Build Progressive Loan Embeddable Jar & Test with a Sample Application
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- name: Build Embeddable Jar
run: ./gradlew --no-daemon --console=plain :fineract-progressive-loan-embeddable-schedule-generator:shadowJar
- name: Pick up the JAR filename
run: |
EMBEDDABLE_JAR_FILE=(`ls fineract-progressive-loan-embeddable-schedule-generator/build/libs/*-all.jar | head -n 1`)
echo "EMBEDDABLE_JAR_FILE=$EMBEDDABLE_JAR_FILE" >> $GITHUB_ENV
- name: Run unit tests
run: ./gradlew --no-daemon --console=plain :fineract-progressive-loan-embeddable-schedule-generator:test
- name: Build Sample Application
run: |
mkdir sample-app
javac -cp $EMBEDDABLE_JAR_FILE -d sample-app fineract-progressive-loan-embeddable-schedule-generator/misc/Main.java
env:
EMBEDDABLE_JAR_FILE: ${{ env.EMBEDDABLE_JAR_FILE }}
- name: Run Schedule Generator Sample Application
run: |
java -cp $EMBEDDABLE_JAR_FILE:sample-app Main
java -cp $EMBEDDABLE_JAR_FILE:sample-app Main 25
env:
EMBEDDABLE_JAR_FILE: ${{ env.EMBEDDABLE_JAR_FILE }}
- name: Archive test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: test-results
path: |
build/reports/
fineract-progressive-loan-embeddable-schedule-generator/build/reports/
+108 -58
View File
@@ -1,92 +1,142 @@
name: Fineract Build & Test - MariaDB
name: Fineract Cargo & Unit- & Integration tests - MariaDB
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
test:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
task: [test-core-1, test-core-2, test-core-3, test-core-4, test-core-5, test-twofactor, test-oauth2]
services:
mariad:
image: mariadb:11.5.2
ports:
- 3306:3306
env:
MARIADB_ROOT_PASSWORD: mysql
options: --health-cmd="healthcheck.sh --su-mysql --connect --innodb_initialized" --health-interval=5s --health-timeout=2s --health-retries=3
mock-oauth2-server:
image: ghcr.io/navikt/mock-oauth2-server:2.1.10
ports:
- 9000:9000
env:
SERVER_PORT: 9000
JSON_CONFIG: '{ "interactiveLogin": true, "httpServer": "NettyWrapper", "tokenCallbacks": [ { "issuerId": "auth/realms/fineract", "tokenExpiry": 120, "requestMappings": [{ "requestParam": "scope", "match": "fineract", "claims": { "sub": "mifos", "scope": [ "test" ] } } ] } ] }'
mariadb:
image: mariadb:11.5.2
ports:
- 3306:3306
env:
MARIADB_ROOT_PASSWORD: mysql
options: --health-cmd="healthcheck.sh --su-mysql --connect --innodb_initialized" --health-interval=5s --health-timeout=2s --health-retries=3
mock-oauth2-server:
image: ghcr.io/navikt/mock-oauth2-server:2.1.10
ports:
- 9000:9000
env:
SERVER_PORT: 9000
JSON_CONFIG: '{ "interactiveLogin": true, "httpServer": "NettyWrapper", "tokenCallbacks": [ { "issuerId": "auth/realms/fineract", "tokenExpiry": 120, "requestMappings": [{ "requestParam": "scope", "match": "fineract", "claims": { "sub": "mifos", "scope": [ "test" ] } } ] } ] }'
env:
TZ: Asia/Kolkata
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
TZ: Asia/Kolkata
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
AWS_ENDPOINT_URL: http://localhost:4566
AWS_ACCESS_KEY_ID: localstack
AWS_SECRET_ACCESS_KEY: localstack
AWS_REGION: us-east-1
FINERACT_REPORT_EXPORT_S3_ENABLED: true
FINERACT_REPORT_EXPORT_S3_BUCKET_NAME: fineract-reports
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Set up JDK 21
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'zulu'
- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Setup Gradle and Validate Wrapper
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@v4.4.1
with:
validate-wrappers: true
- name: Verify MariaDB connection
run: |
while ! mysqladmin ping -h"127.0.0.1" -P3306 ; do
sleep 1
done
while ! mysqladmin ping -h"127.0.0.1" -P3306 ; do
sleep 1
done
- name: Initialise databases
run: |
./gradlew --no-daemon -q createDB -PdbName=fineract_tenants
./gradlew --no-daemon -q createDB -PdbName=fineract_default
./gradlew --no-daemon -q createDB -PdbName=fineract_tenants
./gradlew --no-daemon -q createDB -PdbName=fineract_default
- name: Start LocalStack
env:
AWS_ENDPOINT_URL: http://localhost:4566
AWS_ACCESS_KEY_ID: localstack
AWS_SECRET_ACCESS_KEY: localstack
AWS_REGION: us-east-1
run: |
docker run -d --name localstack -p 4566:4566 -p 4510-4559:4510-4559 localstack/localstack:2.1
sleep 10
docker exec localstack awslocal s3api create-bucket --bucket fineract-reports
echo "LocalStack initialization complete"
- name: Build & Test
env:
AWS_ENDPOINT_URL: http://localhost:4566
AWS_ACCESS_KEY_ID: localstack
AWS_SECRET_ACCESS_KEY: localstack
AWS_REGION: us-east-1
FINERACT_REPORT_EXPORT_S3_ENABLED: true
FINERACT_REPORT_EXPORT_S3_BUCKET_NAME: fineract-reports
- name: Generate test class list (only for test-core-X)
if: startsWith(matrix.task, 'test-core-')
run: |
./gradlew --no-daemon --console=plain build -x cucumber -x test -x doc
./gradlew --no-daemon --console=plain cucumber -x :fineract-e2e-tests-runner:cucumber
./gradlew --no-daemon --console=plain test -x :twofactor-tests:test -x :oauth2-test:test -x :fineract-e2e-tests-runner:test
./gradlew --no-daemon --console=plain :twofactor-tests:test
./gradlew --no-daemon --console=plain :oauth2-tests:test
chmod +x scripts/split-tests.sh
SHARD_INDEX=$(echo "${{ matrix.task }}" | awk -F'-' '{print $3}')
./scripts/split-tests.sh 5 $SHARD_INDEX
cat "shard-tests_${SHARD_INDEX}.txt"
- name: Run Gradle Task
run: |
SHARD_INDEX=$(echo "${{ matrix.task }}" | awk -F'-' '{print $3}')
case "${{ matrix.task }}" in
test-twofactor)
./gradlew --no-daemon :twofactor-tests:test -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x spotlessCheck -x spotlessApply -x spotbugsMain -x spotbugsTest -x javadoc -x javadocJar -x modernizer
;;
test-oauth2)
./gradlew --no-daemon :oauth2-tests:test -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x spotlessCheck -x spotlessApply -x spotbugsMain -x spotbugsTest -x javadoc -x javadocJar -x modernizer
;;
test-core-*)
echo "Grouping test classes by module..."
declare -A module_tests
while IFS=, read -r module class; do
module_tests["$module"]+="$class "
done < "shard-tests_${SHARD_INDEX}.txt"
for module in "${!module_tests[@]}"; do
echo "Running tests in $module:"
for class in ${module_tests[$module]}; do
echo " - $class"
done
# Build test args
test_args=$(for class in ${module_tests[$module]}; do echo --tests "$class"; done | xargs)
# Run test task for this module
./gradlew "$module:test" $test_args -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x spotlessCheck -x spotlessApply -x spotbugsMain -x spotbugsTest -x javadoc -x javadocJar -x modernizer
done
;;
esac
- name: Archive test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@v4
with:
name: test-results
path: |
build/reports/
integration-tests/build/reports/
twofactor-tests/build/reports/
oauth2-tests/build/reports/
name: test-results-${{ matrix.task }}
path: '**/build/reports/'
retention-days: 5
- name: Archive server logs
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@v4
with:
name: server-logs
path: |
integration-tests/build/cargo/
twofactor-tests/build/cargo/
oauth2-tests/build/cargo/
name: server-logs-${{ matrix.task }}
path: '**/build/cargo/'
retention-days: 5
+109 -59
View File
@@ -1,92 +1,142 @@
name: Fineract Build & Test - MySQL
name: Fineract Cargo & Unit- & Integration tests - MySQL
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
test:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
task: [test-core-1, test-core-2, test-core-3, test-core-4, test-core-5, test-twofactor, test-oauth2]
services:
mariad:
image: mysql:9.1
ports:
- 3306:3306
env:
MYSQL_ROOT_PASSWORD: mysql
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
mock-oauth2-server:
image: ghcr.io/navikt/mock-oauth2-server:2.1.10
ports:
- 9000:9000
env:
SERVER_PORT: 9000
JSON_CONFIG: '{ "interactiveLogin": true, "httpServer": "NettyWrapper", "tokenCallbacks": [ { "issuerId": "auth/realms/fineract", "tokenExpiry": 120, "requestMappings": [{ "requestParam": "scope", "match": "fineract", "claims": { "sub": "mifos", "scope": [ "test" ] } } ] } ] }'
mysql:
image: mysql:9.1
ports:
- 3306:3306
env:
MYSQL_ROOT_PASSWORD: mysql
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
mock-oauth2-server:
image: ghcr.io/navikt/mock-oauth2-server:2.1.10
ports:
- 9000:9000
env:
SERVER_PORT: 9000
JSON_CONFIG: '{ "interactiveLogin": true, "httpServer": "NettyWrapper", "tokenCallbacks": [ { "issuerId": "auth/realms/fineract", "tokenExpiry": 120, "requestMappings": [{ "requestParam": "scope", "match": "fineract", "claims": { "sub": "mifos", "scope": [ "test" ] } } ] } ] }'
env:
TZ: Asia/Kolkata
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
TZ: Asia/Kolkata
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
AWS_ENDPOINT_URL: http://localhost:4566
AWS_ACCESS_KEY_ID: localstack
AWS_SECRET_ACCESS_KEY: localstack
AWS_REGION: us-east-1
FINERACT_REPORT_EXPORT_S3_ENABLED: true
FINERACT_REPORT_EXPORT_S3_BUCKET_NAME: fineract-reports
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Set up JDK 21
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'zulu'
- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Setup Gradle and Validate Wrapper
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@v4.4.1
with:
validate-wrappers: true
- name: Verify MariaDB connection
- name: Verify MySQL connection
run: |
while ! mysqladmin ping -h"127.0.0.1" -P3306 ; do
sleep 1
done
while ! mysqladmin ping -h"127.0.0.1" -P3306 ; do
sleep 1
done
- name: Initialise databases
run: |
./gradlew --no-daemon -q createMySQLDB -PdbName=fineract_tenants
./gradlew --no-daemon -q createMySQLDB -PdbName=fineract_default
./gradlew --no-daemon -q createMySQLDB -PdbName=fineract_tenants
./gradlew --no-daemon -q createMySQLDB -PdbName=fineract_default
- name: Start LocalStack
env:
AWS_ENDPOINT_URL: http://localhost:4566
AWS_ACCESS_KEY_ID: localstack
AWS_SECRET_ACCESS_KEY: localstack
AWS_REGION: us-east-1
run: |
docker run -d --name localstack -p 4566:4566 -p 4510-4559:4510-4559 localstack/localstack:2.1
sleep 10
docker exec localstack awslocal s3api create-bucket --bucket fineract-reports
echo "LocalStack initialization complete"
- name: Build & Test
env:
AWS_ENDPOINT_URL: http://localhost:4566
AWS_ACCESS_KEY_ID: localstack
AWS_SECRET_ACCESS_KEY: localstack
AWS_REGION: us-east-1
FINERACT_REPORT_EXPORT_S3_ENABLED: true
FINERACT_REPORT_EXPORT_S3_BUCKET_NAME: fineract-reports
- name: Generate test class list (only for test-core-X)
if: startsWith(matrix.task, 'test-core-')
run: |
./gradlew --no-daemon --console=plain build -x cucumber -x test -x doc
./gradlew --no-daemon --console=plain cucumber -x :fineract-e2e-tests-runner:cucumber
./gradlew --no-daemon --console=plain test -x :twofactor-tests:test -x :oauth2-test:test -x :fineract-e2e-tests-runner:test -PdbType=mysql
./gradlew --no-daemon --console=plain :twofactor-tests:test -PdbType=mysql
./gradlew --no-daemon --console=plain :oauth2-tests:test -PdbType=mysql
chmod +x scripts/split-tests.sh
SHARD_INDEX=$(echo "${{ matrix.task }}" | awk -F'-' '{print $3}')
./scripts/split-tests.sh 5 $SHARD_INDEX
cat "shard-tests_${SHARD_INDEX}.txt"
- name: Run Gradle Task
run: |
SHARD_INDEX=$(echo "${{ matrix.task }}" | awk -F'-' '{print $3}')
case "${{ matrix.task }}" in
test-twofactor)
./gradlew --no-daemon :twofactor-tests:test -PdbType=mysql -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x spotlessCheck -x spotlessApply -x spotbugsMain -x spotbugsTest -x javadoc -x javadocJar -x modernizer
;;
test-oauth2)
./gradlew --no-daemon :oauth2-tests:test -PdbType=mysql -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x spotlessCheck -x spotlessApply -x spotbugsMain -x spotbugsTest -x javadoc -x javadocJar -x modernizer
;;
test-core-*)
echo "Grouping test classes by module..."
declare -A module_tests
while IFS=, read -r module class; do
module_tests["$module"]+="$class "
done < "shard-tests_${SHARD_INDEX}.txt"
for module in "${!module_tests[@]}"; do
echo "Running tests in $module:"
for class in ${module_tests[$module]}; do
echo " - $class"
done
# Build test args
test_args=$(for class in ${module_tests[$module]}; do echo --tests "$class"; done | xargs)
# Run test task for this module
./gradlew "$module:test" $test_args -PdbType=mysql -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x spotlessCheck -x spotlessApply -x spotbugsMain -x spotbugsTest -x javadoc -x javadocJar -x modernizer
done
;;
esac
- name: Archive test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@v4
with:
name: test-results
path: |
build/reports/
integration-tests/build/reports/
twofactor-tests/build/reports/
oauth2-tests/build/reports/
name: test-results-${{ matrix.task }}
path: '**/build/reports/'
retention-days: 5
- name: Archive server logs
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@v4
with:
name: server-logs
path: |
integration-tests/build/cargo/
twofactor-tests/build/cargo/
oauth2-tests/build/cargo/
name: server-logs-${{ matrix.task }}
path: '**/build/cargo/'
retention-days: 5
+109 -59
View File
@@ -1,93 +1,143 @@
name: Fineract Build & Test - PostgreSQL
name: Fineract Cargo & Unit- & Integration tests - PostgreSQL
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
test:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
task: [test-core-1, test-core-2, test-core-3, test-core-4, test-core-5, test-twofactor, test-oauth2]
services:
postgresql:
image: postgres:17.4
ports:
- 5432:5432
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: postgres
options: --health-cmd="pg_isready -q -d postgres -U root" --health-interval=5s --health-timeout=2s --health-retries=3
mock-oauth2-server:
image: ghcr.io/navikt/mock-oauth2-server:2.1.10
ports:
- 9000:9000
env:
SERVER_PORT: 9000
JSON_CONFIG: '{ "interactiveLogin": true, "httpServer": "NettyWrapper", "tokenCallbacks": [ { "issuerId": "auth/realms/fineract", "tokenExpiry": 120, "requestMappings": [{ "requestParam": "scope", "match": "fineract", "claims": { "sub": "mifos", "scope": [ "test" ] } } ] } ] }'
postgresql:
image: postgres:17.4
ports:
- 5432:5432
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: postgres
options: --health-cmd="pg_isready -q -d postgres -U root" --health-interval=5s --health-timeout=2s --health-retries=3
mock-oauth2-server:
image: ghcr.io/navikt/mock-oauth2-server:2.1.10
ports:
- 9000:9000
env:
SERVER_PORT: 9000
JSON_CONFIG: '{ "interactiveLogin": true, "httpServer": "NettyWrapper", "tokenCallbacks": [ { "issuerId": "auth/realms/fineract", "tokenExpiry": 120, "requestMappings": [{ "requestParam": "scope", "match": "fineract", "claims": { "sub": "mifos", "scope": [ "test" ] } } ] } ] }'
env:
TZ: Asia/Kolkata
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
TZ: Asia/Kolkata
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
AWS_ENDPOINT_URL: http://localhost:4566
AWS_ACCESS_KEY_ID: localstack
AWS_SECRET_ACCESS_KEY: localstack
AWS_REGION: us-east-1
FINERACT_REPORT_EXPORT_S3_ENABLED: true
FINERACT_REPORT_EXPORT_S3_BUCKET_NAME: fineract-reports
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Set up JDK 21
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'zulu'
- name: Cache Gradle dependencies
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Setup Gradle and Validate Wrapper
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@v4.4.1
with:
validate-wrappers: true
- name: Verify PostgreSQL connection
run: |
while ! pg_isready -d postgres -U root -h 127.0.0.1 -p 5432 ; do
sleep 1
done
while ! pg_isready -d postgres -U root -h 127.0.0.1 -p 5432 ; do
sleep 1
done
- name: Initialise databases
run: |
./gradlew --no-daemon -q createPGDB -PdbName=fineract_tenants
./gradlew --no-daemon -q createPGDB -PdbName=fineract_default
./gradlew --no-daemon -q createPGDB -PdbName=fineract_tenants
./gradlew --no-daemon -q createPGDB -PdbName=fineract_default
- name: Start LocalStack
env:
AWS_ENDPOINT_URL: http://localhost:4566
AWS_ACCESS_KEY_ID: localstack
AWS_SECRET_ACCESS_KEY: localstack
AWS_REGION: us-east-1
run: |
docker run -d --name localstack -p 4566:4566 -p 4510-4559:4510-4559 localstack/localstack:2.1
sleep 10
docker exec localstack awslocal s3api create-bucket --bucket fineract-reports
echo "LocalStack initialization complete"
- name: Build & Test
env:
AWS_ENDPOINT_URL: http://localhost:4566
AWS_ACCESS_KEY_ID: localstack
AWS_SECRET_ACCESS_KEY: localstack
AWS_REGION: us-east-1
FINERACT_REPORT_EXPORT_S3_ENABLED: true
FINERACT_REPORT_EXPORT_S3_BUCKET_NAME: fineract-reports
- name: Generate test class list (only for test-core-X)
if: startsWith(matrix.task, 'test-core-')
run: |
./gradlew --no-daemon --console=plain build -x cucumber -x test -x doc
./gradlew --no-daemon --console=plain cucumber -x :fineract-e2e-tests-runner:cucumber
./gradlew --no-daemon --console=plain test -x :twofactor-tests:test -x :oauth2-test:test -x :fineract-e2e-tests-runner:test -PdbType=postgresql
./gradlew --no-daemon --console=plain :twofactor-tests:test -PdbType=postgresql
./gradlew --no-daemon --console=plain :oauth2-tests:test -PdbType=postgresql
chmod +x scripts/split-tests.sh
SHARD_INDEX=$(echo "${{ matrix.task }}" | awk -F'-' '{print $3}')
./scripts/split-tests.sh 5 $SHARD_INDEX
cat "shard-tests_${SHARD_INDEX}.txt"
- name: Run Gradle Task
run: |
SHARD_INDEX=$(echo "${{ matrix.task }}" | awk -F'-' '{print $3}')
case "${{ matrix.task }}" in
test-twofactor)
./gradlew --no-daemon :twofactor-tests:test -PdbType=postgresql -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x spotlessCheck -x spotlessApply -x spotbugsMain -x spotbugsTest -x javadoc -x javadocJar -x modernizer
;;
test-oauth2)
./gradlew --no-daemon :oauth2-tests:test -PdbType=postgresql -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x spotlessCheck -x spotlessApply -x spotbugsMain -x spotbugsTest -x javadoc -x javadocJar -x modernizer
;;
test-core-*)
echo "Grouping test classes by module..."
declare -A module_tests
while IFS=, read -r module class; do
module_tests["$module"]+="$class "
done < "shard-tests_${SHARD_INDEX}.txt"
for module in "${!module_tests[@]}"; do
echo "Running tests in $module:"
for class in ${module_tests[$module]}; do
echo " - $class"
done
# Build test args
test_args=$(for class in ${module_tests[$module]}; do echo --tests "$class"; done | xargs)
# Run test task for this module
./gradlew "$module:test" $test_args -PdbType=postgresql -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x spotlessCheck -x spotlessApply -x spotbugsMain -x spotbugsTest -x javadoc -x javadocJar -x modernizer
done
;;
esac
- name: Archive test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@v4
with:
name: test-results
path: |
build/reports/
integration-tests/build/reports/
twofactor-tests/build/reports/
oauth2-tests/build/reports/
name: test-results-${{ matrix.task }}
path: '**/build/reports/'
retention-days: 5
- name: Archive server logs
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
uses: actions/upload-artifact@v4
with:
name: server-logs
path: |
integration-tests/build/cargo/
twofactor-tests/build/cargo/
oauth2-tests/build/cargo/
name: server-logs-${{ matrix.task }}
path: '**/build/cargo/'
retention-days: 5
-41
View File
@@ -1,41 +0,0 @@
name: Fineract Tests
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-24.04
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
IMAGE_NAME: fineract
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- name: Build the image
run: ./gradlew --no-daemon --console=plain :fineract-provider:jibDockerBuild -Djib.to.image=$IMAGE_NAME -x test -x cucumber
- name: Start the Fineract stack
run: docker compose -f docker-compose-postgresql-test-activemq.yml up -d
- name: Check the stack
run: docker ps
- name: Check health Manager
run: curl -f -k --retry 60 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health
- name: Execute tests
env:
BASE_URL: https://localhost:8443
TEST_USERNAME: mifos
TEST_PASSWORD: password
TEST_STRONG_PASSWORD: A1b2c3d4e5f$
TEST_TENANT_ID: default
INITIALIZATION_ENABLED: true
EVENT_VERIFICATION_ENABLED: true
ACTIVEMQ_BROKER_URL: tcp://localhost:61616
ACTIVEMQ_TOPIC_NAME: events
run: ./gradlew --no-daemon --console=plain :fineract-e2e-tests-runner:cucumber --tags 'not @Skip' allureReport
-37
View File
@@ -1,37 +0,0 @@
name: Fineract Kafka Smoke
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-24.04
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
IMAGE_NAME: fineract
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- name: Build the image
run: ./gradlew --no-daemon --console=plain :fineract-provider:jibDockerBuild -Djib.to.image=$IMAGE_NAME -x test -x cucumber
- name: Start the Kafka Stack
run: docker compose -f docker-compose-postgresql-kafka.yml up --scale fineract-worker=1 -d
- name: Check the stack
run: docker ps
- name: Check health Manager
run: curl -f -k --retry 60 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health
- name: Check health Worker1
run: curl -f -k --retry 60 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8444/fineract-provider/actuator/health
- name: Check info Manager
run: (( $(curl -f -k --retry 5 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/info | wc --chars) > 100 ))
- name: Check info Worker1
run: (( $(curl -f -k --retry 5 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8444/fineract-provider/actuator/info | wc --chars) > 100 ))
- name: Run Smoke Test with Remote COB
run: ./gradlew --no-daemon --console=plain :integration-tests:cleanTest :integration-tests:test --tests "org.apache.fineract.integrationtests.investor.externalassetowner.InitiateExternalAssetOwnerTransferTest.saleActiveLoanToExternalAssetOwnerAndBuybackADayLater" -PcargoDisabled
@@ -1,37 +1,61 @@
name: Fineract ActiveMQ Smoke
name: Fineract Messaging Smoke Tests
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
smoke-test:
name: Smoke Test with ${{ matrix.messaging }}
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
include:
- messaging: ActiveMQ
compose_file: docker-compose-postgresql-activemq.yml
- messaging: Kafka
compose_file: docker-compose-postgresql-kafka.yml
env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
IMAGE_NAME: fineract
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4
with:
java-version: '21'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- name: Build the image
run: ./gradlew --no-daemon --console=plain :fineract-provider:jibDockerBuild -Djib.to.image=$IMAGE_NAME -x test -x cucumber
- name: Start the ActiveMQ Stack
run: docker compose -f docker-compose-postgresql-activemq.yml up --scale fineract-worker=1 -d
- name: Start the ${{ matrix.messaging }} Stack
run: docker compose -f ${{ matrix.compose_file }} up --scale fineract-worker=1 -d
- name: Check the stack
run: docker ps
- name: Check health Manager
run: curl -f -k --retry 60 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health
- name: Check health Worker1
run: curl -f -k --retry 60 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8444/fineract-provider/actuator/health
- name: Check info Manager
run: (( $(curl -f -k --retry 5 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/info | wc --chars) > 100 ))
- name: Check info Worker1
run: (( $(curl -f -k --retry 5 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8444/fineract-provider/actuator/info | wc --chars) > 100 ))
- name: Run Smoke Test with Remote COB
run: ./gradlew --no-daemon --console=plain :integration-tests:cleanTest :integration-tests:test --tests "org.apache.fineract.integrationtests.investor.externalassetowner.InitiateExternalAssetOwnerTransferTest.saleActiveLoanToExternalAssetOwnerAndBuybackADayLater" -PcargoDisabled
+2 -1
View File
@@ -341,7 +341,8 @@ allprojects {
"**/generated/**/*MapperImpl.java",
'**/META-INF/fineract-test.config',
// Apache-specific GitHub metadata settings file
'/.asf.yaml'
'/.asf.yaml',
'**/*.sh'
]
}
}
@@ -33,8 +33,11 @@ public interface OfficeDataMapper {
if (hierarchy == null) {
nameDecorated = "";
} else {
nameDecorated = hierarchy.substring(0, (hierarchy.length() - hierarchy.replace(".", "").length() - 1) * 4)
+ Optional.ofNullable(office.getName()).orElse("");
long count = hierarchy.chars().filter(c -> c == '.').count();
if (count > 0) {
count--;
}
nameDecorated = "....".repeat((int) count) + Optional.ofNullable(office.getName()).orElse("");
}
return new OfficeData(office.getId(), office.getName(), nameDecorated, office.getExternalId(), office.getOpeningDate(),
office.getHierarchy(), office.getParent() != null ? office.getParent().getId() : null,
@@ -71,7 +71,7 @@ public class JobService {
.pollInterval(Duration.ofMillis(jobPollingProperties.getIntervalInMillis())) //
.pollDelay(Duration.ofMillis(jobPollingProperties.getDelayInMillis())) //
.until(() -> {
log.info("Waiting for job {} to finish", jobName);
log.debug("Waiting for job {} to finish", jobName);
Long jobId = jobResolver.resolve(job);
Response<GetJobsResponse> getJobsResponse = schedulerJobApi.retrieveOne5(jobId).execute();
ErrorHelper.checkSuccessfulApiCall(getJobsResponse);
@@ -947,10 +947,10 @@ public class BatchApiStepDef extends AbstractStepDef {
if (batchResponseList.isSuccessful() && batchResponseList.body() != null && !batchResponseList.body().isEmpty()) {
BatchResponse response = batchResponseList.body().get(0);
log.info("Batch charge-off API status code: {}", response.getStatusCode());
log.info("Batch charge-off API response body: {}", response.getBody());
log.debug("Batch charge-off API status code: {}", response.getStatusCode());
log.debug("Batch charge-off API response body: {}", response.getBody());
} else {
log.info("Batch charge-off API call failed or returned empty response");
log.warn("Batch charge-off API call failed or returned empty response");
}
}
@@ -4029,7 +4029,7 @@ public class LoanStepDef extends AbstractStepDef {
ErrorHelper.checkSuccessfulApiCall(transactionsByLoanIdFiltered);
List<GetLoansLoanIdTransactionsTransactionIdResponse> transactions = transactionsByLoanIdFiltered.body().getContent();
log.info("Filtered transactions: {}", transactions);
log.debug("Filtered transactions: {}", transactions);
List<String> excludedTypesList = Arrays.stream(excludedTypes.toLowerCase().split(",")).map(String::trim)
.collect(Collectors.toList());
@@ -4052,7 +4052,7 @@ public class LoanStepDef extends AbstractStepDef {
ErrorHelper.checkSuccessfulApiCall(transactionsByLoanExternalIdFiltered);
List<GetLoansLoanIdTransactionsTransactionIdResponse> transactions = transactionsByLoanExternalIdFiltered.body().getContent();
log.info("Filtered transactions: {}", transactions);
log.debug("Filtered transactions: {}", transactions);
List<String> excludedTypesList = Arrays.stream(excludedTypes.toLowerCase().split(",")).map(String::trim)
.collect(Collectors.toList());
@@ -4514,7 +4514,7 @@ public class LoanStepDef extends AbstractStepDef {
// Get current business date to ensure we're not creating backdated transactions
String currentBusinessDate = businessDateHelper.getBusinessDate();
log.info("Current business date: {}, Transaction date: {}", currentBusinessDate, transactionDate);
log.debug("Current business date: {}, Transaction date: {}", currentBusinessDate, transactionDate);
final Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
@@ -4530,7 +4530,7 @@ public class LoanStepDef extends AbstractStepDef {
testContext().set(TestContextKey.LOAN_CAPITALIZED_INCOME_ADJUSTMENT_RESPONSE, adjustmentResponse);
ErrorHelper.checkSuccessfulApiCall(adjustmentResponse);
log.info("Capitalized Income Adjustment created: Transaction ID {}", adjustmentResponse.body().getResourceId());
log.debug("Capitalized Income Adjustment created: Transaction ID {}", adjustmentResponse.body().getResourceId());
}
@And("Admin adds capitalized income adjustment with {string} payment type to the loan on {string} with {string} EUR trn amount with {string} date for capitalized income")
@@ -4554,7 +4554,7 @@ public class LoanStepDef extends AbstractStepDef {
testContext().set(TestContextKey.LOAN_CAPITALIZED_INCOME_ADJUSTMENT_RESPONSE, adjustmentResponse);
ErrorHelper.checkSuccessfulApiCall(adjustmentResponse);
log.info("Capitalized Income Adjustment created: Transaction ID {}", adjustmentResponse.body().getResourceId());
log.debug("Capitalized Income Adjustment created: Transaction ID {}", adjustmentResponse.body().getResourceId());
}
@And("Admin adds invalid capitalized income adjustment with {string} payment type to the loan on {string} with {string} EUR transaction amount")
@@ -4565,7 +4565,7 @@ public class LoanStepDef extends AbstractStepDef {
// Get current business date to ensure we're not creating backdated transactions
String currentBusinessDate = businessDateHelper.getBusinessDate();
log.info("Current business date: {}, Transaction date: {}", currentBusinessDate, transactionDate);
log.debug("Current business date: {}, Transaction date: {}", currentBusinessDate, transactionDate);
final Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
+17 -7
View File
@@ -81,13 +81,23 @@ tasks.named('cucumber').get().finalizedBy 'allureReport'
tasks.named('cucumber').get().dependsOn 'spotlessCheck'
cucumber {
tags = 'not @ignore'
main = 'io.cucumber.core.cli.Main'
shorten = 'argfile'
plugin = [
'pretty',
'io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm'
]
// Use -Pcucumber.features=... if passed, otherwise default
if (project.hasProperty("cucumber.features")) {
featurePath = project.getProperty("cucumber.features")
} else {
featurePath = "src/test/resources/features";
}
tags = 'not @Skip'
if (project.hasProperty("cucumber.tags")) {
tags = [project.getProperty("cucumber.tags")]
}
if (project.hasProperty("cucumber.name")) {
name = project.getProperty("cucumber.name")
}
plugin = ['pretty', 'io.qameta.allure.cucumber7jvm.AllureCucumber7Jvm']
}
allure {
@@ -86,6 +86,9 @@ public class AccrualsOnLoanClosureTest extends BaseLoanIntegrationTest {
transaction(800.0, "Disbursement", "22 April 2024", 800.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
transaction(820.0, "Repayment", "25 April 2024", 0.0, 800.0, 0.0, 0.0, 20.0, 0.0, 0.0),
transaction(20.0, "Accrual", "25 April 2024", 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 0.0));
globalConfigurationHelper.updateGlobalConfiguration(CHARGE_ACCRUAL_DATE,
new PutGlobalConfigurationsRequest().stringValue("due-date"));
});
}
}
@@ -25,14 +25,24 @@ import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.integrationtests.common.Utils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@Slf4j
public class ActuatorIntegrationTest {
private static final String INFO_URL = "/fineract-provider/actuator/info";
@BeforeEach
public void setup() {
Utils.initializeRESTAssured();
}
@Test
public void testActuatorGitBuildInfo() {
log.info(INFO_URL);
Response response = RestAssured.given().headers("Content-Type", ContentType.JSON, "Accept", ContentType.JSON).when().get(INFO_URL)
.then().contentType(ContentType.JSON).extract().response();
@@ -168,9 +168,8 @@ public class DelinquencyAndChargebackIntegrationTest extends BaseLoanIntegration
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
log.info("Current Business date {}", businessDate);
// Run the Loan COB Job
final String jobName = "Loan COB";
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
// Get loan details expecting to have a delinquency classification
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -293,9 +292,8 @@ public class DelinquencyAndChargebackIntegrationTest extends BaseLoanIntegration
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate);
log.info("Current Business date {}", businessDate);
// Run the Loan COB Job
final String jobName = "Loan COB";
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
// Get loan details expecting to have a delinquency classification
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -76,7 +76,6 @@ import org.apache.fineract.integrationtests.common.CommonConstants;
import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
import org.apache.fineract.integrationtests.common.loans.CobHelper;
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
@@ -867,9 +866,8 @@ public class DelinquencyBucketsIntegrationTest extends BaseLoanIntegrationTest {
assertTrue(jobBusinessStepConfigData.getBusinessSteps().stream().anyMatch(
businessStep -> BusinessConfigurationApiTest.LOAN_DELINQUENCY_CLASSIFICATION.equals(businessStep.getStepName())));
// Run first time the Loan COB Job
final String jobName = "Loan COB";
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
// Get loan details expecting to have not a delinquency classification
GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -885,13 +883,11 @@ public class DelinquencyBucketsIntegrationTest extends BaseLoanIntegrationTest {
}
// Move the Business date to get older the loan and to have an overdue loan
LocalDate lastLoanCOBBusinessDate = bussinesLocalDate;
bussinesLocalDate = bussinesLocalDate.plusDays(3);
schedulerJobHelper.fastForwardTime(lastLoanCOBBusinessDate, bussinesLocalDate, jobName, responseSpec);
log.info("Current date {}", bussinesLocalDate);
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, bussinesLocalDate);
// Run Second time the Job
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
// Get loan details expecting to have a delinquency classification
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -968,15 +964,11 @@ public class DelinquencyBucketsIntegrationTest extends BaseLoanIntegrationTest {
log.info("Loan Delinquency Range is null {}", (firstTestCase == null));
loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
final String jobName = "Loan COB";
bussinesLocalDate = Utils.getDateAsLocalDate("31 January 2012");
LocalDate lastLoanCOBBusinessDate = bussinesLocalDate.minusDays(1);
schedulerJobHelper.fastForwardTime(lastLoanCOBBusinessDate, bussinesLocalDate, jobName, responseSpec);
log.info("Current date {}", bussinesLocalDate);
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, bussinesLocalDate);
// Run Second time the Job
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
// Get loan details expecting to have a delinquency classification
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -1059,15 +1051,11 @@ public class DelinquencyBucketsIntegrationTest extends BaseLoanIntegrationTest {
log.info("Loan Product Arrears: {}", loanProductsProductIdResponseUpd.getInArrearsTolerance());
assertEquals(0, loanProductsProductIdResponseUpd.getInArrearsTolerance());
final String jobName = "Loan COB";
bussinesLocalDate = Utils.getDateAsLocalDate("31 January 2012");
LocalDate lastLoanCOBBusinessDate = bussinesLocalDate.minusDays(1);
schedulerJobHelper.fastForwardTime(lastLoanCOBBusinessDate, bussinesLocalDate, jobName, responseSpec);
log.info("Current date {}", bussinesLocalDate);
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, bussinesLocalDate);
// Run Second time the Job
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
// Get loan details expecting to have a delinquency classification
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -1114,24 +1102,22 @@ public class DelinquencyBucketsIntegrationTest extends BaseLoanIntegrationTest {
log.info("Loan Account Arrears {}", getLoansLoanIdResponse.getInArrearsTolerance());
assertEquals(3, getLoansLoanIdResponse.getInArrearsTolerance());
final String jobName = "Loan COB";
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "04 February 2012");
updateBusinessDate("06 February 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 1033.33, 5);
PostLoansDelinquencyActionResponse pauseDelinquencyResponse = loanTransactionHelper
.createLoanDelinquencyAction(loanId.longValue(), PAUSE, "06 February 2012", "10 February 2012");
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "07 February 2012");
updateBusinessDate("09 February 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 1033.33, 5);
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "10 March 2012");
updateBusinessDate("12 March 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 2049.99, 36);
});
}
@@ -1165,28 +1151,26 @@ public class DelinquencyBucketsIntegrationTest extends BaseLoanIntegrationTest {
log.info("Loan Account Arrears {}", getLoansLoanIdResponse.getInArrearsTolerance());
assertEquals(3, getLoansLoanIdResponse.getInArrearsTolerance());
final String jobName = "Loan COB";
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "04 February 2012");
updateBusinessDate("06 February 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 1033.33, 5);
PostLoansDelinquencyActionResponse pauseDelinquencyResponse = loanTransactionHelper
.createLoanDelinquencyAction(loanId.longValue(), PAUSE, "06 February 2012", "10 March 2012");
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "07 February 2012");
updateBusinessDate("09 February 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 1033.33, 5);
bussinesLocalDate = Utils.getDateAsLocalDate("10 February 2012");
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, bussinesLocalDate);
loanTransactionHelper.createLoanDelinquencyAction(loanId.longValue(), RESUME, "10 February 2012");
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "10 March 2012");
updateBusinessDate("12 March 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 2049.99, 36);
});
}
@@ -1217,38 +1201,36 @@ public class DelinquencyBucketsIntegrationTest extends BaseLoanIntegrationTest {
log.info("Loan Account Arrears {}", getLoansLoanIdResponse.getInArrearsTolerance());
assertEquals(3, getLoansLoanIdResponse.getInArrearsTolerance());
final String jobName = "Loan COB";
// delinquent days: 5
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "04 February 2012");
updateBusinessDate("06 February 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 1033.33, 5);
// Add delinquency pause on 06 February 2012
PostLoansDelinquencyActionResponse pauseDelinquencyResponse = loanTransactionHelper
.createLoanDelinquencyAction(loanId.longValue(), PAUSE, "06 February 2012", "10 March 2012");
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "07 February 2012");
updateBusinessDate("09 February 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 1033.33, 5);
// Add delinquency resume on 10 February 2012
updateBusinessDate("10 February 2012");
loanTransactionHelper.createLoanDelinquencyAction(loanId.longValue(), RESUME, "10 February 2012");
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "11 February 2012");
updateBusinessDate("13 February 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 1033.33, 8);
// Add new pause on 13 February 2012
pauseDelinquencyResponse = loanTransactionHelper.createLoanDelinquencyAction(loanId.longValue(), PAUSE, "13 February 2012",
"18 February 2012");
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "21 February 2012");
updateBusinessDate("23 February 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 1033.33, 13);
// Add new pause on 23 February 2012
@@ -1256,9 +1238,9 @@ public class DelinquencyBucketsIntegrationTest extends BaseLoanIntegrationTest {
"28 February 2012");
updateBusinessDate("25 February 2012");
loanTransactionHelper.createLoanDelinquencyAction(loanId.longValue(), RESUME, "25 February 2012");
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "10 March 2012");
updateBusinessDate("12 March 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 2049.99, 29);
});
}
@@ -1299,36 +1281,34 @@ public class DelinquencyBucketsIntegrationTest extends BaseLoanIntegrationTest {
log.info("Loan Account Arrears {}", getLoansLoanIdResponse.getInArrearsTolerance());
assertEquals(3, getLoansLoanIdResponse.getInArrearsTolerance());
final String jobName = "Loan COB";
// delinquent days: 5
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "04 February 2012");
updateBusinessDate("06 February 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 1033.33, 5);
PostLoansDelinquencyActionResponse pauseDelinquencyResponse = loanTransactionHelper
.createLoanDelinquencyAction(loanId.longValue(), PAUSE, "06 February 2012", "10 March 2012");
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "07 February 2012");
updateBusinessDate("09 February 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 1033.33, 5);
updateBusinessDate("10 February 2012");
loanTransactionHelper.createLoanDelinquencyAction(loanId.longValue(), RESUME, "10 February 2012");
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "11 February 2012");
updateBusinessDate("13 February 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 1033.33, 8);
pauseDelinquencyResponse = loanTransactionHelper.createLoanDelinquencyAction(loanId.longValue(), PAUSE, "13 February 2012",
"18 February 2012");
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "21 February 2012");
updateBusinessDate("23 February 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
verifyDelinquency(loanId, "01 February 2012", 1033.33, 13);
pauseDelinquencyResponse = loanTransactionHelper.createLoanDelinquencyAction(loanId.longValue(), PAUSE, "23 February 2012",
@@ -1337,9 +1317,9 @@ public class DelinquencyBucketsIntegrationTest extends BaseLoanIntegrationTest {
updateBusinessDate("25 February 2012");
loanTransactionHelper.createLoanDelinquencyAction(loanId.longValue(), RESUME, "25 February 2012");
CobHelper.fastForwardLoansLastCOBDate(requestSpec, responseSpec204, loanId, "12 March 2012");
updateBusinessDate("14 March 2012");
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
loanTransactionHelper.printDelinquencyData(getLoansLoanIdResponse);
@@ -1447,10 +1427,10 @@ public class DelinquencyBucketsIntegrationTest extends BaseLoanIntegrationTest {
getLoanProductsProductResponse.getId().toString(), operationDate, null);
// run cob for business date 01 January 2012
final String jobName = "Loan COB";
bussinesLocalDate = Utils.getDateAsLocalDate(operationDate);
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, bussinesLocalDate);
schedulerJobHelper.executeAndAwaitJob(jobName);
// Run the Loan inline COB Job
inlineLoanCOBHelper.executeInlineCOB(Long.valueOf(loanId));
// Loan delinquency data
GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
@@ -192,6 +192,8 @@ public class UndoRepaymentWithDownPaymentIntegrationTest extends BaseLoanIntegra
loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
assertEquals(500.0, Utils.getDoubleValue(loanDetails.getSummary().getTotalOutstanding()));
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
new PutGlobalConfigurationsRequest().enabled(false));
}
private Integer createLoanProductWithPeriodicAccrualAccountingAndAdvancedPaymentAllocationStrategy() {
@@ -544,7 +544,7 @@ public class GlobalConfigurationHelper {
assetOwnerTransferInterestOutstandingStrategy.put("name",
GlobalConfigurationConstants.ASSET_OWNER_TRANSFER_OUTSTANDING_INTEREST_CALCULATION_STRATEGY);
assetOwnerTransferInterestOutstandingStrategy.put("value", 0L);
assetOwnerTransferInterestOutstandingStrategy.put("enabled", false);
assetOwnerTransferInterestOutstandingStrategy.put("enabled", true);
assetOwnerTransferInterestOutstandingStrategy.put("trapDoor", false);
assetOwnerTransferInterestOutstandingStrategy.put("string_value", "TOTAL_OUTSTANDING_INTEREST");
defaults.add(assetOwnerTransferInterestOutstandingStrategy);
@@ -142,7 +142,7 @@ public class OfficeHelper {
public static String getAsJSON(String externalId, final String openingDate) {
final HashMap<String, String> map = new HashMap<>();
map.put("parentId", "1");
map.put("name", Utils.uniqueRandomStringGenerator("Office_", 4));
map.put("name", Utils.uniqueRandomStringGenerator("O_", 9));
map.put("dateFormat", "dd MMMM yyyy");
map.put("locale", "en");
map.put("openingDate", openingDate);
@@ -272,7 +272,7 @@ public class SchedulerJobHelper {
private void awaitJob(Instant beforeExecuteTime, Supplier<Callable<Map<String, String>>> retrieveLastRunHistory) {
final Duration timeout = Duration.ofMinutes(2);
final Duration pause = Duration.ofSeconds(5);
final Duration pause = Duration.ofSeconds(1);
DateTimeFormatter df = DateTimeFormatter.ISO_INSTANT; // FINERACT-926
// Await JobDetailData.lastRunHistory [JobDetailHistoryData]
// jobRunStartTime >= beforeExecuteTime (or timeout)
@@ -109,6 +109,7 @@ public class SearchExternalAssetOwnerTransferTest extends ExternalAssetOwnerTran
@Test
public void initialSearchExternalAssetOwnerTransferUsingTextTest() {
saleActiveLoanToExternalAssetOwnerWithSearching();
String textToSearch = UUID.randomUUID().toString();
PagedRequestExternalAssetOwnerSearchRequest searchRequest = EXTERNAL_ASSET_OWNER_HELPER
.buildExternalAssetOwnerSearchRequest(textToSearch, "", null, null, 0, 10);
@@ -118,10 +119,10 @@ public class SearchExternalAssetOwnerTransferTest extends ExternalAssetOwnerTran
// Search over the current Asset Transfers and get just the first five
textToSearch = "";
searchRequest = EXTERNAL_ASSET_OWNER_HELPER.buildExternalAssetOwnerSearchRequest(textToSearch, "", null, null, 0, 5);
searchRequest = EXTERNAL_ASSET_OWNER_HELPER.buildExternalAssetOwnerSearchRequest(textToSearch, "", null, null, 0, 1);
response = EXTERNAL_ASSET_OWNER_HELPER.searchExternalAssetOwnerTransfer(searchRequest);
assertNotNull(response);
assertEquals(5, response.getContent().size(), "Expecting first five results");
assertEquals(1, response.getContent().size(), "Expecting first result");
textToSearch = response.getContent().iterator().next().getOwner().getExternalId();
searchRequest = EXTERNAL_ASSET_OWNER_HELPER.buildExternalAssetOwnerSearchRequest(textToSearch, "", null, null, 0, 5);
@@ -133,6 +134,7 @@ public class SearchExternalAssetOwnerTransferTest extends ExternalAssetOwnerTran
@Test
public void initialSearchExternalAssetOwnerTransferUsingEffectiveDateTest() {
saleActiveLoanToExternalAssetOwnerWithSearching();
final String attribute = "effective";
LocalDate fromDate = Utils.getDateAsLocalDate("01 March 2023");
LocalDate toDate = fromDate.plusMonths(3);
+110
View File
@@ -0,0 +1,110 @@
#!/bin/bash
# Exit on any error
set -e
# Get the number of shards (default to 10 if not provided)
NUM_SHARDS=${1:-10}
# Convert to 0-based index for internal calculations
SHARD_INDEX_ZERO_BASED=$((${2:-1} - 1))
SHARD_INDEX=${2:-1} # Keep original 1-based index for output
# Directory containing feature files
FEATURES_DIR="fineract-e2e-tests-runner/src/test/resources/features"
TEMP_FILE="/tmp/feature_scenarios_$(date +%s).txt"
# Check if features directory exists
if [ ! -d "$FEATURES_DIR" ]; then
echo "Error: Features directory not found at $FEATURES_DIR"
exit 1
fi
# Function to count scenarios in a feature file
count_scenarios() {
local file="$1"
# Count scenario and scenario outline keywords, excluding commented lines
grep -v '^[[:space:]]*#' "$file" | grep -c 'Scenario\( Outline\)\?:' || echo "0"
}
# Process each feature file and count scenarios
echo "Analyzing feature files to count scenarios..."
> "$TEMP_FILE"
while IFS= read -r -d $'\0' file; do
# Remove the 'fineract-e2e-tests-runner/' prefix
rel_path="${file#fineract-e2e-tests-runner/}"
scenario_count=$(count_scenarios "$file")
echo "$scenario_count $rel_path"
done < <(find "$FEATURES_DIR" -type f -name '*.feature' -print0) | sort -nr > "$TEMP_FILE"
# Read the sorted list of features
SORTED_FEATURES=()
while IFS= read -r line; do
# Extract just the file path (removing the scenario count)
path=$(echo "$line" | awk '{$1=""; print $0}' | sed 's/^ //')
[ -n "$path" ] && SORTED_FEATURES+=("$path")
done < "$TEMP_FILE"
TOTAL_FEATURES=${#SORTED_FEATURES[@]}
# Check if any feature files were found
if [ $TOTAL_FEATURES -eq 0 ]; then
echo "Warning: No feature files found in $FEATURES_DIR"
# Create an empty feature list file
FEATURE_LIST_FILE="feature_shard_${SHARD_INDEX}.txt"
> "$FEATURE_LIST_FILE"
echo "Created empty feature list file: $FEATURE_LIST_FILE"
rm -f "$TEMP_FILE"
exit 0
fi
# Create a file to store the feature file paths
# Use the 1-based index for the filename
FEATURE_LIST_FILE="feature_shard_${SHARD_INDEX}.txt"
> "$FEATURE_LIST_FILE"
# Distribute features to shards in a round-robin fashion to balance scenario counts
for ((i=0; i<TOTAL_FEATURES; i++)); do
if (( i % NUM_SHARDS == SHARD_INDEX_ZERO_BASED )); then
echo "${SORTED_FEATURES[$i]}" >> "$FEATURE_LIST_FILE"
fi
done
# Count scenarios in this shard
SHARD_SCENARIOS=0
while IFS= read -r line; do
[ -z "$line" ] && continue
count=$(grep -m 1 -F "$line" "$TEMP_FILE" | awk '{print $1}')
SHARD_SCENARIOS=$((SHARD_SCENARIOS + count))
done < "$FEATURE_LIST_FILE"
# Get the list of features in this shard for output
SHARD_FEATURES=()
while IFS= read -r line; do
[ -n "$line" ] && SHARD_FEATURES+=("$line")
done < "$FEATURE_LIST_FILE"
NUM_FEATURES=${#SHARD_FEATURES[@]}
# Output the shard information
echo "Shard $SHARD_INDEX (1-based): $NUM_FEATURES features with $SHARD_SCENARIOS total scenarios"
if [ $NUM_FEATURES -gt 0 ]; then
echo "First feature: ${SHARD_FEATURES[0]}"
if [ $NUM_FEATURES -gt 1 ]; then
echo "Last feature: ${SHARD_FEATURES[$((NUM_FEATURES-1))]}"
fi
echo "Features in this shard (scenario count):"
while IFS= read -r line; do
[ -z "$line" ] && continue
count=$(grep -m 1 -F "$line" "$TEMP_FILE" | awk '{print $1}')
echo " - $line ($count scenarios)"
done < "$FEATURE_LIST_FILE"
fi
echo "Feature list written to $FEATURE_LIST_FILE"
# Clean up temp file
rm -f "$TEMP_FILE"
# Set output for GitHub Actions
if [ -n "$GITHUB_OUTPUT" ]; then
echo "FEATURE_LIST_FILE=$FEATURE_LIST_FILE" >> $GITHUB_OUTPUT
fi
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
# Usage: ./split-tests.sh <total-shards> <shard-index>
set -e
TOTAL_SHARDS=$1
SHARD_INDEX=$2
if [[ -z "$TOTAL_SHARDS" || -z "$SHARD_INDEX" ]]; then
echo "ERROR: You must provide <total-shards> and <shard-index>."
exit 1
fi
echo "🔍 Searching for eligible JUnit test classes..."
ALL_TESTS=$(find . -type f -path "*/src/test/java/*Test.java" \
| while read filepath; do
filename=$(basename "$filepath")
# Skip abstract class or interface by name
if [[ "$filename" =~ ^Abstract.*Test\.java$ || "$filename" =~ .*AbstractTest\.java$ ]]; then
echo "Skipping abstract-named file: $filename" >&2
continue
fi
# Skip if file doesn't contain valid JUnit annotations
if ! grep -qE "@Test|@Nested|@ParameterizedTest" "$filepath"; then
continue
fi
# Extract module directory path (everything before /src/test/java)
module_path="${filepath%%/src/test/java/*}"
# Convert from ./custom/acme/loan/job to :custom:acme:loan:job
module_name=$(echo "$module_path" | sed 's|^\./||; s|/|:|g; s|^|:|')
# Extract fully qualified test class name
class_name=$(echo "$filepath" | sed 's|^.*src/test/java/||; s|/|.|g; s|.java$||')
echo "$module_name,$class_name"
done \
| sort)
TOTAL_COUNT=$(echo "$ALL_TESTS" | wc -l)
echo "Found $TOTAL_COUNT eligible test classes."
SELECTED_CLASSES=$(echo "$ALL_TESTS" \
| awk -v ts="$TOTAL_SHARDS" -v si="$SHARD_INDEX" 'NR % ts == (si - 1)')
OUTPUT_FILE="shard-tests_${SHARD_INDEX}.txt"
echo "$SELECTED_CLASSES" > "$OUTPUT_FILE"
echo "Selected $(wc -l < "$OUTPUT_FILE") classes for shard $SHARD_INDEX of $TOTAL_SHARDS:"
cat "$OUTPUT_FILE"