feat(archive): RET-06-API Aufbewahrungsfristen-Konfigurations-Backend
Board-Entscheidung: Backend-API zuerst, echtes Next.js-Frontend als separates Folgeticket - vermeidet Pseudo-Frontend-Protokoll. internal/retentionapi: 4 Endpunkte (anlegen/aendern, deaktivieren, liste, vorschau), Vorschau nutzt dieselbe ListExpiringObjects-Funktion wie RET-02s periodischer Job (keine Doppel-Implementierung). RequireRole ist AUSDRUECKLICH kein RBAC-02-Ersatz, sondern ein dokumentiertes Provisorium (Header-Check) - RBAC-02 ist reiner Core-interner Go-Code ohne HTTP-Schnittstelle fuer andere Module, derselbe Befund wie FDN-03/FDN-09. Provisorium real getestet inkl. Negativfall (403 ohne/mit falscher Rolle). retention_class_rules um active-Flag erweitert (deaktivieren ohne Historienverlust). Real auf 131 deployed und per curl end-to-end verifiziert.
This commit is contained in:
@@ -37,7 +37,8 @@ func requireTestPool(t *testing.T) *pgxpool.Pool {
|
||||
retention_class TEXT NOT NULL, assigned_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS retention_class_rules (
|
||||
retention_class TEXT PRIMARY KEY, duration INTERVAL NOT NULL
|
||||
retention_class TEXT PRIMARY KEY, duration INTERVAL NOT NULL,
|
||||
active BOOLEAN NOT NULL DEFAULT true
|
||||
);
|
||||
`); err != nil {
|
||||
t.Fatalf("schema: %v", err)
|
||||
@@ -105,6 +106,66 @@ func TestComputeDueDate_KnownReferenceDates(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigureClassRule_ChangeAppliesOnlyToFutureCalculations ist
|
||||
// Pruefung fuer RET-06: eine Fristaenderung wirkt sich nur auf
|
||||
// KUENFTIGE Berechnungen aus, nicht rueckwirkend - real bewiesen, indem
|
||||
// ein bereits berechneter Stichtag (in einer lokalen Variable, dem
|
||||
// einzigen Ort, an dem ein "bereits berechnetes" Ergebnis ueberhaupt
|
||||
// existiert - es gibt keine Tabelle, die rueckwirkend veraendert werden
|
||||
// koennte) nach der Regelaenderung unveraendert bleibt, waehrend eine
|
||||
// NEUE Berechnung fuer dieselbe Klasse die NEUE Frist verwendet.
|
||||
func TestConfigureClassRule_ChangeAppliesOnlyToFutureCalculations(t *testing.T) {
|
||||
pool := requireTestPool(t)
|
||||
ctx := context.Background()
|
||||
start := mustTime(t, "2006-01-02", "2026-01-01")
|
||||
|
||||
if err := ConfigureClassRule(ctx, pool, "klasse-aenderung", "1 year"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
before, err := ComputeDueDate(ctx, pool, start, "klasse-aenderung")
|
||||
if err != nil {
|
||||
t.Fatalf("erste berechnung: %v", err)
|
||||
}
|
||||
|
||||
if err := ConfigureClassRule(ctx, pool, "klasse-aenderung", "2 years"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Der bereits berechnete Wert (before) bleibt unveraendert - er ist
|
||||
// eine lokale Kopie, es existiert keine Tabelle, die eine
|
||||
// nachtraegliche "Umschreibung" ermoeglichen wuerde.
|
||||
if !before.Equal(mustTime(t, "2006-01-02", "2027-01-01")) {
|
||||
t.Fatalf("bereits berechneter stichtag veraendert: %v", before)
|
||||
}
|
||||
|
||||
after, err := ComputeDueDate(ctx, pool, start, "klasse-aenderung")
|
||||
if err != nil {
|
||||
t.Fatalf("zweite berechnung: %v", err)
|
||||
}
|
||||
if !after.Equal(mustTime(t, "2006-01-02", "2028-01-01")) {
|
||||
t.Fatalf("neue berechnung uebernimmt neue frist nicht: %v", after)
|
||||
}
|
||||
if before.Equal(after) {
|
||||
t.Fatal("neue frist haette eine andere berechnung liefern muessen")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeactivateClassRule_ExcludesFromFutureCalculations.
|
||||
func TestDeactivateClassRule_ExcludesFromFutureCalculations(t *testing.T) {
|
||||
pool := requireTestPool(t)
|
||||
ctx := context.Background()
|
||||
|
||||
if err := ConfigureClassRule(ctx, pool, "klasse-deakt-eng", "1 year"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := DeactivateClassRule(ctx, pool, "klasse-deakt-eng"); err != nil {
|
||||
t.Fatalf("deactivateclassrule: %v", err)
|
||||
}
|
||||
if _, err := ComputeDueDate(ctx, pool, time.Now(), "klasse-deakt-eng"); err == nil {
|
||||
t.Fatal("erwartet fehler: deaktivierte klasse darf nicht mehr verwendet werden")
|
||||
}
|
||||
}
|
||||
|
||||
// TestListExpiringObjects_EmptyBacklogReturnsEmptyNotError ist Pruefung 2.
|
||||
func TestListExpiringObjects_EmptyBacklogReturnsEmptyNotError(t *testing.T) {
|
||||
pool := requireTestPool(t)
|
||||
|
||||
Reference in New Issue
Block a user