name: PR Validation on: pull_request: branches: [ main ] push: branches: [ main ] permissions: contents: read pull-requests: write checks: write security-events: write jobs: # Fast feedback - format and basic checks quick-checks: name: Quick Checks runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Format check run: | # Exclude vendor directory from format checks UNFORMATTED=$(gofmt -s -l . | grep -v "^vendor/" || true) if [ -n "$UNFORMATTED" ]; then echo "Code is not formatted. Run: gofmt -s -w ." echo "Unformatted files:" echo "$UNFORMATTED" gofmt -s -d $(echo "$UNFORMATTED") exit 1 fi - name: Go vet run: go vet ./... - name: Go mod verify run: go mod verify - name: Go mod tidy check run: | go mod tidy git diff --exit-code go.mod go.sum # Static analysis with golangci-lint (advisory - will not fail the build) golangci-lint: name: golangci-lint runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: golangci-lint uses: golangci/golangci-lint-action@v8 with: version: latest args: --timeout=10m continue-on-error: true # Allow pipeline to continue even with linting warnings # Staticcheck analysis staticcheck: name: Staticcheck runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Install staticcheck run: go install honnef.co/go/tools/cmd/staticcheck@latest - name: Run staticcheck run: staticcheck ./... # Security scanning with gosec gosec: name: Gosec Security Scanner runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Run Gosec Security Scanner run: | go install github.com/securego/gosec/v2/cmd/gosec@latest gosec -no-fail -fmt sarif -out results.sarif ./... || echo "Gosec completed with warnings" continue-on-error: true - name: Upload SARIF file if: always() && hashFiles('results.sarif') != '' uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif continue-on-error: true # Vulnerability scanning govulncheck: name: Vulnerability Scan runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Install govulncheck run: go install golang.org/x/vuln/cmd/govulncheck@latest - name: Run govulncheck run: govulncheck ./... # CodeQL analysis codeql: name: CodeQL Analysis runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: go continue-on-error: true - name: Autobuild uses: github/codeql-action/autobuild@v3 continue-on-error: true - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 continue-on-error: true # Unit tests with race detection test-race: name: Unit Tests (Race Detector) runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Run tests with race detector run: go test -race -timeout=15m -count=1 -v ./... env: GOMAXPROCS: 4 # Coverage analysis with threshold check test-coverage: name: Test Coverage runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Run tests with coverage run: | go test -coverprofile=coverage.out -covermode=atomic -timeout=15m ./... go tool cover -func=coverage.out -o=coverage.txt - name: Calculate coverage id: coverage run: | COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//') echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT echo "Total Coverage: $COVERAGE%" - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: file: ./coverage.out flags: unittests name: codecov-umbrella fail_ci_if_error: false continue-on-error: true - name: Comment coverage on PR if: github.event_name == 'pull_request' uses: actions/github-script@v8 with: script: | const coverage = '${{ steps.coverage.outputs.coverage }}'; const threshold = 70; const coverageNum = parseFloat(coverage); const emoji = coverageNum >= threshold ? '✅' : '⚠️'; const status = coverageNum >= threshold ? 'meets' : 'below'; const body = `## ${emoji} Test Coverage Report | Metric | Value | |--------|-------| | **Total Coverage** | ${coverage}% | | **Threshold** | ${threshold}% | | **Status** | ${emoji} Coverage ${status} threshold |`; // Find existing comment const { data: comments } = await github.rest.issues.listComments({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, }); const botComment = comments.find(comment => comment.user.type === 'Bot' && comment.body.includes('Test Coverage Report') ); if (botComment) { await github.rest.issues.updateComment({ comment_id: botComment.id, owner: context.repo.owner, repo: context.repo.repo, body: body }); } else { await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: body }); } - name: Check coverage threshold run: | COVERAGE=${{ steps.coverage.outputs.coverage }} THRESHOLD=70 echo "Coverage: $COVERAGE%" echo "Threshold: $THRESHOLD%" if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) )); then echo "⚠️ Coverage $COVERAGE% is below threshold $THRESHOLD%" exit 1 fi echo "✅ Coverage $COVERAGE% meets threshold $THRESHOLD%" # Memory leak detection test-memory-leaks: name: Memory Leak Detection runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Run goroutine leak tests run: | echo "Running goroutine leak detection tests..." go test -v -timeout=20m -run='.*[Gg]oroutine.*[Ll]eak.*' ./... || echo "No goroutine leak tests found" - name: Run memory leak tests run: | echo "Running memory leak detection tests..." go test -v -timeout=20m -run='.*[Mm]emory.*[Ll]eak.*' ./... || echo "No memory leak tests found" - name: Run cleanup tests run: | echo "Running cleanup and resource management tests..." go test -v -timeout=20m -run='.*[Cc]leanup.*' ./... || echo "No cleanup tests found" # Integration tests test-integration: name: Integration Tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Run integration tests run: | if [ -d "./integration" ]; then go test -v -timeout=20m ./integration/... else echo "Running integration tests from all packages..." go test -v -timeout=20m -run='.*[Ii]ntegration.*' ./... fi # Regression tests test-regression: name: Regression Tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Run regression tests run: | echo "Running regression tests..." go test -v -timeout=20m -run='.*[Rr]egression.*' ./... # Provider-specific tests (parallel matrix) test-providers: name: Provider Tests (${{ matrix.provider }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: provider: - google - azure - auth0 - okta - keycloak - cognito - gitlab - github - generic steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Run ${{ matrix.provider }} provider tests run: | PROVIDER_CAP=$(echo "${{ matrix.provider }}" | sed 's/.*/\u&/') echo "Testing $PROVIDER_CAP provider..." go test -v -timeout=10m -run=".*$PROVIDER_CAP.*" ./internal/providers/... || true go test -v -timeout=10m -run=".*${{ matrix.provider }}.*" ./... || true # Benchmark tests with performance tracking benchmark: name: Benchmark Tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Run benchmarks run: | echo "Running benchmark tests..." go test -bench=. -benchmem -benchtime=1s -run=^$ ./... | tee benchmark.txt - name: Upload benchmark results uses: actions/upload-artifact@v4 with: name: benchmark-results path: benchmark.txt retention-days: 30 - name: Compare benchmarks if: github.event_name == 'pull_request' continue-on-error: true run: | echo "Benchmark results available in artifacts" echo "To compare with main branch, download previous benchmark results" # Build validation across platforms build: name: Build (${{ matrix.os }}/${{ matrix.arch }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: os: [linux, darwin] arch: [amd64, arm64] steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Build for ${{ matrix.os }}/${{ matrix.arch }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: | echo "Building for $GOOS/$GOARCH..." go build -v -ldflags="-s -w" ./... # Security-specific edge case tests test-security-edge-cases: name: Security Edge Cases runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Run security edge case tests run: | echo "Running security edge case tests..." go test -v -timeout=15m -run='.*[Ss]ecurity.*' ./... # Session management tests test-session: name: Session Management Tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Run session tests run: | echo "Running session management tests..." go test -v -timeout=15m -run='.*[Ss]ession.*' ./... # Token validation tests test-token: name: Token Validation Tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Run token validation tests run: | echo "Running token validation tests..." go test -v -timeout=15m -run='.*[Tt]oken.*' ./... # CSRF and security tests test-csrf: name: CSRF and Security Tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24' cache: true - name: Run CSRF tests run: | echo "Running CSRF and security tests..." go test -v -timeout=15m -run='.*[Cc][Ss][Rr][Ff].*' ./... # Multi-Go version compatibility test-go-versions: name: Go ${{ matrix.go-version }} Compatibility runs-on: ubuntu-latest strategy: fail-fast: false matrix: go-version: ['1.24'] steps: - name: Checkout code uses: actions/checkout@v5 - name: Setup Go ${{ matrix.go-version }} uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} cache: true - name: Run tests on Go ${{ matrix.go-version }} run: go test -short -timeout=10m ./... # Final validation - all checks must pass (golangci-lint is advisory) all-checks-passed: name: ✅ All Checks Passed runs-on: ubuntu-latest needs: - quick-checks - golangci-lint - staticcheck - gosec - govulncheck - codeql - test-race - test-coverage - test-memory-leaks - test-integration - test-regression - test-providers - benchmark - build - test-security-edge-cases - test-session - test-token - test-csrf - test-go-versions if: always() steps: - name: Check all jobs status run: | echo "Checking status of all jobs..." # Check critical jobs (excluding golangci-lint which is advisory) CRITICAL_FAILURES=false if [ "${{ needs.quick-checks.result }}" == "failure" ] || \ [ "${{ needs.staticcheck.result }}" == "failure" ] || \ [ "${{ needs.test-race.result }}" == "failure" ] || \ [ "${{ needs.test-coverage.result }}" == "failure" ] || \ [ "${{ needs.build.result }}" == "failure" ]; then CRITICAL_FAILURES=true fi if [ "$CRITICAL_FAILURES" == "true" ]; then echo "❌ Critical checks failed" exit 1 elif [ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]; then echo "⚠️ Some checks were cancelled" exit 1 else echo "✅ All critical checks passed successfully!" if [ "${{ needs.golangci-lint.result }}" != "success" ]; then echo "ℹ️ Note: golangci-lint reported issues (advisory only)" fi fi - name: Post summary if: always() run: | echo "# PR Validation Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "## Job Status" >> $GITHUB_STEP_SUMMARY echo "- Quick Checks: ${{ needs.quick-checks.result }}" >> $GITHUB_STEP_SUMMARY echo "- Linting (advisory): ${{ needs.golangci-lint.result }}" >> $GITHUB_STEP_SUMMARY echo "- Static Analysis: ${{ needs.staticcheck.result }}" >> $GITHUB_STEP_SUMMARY echo "- Security Scan (gosec): ${{ needs.gosec.result }}" >> $GITHUB_STEP_SUMMARY echo "- Vulnerability Scan: ${{ needs.govulncheck.result }}" >> $GITHUB_STEP_SUMMARY echo "- CodeQL: ${{ needs.codeql.result }}" >> $GITHUB_STEP_SUMMARY echo "- Race Detection: ${{ needs.test-race.result }}" >> $GITHUB_STEP_SUMMARY echo "- Coverage: ${{ needs.test-coverage.result }}" >> $GITHUB_STEP_SUMMARY echo "- Memory Leaks: ${{ needs.test-memory-leaks.result }}" >> $GITHUB_STEP_SUMMARY echo "- Integration Tests: ${{ needs.test-integration.result }}" >> $GITHUB_STEP_SUMMARY echo "- Regression Tests: ${{ needs.test-regression.result }}" >> $GITHUB_STEP_SUMMARY echo "- Provider Tests: ${{ needs.test-providers.result }}" >> $GITHUB_STEP_SUMMARY echo "- Benchmarks: ${{ needs.benchmark.result }}" >> $GITHUB_STEP_SUMMARY echo "- Build: ${{ needs.build.result }}" >> $GITHUB_STEP_SUMMARY echo "- Security Edge Cases: ${{ needs.test-security-edge-cases.result }}" >> $GITHUB_STEP_SUMMARY echo "- Session Tests: ${{ needs.test-session.result }}" >> $GITHUB_STEP_SUMMARY echo "- Token Tests: ${{ needs.test-token.result }}" >> $GITHUB_STEP_SUMMARY echo "- CSRF Tests: ${{ needs.test-csrf.result }}" >> $GITHUB_STEP_SUMMARY echo "- Go Version Compatibility: ${{ needs.test-go-versions.result }}" >> $GITHUB_STEP_SUMMARY