# Sub2API 完整API测试工作流 # 包含单元测试、契约测试、安全测试、性能测试和E2E测试 name: API Testing Suite on: push: branches: [main, develop, test-fix-branch] paths: - 'backend/**' - 'tests/**' - 'frontend/**' - '.github/workflows/api-testing.yml' pull_request: branches: [main, develop] paths: - 'backend/**' - 'tests/**' - 'frontend/**' env: GO_VERSION: '1.21' NODE_VERSION: '20' POSTGRES_VERSION: '15' REDIS_VERSION: '7' jobs: # ============================================ # Job 1: 代码检查和依赖安装 # ============================================ prepare: runs-on: ubuntu-latest outputs: go-version: ${{ env.GO_VERSION }} node-version: ${{ env.NODE_VERSION }} steps: - uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v5 with: go-version: ${{ env.GO_VERSION }} - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - name: Cache Go modules uses: actions/cache@v3 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- # ============================================ # Job 2: 单元测试 # ============================================ unit-tests: runs-on: ubuntu-latest needs: prepare services: postgres: image: postgres:${{ env.POSTGRES_VERSION }} env: POSTGRES_USER: test POSTGRES_PASSWORD: test POSTGRES_DB: sub2api_test options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 redis: image: redis:${{ env.REDIS_VERSION }} ports: - 6379:6379 steps: - uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v5 with: go-version: ${{ needs.prepare.outputs.go-version }} - name: Run Unit Tests working-directory: ./backend env: TEST_DB_HOST: localhost TEST_DB_PORT: 5432 TEST_DB_USER: test TEST_DB_PASSWORD: test TEST_DB_NAME: sub2api_test TEST_REDIS_HOST: localhost TEST_REDIS_PORT: 6379 run: | go test -tags=unit -v -race -coverprofile=coverage.out ./... go tool cover -func=coverage.out > coverage.txt - name: Upload Coverage Report uses: codecov/codecov-action@v3 with: files: ./backend/coverage.out flags: unittests name: unit-tests - name: Comment Coverage uses: actions/github-script@v6 with: script: | const fs = require('fs'); const coverage = fs.readFileSync('./backend/coverage.txt', 'utf8'); const totalLine = coverage.split('\n').find(line => line.includes('total:')); if (totalLine) { github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: `### 📊 单元测试覆盖率\n\n\`\`\`\n${totalLine}\n\`\`\`` }); } # ============================================ # Job 3: 契约测试 # ============================================ contract-tests: runs-on: ubuntu-latest needs: prepare services: postgres: image: postgres:${{ env.POSTGRES_VERSION }} env: POSTGRES_USER: test POSTGRES_PASSWORD: test POSTGRES_DB: sub2api_test options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 redis: image: redis:${{ env.REDIS_VERSION }} ports: - 6379:6379 steps: - uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v5 with: go-version: ${{ needs.prepare.outputs.go-version }} - name: Run Contract Tests working-directory: ./backend env: TEST_DB_HOST: localhost TEST_DB_PORT: 5432 TEST_DB_USER: test TEST_DB_PASSWORD: test TEST_DB_NAME: sub2api_test TEST_REDIS_HOST: localhost TEST_REDIS_PORT: 6379 run: | go test -tags=integration -v ./internal/server/... -run TestAPIContracts -timeout 10m # ============================================ # Job 4: 安全测试 # ============================================ security-tests: runs-on: ubuntu-latest needs: [prepare, unit-tests] services: postgres: image: postgres:${{ env.POSTGRES_VERSION }} env: POSTGRES_USER: test POSTGRES_PASSWORD: test POSTGRES_DB: sub2api_test options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 redis: image: redis:${{ env.REDIS_VERSION }} ports: - 6379:6379 steps: - uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v5 with: go-version: ${{ needs.prepare.outputs.go-version }} - name: Run Security Tests working-directory: ./backend env: TEST_DB_HOST: localhost TEST_DB_PORT: 5432 TEST_DB_USER: test TEST_DB_PASSWORD: test TEST_DB_NAME: sub2api_test TEST_REDIS_HOST: localhost TEST_REDIS_PORT: 6379 run: | go test -tags=security -v ./internal/server/... -run TestAPI_Security -timeout 10m - name: Run Gosec Security Scanner uses: securego/gosec@master with: args: '-fmt sarif -out security.sarif ./backend/...' - name: Upload SARIF file uses: github/codeql-action/upload-sarif@v2 with: sarif_file: security.sarif # ============================================ # Job 5: 性能测试 (仅在main分支或手动触发) # ============================================ performance-tests: runs-on: ubuntu-latest needs: [prepare, unit-tests, contract-tests] if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' steps: - uses: actions/checkout@v4 - name: Setup k6 uses: grafana/setup-k6-action@v1 - name: Start Test Environment run: | docker-compose -f docker-compose.test.yml up -d sleep 30 - name: Wait for Services run: | chmod +x scripts/wait-for-services.sh ./scripts/wait-for-services.sh - name: Seed Test Data run: | docker-compose -f docker-compose.test.yml exec -T backend go run cmd/seed/main.go - name: Run Load Tests run: | k6 run \ --out influxdb=http://localhost:8086/k6 \ --env BASE_URL=http://localhost:8080 \ --env API_KEY=sk-test-key \ tests/performance/gateway-load-test.js - name: Run Stress Tests (Short) run: | k6 run \ --duration 5m \ --env BASE_URL=http://localhost:8080 \ --env API_KEY=sk-test-key \ tests/performance/gateway-stress-test.js - name: Upload Performance Results uses: actions/upload-artifact@v3 if: always() with: name: performance-results path: | summary.json summary.html - name: Cleanup if: always() run: docker-compose -f docker-compose.test.yml down -v # ============================================ # Job 6: E2E测试 # ============================================ e2e-tests: runs-on: ubuntu-latest needs: [prepare, unit-tests] services: postgres: image: postgres:${{ env.POSTGRES_VERSION }} env: POSTGRES_USER: test POSTGRES_PASSWORD: test POSTGRES_DB: sub2api_test options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 redis: image: redis:${{ env.REDIS_VERSION }} ports: - 6379:6379 steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ needs.prepare.outputs.node-version }} cache: 'npm' - name: Install Dependencies run: | npm ci npx playwright install --with-deps - name: Build Frontend working-directory: ./frontend run: | npm ci npm run build - name: Start Backend working-directory: ./backend env: DB_HOST: localhost DB_PORT: 5432 DB_USER: test DB_PASSWORD: test DB_NAME: sub2api_test REDIS_HOST: localhost REDIS_PORT: 6379 MODE: test run: | go build -o server ./cmd/server ./server & sleep 10 - name: Run E2E Tests run: | npx playwright test tests/e2e/ --reporter=html,junit env: BASE_URL: http://localhost:8080 - name: Upload Playwright Report uses: actions/upload-artifact@v3 if: always() with: name: playwright-report path: | playwright-report/ test-results/ junit-results.xml - name: Cleanup if: always() run: pkill -f './server' || true # ============================================ # Job 7: API测试汇总报告 # ============================================ test-summary: runs-on: ubuntu-latest needs: [unit-tests, contract-tests, security-tests, e2e-tests] if: always() steps: - name: Test Summary uses: actions/github-script@v6 with: script: | const jobs = { '单元测试': '${{ needs.unit-tests.result }}', '契约测试': '${{ needs.contract-tests.result }}', '安全测试': '${{ needs.security-tests.result }}', 'E2E测试': '${{ needs.e2e-tests.result }}', }; let summary = '## 🧪 API测试执行结果\n\n'; let allPassed = true; for (const [name, result] of Object.entries(jobs)) { const icon = result === 'success' ? '✅' : (result === 'skipped' ? '⏭️' : '❌'); summary += `- ${icon} **${name}**: ${result}\n`; if (result === 'failure') allPassed = false; } summary += '\n'; if (allPassed) { summary += '🎉 所有测试通过!'; } else { summary += '⚠️ 部分测试失败,请检查详细日志。'; } github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: summary });