# 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_COLOR` env 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.Once` lazy 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 token - `VerifyRefreshToken`: verify + token rotation (2nd call fails), expired, revoked **Pattern**: Already works in `handlers/auth/auth_test.go:TestRefreshToken_Generation` — adapt for package-level **Test infra**: `auth/testmain_test.go` already sets up `InitJWT` + 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 data - `RespondError`: confirm JSON shape `{"error": "..."}` - **Action**: `RespondError` is 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**: - `CreateGuestUserHandler` success path (currently only validation failure tests) - `processProfileImage` unit test with a real JPEG file in `testdata/` - `GetAdminUserHandler` error 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`, `GetAllBookingsByUserHandler` - `SearchAdminBookingsHandler`, `GetOverlappingBookings*`, `GetBookingsBy*Range*` **Pattern**: `serveChiHandler` with manual admin context injection (already used in `overlap_test.go` and `admin_reserve_test.go`) **Verify**: `AdminListPendingBookingsHandler` and `AdminGetInProgressBookingHandler` — 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 `Uploader` interface mock - `Connect()` integration test requires local S3 (RustFS/MinIO) → tag `//go:build integration` - `GetURL` URL-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** |