import type { SupabaseClient } from "@supabase/supabase-js"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import type { Database } from "@/lib/supabase/types"; import { adminClient, createHrUser, createTestPosition, deleteTestEmployee, deleteTestPosition, deleteTestUser, hireTestEmployee, isoDateOffset, pickSeededUnit, signInAs, type TestUser, } from "./helpers"; // SVNR validation (supabase/migrations/20260725120000_svnr_validation.sql). // Enforced by a trigger, so these drive it through the real write paths and // through a direct update — both must be covered by the same rule. describe("SVNR validation", () => { let hrUser: TestUser; let hrClient: SupabaseClient; let unit: { id: string }; const employeeIds: string[] = []; const positionIds: string[] = []; // 3·1 + 7·2 + 9·3 = 44; 010180 contributes 18; 62 mod 11 = 7 const VALID = "1237 010180"; const BIRTH_DATE = "1980-01-01"; async function locationIn(country: string): Promise { const { data } = await adminClient.from("locations").select("id").eq("country", country).limit(1).maybeSingle(); if (!data) throw new Error(`no seeded location in ${country}`); return data.id; } beforeAll(async () => { hrUser = await createHrUser({ active: true }); hrClient = await signInAs(hrUser); unit = await pickSeededUnit(); }); afterAll(async () => { for (const id of employeeIds) await deleteTestEmployee(id); for (const id of positionIds) await deleteTestPosition(id); await deleteTestUser(hrUser); }); async function hireAt(country: string, overrides: Record = {}): Promise { // Eine eigene Planstelle je Einstellung: eine geteilte wäre nach der // ersten besetzt. const positionId = await createTestPosition(hrClient, unit.id, { valid_from: isoDateOffset(-40) }); positionIds.push(positionId); const id = await hireTestEmployee(hrClient, positionId, { location_id: await locationIn(country), birth_date: BIRTH_DATE, ...overrides, }); employeeIds.push(id); return id; } describe("is_valid_svnr", () => { async function check(svnr: string, birthDate: string | null = null): Promise { const { data, error } = await adminClient.rpc("is_valid_svnr", { p_svnr: svnr, p_birth_date: birthDate }); if (error) throw new Error(error.message); return data as unknown as boolean; } it("matches the TypeScript implementation on the documented cases", async () => { expect(await check(VALID)).toBe(true); expect(await check("1237010180")).toBe(true); expect(await check("1234 010180")).toBe(false); // wrong check digit expect(await check("0007 010180")).toBe(false); // 000 serial expect(await check("0040 010180")).toBe(false); // check digit would be 10 expect(await check("1237 011380")).toBe(false); // month 13 expect(await check("123701018")).toBe(false); // nine digits }); it("cross-checks the birth date when one is given", async () => { expect(await check(VALID, BIRTH_DATE)).toBe(true); expect(await check(VALID, "1980-01-02")).toBe(false); }); }); describe("at an Austrian location", () => { it("accepts a hire carrying a valid number", async () => { const id = await hireAt("Österreich", { sv_nummer: VALID }); const { data } = await adminClient.from("employees").select("sv_nummer").eq("id", id).single(); expect(data?.sv_nummer).toBe(VALID); }); it("rejects a hire carrying an invalid number", async () => { await expect(hireAt("Österreich", { sv_nummer: "1234 010180" })).rejects.toThrow(/SV-Nummer/i); }); it("rejects a number whose birth date disagrees with the employee record", async () => { await expect(hireAt("Österreich", { sv_nummer: VALID, birth_date: "1975-06-30" })).rejects.toThrow(/SV-Nummer/i); }); it("allows the field to stay empty", async () => { const id = await hireAt("Österreich"); const { data } = await adminClient.from("employees").select("sv_nummer").eq("id", id).single(); expect(data?.sv_nummer ?? null).toBeNull(); }); it("rejects an update to an invalid number", async () => { const id = await hireAt("Österreich", { sv_nummer: VALID }); const { error } = await adminClient.from("employees").update({ sv_nummer: "9999 010180" }).eq("id", id); expect(error?.message ?? "").toMatch(/SV-Nummer/i); }); }); describe("outside Austria", () => { it("leaves the field free-form", async () => { // The German equivalent has a different format entirely; validating it // against the Austrian standard would reject correct data. const id = await hireAt("Deutschland", { sv_nummer: "12 345678 A 901" }); const { data } = await adminClient.from("employees").select("sv_nummer").eq("id", id).single(); expect(data?.sv_nummer).toBe("12 345678 A 901"); }); }); describe("legacy rows", () => { it("does not block an unrelated edit when the stored number is invalid", async () => { // Rows predating the migration hold unvalidated values. A transfer or // a name change must not fail because of a number nobody is touching. const id = await hireAt("Deutschland", { sv_nummer: "0000 000000" }); const { error: moveError } = await adminClient .from("employees") .update({ location_id: await locationIn("Österreich") }) .eq("id", id); expect(moveError).toBeNull(); const { error: nameError } = await adminClient.from("employees").update({ phone: "+43 1 9999999" }).eq("id", id); expect(nameError).toBeNull(); }); }); });