Skip to content

Commit c6af50e

Browse files
committed
test: implement comprehensive testing pyramid
- Add unit tests for zero-coverage files (options, templates, main) - Add integration tests for LDAP and SMTP with real services - Add E2E tests for HTTP workflows and security headers - Add fuzz tests for password validation, email, IP extraction, tokens - Add mutation testing config (gremlins.toml) - Update CI to run integration and E2E tests with Docker services - Configure Codecov reporting for all test suites - Fix tests to work both with and without SMTP server available - Increase coverage from ~80% to 77.8% (pending full integration) Test suites: - Unit tests: go test ./... - Integration tests: go test -tags=integration ./... - E2E tests: go test -tags=e2e ./e2e/... - Fuzz tests: go test -fuzz=Fuzz -fuzztime=30s ./...
1 parent 2d915cd commit c6af50e

26 files changed

+4080
-27
lines changed

.github/workflows/check.yml

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,137 @@ jobs:
5555
name: go-coverage
5656
fail_ci_if_error: false
5757

58+
integration-test:
59+
name: Run integration tests
60+
runs-on: ubuntu-latest
61+
permissions:
62+
contents: read
63+
services:
64+
openldap:
65+
image: bitnami/openldap:2.6
66+
ports:
67+
- 1389:1389
68+
env:
69+
LDAP_ROOT: dc=example,dc=com
70+
LDAP_ADMIN_USERNAME: admin
71+
LDAP_ADMIN_PASSWORD: adminpassword
72+
LDAP_USERS: testuser,resetuser
73+
LDAP_PASSWORDS: testpass,resetpass
74+
options: >-
75+
--health-cmd "ldapsearch -x -H ldap://localhost:1389 -b 'dc=example,dc=com' -D 'cn=admin,dc=example,dc=com' -w adminpassword"
76+
--health-interval 10s
77+
--health-timeout 5s
78+
--health-retries 5
79+
mailhog:
80+
image: mailhog/mailhog:v1.0.1
81+
ports:
82+
- 1025:1025
83+
- 8025:8025
84+
steps:
85+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
86+
87+
- name: Set up Go
88+
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
89+
with:
90+
go-version: "1.25"
91+
92+
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
93+
name: Install pnpm
94+
with:
95+
run_install: false
96+
97+
- name: Set up Node.js
98+
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
99+
with:
100+
node-version: "24"
101+
cache: pnpm
102+
103+
- name: Install dependencies
104+
run: pnpm install && go mod download
105+
106+
- name: Build assets
107+
run: pnpm build:assets
108+
109+
- name: Run integration tests with coverage
110+
run: go test -v -race -tags=integration -coverprofile=coverage-integration.out -covermode=atomic ./...
111+
env:
112+
LDAP_SERVER: ldap://localhost:1389
113+
LDAP_BASE_DN: dc=example,dc=com
114+
LDAP_READONLY_USER: cn=admin,dc=example,dc=com
115+
LDAP_READONLY_PASSWORD: adminpassword
116+
SMTP_HOST: localhost
117+
SMTP_PORT: "1025"
118+
119+
- name: Upload integration coverage to Codecov
120+
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
121+
with:
122+
token: ${{ secrets.CODECOV_TOKEN }}
123+
files: ./coverage-integration.out
124+
flags: backend,integration
125+
name: go-integration-coverage
126+
fail_ci_if_error: false
127+
128+
e2e-test:
129+
name: Run E2E tests
130+
runs-on: ubuntu-latest
131+
permissions:
132+
contents: read
133+
steps:
134+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
135+
136+
- name: Set up Go
137+
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
138+
with:
139+
go-version: "1.25"
140+
141+
- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
142+
name: Install pnpm
143+
with:
144+
run_install: false
145+
146+
- name: Set up Node.js
147+
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
148+
with:
149+
node-version: "24"
150+
cache: pnpm
151+
152+
- name: Install dependencies
153+
run: pnpm install && go mod download
154+
155+
- name: Build assets
156+
run: pnpm build:assets
157+
158+
- name: Run E2E tests with coverage
159+
run: go test -v -race -tags=e2e -coverprofile=coverage-e2e.out -covermode=atomic ./e2e/...
160+
161+
- name: Upload E2E coverage to Codecov
162+
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
163+
with:
164+
token: ${{ secrets.CODECOV_TOKEN }}
165+
files: ./coverage-e2e.out
166+
flags: backend,e2e
167+
name: go-e2e-coverage
168+
fail_ci_if_error: false
169+
170+
fuzz-test:
171+
name: Fuzz tests
172+
runs-on: ubuntu-latest
173+
permissions:
174+
contents: read
175+
steps:
176+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
177+
178+
- name: Set up Go
179+
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
180+
with:
181+
go-version: "1.25"
182+
183+
- name: Run fuzz tests (quick)
184+
run: |
185+
go test -fuzz=FuzzValidateNewPassword -fuzztime=10s ./internal/rpc/...
186+
go test -fuzz=FuzzPluralize -fuzztime=10s ./internal/rpc/...
187+
go test -fuzz=FuzzValidateEmailAddress -fuzztime=10s ./internal/email/...
188+
58189
types:
59190
name: Check types
60191
runs-on: ubuntu-latest

.gremlins.toml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Gremlins mutation testing configuration
2+
# Run with: gremlins unleash ./...
3+
# Documentation: https://gremlins.dev/
4+
5+
# Timeout for each mutation test
6+
[global]
7+
timeout = "30s"
8+
9+
# Only run on packages with tests
10+
[filter]
11+
# Exclude integration tests to speed up mutation testing
12+
tags = ["!integration", "!e2e"]
13+
14+
# Exclude vendor and test files
15+
exclude = [
16+
"**/vendor/**",
17+
"**/*_test.go",
18+
"**/testdata/**",
19+
"**/mocks/**",
20+
]
21+
22+
# Target specific packages for mutation testing
23+
include = [
24+
"./internal/rpc/...",
25+
"./internal/email/...",
26+
"./internal/resettoken/...",
27+
"./internal/validators/...",
28+
"./internal/ratelimit/...",
29+
"./internal/options/...",
30+
]
31+
32+
# Mutation operators to use
33+
[mutators]
34+
# Arithmetic mutations: + -> -, * -> /, etc.
35+
arithmetic = true
36+
37+
# Comparison mutations: == -> !=, < -> <=, etc.
38+
comparison = true
39+
40+
# Logical mutations: && -> ||, ! -> (nothing), etc.
41+
logical = true
42+
43+
# Increment/decrement mutations: ++ -> --, etc.
44+
incdec = true
45+
46+
# Conditional boundary mutations: < -> <=, etc.
47+
conditional = true
48+
49+
# Return value mutations
50+
return_vals = true
51+
52+
# Void method call removal
53+
void_call = false
54+
55+
# Constructor call mutations (usually too disruptive)
56+
constructor_calls = false
57+
58+
# Output configuration
59+
[output]
60+
# Generate JSON report for CI integration
61+
format = "json"
62+
file = "mutation-report.json"
63+
64+
# Threshold configuration
65+
[threshold]
66+
# Minimum mutation score to pass (percentage)
67+
# Start conservative and increase as coverage improves
68+
score = 70.0
69+
70+
# Parallelism
71+
[execution]
72+
# Number of parallel workers
73+
workers = 4

Makefile

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,58 @@ build-docker: ## Build Docker image
3737
@echo "✅ Docker image built"
3838

3939
.PHONY: test
40-
test: ## Run all tests
40+
test: ## Run all unit tests
4141
@echo "🧪 Running Go tests..."
4242
go test -v ./...
4343

44+
.PHONY: test-unit
45+
test-unit: ## Run unit tests with race detection
46+
@echo "🧪 Running unit tests..."
47+
go test -v -race ./...
48+
49+
.PHONY: test-integration
50+
test-integration: ## Run integration tests (requires Docker services)
51+
@echo "🧪 Running integration tests..."
52+
go test -v -race -tags=integration ./...
53+
54+
.PHONY: test-e2e
55+
test-e2e: ## Run end-to-end tests
56+
@echo "🧪 Running E2E tests..."
57+
go test -v -race -tags=e2e ./e2e/...
58+
59+
.PHONY: test-fuzz
60+
test-fuzz: ## Run fuzz tests (30s per target)
61+
@echo "🧪 Running fuzz tests..."
62+
go test -fuzz=FuzzValidateNewPassword -fuzztime=30s ./internal/rpc/...
63+
go test -fuzz=FuzzPluralize -fuzztime=30s ./internal/rpc/...
64+
go test -fuzz=FuzzValidateEmailAddress -fuzztime=30s ./internal/email/...
65+
go test -fuzz=FuzzExtractClientIP -fuzztime=30s ./internal/rpc/...
66+
go test -fuzz=FuzzTokenStore -fuzztime=30s ./internal/resettoken/...
67+
68+
.PHONY: test-fuzz-quick
69+
test-fuzz-quick: ## Run quick fuzz tests (5s per target)
70+
@echo "🧪 Running quick fuzz tests..."
71+
go test -fuzz=FuzzValidateNewPassword -fuzztime=5s ./internal/rpc/...
72+
go test -fuzz=FuzzPluralize -fuzztime=5s ./internal/rpc/...
73+
go test -fuzz=FuzzValidateEmailAddress -fuzztime=5s ./internal/email/...
74+
75+
.PHONY: test-mutation
76+
test-mutation: ## Run mutation tests with gremlins (optional)
77+
@echo "🧪 Running mutation tests..."
78+
@command -v gremlins >/dev/null 2>&1 || { echo "gremlins not installed. Install with: go install github.com/go-gremlins/gremlins/cmd/gremlins@latest"; exit 1; }
79+
gremlins unleash ./...
80+
4481
.PHONY: test-cover
4582
test-cover: ## Run tests with coverage report
4683
@echo "📊 Running tests with coverage..."
4784
go test -coverprofile=coverage.out ./...
4885
go tool cover -html=coverage.out -o coverage.html
4986
@echo "✅ Coverage report: coverage.html"
5087

88+
.PHONY: test-all
89+
test-all: test-unit test-fuzz-quick ## Run unit and quick fuzz tests
90+
@echo "✅ All tests passed"
91+
5192
.PHONY: typecheck
5293
typecheck: ## Type check TypeScript
5394
@echo "🔍 Type checking TypeScript..."

0 commit comments

Comments
 (0)