| name: Fineract Docker & Unit- & Integration tests - PostgreSQL |
| |
| on: |
| workflow_call: |
| secrets: |
| DEVELOCITY_ACCESS_KEY: |
| required: false |
| |
| permissions: |
| contents: read |
| |
| jobs: |
| test: |
| name: PostgreSQL Tests (Shard ${{ matrix.shard_index }} of ${{ matrix.total_shards }}) |
| runs-on: ubuntu-24.04 |
| timeout-minutes: 90 |
| |
| 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, 11, 12, 13, 14, 15 ] |
| total_shards: [ 15 ] |
| |
| env: |
| TZ: Asia/Kolkata |
| DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} |
| IMAGE_NAME: fineract |
| COMPOSE_FILE: docker-compose-postgresql-test.yml |
| DOCKER_LOG_DIR: ci-logs/postgresql-shard-${{ matrix.shard_index }} |
| 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 |
| FINERACT_TEST_EXTERNAL_HOST: host.docker.internal |
| |
| steps: |
| - name: Set up JDK 21 |
| uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5 |
| with: |
| java-version: '21' |
| distribution: 'zulu' |
| |
| - name: Download workspace |
| id: download-workspace |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 |
| continue-on-error: true |
| with: |
| name: fineract-workspace-${{ github.run_id }} |
| path: . |
| |
| - name: Extract workspace |
| if: steps.download-workspace.outcome == 'success' |
| run: tar -xf fineract-workspace.tar |
| |
| - name: Checkout |
| if: steps.download-workspace.outcome != 'success' |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 |
| with: |
| persist-credentials: false |
| fetch-depth: 0 |
| fetch-tags: true |
| |
| - name: Build workspace fallback |
| if: steps.download-workspace.outcome != 'success' |
| uses: ./.github/actions/setup-and-build-fineract |
| with: |
| cache-read-only: false |
| |
| - name: Setup Gradle and Validate Wrapper |
| uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0 |
| with: |
| validate-wrappers: true |
| |
| - name: Build the image |
| run: ./gradlew --no-daemon --console=plain :fineract-provider:jibDockerBuild -Djib.to.image=$IMAGE_NAME -x test -x cucumber -x buildJavaSdk |
| |
| - name: Start dependencies |
| run: docker compose -f "$COMPOSE_FILE" up -d db localstack mock-oauth2-server |
| |
| - name: Initialise LocalStack |
| run: | |
| timeout 120 bash -c 'until docker compose -f "$COMPOSE_FILE" exec -T localstack awslocal s3api head-bucket --bucket fineract-reports >/dev/null 2>&1 || docker compose -f "$COMPOSE_FILE" exec -T localstack awslocal s3api create-bucket --bucket fineract-reports; do |
| sleep 5 |
| done' |
| |
| - name: Start Fineract |
| run: docker compose -f "$COMPOSE_FILE" up -d fineract |
| |
| - name: Check the stack |
| run: docker ps |
| |
| - name: Wait for Fineract to be ready |
| run: curl -f -k --retry 60 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health |
| |
| - name: Generate test class list |
| run: | |
| chmod +x scripts/split-tests.sh |
| ./scripts/split-tests.sh ${{ matrix.total_shards }} ${{ matrix.shard_index }} |
| cat "shard-tests_${{ matrix.shard_index }}.txt" |
| |
| - name: Run Gradle Task |
| run: | |
| set -e # Fail the script if any command fails |
| FAILED=0 |
| FINERACT_AUTH_MODE=basic |
| |
| wait_for_fineract() { |
| curl -f -k --retry 60 --retry-all-errors --connect-timeout 30 --retry-delay 30 https://localhost:8443/fineract-provider/actuator/health |
| } |
| |
| configure_sms_gateway_host() { |
| docker compose -f "$COMPOSE_FILE" exec -T db sh -lc 'PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d fineract_default -c "UPDATE c_external_service_properties SET value = '\''host.docker.internal'\'' WHERE name = '\''host_name'\'' AND external_service_id = (SELECT id FROM c_external_service WHERE name = '\''MESSAGE_GATEWAY'\'')"' |
| } |
| |
| configure_twofactor_smtp_host() { |
| docker compose -f "$COMPOSE_FILE" exec -T db sh -lc 'PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d fineract_default -c "UPDATE c_external_service_properties SET value = '\''host.docker.internal'\'' WHERE name = '\''host'\'' AND external_service_id = (SELECT id FROM c_external_service WHERE name = '\''SMTP_Email_Account'\'')"' |
| } |
| |
| switch_fineract_auth_mode() { |
| local module="$1" |
| local target_auth_mode="basic" |
| local compose_args=(-f "$COMPOSE_FILE") |
| |
| if [ "$module" = ":oauth2-tests" ]; then |
| target_auth_mode="oauth2" |
| compose_args=(-f "$COMPOSE_FILE" -f docker-compose-oauth2-test.yml) |
| elif [ "$module" = ":twofactor-tests" ]; then |
| target_auth_mode="twofactor" |
| compose_args=(-f "$COMPOSE_FILE" -f docker-compose-twofactor-test.yml) |
| fi |
| |
| if [ "$FINERACT_AUTH_MODE" != "$target_auth_mode" ]; then |
| echo "Switching Fineract auth mode to $target_auth_mode for $module" |
| docker compose "${compose_args[@]}" up -d --force-recreate fineract |
| wait_for_fineract |
| FINERACT_AUTH_MODE="$target_auth_mode" |
| fi |
| |
| if [ "$target_auth_mode" = "twofactor" ]; then |
| configure_twofactor_smtp_host |
| fi |
| } |
| |
| configure_sms_gateway_host |
| |
| echo "Grouping test classes by module..." |
| declare -A module_tests |
| declare -A module_seen |
| module_order=() |
| |
| while IFS=, read -r module class; do |
| if [ -z "$module" ] || [ -z "$class" ]; then |
| continue |
| fi |
| if [ -z "${module_seen[$module]}" ]; then |
| module_seen["$module"]=1 |
| module_order+=("$module") |
| fi |
| module_tests["$module"]+="$class " |
| done < "shard-tests_${{ matrix.shard_index }}.txt" |
| |
| ordered_modules=() |
| for module in "${module_order[@]}"; do |
| if [ "$module" != ":oauth2-tests" ] && [ "$module" != ":twofactor-tests" ]; then |
| ordered_modules+=("$module") |
| fi |
| done |
| for module in ":oauth2-tests" ":twofactor-tests"; do |
| if [ -n "${module_tests[$module]}" ]; then |
| ordered_modules+=("$module") |
| fi |
| done |
| |
| for module in "${ordered_modules[@]}"; do |
| echo "::group::Running tests in $module" |
| for class in ${module_tests[$module]}; do |
| echo " - $class" |
| done |
| |
| switch_fineract_auth_mode "$module" |
| |
| # Build test args |
| test_args=$(for class in ${module_tests[$module]}; do echo --tests "$class"; done | xargs) |
| |
| # Run test task for this module |
| if ! ./gradlew --no-daemon "$module:test" $test_args -PcargoDisabled -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x spotlessCheck -x spotlessApply -x spotbugsMain -x spotbugsTest -x javadoc -x javadocJar -x modernizer -x buildJavaSdk ; then |
| echo "::error::Tests failed in module $module" |
| FAILED=1 |
| fi |
| echo "::endgroup::" |
| done |
| |
| # Exit with failure status if any test failed |
| if [ "$FAILED" -eq 1 ]; then |
| echo "::error::Some tests failed in this job" |
| exit 1 |
| fi |
| |
| - name: Archive test results |
| if: failure() |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 |
| with: |
| name: test-results-postgresql-shard-${{ matrix.shard_index }}-attempt-${{ github.run_attempt }} |
| path: '**/build/reports/' |
| retention-days: 5 |
| compression-level: 9 |
| |
| - name: Collect Docker logs |
| if: always() |
| run: | |
| mkdir -p "$DOCKER_LOG_DIR" |
| docker ps -a > "$DOCKER_LOG_DIR/docker-ps.txt" 2>&1 || true |
| docker compose -f "$COMPOSE_FILE" ps --all > "$DOCKER_LOG_DIR/docker-compose-ps.txt" 2>&1 || true |
| docker compose -f "$COMPOSE_FILE" logs --no-color --timestamps > "$DOCKER_LOG_DIR/docker-compose.log" 2>&1 || true |
| docker stats --no-stream > "$DOCKER_LOG_DIR/docker-stats.txt" 2>&1 || true |
| |
| docker compose -f "$COMPOSE_FILE" ps -q | while read -r container_id; do |
| [ -n "$container_id" ] || continue |
| container_name="$(docker inspect --format '{{.Name}}' "$container_id" 2>/dev/null | sed 's#^/##; s#[^A-Za-z0-9_.-]#_#g')" |
| [ -n "$container_name" ] || container_name="$container_id" |
| docker logs --timestamps "$container_id" > "$DOCKER_LOG_DIR/${container_name}.log" 2>&1 || true |
| docker inspect "$container_id" > "$DOCKER_LOG_DIR/${container_name}.inspect.json" 2>&1 || true |
| done |
| |
| - name: Archive server logs |
| if: failure() |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 |
| with: |
| name: server-logs-postgresql-shard-${{ matrix.shard_index }}-attempt-${{ github.run_attempt }} |
| path: | |
| ci-logs/ |
| build/fineract/logs/ |
| retention-days: 5 |
| compression-level: 9 |
| |
| - name: Clean up |
| if: always() |
| run: docker compose -f "$COMPOSE_FILE" down -v |