blob: 26c4700e75bd7c6c1e8944d8fad75e728cba5ff1 [file]
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