pediatric-ai-scribe-v3/shared/schemas.test.ts
Daniel 6d30edf88f build(ts): day 5 — Vitest + Zod + Knip tooling
Adds the three high-ROI tools the planning conversation identified:

  vitest 4.x — fast TS-native unit test runner. Runs against pure
    functions (calculators, validators, prompt builders). Playwright
    stays for e2e. package.json `npm test` now runs Vitest;
    `npm run test:node` preserves the old `node --test` runner
    for the 3 legacy tests under test/.

  zod 4.x — runtime request-body validation at API boundaries. The
    new shared/schemas.ts exports a schema per endpoint request
    (LoginRequestSchema, SoapRequestSchema, PeNarrativeRequestSchema,
    ExtensionCreateSchema, etc.). Routes will adopt these one at a
    time post-migration — usage pattern:
      const body = SoapRequestSchema.parse(req.body);
    Invalid input becomes a structured 400 instead of a silent
    undefined-access crash.

  knip 6.x — dead-code / unused-export detector. knip.json scopes
    it to the backend (client/ excluded since it lives in its own
    workspace). Run with `npm run lint:dead`. Catches the class of
    bug that kept public/js/adminMilestones.js dead-loaded for a
    year — a future orphaned file would fail the lint.

  @vitest/coverage-v8 — coverage reporter backed by v8 profiler.

Shipped schemas.test.ts with 8 example cases to prove the toolchain
(`npx vitest run` green).

Not done on day 5 (punted to post-migration): flipping tsconfig to
`strict: true`. That cascade would light up hundreds of implicit-
any errors in handler signatures that would cost more commit bandwidth
than available in this pass. The Day 4 permissive mode is already
catching the big wins (wrong response shapes, orphan refs, undefined
destructures). Post-migration, Codex/vendor model can flip strict flags
one at a time and fix handler-by-handler.
2026-04-23 19:54:30 +02:00

71 lines
2 KiB
TypeScript

// Example Vitest spec. Proves the toolchain runs and demonstrates the
// Zod + schema-inferred type pattern. Run with: npm test
import { describe, it, expect } from 'vitest';
import {
LoginRequestSchema,
ExtensionCreateSchema,
PeNarrativeRequestSchema,
} from './schemas';
describe('LoginRequestSchema', () => {
it('accepts a valid login body', () => {
const ok = LoginRequestSchema.safeParse({
email: 'danvics@ped-ai.test',
password: 'some-password',
});
expect(ok.success).toBe(true);
});
it('rejects an invalid email', () => {
const bad = LoginRequestSchema.safeParse({
email: 'not-an-email',
password: 'x',
});
expect(bad.success).toBe(false);
});
it('rejects missing password', () => {
const bad = LoginRequestSchema.safeParse({ email: 'a@b.com' });
expect(bad.success).toBe(false);
});
});
describe('ExtensionCreateSchema', () => {
it('accepts a minimal extension', () => {
const ok = ExtensionCreateSchema.safeParse({
location: 'Main Hospital',
name: 'Nursery',
number: '5866',
});
expect(ok.success).toBe(true);
});
it('defaults type when omitted (Zod returns the field as undefined)', () => {
const ok = ExtensionCreateSchema.safeParse({
location: 'X',
name: 'Y',
number: '1',
});
expect(ok.success).toBe(true);
if (ok.success) expect(ok.data.type).toBeUndefined();
});
it('rejects missing required fields', () => {
const bad = ExtensionCreateSchema.safeParse({ location: 'X' });
expect(bad.success).toBe(false);
});
});
describe('PeNarrativeRequestSchema', () => {
it('requires a non-empty steps array', () => {
const bad = PeNarrativeRequestSchema.safeParse({ steps: [] });
expect(bad.success).toBe(false);
});
it('accepts a single step', () => {
const ok = PeNarrativeRequestSchema.safeParse({
steps: [{ label: 'General appearance', status: 'normal' }],
});
expect(ok.success).toBe(true);
});
});