import { randomUUID } from "node:crypto"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { adminClient, createHrUser, createTestPosition, deleteTestEmployee, deleteTestPosition, deleteTestUser, hireTestEmployee, isoDateOffset, pickSeededLocation, pickSeededTeam, signInAs, teamLeadId, type TestUser, } from "./helpers"; import type { SupabaseClient } from "@supabase/supabase-js"; import type { Database } from "@/lib/supabase/types"; // Position validity window + delete (supabase/migrations/20260716120000_position_validity_and_delete.sql): // positions now carry a required valid_from ("gültig ab") date, an open // position can be deleted again, and neither internal staffing nor an // external hire may assign an employee to a position before that date. describe("position validity and delete", () => { let hrUser: TestUser; let hrClient: SupabaseClient; let teamA: { id: string }; let superiorId: string; const positionIds: string[] = []; const employeeIds: string[] = []; beforeAll(async () => { hrUser = await createHrUser({ active: true }); hrClient = await signInAs(hrUser); teamA = await pickSeededTeam(); superiorId = (await teamLeadId(teamA.id))!; }); afterAll(async () => { // Positions first: reports_to_employee_id / filled_by_employee_id // reference employees(id) with no cascade. for (const id of positionIds) await deleteTestPosition(id); for (const id of employeeIds) await deleteTestEmployee(id); await deleteTestUser(hrUser); }); it("create_position records the given valid_from", async () => { const validFrom = isoDateOffset(10); const positionId = await createTestPosition(hrClient, superiorId, { valid_from: validFrom }); positionIds.push(positionId); const { data } = await adminClient.from("positions").select("valid_from").eq("id", positionId).single(); expect(data?.valid_from).toBe(validFrom); }); it("create_position defaults valid_from to today when omitted", async () => { const positionId = await createTestPosition(hrClient, superiorId); positionIds.push(positionId); const { data } = await adminClient.from("positions").select("valid_from").eq("id", positionId).single(); expect(data?.valid_from).toBe(isoDateOffset(0)); }); it("delete_position removes an open position", async () => { const positionId = await createTestPosition(hrClient, superiorId); const { error } = await hrClient.rpc("delete_position", { payload: { position_id: positionId } }); expect(error).toBeNull(); const { data } = await adminClient.from("positions").select("id").eq("id", positionId).maybeSingle(); expect(data).toBeNull(); }); it("delete_position rejects a filled position", async () => { const positionId = await createTestPosition(hrClient, superiorId, { valid_from: isoDateOffset(-10) }); positionIds.push(positionId); const employeeId = await hireTestEmployee(hrClient, teamA.id); employeeIds.push(employeeId); const { error: staffError } = await hrClient.rpc("staff_position_internally", { payload: { position_id: positionId, employee_id: employeeId }, }); expect(staffError).toBeNull(); const { error } = await hrClient.rpc("delete_position", { payload: { position_id: positionId } }); expect(error?.message).toMatch(/Nur offene Positionen können gelöscht werden/); }); it("staff_position_internally rejects assigning to a position before its valid_from", async () => { const positionId = await createTestPosition(hrClient, superiorId, { valid_from: isoDateOffset(10) }); positionIds.push(positionId); const employeeId = await hireTestEmployee(hrClient, teamA.id); employeeIds.push(employeeId); const { error } = await hrClient.rpc("staff_position_internally", { payload: { position_id: positionId, employee_id: employeeId }, }); expect(error?.message).toMatch(/erst ab .* gültig/); }); it("staff_position_internally accepts assigning to a position on/after its valid_from", async () => { const positionId = await createTestPosition(hrClient, superiorId, { valid_from: isoDateOffset(-1) }); positionIds.push(positionId); const employeeId = await hireTestEmployee(hrClient, teamA.id); employeeIds.push(employeeId); const { error } = await hrClient.rpc("staff_position_internally", { payload: { position_id: positionId, employee_id: employeeId }, }); expect(error).toBeNull(); }); it("hire_employee rejects an entry_date before the position's valid_from", async () => { const validFrom = isoDateOffset(10); const positionId = await createTestPosition(hrClient, superiorId, { valid_from: validFrom }); positionIds.push(positionId); const location = await pickSeededLocation(); const { error } = await hrClient.rpc("hire_employee", { payload: { first_name: "Integrationstest", last_name: `Person-${randomUUID().slice(0, 8)}`, gender: "w", birth_date: "1990-01-01", location_id: location.id, position_id: positionId, entry_date: isoDateOffset(5), source: "Extern", }, }); expect(error?.message).toMatch(/Eintrittsdatum darf nicht vor dem Gültigkeitsbeginn/); }); it("hire_employee accepts an entry_date on/after the position's valid_from", async () => { const validFrom = isoDateOffset(10); const positionId = await createTestPosition(hrClient, superiorId, { valid_from: validFrom }); positionIds.push(positionId); const location = await pickSeededLocation(); const { data, error } = await hrClient.rpc("hire_employee", { payload: { first_name: "Integrationstest", last_name: `Person-${randomUUID().slice(0, 8)}`, gender: "w", birth_date: "1990-01-01", location_id: location.id, position_id: positionId, entry_date: validFrom, source: "Extern", }, }); expect(error).toBeNull(); if (data) employeeIds.push(data); }); });