name: SonarQube Analysis on: push: branches: [main] pull_request: types: [opened, synchronize, reopened] workflow_dispatch: jobs: sonarqube: runs-on: ubuntu-latest # The scanner needs every coverage report in one workspace, so the suites run # here rather than being passed between jobs as artifacts. That makes this the # long job. The timeout is a stop, not a budget: the integration suite has # hung after completing before (see backend-integration.yml) and cost three # hours of runner time. timeout-minutes: 30 services: postgres: image: postgres:16 env: POSTGRES_USER: redefined_test POSTGRES_PASSWORD: redefined_test POSTGRES_DB: redefined_test options: >- --health-cmd "pg_isready -U redefined_test" --health-interval 5s --health-timeout 5s --health-retries 10 env: # Consumed by the integration suite's setup files. TEST_PGHOST: postgres TEST_PGPORT: 5432 TEST_PGUSER: redefined_test TEST_PGPASSWORD: redefined_test TEST_PGDATABASE: redefined_test # Consumed by the backend process the end-to-end run drives. PGHOST: postgres PGPORT: 5432 PGUSER: redefined_test PGPASSWORD: redefined_test PGDATABASE: redefined_test PORT: 3000 DEMO_MODE: 'true' UPLOADS_DIR: /tmp/redefined-uploads # NODE_ENV is deliberately unset: `npm install` omits devDependencies when # NODE_ENV=production, which strips tsc/vite/@playwright/test and breaks the # build. It would also flip the session cookie to Secure, which the e2e run # serves over plain http. steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node uses: actions/setup-node@v4 with: node-version: '20' - name: Install backend deps run: npm install working-directory: backend - name: Install frontend deps run: npm install working-directory: frontend - name: TypeScript build check (backend) run: npm run build working-directory: backend - name: TypeScript build check (frontend) run: npm run build working-directory: frontend - name: Check the Sonar tsconfig has not drifted run: node scripts/check-sonar-tsconfig.js - name: Run migrations run: node migrate.js up working-directory: backend - name: Backend unit tests with coverage run: npm run test:unit:cov working-directory: backend # Covers everything in src/routes, which the unit suite does not touch — # without this the backend reports around 11% rather than the ~73% it # actually has. - name: Backend integration tests with coverage run: npm run test:integration:cov working-directory: backend - name: Start backend for the end-to-end run run: | mkdir -p /tmp/redefined-uploads node dist/server.js > /tmp/backend.log 2>&1 & for i in $(seq 1 30); do if node -e "require('http').get('http://localhost:3000/api/config', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"; then echo "Backend ready after ${i}s" exit 0 fi sleep 1 done echo "Backend did not become ready within 30s:" cat /tmp/backend.log exit 1 working-directory: backend - name: Install Playwright browsers run: npx playwright install --with-deps chromium working-directory: frontend # Runs against an istanbul-instrumented dev server, which is what produces # window.__coverage__ for the fixture to collect. - name: Frontend end-to-end tests with coverage run: npm run test:e2e:cov working-directory: frontend - name: Backend log if: failure() run: cat /tmp/backend.log # Fails when nothing was collected rather than writing an empty report. An # uninstrumented dev server lets every test pass while gathering nothing, # and the resulting 0% reads as "the tests stopped covering things". - name: Merge frontend coverage run: npm run coverage:report working-directory: frontend - name: SonarQube Scan uses: sonarsource/sonarqube-scan-action@v4 env: SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}