fix: [v2][contracts] Canonical deployment manifest and per-networ (#1β¦ #5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Tests | ||
| on: | ||
| push: | ||
| branches: [main] | ||
| pull_request: | ||
| branches: [main] | ||
| workflow_dispatch: | ||
| inputs: | ||
| force_full_run: | ||
| description: 'Force a full test run' | ||
| type: boolean | ||
| default: false | ||
| jobs: | ||
| changes: | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| backend: ${{ steps.filter.outputs.backend }} | ||
| client: ${{ steps.filter.outputs.client }} | ||
| force_full_run: ${{ github.event.inputs.force_full_run || 'false' }} | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| - uses: dorny/paths-filter@v4 | ||
| id: filter | ||
| with: | ||
| filters: | | ||
| backend: | ||
| - 'backend/**' | ||
| - 'shared/**' | ||
| client: | ||
| - 'client/**' | ||
| - 'shared/**' | ||
| test-client: | ||
| name: Test Client | ||
| runs-on: ubuntu-latest | ||
| needs: changes | ||
| if: ${{ needs.changes.outputs.force_full_run == 'true' || needs.changes.outputs.client == 'true' }} | ||
| defaults: | ||
| run: | ||
| working-directory: client | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| with: | ||
| fetch-depth: 0 # Fetch all history for coverage comparison | ||
| - uses: actions/setup-node@v7 | ||
| with: | ||
| node-version: "20" | ||
| cache: "npm" | ||
| cache-dependency-path: client/package-lock.json | ||
| - name: Install dependencies | ||
| run: npm ci | ||
| - name: Run client unit tests with coverage | ||
| run: npm test -- --coverage --run | ||
| continue-on-error: false | ||
| - name: Check coverage thresholds | ||
| id: coverage-check | ||
| run: | | ||
| echo "Checking coverage thresholds..." | ||
| npm test -- --coverage --run --reporter=json > coverage-output.json || true | ||
| # Extract coverage summary | ||
| if [ -f coverage/coverage-summary.json ]; then | ||
| echo "Coverage report found" | ||
| cat coverage/coverage-summary.json | ||
| # Parse thresholds (lines: 80%, branches: 75%, functions: 85%, statements: 80%) | ||
| LINES=$(jq '.total.lines.pct' coverage/coverage-summary.json) | ||
| BRANCHES=$(jq '.total.branches.pct' coverage/coverage-summary.json) | ||
| FUNCTIONS=$(jq '.total.functions.pct' coverage/coverage-summary.json) | ||
| STATEMENTS=$(jq '.total.statements.pct' coverage/coverage-summary.json) | ||
| echo "lines_coverage=$LINES" >> $GITHUB_OUTPUT | ||
| echo "branches_coverage=$BRANCHES" >> $GITHUB_OUTPUT | ||
| echo "functions_coverage=$FUNCTIONS" >> $GITHUB_OUTPUT | ||
| echo "statements_coverage=$STATEMENTS" >> $GITHUB_OUTPUT | ||
| FAILED=0 | ||
| if (( $(echo "$LINES < 80" | bc -l) )); then | ||
| echo "β Line coverage ($LINES%) is below threshold (80%)" | ||
| FAILED=1 | ||
| fi | ||
| if (( $(echo "$BRANCHES < 75" | bc -l) )); then | ||
| echo "β Branch coverage ($BRANCHES%) is below threshold (75%)" | ||
| FAILED=1 | ||
| fi | ||
| if (( $(echo "$FUNCTIONS < 85" | bc -l) )); then | ||
| echo "β Function coverage ($FUNCTIONS%) is below threshold (85%)" | ||
| FAILED=1 | ||
| fi | ||
| if (( $(echo "$STATEMENTS < 80" | bc -l) )); then | ||
| echo "β Statement coverage ($STATEMENTS%) is below threshold (80%)" | ||
| FAILED=1 | ||
| fi | ||
| if [ $FAILED -eq 1 ]; then | ||
| echo "threshold_passed=false" >> $GITHUB_OUTPUT | ||
| exit 1 | ||
| else | ||
| echo "β All coverage thresholds passed" | ||
| echo "threshold_passed=true" >> $GITHUB_OUTPUT | ||
| fi | ||
| else | ||
| echo "β οΈ Coverage summary not found" | ||
| echo "threshold_passed=unknown" >> $GITHUB_OUTPUT | ||
| exit 1 | ||
| fi | ||
| - name: Upload coverage reports to Codecov | ||
| uses: codecov/codecov-action@v4 | ||
| with: | ||
| files: ./client/coverage/lcov.info | ||
| flags: client | ||
| name: client-coverage | ||
| fail_ci_if_error: false | ||
| token: ${{ secrets.CODECOV_TOKEN }} | ||
| - name: Comment PR with coverage | ||
| if: github.event_name == 'pull_request' | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| // Read coverage summary | ||
| let coverageSummary = ''; | ||
| try { | ||
| const summaryPath = path.join(process.cwd(), 'client/coverage/coverage-summary.json'); | ||
| const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8')); | ||
| const total = summary.total; | ||
| const lines = total.lines.pct.toFixed(2); | ||
| const branches = total.branches.pct.toFixed(2); | ||
| const functions = total.functions.pct.toFixed(2); | ||
| const statements = total.statements.pct.toFixed(2); | ||
| // Determine status emojis | ||
| const lineStatus = lines >= 80 ? 'β ' : 'β'; | ||
| const branchStatus = branches >= 75 ? 'β ' : 'β'; | ||
| const functionStatus = functions >= 85 ? 'β ' : 'β'; | ||
| const statementStatus = statements >= 80 ? 'β ' : 'β'; | ||
| coverageSummary = `## π Client Test Coverage Report | ||
| | Metric | Coverage | Threshold | Status | | ||
| |--------|----------|-----------|--------| | ||
| | Lines | ${lines}% | 80% | ${lineStatus} | | ||
| | Branches | ${branches}% | 75% | ${branchStatus} | | ||
| | Functions | ${functions}% | 85% | ${functionStatus} | | ||
| | Statements | ${statements}% | 80% | ${statementStatus} | | ||
| ${lines >= 80 && branches >= 75 && functions >= 85 && statements >= 80 ? 'β All coverage thresholds passed!' : 'β οΈ Some coverage thresholds not met. Please add more tests.'} | ||
| [View detailed coverage report](https://codecov.io/gh/${{ github.repository }}/pull/${{ github.event.pull_request.number }}) | ||
| `; | ||
| } catch (error) { | ||
| coverageSummary = 'β οΈ Unable to read coverage summary'; | ||
| } | ||
| // Find existing comment | ||
| const { data: comments } = await github.rest.issues.listComments({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| }); | ||
| const botComment = comments.find(comment => | ||
| comment.user.type === 'Bot' && | ||
| comment.body.includes('π Client Test Coverage Report') | ||
| ); | ||
| if (botComment) { | ||
| // Update existing comment | ||
| await github.rest.issues.updateComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| comment_id: botComment.id, | ||
| body: coverageSummary | ||
| }); | ||
| } else { | ||
| // Create new comment | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| body: coverageSummary | ||
| }); | ||
| } | ||
| - uses: actions/upload-artifact@v7 | ||
| if: always() | ||
| with: | ||
| name: client-coverage-report | ||
| path: client/coverage/ | ||
| test-backend: | ||
| name: Test Backend | ||
| runs-on: ubuntu-latest | ||
| needs: changes | ||
| if: ${{ needs.changes.outputs.force_full_run == 'true' || needs.changes.outputs.backend == 'true' }} | ||
| defaults: | ||
| run: | ||
| working-directory: backend | ||
| env: | ||
| NODE_ENV: test | ||
| SUPABASE_URL: ${{ secrets.SUPABASE_URL }} | ||
| SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} | ||
| JWT_SECRET: test-secret | ||
| ADMIN_API_KEY: test-admin-key | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| - uses: actions/setup-node@v7 | ||
| with: | ||
| node-version: "20" | ||
| cache: "npm" | ||
| cache-dependency-path: backend/package-lock.json | ||
| - name: Install dependencies | ||
| run: npm install | ||
| - name: Run tests with coverage | ||
| run: npm test -- --coverage --ci | ||
| - uses: actions/upload-artifact@v7 | ||
| if: always() | ||
| with: | ||
| name: coverage-report | ||
| path: backend/coverage/ | ||
| test-sdk: | ||
| name: Test SDK | ||
| runs-on: ubuntu-latest | ||
| needs: changes | ||
| if: ${{ needs.changes.outputs.force_full_run == 'true' }} | ||
| defaults: | ||
| run: | ||
| working-directory: sdk | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| - uses: actions/setup-node@v7 | ||
| with: | ||
| node-version: "20" | ||
| cache: "npm" | ||
| cache-dependency-path: sdk/package-lock.json | ||
| - name: Install dependencies | ||
| run: npm install | ||
| - name: Run tests with coverage (thresholds enforced) | ||
| run: npx jest --coverage --ci | ||
| - uses: actions/upload-artifact@v7 | ||
| if: always() | ||
| with: | ||
| name: sdk-coverage-report | ||
| path: sdk/coverage/ | ||
| test-shared: | ||
| name: Test Shared | ||
| runs-on: ubuntu-latest | ||
| needs: changes | ||
| if: ${{ needs.changes.outputs.force_full_run == 'true' }} | ||
| defaults: | ||
| run: | ||
| working-directory: shared | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| - uses: actions/setup-node@v7 | ||
| with: | ||
| node-version: "20" | ||
| cache: "npm" | ||
| cache-dependency-path: shared/package-lock.json | ||
| - name: Install dependencies | ||
| run: npm install | ||
| - name: Run tests with coverage (thresholds enforced) | ||
| run: npx jest --coverage --ci | ||
| - uses: actions/upload-artifact@v7 | ||
| if: always() | ||
| with: | ||
| name: shared-coverage-report | ||
| path: shared/coverage/ | ||
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| # Issue #1304 β Integration suite against a local Soroban sandbox. | ||
| # Runs on: changes to contracts/, sdk/, or workflow_dispatch with full run. | ||
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | ||
| sdk-integration: | ||
| name: SDK Integration Suite (Soroban Sandbox) | ||
| runs-on: ubuntu-latest | ||
| needs: changes | ||
| if: | | ||
| github.event.inputs.force_full_run == 'true' || | ||
| contains(github.event.pull_request.labels.*.name, 'run-integration') | ||
| timeout-minutes: 30 | ||
| defaults: | ||
| run: | ||
| working-directory: sdk | ||
| env: | ||
| SOROBAN_RPC_URL: http://localhost:8000/soroban/rpc | ||
| SOROBAN_NETWORK_PASSPHRASE: "Standalone Network ; February 2017" | ||
| INTEGRATION_API_KEY: ci-integration-key | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| - uses: actions/setup-node@v7 | ||
| with: | ||
| node-version: "20" | ||
| cache: "npm" | ||
| cache-dependency-path: sdk/package-lock.json | ||
| - name: Install Rust + wasm32 target | ||
| uses: dtolnay/rust-toolchain@stable | ||
| with: | ||
| targets: wasm32-unknown-unknown | ||
| - name: Cache Rust dependencies | ||
| uses: Swatinem/rust-cache@v2 | ||
| with: | ||
| workspaces: contracts | ||
| - name: Install Stellar CLI | ||
| run: cargo install --locked stellar-cli --features opt | ||
| - name: Install SDK dependencies | ||
| working-directory: sdk | ||
| run: npm install | ||
| - name: Install shared dependencies and build | ||
| working-directory: shared | ||
| run: | | ||
| npm install | ||
| npm run build | ||
| - name: Run SDK integration suite (starts sandbox, deploys contracts, runs tests) | ||
| working-directory: sdk | ||
| run: npm run test:integration | ||
| - uses: actions/upload-artifact@v7 | ||
| if: always() | ||
| with: | ||
| name: sdk-integration-results | ||
| path: sdk/coverage/ | ||