Skip to content

Merge pull request #457 from Hack23/dependabot/npm_and_yarn/developme… #1894

Merge pull request #457 from Hack23/dependabot/npm_and_yarn/developme…

Merge pull request #457 from Hack23/dependabot/npm_and_yarn/developme… #1894

name: Test and Report
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
# Set default permissions to read-only
permissions: read-all
# Resilience against transient npm registry / mirror failures.
# Applied to every job; npm honours these env vars natively.
env:
# Single source of truth for all setup-node steps and cache keys in this workflow.
NODE_VERSION: "26"
NPM_CONFIG_FETCH_RETRIES: "5"
NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: "20000"
NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: "120000"
NPM_CONFIG_FETCH_TIMEOUT: "300000"
jobs:
prepare:
runs-on: ubuntu-latest
# Only needs read permissions
permissions:
contents: read # Required to check out code
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ env.NODE_VERSION }}
# Built-in npm cache (single source of truth — no extra actions/cache step).
# Cache key automatically derived from package-lock.json + Node major version.
cache: "npm"
- name: Install dependencies
run: npm ci
build-validation:
needs: prepare
runs-on: ubuntu-latest
# Needs write permissions to upload artifacts
permissions:
contents: write # Required to check out code
actions: read # Required to use GitHub actions
id-token: write # Required for attestation
pull-requests: write # Required to upload artifacts (implicit permission)
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ env.NODE_VERSION }}
# Built-in npm cache (single source of truth — no extra actions/cache step).
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Cache build artifacts
id: build-cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: dist
# Unique key: cache-version + OS + Node major + lockfile hash + source hash + tsconfig hash.
# Bumping the `build-vN-` prefix expires every old build cache atomically.
key: build-v2-${{ runner.os }}-node${{ env.NODE_VERSION }}-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('src/**/*.ts', 'tsconfig*.json') }}
# TypeScript type checking
- name: Type check
run: npm run type-check
# TypeScript type checking for test helpers (mockFactory.ts, assertions.ts)
- name: Type check test helpers
run: npm run type-check:helpers
# Run linter
- name: Lint
run: npm run lint
# Check for unused dependencies with knip
- name: Check for unused dependencies
run: npm run knip
# Build the project (ensures dist/ output is valid)
- name: Build
# cache-hit is emitted as the string "true"/"false" by actions/cache.
if: steps.build-cache.outputs.cache-hit != 'true'
run: npm run build
# Verify package can be packed (catches prepublish issues before release)
- name: Validate package
run: npm pack --dry-run
# Check licenses
- name: Check licenses
run: npm run test:licenses
- name: Generate SBOM
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
id: sbom
with:
format: spdx-json
output-file: european-parliament-mcp-server.spdx.json
artifact-name: european-parliament-mcp-server
- name: Install SBOMQS
# Resilient download: retry transient network failures, fail fast on hard errors.
run: |
set -euo pipefail
SBOMQS_VERSION="1.2.0"
SBOMQS_BINARY="sbomqs-linux-amd64"
SBOMQS_DOWNLOAD_PATH="/tmp/sbomqs"
SBOMQS_BASE_URL="https://github.com/interlynk-io/sbomqs/releases/download/v${SBOMQS_VERSION}"
curl --fail --location --show-error --silent \
--retry 5 --retry-delay 5 --retry-max-time 120 \
--connect-timeout 30 --max-time 180 \
-o "${SBOMQS_DOWNLOAD_PATH}" \
"${SBOMQS_BASE_URL}/${SBOMQS_BINARY}"
curl --fail --location --show-error --silent \
--retry 5 --retry-delay 5 --retry-max-time 120 \
--connect-timeout 30 --max-time 180 \
-o /tmp/sbomqs_checksums.txt \
"${SBOMQS_BASE_URL}/sbomqs_${SBOMQS_VERSION}_checksums.txt"
expected_sha="$(awk -v file="${SBOMQS_BINARY}" '$NF == file { print $1; exit }' /tmp/sbomqs_checksums.txt)"
if [ -z "${expected_sha}" ]; then
echo "::error::Unable to find checksum entry for ${SBOMQS_BINARY} in checksums file. Verify the release asset and checksum format."
exit 1
fi
printf '%s %s\n' "${expected_sha}" "${SBOMQS_DOWNLOAD_PATH}" | sha256sum -c -
sudo mv "${SBOMQS_DOWNLOAD_PATH}" /usr/local/bin/sbomqs
sudo chmod a+x /usr/local/bin/sbomqs
- name: Details SBOM Quality
run: sbomqs score european-parliament-mcp-server.spdx.json --detailed
- name: Check SBOM Quality
run: |
score=$(sbomqs score european-parliament-mcp-server.spdx.json --json | jq '.files[0].avg_score')
echo "SBOM Score: $score/10"
if (( $(echo "$score < 7.0" | bc -l) )); then
echo "::error::SBOM quality score too low: $score"
exit 1
fi
- name: Upload build artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: build-output
path: |
dist
european-parliament-mcp-server.spdx.json
if-no-files-found: warn
unit-tests:
needs: [prepare, build-validation]
runs-on: ubuntu-latest
# Needs write permissions to upload artifacts
permissions:
contents: write # Required to check out code
actions: read # Required to use GitHub actions
checks: write # Required to upload artifacts (implicit permission)
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ env.NODE_VERSION }}
# Built-in npm cache (single source of truth — no extra actions/cache step).
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run unit tests with coverage
run: npm run test:coverage
- name: Check coverage thresholds
run: |
# Extract coverage from report
if [ -f "coverage/coverage-summary.json" ]; then
COVERAGE=$(jq '.total.lines.pct' coverage/coverage-summary.json)
echo "Current coverage: $COVERAGE%"
# Convert coverage to integer (percentage * 100) for comparison
COVERAGE_INT=$(awk -v c="$COVERAGE" 'BEGIN { printf "%d", c * 100 }')
# Check if coverage meets requirements (80% minimum = 8000)
if [ "$COVERAGE_INT" -lt 8000 ]; then
echo "⚠️ Coverage $COVERAGE% is below required 80%"
echo "::warning::Coverage $COVERAGE% is below required 80%"
else
echo "✅ Coverage $COVERAGE% meets requirement"
fi
else
echo "⚠️ Coverage summary not found"
fi
continue-on-error: true
- name: Upload coverage to Codecov
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5.3.2
with:
files: ./coverage/coverage-final.json
fail_ci_if_error: false
verbose: true
continue-on-error: true
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-report
path: coverage
if-no-files-found: warn
report:
needs: [unit-tests]
runs-on: ubuntu-latest
if: always()
# Needs write permissions to upload artifacts
permissions:
contents: write # Required to check out code
actions: read # Required to use GitHub actions
checks: write # Required to upload artifacts (implicit permission)
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
with:
egress-policy: audit
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: artifacts
continue-on-error: true
- name: Upload combined reports
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-reports
path: |
artifacts/coverage-report
if-no-files-found: warn
- name: Test summary
run: |
echo "## Test Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Status" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Build validation: Passed" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Unit tests: Passed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Note" >> $GITHUB_STEP_SUMMARY
echo "Integration and E2E tests run in separate workflow: integration-tests.yml" >> $GITHUB_STEP_SUMMARY