name: Tests

on:
  pull_request:
  push:
    branches: [ master ]
  schedule:
    - cron: "0 3 * * *"  # 03:00 UTC daily
  workflow_dispatch:

permissions:
  contents: read

jobs:
  test:
    name: Node ${{ matrix.node-version }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        node-version: [22, 24, 26]

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Set up Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install dependencies
        run: npm ci

      - name: Run unit tests
        run: npm test && npm run ci

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: test-results-${{ matrix.node-version }}
          path: test/

  security-audit:
    name: Security Audit
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Set up Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 24

      - name: Run npm audit
        id: npm_audit
        # Do not audit dev dependencies
        run: |
          set +e
          npm audit --omit=dev --audit-level=high --json > npm-audit.json
          audit_exit_code=$?
          echo "exit_code=${audit_exit_code}" >> "$GITHUB_OUTPUT"
          exit 0

      - name: Generate audit summary
        run: python3 ci/audit_report.py npm-audit.json >> "$GITHUB_STEP_SUMMARY"

      - name: Upload audit output
        uses: actions/upload-artifact@v7
        with:
          name: npm-audit-report
          path: npm-audit.json

      - name: Fail if vulnerabilities found
        run: |
          if [[ "${{ steps.npm_audit.outputs.exit_code }}" != "0" ]]; then
            echo "npm audit detected vulnerabilities at or above the configured threshold." >&2
            exit 1
          fi

  report:
    name: Unit Test Report
    needs: test
    if: always()
    runs-on: ubuntu-latest
    permissions:
      contents: read
      checks: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Download test results
        uses: actions/download-artifact@v8
        with:
          pattern: test-results-*
          merge-multiple: false

      - name: Publish test report
        # dorny/test-reporter v3.0.0 — pinned to SHA for supply-chain safety
        uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2
        with:
          name: Unit Test Results
          path: "test-results-*/tests.xml"
          reporter: java-junit

  coverage:
    name: Coverage Report
    needs: test
    if: always()
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Download test results
        uses: actions/download-artifact@v8
        with:
          pattern: test-results-*
          merge-multiple: false

      - name: Generate coverage summary
        run: |
          args=()
          for dir in test-results-*/; do
            version="${dir#test-results-}"
            version="${version%/}"
            xml="${dir}coverage/cobertura-coverage.xml"
            if [[ -f "$xml" ]]; then
              args+=("${version}:${xml}")
            else
              echo "::warning::Coverage report not found for Node ${version}: ${xml}" >&2
            fi
          done
          if [[ ${#args[@]} -eq 0 ]]; then
            echo "::error::No coverage reports found; cannot generate summary." >&2
            exit 1
          fi
          python3 ci/coverage_report.py "${args[@]}" >> "$GITHUB_STEP_SUMMARY"
