diff --git a/.github/workflows/build-cucumber.yml b/.github/workflows/build-cucumber.yml new file mode 100644 index 000000000..ce4a18fd9 --- /dev/null +++ b/.github/workflows/build-cucumber.yml @@ -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 diff --git a/.github/workflows/build-docker-postgresql.yml b/.github/workflows/build-docker-postgresql.yml deleted file mode 100644 index 9da361e8a..000000000 --- a/.github/workflows/build-docker-postgresql.yml +++ /dev/null @@ -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 )) diff --git a/.github/workflows/build-docker-mariadb.yml b/.github/workflows/build-docker.yml similarity index 67% rename from .github/workflows/build-docker-mariadb.yml rename to .github/workflows/build-docker.yml index 9c633a62e..641cf598a 100644 --- a/.github/workflows/build-docker-mariadb.yml +++ b/.github/workflows/build-docker.yml @@ -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 )) diff --git a/.github/workflows/build-e2e-tests.yml b/.github/workflows/build-e2e-tests.yml new file mode 100644 index 000000000..8648f48b2 --- /dev/null +++ b/.github/workflows/build-e2e-tests.yml @@ -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 diff --git a/.github/workflows/build-embeddable-progressive-loan-jar.yml b/.github/workflows/build-embeddable-progressive-loan-jar.yml deleted file mode 100644 index e29125955..000000000 --- a/.github/workflows/build-embeddable-progressive-loan-jar.yml +++ /dev/null @@ -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/ diff --git a/.github/workflows/build-mariadb.yml b/.github/workflows/build-mariadb.yml index c59b24272..056433f8b 100644 --- a/.github/workflows/build-mariadb.yml +++ b/.github/workflows/build-mariadb.yml @@ -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 diff --git a/.github/workflows/build-mysql.yml b/.github/workflows/build-mysql.yml index dce0d0be8..1b31c9a26 100644 --- a/.github/workflows/build-mysql.yml +++ b/.github/workflows/build-mysql.yml @@ -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 diff --git a/.github/workflows/build-postgresql.yml b/.github/workflows/build-postgresql.yml index 9e0ffebc7..f7b712301 100644 --- a/.github/workflows/build-postgresql.yml +++ b/.github/workflows/build-postgresql.yml @@ -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 diff --git a/.github/workflows/build-tests.yml b/.github/workflows/build-tests.yml deleted file mode 100644 index 221b31655..000000000 --- a/.github/workflows/build-tests.yml +++ /dev/null @@ -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 diff --git a/.github/workflows/smoke-kafka.yml b/.github/workflows/smoke-kafka.yml deleted file mode 100644 index fe0f9d7c5..000000000 --- a/.github/workflows/smoke-kafka.yml +++ /dev/null @@ -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 diff --git a/.github/workflows/smoke-activemq.yml b/.github/workflows/smoke-messaging.yml similarity index 79% rename from .github/workflows/smoke-activemq.yml rename to .github/workflows/smoke-messaging.yml index e52bcde93..0e88f3d13 100644 --- a/.github/workflows/smoke-activemq.yml +++ b/.github/workflows/smoke-messaging.yml @@ -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 diff --git a/build.gradle b/build.gradle index 0724494dc..b0b164612 100644 --- a/build.gradle +++ b/build.gradle @@ -341,7 +341,8 @@ allprojects { "**/generated/**/*MapperImpl.java", '**/META-INF/fineract-test.config', // Apache-specific GitHub metadata settings file - '/.asf.yaml' + '/.asf.yaml', + '**/*.sh' ] } } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/office/mapper/OfficeDataMapper.java b/fineract-core/src/main/java/org/apache/fineract/organisation/office/mapper/OfficeDataMapper.java index e62a2fccc..754d7181d 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/office/mapper/OfficeDataMapper.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/office/mapper/OfficeDataMapper.java @@ -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, diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/service/JobService.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/service/JobService.java index e8e26533c..26c9e2719 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/service/JobService.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/service/JobService.java @@ -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 = schedulerJobApi.retrieveOne5(jobId).execute(); ErrorHelper.checkSuccessfulApiCall(getJobsResponse); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java index a0c25219b..c171b40ec 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/BatchApiStepDef.java @@ -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"); } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java index 18c2d9c4e..19f70a561 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java @@ -4029,7 +4029,7 @@ public class LoanStepDef extends AbstractStepDef { ErrorHelper.checkSuccessfulApiCall(transactionsByLoanIdFiltered); List transactions = transactionsByLoanIdFiltered.body().getContent(); - log.info("Filtered transactions: {}", transactions); + log.debug("Filtered transactions: {}", transactions); List 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 transactions = transactionsByLoanExternalIdFiltered.body().getContent(); - log.info("Filtered transactions: {}", transactions); + log.debug("Filtered transactions: {}", transactions); List 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 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 loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute(); ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); diff --git a/fineract-e2e-tests-runner/build.gradle b/fineract-e2e-tests-runner/build.gradle index 9f233ccf7..ccc64f51d 100644 --- a/fineract-e2e-tests-runner/build.gradle +++ b/fineract-e2e-tests-runner/build.gradle @@ -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 { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccrualsOnLoanClosureTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccrualsOnLoanClosureTest.java index e903059f9..c9c742646 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccrualsOnLoanClosureTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AccrualsOnLoanClosureTest.java @@ -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")); }); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ActuatorIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ActuatorIntegrationTest.java index 824b6c2bd..3a6437cef 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ActuatorIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ActuatorIntegrationTest.java @@ -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(); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyAndChargebackIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyAndChargebackIntegrationTest.java index cbaebb639..1ba83b0b1 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyAndChargebackIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyAndChargebackIntegrationTest.java @@ -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); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java index f8c252272..6f7669bb3 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java @@ -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); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/UndoRepaymentWithDownPaymentIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/UndoRepaymentWithDownPaymentIntegrationTest.java index e584fac46..b2d145f7e 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/UndoRepaymentWithDownPaymentIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/UndoRepaymentWithDownPaymentIntegrationTest.java @@ -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() { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java index e636d6346..c16780be2 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java @@ -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); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/OfficeHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/OfficeHelper.java index 18aea876b..8e3ec6efe 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/OfficeHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/OfficeHelper.java @@ -142,7 +142,7 @@ public class OfficeHelper { public static String getAsJSON(String externalId, final String openingDate) { final HashMap 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); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java index 8a325476f..093f2f0c2 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/SchedulerJobHelper.java @@ -272,7 +272,7 @@ public class SchedulerJobHelper { private void awaitJob(Instant beforeExecuteTime, Supplier>> 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) diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/SearchExternalAssetOwnerTransferTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/SearchExternalAssetOwnerTransferTest.java index 29d5aec6f..30267956a 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/SearchExternalAssetOwnerTransferTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/SearchExternalAssetOwnerTransferTest.java @@ -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); diff --git a/scripts/split-features.sh b/scripts/split-features.sh new file mode 100755 index 000000000..b793ce18f --- /dev/null +++ b/scripts/split-features.sh @@ -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> "$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 diff --git a/scripts/split-tests.sh b/scripts/split-tests.sh new file mode 100755 index 000000000..c9a84add2 --- /dev/null +++ b/scripts/split-tests.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Usage: ./split-tests.sh +set -e + +TOTAL_SHARDS=$1 +SHARD_INDEX=$2 + +if [[ -z "$TOTAL_SHARDS" || -z "$SHARD_INDEX" ]]; then + echo "ERROR: You must provide and ." + 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"