From ba4a0e3b439b0f5a8ff2154aa1f491099364ca2f Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Fri, 3 Jul 2026 13:11:55 +1000 Subject: [PATCH] test: gc sweep integration (delete old orphan, empty no-op) --- internal/gc/gc_integration_test.go | 96 ++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 internal/gc/gc_integration_test.go diff --git a/internal/gc/gc_integration_test.go b/internal/gc/gc_integration_test.go new file mode 100644 index 0000000..991facc --- /dev/null +++ b/internal/gc/gc_integration_test.go @@ -0,0 +1,96 @@ +package gc + +import ( + "bytes" + "context" + "os" + "testing" + "time" + + "git.unkin.net/unkin/artifactapi/internal/database" + "git.unkin.net/unkin/artifactapi/internal/storage" + "git.unkin.net/unkin/artifactapi/internal/testsupport" +) + +var ( + testDB *database.DB + testStore *storage.S3 +) + +func TestMain(m *testing.M) { + ctx := context.Background() + dsn, termPG, err := testsupport.StartPostgres(ctx) + if err != nil { + os.Exit(m.Run()) + } + minio, termMinio, err := testsupport.StartMinio(ctx) + if err != nil { + termPG() + os.Exit(m.Run()) + } + db, err := database.New(dsn) + if err != nil { + panic(err) + } + var s3 *storage.S3 + for i := 0; i < 20; i++ { + if s3, err = storage.NewS3(minio.Endpoint, minio.AccessKey, minio.SecretKey, "gc-test", false, ""); err == nil { + break + } + time.Sleep(500 * time.Millisecond) + } + if err != nil { + panic(err) + } + testDB = db + testStore = s3 + + code := m.Run() + db.Close() + termMinio() + termPG() + if code != 0 { + os.Exit(code) + } +} + +func TestSweepDeletesOldOrphan(t *testing.T) { + if testDB == nil { + t.Skip("Docker unavailable") + } + ctx := context.Background() + hash := "sha256:gcorphan" + key := storage.BlobKey("gcorphan") + + if err := testStore.Upload(ctx, key, bytes.NewReader([]byte("orphan")), 6, "application/octet-stream"); err != nil { + t.Fatal(err) + } + if err := testDB.UpsertBlob(ctx, hash, key, 6, "application/octet-stream"); err != nil { + t.Fatal(err) + } + // Age the blob past the grace period. + if _, err := testDB.Pool.Exec(ctx, `UPDATE blobs SET created_at = now() - interval '2 hours' WHERE content_hash = $1`, hash); err != nil { + t.Fatal(err) + } + + c := New(testDB, testStore, time.Hour) + c.sweep(ctx) + + if exists, _ := testStore.Exists(ctx, key); exists { + t.Error("expected orphan object deleted from store") + } + orphans, _ := testDB.FindOrphanedBlobs(ctx, 0) + for _, b := range orphans { + if b.ContentHash == hash { + t.Error("expected orphan blob row deleted") + } + } +} + +func TestSweepNoOrphans(t *testing.T) { + if testDB == nil { + t.Skip("Docker unavailable") + } + // A sweep with nothing to collect should be a clean no-op. + New(testDB, testStore, time.Hour).sweep(context.Background()) +}