From 704b521b7d993dbf849eaff7f4dac773462483ea Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Wed, 27 May 2026 23:46:18 +0200 Subject: [PATCH] feat(defects): add shared arktype defect input schema Shared validation source for the DefectForm and /api/defects route (Task 5): trimmed non-empty sectionId/verbatim and a uuid projectId. --- shared/schemas/defect.ts | 19 +++++++++++++++++++ tests/defect-schema.spec.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 shared/schemas/defect.ts create mode 100644 tests/defect-schema.spec.ts diff --git a/shared/schemas/defect.ts b/shared/schemas/defect.ts new file mode 100644 index 0000000..20b9465 --- /dev/null +++ b/shared/schemas/defect.ts @@ -0,0 +1,19 @@ +import { type } from 'arktype' + +/** + * The user-supplied fields when filing a defect. `reporterEmail` and the + * timestamp are resolved server-side (getReporter / DB default), so they are + * not part of this input. This is the single source of validation truth shared + * by the DefectForm and the /api/defects route (wired in Task 5); import it via + * the `#shared` alias from either client or server. + */ +export const defectInput = type({ + // section_id comes from the clicked board section — a non-empty slug. + sectionId: type('string.trim').to('string >= 1'), + // projectId references projects.id, a uuid. + projectId: 'string.uuid', + // The reporter's words — required, trimmed. + verbatim: type('string.trim').to('string >= 1'), +}) + +export type DefectInput = typeof defectInput.infer diff --git a/tests/defect-schema.spec.ts b/tests/defect-schema.spec.ts new file mode 100644 index 0000000..9de4127 --- /dev/null +++ b/tests/defect-schema.spec.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest' +import { type } from 'arktype' +import { defectInput } from '../shared/schemas/defect' + +const valid = { + sectionId: 'a4-flow', + projectId: '123e4567-e89b-12d3-a456-426614174000', + verbatim: 'The thing broke', +} + +describe('defectInput schema', () => { + it('accepts a valid defect and trims verbatim', () => { + const out = defectInput({ ...valid, verbatim: ' hi ' }) + expect(out instanceof type.errors).toBe(false) + expect((out as typeof valid).verbatim).toBe('hi') + }) + + it('rejects empty verbatim', () => { + expect(defectInput({ ...valid, verbatim: '' }) instanceof type.errors).toBe(true) + }) + + it('rejects whitespace-only verbatim', () => { + expect(defectInput({ ...valid, verbatim: ' ' }) instanceof type.errors).toBe(true) + }) + + it('rejects a non-uuid projectId', () => { + expect(defectInput({ ...valid, projectId: 'not-a-uuid' }) instanceof type.errors).toBe(true) + }) +})