Skip to content

Commit 70bb99e

Browse files
author
Waldek Herka
committed
ci(e2e): implement comprehensive cross-platform E2E testing infrastructure
Adds end-to-end testing for all released artifacts across multiple platforms: Platform Coverage: - Linux AMD64/ARM64 binaries - Fedora AMD64/ARM64 RPM packages - Ubuntu AMD64/ARM64 DEB packages - macOS AMD64 (macos-15-intel) and ARM64 (macos-latest) binaries - Windows AMD64 (windows-latest) and ARM64 (windows-11-arm) binaries Key Fixes: - Skip Unix permission checks on Windows (ACL-based permissions) - Fix credential helper path formatting for Windows batch wrapper - Update RPM/DEB download patterns with wildcards for release numbers Infrastructure: - E2E workflow with prerelease gate pattern - Package-specific test jobs with container isolation - nfpm.yaml for .deb/.rpm packaging - Updated Makefile with packaging targets Closes #39
1 parent 62443ec commit 70bb99e

14 files changed

Lines changed: 2090 additions & 70 deletions

.github/workflows/e2e.yml

Lines changed: 490 additions & 0 deletions
Large diffs are not rendered by default.

.github/workflows/release.yml

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,79 @@
11
name: Release
22

33
on:
4-
release:
5-
types: [prereleased]
4+
push:
5+
tags:
6+
- 'v*'
67

78
permissions:
89
contents: write
910

1011
jobs:
11-
release:
12+
# ─────────────────────────────────────────────────────────
13+
# Build and upload artifacts as PRERELEASE.
14+
# The release is NOT marked latest until E2E tests pass.
15+
# ─────────────────────────────────────────────────────────
16+
release-prerelease:
17+
name: Build & Prerelease
1218
runs-on: ubuntu-latest
19+
outputs:
20+
release_tag: ${{ github.ref_name }}
1321
steps:
1422
- uses: actions/checkout@v4
1523
with:
1624
fetch-depth: 0
1725

26+
- name: Verify release exists as prerelease
27+
env:
28+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29+
run: |
30+
TAG="${{ github.ref_name }}"
31+
echo "Verifying release $TAG exists as prerelease..."
32+
33+
# Check if release exists and get prerelease status
34+
RELEASE_INFO=$(gh release view "$TAG" --json isPrerelease 2>/dev/null) || {
35+
echo "Error: Release $TAG does not exist. Create it as prerelease first."
36+
exit 1
37+
}
38+
39+
IS_PRERELEASE=$(echo "$RELEASE_INFO" | jq -r '.isPrerelease')
40+
41+
if [ "$IS_PRERELEASE" != "true" ]; then
42+
echo "Error: Release $TAG exists but is NOT a prerelease. Cannot upload to immutable release."
43+
exit 1
44+
fi
45+
46+
echo "✓ Release $TAG is a prerelease - proceeding with upload"
47+
1848
- name: Set up Go
1949
uses: actions/setup-go@v5
2050
with:
2151
go-version: '1.21'
2252
cache: true
2353

24-
- name: Install tools (if needed)
25-
run: |
26-
# Check if envsubst is available, install only if missing
27-
if ! command -v envsubst &> /dev/null; then
28-
echo "envsubst not found, installing gettext-base..."
29-
sudo apt-get update && sudo apt-get install -y gettext-base
30-
else
31-
echo "envsubst is already available"
32-
fi
33-
34-
- name: Run tests
54+
- name: Run unit tests
3555
run: go test ./...
3656

3757
- name: Build release binaries and packages
3858
run: |
39-
# Set version from tag (strip 'v' prefix for package version)
59+
# Extract version from tag (strip 'v' prefix for package version)
4060
VERSION="${GITHUB_REF_NAME#v}"
4161
export VERSION
4262
export RPM_RELEASE=1
43-
echo "Building version: $VERSION"
44-
echo "RPM release: $RPM_RELEASE"
4563
64+
echo "Building version: $VERSION"
4665
make release packages
4766
48-
# List all built artifacts
49-
echo ""
67+
- name: List all built artifacts
68+
run: |
5069
echo "=== Built artifacts ==="
5170
ls -lh dist/
5271
53-
# Display package metadata
72+
echo ""
5473
echo "=== DEB Package Info ==="
5574
for deb in dist/*.deb; do
5675
echo "--- $deb ---"
57-
dpkg-deb -I "$deb" | grep -E "(Package|Version|Architecture|Maintainer)"
76+
dpkg-deb -I "$deb" 2>/dev/null | grep -E "(Package|Version|Architecture|Maintainer)" || echo "dpkg-deb not available"
5877
done
5978
6079
echo ""
@@ -64,18 +83,28 @@ jobs:
6483
rpm -qip "$rpm" 2>/dev/null | grep -E "(Name|Version|Release|Architecture|Group)" || \
6584
echo "rpm command not available for detailed info"
6685
done
67-
- name: Upload artifacts and set release as latest
86+
87+
- name: Upload artifacts to prerelease
6888
env:
6989
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7090
run: |
71-
VERSION="${{ github.event.release.tag_name }}"
72-
73-
# Note: the relase is assumed as created as prerelease
74-
# The following section is aimmed at pushing the artifacts to the release
75-
# and promoting it to latest
91+
VERSION="${{ github.ref_name }}"
7692
77-
echo "Uploading artifacts to release $VERSION..."
78-
gh release upload "$VERSION" dist/* --clobber
93+
echo "Uploading artifacts to prerelease $VERSION..."
94+
gh release upload "$VERSION" dist/*
7995
80-
echo "Setting release as latest..."
81-
gh release edit "$VERSION" --prerelease=false --latest
96+
# ─────────────────────────────────────────────────────────
97+
# E2E gate: runs against the prerelease artifacts.
98+
# Promotes to latest only if all platform tests pass.
99+
# ─────────────────────────────────────────────────────────
100+
e2e-gate:
101+
name: E2E Gate
102+
needs: release-prerelease
103+
uses: ./.github/workflows/e2e.yml
104+
with:
105+
release_tag: ${{ needs.release-prerelease.outputs.release_tag }}
106+
test_org_1: "gh-app-auth-test-1"
107+
test_org_2: "gh-app-auth-test-2"
108+
secrets:
109+
E2E_APP_ID: ${{ secrets.E2E_APP_ID }}
110+
E2E_PRIVATE_KEY_B64: ${{ secrets.E2E_PRIVATE_KEY_B64 }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ vscode/
4949

5050
# Environment files
5151
.env
52+
.env.*
5253
.envrc
5354

5455
# Local configuration files (not GitHub templates)

Makefile

Lines changed: 81 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# gh-app-auth Makefile
22

3-
.PHONY: help build test lint clean install dev-setup security-scan release deps vet gocyclo staticcheck ineffassign misspell test-coverage-check markdownlint yamllint actionlint cli-smoke-test package-deb package-rpm packages
3+
.PHONY: help build test lint clean install dev-setup security-scan release deps vet gocyclo staticcheck ineffassign misspell test-coverage-check markdownlint yamllint actionlint cli-smoke-test package-deb package-rpm packages test-e2e test-e2e-local
44

55
# Default target
66
help:
@@ -28,13 +28,15 @@ help:
2828
@echo " uninstall Uninstall extension from GitHub CLI"
2929
@echo " dev-setup Set up development environment (config only)"
3030
@echo " validate-tools Validate core tools are installed"
31-
@echo " validate-lint-tools Validate linting tools can run"
31+
@echo " validate-lint-tools Validate linting tools are installed"
3232
@echo " security-scan Run security scans (gosec, govulncheck)"
3333
@echo " deps Download and verify dependencies"
3434
@echo " dev Quick development cycle (fmt + lint + test + build)"
3535
@echo " ci CI pipeline simulation (mirrors GitHub CI)"
3636
@echo " quality Full quality check (all linters + tests + security)"
3737
@echo " release Build release binaries for all platforms"
38+
@echo " test-e2e Run E2E tests (requires test infra + secrets)"
39+
@echo " test-e2e-local Run E2E tests with locally built binary"
3840
@echo ""
3941
@echo "Packaging targets:"
4042
@echo " package-deb Build DEB package for amd64"
@@ -47,9 +49,10 @@ help:
4749
@echo " validate-packages Verify binary/package architectures match targets"
4850
@echo ""
4951
@echo "Presentation targets:"
52+
@echo " presentation-setup Install presentation tools (mermaid-cli, mermaid-filter)"
5053
@echo " presentation Build both HTML and PDF presentations"
5154
@echo " presentation-html Build interactive HTML presentation"
52-
@echo " presentation-pdf Build PDF presentation"
55+
@echo " presentation-pdf Build PDF presentation (requires presentation-setup)"
5356
@echo " presentation-serve Serve presentation locally on :8000"
5457
@echo " presentation-clean Clean presentation build artifacts"
5558

@@ -62,16 +65,17 @@ COMMIT := $(shell git rev-parse --short HEAD)
6265
BUILD_TIME := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
6366
LDFLAGS := -ldflags "-X main.Version=$(VERSION) -X main.Commit=$(COMMIT) -X main.BuildTime=$(BUILD_TIME)"
6467

65-
# Go tool commands
66-
GOLANGCI_LINT := go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
67-
GOIMPORTS := go run golang.org/x/tools/cmd/goimports@latest
68-
STATICCHECK := go run honnef.co/go/tools/cmd/staticcheck@latest
69-
GOCYCLO := go run github.com/fzipp/gocyclo/cmd/gocyclo@latest
70-
INEFFASSIGN := go run github.com/gordonklaus/ineffassign@latest
71-
MISSPELL := go run github.com/client9/misspell/cmd/misspell@latest
72-
GOSEC := go run github.com/securego/gosec/v2/cmd/gosec@latest
73-
GOVULNCHECK := go run golang.org/x/vuln/cmd/govulncheck@latest
74-
ACTIONLINT := go run github.com/rhysd/actionlint/cmd/actionlint@latest
68+
# Tool paths
69+
GOPATH := $(shell go env GOPATH)
70+
GOLANGCI_LINT := $(GOPATH)/bin/golangci-lint
71+
GOIMPORTS := $(GOPATH)/bin/goimports
72+
STATICCHECK := $(GOPATH)/bin/staticcheck
73+
GOCYCLO := $(GOPATH)/bin/gocyclo
74+
INEFFASSIGN := $(GOPATH)/bin/ineffassign
75+
MISSPELL := $(GOPATH)/bin/misspell
76+
GOSEC := $(GOPATH)/bin/gosec
77+
GOVULNCHECK := $(GOPATH)/bin/govulncheck
78+
NFPM_CMD := $(GOPATH)/bin/nfpm
7579

7680
# Build the extension
7781
build:
@@ -112,7 +116,7 @@ test-coverage-check:
112116
echo "✅ Coverage $$COVERAGE% meets threshold $(COVERAGE_THRESHOLD)%"; \
113117
fi
114118

115-
# Lint code with golangci-lint
119+
# Lint code with golangci-lint (comprehensive)
116120
lint:
117121
@echo "Running golangci-lint..."
118122
$(GOLANGCI_LINT) run
@@ -154,10 +158,11 @@ yamllint:
154158
@command -v yamllint >/dev/null 2>&1 || { echo "⚠️ yamllint not found, skipping (install: pip install yamllint)"; exit 0; }
155159
yamllint -d relaxed . || echo "⚠️ YAML lint issues found"
156160

157-
# Run actionlint on GitHub workflow files
161+
# Run actionlint on GitHub workflow files (requires actionlint binary)
158162
actionlint:
159163
@echo "Running actionlint..."
160-
@$(ACTIONLINT) || echo "⚠️ Action lint issues found"
164+
@command -v actionlint >/dev/null 2>&1 || { echo "⚠️ actionlint not found, skipping (install: go install github.com/rhysd/actionlint/cmd/actionlint@latest)"; exit 0; }
165+
actionlint || echo "⚠️ Action lint issues found"
161166

162167
# CLI smoke test - verify binary works
163168
cli-smoke-test: build
@@ -173,8 +178,8 @@ lint-all: vet staticcheck gocyclo ineffassign misspell markdownlint yamllint act
173178
# Format code
174179
fmt:
175180
@echo "Formatting code..."
176-
$(GOIMPORTS) -w .
177181
gofmt -s -w .
182+
$(GOIMPORTS) -w .
178183

179184
# Clean build artifacts
180185
clean:
@@ -193,25 +198,45 @@ uninstall:
193198
@echo "Uninstalling extension from GitHub CLI..."
194199
gh extension remove app-auth || true
195200

196-
# Set up development environment (configuration only, no tool installation)
201+
# Set up development environment
197202
dev-setup:
198203
@echo "Setting up development environment..."
199204
go mod download
205+
@echo "Installing linting tools..."
206+
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
207+
go install golang.org/x/tools/cmd/goimports@latest
208+
go install github.com/securego/gosec/v2/cmd/gosec@latest
209+
go install honnef.co/go/tools/cmd/staticcheck@latest
210+
go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
211+
go install github.com/gordonklaus/ineffassign@latest
212+
go install github.com/client9/misspell/cmd/misspell@latest
213+
go install golang.org/x/vuln/cmd/govulncheck@latest
214+
go install github.com/goreleaser/nfpm/v2/cmd/nfpm@latest
200215
@echo "Setting up git commit template..."
201216
git config commit.template .gitmessage
202217
@echo "Development environment ready!"
203218
@echo ""
204219
@echo "💡 Tip: Use 'git commit' (without -m) to use the conventional commit template"
205220
@echo "📖 See CONTRIBUTING.md for conventional commit guidelines"
206221

222+
# Set up presentation tools (installs mermaid-cli and mermaid-filter globally)
223+
presentation-setup:
224+
@echo "Setting up presentation tools..."
225+
@command -v npm >/dev/null 2>&1 || { echo "npm is required. Install Node.js first"; exit 1; }
226+
@echo "Installing Mermaid CLI..."
227+
npm install -g @mermaid-js/mermaid-cli
228+
@echo "Installing mermaid-filter for pandoc..."
229+
npm install -g mermaid-filter
230+
@echo "✅ Presentation tools installed globally"
231+
207232
# Run security scans
208233
security-scan:
209234
@echo "Running security scans..."
210235
$(GOSEC) -fmt sarif -out gosec.sarif ./... || true
211236
@echo "Running vulnerability check..."
212237
$(GOVULNCHECK) ./... || true
213238

214-
# Download and verify dependencies
239+
# Download and verify dependencies
215240
deps:
216241
@echo "Downloading dependencies..."
217242
go mod download
@@ -265,18 +290,18 @@ validate-tools:
265290
@command -v git >/dev/null 2>&1 || { echo "❌ Git is required but not installed"; exit 1; }
266291
@echo "✅ Core tools are installed."
267292

268-
# Validate that linting tools can be executed
293+
# Validate that linting tools are installed
269294
validate-lint-tools:
270-
@echo "Validating linting tools can be executed..."
271-
@$(GOLANGCI_LINT) version >/dev/null 2>&1 || { echo "❌ golangci-lint failed"; exit 1; }
272-
@$(GOIMPORTS) -l . >/dev/null 2>&1 || { echo "❌ goimports failed"; exit 1; }
273-
@$(STATICCHECK) --help >/dev/null 2>&1 || { echo "❌ staticcheck failed"; exit 1; }
274-
@$(GOCYCLO) . >/dev/null 2>&1 || { echo "❌ gocyclo failed"; exit 1; }
275-
@$(INEFFASSIGN) . >/dev/null 2>&1 || { echo "❌ ineffassign failed"; exit 1; }
276-
@$(MISSPELL) --help >/dev/null 2>&1 || { echo "❌ misspell failed"; exit 1; }
277-
@$(GOSEC) --help >/dev/null 2>&1 || { echo "❌ gosec failed"; exit 1; }
278-
@$(GOVULNCHECK) --help >/dev/null 2>&1 || { echo "❌ govulncheck failed"; exit 1; }
279-
@echo "✅ All linting tools work"
295+
@echo "Validating linting tools are installed..."
296+
@test -f $(GOLANGCI_LINT) || { echo "❌ golangci-lint not installed. Run 'make dev-setup'"; exit 1; }
297+
@test -f $(GOIMPORTS) || { echo "❌ goimports not installed. Run 'make dev-setup'"; exit 1; }
298+
@test -f $(STATICCHECK) || { echo "❌ staticcheck not installed. Run 'make dev-setup'"; exit 1; }
299+
@test -f $(GOCYCLO) || { echo "❌ gocyclo not installed. Run 'make dev-setup'"; exit 1; }
300+
@test -f $(INEFFASSIGN) || { echo "❌ ineffassign not installed. Run 'make dev-setup'"; exit 1; }
301+
@test -f $(MISSPELL) || { echo "❌ misspell not installed. Run 'make dev-setup'"; exit 1; }
302+
@test -f $(GOSEC) || { echo "❌ gosec not installed. Run 'make dev-setup'"; exit 1; }
303+
@test -f $(GOVULNCHECK) || { echo "❌ govulncheck not installed. Run 'make dev-setup'"; exit 1; }
304+
@echo "✅ All linting tools are installed"
280305

281306
# Quick development cycle
282307
dev: fmt lint test build
@@ -333,6 +358,28 @@ ci: deps validate-tools validate-lint-tools
333358
quality: validate-lint-tools fmt lint-all test-coverage-check security-scan
334359
@echo "Quality check complete!"
335360

361+
# Run E2E tests using a pre-built or user-supplied binary.
362+
# Requires test infrastructure (see docs/E2E_INFRASTRUCTURE.md) and secrets:
363+
# export E2E_APP_ID=<app-id>
364+
# export E2E_PRIVATE_KEY_B64=$(base64 -w 0 </path/to/key.pem>)
365+
# export E2E_GITHUB_TOKEN=<github-token-with-repo-scope>
366+
# Optional: export E2E_BINARY_PATH=<path/to/binary> (builds from source if unset)
367+
.PHONY: test-e2e
368+
test-e2e:
369+
@echo "Running E2E tests (requires test infrastructure)..."
370+
go test -v -tags=e2e -timeout=15m ./test/e2e/...
371+
372+
# Run E2E tests using a locally built binary (no prerelease needed).
373+
# Builds the binary from source automatically.
374+
.PHONY: test-e2e-local
375+
test-e2e-local:
376+
@echo "Building binary for E2E tests..."
377+
go build -o /tmp/gh-app-auth-e2e-local .
378+
@echo "Running E2E tests with local binary..."
379+
E2E_BINARY_PATH=/tmp/gh-app-auth-e2e-local \
380+
go test -v -tags=e2e -timeout=15m ./test/e2e/...
381+
rm -f /tmp/gh-app-auth-e2e-local
382+
336383
# Packaging targets
337384
.PHONY: package-deb package-deb-arm64 package-deb-arm package-rpm package-rpm-arm64 package-rpm-arm packages packages-local validate-packages
338385

@@ -477,7 +524,7 @@ else
477524
endif
478525

479526
# Presentation targets
480-
.PHONY: presentation presentation-html presentation-pdf presentation-serve presentation-clean
527+
.PHONY: presentation presentation-setup presentation-html presentation-pdf presentation-serve presentation-clean
481528

482529
# Build presentation HTML
483530
presentation-html:
@@ -509,10 +556,9 @@ presentation-pdf:
509556
@echo "Building presentation PDF..."
510557
@command -v pandoc >/dev/null 2>&1 || { echo "Pandoc is required. Install: apt install pandoc"; exit 1; }
511558
@command -v xelatex >/dev/null 2>&1 || { echo "XeLaTeX is required. Install: apt install texlive-xetex"; exit 1; }
512-
@echo "Ensuring mermaid-filter is available..."
513-
@command -v mermaid-filter >/dev/null 2>&1 || { echo "Installing mermaid-filter..." && npm install -g mermaid-filter; }
514-
@mkdir -p dist/presentation
515-
export PATH="$$(npm config get prefix)/bin:$$PATH"; \
559+
@command -v mmdc >/dev/null 2>&1 || { echo "Mermaid CLI is required. Run: make presentation-setup"; exit 1; }
560+
@npm list -g mermaid-filter >/dev/null 2>&1 || { echo "mermaid-filter is required. Run: make presentation-setup"; exit 1; }
561+
mkdir -p dist/presentation
516562
pandoc docs/presentation.md \
517563
-o dist/presentation/presentation.pdf \
518564
--pdf-engine=xelatex \

0 commit comments

Comments
 (0)