package reconcile import ( "context" "os" "path/filepath" "testing" "github.com/jackc/pgx/v5/pgxpool" ) 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() }) // Minimalschema, das exakt DMS FDN-02s file_revisions-Spalten spiegelt // (Archive kann DMS' internal/-Pakete als eigenes Go-Modul nicht // importieren, daher hier als Testfixture kopiert statt real migriert). if _, err := pool.Exec(ctx, ` CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email TEXT NOT NULL UNIQUE, name TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS documents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title TEXT NOT NULL, created_by UUID NOT NULL REFERENCES users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS file_revisions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE, storage_key TEXT NOT NULL, checksum_sha256 TEXT NOT NULL, size_bytes BIGINT NOT NULL, mime_type TEXT NOT NULL, revision_number INTEGER NOT NULL, created_by UUID NOT NULL REFERENCES users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); `); err != nil { t.Fatalf("schema: %v", err) } t.Cleanup(func() { _, _ = pool.Exec(context.Background(), `TRUNCATE file_revisions, documents, users CASCADE`) }) return pool } // TestListDBStorageKeys_ReadsRealFileRevisions ist der Nachweis, dass // ListDBStorageKeys tatsaechlich gegen eine echte Postgres-Instanz mit // DMS-FDN-02-Schema liest — kein Mock. func TestListDBStorageKeys_ReadsRealFileRevisions(t *testing.T) { pool := requireTestPool(t) ctx := context.Background() var userID, docID string if err := pool.QueryRow(ctx, `INSERT INTO users (email, name) VALUES ('reconcile-test@example.test', 'Test') RETURNING id`).Scan(&userID); err != nil { t.Fatalf("testbenutzer anlegen: %v", err) } if err := pool.QueryRow(ctx, `INSERT INTO documents (title, created_by) VALUES ('doc', $1) RETURNING id`, userID).Scan(&docID); err != nil { t.Fatalf("testdokument anlegen: %v", err) } if _, err := pool.Exec(ctx, ` INSERT INTO file_revisions (document_id, storage_key, checksum_sha256, size_bytes, mime_type, revision_number, created_by) VALUES ($1, 'documents/x/revisions/1', 'abc', 10, 'text/plain', 1, $2) `, docID, userID); err != nil { t.Fatalf("testrevision anlegen: %v", err) } entries, err := ListDBStorageKeys(ctx, pool) if err != nil { t.Fatalf("listdbstoragekeys: %v", err) } if len(entries) != 1 { t.Fatalf("erwartet 1 eintrag, habe %d", len(entries)) } if entries[0].StorageKey != "documents/x/revisions/1" { t.Fatalf("storage_key = %q, want %q", entries[0].StorageKey, "documents/x/revisions/1") } if entries[0].DocumentID != docID { t.Fatalf("document_id = %q, want %q", entries[0].DocumentID, docID) } } // TestListStorageObjects_WalksRealDirectory ist der Nachweis, dass // ListStorageObjects tatsaechlich das Dateisystem durchlaeuft. func TestListStorageObjects_WalksRealDirectory(t *testing.T) { baseDir := t.TempDir() mustWriteFile(t, filepath.Join(baseDir, "documents", "d1", "revisions", "r1"), "inhalt") mustWriteFile(t, filepath.Join(baseDir, "documents", "d2", "revisions", "r1"), "inhalt") keys, err := ListStorageObjects(baseDir) if err != nil { t.Fatalf("liststorageobjects: %v", err) } if len(keys) != 2 { t.Fatalf("erwartet 2 objektschluessel, habe %d: %v", len(keys), keys) } want := []string{"documents/d1/revisions/r1", "documents/d2/revisions/r1"} for i, w := range want { if keys[i] != w { t.Fatalf("schluessel[%d] = %q, want %q (voll: %v)", i, keys[i], w, keys) } } } // TestListStorageObjects_MissingDirectoryReturnsEmpty prueft das // Verhalten, wenn das Basisverzeichnis (noch) gar nicht existiert - // sollte als "keine Objekte", nicht als Fehler behandelt werden. func TestListStorageObjects_MissingDirectoryReturnsEmpty(t *testing.T) { keys, err := ListStorageObjects("/pfad/der/nicht/existiert/fuer/diesen/test") if err != nil { t.Fatalf("erwartet keinen fehler bei fehlendem verzeichnis, habe: %v", err) } if len(keys) != 0 { t.Fatalf("erwartet 0 schluessel, habe %d", len(keys)) } } func mustWriteFile(t *testing.T, path, content string) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatalf("verzeichnis anlegen: %v", err) } if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatalf("datei schreiben: %v", err) } }