/** * Tests for the zod-validation in PATCH /api/profile/me/demographics. * Replicates the schema from the endpoint to test the validation * boundaries (anti-Cascade-Bug: invalid input must 422, not 500). */ import { describe, expect, it } from "vitest"; import { z } from "zod"; const BUNDESLAND_REGEX = /^DE-(BW|BY|BE|BB|HB|HH|HE|MV|NI|NW|RP|SL|SN|ST|SH|TH)$/; const GENDER_VALUES = ["male", "female", "diverse", "no_answer"] as const; const MARITAL_VALUES = [ "single", "partnered", "married", "divorced", "widowed", "no_answer", ] as const; const Schema = z.object({ birthYear: z.number().int().min(1920).max(2010).nullable().optional(), gender: z.enum(GENDER_VALUES).nullable().optional(), maritalStatus: z.enum(MARITAL_VALUES).nullable().optional(), profession: z.string().trim().max(80).nullable().optional(), bundesland: z.string().regex(BUNDESLAND_REGEX).nullable().optional(), city: z.string().trim().max(80).nullable().optional(), }); describe("demographics zod schema — happy", () => { it("accepts a complete valid demographic payload", () => { const r = Schema.safeParse({ birthYear: 1989, gender: "diverse", maritalStatus: "partnered", profession: "Pflege", bundesland: "DE-BY", city: "München", }); expect(r.success).toBe(true); }); it("accepts a partial subset (1 field at a time)", () => { expect(Schema.safeParse({ birthYear: 1989 }).success).toBe(true); expect(Schema.safeParse({ city: "Berlin" }).success).toBe(true); }); it("accepts explicit nulls (= clear field)", () => { expect(Schema.safeParse({ city: null, birthYear: null }).success).toBe(true); }); }); describe("demographics zod schema — invalid input must reject (not 500)", () => { it("rejects birthYear out of range (< 1920)", () => { const r = Schema.safeParse({ birthYear: 1800 }); expect(r.success).toBe(false); }); it("rejects birthYear out of range (> 2010 = under-13 protection)", () => { const r = Schema.safeParse({ birthYear: 2024 }); expect(r.success).toBe(false); }); it("rejects unknown gender enum", () => { const r = Schema.safeParse({ gender: "alien" }); expect(r.success).toBe(false); }); it("rejects bundesland with foreign-country code", () => { expect(Schema.safeParse({ bundesland: "AT-9" }).success).toBe(false); expect(Schema.safeParse({ bundesland: "BY" }).success).toBe(false); expect(Schema.safeParse({ bundesland: "DE-XX" }).success).toBe(false); }); it("accepts all 16 valid Bundesländer codes", () => { const codes = [ "DE-BW","DE-BY","DE-BE","DE-BB","DE-HB","DE-HH","DE-HE","DE-MV", "DE-NI","DE-NW","DE-RP","DE-SL","DE-SN","DE-ST","DE-SH","DE-TH", ]; for (const c of codes) { expect(Schema.safeParse({ bundesland: c }).success).toBe(true); } }); it("rejects profession longer than 80 chars", () => { const r = Schema.safeParse({ profession: "x".repeat(81) }); expect(r.success).toBe(false); }); it("rejects city longer than 80 chars", () => { const r = Schema.safeParse({ city: "x".repeat(81) }); expect(r.success).toBe(false); }); });