# ============================================================================= # AI-assisted test coverage pipeline (EQDEV-620 pattern) # Goal: raise SonarQube line coverage > 91% by ADDING tests only. # Hard constraints: no pom.xml changes, no src/main changes. # # This file shows BOTH options. In practice pick one generation path # (Option 1 OR Option 2) and ALWAYS keep the `guardrail:enforce` job, # because that is what actually enforces "tests only". # ============================================================================= stages: - test - ai-augment # Option 1 only (manual / scheduled). Remove if using Option 2. - guardrail # shared, runs on every MR - sonar variables: MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository" TEST_DIR: "src/test" # Model string for Claude Opus 4.8 (Option 1) CLAUDE_MODEL: "claude-opus-4-8" # ----------------------------------------------------------------------------- # Baseline: run tests, produce JaCoCo report. Sonar reads # target/site/jacoco/jacoco.xml — no pom change needed if jacoco is already # wired up (it is, since Sonar already reports coverage today). # ----------------------------------------------------------------------------- test:coverage: stage: test image: maven:3.9-eclipse-temurin-21 script: - mvn -B clean verify artifacts: when: always paths: - target/site/jacoco/jacoco.xml - target/jacoco.exec reports: junit: target/surefire-reports/TEST-*.xml expire_in: 1 week # ============================================================================= # OPTION 1 — In-pipeline autonomous agent (Claude Opus 4.8, headless) # Manual trigger or scheduled. Generates tests, validates, opens an MR. # NEVER auto-merges. Requires ANTHROPIC_API_KEY as a masked/protected var. # ============================================================================= ai:augment-coverage: stage: ai-augment image: maven:3.9-eclipse-temurin-21 needs: ["test:coverage"] rules: - if: '$CI_PIPELINE_SOURCE == "schedule"' - if: '$CI_PIPELINE_SOURCE == "web"' # manual run from UI when: manual before_script: # Install Claude Code (Node 20+ required) - apt-get update && apt-get install -y nodejs npm git jq - npm install -g @anthropic-ai/claude-code # ---- Filesystem guardrail: make production code physically read-only ---- - chmod -R a-w src/main pom.xml script: - export ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" - | claude -p "Read target/site/jacoco/jacoco.xml. Identify the classes with the lowest line coverage. Write JUnit 5 tests ONLY under src/test/java to cover the uncovered branches and lines. Mirror the existing test package structure and naming conventions. Use meaningful behavioral assertions — do NOT write tests that merely execute lines without asserting outcomes. You may ONLY create or edit files under src/test/. Do NOT touch src/main or pom.xml." \ --model "$CLAUDE_MODEL" \ --permission-mode acceptEdits \ --allowedTools "Read,Write,Edit,Bash(mvn:*)" # ---- Validate the generated tests actually compile and pass ---- - chmod -R u+w src/main pom.xml # restore for the build step - mvn -B clean verify # ---- Hard diff gate before we let anything leave this job ---- - !reference [.diff_guardrail, script] # ---- Open an MR; do NOT push to the default branch ---- - | BRANCH="ai/coverage-$CI_PIPELINE_ID" git config user.email "ci-bot@be-digital.biz" git config user.name "coverage-bot" git checkout -b "$BRANCH" git add src/test git commit -m "test(coverage): AI-generated tests for EQDEV-620 [skip ci]" git push -o merge_request.create \ -o merge_request.target="$CI_DEFAULT_BRANCH" \ -o merge_request.title="AI coverage tests (review required)" \ https://oauth2:${CI_PUSH_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git \ "$BRANCH" # ============================================================================= # SHARED GUARDRAIL — runs on every MR. This is the real enforcement of # "tests only / no pom / no main". Keep this job regardless of which option. # ============================================================================= .diff_guardrail: script: - | git fetch origin "$CI_DEFAULT_BRANCH" CHANGED=$(git diff --name-only "origin/$CI_DEFAULT_BRANCH"...HEAD) echo "Changed files:"; echo "$CHANGED" # 1) anything outside src/test/ is a violation BAD=$(echo "$CHANGED" | grep -vE '^src/test/' || true) if [ -n "$BAD" ]; then echo "GUARDRAIL VIOLATION — files changed outside src/test/:" echo "$BAD" exit 1 fi # 2) explicit pom.xml check (belt and suspenders) if echo "$CHANGED" | grep -q 'pom.xml'; then echo "GUARDRAIL VIOLATION — pom.xml must not change" exit 1 fi echo "Guardrail passed: changes are confined to src/test/" guardrail:enforce: stage: guardrail image: alpine/git:latest rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' script: - !reference [.diff_guardrail, script] # ============================================================================= # OPTIONAL quality gate against coverage-theater: mutation testing. # Fails if AI-generated tests don't actually kill mutants (i.e. assert nothing). # Requires the PIT plugin available via CLI; no pom change if run as a goal. # ============================================================================= test:mutation: stage: guardrail image: maven:3.9-eclipse-temurin-21 rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' when: manual # keep manual to control runtime cost script: - mvn -B org.pitest:pitest-maven:mutationCoverage -Dpit.mutationThreshold=70 allow_failure: false # ============================================================================= # Sonar scan — quality gate (incl. coverage > 91%) is configured SERVER-SIDE. # ============================================================================= sonar: stage: sonar image: maven:3.9-eclipse-temurin-21 needs: ["test:coverage"] script: - mvn -B sonar:sonar -Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml -Dsonar.qualitygate.wait=true # fail pipeline if gate (91%) not met # ============================================================================= # OPTION 2 — Dev-side generation (Copilot or Claude Code on your machine). # There is NO generation job in CI. You run, locally: # # Copilot: open the low-coverage class, "Copilot: Generate Tests", # review assertions, save under src/test/java. # Claude Code: claude -p "write JUnit 5 tests under src/test for the # uncovered lines in , behavioral assertions only" # # Then push. The pipeline runs ONLY: test:coverage -> guardrail:enforce -> # test:mutation -> sonar. The guardrail blocks any stray main/pom change. # This is the recommended starting point for a regulated codebase. # =============================================================================