rehire_employee war seit dem 17.09. bei jedem Aufruf kaputt, und keine Pruefung konnte das bemerken: die Selbstpruefungen der Migrationen lesen den Text der Funktionen, die Unit-Tests laufen ohne Datenbank, und die Integrationstests haben keinen Bestand, gegen den sie liefen. db/tests/rauchtest.sql schliesst die Luecke -- es ruft die Funktionen auf, gegen die echte Datenbank, in der Reihenfolge des Lebenszyklus: anlegen, versetzen, befoerdern, vormerken, austreten, wiedereintreten. Zwei Eigenschaften, ohne die es gefaehrlich waere. Es laeuft als Anwendungsrolle statt als Superuser -- sonst bewiese es nur, dass die Funktionen fuer niemanden gehen, der sie benutzt. Und es rollt am Ende zurueck, mit einer Zaehlung danach, die das belegt: beim Test vom 29.09. sind reale Personen auf Testplanstellen umgezogen und dort geblieben. Beim Schreiben fiel die fuenfte Stelle mit dem Paar aus Schliessen und Einfuegen auf: terminate_employee. Wer heute eingestellt und heute wieder ausgetragen wird -- ohne den Grund "No Show" --, lief in chk_assignment_range. Die No-Show-Haelfte derselben Funktion macht es laengst richtig und erklaert auch, warum; es galt nur fuer genau einen Austrittsgrund.
186 lines
8.8 KiB
PL/PgSQL
186 lines
8.8 KiB
PL/PgSQL
-- Rauchtest: der Lebenszyklus, einmal wirklich ausgeführt
|
|
--
|
|
-- docker compose run --rm psql -f /repo/db/tests/rauchtest.sql
|
|
--
|
|
-- ═══ Warum es das gibt ════════════════════════════════════════════
|
|
--
|
|
-- rehire_employee war seit dem 17.09. bei jedem Aufruf kaputt: ein fehlender
|
|
-- Umwandlungsschritt beim Status. Aufgefallen ist es erst beim Handtest am
|
|
-- 29.09. Keine Prüfung der Anwendung hat es bemerkt, und keine konnte es:
|
|
-- die Selbstprüfungen der Migrationen lesen den **Text** der Funktionen, die
|
|
-- Unit-Tests laufen ohne Datenbank, und die Integrationstests haben keinen
|
|
-- Bestand, gegen den sie liefen.
|
|
--
|
|
-- Diese Datei schliesst genau diese Lücke: sie **ruft** die Funktionen auf,
|
|
-- gegen die echte Datenbank, in der Reihenfolge des Lebenszyklus.
|
|
--
|
|
-- ═══ Zwei Eigenschaften, ohne die es gefährlich wäre ══════════════
|
|
--
|
|
-- 1. **Es wird zurückgerollt.** Alles läuft in einer Transaktion, die am Ende
|
|
-- verworfen wird. Nach dem Lauf steht kein Testmensch, keine Testplanstelle
|
|
-- und keine Testeinheit in der Datenbank. Der Anlass steht daneben: beim
|
|
-- Test vom 29.09. sind reale Personen auf Testplanstellen umgezogen und
|
|
-- dort geblieben.
|
|
--
|
|
-- 2. **Es läuft als Anwendungsrolle.** psql verbindet sich als Superuser, und
|
|
-- der geht an jeder Zeilenschutz-Regel und an require_hr_admin() vorbei.
|
|
-- Ein Lauf als postgres würde beweisen, dass die Funktionen für niemanden
|
|
-- funktionieren, der sie benutzt.
|
|
--
|
|
-- Fehlschläge brechen mit einer Meldung ab, die den Schritt nennt. Kommt der
|
|
-- Lauf bis „Rauchtest bestanden" durch, ist der Lebenszyklus gegangen worden.
|
|
|
|
\set ON_ERROR_STOP on
|
|
|
|
begin;
|
|
|
|
select set_config(
|
|
'app.user_id',
|
|
(select p.id::text from profiles p where p.role = 'hr' and p.is_active limit 1),
|
|
true
|
|
) as angemeldet_als;
|
|
|
|
set local role alpenwerk_app;
|
|
|
|
do $rauchtest$
|
|
declare
|
|
v_wurzel uuid;
|
|
v_einheit uuid;
|
|
v_p1 uuid; v_p2 uuid; v_p3 uuid; v_p4 uuid;
|
|
v_a uuid; v_b uuid;
|
|
v_ort uuid;
|
|
v_nr int;
|
|
v_status text;
|
|
v_stelle uuid;
|
|
v_offen int;
|
|
v_meldung text;
|
|
begin
|
|
select id into v_wurzel from org_units where parent_id is null;
|
|
select id into v_ort from locations order by name limit 1;
|
|
select coalesce(max(personnel_number), 0) + 1 into v_nr from employees;
|
|
if v_wurzel is null or v_ort is null then
|
|
raise exception 'Kein Bestand: es fehlt die oberste Einheit oder ein Standort.';
|
|
end if;
|
|
|
|
-- ── Spielwiese ──────────────────────────────────────────────────
|
|
v_einheit := create_org_unit(jsonb_build_object(
|
|
'parent_id', v_wurzel, 'org_number', 'RAUCHTEST', 'name', 'Rauchtest',
|
|
'unit_type', 'Team', 'valid_from', (current_date - 30)::text));
|
|
|
|
v_p1 := create_position(jsonb_build_object('org_unit_id', v_einheit,
|
|
'job_title', 'Rauchtest Eins', 'is_chief', false, 'valid_from', (current_date - 30)::text));
|
|
v_p2 := create_position(jsonb_build_object('org_unit_id', v_einheit,
|
|
'job_title', 'Rauchtest Zwei', 'is_chief', false, 'valid_from', (current_date - 30)::text));
|
|
v_p3 := create_position(jsonb_build_object('org_unit_id', v_einheit,
|
|
'job_title', 'Rauchtest Drei', 'is_chief', false, 'valid_from', (current_date - 30)::text));
|
|
v_p4 := create_position(jsonb_build_object('org_unit_id', v_einheit,
|
|
'job_title', 'Rauchtest Vier', 'is_chief', false, 'valid_from', (current_date - 30)::text));
|
|
raise notice '1/8 Einheit und vier Planstellen angelegt';
|
|
|
|
-- ── Einstellung ─────────────────────────────────────────────────
|
|
v_a := hire_employee(jsonb_build_object(
|
|
'personnel_number', v_nr, 'first_name', 'Rauch', 'last_name', 'Test-A',
|
|
'gender', 'w', 'birth_date', '1990-01-01', 'location_id', v_ort,
|
|
'position_id', v_p1, 'entry_date', current_date::text));
|
|
raise notice '2/8 Einstellung auf Planstelle 1';
|
|
|
|
-- ── Versetzung am selben Tag (20260929180000) ───────────────────
|
|
-- Die Besetzung begann heute; sie auf heute zu schliessen wäre ein
|
|
-- Intervall von null Tagen. Vorher: chk_assignment_range.
|
|
perform transfer_employee(jsonb_build_object(
|
|
'employee_id', v_a, 'target_position_id', v_p2, 'effective_date', current_date::text));
|
|
|
|
select position_id into v_stelle from position_assignments
|
|
where employee_id = v_a and valid_to is null;
|
|
if v_stelle is distinct from v_p2 then
|
|
raise exception 'Nach der Versetzung sitzt die Person nicht auf Planstelle 2.';
|
|
end if;
|
|
raise notice '3/8 Versetzung am selben Tag';
|
|
|
|
-- ── Beförderung mit Planstellenwechsel, ebenfalls heute ─────────
|
|
perform promote_employee(jsonb_build_object(
|
|
'employee_id', v_a, 'effective_date', current_date::text,
|
|
'new_title', 'Rauchtest Leitung', 'new_paygrade', 'HG15',
|
|
'target_position_id', v_p3));
|
|
|
|
select position_id into v_stelle from position_assignments
|
|
where employee_id = v_a and valid_to is null;
|
|
if v_stelle is distinct from v_p3 then
|
|
raise exception 'Nach der Beförderung sitzt die Person nicht auf Planstelle 3.';
|
|
end if;
|
|
if (select paygrade::text from employees where id = v_a) <> 'HG15' then
|
|
raise exception 'Die Beförderung hat den Hay-Grade nicht gesetzt.';
|
|
end if;
|
|
raise notice '4/8 Beförderung am selben Tag, Hay-Grade gesetzt';
|
|
|
|
-- ── Vorgemerkte Planstelle (20260929200000) ─────────────────────
|
|
-- Eine Versetzung in die Zukunft belegt die Zielstelle, obwohl dort noch
|
|
-- keine Besetzung steht. Wer sie vorher besetzt, bringt den Nachtlauf zum
|
|
-- Stehen — vorher ging genau das durch.
|
|
perform transfer_employee(jsonb_build_object(
|
|
'employee_id', v_a, 'target_position_id', v_p4, 'effective_date', (current_date + 10)::text));
|
|
|
|
begin
|
|
perform hire_employee(jsonb_build_object(
|
|
'personnel_number', v_nr + 1, 'first_name', 'Rauch', 'last_name', 'Test-B',
|
|
'gender', 'm', 'birth_date', '1990-01-01', 'location_id', v_ort,
|
|
'position_id', v_p4, 'entry_date', current_date::text));
|
|
raise exception 'Die vorgemerkte Planstelle liess sich besetzen.';
|
|
exception
|
|
when others then
|
|
v_meldung := SQLERRM;
|
|
if v_meldung not like '%vorgemerkt%' then
|
|
raise exception 'Erwartet wurde die Meldung zur Vormerkung, kam aber: %', v_meldung;
|
|
end if;
|
|
end;
|
|
raise notice '5/8 Vorgemerkte Planstelle wird abgewiesen';
|
|
|
|
-- Die eigene Vormerkung darf nicht im Weg stehen: dieselbe Versetzung noch
|
|
-- einmal zu erfassen ist eine Berichtigung.
|
|
perform transfer_employee(jsonb_build_object(
|
|
'employee_id', v_a, 'target_position_id', v_p4, 'effective_date', (current_date + 20)::text));
|
|
raise notice '6/8 Die eigene Vormerkung steht nicht im Weg';
|
|
|
|
-- ── Austritt am selben Tag (20260929220000) ─────────────────────
|
|
perform terminate_employee(jsonb_build_object(
|
|
'employee_id', v_a, 'exit_date', current_date::text,
|
|
'exit_reason', 'Einvernehmliche Auflösung', 'austrittsart', 'freiwillig'));
|
|
|
|
select count(*) into v_offen from position_assignments
|
|
where employee_id = v_a and valid_to is null;
|
|
if v_offen <> 0 then
|
|
raise exception 'Nach dem Austritt ist die Planstelle nicht frei.';
|
|
end if;
|
|
raise notice '7/8 Austritt am selben Tag, Planstelle frei';
|
|
|
|
-- ── Wiedereintritt (20260929160000, K.01) ───────────────────────
|
|
perform rehire_employee(jsonb_build_object(
|
|
'employee_id', v_a, 'rehire_date', current_date::text, 'position_id', v_p1,
|
|
'first_name', 'Rauch', 'last_name', 'Test-A', 'gender', 'w',
|
|
'birth_date', '1990-01-01', 'nationality', 'Österreich', 'location_id', v_ort));
|
|
|
|
select status::text into v_status from employees where id = v_a;
|
|
if v_status <> 'Aktiv' then
|
|
raise exception 'Nach dem Wiedereintritt steht der Status auf %, erwartet Aktiv.', v_status;
|
|
end if;
|
|
select position_id into v_stelle from position_assignments
|
|
where employee_id = v_a and valid_to is null;
|
|
if v_stelle is distinct from v_p1 then
|
|
raise exception 'Nach dem Wiedereintritt sitzt die Person nicht auf Planstelle 1.';
|
|
end if;
|
|
raise notice '8/8 Wiedereintritt';
|
|
|
|
raise notice '';
|
|
raise notice 'Rauchtest bestanden. Alles Angelegte wird gleich verworfen.';
|
|
end
|
|
$rauchtest$;
|
|
|
|
rollback;
|
|
|
|
-- Beweis, dass nichts geblieben ist. Läuft ausserhalb der verworfenen
|
|
-- Transaktion und muss überall 0 liefern.
|
|
select
|
|
(select count(*) from org_units where org_number = 'RAUCHTEST') as einheiten,
|
|
(select count(*) from employees where last_name like 'Test-%' and first_name = 'Rauch') as personen,
|
|
(select count(*) from jobs where title like 'Rauchtest %') as taetigkeiten;
|