Les enseignants ont besoin de moyennes à jour immédiatement après la publication ou modification des notes, sans attendre un batch nocturne. Le système recalcule via Domain Events synchrones : statistiques d'évaluation (min/max/moyenne/médiane), moyennes matières pondérées (normalisation /20), et moyenne générale par élève. Les résultats sont stockés dans des tables dénormalisées avec cache Redis (TTL 5 min). Trois endpoints API exposent les données avec contrôle d'accès par rôle. Une commande console permet le backfill des données historiques au déploiement.
329 lines
10 KiB
YAML
329 lines
10 KiB
YAML
# GitHub Actions CI/CD Pipeline for Test Execution
|
|
# Generated by BMad TEA Agent - Test Architect Module
|
|
# Optimized for: Parallel Sharding, Burn-In Loop
|
|
# Stack: {test_stack_type} | Framework: {test_framework}
|
|
#
|
|
# Variables to customize per project:
|
|
# INSTALL_CMD - dependency install command (e.g., npm ci, pnpm install --frozen-lockfile, yarn --frozen-lockfile)
|
|
# TEST_CMD - main test command (e.g., npm run test:e2e, npm test, npx vitest)
|
|
# LINT_CMD - lint command (e.g., npm run lint)
|
|
# BROWSER_INSTALL - browser install command (frontend/fullstack only; omit for backend)
|
|
# BROWSER_CACHE_PATH - browser cache path (frontend/fullstack only; omit for backend)
|
|
|
|
name: Test Pipeline
|
|
|
|
on:
|
|
push:
|
|
branches: [main, develop]
|
|
pull_request:
|
|
branches: [main, develop]
|
|
schedule:
|
|
# Weekly burn-in on Sundays at 2 AM UTC
|
|
- cron: "0 2 * * 0"
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# Lint stage - Code quality checks
|
|
lint:
|
|
name: Lint
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Determine Node version
|
|
id: node-version
|
|
run: |
|
|
if [ -f .nvmrc ]; then
|
|
echo "value=$(cat .nvmrc)" >> "$GITHUB_OUTPUT"
|
|
echo "Using Node from .nvmrc"
|
|
else
|
|
echo "value=24" >> "$GITHUB_OUTPUT"
|
|
echo "Using default Node 24 (current LTS)"
|
|
fi
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ steps.node-version.outputs.value }}
|
|
cache: "npm"
|
|
|
|
- name: Install dependencies
|
|
run: npm ci # Replace with INSTALL_CMD
|
|
|
|
- name: Run linter
|
|
run: npm run lint # Replace with LINT_CMD
|
|
|
|
# Test stage - Parallel execution with sharding
|
|
test:
|
|
name: Test (Shard ${{ matrix.shard }})
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
needs: lint
|
|
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
shard: [1, 2, 3, 4]
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Determine Node version
|
|
id: node-version
|
|
run: |
|
|
if [ -f .nvmrc ]; then
|
|
echo "value=$(cat .nvmrc)" >> "$GITHUB_OUTPUT"
|
|
echo "Using Node from .nvmrc"
|
|
else
|
|
echo "value=22" >> "$GITHUB_OUTPUT"
|
|
echo "Using default Node 22 (current LTS)"
|
|
fi
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ steps.node-version.outputs.value }}
|
|
cache: "npm"
|
|
|
|
- name: Cache Playwright browsers
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/.cache/ms-playwright
|
|
key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-playwright-
|
|
|
|
- name: Install dependencies
|
|
run: npm ci # Replace with INSTALL_CMD
|
|
|
|
# Frontend/Fullstack only — remove this step for backend-only stacks
|
|
- name: Install Playwright browsers
|
|
run: npx playwright install --with-deps chromium # Replace with BROWSER_INSTALL
|
|
|
|
- name: Run tests (shard ${{ matrix.shard }}/4)
|
|
run: npm run test:e2e -- --shard=${{ matrix.shard }}/4 # Replace with TEST_CMD + shard args
|
|
|
|
- name: Upload test results
|
|
if: failure()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: test-results-${{ matrix.shard }}
|
|
path: |
|
|
test-results/
|
|
playwright-report/
|
|
retention-days: 30
|
|
|
|
# Burn-in stage - Flaky test detection
|
|
burn-in:
|
|
name: Burn-In (Flaky Detection)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
needs: test
|
|
# Only run burn-in on PRs to main/develop or on schedule
|
|
if: github.event_name == 'pull_request' || github.event_name == 'schedule'
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Determine Node version
|
|
id: node-version
|
|
run: |
|
|
if [ -f .nvmrc ]; then
|
|
echo "value=$(cat .nvmrc)" >> "$GITHUB_OUTPUT"
|
|
echo "Using Node from .nvmrc"
|
|
else
|
|
echo "value=22" >> "$GITHUB_OUTPUT"
|
|
echo "Using default Node 22 (current LTS)"
|
|
fi
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ steps.node-version.outputs.value }}
|
|
cache: "npm"
|
|
|
|
# Frontend/Fullstack only — remove this step for backend-only stacks
|
|
- name: Cache Playwright browsers
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/.cache/ms-playwright # Replace with BROWSER_CACHE_PATH
|
|
key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
|
|
|
|
- name: Install dependencies
|
|
run: npm ci # Replace with INSTALL_CMD
|
|
|
|
# Frontend/Fullstack only — remove this step for backend-only stacks
|
|
- name: Install Playwright browsers
|
|
run: npx playwright install --with-deps chromium # Replace with BROWSER_INSTALL
|
|
|
|
# Note: Burn-in targets UI flakiness. For backend-only stacks, remove this job entirely.
|
|
- name: Run burn-in loop (10 iterations)
|
|
run: |
|
|
echo "🔥 Starting burn-in loop - detecting flaky tests"
|
|
for i in {1..10}; do
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "🔥 Burn-in iteration $i/10"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
npm run test:e2e || exit 1 # Replace with TEST_CMD
|
|
done
|
|
echo "✅ Burn-in complete - no flaky tests detected"
|
|
|
|
- name: Upload burn-in failure artifacts
|
|
if: failure()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: burn-in-failures
|
|
path: |
|
|
test-results/
|
|
playwright-report/
|
|
retention-days: 30
|
|
|
|
# Report stage - Aggregate and publish results
|
|
report:
|
|
name: Test Report
|
|
runs-on: ubuntu-latest
|
|
needs: [test, burn-in]
|
|
if: always()
|
|
|
|
steps:
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: artifacts
|
|
|
|
- name: Generate summary
|
|
run: |
|
|
echo "## Test Execution Summary" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Status**: ${{ needs.test.result }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Burn-in**: ${{ needs.burn-in.result }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Shards**: 4" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
|
|
if [ "${{ needs.burn-in.result }}" == "failure" ]; then
|
|
echo "⚠️ **Flaky tests detected** - Review burn-in artifacts" >> $GITHUB_STEP_SUMMARY
|
|
fi
|
|
|
|
# ============================================================================
|
|
# EXTENSION PATTERNS — Script Injection Prevention
|
|
# ============================================================================
|
|
# When extending this template into reusable workflows, manual dispatch
|
|
# workflows, or composite actions, NEVER use ${{ inputs.* }} directly in
|
|
# run: blocks. Always pass through env: intermediaries.
|
|
#
|
|
# KEY PRINCIPLE: Inputs must be DATA, not COMMANDS.
|
|
# Pass inputs through env: and interpolate as quoted arguments into fixed
|
|
# commands. NEVER accept command-shaped inputs (e.g., install-command,
|
|
# test-command) that get executed as shell code — even through env:.
|
|
#
|
|
# --- Reusable Workflow (workflow_call) ---
|
|
#
|
|
# on:
|
|
# workflow_call:
|
|
# inputs:
|
|
# test-grep:
|
|
# description: 'Test grep filter (data only — not a command)'
|
|
# type: string
|
|
# required: false
|
|
# default: ''
|
|
# base-ref:
|
|
# description: 'Base branch for diff'
|
|
# type: string
|
|
# required: false
|
|
# default: 'main'
|
|
# burn-in-count:
|
|
# description: 'Number of burn-in iterations'
|
|
# type: string
|
|
# required: false
|
|
# default: '10'
|
|
#
|
|
# jobs:
|
|
# test:
|
|
# runs-on: ubuntu-latest
|
|
# steps:
|
|
# - uses: actions/checkout@v4
|
|
# # Fixed command — not derived from inputs
|
|
# - name: Install dependencies
|
|
# run: npm ci
|
|
# # ✅ SAFE — input is DATA passed as an argument to a fixed command
|
|
# - name: Run tests
|
|
# env:
|
|
# TEST_GREP: ${{ inputs.test-grep }}
|
|
# run: |
|
|
# # Security: inputs passed through env: to prevent script injection
|
|
# if [ -n "$TEST_GREP" ]; then
|
|
# npx playwright test --grep "$TEST_GREP"
|
|
# else
|
|
# npx playwright test
|
|
# fi
|
|
#
|
|
# --- Manual Dispatch (workflow_dispatch) ---
|
|
#
|
|
# on:
|
|
# workflow_dispatch:
|
|
# inputs:
|
|
# test-grep:
|
|
# description: 'Test grep filter (data only — not a command)'
|
|
# type: string
|
|
# required: false
|
|
# environment:
|
|
# description: 'Target environment'
|
|
# type: choice
|
|
# options: [staging, production]
|
|
#
|
|
# jobs:
|
|
# run-tests:
|
|
# runs-on: ubuntu-latest
|
|
# steps:
|
|
# - uses: actions/checkout@v4
|
|
# # ✅ SAFE — input is DATA interpolated into a fixed command
|
|
# - name: Run selected tests
|
|
# env:
|
|
# TEST_GREP: ${{ inputs.test-grep }}
|
|
# run: |
|
|
# # Security: inputs passed through env: to prevent script injection
|
|
# npx playwright test --grep "$TEST_GREP"
|
|
#
|
|
# --- Composite Action (action.yml) ---
|
|
#
|
|
# inputs:
|
|
# test-grep:
|
|
# description: 'Test grep filter (data only — not a command)'
|
|
# required: false
|
|
# default: ''
|
|
# burn-in-count:
|
|
# description: 'Number of burn-in iterations'
|
|
# required: false
|
|
# default: '10'
|
|
#
|
|
# runs:
|
|
# using: composite
|
|
# steps:
|
|
# # ✅ SAFE — inputs are DATA arguments to fixed commands
|
|
# - name: Run burn-in
|
|
# shell: bash
|
|
# env:
|
|
# TEST_GREP: ${{ inputs.test-grep }}
|
|
# BURN_IN_COUNT: ${{ inputs.burn-in-count }}
|
|
# run: |
|
|
# # Security: inputs passed through env: to prevent script injection
|
|
# for i in $(seq 1 "$BURN_IN_COUNT"); do
|
|
# echo "Burn-in iteration $i/$BURN_IN_COUNT"
|
|
# npx playwright test --grep "$TEST_GREP" || exit 1
|
|
# done
|
|
#
|
|
# ❌ NEVER DO THIS:
|
|
# # Direct ${{ inputs.* }} in run: — GitHub expression injection
|
|
# - run: npx playwright test --grep "${{ inputs.test-grep }}"
|
|
#
|
|
# # Executing input-derived env var as a command — still command injection
|
|
# - env:
|
|
# CMD: ${{ inputs.test-command }}
|
|
# run: $CMD
|
|
# ============================================================================
|