Feature/Optional Database Caching #152
Workflow file for this run
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: CI | |
| on: | |
| pull_request: | |
| branches: [ master, develop ] | |
| types: [ opened, synchronize, reopened ] | |
| env: | |
| GO_VERSION: '1.24.2' | |
| jobs: | |
| # Branch validation - ensures PR follows branching model | |
| validate-branch: | |
| name: Validate Branch Model | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Validate branching model | |
| run: | | |
| SOURCE_BRANCH="${{ github.head_ref }}" | |
| TARGET_BRANCH="${{ github.base_ref }}" | |
| echo "Source branch: $SOURCE_BRANCH" | |
| echo "Target branch: $TARGET_BRANCH" | |
| # Define validation function | |
| validate_branch() { | |
| case "$SOURCE_BRANCH" in | |
| enhancement/*) | |
| if [ "$TARGET_BRANCH" != "develop" ]; then | |
| echo "❌ Enhancement branches can only target 'develop', not '$TARGET_BRANCH'" | |
| exit 1 | |
| fi | |
| ;; | |
| feature/*) | |
| if [ "$TARGET_BRANCH" != "develop" ]; then | |
| echo "❌ Feature branches can only target 'develop', not '$TARGET_BRANCH'" | |
| exit 1 | |
| fi | |
| ;; | |
| fix/*) | |
| if [ "$TARGET_BRANCH" != "develop" ]; then | |
| echo "❌ Fix branches can only target 'develop', not '$TARGET_BRANCH'" | |
| exit 1 | |
| fi | |
| ;; | |
| refactor/*) | |
| if [ "$TARGET_BRANCH" != "develop" ]; then | |
| echo "❌ Refactor branches can only target 'develop', not '$TARGET_BRANCH'" | |
| exit 1 | |
| fi | |
| ;; | |
| dependabot/*) | |
| if [ "$TARGET_BRANCH" != "develop" ]; then | |
| echo "❌ Dependabot branches can only target 'develop', not '$TARGET_BRANCH'" | |
| exit 1 | |
| fi | |
| ;; | |
| hotfix/*) | |
| if [ "$TARGET_BRANCH" != "master" ]; then | |
| echo "❌ Hotfix branches can only target 'master', not '$TARGET_BRANCH'" | |
| exit 1 | |
| fi | |
| ;; | |
| release/*) | |
| if [ "$TARGET_BRANCH" != "master" ]; then | |
| echo "❌ Release branches can only target 'master', not '$TARGET_BRANCH'" | |
| exit 1 | |
| fi | |
| # Additional check: release branches should come from develop | |
| MERGE_BASE=$(git merge-base origin/develop HEAD) | |
| DEVELOP_HEAD=$(git rev-parse origin/develop) | |
| if [ "$MERGE_BASE" != "$DEVELOP_HEAD" ]; then | |
| echo "❌ Release branches must be created from the latest 'develop' branch" | |
| exit 1 | |
| fi | |
| ;; | |
| *) | |
| echo "❌ Invalid branch name '$SOURCE_BRANCH'. Must follow pattern:" | |
| echo " - enhancement/name (→ develop)" | |
| echo " - feature/name (→ develop)" | |
| echo " - fix/name (→ develop)" | |
| echo " - refactor/name (→ develop)" | |
| echo " - hotfix/name (→ master)" | |
| echo " - release/yyyy-mm-dd (develop → master)" | |
| exit 1 | |
| ;; | |
| esac | |
| echo "✅ Quiver loves your PR: $SOURCE_BRANCH → $TARGET_BRANCH" | |
| } | |
| validate_branch | |
| # Security and linting checks | |
| code-quality: | |
| name: Code Quality & Security | |
| runs-on: ubuntu-latest | |
| needs: validate-branch | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Set up Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - name: Cache Go modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cache/go-build | |
| ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go- | |
| - name: Download dependencies | |
| run: make deps | |
| - name: Run linting checks | |
| run: make lint | |
| - name: Install gosec | |
| run: go install github.com/securego/gosec/v2/cmd/gosec@latest | |
| - name: Run security scan with gosec | |
| run: | | |
| gosec -fmt=json -out=gosec-results.json -no-fail ./... | |
| gosec ./... | |
| - name: Upload security scan results | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: gosec-results | |
| path: gosec-results.json | |
| retention-days: 7 | |
| # Docker-based testing with coverage | |
| test-coverage: | |
| name: Test Coverage (Docker) | |
| runs-on: ubuntu-latest | |
| needs: validate-branch | |
| permissions: | |
| pull-requests: write | |
| issues: write | |
| contents: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Run tests with coverage in Docker | |
| run: | | |
| docker run --rm \ | |
| -v ${{ github.workspace }}:/app \ | |
| -w /app \ | |
| golang:${{ env.GO_VERSION }}-alpine \ | |
| sh -c " | |
| apk add --no-cache git make bc gcc musl-dev && | |
| go mod download && | |
| mkdir -p coverage && | |
| CGO_ENABLED=1 go test -race -buildvcs=false -coverprofile=coverage.out.tmp -covermode=atomic ./... && | |
| grep -v '/mocks/' coverage.out.tmp > coverage.out || true && | |
| rm -f coverage.out.tmp && | |
| go tool cover -func=coverage.out > coverage.txt && | |
| go tool cover -html=coverage.out -o coverage.html | |
| " | |
| - name: Check overall coverage | |
| run: | | |
| if [ ! -f "coverage.txt" ]; then | |
| echo "❌ Coverage file not found" | |
| exit 1 | |
| fi | |
| OVERALL_COVERAGE=$(grep "total:" coverage.txt | awk '{print $3}' | sed 's/%//') | |
| echo "Overall coverage: ${OVERALL_COVERAGE}%" | |
| if [ $(echo "$OVERALL_COVERAGE < 90" | bc -l) -eq 1 ]; then | |
| echo "❌ Overall coverage ${OVERALL_COVERAGE}% is below required 90%" | |
| cat coverage.txt | |
| exit 1 | |
| fi | |
| echo "✅ Overall coverage ${OVERALL_COVERAGE}% meets requirement (≥90%)" | |
| - name: Comment coverage on PR | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const coverage = fs.readFileSync('coverage.txt', 'utf8'); | |
| const match = coverage.match(/total:.*?(\d+\.\d+)%/); | |
| const total = match ? match[1] : 'unknown'; | |
| const threshold = 90; | |
| const passed = parseFloat(total) >= threshold; | |
| const icon = passed ? '✅' : '❌'; | |
| const body = `## 📊 Test Coverage Report\n\n` + | |
| `${icon} **Total Coverage:** ${total}%\n` + | |
| `**Threshold:** ${threshold}%\n` + | |
| `**Status:** ${passed ? 'PASSED' : 'FAILED'}\n\n` + | |
| `<details><summary>📋 View Detailed Report</summary>\n\n` + | |
| `\`\`\`\n${coverage}\n\`\`\`\n</details>`; | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }); | |
| - name: Upload coverage reports | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: coverage-reports | |
| path: | | |
| coverage.out | |
| coverage.html | |
| coverage.txt | |
| retention-days: 7 | |
| # Multi-platform testing - tests on Linux, macOS, and Windows | |
| test-multi-platform: | |
| name: Test (${{ matrix.os }} / Go ${{ matrix.go-version }}) | |
| runs-on: ${{ matrix.os }} | |
| needs: [test-coverage, code-quality] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| go-version: ['1.24.2'] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Set up Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: ${{ matrix.go-version }} | |
| - name: Cache Go modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cache/go-build | |
| ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go-${{ matrix.go-version }}- | |
| ${{ runner.os }}-go- | |
| - name: Download dependencies | |
| run: go mod download | |
| - name: Run tests with race detector | |
| run: go test -race -v ./... | |
| # Build validation - ensures code compiles | |
| build: | |
| name: Build Validation | |
| runs-on: ubuntu-latest | |
| needs: validate-branch | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Set up Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: ${{ env.GO_VERSION }} | |
| - name: Cache Go modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cache/go-build | |
| ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go- | |
| - name: Download dependencies | |
| run: make deps | |
| - name: Format check | |
| run: | | |
| make fmt | |
| if [ -n "$(git diff --name-only)" ]; then | |
| echo "❌ Code is not properly formatted. Please run 'make fmt'" | |
| git diff | |
| exit 1 | |
| fi | |
| echo "✅ Code formatting is correct" | |
| - name: Vet check | |
| run: make vet | |
| - name: Build application | |
| run: make build | |
| - name: Verify binary | |
| run: | | |
| if [ ! -f "bin/quiver" ]; then | |
| echo "❌ Binary not found after build" | |
| exit 1 | |
| fi | |
| echo "✅ Binary built successfully" | |
| ls -la bin/ |