name: Tests on: pull_request: push: jobs: unit: name: Unit (required) # Match the labels exposed by the Coolify-managed GitHub runner. runs-on: [self-hosted, Linux, X64, default] steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 - name: Check AI workflow sync run: node scripts/sync-ai-workflow.mjs --check - name: Materialize compose env files env: COMPOSE_ENV: ${{ secrets.COMPOSE_ENV }} COMPOSE_ENV_STAGING: ${{ secrets.COMPOSE_ENV_STAGING }} run: | set -euo pipefail if [ -z "${COMPOSE_ENV}" ]; then echo "Required GitHub secret COMPOSE_ENV is not configured." >&2 exit 1 fi if [ -z "${COMPOSE_ENV_STAGING}" ]; then echo "Required GitHub secret COMPOSE_ENV_STAGING is not configured." >&2 exit 1 fi printf '%s\n' "$COMPOSE_ENV" > .env printf '%s\n' "$COMPOSE_ENV_STAGING" > .env.staging - name: Boot php1 test stack run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml up -d redis mysql-debug php1 - name: Sync PHP app checkout run: tar -C services/nginx/app -cf - . | docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 tar -C /var/www/html -xf - - name: Resolve dependencies run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc "cd /var/www/html && composer update --no-interaction --prefer-dist" - name: Run unit tests run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc "cd /var/www/html && composer test:unit" - name: Generate coverage report run: | docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc "cd /var/www/html && if php -m | grep -Eq '^(pcov|xdebug)$'; then composer test:coverage; else echo 'Skipping coverage: no pcov/xdebug extension available in php1 image.'; fi" - name: Upload coverage artifact if: ${{ github.event_name == 'pull_request' }} continue-on-error: true uses: actions/upload-artifact@v4 with: name: unit-coverage-clover path: services/nginx/app/build/logs/clover.xml if-no-files-found: warn retention-days: 1 - name: Tear down php1 test stack if: always() run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml down -v edge-agent: name: Edge Agent (required) runs-on: [self-hosted, Linux, X64, default] steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: npm cache-dependency-path: services/edge-agent/package-lock.json - name: Install dependencies working-directory: services/edge-agent run: npm ci - name: Run edge agent tests working-directory: services/edge-agent run: npm test edge-broker: name: Edge Broker (required) runs-on: [self-hosted, Linux, X64, default] steps: - name: Checkout uses: actions/checkout@v4 - name: Materialize compose env files env: COMPOSE_ENV: ${{ secrets.COMPOSE_ENV }} COMPOSE_ENV_STAGING: ${{ secrets.COMPOSE_ENV_STAGING }} run: | set -euo pipefail if [ -z "${COMPOSE_ENV}" ]; then echo "Required GitHub secret COMPOSE_ENV is not configured." >&2 exit 1 fi if [ -z "${COMPOSE_ENV_STAGING}" ]; then echo "Required GitHub secret COMPOSE_ENV_STAGING is not configured." >&2 exit 1 fi printf '%s\n' "$COMPOSE_ENV" > .env printf '%s\n' "$COMPOSE_ENV_STAGING" > .env.staging - name: Validate compose contracts run: | docker compose -f docker-compose.yml -f docker-compose.prod.yml config > /dev/null docker compose -f docker-compose.example.yml config > /dev/null - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 cache: npm cache-dependency-path: services/edge-broker/package-lock.json - name: Install dependencies working-directory: services/edge-broker run: npm ci - name: Run edge broker tests working-directory: services/edge-broker run: npm test edge-gateway-backend: name: Edge Gateway Backend (required) runs-on: [self-hosted, Linux, X64, default] env: COMPOSE_FILE: docker-compose.yml:.github/docker-compose.ci.yml TRAEFIK_WEB_PORT: "18080" TRAEFIK_WEBSECURE_PORT: "18443" TRAEFIK_WEBSECURE_STAGING_PORT: "18433" TRAEFIK_METRICS_PORT: "19100" EDGE_GATEWAY_E2E_BASE_URL: "http://localhost:18080/api" steps: - name: Checkout uses: actions/checkout@v4 - name: Materialize compose env files env: COMPOSE_ENV: ${{ secrets.COMPOSE_ENV }} COMPOSE_ENV_STAGING: ${{ secrets.COMPOSE_ENV_STAGING }} run: | set -euo pipefail if [ -z "${COMPOSE_ENV}" ]; then echo "Required GitHub secret COMPOSE_ENV is not configured." >&2 exit 1 fi if [ -z "${COMPOSE_ENV_STAGING}" ]; then echo "Required GitHub secret COMPOSE_ENV_STAGING is not configured." >&2 exit 1 fi printf '%s\n' "$COMPOSE_ENV" > .env printf '%s\n' "$COMPOSE_ENV_STAGING" > .env.staging - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 22 - name: Boot local stack run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml up -d traefik redis mysql-debug edge-broker php1 caddy - name: Sync PHP app checkout run: tar -C services/nginx/app -cf - . | docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 tar -C /var/www/html -xf - - name: Resolve dependencies run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc "cd /var/www/html && composer update --no-interaction --prefer-dist" - name: Verify edge gateway test files run: > docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc "cd /var/www/html && php -r '\$composer = json_decode(file_get_contents(\"composer.json\"), true); echo \"Composer scripts: \", implode(\",\", array_keys(\$composer[\"scripts\"] ?? [])), PHP_EOL;' && find tests/Api -maxdepth 1 -type f -name 'EdgeGateway*ApiTest.php' -print && test -f tests/Api/EdgeGatewayAgentApiTest.php && test -f tests/Api/EdgeGatewayBrokerApiTest.php && test -f tests/Api/EdgeGatewayOperatorApiTest.php" - name: Run edge gateway API tests run: > docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc "cd /var/www/html && RUN_API_TESTS=1 API_TEST_BOOTSTRAP_SCHEMA=1 CONFIG_DB_TARGET=debug API_TEST_REQUEST_TIMEOUT=180 EDGE_GATEWAY_VIEW_CACHE_TTL=0 EDGE_BROKER_URL= vendor/bin/pest tests/Api/EdgeGatewayAgentApiTest.php tests/Api/EdgeGatewayBrokerApiTest.php tests/Api/EdgeGatewayOperatorApiTest.php --colors=always" - name: Run edge gateway integration tests run: > docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc "cd /var/www/html && RUN_INTEGRATION_TESTS=1 CONFIG_DB_TARGET=debug EDGE_BROKER_URL= vendor/bin/pest tests/Integration/EdgeGateway --colors=always" - name: Run edge gateway E2E smoke run: | set -euo pipefail compose_project="${COMPOSE_PROJECT_NAME:-$(basename "$PWD")}" docker run --rm \ --network "${compose_project}_default" \ -e COMPOSE_FILE="$COMPOSE_FILE" \ -e EDGE_GATEWAY_E2E_BASE_URL="http://caddy" \ -e EDGE_GATEWAY_E2E_COMPOSE_PROJECT="$compose_project" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "$PWD:$PWD" \ -w "$PWD" \ node:22-alpine \ sh -lc "apk add --no-cache docker-cli docker-cli-compose >/dev/null && node scripts/edge-gateway-e2e.mjs" - name: Tear down local stack if: always() run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml down -v integration: name: Integration (advisory) runs-on: [self-hosted, Linux, X64, default] continue-on-error: true steps: - name: Checkout uses: actions/checkout@v4 - name: Materialize compose env files env: COMPOSE_ENV: ${{ secrets.COMPOSE_ENV }} COMPOSE_ENV_STAGING: ${{ secrets.COMPOSE_ENV_STAGING }} run: | set -euo pipefail if [ -z "${COMPOSE_ENV}" ]; then echo "Required GitHub secret COMPOSE_ENV is not configured." >&2 exit 1 fi if [ -z "${COMPOSE_ENV_STAGING}" ]; then echo "Required GitHub secret COMPOSE_ENV_STAGING is not configured." >&2 exit 1 fi printf '%s\n' "$COMPOSE_ENV" > .env printf '%s\n' "$COMPOSE_ENV_STAGING" > .env.staging - name: Boot integration stack run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml up -d redis mysql-debug php1 - name: Sync PHP app checkout run: tar -C services/nginx/app -cf - . | docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 tar -C /var/www/html -xf - - name: Resolve dependencies run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc "cd /var/www/html && composer update --no-interaction --prefer-dist" - name: Run integration tests run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T -e RUN_INTEGRATION_TESTS=1 php1 sh -lc "cd /var/www/html && composer test:integration" - name: Tear down integration stack if: always() run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml down -v api: name: API (advisory) runs-on: [self-hosted, Linux, X64, default] continue-on-error: true steps: - name: Checkout uses: actions/checkout@v4 - name: Materialize compose env files env: COMPOSE_ENV: ${{ secrets.COMPOSE_ENV }} COMPOSE_ENV_STAGING: ${{ secrets.COMPOSE_ENV_STAGING }} run: | set -euo pipefail if [ -z "${COMPOSE_ENV}" ]; then echo "Required GitHub secret COMPOSE_ENV is not configured." >&2 exit 1 fi if [ -z "${COMPOSE_ENV_STAGING}" ]; then echo "Required GitHub secret COMPOSE_ENV_STAGING is not configured." >&2 exit 1 fi printf '%s\n' "$COMPOSE_ENV" > .env printf '%s\n' "$COMPOSE_ENV_STAGING" > .env.staging - name: Boot API stack run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml up -d redis mysql-debug php1 - name: Sync PHP app checkout run: tar -C services/nginx/app -cf - . | docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 tar -C /var/www/html -xf - - name: Resolve dependencies run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc "cd /var/www/html && composer update --no-interaction --prefer-dist" - name: Run API tests run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T -e RUN_API_TESTS=1 -e API_TEST_BOOTSTRAP_SCHEMA=1 php1 sh -lc "cd /var/www/html && composer test:api" - name: Tear down API stack if: always() run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml down -v