Skip to content

Merge remote-tracking branch 'origin/main' into dependabot/npm_and_ya… #735

Merge remote-tracking branch 'origin/main' into dependabot/npm_and_ya…

Merge remote-tracking branch 'origin/main' into dependabot/npm_and_ya… #735

Workflow file for this run

name: Comprehensive Test Suite
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
# Enforced by cargo llvm-cov --fail-under-lines (line %, package-scoped runs).
COVERAGE_FAIL_LINES_PREDICT_IQ: "80"
COVERAGE_FAIL_LINES_PREDICTIQ_API: "17"
# Soroban contract WASM size limit (64 KB internal budget target)
# Soroban's actual limit is larger, but we enforce this stricter threshold
# to ensure contract remains performant and deployable across all networks.
WASM_SIZE_LIMIT_BYTES: "65536"
jobs:
prometheus-rules-test:
name: Prometheus SLO Rules Testing
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Prometheus tools
run: |
wget https://github.com/prometheus/prometheus/releases/download/v2.45.0/prometheus-2.45.0.linux-amd64.tar.gz
tar xzf prometheus-2.45.0.linux-amd64.tar.gz
sudo mv prometheus-2.45.0.linux-amd64/promtool /usr/local/bin/
- name: Check Prometheus rules syntax
run: promtool check rules performance/config/prometheus-slo-rules.yml
- name: Check alert rules syntax
run: promtool check rules performance/config/alerts.yaml
- name: Run Prometheus rules unit tests
run: promtool test rules performance/config/prometheus-slo-rules.test.yml
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo registry
uses: actions/cache@v5
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v5
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v5
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- name: Install Soroban CLI
run: |
cargo install --locked soroban-cli --features opt
- name: Run unit tests
run: cargo test --lib --workspace
working-directory: contracts/predict-iq
- name: Run module tests
run: cargo test --lib --features testutils
working-directory: contracts/predict-iq
integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache dependencies
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install Soroban CLI
run: cargo install --locked soroban-cli --features opt
- name: Run integration tests
run: cargo test --test '*' --workspace
working-directory: contracts/predict-iq
api-rate-limit-tests:
name: API Rate Limiting Integration Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache dependencies
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-api-${{ hashFiles('services/api/Cargo.lock') }}
- name: Run rate limiting integration tests
run: cargo test --test rate_limiting_tests
working-directory: services/api
gas-benchmarks:
name: Gas Benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache dependencies
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install Soroban CLI
run: cargo install --locked soroban-cli --features opt
- name: Run gas benchmarks
run: cargo test --benches --features testutils 2>&1 | tee benchmark-output.txt
working-directory: contracts/predict-iq
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "18"
- name: Compare gas benchmarks against baseline
working-directory: contracts/predict-iq
run: |
node scripts/compare-gas-benchmarks.js --branch main --threshold 10
- name: Upload benchmark report
if: always()
uses: actions/upload-artifact@v7
with:
name: gas-benchmark-report
path: contracts/predict-iq/target/gas-benchmark-report.md
retention-days: 30
- name: Comment PR with benchmark results
if: github.event_name == 'pull_request' && always()
uses: actions/github-script@v9
with:
script: |
const fs = require('fs');
const path = 'contracts/predict-iq/target/gas-benchmark-report.md';
if (fs.existsSync(path)) {
const report = fs.readFileSync(path, 'utf8');
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: report
});
}
- name: Fail if gas regression detected
if: failure() && github.event_name == 'pull_request'
run: |
echo "❌ Gas regression detected. Check the PR comment for details."
exit 1
save-gas-baseline:
name: Save Gas Benchmark Baseline
runs-on: ubuntu-latest
needs: [gas-benchmarks]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache dependencies
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install Soroban CLI
run: cargo install --locked soroban-cli --features opt
- name: Run gas benchmarks
run: cargo test --benches --features testutils
working-directory: contracts/predict-iq
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "18"
- name: Save baseline for main branch
working-directory: contracts/predict-iq
run: |
node scripts/compare-gas-benchmarks.js --save-baseline --branch main
- name: Create Pull Request with baseline updates
uses: peter-evans/create-pull-request@v6
with:
commit-message: "chore: update gas benchmark baseline for main branch"
title: "chore: update gas benchmark baseline"
body: |
## Automated Gas Benchmark Baseline Update
This PR updates the gas benchmark baseline after changes to main branch.
**Changes:**
- Updated `.gas-benchmarks/` files with latest benchmark results
**Note:** This is an automated PR. Please review the baseline changes before merging.
branch: chore/gas-baseline-${{ github.run_id }}
delete-branch: true
labels: |
ci/cd
automated
backend-coverage:
name: Backend coverage (contracts + API)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview
- name: Install cargo-llvm-cov
run: cargo install cargo-llvm-cov --locked
- name: Install Soroban CLI
run: cargo install --locked soroban-cli --features opt
- name: Prepare coverage output directory
run: mkdir -p coverage-reports
- name: Coverage gate — predict-iq (Soroban contract)
env:
CARGO_LLVM_COV_TARGET_DIR: ${{ github.workspace }}/llvm-cov-target/predict-iq
run: >
cargo llvm-cov -p predict-iq --all-features
--fail-under-lines ${COVERAGE_FAIL_LINES_PREDICT_IQ}
--lcov --output-path coverage-reports/predict-iq.lcov
--html --output-dir coverage-reports/predict-iq-html
- name: Coverage gate — predictiq-api (Rust API service)
env:
CARGO_LLVM_COV_TARGET_DIR: ${{ github.workspace }}/llvm-cov-target/predictiq-api
working-directory: services/api
run: >
cargo llvm-cov
--fail-under-lines ${COVERAGE_FAIL_LINES_PREDICTIQ_API}
--lcov --output-path ../../coverage-reports/predictiq-api.lcov
--html --output-dir ../../coverage-reports/predictiq-api-html
- name: Upload HTML + LCOV artifacts
uses: actions/upload-artifact@v7
with:
name: backend-coverage
path: |
coverage-reports/*.lcov
coverage-reports/predict-iq-html
coverage-reports/predictiq-api-html
if-no-files-found: error
- name: Upload predict-iq LCOV to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage-reports/predict-iq.lcov
flags: predict-iq
fail_ci_if_error: true
verbose: true
- name: Upload predictiq-api LCOV to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage-reports/predictiq-api.lcov
flags: predictiq-api
fail_ci_if_error: true
verbose: true
security-audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-audit
run: cargo install cargo-audit
- name: Run security audit — contracts/predict-iq
run: cargo audit
working-directory: contracts/predict-iq
- name: Run security audit — services/api
run: cargo audit
working-directory: services/api
sast-scanning:
name: SAST Security Scanning
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Run Semgrep SAST
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/rust
p/nodejs
p/typescript
p/javascript
generateSarif: true
- name: Upload Semgrep SARIF
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: semgrep.sarif
- name: Fail on critical findings
run: |
if [ -f semgrep.sarif ]; then
critical_count=$(jq '[.runs[].results[] | select(.level == "error")] | length' semgrep.sarif)
if [ "$critical_count" -gt 0 ]; then
echo "Found $critical_count critical security findings"
exit 1
fi
fi
secrets-scanning:
name: Secrets Scanning
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
- name: Run TruffleHog
uses: trufflesecurity/trufflehog@9f0b97f1600cd5f51e5ecb8380087807acb790f9 # main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --only-verified
dependency-scanning:
name: Dependency Vulnerability Scanning
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@314ff8b43182423b84c50b1670b0e10f858f2d98 # master
with:
scan-type: "fs"
scan-ref: "."
format: "sarif"
output: "trivy-results.sarif"
severity: "CRITICAL,HIGH"
exit-code: "1"
- name: Upload Trivy SARIF
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: trivy-results.sarif
- name: Scan Rust dependencies
run: |
cargo install cargo-audit
cargo audit --deny warnings
working-directory: contracts/predict-iq
- name: Scan Node.js dependencies
# Fails CI on high/critical npm vulnerabilities.
# Exception process: for known false positives, add an entry to
# frontend/.nsprc (npm audit ignore) with a justification comment
# and link to the tracking issue, then get security team approval
# before merging.
run: |
npm audit --audit-level=high
working-directory: frontend
container-scanning:
name: Container Image Scanning
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- name: Build API container image
run: docker build -t predictiq-api:${{ github.sha }} -f services/api/Dockerfile .
continue-on-error: true
- name: Scan API container with Trivy
uses: aquasecurity/trivy-action@314ff8b43182423b84c50b1670b0e10f858f2d98 # master
if: success()
with:
image-ref: "predictiq-api:${{ github.sha }}"
format: "sarif"
output: "trivy-api.sarif"
severity: "CRITICAL,HIGH"
exit-code: "1"
- name: Upload API container scan SARIF
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: trivy-api.sarif
category: trivy-api
- name: Build TTS container image
run: docker build -t predictiq-tts:${{ github.sha }} -f services/tts/Dockerfile services/tts/
continue-on-error: true
- name: Scan TTS container with Trivy
uses: aquasecurity/trivy-action@314ff8b43182423b84c50b1670b0e10f858f2d98 # master
if: success()
with:
image-ref: "predictiq-tts:${{ github.sha }}"
format: "sarif"
output: "trivy-tts.sarif"
severity: "CRITICAL,HIGH"
exit-code: "1"
- name: Upload TTS container scan SARIF
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: trivy-tts.sarif
category: trivy-tts
codeql-analysis:
name: CodeQL Analysis
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
strategy:
matrix:
language: ["javascript", "typescript", "rust"]
steps:
- uses: actions/checkout@v4
- name: Install Rust (for Rust analysis)
if: matrix.language == 'rust'
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
queries: security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{ matrix.language }}"
clippy:
name: Clippy Lints
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
components: clippy

Check failure on line 525 in .github/workflows/test.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/test.yml

Invalid workflow file

You have an error in your yaml syntax on line 525
- name: Run Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
working-directory: contracts/predict-iq
oracle-quality-gate:
name: Oracle Module — Clippy + Warnings-as-Errors
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
components: clippy
- name: Clippy (deny warnings) — oracle module
env:
RUSTFLAGS: -D warnings
run: >
cargo clippy
--package predict-iq
--all-features
-- -D warnings
working-directory: contracts/predict-iq
- name: Build (deny warnings) — contract crate
env:
RUSTFLAGS: -D warnings
run: cargo build --package predict-iq --all-features
working-directory: contracts/predict-iq
- name: Run oracle tests
run: cargo test --package predict-iq --lib oracles
working-directory: contracts/predict-iq
format:
name: Code Formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
components: rustfmt
- name: Check formatting
run: cargo fmt --all -- --check
working-directory: contracts/predict-iq
build-optimized:
name: Build Optimized Contract
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
target: wasm32-unknown-unknown
- name: Install Soroban CLI
run: cargo install --locked soroban-cli --features opt
- name: Build optimized WASM
run: cargo build --target wasm32-unknown-unknown --release
working-directory: contracts/predict-iq
- name: Optimize WASM
run: |
soroban contract optimize \
--wasm target/wasm32-unknown-unknown/release/predict_iq.wasm \
--wasm-out target/wasm32-unknown-unknown/release/predict_iq_optimized.wasm
working-directory: contracts/predict-iq
- name: Check WASM size
run: |
size=$(wc -c < target/wasm32-unknown-unknown/release/predict_iq_optimized.wasm)
echo "Optimized WASM size: $size bytes"
if [ $size -gt ${{ env.WASM_SIZE_LIMIT_BYTES }} ]; then
echo "WASM size $size exceeds limit of ${{ env.WASM_SIZE_LIMIT_BYTES }} bytes"
exit 1
fi
working-directory: contracts/predict-iq
api-cache-tests:
name: API Cache Tests (Redis)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo dependencies
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-api-${{ hashFiles('services/api/Cargo.lock') }}
- name: Run cache tests
run: cargo test --lib cache
working-directory: services/api
e2e-market-creation:
name: E2E — Market Creation Flow
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "18"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run market-creation E2E tests
run: npx playwright test e2e/market-creation.spec.ts --project=chromium
env:
CI: "true"
- name: Run flaky test detection
run: npx playwright test e2e/market-creation.spec.ts --project=chromium
env:
CI: "true"
FLAKY_DETECTION: "true"
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v7
with:
name: playwright-market-creation-report
path: frontend/playwright-report/
retention-days: 14
# ── #948: mocked E2E — no live backend required ───────────────────────────────
e2e-mocked:
name: E2E — Mocked (no backend)
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "18"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run mocked E2E suite
run: |
npx playwright test \
e2e/user-journeys.spec.ts \
e2e/accessibility.spec.ts \
e2e/interactions.spec.ts \
e2e/mobile.spec.ts \
e2e/performance.spec.ts \
--project=chromium
env:
CI: "true"
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v7
with:
name: playwright-mocked-report
path: frontend/playwright-report/
retention-days: 14
# ── #949: visual regression tests inside pinned Docker image ─────────────────
e2e-visual-regression:
name: E2E — Visual Regression (Docker)
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- name: Build visual-regression Docker image
run: |
docker build \
-f e2e/docker/Dockerfile.visual-regression \
-t predictiq-visual-regression \
.
- name: Run visual regression tests
run: |
docker run --rm \
-v "${{ github.workspace }}/frontend/e2e:/app/e2e" \
--network host \
-e BASE_URL=http://localhost:3000 \
-e CI=true \
predictiq-visual-regression
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v7
with:
name: playwright-visual-regression-report
path: frontend/playwright-report/
retention-days: 14
# ── #950: bundle size budget ──────────────────────────────────────────────────
bundle-size:
name: Bundle Size Budget
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "18"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: npm ci
- name: Build with bundle analyzer
run: npm run build
env:
NEXT_PUBLIC_API_URL: "https://placeholder.example.com"
- name: Check bundle size budgets
run: npx bundlewatch --config bundlewatch.config.json
env:
BUNDLEWATCH_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ── #740: validate every migration has a rollback script ────────────────────
validate-migration-rollbacks:
name: Validate Migration Rollback Scripts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check every migration has a rollback script
run: |
MISSING=0
MIGRATIONS_DIR="services/api/database/migrations"
ROLLBACKS_DIR="services/api/database/migrations/rollbacks"
for f in "$MIGRATIONS_DIR"/*.sql; do
base=$(basename "$f" .sql)
down="${ROLLBACKS_DIR}/${base}_down.sql"
if [ ! -f "$down" ]; then
echo "❌ Missing rollback: $down (for $f)"
MISSING=1
else
echo "✅ $down"
fi
done
if [ "$MISSING" -eq 1 ]; then
echo ""
echo "One or more migrations are missing rollback scripts."
echo "Add the corresponding *_down.sql file in $ROLLBACKS_DIR."
exit 1
fi
echo ""
echo "All migration files have corresponding rollback scripts."
- name: Validate rollback SQL syntax (dry-run)
run: |
for f in services/api/database/migrations/rollbacks/*_down.sql; do
# psql --dry-run is not universally available; use a syntax check via
# pg_format or simply assert the file is non-empty and contains SQL keywords.
if [ ! -s "$f" ]; then
echo "❌ Empty rollback script: $f"
exit 1
fi
if ! grep -qiE "^(DROP|ALTER|DELETE|TRUNCATE)" "$f"; then
echo "❌ Rollback script appears to contain no DDL/DML: $f"
exit 1
fi
echo "✅ $f"
done
# ── #906: verify rollback scripts are idempotent (run each twice) ────────────
rollback-idempotency:
name: Rollback Idempotency Check
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: predictiq_test
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U postgres"
--health-interval=10s
--health-timeout=5s
--health-retries=5
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/predictiq_test
steps:
- uses: actions/checkout@v4
- name: Apply all forward migrations
run: |
for f in $(ls services/api/database/migrations/*.sql | sort); do
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f "$f"
done
- name: Run every rollback script twice and verify second run exits 0
run: |
FAILED=0
for f in $(ls services/api/database/migrations/rollbacks/*_down.sql | sort -r); do
echo "--- First run: $f ---"
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f "$f" || { echo "❌ First run failed: $f"; FAILED=1; continue; }
echo "--- Second run (idempotency check): $f ---"
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f "$f"
RC=$?
if [ $RC -ne 0 ]; then
echo "❌ Second run of $f exited with code $RC (not idempotent)"
FAILED=1
else
echo "✅ $f is idempotent"
fi
done
if [ "$FAILED" -eq 1 ]; then
echo "One or more rollback scripts are not idempotent."
exit 1
fi
echo "All rollback scripts passed the idempotency check."
# ── #741: API Criterion benchmarks ───────────────────────────────────────────
api-criterion-benchmarks:
name: API Criterion Benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-api-bench-${{ hashFiles('services/api/Cargo.lock') }}
- name: Run API benchmarks
run: cargo bench 2>&1 | tee ../../bench-output.txt
working-directory: services/api
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Parse benchmark output
run: |
node services/api/scripts/parse-bench-output.js \
bench-output.txt bench-results.json
- name: Fetch baseline from benchmarks branch
if: github.event_name == 'pull_request'
run: |
git fetch origin benchmarks:refs/remotes/origin/benchmarks 2>/dev/null || true
if git show origin/benchmarks:api-benchmark-baseline.json > baseline-from-branch.json 2>/dev/null; then
echo "BASELINE_FILE=baseline-from-branch.json" >> "$GITHUB_ENV"
else
echo "BASELINE_FILE=services/api/benches/.benchmarks/baseline.json" >> "$GITHUB_ENV"
echo "No dedicated branch baseline found, falling back to repo baseline."
fi
- name: Compare against baseline
if: github.event_name == 'pull_request'
run: |
node services/api/scripts/compare-api-benchmarks.js \
bench-results.json \
"$BASELINE_FILE" \
--threshold 20
- name: Upload benchmark results
if: always()
uses: actions/upload-artifact@v4
with:
name: api-benchmark-results
path: |
bench-output.txt
bench-results.json
retention-days: 30
save-api-bench-baseline:
name: Save API Benchmark Baseline
runs-on: ubuntu-latest
needs: [api-criterion-benchmarks]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-api-bench-${{ hashFiles('services/api/Cargo.lock') }}
- name: Run API benchmarks
run: cargo bench 2>&1 | tee ../../bench-output.txt
working-directory: services/api
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Parse benchmark results
run: |
node services/api/scripts/parse-bench-output.js \
bench-output.txt \
api-benchmark-baseline.json
- name: Push baseline to benchmarks branch
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Switch to (or create) the dedicated benchmarks branch.
git fetch origin benchmarks 2>/dev/null || true
if git show-ref --verify --quiet refs/remotes/origin/benchmarks; then
git checkout -B benchmarks origin/benchmarks
else
git checkout --orphan benchmarks
git rm -rf . --quiet
fi
cp api-benchmark-baseline.json .
git add api-benchmark-baseline.json
git commit --allow-empty -m "chore: update API benchmark baseline (run ${{ github.run_id }})"
git push origin benchmarks
# ── #743: frontend unit-test coverage gate ───────────────────────────────────
frontend-unit-coverage:
name: Frontend Unit Test Coverage
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "18"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: npm ci
- name: Run Jest with coverage
run: npm run test:ci
env:
CI: "true"
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: frontend-coverage-report
path: frontend/coverage/
retention-days: 14
# ── #742: schema drift detection ─────────────────────────────────────────────
schema-drift-check:
name: Schema Drift Detection
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: predictiq_schema_check
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
PGUSER: postgres
PGPASSWORD: postgres
PGHOST: localhost
PGDATABASE: predictiq_schema_check
steps:
- uses: actions/checkout@v4
- name: Apply all migrations
run: |
for f in $(ls services/api/database/migrations/*.sql | sort); do
echo "▶ $f"
psql -f "$f" || { echo "❌ Migration failed: $f"; exit 1; }
done
- name: Dump live schema
run: |
pg_dump \
--schema-only \
--no-owner \
--no-privileges \
--no-comments \
--schema=public \
> /tmp/live-schema.sql
- name: Normalise both dumps for comparison
run: |
# Strip pg_dump header lines (SET …, SELECT …) that are not structural.
normalize() {
grep -vE '^(SET |SELECT |--|$)' "$1" \
| sed 's/ *$//; s/\t/ /g' \
| sort
}
normalize /tmp/live-schema.sql > /tmp/live-normalized.sql
normalize services/api/database/schema.sql > /tmp/snap-normalized.sql
- name: Compare against committed snapshot
run: |
if ! diff -u /tmp/snap-normalized.sql /tmp/live-normalized.sql; then
echo ""
echo "❌ Schema drift detected!"
echo " The live schema (after running all migrations) differs from"
echo " services/api/database/schema.sql."
echo ""
echo " To update the snapshot:"
echo " pg_dump --schema-only --no-owner --no-privileges --no-comments \\"
echo " --schema=public <db> > services/api/database/schema.sql"
echo " git add services/api/database/schema.sql"
echo " git commit -m 'chore: update schema snapshot'"
exit 1
fi
echo "✅ Schema matches snapshot — no drift detected."
- name: Upload live schema dump
if: always()
uses: actions/upload-artifact@v4
with:
name: live-schema-dump
path: /tmp/live-schema.sql
retention-days: 7
all-tests-passed:
name: All Tests Passed
needs:
- prometheus-rules-test
- unit-tests
- integration-tests
- api-rate-limit-tests
- gas-benchmarks
- backend-coverage
- security-audit
- sast-scanning
- secrets-scanning
- dependency-scanning
- codeql-analysis
- clippy
- oracle-quality-gate
- format
- build-optimized
- api-cache-tests
- e2e-market-creation
- e2e-mocked
- e2e-visual-regression
- bundle-size
- documentation-sync
- validate-migration-rollbacks
- rollback-idempotency
- api-criterion-benchmarks
- frontend-unit-coverage
- schema-drift-check
runs-on: ubuntu-latest
steps:
- name: Success
run: echo "All tests passed successfully!"
documentation-sync:
name: Documentation Sync Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Check API_SPEC.md is in sync with openapi.yaml
run: node scripts/generate-api-spec.js --check
- name: Fail if out of sync
if: failure()
run: |
echo "❌ API_SPEC.md is out of sync with openapi.yaml"
echo ""
echo "To fix this, run:"
echo " node scripts/generate-api-spec.js"
echo " git add API_SPEC.md"
echo " git commit -m 'chore: regenerate API_SPEC.md'"
exit 1