Skip to content

feat(#984): Add idempotency keys to transaction-submission endpoints #2

feat(#984): Add idempotency keys to transaction-submission endpoints

feat(#984): Add idempotency keys to transaction-submission endpoints #2

Workflow file for this run

name: CI

Check failure on line 1 in .github/workflows/ci.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/ci.yml

Invalid workflow file

No steps defined in `steps` and no workflow called in `uses` for the following jobs: accessibility-tests
on:
push:
branches: ['**']
pull_request:
branches: [main]
jobs:
backend-test:
name: Backend - Install, Build, Test, Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Lint backend
run: npm run lint --workspace=apps/backend
- name: Build backend
run: npm run build --workspace=apps/backend
env:
GIT_COMMIT_SHA: ${{ github.sha }}
- name: Check shared types
run: npm run type-check --workspace=packages/types
- name: Check SDK types (strict mode)
run: npm run type-check --workspace=packages/sdk
- name: Run unit tests with coverage
run: npx jest --coverage --passWithNoTests
working-directory: apps/backend
- name: Run Pact provider verification
run: npm run test:pact --workspace=apps/backend
continue-on-error: true
migration-validate:
name: Backend - Validate Migrations
runs-on: ubuntu-latest
needs: backend-test
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Build backend
run: npm run build --workspace=apps/backend
- name: Validate migration ordering
run: npm run migration:validate --workspace=apps/backend
env:
DATABASE_HOST: localhost
DATABASE_PORT: 5432
DATABASE_USER: brain-storm
DATABASE_PASSWORD: brain-storm
DATABASE_NAME: brain-storm
continue-on-error: true
backend-integration-test:
name: Backend - Integration Tests (PostgreSQL)
runs-on: ubuntu-latest
services:
docker:
image: docker:latest
options: --privileged
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Run integration tests with coverage
run: npm run test:integration -- --coverage --coverageReporters=text --coverageReporters=json-summary
working-directory: apps/backend
env:
NODE_ENV: test
- name: Upload integration test coverage
if: always()
uses: actions/upload-artifact@v4
with:
name: backend-integration-coverage
path: apps/backend/coverage/
retention-days: 7
- name: Comment PR with coverage summary
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
try {
const coverage = JSON.parse(fs.readFileSync('apps/backend/coverage/coverage-summary.json', 'utf8'));
const total = coverage.total;
const comment = `## 🧪 Integration Test Coverage\n\n| Metric | Coverage |\n|--------|----------|\n| Lines | ${total.lines.pct}% |\n| Statements | ${total.statements.pct}% |\n| Functions | ${total.functions.pct}% |\n| Branches | ${total.branches.pct}% |`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
} catch (e) {
console.log('Coverage summary not found');
}
backend-soroban-test:
name: Backend - Soroban Testnet Tests
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Install Stellar CLI
run: |
curl -sSL https://github.com/stellar/stellar-cli/releases/download/v21.5.0/stellar-cli-21.5.0-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo mv stellar /usr/local/bin/
- name: Install dependencies
run: npm install
- name: Build contracts
run: ./scripts/build.sh
- name: Run Soroban tests
run: npx jest --testPathPattern='soroban-spec' --runInBand
working-directory: apps/backend
env:
CI: 'true'
STELLAR_SECRET_KEY: ${{ secrets.STELLAR_SECRET_KEY }}
frontend-build:
name: Frontend - Install, Build, Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Lint frontend
run: npm run lint --workspace=apps/frontend
- name: Build frontend
run: npm run build --workspace=apps/frontend
env:
NEXT_PUBLIC_GIT_COMMIT_SHA: ${{ github.sha }}
- name: Check bundle size budget
run: |
FIRST_LOAD_KB=$(node -e "
const fs = require('fs');
const path = 'apps/frontend/.next/build-manifest.json';
if (!fs.existsSync(path)) { console.log('0'); process.exit(0); }
const manifest = JSON.parse(fs.readFileSync(path, 'utf8'));
const pages = manifest.pages || {};
const rootChunks = pages['/'] || [];
let totalBytes = 0;
rootChunks.forEach(chunk => {
const chunkPath = 'apps/frontend/.next/' + chunk;
if (fs.existsSync(chunkPath)) totalBytes += fs.statSync(chunkPath).size;
});
console.log(Math.round(totalBytes / 1024));
")
echo "Initial route JS budget check: ${FIRST_LOAD_KB} KB"
echo "### Bundle Size Report" >> $GITHUB_STEP_SUMMARY
echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Root route JS chunks | ${FIRST_LOAD_KB} KB |" >> $GITHUB_STEP_SUMMARY
if [ "$FIRST_LOAD_KB" -gt 500 ]; then
echo "::warning::Initial JS bundle exceeds 500 KB (${FIRST_LOAD_KB} KB). Review dynamic imports."
fi
- name: Upload build stats
uses: actions/upload-artifact@v4
if: always()
with:
name: next-build-stats
path: |
apps/frontend/.next/build-manifest.json
apps/frontend/.next/app-build-manifest.json
retention-days: 30
- name: Run Pact consumer tests
run: npm run test:pact --workspace=apps/frontend
continue-on-error: true
chromatic-visual-tests:
name: Frontend - Visual Regression Tests (Chromatic)
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Build Storybook
run: npm run build-storybook --workspace=apps/frontend
- name: Publish to Chromatic
uses: chromaui/action@v1
with:
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
storybookBuildDir: apps/frontend/storybook-static
exitZeroOnChanges: true
accessibility-tests:
name: Frontend - Accessibility Tests (WCAG 2.1 AA)
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
percy-visual-tests:
name: Frontend - Visual Regression Tests (Percy)
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: brainstorm_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Build frontend
run: npm run build --workspace=apps/frontend
- name: Start frontend server
run: npm run start --workspace=apps/frontend &
timeout-minutes: 2
- name: Wait for frontend to be ready
run: |
for i in {1..30}; do
if curl -f http://localhost:3000; then
echo "Frontend is ready"
exit 0
fi
echo "Waiting for frontend... ($i/30)"
sleep 2
done
echo "Frontend failed to start"
exit 1
- name: Run accessibility tests
run: npm run test:e2e -- e2e/accessibility.spec.ts
working-directory: apps/frontend
continue-on-error: false
- name: Upload accessibility test results
if: always()
uses: actions/upload-artifact@v4
with:
name: accessibility-test-results
path: apps/frontend/test-results/
retention-days: 30
contracts-check:
name: Contracts - Test, Format, Lint, Audit, Deny
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
components: rustfmt, clippy
- name: cargo test
run: cargo test
- name: cargo test with property-based fuzzing (60s timeout)
run: timeout 60 cargo test --lib fuzz -- --test-threads=1 || true
- name: cargo test with proptest fuzzing (120s timeout)
run: timeout 120 cargo test --lib proptest -- --test-threads=1 --nocapture || true
- name: cargo fmt check
run: cargo fmt --check
- name: cargo clippy
run: cargo clippy -- -D warnings
- name: Install cargo-audit and cargo-deny
run: |
cargo install cargo-audit
cargo install cargo-deny
- name: cargo audit
run: cargo audit --deny warnings
- name: cargo deny
run: cargo deny check
- name: Upload fuzzing results
if: always()
uses: actions/upload-artifact@v4
with:
name: fuzzing-results
path: target/debug/deps/
retention-days: 7
load-tests:
name: Load Tests - k6
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up k6
uses: grafana/setup-k6-action@v1
- name: Run load tests
run: |
./scripts/load-test.sh
env:
API_URL: http://localhost:3000
continue-on-error: true
sonarcloud:
name: Code Quality - SonarCloud
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Generate frontend coverage
run: npm run test:coverage --workspace=apps/frontend
continue-on-error: true
- name: Generate backend coverage
run: npm run test --workspace=apps/backend -- --coverage
working-directory: apps/backend
continue-on-error: true
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
docker-security-scan:
name: Security - Docker Image Scanning (Trivy)
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Build backend Docker image
run: docker build -t brain-storm-backend:${{ github.sha }} -f apps/backend/Dockerfile .
- name: Build frontend Docker image
run: docker build -t brain-storm-frontend:${{ github.sha }} -f apps/frontend/Dockerfile .
- name: Run Trivy vulnerability scanner on backend image
uses: aquasecurity/trivy-action@master
with:
image-ref: brain-storm-backend:${{ github.sha }}
format: 'sarif'
output: 'trivy-backend-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Run Trivy vulnerability scanner on frontend image
uses: aquasecurity/trivy-action@master
with:
image-ref: brain-storm-frontend:${{ github.sha }}
format: 'sarif'
output: 'trivy-frontend-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy backend results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-backend-results.sarif'
category: 'trivy-backend'
- name: Upload Trivy frontend results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-frontend-results.sarif'
category: 'trivy-frontend'
- name: Generate Trivy HTML report
run: |
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image --format template --template '@/contrib/html.tpl' \
-o trivy-report.html brain-storm-backend:${{ github.sha }}
- name: Upload Trivy HTML report
uses: actions/upload-artifact@v4
if: always()
with:
name: trivy-security-report
path: trivy-report.html
retention-days: 30
- name: Fail on critical vulnerabilities
run: |
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image --severity CRITICAL --exit-code 1 \
brain-storm-backend:${{ github.sha }}
security-scan:
name: Security - OWASP ZAP Scan
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: brainstorm_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Start backend server
run: npm run start:prod --workspace=apps/backend &
env:
NODE_ENV: production
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/brainstorm_test
REDIS_URL: redis://localhost:6379
timeout-minutes: 2
- name: Wait for backend to be ready
run: |
for i in {1..30}; do
if curl -f http://localhost:3000/health; then
echo "Backend is ready"
exit 0
fi
echo "Waiting for backend... ($i/30)"
sleep 2
done
echo "Backend failed to start"
exit 1
- name: Run OWASP ZAP baseline scan
uses: zaproxy/action-baseline@v0.7.0
with:
target: 'http://localhost:3000'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a'
- name: Upload ZAP report
if: always()
uses: actions/upload-artifact@v4
with:
name: zap-report
path: report_html.html
retention-days: 30
- name: Check for HIGH severity findings
run: |
if grep -q "HIGH" report_html.html; then
echo "HIGH severity findings detected in ZAP scan"
exit 1
fi
continue-on-error: true
ci-summary:
name: CI Pipeline Summary
runs-on: ubuntu-latest
needs: [backend-test, frontend-build, contracts-check]
if: always()
steps:
- name: Generate CI summary
run: |
echo "## ✅ CI Pipeline Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Component | Status |" >> $GITHUB_STEP_SUMMARY
echo "|-----------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Backend Tests | ${{ needs.backend-test.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Frontend Build | ${{ needs.frontend-build.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Contract Tests | ${{ needs.contracts-check.result }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Quality Gates" >> $GITHUB_STEP_SUMMARY
echo "- Code Quality: Enforced via code-quality-gates workflow" >> $GITHUB_STEP_SUMMARY
echo "- Security: Enforced via security-scanning workflow" >> $GITHUB_STEP_SUMMARY
echo "- Performance: Monitored via performance-regression-testing workflow" >> $GITHUB_STEP_SUMMARY
- name: Fail if critical checks failed
if: |
needs.backend-test.result == 'failure' ||
needs.frontend-build.result == 'failure' ||
needs.contracts-check.result == 'failure'
run: |
echo "❌ CI pipeline failed - critical checks did not pass"
exit 1