CI / Nginx config check (push) Successful in 13s
CI / Env docs check (push) Successful in 15s
CI / Docker compose check (push) Successful in 15s
CI / Frontend major deps (push) Failing after 24s
CI / Frontend deps check (push) Successful in 30s
CI / Secrets scan (push) Successful in 38s
CI / Go build (push) Successful in 39s
CI / Frontend build (push) Successful in 1m3s
CI / Knip (push) Successful in 45s
CI / Go vet (prod) (push) Failing after 1m42s
CI / Frontend a11y check (push) Successful in 2m34s
CI / Go vet (dev) (push) Successful in 2m29s
CI / Staticcheck (prod) (push) Failing after 2m38s
CI / go mod tidy (push) Successful in 1m3s
CI / Staticcheck (dev) (push) Successful in 2m55s
CI / Frontend QC (audit) (push) Successful in 51s
CI / golangci-lint (push) Successful in 3m22s
CI / Go vulnerabilities (push) Successful in 1m26s
CI / Frontend QC (typecheck) (push) Successful in 2m18s
CI / Security scan (prod) (push) Successful in 4m18s
CI / Security scan (dev) (push) Successful in 4m40s
CI / Tests (prod) (push) Has been skipped
CI / Tests (dev) (push) Has been skipped
CI / Race (prod) (push) Has been skipped
CI / Race (dev) (push) Has been skipped
CI / Frontend QC (lint) (push) Successful in 2m18s
CI / Svelte strict check (push) Successful in 43s
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)
191 lines
10 KiB
Markdown
191 lines
10 KiB
Markdown
# 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** |
|