6.6 KiB
Code Review: User Preferences API
Summary
Overall assessment: NEEDS_FIX
The implementation is well-structured and follows the hexagonal architecture pattern correctly. Domain validation, service layer, handlers, routes, OpenAPI spec, and database migration are all present and properly wired. Tests pass and cover the key scenarios. However, there is one blocker (compiled binary committed to git) and several warnings that should be addressed before merging.
Findings
Blockers
services/preferences-api/server— Compiled binary (12MB) committed to git — A Go compiled binary has been tracked and committed to the repository. This bloats the repo, is platform-specific, and should never be checked in. Remove it from git tracking withgit rm services/preferences-api/serverand add it to.gitignore.
Warnings
-
services/preferences-api/internal/domain/preferences.go:34-43— Map iteration order is non-deterministic —ValidatePreferencesiterates over the map and returns on the first error. Due to Go's random map iteration, the same invalid input can produce different error messages across runs. This is not a bug per se, but makes error messages inconsistent for users sending multiple invalid keys. Consider sorting keys before validation or collecting all errors. -
services/preferences-api/internal/api/handlers/preferences.go:47-65— Missing domain error mapping on Get path — TheGethandler returnsh.svc.Get(...)errors raw (line 60-61), while theUpdatehandler correctly maps domain errors viamapPreferencesDomainError. WhileGetcurrently cannot produce domain validation errors, any future change to the service's Get method that introduces domain errors would leak as 500s. Consider applyingmapPreferencesDomainErrorconsistently. -
services/preferences-api/internal/domain/preferences.go— No domain-level unit tests — The QA plan specifies 8 domain validation unit tests (DV-1 through DV-8), but there are no test files in thedomainpackage. Domain validation is tested indirectly via the service layer tests, but direct domain tests provide better isolation and clearer failure diagnostics. AC-13 specifically requires "Domain layer validates preference keys and values independently of HTTP layer." -
services/preferences-api/internal/api/handlers/preferences_test.go:80-82—auth.SetUserused directly in tests bypasses middleware — The handler tests usewithAuthUserto inject the user into context, but there is no test verifying that the auth middleware itself is applied to the route group. Auth enforcement is tested by convention (route registration review) rather than by execution. A test that hits the route without auth context would provide stronger assurance, though this may require middleware integration testing. -
services/preferences-api/internal/api/routes.go:32— Auth is conditionally applied based oncfg.AuthEnabled— IfAUTH_ENABLEDis not set to"true", all preference endpoints are completely unauthenticated. While this is useful for development, it meansauth.MustGetUserin the handler will panic in production if the middleware isn't applied. Consider either: (a) documenting this clearly, (b) usingauth.GetUserwith a nil check instead ofauth.MustGetUser, or (c) making auth always required in production builds.
Suggestions
-
services/preferences-api/internal/adapter/postgres/preferences.go— No adapter-level tests — The PostgreSQL adapter has no test coverage. While testing against a real database is harder, the adapter could be tested with integration tests or at minimum documented as requiring manual verification. -
services/preferences-api/internal/api/handlers/preferences_test.go:21-22— Test user UUIDs don't match —otherUserIDis550e8400-e29b-41d4-a716-446655440001which only differs fromtestUserID(...440000) by the last digit. This works but could be more distinctive for readability (e.g., a completely different UUID). -
services/preferences-api/internal/api/spec.go:24-30— UpdatePreferencesRequest schema doesn't mark individual preference keys as optional — The OpenAPI schema haspreferencesas required, but individual preference keys within the object aren't marked as optional. Since partial updates are supported, the schema should indicate that individual preference fields are optional to accurately document the API behavior.
Spec Alignment
The implementation matches the spec well across all major requirements:
| Spec Requirement | Status | Notes |
|---|---|---|
| GET returns preferences as key-value pairs | OK | |
| GET returns empty preferences (not 404) for new users | OK | Service layer creates empty struct |
| PUT creates or updates (upsert semantics) | OK | PostgreSQL ON CONFLICT |
| PUT supports partial updates (merge) | OK | JSONB ` |
| Supported preference keys validated | OK | Domain layer enforces closed set |
| Invalid values rejected with 400 | OK | Domain validation with descriptive errors |
| Unknown keys rejected with 400 | OK | |
| Both endpoints require authentication (401) | OK | Via auth.Middleware (when enabled) |
| Users can only access own preferences (403) | OK | checkOwnership in both handlers |
| PostgreSQL persistence | OK | |
Standard {data, meta} envelope |
OK | Via httpresponse.OK |
| OpenAPI spec documented | OK | Full spec with schemas, params, security |
| Domain layer validates independently | Partial | Validation exists but no direct unit tests |
| Hexagonal architecture followed | OK | Clean separation across all layers |
| Service layer unit tests | OK | 10 test cases |
| Handler integration tests | OK | 10 test cases |
Test Coverage Assessment
Covered by tests:
- Service layer: Get (empty user, existing user, repo error), Update (valid prefs, unknown key, invalid theme, invalid language, invalid notifications_enabled, repo error, partial merge)
- Handler layer: Get (200 existing, 200 empty, 400 invalid UUID, 403 mismatch), Update (200 success, 400 unknown key, 400 invalid value, 400 missing prefs, 400 invalid UUID, 403 mismatch)
Missing test coverage:
- Domain validation unit tests — DV-1 through DV-8 from QA plan are not implemented
- Edge case EC-3 — Empty preferences object
{}on PUT (what happens?) - Error cases ER-20, ER-21, ER-22 — Null values, nested objects, arrays as preference values
- Auth middleware integration — No test verifying unauthenticated requests get 401
- PostgreSQL adapter — No tests for the adapter layer
All tests pass:
go test ./...— 20 tests, all PASSgo vet ./...— clean