Skip to content

Merge pull request #833 from uchechithelmaonye-cpu/security/enhance-s… #621

Merge pull request #833 from uchechithelmaonye-cpu/security/enhance-s…

Merge pull request #833 from uchechithelmaonye-cpu/security/enhance-s… #621

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: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- 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: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- 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: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- 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: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- 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: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- 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: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
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: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Install cargo-audit
run: cargo install cargo-audit
- name: Run security audit
run: cargo audit
working-directory: contracts/predict-iq
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/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@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@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
run: |
npm audit --audit-level=high
working-directory: frontend
continue-on-error: true
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@master
if: success()
with:
image-ref: "predictiq-api:${{ github.sha }}"
format: "sarif"
output: "trivy-container.sarif"
severity: "CRITICAL,HIGH"
exit-code: "1"
- name: Upload container scan SARIF
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: trivy-container.sarif
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: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: clippy
- 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: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
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: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
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: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
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=$(stat -f%z target/wasm32-unknown-unknown/release/predict_iq_optimized.wasm 2>/dev/null || stat -c%s 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: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- 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
# ── #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
# ── #741: API Criterion benchmarks ───────────────────────────────────────────
api-criterion-benchmarks:
name: API Criterion Benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Cache cargo dependencies
uses: actions/cache@v3
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: Compare against baseline
if: github.event_name == 'pull_request'
run: |
node services/api/scripts/compare-api-benchmarks.js \
bench-results.json \
services/api/benches/.benchmarks/baseline.json \
--threshold 10
- name: Upload benchmark results
if: always()
uses: actions/upload-artifact@v3
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'
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Cache cargo dependencies
uses: actions/cache@v3
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 and save new baseline
run: |
node services/api/scripts/parse-bench-output.js \
bench-output.txt \
services/api/benches/.benchmarks/baseline.json
- name: Create Pull Request with baseline updates
uses: peter-evans/create-pull-request@v6
with:
commit-message: "chore: update API benchmark baseline for main branch"
title: "chore: update API benchmark baseline"
body: |
## Automated API Benchmark Baseline Update
This PR updates the API benchmark baseline after changes to main branch.
**Changes:**
- Updated `services/api/benches/.benchmarks/baseline.json` with latest benchmark results
**Note:** This is an automated PR. Please review the baseline changes before merging.
branch: chore/api-baseline-${{ github.run_id }}
delete-branch: true
labels: |
ci/cd
automated
# ── #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
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
- documentation-sync
- validate-migration-rollbacks
- api-criterion-benchmarks
- frontend-unit-coverage
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