package db import ( "errors" "testing" ) func TestDeleteManifest(t *testing.T) { d := openTestDB(t) if err := d.Migrate(); err != nil { t.Fatalf("Migrate: %v", err) } // Set up repo, manifest, tag, blob, manifest_blob. _, err := d.Exec(`INSERT INTO repositories (name) VALUES ('testrepo')`) if err != nil { t.Fatalf("insert repo: %v", err) } _, err = d.Exec(`INSERT INTO manifests (repository_id, digest, media_type, content, size) VALUES (1, 'sha256:m1', 'application/vnd.oci.image.manifest.v1+json', '{}', 2)`) if err != nil { t.Fatalf("insert manifest: %v", err) } _, err = d.Exec(`INSERT INTO tags (repository_id, name, manifest_id) VALUES (1, 'latest', 1)`) if err != nil { t.Fatalf("insert tag: %v", err) } _, err = d.Exec(`INSERT INTO blobs (digest, size) VALUES ('sha256:b1', 100)`) if err != nil { t.Fatalf("insert blob: %v", err) } _, err = d.Exec(`INSERT INTO manifest_blobs (manifest_id, blob_id) VALUES (1, 1)`) if err != nil { t.Fatalf("insert manifest_blob: %v", err) } // Delete the manifest. if err := d.DeleteManifest(1, "sha256:m1"); err != nil { t.Fatalf("DeleteManifest: %v", err) } // Verify manifest is gone. _, err = d.GetManifestByDigest(1, "sha256:m1") if !errors.Is(err, ErrManifestNotFound) { t.Fatalf("expected ErrManifestNotFound, got %v", err) } // Verify tag was cascaded. var tagCount int if err := d.QueryRow(`SELECT COUNT(*) FROM tags`).Scan(&tagCount); err != nil { t.Fatalf("count tags: %v", err) } if tagCount != 0 { t.Fatalf("tag count: got %d, want 0", tagCount) } // Verify manifest_blobs was cascaded. var mbCount int if err := d.QueryRow(`SELECT COUNT(*) FROM manifest_blobs`).Scan(&mbCount); err != nil { t.Fatalf("count manifest_blobs: %v", err) } if mbCount != 0 { t.Fatalf("manifest_blobs count: got %d, want 0", mbCount) } // Verify blob row still exists (GC handles file cleanup). var blobCount int if err := d.QueryRow(`SELECT COUNT(*) FROM blobs`).Scan(&blobCount); err != nil { t.Fatalf("count blobs: %v", err) } if blobCount != 1 { t.Fatalf("blob count: got %d, want 1 (should not be deleted)", blobCount) } } func TestDeleteManifestNotFound(t *testing.T) { d := openTestDB(t) if err := d.Migrate(); err != nil { t.Fatalf("Migrate: %v", err) } _, err := d.Exec(`INSERT INTO repositories (name) VALUES ('testrepo')`) if err != nil { t.Fatalf("insert repo: %v", err) } err = d.DeleteManifest(1, "sha256:nonexistent") if !errors.Is(err, ErrManifestNotFound) { t.Fatalf("expected ErrManifestNotFound, got %v", err) } } func TestDeleteBlobFromRepo(t *testing.T) { d := openTestDB(t) if err := d.Migrate(); err != nil { t.Fatalf("Migrate: %v", err) } // Set up: repo, manifest, blob, manifest_blob. _, err := d.Exec(`INSERT INTO repositories (name) VALUES ('testrepo')`) if err != nil { t.Fatalf("insert repo: %v", err) } _, err = d.Exec(`INSERT INTO manifests (repository_id, digest, media_type, content, size) VALUES (1, 'sha256:m1', 'application/vnd.oci.image.manifest.v1+json', '{}', 2)`) if err != nil { t.Fatalf("insert manifest: %v", err) } _, err = d.Exec(`INSERT INTO blobs (digest, size) VALUES ('sha256:b1', 100)`) if err != nil { t.Fatalf("insert blob: %v", err) } _, err = d.Exec(`INSERT INTO manifest_blobs (manifest_id, blob_id) VALUES (1, 1)`) if err != nil { t.Fatalf("insert manifest_blob: %v", err) } // Delete blob from repo. if err := d.DeleteBlobFromRepo(1, "sha256:b1"); err != nil { t.Fatalf("DeleteBlobFromRepo: %v", err) } // Verify manifest_blobs is gone. var mbCount int if err := d.QueryRow(`SELECT COUNT(*) FROM manifest_blobs`).Scan(&mbCount); err != nil { t.Fatalf("count manifest_blobs: %v", err) } if mbCount != 0 { t.Fatalf("manifest_blobs count: got %d, want 0", mbCount) } // Verify blob row still exists (GC responsibility). var blobCount int if err := d.QueryRow(`SELECT COUNT(*) FROM blobs`).Scan(&blobCount); err != nil { t.Fatalf("count blobs: %v", err) } if blobCount != 1 { t.Fatalf("blob count: got %d, want 1", blobCount) } // Verify manifest still exists. var mCount int if err := d.QueryRow(`SELECT COUNT(*) FROM manifests`).Scan(&mCount); err != nil { t.Fatalf("count manifests: %v", err) } if mCount != 1 { t.Fatalf("manifest count: got %d, want 1", mCount) } } func TestDeleteBlobFromRepoNotFound(t *testing.T) { d := openTestDB(t) if err := d.Migrate(); err != nil { t.Fatalf("Migrate: %v", err) } _, err := d.Exec(`INSERT INTO repositories (name) VALUES ('testrepo')`) if err != nil { t.Fatalf("insert repo: %v", err) } // Blob doesn't exist at all. err = d.DeleteBlobFromRepo(1, "sha256:nonexistent") if !errors.Is(err, ErrBlobNotFound) { t.Fatalf("expected ErrBlobNotFound, got %v", err) } } func TestDeleteBlobFromRepoExistsGloballyButNotInRepo(t *testing.T) { d := openTestDB(t) if err := d.Migrate(); err != nil { t.Fatalf("Migrate: %v", err) } // Two repos: blob only referenced in repo 2. _, err := d.Exec(`INSERT INTO repositories (name) VALUES ('repo1')`) if err != nil { t.Fatalf("insert repo1: %v", err) } _, err = d.Exec(`INSERT INTO repositories (name) VALUES ('repo2')`) if err != nil { t.Fatalf("insert repo2: %v", err) } _, err = d.Exec(`INSERT INTO manifests (repository_id, digest, media_type, content, size) VALUES (2, 'sha256:m2', 'application/vnd.oci.image.manifest.v1+json', '{}', 2)`) if err != nil { t.Fatalf("insert manifest: %v", err) } _, err = d.Exec(`INSERT INTO blobs (digest, size) VALUES ('sha256:shared', 500)`) if err != nil { t.Fatalf("insert blob: %v", err) } _, err = d.Exec(`INSERT INTO manifest_blobs (manifest_id, blob_id) VALUES (1, 1)`) if err != nil { t.Fatalf("insert manifest_blob: %v", err) } // Try to delete from repo1 — blob exists globally but not in repo1. err = d.DeleteBlobFromRepo(1, "sha256:shared") if !errors.Is(err, ErrBlobNotFound) { t.Fatalf("expected ErrBlobNotFound for blob not in repo1, got %v", err) } }