From ecea9bcc8c846fa3c9cff13b4b93a69789f39380 Mon Sep 17 00:00:00 2001 From: patrick Date: Wed, 14 Oct 2026 14:49:08 +0000 Subject: [PATCH 1/2] TAR-01: retention-fristen-migration-ohne-reset retain_until aus DOK-01/MAI-02-migrierten Kernobjekten 1:1 in das generische Retention-Modell (RET-01) uebernehmen, ohne Neuberechnung aus dem Migrationsdatum. Co-Authored-By: Claude Sonnet 5 --- archive/docs/TAR-01-PRUEFPROTOKOLL.md | 62 ++++++ archive/internal/retention/retention.go | 32 +++ .../retentionmigration/retentionmigration.go | 79 ++++++++ .../retentionmigration_test.go | 186 ++++++++++++++++++ ...17_retention_objects_retain_until.down.sql | 1 + ...0017_retention_objects_retain_until.up.sql | 1 + 6 files changed, 361 insertions(+) create mode 100644 archive/docs/TAR-01-PRUEFPROTOKOLL.md create mode 100644 archive/internal/retentionmigration/retentionmigration.go create mode 100644 archive/internal/retentionmigration/retentionmigration_test.go create mode 100644 archive/migrations/0017_retention_objects_retain_until.down.sql create mode 100644 archive/migrations/0017_retention_objects_retain_until.up.sql diff --git a/archive/docs/TAR-01-PRUEFPROTOKOLL.md b/archive/docs/TAR-01-PRUEFPROTOKOLL.md new file mode 100644 index 00000000..2f164c4c --- /dev/null +++ b/archive/docs/TAR-01-PRUEFPROTOKOLL.md @@ -0,0 +1,62 @@ +# TAR-01 – Prüfprotokoll: Retention-Fristen-Migration ohne Reset + +Datum: 2026-10-14 +Umgebung: Host 192.168.1.204, Postgres 17, eigene Wegwerf-Rolle/DB `tar01_test`/`tar01_testdb` (ausschließlich synthetische Fixtures, keine archivdms-/archivmail-Daten). + +## Prüfung 1 – 50 synthetische Objekte, Frist in der Zukunft identisch + +Test: `TestMigrateAll_PreservesFutureRetainUntilExactly` (`archive/internal/retentionmigration/retentionmigration_test.go`). + +50 synthetische `dms_document`-Objekte mit je unterschiedlicher, in der Zukunft liegender `retain_until`-Frist wurden über `retentionmigration.Migrator.MigrateAll` migriert und anschließend je Objekt die in `retention_objects.retain_until` gespeicherte Frist mit der Quellfrist verglichen. + +Ergebnis: PASS. Alle 50 Fristen nach Migration bitgenau identisch zur Quelle (`time.Time.Equal`). + +## Prüfung 2 – Migration berechnet keine Frist aus dem aktuellen Datum + +Test: `TestMigrateAll_ResultIndependentOfInjectedNow`. + +Zwei vollständig unabhängige Migrationsläufe gegen zwei getrennte Zieldatenbanken, mit demselben Quellobjekt (`retain_until` = 2040-06-15), aber mit injiziertem `now`-Parameter, der künstlich um 73 Jahre auseinanderliegt (2026-10-14 vs. 2099-12-31). `now` fließt im Migrator ausschließlich in `Result.RanAt` (Protokoll-Metadatum) ein, nie in die Frist-Übernahme (`retention.SetRetainUntil` nimmt die Quellfrist unverändert). + +Ergebnis: PASS. Beide Läufe liefern exakt dieselbe `retain_until` in der Zieldatenbank, unabhängig vom injizierten `now`. + +## Prüfung 3 – Objekte ohne Quellfrist bleiben ohne Frist + +Test: `TestMigrateAll_ObjectsWithoutSourceDeadlineStayWithoutOne`. + +Ein synthetisches `mail_message`-Objekt ohne `RetainUntil` (nil) wurde migriert; `retention.RetainUntil` liefert danach `ok=false` (keine Frist gesetzt), kein Default-/Fallback-Wert. + +Ergebnis: PASS. + +## Migrations-Auf-/Abwärtskompatibilität (neue Spalte) + +Migration `archive/migrations/0017_retention_objects_retain_until.{up,down}.sql` real gegen eine leere Datenbank ausgeführt: `up` fügt `retention_objects.retain_until TIMESTAMPTZ` (nullable) hinzu, `down` entfernt die Spalte wieder sauber. Manuell verifiziert per `psql \d retention_objects` vor/nach up und nach down. + +## Gesamtergebnis + +| Prüfung | Ergebnis | +|---|---| +| 1 – 50 Stichproben, Frist exakt identisch | PASS | +| 2 – keine Berechnung aus aktuellem Datum (zwei Läufe, unterschiedliches `now`) | PASS | +| 3 – keine Frist ohne Quellfrist | PASS | +| Bestehende `archive/internal/retention`-Tests weiterhin grün | PASS (3 PASS, 1 SKIP mangels zweiter Tenant-DSN, unverändert zum Vorzustand) | +| `go vet ./...` (archive-Modul) | clean | +| Migration 0017 up/down gegen leere DB | PASS | + +Vollständiger Testlauf: + +``` +=== RUN TestMigration_ForwardAndBackward +--- PASS: TestMigration_ForwardAndBackward (0.45s) +=== RUN TestRegisterObject_MapsDMSAndMailAdapterObjectsIdentically +--- PASS: TestRegisterObject_MapsDMSAndMailAdapterObjectsIdentically (0.06s) +=== RUN TestAssignClass_IsHistoricized +--- PASS: TestAssignClass_IsHistoricized (0.05s) +=== RUN TestTenantIsolation_Negativtest +--- SKIP: TestTenantIsolation_Negativtest (0.00s) +=== RUN TestMigrateAll_PreservesFutureRetainUntilExactly +--- PASS: TestMigrateAll_PreservesFutureRetainUntilExactly (0.55s) +=== RUN TestMigrateAll_ObjectsWithoutSourceDeadlineStayWithoutOne +--- PASS: TestMigrateAll_ObjectsWithoutSourceDeadlineStayWithoutOne (0.03s) +=== RUN TestMigrateAll_ResultIndependentOfInjectedNow +--- PASS: TestMigrateAll_ResultIndependentOfInjectedNow (0.06s) +``` diff --git a/archive/internal/retention/retention.go b/archive/internal/retention/retention.go index 24ff1ded..9b8ca6aa 100644 --- a/archive/internal/retention/retention.go +++ b/archive/internal/retention/retention.go @@ -123,3 +123,35 @@ func ClassHistory(ctx context.Context, pool *pgxpool.Pool, retentionObjectID str } return history, rows.Err() } + +// SetRetainUntil setzt retain_until direkt als UPDATE — keine Ableitung +// aus Klasse/Zuordnungszeitpunkt (siehe retentionengine.ComputeDueDate), +// damit eine 1:1 aus einem Altsystem übernommene Frist unverändert bleibt +// (TAR-01). +func SetRetainUntil(ctx context.Context, pool *pgxpool.Pool, retentionObjectID string, retainUntil time.Time) error { + tag, err := pool.Exec(ctx, ` + UPDATE retention_objects SET retain_until = $2 WHERE id = $1 + `, retentionObjectID, retainUntil) + if err != nil { + return fmt.Errorf("retention: retain_until setzen: %w", err) + } + if tag.RowsAffected() == 0 { + return fmt.Errorf("retention: unbekanntes retention-objekt %q", retentionObjectID) + } + return nil +} + +// RetainUntil liefert die gesetzte Frist, ok=false falls keine gesetzt ist. +func RetainUntil(ctx context.Context, pool *pgxpool.Pool, retentionObjectID string) (retainUntil time.Time, ok bool, err error) { + var ru *time.Time + err = pool.QueryRow(ctx, ` + SELECT retain_until FROM retention_objects WHERE id = $1 + `, retentionObjectID).Scan(&ru) + if err != nil { + return time.Time{}, false, fmt.Errorf("retention: retain_until lesen: %w", err) + } + if ru == nil { + return time.Time{}, false, nil + } + return *ru, true, nil +} diff --git a/archive/internal/retentionmigration/retentionmigration.go b/archive/internal/retentionmigration/retentionmigration.go new file mode 100644 index 00000000..47f9999c --- /dev/null +++ b/archive/internal/retentionmigration/retentionmigration.go @@ -0,0 +1,79 @@ +// Package retentionmigration implementiert TAR-01: Übernahme von +// retain_until-Fristen aus den Altsystemen (bereits migrierte DOK-01- +// Dokumente und MAI-02-Nachrichten) in das generische Retention-Modell +// (archive/internal/retention, RET-01) — exakt 1:1, ohne Neuberechnung. +package retentionmigration + +import ( + "context" + "fmt" + "time" + + "github.com/jackc/pgx/v5/pgxpool" + + "gitea.perlbach24.de/scripte/nexarch/archive/internal/retention" +) + +// SourceObject ist EIN bereits migriertes Kernobjekt (Dokument oder +// Nachricht) mit optionaler Frist aus der Quelle. RetainUntil == nil +// bedeutet: Objekt hatte in der Quelle keine Frist. +type SourceObject struct { + ObjectType string + ObjectReference string + RetainUntil *time.Time +} + +// Source liefert die bereits migrierten Kernobjekte je Altsystem-Adapter +// (DOK-01/MAI-02 laufen vor diesem Schritt und liefern die ObjectReference). +type Source interface { + Objects(ctx context.Context) ([]SourceObject, error) +} + +type Migrator struct { + pool *pgxpool.Pool + source Source +} + +func NewMigrator(pool *pgxpool.Pool, source Source) *Migrator { + return &Migrator{pool: pool, source: source} +} + +// Result fasst den Ausgang eines Migrationslaufs zusammen. RanAt ist +// reine Protokoll-Metadaten (wann der Lauf stattfand) und fließt an +// keiner Stelle in die übernommenen retain_until-Werte ein. +type Result struct { + ObjectsRegistered int + RetentionDatesSet int + RanAt time.Time +} + +// MigrateAll übernimmt retain_until 1:1 je Objekt (Akzeptanzkriterium 1), +// setzt keine Frist, wo die Quelle keine hatte (Akzeptanzkriterium 3), und +// berechnet an keiner Stelle aus now eine Frist (Akzeptanzkriterium 2) — +// now dient ausschließlich Result.RanAt. +func (m *Migrator) MigrateAll(ctx context.Context, now time.Time) (Result, error) { + res := Result{RanAt: now} + + objs, err := m.source.Objects(ctx) + if err != nil { + return res, fmt.Errorf("retentionmigration: objekte aus quelle lesen: %w", err) + } + + for _, obj := range objs { + id, err := retention.RegisterObject(ctx, m.pool, obj.ObjectType, obj.ObjectReference) + if err != nil { + return res, fmt.Errorf("retentionmigration: objekt %s/%s registrieren: %w", obj.ObjectType, obj.ObjectReference, err) + } + res.ObjectsRegistered++ + + if obj.RetainUntil == nil { + continue + } + if err := retention.SetRetainUntil(ctx, m.pool, id, *obj.RetainUntil); err != nil { + return res, fmt.Errorf("retentionmigration: frist fuer %s/%s uebernehmen: %w", obj.ObjectType, obj.ObjectReference, err) + } + res.RetentionDatesSet++ + } + + return res, nil +} diff --git a/archive/internal/retentionmigration/retentionmigration_test.go b/archive/internal/retentionmigration/retentionmigration_test.go new file mode 100644 index 00000000..a31788e0 --- /dev/null +++ b/archive/internal/retentionmigration/retentionmigration_test.go @@ -0,0 +1,186 @@ +package retentionmigration + +import ( + "context" + "fmt" + "os" + "testing" + "time" + + "github.com/jackc/pgx/v5/pgxpool" + + "gitea.perlbach24.de/scripte/nexarch/archive/internal/retention" +) + +const schemaSQL = ` + CREATE EXTENSION IF NOT EXISTS pgcrypto; + CREATE TABLE IF NOT EXISTS retention_objects ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), object_type TEXT NOT NULL, + object_reference TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'expired', 'deleted')), + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + retain_until TIMESTAMPTZ, + UNIQUE (object_type, object_reference) + ); +` + +func requireTestPool(t *testing.T) *pgxpool.Pool { + t.Helper() + dsn := os.Getenv("TEST_TENANT_DSN") + if dsn == "" { + t.Skip("TEST_TENANT_DSN nicht gesetzt, Integrationstest uebersprungen") + } + ctx := context.Background() + pool, err := pgxpool.New(ctx, dsn) + if err != nil { + t.Fatalf("pool: %v", err) + } + t.Cleanup(func() { pool.Close() }) + + if _, err := pool.Exec(ctx, schemaSQL); err != nil { + t.Fatalf("schema: %v", err) + } + if _, err := pool.Exec(ctx, `ALTER TABLE retention_objects ADD COLUMN IF NOT EXISTS retain_until TIMESTAMPTZ`); err != nil { + t.Fatalf("schema (retain_until nachruesten): %v", err) + } + t.Cleanup(func() { + _, _ = pool.Exec(context.Background(), `TRUNCATE retention_objects CASCADE`) + }) + return pool +} + +// fixtureSource ist eine SYNTHETISCHE In-Memory-Quelle: simuliert bereits +// migrierte DOK-01/MAI-02-Objekte, keine echten Altsystemdaten. +type fixtureSource struct { + objects []SourceObject +} + +func (s *fixtureSource) Objects(ctx context.Context) ([]SourceObject, error) { + return s.objects, nil +} + +func mustRetainUntil(t *testing.T, pool *pgxpool.Pool, retentionObjectID string) (time.Time, bool) { + t.Helper() + ru, ok, err := retention.RetainUntil(context.Background(), pool, retentionObjectID) + if err != nil { + t.Fatalf("retain_until lesen: %v", err) + } + return ru, ok +} + +func objectID(t *testing.T, pool *pgxpool.Pool, objectType, objectReference string) string { + t.Helper() + var id string + err := pool.QueryRow(context.Background(), ` + SELECT id FROM retention_objects WHERE object_type = $1 AND object_reference = $2 + `, objectType, objectReference).Scan(&id) + if err != nil { + t.Fatalf("objekt-id lesen: %v", err) + } + return id +} + +// TestMigrateAll_PreservesFutureRetainUntilExactly ist Pruefung 1 +// (Akzeptanzkriterium 1): Stichprobe von 50 synthetischen Objekten mit +// Frist in der Zukunft, Frist vor/nach Migration exakt identisch. +func TestMigrateAll_PreservesFutureRetainUntilExactly(t *testing.T) { + pool := requireTestPool(t) + + const sampleSize = 50 + var objs []SourceObject + want := map[string]time.Time{} + for i := 0; i < sampleSize; i++ { + ref := fmt.Sprintf("doc-%03d", i) + due := time.Date(2031, time.January, 1, 0, 0, 0, 0, time.UTC). + AddDate(0, 0, i). + Add(time.Duration(i) * time.Hour) + objs = append(objs, SourceObject{ObjectType: "dms_document", ObjectReference: ref, RetainUntil: &due}) + want[ref] = due + } + + m := NewMigrator(pool, &fixtureSource{objects: objs}) + res, err := m.MigrateAll(context.Background(), time.Now()) + if err != nil { + t.Fatalf("migrateall: %v", err) + } + if res.ObjectsRegistered != sampleSize || res.RetentionDatesSet != sampleSize { + t.Fatalf("result = %+v, want %d/%d", res, sampleSize, sampleSize) + } + + for ref, due := range want { + id := objectID(t, pool, "dms_document", ref) + got, ok := mustRetainUntil(t, pool, id) + if !ok { + t.Fatalf("objekt %q: erwartet gesetzte frist, habe keine", ref) + } + if !got.Equal(due) { + t.Fatalf("objekt %q: frist nach migration = %v, quelle = %v", ref, got, due) + } + } +} + +// TestMigrateAll_ObjectsWithoutSourceDeadlineStayWithoutOne ist Pruefung +// fuer Akzeptanzkriterium 3: Objekte ohne Frist in der Quelle bleiben +// nach der Migration ohne Frist. +func TestMigrateAll_ObjectsWithoutSourceDeadlineStayWithoutOne(t *testing.T) { + pool := requireTestPool(t) + + m := NewMigrator(pool, &fixtureSource{objects: []SourceObject{ + {ObjectType: "mail_message", ObjectReference: "msg-ohne-frist", RetainUntil: nil}, + }}) + res, err := m.MigrateAll(context.Background(), time.Now()) + if err != nil { + t.Fatalf("migrateall: %v", err) + } + if res.ObjectsRegistered != 1 || res.RetentionDatesSet != 0 { + t.Fatalf("result = %+v, want 1 objekt registriert, 0 fristen gesetzt", res) + } + + id := objectID(t, pool, "mail_message", "msg-ohne-frist") + if _, ok := mustRetainUntil(t, pool, id); ok { + t.Fatalf("objekt ohne quellfrist hat nach migration eine frist") + } +} + +// TestMigrateAll_ResultIndependentOfInjectedNow ist Pruefung 2 +// (Akzeptanzkriterium 2): zwei Migrationslaeufe mit weit auseinander +// liegenden "jetzt"-Zeitpunkten liefern IDENTISCHE retain_until-Werte - +// echter Nachweis statt Behauptung, dass now() an keiner Stelle in die +// berechnete Frist einfliesst. +func TestMigrateAll_ResultIndependentOfInjectedNow(t *testing.T) { + poolA := requireTestPool(t) + poolB := requireTestPool(t) + + due := time.Date(2040, time.June, 15, 12, 0, 0, 0, time.UTC) + source := func() *fixtureSource { + return &fixtureSource{objects: []SourceObject{ + {ObjectType: "dms_document", ObjectReference: "doc-clock-test", RetainUntil: &due}, + }} + } + + nowA := time.Date(2026, time.October, 14, 0, 0, 0, 0, time.UTC) + nowB := time.Date(2099, time.December, 31, 23, 59, 59, 0, time.UTC) + + mA := NewMigrator(poolA, source()) + if _, err := mA.MigrateAll(context.Background(), nowA); err != nil { + t.Fatalf("migrateall (now=A): %v", err) + } + mB := NewMigrator(poolB, source()) + if _, err := mB.MigrateAll(context.Background(), nowB); err != nil { + t.Fatalf("migrateall (now=B): %v", err) + } + + idA := objectID(t, poolA, "dms_document", "doc-clock-test") + idB := objectID(t, poolB, "dms_document", "doc-clock-test") + gotA, okA := mustRetainUntil(t, poolA, idA) + gotB, okB := mustRetainUntil(t, poolB, idB) + if !okA || !okB { + t.Fatalf("erwartet beide laeufe mit gesetzter frist, okA=%v okB=%v", okA, okB) + } + if !gotA.Equal(due) || !gotB.Equal(due) { + t.Fatalf("frist weicht von quelle ab: A=%v B=%v quelle=%v", gotA, gotB, due) + } + if !gotA.Equal(gotB) { + t.Fatalf("lauf mit now=%v lieferte andere frist als lauf mit now=%v: %v != %v", nowA, nowB, gotA, gotB) + } +} diff --git a/archive/migrations/0017_retention_objects_retain_until.down.sql b/archive/migrations/0017_retention_objects_retain_until.down.sql new file mode 100644 index 00000000..ecd4d8ce --- /dev/null +++ b/archive/migrations/0017_retention_objects_retain_until.down.sql @@ -0,0 +1 @@ +ALTER TABLE retention_objects DROP COLUMN IF EXISTS retain_until; diff --git a/archive/migrations/0017_retention_objects_retain_until.up.sql b/archive/migrations/0017_retention_objects_retain_until.up.sql new file mode 100644 index 00000000..705cf8c9 --- /dev/null +++ b/archive/migrations/0017_retention_objects_retain_until.up.sql @@ -0,0 +1 @@ +ALTER TABLE retention_objects ADD COLUMN IF NOT EXISTS retain_until TIMESTAMPTZ; -- 2.47.3 From bc60e11b4c4a1cddc9707f8683d14eddbc254d44 Mon Sep 17 00:00:00 2001 From: patrick Date: Wed, 14 Oct 2026 15:23:38 +0000 Subject: [PATCH 2/2] TAR-01: now-unabhaengigkeits-test auf echte getrennte Datensaetze umgestellt Co-Authored-By: Claude Sonnet 5 --- archive/docs/TAR-01-PRUEFPROTOKOLL.md | 6 +-- .../retentionmigration_test.go | 39 ++++++++++--------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/archive/docs/TAR-01-PRUEFPROTOKOLL.md b/archive/docs/TAR-01-PRUEFPROTOKOLL.md index 2f164c4c..86a830e8 100644 --- a/archive/docs/TAR-01-PRUEFPROTOKOLL.md +++ b/archive/docs/TAR-01-PRUEFPROTOKOLL.md @@ -15,9 +15,9 @@ Ergebnis: PASS. Alle 50 Fristen nach Migration bitgenau identisch zur Quelle (`t Test: `TestMigrateAll_ResultIndependentOfInjectedNow`. -Zwei vollständig unabhängige Migrationsläufe gegen zwei getrennte Zieldatenbanken, mit demselben Quellobjekt (`retain_until` = 2040-06-15), aber mit injiziertem `now`-Parameter, der künstlich um 73 Jahre auseinanderliegt (2026-10-14 vs. 2099-12-31). `now` fließt im Migrator ausschließlich in `Result.RanAt` (Protokoll-Metadatum) ein, nie in die Frist-Übernahme (`retention.SetRetainUntil` nimmt die Quellfrist unverändert). +Zwei Migrationsläufe gegen dieselbe Zieldatenbank, aber auf zwei voneinander unabhängigen Quellobjekten (`object_reference` `doc-clock-test-a` bzw. `doc-clock-test-b`, dieselbe Quellfrist `retain_until` = 2030-06-15), mit injiziertem `now`-Parameter, der künstlich um 73 Jahre auseinanderliegt (2026-01-01 vs. 2099-01-01). `now` fließt im Migrator ausschließlich in `Result.RanAt` (Protokoll-Metadatum) ein, nie in die Frist-Übernahme (`retention.SetRetainUntil` nimmt die Quellfrist unverändert). -Ergebnis: PASS. Beide Läufe liefern exakt dieselbe `retain_until` in der Zieldatenbank, unabhängig vom injizierten `now`. +Ergebnis: PASS. Beide (unabhängigen) Datensätze erhalten exakt dieselbe `retain_until`, unabhängig vom injizierten `now`. Gegenprobe durchgeführt: wird `now` testweise künstlich in die Fristberechnung eingebaut, schlägt der Test fehl (Vergleich `gotA.Equal(gotB)`), was belegt, dass der Test tatsächlich scharf ist; der Eingriff wurde danach wieder zurückgenommen und nicht committet. ## Prüfung 3 – Objekte ohne Quellfrist bleiben ohne Frist @@ -36,7 +36,7 @@ Migration `archive/migrations/0017_retention_objects_retain_until.{up,down}.sql` | Prüfung | Ergebnis | |---|---| | 1 – 50 Stichproben, Frist exakt identisch | PASS | -| 2 – keine Berechnung aus aktuellem Datum (zwei Läufe, unterschiedliches `now`) | PASS | +| 2 – keine Berechnung aus aktuellem Datum (zwei unabhängige Datensätze, unterschiedliches `now`) | PASS | | 3 – keine Frist ohne Quellfrist | PASS | | Bestehende `archive/internal/retention`-Tests weiterhin grün | PASS (3 PASS, 1 SKIP mangels zweiter Tenant-DSN, unverändert zum Vorzustand) | | `go vet ./...` (archive-Modul) | clean | diff --git a/archive/internal/retentionmigration/retentionmigration_test.go b/archive/internal/retentionmigration/retentionmigration_test.go index a31788e0..63d0d4f6 100644 --- a/archive/internal/retentionmigration/retentionmigration_test.go +++ b/archive/internal/retentionmigration/retentionmigration_test.go @@ -144,36 +144,37 @@ func TestMigrateAll_ObjectsWithoutSourceDeadlineStayWithoutOne(t *testing.T) { // TestMigrateAll_ResultIndependentOfInjectedNow ist Pruefung 2 // (Akzeptanzkriterium 2): zwei Migrationslaeufe mit weit auseinander -// liegenden "jetzt"-Zeitpunkten liefern IDENTISCHE retain_until-Werte - -// echter Nachweis statt Behauptung, dass now() an keiner Stelle in die -// berechnete Frist einfliesst. +// liegenden "jetzt"-Zeitpunkten, auf zwei voneinander unabhaengigen +// Datensaetzen (unterschiedliche ObjectReference, gleiche Quellfrist), +// liefern IDENTISCHE retain_until-Werte - echter Nachweis statt +// Behauptung, dass now() an keiner Stelle in die berechnete Frist +// einfliesst. Wuerde now faelschlich einfliessen, muessten sich die +// beiden Ergebnisse unterscheiden (naeher am jeweils injizierten now). func TestMigrateAll_ResultIndependentOfInjectedNow(t *testing.T) { - poolA := requireTestPool(t) - poolB := requireTestPool(t) + pool := requireTestPool(t) - due := time.Date(2040, time.June, 15, 12, 0, 0, 0, time.UTC) - source := func() *fixtureSource { - return &fixtureSource{objects: []SourceObject{ - {ObjectType: "dms_document", ObjectReference: "doc-clock-test", RetainUntil: &due}, - }} - } + due := time.Date(2030, time.June, 15, 12, 0, 0, 0, time.UTC) - nowA := time.Date(2026, time.October, 14, 0, 0, 0, 0, time.UTC) - nowB := time.Date(2099, time.December, 31, 23, 59, 59, 0, time.UTC) + nowA := time.Date(2026, time.January, 1, 0, 0, 0, 0, time.UTC) + nowB := time.Date(2099, time.January, 1, 0, 0, 0, 0, time.UTC) - mA := NewMigrator(poolA, source()) + mA := NewMigrator(pool, &fixtureSource{objects: []SourceObject{ + {ObjectType: "dms_document", ObjectReference: "doc-clock-test-a", RetainUntil: &due}, + }}) if _, err := mA.MigrateAll(context.Background(), nowA); err != nil { t.Fatalf("migrateall (now=A): %v", err) } - mB := NewMigrator(poolB, source()) + mB := NewMigrator(pool, &fixtureSource{objects: []SourceObject{ + {ObjectType: "dms_document", ObjectReference: "doc-clock-test-b", RetainUntil: &due}, + }}) if _, err := mB.MigrateAll(context.Background(), nowB); err != nil { t.Fatalf("migrateall (now=B): %v", err) } - idA := objectID(t, poolA, "dms_document", "doc-clock-test") - idB := objectID(t, poolB, "dms_document", "doc-clock-test") - gotA, okA := mustRetainUntil(t, poolA, idA) - gotB, okB := mustRetainUntil(t, poolB, idB) + idA := objectID(t, pool, "dms_document", "doc-clock-test-a") + idB := objectID(t, pool, "dms_document", "doc-clock-test-b") + gotA, okA := mustRetainUntil(t, pool, idA) + gotB, okB := mustRetainUntil(t, pool, idB) if !okA || !okB { t.Fatalf("erwartet beide laeufe mit gesetzter frist, okA=%v okB=%v", okA, okB) } -- 2.47.3