New test files cover previously untested paths across DAV, validators, S3, Square, mw, bookings, user, and payments packages. Includes mock fix: HoldCheckouts flag on MockClient allows tests to pause auto-complete goroutine for testing PENDING checkout states. Coverage: 50.4% → 65.0% (+14.6pp)
10 KiB
Test Coverage Improvement Report
Date: 2026-07-10
Overall Coverage: 50.4% of statements
Test Command: go test -tags "test,dev" -coverprofile=coverage.out -covermode=atomic ./...
Coverage by Package
| Package | Coverage | Status |
|---|---|---|
handlers/webhooks |
100.0% | ✅ |
internal/images |
100.0% | ✅ |
internal/jobs |
93.5% | ✅ |
mw |
89.3% | ✅ (small gaps) |
internal/validators |
82.9% | ✅ |
clock |
80.0% | ✅ |
internal/square |
74.4% | ✅ |
handlers/scheduling |
73.1% | ✅ |
handlers/notifications |
71.5% | ✅ |
handlers/admin |
68.6% | ✅ |
handlers/auth |
66.0% | ✅ |
db |
66.7% | ✅ |
handlers/today |
64.5% | ✅ |
handlers/payments |
55.4% | 🟡 Moderate |
handlers/bookings |
47.7% | 🟡 Low |
handlers/portfolio |
48.9% | 🟡 Low |
auth |
45.0% | 🟡 Low |
handlers/user |
39.1% | 🔴 Very Low |
handlers/services |
30.3% | 🔴 Very Low |
crussell (root) |
5.4% | 🔴 Critical |
internal/dav |
0.0% | ⚫ Zero |
internal/logutil |
0.0% | ⚫ Zero |
internal/s3 |
0.0% | ⚫ Zero |
internal/zxcvbnjs |
0.0% | ⚫ Zero |
Critical Finding: Cross-Package Coverage Blind Spot
Several functions show 0% in their own package but HAVE tests in the admin package. Go's per-package coverage (go test ./handlers/services/) only counts tests within that package. Cross-package test calls (from handlers/admin/) don't count.
Affected handlers (all tested in handlers/admin/ but show 0% in their own package):
| Function | Actual Coverage | Where Tested |
|---|---|---|
handlers/services: ToggleService, CreateServiceHandler, DeleteServiceHandler, AllServicesHandler |
✅ Tested | admin/services_test.go |
handlers/user: GetAdminUserHandler, ListAdminUsersHandler, GetEligiblePatchTestServicesHandler, AddPatchTestHandler |
✅ Tested | admin/users_test.go |
handlers/bookings: GetAllAdminBookingsHandler, GetAdminBookingHandler, GetAllBookingsByUserHandler, SearchAdminBookingsHandler, etc. |
❌ Untested | No admin tests written |
handlers/bookings: AdminListPendingBookingsHandler, AdminGetInProgressBookingHandler |
❌ Dead code | No routes registered in main.go |
Priority 1: Immediate Wins (<30 min each, no new dependencies)
1.1 internal/logutil/logutil_test.go — 70 lines, pure stdlib
Functions: ColoredDuration(d time.Duration) string, ColoredRows(n int) string
What to test:
ColoredDuration: 3 branches (<500ms → green, <5s → yellow, >=5s → red)ColoredRows: singular (n=1) vs plural (n≠1)NO_COLORenv var behavior (ANSI codes vs empty strings) Lines added: ~40
1.2 internal/zxcvbnjs/zxcvbn_test.go — 68 Go lines, 1 public function
Function: Score(password string) (int, error)
What to test:
- Known weak passwords (
"password","123456") → score 0-1 - Strong passwords → score 3-4
- Empty string → handle gracefully
sync.Oncelazy init works across repeated calls Lines added: ~30
1.3 auth/jwt_test.go — Refresh token functions (already have handler-level tests)
Functions: generateRefreshTokenString(), GenerateRefreshToken(ctx, userID, role), VerifyRefreshToken(ctx, tokenString)
What to test:
generateRefreshTokenString: format (64-char hex) + uniqueness (100 calls)GenerateRefreshToken: stores in DB, returns non-empty tokenVerifyRefreshToken: verify + token rotation (2nd call fails), expired, revoked Pattern: Already works inhandlers/auth/auth_test.go:TestRefreshToken_Generation— adapt for package-level Test infra:auth/testmain_test.goalready sets upInitJWT+ test DB pool — no setup needed
1.4 handlers/bookings/repo_test.go — 4 trivial DB helpers (~5 lines each)
Functions: GetBookingStatus, GetBookingStartTime, BookingExists, CountUserBookingsInStatus
What to test:
- Happy path: insert booking → call function → assert result
- Not-found: non-existent ID → assert error
Pattern:
ctx, tx := testutils.SetupTestTx(t)→fixtures.CreateTestUser(tx)→fixtures.CreateTestService(tx)→fixtures.CreateTestBooking(tx, userID, svcID)→ call function directly
1.5 mw/response_test.go — Trivial helpers
Functions: RespondJSON(w, status, data), RespondError(w, status, message)
What to test:
RespondJSON: basic write, error encoding, nil dataRespondError: confirm JSON shape{"error": "..."}- Action:
RespondErroris dead code (never called anywhere) — either remove it or test + start using it Ease: 5-line pure functions,httptest.ResponseRecorder+ standard assertions
1.6 internal/dav/types_test.go — Pure string builders
Functions: GenerateICalEvent(EventInput) string, GenerateVCard(ContactInput) string
What to test:
- All-day vs timed events, attendees list, special characters (iCal escaping)
- vCard formatting with various field combinations
- Edge cases: empty fields, long strings No DB needed: Pure functions, no dependencies
Priority 2: Medium Effort (follow existing test patterns, 1-2h each)
2.1 handlers/services/services_test.go — Jump 30% → ~90%
What: Move/add in-package tests for ToggleService, CreateServiceHandler, DeleteServiceHandler, AllServicesHandler
Why: These are already tested in handlers/admin/services_test.go — just need to replicate in the services package
Pattern: Add helper makeAdminContextRequest (5 lines setting mw.UserIDKey + mw.UserRoleKey on chi context like admin/test_helpers.go)
Bonus: Remove dead code on lines 200-208 of services.go (unreachable after unconditional return)
2.2 handlers/user/*_test.go — Jump 39% → ~55%
What: Add in-package tests for GetAdminUserHandler, ListAdminUsersHandler, GetEligiblePatchTestServicesHandler, AddPatchTestHandler
Why: Same cross-package issue — tested in admin/users_test.go
Also add:
CreateGuestUserHandlersuccess path (currently only validation failure tests)processProfileImageunit test with a real JPEG file intestdata/GetAdminUserHandlererror paths (not-found, social login query error)
2.3 handlers/bookings/admin_*_test.go — Admin GET handler tests
What: Add HTTP handler tests for uncovered admin GET handlers in bookings.go:
GetAllAdminBookingsHandler,GetAdminBookingHandler,GetAllBookingsByUserHandlerSearchAdminBookingsHandler,GetOverlappingBookings*,GetBookingsBy*Range*Pattern:serveChiHandlerwith manual admin context injection (already used inoverlap_test.goandadmin_reserve_test.go) Verify:AdminListPendingBookingsHandlerandAdminGetInProgressBookingHandler— confirmed unreachable (no routes). Either remove or add routes + tests.
2.4 mw/ratelimit_test.go — Middleware wrapper tests
Functions (dev stubs): ProgressiveRateLimit, RateLimit
What to test: Wrap handler with middleware in dev mode, verify pass-through behavior
Pattern: httptest.NewRecorder + httptest.NewRequest + standard HTTP test
Priority 3: Heavier Effort (needs mocks or refactoring, 2-4h each)
3.1 handlers/portfolio/images_test.go — UploadImage (currently 7.5%)
Blockers: s3.Client is nil in tests → handler returns "Storage not configured" early
Fix: Mock s3.Uploader interface (already exists), set s3.Client = &mockUploader{} in test setup
What to test: Multi-part form with JPEG data, verify 200, verify DB record inserted with correct URLs
Also: ListFilters 31.7% — add tests for ?filter[category]=value (dual-query path), ?tag=forest, combined filters
3.2 handlers/user/profile_test.go — UploadProfilePictureHandler (8.8%)
Blocker: Same S3 nil issue as portfolio
Fix: Same mock approach — s3.Client = &mockUploader{}
Also: updateCardDAV (11.1%) — currently requires DAV_BASE_URL env var and HTTP server. Mock the function or use httptest.NewServer.
3.3 internal/dav/shared_test.go — DB-backed CardDAV operations
Functions: 14 BaseService methods (CRUD for calendars, contacts, events)
Requires: PostgreSQL test DB pool (existing pattern in testutils/testdb/)
Pattern: Create test fixtures (calendar, contact records), call methods, assert DB state
Note: Importing dav package triggers service_dev.go's init() which tries to connect to Postgres. Either set GO_TESTING env var or construct BaseService directly.
3.4 internal/s3/s3_test.go — S3 storage
Approach:
- Unit tests via
Uploaderinterface mock Connect()integration test requires local S3 (RustFS/MinIO) → tag//go:build integrationGetURLURL-builder logic can be tested independently (currently 0% on both build tags)
Dead Code Found During Investigation
| Location | Function | Status |
|---|---|---|
mw/response.go:19 |
RespondError |
Defined but NEVER called — remove or wire up |
bookings/manage.go:309 |
AdminListPendingBookingsHandler |
No route registered in main.go |
bookings/manage.go:320 |
AdminGetInProgressBookingHandler |
No route registered in main.go |
services/services.go:200-208 |
Duplicate-key error block | Unreachable — after unconditional return on line 198 |
Summary: Coverage Impact by Action
| Action | Estimated Coverage Gain |
|---|---|
| Fix cross-package gap (services) | +15-20% in handlers/services |
| Fix cross-package gap (user admin handlers) | +10-15% in handlers/user |
| Add repo.go tests | +2-3% in handlers/bookings |
| Add admin booking GET handler tests | +15-20% in handlers/bookings |
| Auth refresh token tests | +15-20% in auth |
| logutil + zxcvbnjs tests | 0% → 80-100% in those packages |
| RespondJSON test | 0% → 100% in mw/response.go |
| Portfolio S3 mock + tests | 7.5% → 60-70% in handlers/portfolio |
| dav/types pure func tests | 0% → ~25% in internal/dav |
| Remove dead code | Removes false-negative 0% entries |
| Total estimated improvement | ~50% → ~65-70% overall |