// Smoke-Test des S3-Objektspeichers gegen einen echten Garage-Node (Phase C/D der // MinIO→Garage-Migration, docs/KONZEPT-garage-migration.md §9). // // Prüft den realen S3-Pfad (AWS SDK v3, path-style) über die exportierte // `resolveBackupStore()`: // 1. Put → Get (Byte-Identität), // 2. List (Prefix enthält den Key), // 3. Delete (remove räumt den Prefix), // 4. Negativfall: fehlender Bucket → sprechender Konfigurationsfehler // (KEIN stiller CreateBucket-Versuch mehr). // // Ausführung: // - OHNE S3_ENDPOINT → der Test ÜBERSPRINGT sich (Exit 0), damit das Standard-Gate // (npm run test) ohne laufenden Objektspeicher grün bleibt. // - MIT gesetzten S3_*-Variablen (z. B. gegen den Compose-Garage oder einen lokalen // Container) → echter End-to-End-Durchstich. // // S3_ENDPOINT=http://127.0.0.1:3900 S3_ACCESS_KEY=… S3_SECRET_KEY=… \ // S3_BUCKET=isms-documents S3_REGION=us-east-1 npx tsx scripts/test-garage-storage.ts import { resolveBackupStore, type BackupTargetConfig } from "../src/server/storage/backup-store"; let failures = 0; const ok = (cond: boolean, msg: string) => { console.log(`${cond ? "✓" : "✗ FEHLER"} ${msg}`); if (!cond) failures++; }; function envCfg(bucketOverride?: string): BackupTargetConfig { return { backupTarget: "s3", backupLocalDir: null, backupS3Endpoint: process.env.S3_ENDPOINT?.trim() ?? null, backupS3Bucket: bucketOverride ?? process.env.S3_BUCKET?.trim() ?? "isms-documents", backupS3Region: process.env.S3_REGION?.trim() || "us-east-1", backupS3AccessKey: process.env.S3_ACCESS_KEY?.trim() ?? null, backupS3SecretKey: process.env.S3_SECRET_KEY?.trim() ?? null, }; } async function main() { const endpoint = process.env.S3_ENDPOINT?.trim(); if (!endpoint) { console.log("↷ übersprungen: kein S3_ENDPOINT gesetzt (kein laufender Objektspeicher)."); console.log(" Für den echten Durchstich S3_ENDPOINT/S3_ACCESS_KEY/S3_SECRET_KEY/S3_BUCKET setzen."); return; } if (!process.env.S3_ACCESS_KEY || !process.env.S3_SECRET_KEY) { console.log("✗ FEHLER S3_ENDPOINT gesetzt, aber S3_ACCESS_KEY/S3_SECRET_KEY fehlen."); failures++; return; } // ── Positiv: Put → Get → List → Delete gegen den provisionierten Bucket ────── const store = resolveBackupStore(envCfg()); const prefix = `__smoketest__/${process.pid}-${Date.now()}/`; const key = `${prefix}garage-probe.bin`; const payload = Buffer.from("certvia · garage smoke test · äöü · 🗄️", "utf8"); await store.put(key, payload); ok(true, "Put gegen Garage erfolgreich"); const got = await store.get(key); ok(got != null && got.equals(payload), "Get liefert identische Bytes zurück"); const missing = await store.get(`${prefix}gibtsnicht.bin`); ok(missing === null, "Get eines fehlenden Keys → null (kein Wurf)"); const listed = await store.list(prefix); ok(listed.includes(key), "List enthält den geschriebenen Key (Prefix-Scan)"); const removed = await store.remove(prefix); ok(removed >= 1, `Delete räumt den Prefix (entfernt: ${removed})`); const afterRemove = await store.list(prefix); ok(afterRemove.length === 0, "Prefix ist nach remove leer"); // ── Negativ: fehlender Bucket → sprechender Konfigurationsfehler ───────────── const badBucket = `does-not-exist-${process.pid}-${Date.now()}`; const badStore = resolveBackupStore(envCfg(badBucket)); try { await badStore.put(`${badBucket}/x.bin`, Buffer.from("x")); ok(false, "fehlender Bucket hätte einen Fehler werfen müssen"); } catch (err) { const msg = err instanceof Error ? err.message : String(err); const speaks = /nicht provisioniert|nicht erreichbar/.test(msg) && msg.includes(badBucket); ok(speaks, "fehlender Bucket → sprechender Konfigurationsfehler (kein stiller CreateBucket)"); if (!speaks) console.log(" erhaltene Meldung:", msg); } } main() .then(() => { console.log(failures === 0 ? "\nGarage-Storage-Smoke-Test grün." : `\n${failures} Prüfung(en) fehlgeschlagen.`); process.exit(failures === 0 ? 0 : 1); }) .catch((err) => { console.error("\n✗ Unerwarteter Fehler im Smoke-Test:", err); process.exit(1); });