feat: MCPB bundle with generated manifest and release packaging #1171
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: Test Suite | |
| on: | |
| push: | |
| branches: [main, feat/comprehensive-testing-suite] | |
| paths-ignore: | |
| - '**.md' | |
| - '**.txt' | |
| - 'docs/**' | |
| - 'examples/**' | |
| - '.github/FUNDING.yml' | |
| - '.github/ISSUE_TEMPLATE/**' | |
| - '.github/pull_request_template.md' | |
| - '.gitignore' | |
| - 'LICENSE*' | |
| - 'ATTRIBUTION.md' | |
| - 'SECURITY.md' | |
| - 'CODE_OF_CONDUCT.md' | |
| pull_request: | |
| branches: [main] | |
| paths-ignore: | |
| - '**.md' | |
| - '**.txt' | |
| - 'docs/**' | |
| - 'examples/**' | |
| - '.github/FUNDING.yml' | |
| - '.github/ISSUE_TEMPLATE/**' | |
| - '.github/pull_request_template.md' | |
| - '.gitignore' | |
| - 'LICENSE*' | |
| - 'ATTRIBUTION.md' | |
| - 'SECURITY.md' | |
| - 'CODE_OF_CONDUCT.md' | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| checks: write | |
| jobs: | |
| test: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 # Increased from 10 to accommodate larger database with community nodes | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: 'npm' | |
| - name: Install dependencies | |
| # --legacy-peer-deps: n8n-nodes-base 2.20.x pulls in mappersmith with a `diff` | |
| # peer that conflicts with ts-node's `diff@^4`; npm 7+ strict resolution leaves | |
| # the lock internally inconsistent. Match local dev (.npmrc legacy-peer-deps=true). | |
| run: npm ci --legacy-peer-deps | |
| # Verify test environment setup | |
| - name: Verify test environment | |
| run: | | |
| echo "Current directory: $(pwd)" | |
| echo "Checking for .env.test file:" | |
| ls -la .env.test || echo ".env.test not found!" | |
| echo "First few lines of .env.test:" | |
| head -5 .env.test || echo "Cannot read .env.test" | |
| # Run unit tests first (without MSW) | |
| - name: Run unit tests with coverage | |
| run: npm run test:unit -- --coverage --coverage.thresholds.lines=0 --coverage.thresholds.functions=0 --coverage.thresholds.branches=0 --coverage.thresholds.statements=0 --reporter=default --reporter=junit | |
| env: | |
| CI: true | |
| # Pre-build the Docker test image used by tests/integration/docker/*.test.ts | |
| # Two test files used to build it independently inside their beforeAll hooks; the | |
| # builder stage does an `npm install` from the public registry which intermittently | |
| # fails on CI runners and tanked the entire test job. Building it here once gets us: | |
| # - one build attempt instead of two | |
| # - a clear, separately-visible CI step when the npm registry is flaky | |
| # - the test suite degrades to skipped tests if this step fails (won't block PRs) | |
| - name: Build Docker test image | |
| run: npm run docker:test:build | |
| timeout-minutes: 8 | |
| continue-on-error: true | |
| # Run integration tests separately (with MSW setup) | |
| - name: Run integration tests | |
| run: npm run test:integration -- --reporter=default --reporter=junit | |
| env: | |
| CI: true | |
| N8N_API_URL: ${{ secrets.N8N_API_URL }} | |
| N8N_API_KEY: ${{ secrets.N8N_API_KEY }} | |
| N8N_TEST_WEBHOOK_GET_URL: ${{ secrets.N8N_TEST_WEBHOOK_GET_URL }} | |
| N8N_TEST_WEBHOOK_POST_URL: ${{ secrets.N8N_TEST_WEBHOOK_POST_URL }} | |
| N8N_TEST_WEBHOOK_PUT_URL: ${{ secrets.N8N_TEST_WEBHOOK_PUT_URL }} | |
| N8N_TEST_WEBHOOK_DELETE_URL: ${{ secrets.N8N_TEST_WEBHOOK_DELETE_URL }} | |
| # Generate test summary | |
| - name: Generate test summary | |
| if: always() | |
| run: node scripts/generate-test-summary.js | |
| # Generate detailed reports | |
| - name: Generate detailed reports | |
| if: always() | |
| run: node scripts/generate-detailed-reports.js | |
| # Upload test results artifacts | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-results-${{ github.run_number }}-${{ github.run_attempt }} | |
| path: | | |
| test-results/ | |
| test-summary.md | |
| test-reports/ | |
| retention-days: 30 | |
| if-no-files-found: warn | |
| # Upload coverage artifacts | |
| - name: Upload coverage reports | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-${{ github.run_number }}-${{ github.run_attempt }} | |
| path: | | |
| coverage/ | |
| retention-days: 30 | |
| if-no-files-found: warn | |
| # Upload coverage to Codecov | |
| - name: Upload coverage to Codecov | |
| if: always() | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: ./coverage/lcov.info | |
| flags: unittests | |
| name: codecov-umbrella | |
| fail_ci_if_error: false | |
| verbose: true | |
| # Run linting | |
| - name: Run linting | |
| run: npm run lint | |
| # Run type checking | |
| - name: Run type checking | |
| run: npm run typecheck | |
| # Create test report comment for PRs | |
| - name: Create test report comment | |
| if: github.event_name == 'pull_request' && always() | |
| uses: actions/github-script@v7 | |
| continue-on-error: true | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| let summary = '## Test Results\n\nTest summary generation failed.'; | |
| try { | |
| if (fs.existsSync('test-summary.md')) { | |
| summary = fs.readFileSync('test-summary.md', 'utf8'); | |
| } | |
| } catch (error) { | |
| console.error('Error reading test summary:', error); | |
| } | |
| try { | |
| // 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('## Test Results') | |
| ); | |
| if (botComment) { | |
| // Update existing comment | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: summary | |
| }); | |
| } else { | |
| // Create new comment | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: summary | |
| }); | |
| } | |
| } catch (error) { | |
| console.error('Failed to create/update PR comment:', error.message); | |
| console.log('This is likely due to insufficient permissions for external PRs.'); | |
| console.log('Test results have been saved to the job summary instead.'); | |
| } | |
| # Generate job summary | |
| - name: Generate job summary | |
| if: always() | |
| run: | | |
| echo "# Test Run Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ -f test-summary.md ]; then | |
| cat test-summary.md >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "Test summary generation failed." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "## 📥 Download Artifacts" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- [Test Results](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY | |
| echo "- [Coverage Report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY | |
| # Store test metadata | |
| - name: Store test metadata | |
| if: always() | |
| run: | | |
| cat > test-metadata.json << EOF | |
| { | |
| "run_id": "${{ github.run_id }}", | |
| "run_number": "${{ github.run_number }}", | |
| "run_attempt": "${{ github.run_attempt }}", | |
| "sha": "${{ github.sha }}", | |
| "ref": "${{ github.ref }}", | |
| "event_name": "${{ github.event_name }}", | |
| "repository": "${{ github.repository }}", | |
| "actor": "${{ github.actor }}", | |
| "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", | |
| "node_version": "$(node --version)", | |
| "npm_version": "$(npm --version)" | |
| } | |
| EOF | |
| - name: Upload test metadata | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-metadata-${{ github.run_number }}-${{ github.run_attempt }} | |
| path: test-metadata.json | |
| retention-days: 30 | |
| # Publish test results as checks | |
| publish-results: | |
| needs: test | |
| runs-on: ubuntu-latest | |
| if: always() | |
| permissions: | |
| checks: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download test results | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: Publish test results | |
| uses: dorny/test-reporter@v3 | |
| if: always() | |
| continue-on-error: true | |
| with: | |
| name: Test Results | |
| path: 'artifacts/test-results-*/test-results/junit.xml' | |
| reporter: java-junit | |
| fail-on-error: false | |
| fail-on-empty: false |