name: Integration Test on: pull_request: branches: [ "main" ] workflow_dispatch: {} concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: # ── Job 1: Platform Conformance ─────────────────────────────────────────── platform-check: name: Platform Conformance runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Platform conformance uses: ./.gitea/actions/platform-check # ── Job 2: Unit Tests + Container Smoke ─────────────────────────────────── smoke-test: name: Unit Tests + Container Smoke runs-on: ubuntu-latest needs: platform-check timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@v4 - name: Free disk space run: | docker system prune -f 2>/dev/null || true df -h / 2>/dev/null || true - name: Unit tests run: | RUNTIME=$(grep '^runtime:' .platform/config.yaml | sed 's/^runtime: //' | tr -d ' \r') echo "PLATFORM_RUNTIME=$RUNTIME" >> $GITHUB_ENV case "$RUNTIME" in go) if ! command -v go &>/dev/null; then GO_VERSION=1.23.6 wget -q "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" -O /tmp/go.tar.gz rm -rf /usr/local/go && tar -C /usr/local -xzf /tmp/go.tar.gz export PATH=$PATH:/usr/local/go/bin fi go mod tidy go test ./... -v ;; java-springboot|java-liberty) # Install JDK if not present (Debian runner) if ! command -v java &>/dev/null; then apt-get update -qq apt-get install -y --no-install-recommends openjdk-17-jdk-headless fi export JAVA_HOME=$(dirname $(dirname $(readlink -f "$(which java)"))) if ! command -v mvn &>/dev/null; then MVN_VERSION=3.9.9 wget -q "https://archive.apache.org/dist/maven/maven-3/${MVN_VERSION}/binaries/apache-maven-${MVN_VERSION}-bin.tar.gz" tar -xzf "apache-maven-${MVN_VERSION}-bin.tar.gz" -C /opt ln -sf "/opt/apache-maven-${MVN_VERSION}/bin/mvn" /usr/local/bin/mvn fi # package produces the build artifact (WAR/JAR) needed by the Dockerfile mvn package -B ;; nodejs-express|typescript-nestjs) npm install npm test ;; python-fastapi) pip install -r requirements.txt -q # exit 5 = no tests collected — treat as pass pytest app/ -v --tb=short || [ $? -eq 5 ] ;; *) echo "Unknown runtime '$RUNTIME' — skipping unit tests" ;; esac - name: Install Docker CLI run: command -v docker &>/dev/null || (apt-get update -qq && apt-get install -y docker.io) - name: Build container image run: docker build -t ci-image:test . - name: Start service and wait for health run: | # Unique name per run — prevents cross-job container conflicts on shared Docker daemon CONTAINER_NAME="ci-${GITHUB_RUN_ID}" PORT=$(grep '^container_port:' .platform/config.yaml | sed 's/^container_port: //' | tr -d ' \r') HEALTH_PATH=$(grep '^health_path:' .platform/config.yaml | sed 's/^health_path: //' | tr -d ' \r') PORT="${PORT:-8080}" HEALTH_PATH="${HEALTH_PATH:-/health}" echo "CONTAINER_NAME=${CONTAINER_NAME}" >> $GITHUB_ENV echo "CONTAINER_PORT=${PORT}" >> $GITHUB_ENV echo "HEALTH_PATH=${HEALTH_PATH}" >> $GITHUB_ENV # No port binding — connect to container's bridge IP directly. # The runner itself runs inside a container (DinD via socket); the ci-service # container is placed on the same Docker bridge and its IP is reachable from # the runner container. Using localhost:HOST_PORT would hit the runner's own # loopback, not the host's port mapping. # # OTEL_SDK_DISABLED=true: disables the OTel Java agent's class-transformation # and exporter threads in CI where no collector is running. Without this, # Spring Boot + OTel agent takes 60-90s to start, exceeding the health timeout. docker run -d --name "${CONTAINER_NAME}" -e OTEL_SDK_DISABLED=true ci-image:test # Wait for bridge IP to be assigned (< 2s normally) for i in $(seq 1 10); do CONTAINER_IP=$(docker inspect "${CONTAINER_NAME}" --format '{{.NetworkSettings.IPAddress}}' 2>/dev/null) [ -n "${CONTAINER_IP}" ] && break sleep 1 done echo "CONTAINER_IP=${CONTAINER_IP}" >> $GITHUB_ENV echo "Container IP: ${CONTAINER_IP}" echo "Waiting for ${HEALTH_PATH} on ${CONTAINER_IP}:${PORT} (up to 180s)..." DEADLINE=$(($(date +%s) + 180)) while true; do if curl -sf "http://${CONTAINER_IP}:${PORT}${HEALTH_PATH}" >/dev/null 2>&1; then break fi if [ $(date +%s) -ge $DEADLINE ]; then echo "Timeout waiting for health check" echo "Container logs:" docker logs "${CONTAINER_NAME}" 2>&1 | tail -30 exit 1 fi if ! docker ps --filter "name=${CONTAINER_NAME}" --format '{{.Status}}' | grep -q Up; then echo "Container exited:" docker logs "${CONTAINER_NAME}" 2>&1 | tail -30 exit 1 fi echo " still waiting..."; sleep 3 done echo "✓ Service started at ${CONTAINER_IP}:${PORT}, health endpoint: ${HEALTH_PATH}" - name: Validate health response run: | curl -sf "http://${CONTAINER_IP}:${CONTAINER_PORT}${HEALTH_PATH}" > /tmp/health.json echo "Health response:" cat /tmp/health.json python3 - <<'PYEOF' import json, sys body = json.load(open('/tmp/health.json')) status = str(body.get('status') or '').upper() if status not in ('UP', 'OK', 'HEALTHY'): print(f" ✗ unexpected health status: {body.get('status')!r}") sys.exit(1) print(f" ✓ health status: {body['status']}") PYEOF echo "✓ Container smoke test: PASSED" - name: Write job summary if: always() run: | COMPONENT=$(python3 -c "import yaml; docs=list(yaml.safe_load_all(open('catalog-info.yaml'))); d=next((x for x in docs if isinstance(x,dict) and x.get('kind')=='Component'),docs[0]); print(d['metadata']['name'])" 2>/dev/null || echo "test-for-174--015") RUNTIME=$(grep '^runtime:' .platform/config.yaml | sed 's/^runtime: //' | tr -d ' \r' 2>/dev/null || echo "unknown") echo "## Integration Test: \`${COMPONENT}\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Stage | Detail |" >> $GITHUB_STEP_SUMMARY echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY echo "| Platform conformance | catalog-info.yaml ✓ \`.platform/initialized.md\` ✓ |" >> $GITHUB_STEP_SUMMARY echo "| Unit tests | runtime: \`${RUNTIME}\` |" >> $GITHUB_STEP_SUMMARY echo "| Container smoke | \`GET ${HEALTH_PATH:-/health}\` → HTTP 200 |" >> $GITHUB_STEP_SUMMARY - name: Cleanup if: always() run: docker rm -f "ci-${GITHUB_RUN_ID}" 2>/dev/null || true - name: Post commit status if: always() env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} JOB_STATUS: ${{ job.status }} run: | STATE=$([[ "$JOB_STATUS" == "success" ]] && echo "success" || echo "failure") DESC=$([[ "$STATE" == "success" ]] && echo "All checks passed" || echo "Some checks failed") curl -sf -X POST \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/statuses/${GITHUB_SHA}" \ -d "{\"state\":\"${STATE}\",\"context\":\"Integration Test / Unit Tests + Container Smoke (workflow_dispatch)\",\"description\":\"${DESC}\"}" \ || true