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, deleteTestEmployee, deleteTestUser, hireTestEmployee, isoDateOffset, pickSeededTeam, signInAs, type TestUser, } from "./helpers"; // Org-assignment history (supabase/migrations/20260724120000_employee_ // assignment_history.sql). The point of capturing this with a trigger rather // than inside each RPC is that it holds for *every* write path — so these // tests drive the real RPCs and assert on the timeline they leave behind. describe("employee_assignments history", () => { let hrUser: TestUser; let hrClient: SupabaseClient; let teamA: { id: string }; let teamB: { id: string }; const employeeIds: string[] = []; beforeAll(async () => { hrUser = await createHrUser({ active: true }); hrClient = await signInAs(hrUser); teamA = await pickSeededTeam(); teamB = await pickSeededTeam(teamA.id); }); afterAll(async () => { for (const id of employeeIds) await deleteTestEmployee(id); await deleteTestUser(hrUser); }); async function freshEmployee(teamId: string): Promise { const id = await hireTestEmployee(hrClient, teamId); employeeIds.push(id); return id; } async function assignmentsFor(employeeId: string) { const { data } = await adminClient .from("employee_assignments") .select("team_id, job_title, valid_from, valid_to") .eq("employee_id", employeeId) .order("valid_from"); return data ?? []; } it("opens an interval when an employee is hired", async () => { const employeeId = await freshEmployee(teamA.id); const rows = await assignmentsFor(employeeId); expect(rows).toHaveLength(1); expect(rows[0].team_id).toBe(teamA.id); expect(rows[0].valid_to).toBeNull(); }); it("closes the old interval and opens a new one on transfer", async () => { const employeeId = await freshEmployee(teamA.id); const { error } = await hrClient.rpc("transfer_employee", { payload: { employee_id: employeeId, effective_date: isoDateOffset(0), new_team_id: teamB.id }, }); expect(error).toBeNull(); const rows = await assignmentsFor(employeeId); expect(rows).toHaveLength(2); expect(rows[0].team_id).toBe(teamA.id); expect(rows[0].valid_to).toBe(isoDateOffset(0)); expect(rows[1].team_id).toBe(teamB.id); expect(rows[1].valid_to).toBeNull(); // Intervals must abut exactly, or an as-of query lands in a gap. expect(rows[1].valid_from).toBe(rows[0].valid_to); }); it("rewrites in place rather than leaving a zero-length interval for a same-day second move", async () => { const employeeId = await freshEmployee(teamA.id); await hrClient.rpc("transfer_employee", { payload: { employee_id: employeeId, effective_date: isoDateOffset(0), new_team_id: teamB.id }, }); await hrClient.rpc("transfer_employee", { payload: { employee_id: employeeId, effective_date: isoDateOffset(0), new_team_id: teamA.id }, }); const rows = await assignmentsFor(employeeId); expect(rows.every((r) => r.valid_to === null || r.valid_to > r.valid_from)).toBe(true); expect(rows.filter((r) => r.valid_to === null)).toHaveLength(1); expect(rows.at(-1)?.team_id).toBe(teamA.id); }); it("records a promotion's new title as its own interval", async () => { const employeeId = await freshEmployee(teamA.id); const { error } = await hrClient.rpc("promote_employee", { payload: { employee_id: employeeId, effective_date: isoDateOffset(0), new_title: "Senior Testtitel" }, }); expect(error).toBeNull(); const rows = await assignmentsFor(employeeId); expect(rows.at(-1)?.job_title).toBe("Senior Testtitel"); expect(rows.at(-1)?.valid_to).toBeNull(); }); it("writes no new interval when nothing about the placement changed", async () => { const employeeId = await freshEmployee(teamA.id); const before = await assignmentsFor(employeeId); const { error } = await hrClient.rpc("change_employee_data", { payload: { employee_id: employeeId, effective_date: isoDateOffset(0), person: { phone: "+43 1 2345678" }, contract: {}, role: {}, }, }); expect(error).toBeNull(); expect(await assignmentsFor(employeeId)).toHaveLength(before.length); }); it("keeps exactly one open interval per employee", async () => { const employeeId = await freshEmployee(teamA.id); await hrClient.rpc("transfer_employee", { payload: { employee_id: employeeId, effective_date: isoDateOffset(0), new_team_id: teamB.id }, }); const rows = await assignmentsFor(employeeId); expect(rows.filter((r) => r.valid_to === null)).toHaveLength(1); }); it("is not readable without an active HR session", async () => { const outsider = await createHrUser({ active: false }); const outsiderClient = await signInAs(outsider); const { data } = await outsiderClient.from("employee_assignments").select("id").limit(1); expect(data ?? []).toHaveLength(0); await deleteTestUser(outsider); }); });