fix/rpu-12-digit
needs attention427115e · incrementalPR #246reviewed 2026-07-05 21:43 UTC0H · 1M · 3L · 1I- Purpose
- CFE RPU validation was too permissive — 11-digit RPUs passed the lookup gate, reached the create form, and failed server-side with a raw ts-rest ZodError that surfaced as a blank toast.
- Goal
- Enforce the exactly-12-digit CFE RPU rule client-side so incomplete RPUs never reach the server, and surface descriptive error messages when the server does reject.
- Sub-goals
- SG-1: Single-source isValidRpu / normalizeRpuInput / extractServerErrorMessage in rpu-errors.ts
- SG-2: Gate AddContractDrawer lookup + create form on isValidRpu (12 digits, not 11)
- SG-3: InlineContractForm client-side guard + descriptive toast + release submit lock on error
- SG-4: i18n strings rpuInvalid / createFailed (es + en)
- SG-5: Unit + component tests for all three layers
- What
- New rpu-errors.ts module with isValidRpu, normalizeRpuInput, extractServerErrorMessage. AddContractDrawer gates on isValidRpu (no maxLength, paste-safe via normalizeRpuInput). InlineContractForm adds client guard, descriptive error toast, submit-lock release. i18n strings added. Three new test files covering the pure logic and component behaviour.
- Why
- Incomplete RPU slipped through as 'available' (>= 11 digit check), form submitted, server returned raw ZodError 400 that the form read as blank — UX dead-end with frozen 'Creando…' submit button.
- Areas
- apps/platform/src/app/[locale]/(dashboard)/bills/_components+317−12apps/platform/src/app/[locale]/(dashboard)/bills/_lib+233−0apps/platform/src/messages+6−2
- Blast
- 8 files, +556/-14; all within the bills module. No schema, shell, or API contract changes — pure frontend validation + UI polish.
Findings · 5
tests5
Non-null assertions without toBeDefined() — throws instead of asserting on regression
apps/platform/src/app/[locale]/(dashboard)/bills/_lib/__tests__/rpu-errors.test.ts:124
Lines 124, 135, 136 use `out!.length` / `viaData!.length` / `viaZod!.length` without a prior `expect(out).toBeDefined()`. If extractServerErrorMessage regresses to undefined, the test throws a TypeError rather than producing a clean Vitest diff. Add expect(x).toBeDefined() before each non-null access.
Empty-string body.data.message fallthrough untested
apps/platform/src/app/[locale]/(dashboard)/bills/_lib/__tests__/rpu-errors.test.ts:139
The source guards body.data.message with a truthiness check, so an empty string falls through to the next extraction branch. A fixture like { body: { data: { message: '' }, message: 'fallback' } } would verify this is intentional and which branch wins.
normalizeRpuInput 'caps at 12 digits' test has a redundant non-over-long assertion
apps/platform/src/app/[locale]/(dashboard)/bills/_lib/__tests__/rpu-errors.test.ts:52
`expect(normalizeRpuInput('123456789012').length).toBe(12)` passes an exactly-12-digit input that the function never truncates. Intent is idempotence, but the title says 'over-long RPU'. Swap for `expect(normalizeRpuInput('123456789012')).toBe('123456789012')` or add a comment clarifying this is the pass-through case.
ZodError clamp fixture deviates from the realistic prod shape used elsewhere
apps/platform/src/app/[locale]/(dashboard)/bills/_lib/__tests__/rpu-errors.test.ts:133
The 'clamps on every branch' ZodError fixture omits `origin`, `code`, `path` that the realistic prod fixture includes. The logic works regardless, but mixing fidelity within the same describe makes future maintenance harder.
normalizeRpuInput('') not tested
apps/platform/src/app/[locale]/(dashboard)/bills/_lib/__tests__/rpu-errors.test.ts:55
Empty string returns '' (strips nothing, slice is a no-op) — consistent with isValidRpu('') → false, but the user-clears-input path is not explicitly exercised. Low risk.
History · 6 commits
- 427115eneeds attentionincremental0H · 1M · 3L2026-07-05 21:43current
- c6ce7d8needs attentionincremental0H · 3M · 6L2026-07-05 21:35
- d8cbd91needs attentionincremental0H · 0M · 3L2026-07-05 21:25
- 20aa256safeincremental0H · 0M · 2L2026-07-05 21:14
- 77a5d70needs attentionincremental0H · 2M · 9L2026-07-05 21:09
- 586531aneeds attentionfull0H · 5M · 10L2026-07-05 20:51