test(store): cover appearance-preferences patch normalization - #113
Conversation
Add characterization tests for NormalizeAppearancePreferencesPatch and AppearancePreferencesPatchEmpty (internal/store/appearance.go), the appearance-update validators invoked from both the sqlite and postgres store backends. Pins the default-alias-to-empty canonicalization, the per-field allow-list, the invalid-value error path, and the nil round-trip. Test-only; no production code touched.
|
Codex review: needs maintainer review before merge. Reviewed August 2, 2026, 3:13 AM ET / 07:13 UTC. ClawSweeper reviewWhat this changesAdds 141 lines of Go tests for appearance-preference patch normalization, invalid-value rejection, nil preservation, and empty-patch detection in the shared store layer. Merge readinessKeep open: this is a focused, clean test-only PR that adds direct characterization coverage still absent from current main for the appearance-patch validator contract. No actionable correctness or security defect was found; it needs ordinary maintainer review rather than automated repair. Priority: P3 Review scores
Verification
How this fits togetherAppearance settings updates enter through the SQLite and PostgreSQL stores, which normalize and validate an optional patch before deciding whether to persist any fields. The shared validator determines both accepted preference values and whether an update becomes a database write. flowchart LR
A[Appearance settings request] --> B[SQLite or PostgreSQL store]
B --> C[Shared patch normalizer]
C --> D[Validation and default canonicalization]
D --> E{Any field patched?}
E -->|Yes| F[Persist preference fields]
E -->|No| G[Return current preferences]
Before merge
Agent review detailsSecurityNone. Review metrics
Technical reviewBest possible solution: Merge the focused shared-store characterization tests if maintainers want to preserve the current normalization contract against future regressions. Do we have a high-confidence way to reproduce the issue? Not applicable: this PR characterizes an existing validator contract rather than reporting a runtime failure; current source and existing backend tests establish the behavior being pinned. Is this the best way to solve the issue? Yes: direct tests of the shared pure functions are the narrowest maintainable way to protect behavior used by both store backends, without duplicating production logic or changing configuration. AGENTS.md: found, but no applicable review policy affected this item. Codex review notes: model internal, reasoning high; reviewed against b8293987baa5. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (15 earlier review cycles; latest 8 shown)
|
What Problem This Solves
internal/store/appearance.goholds the two validators that gate every appearance-preferences update:NormalizeAppearancePreferencesPatch(canonicalize + validate a caller-supplied patch) andAppearancePreferencesPatchEmpty(decide whether there is anything to persist). Both are invoked from both store backends —internal/store/sqlite/sqlite.go:294/:364andinternal/store/postgres/postgres.go:280/:350— yet shipped with no direct test coverage.The normalizer carries a subtle contract that is easy to regress silently:
color_mode,board_theme,message_layout,density) validates against its own allow-list;system/signal/standard/comfortable) canonicalizes to the empty string — the stored representation of "use the default" — while an explicit value (dark,iris,outlined,compact, …) passes through unchanged;"<field> is invalid", never be persisted;nilfield pointer means "not being patched" and must round-trip asnil(the callers gate the DB write onAppearancePreferencesPatchEmpty, so a nil→empty slip would turn an untouched field into a write).A regression in any of these — dropping the default→empty canonicalization, remapping an allow-list entry, or dropping a field from the
Emptyconjunction — would not be caught before merge.Why This Change Was Made
Coverage-only. This adds one new file,
internal/store/appearance_test.go(+141, no production code touched), pinning the two validators' currentmainbehavior. The tests drive the real exported functions (same-package white-box, matching the harness/layout of the siblingsetup_code_defaults_test.go), not a stub. Scenarios were chosen to bite on the real failure modes — the default-alias canonicalization, the invalid-value gate, the nil round-trip, and each field of theEmptyconjunction — rather than to chase line count.Scope boundary: no runtime/production changes, no new config, defaults, or dependencies; the diff is a single new
_test.gounderinternal/store/.User Impact
No user-visible or runtime change. For maintainers, the appearance-preferences validation gates now regress loudly instead of silently: a future edit that breaks the default→empty canonicalization, an allow-list, the invalid-value rejection, or the
Emptycheck will fail this suite.Evidence
Linux, Go 1.26.5, branched off current
main(55da179). Run with the repo's standardgo test.6/6 pass on clean
main:Full package green (
go test ./apps/api/internal/store/→ok),go vetclean,gofmt -lclean.Non-vacuous — the suite bites when the target is mutated (each mutation applied to
appearance.go, suite re-run, then reverted; file restored byte-identical):appearance.go"system"default alias maps to"system"instead of""CanonicalizesDefaultsToEmptyfails!okinvalid-value guard (accept any value)RejectsInvalidPerFieldfailspatch.Density == nilfrom theEmptyconjunctionAppearancePreferencesPatchEmptyfailsWhat was not tested / limitations: the DB-persistence call sites in
sqlite.go/postgres.goare out of scope — this pins the pure validator contract they depend on, at the same level as the merged sibling coverage (#104). No production code is touched, so there is no runtime behavior to exercise beyond the exported functions.Related: none — coverage mined from the module itself; no linked issue.
AI-assisted contribution.
Generated by Claude Code