package oci import ( "encoding/json" "net/http" "net/http/httptest" "testing" ) func TestManifestDeleteByDigest(t *testing.T) { fdb := newFakeDB() fdb.addRepo("myrepo", 1) content := []byte(`{"schemaVersion":2}`) fdb.addManifest(1, "latest", "sha256:aaaa", "application/vnd.oci.image.manifest.v1+json", content) h := NewHandler(fdb, newFakeBlobs(), allowAll(), nil) router := testRouter(h) req := authedRequest("DELETE", "/v2/myrepo/manifests/sha256:aaaa", nil) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusAccepted { t.Fatalf("status: got %d, want %d", rr.Code, http.StatusAccepted) } // Verify manifest was deleted from fakeDB. _, err := fdb.GetManifestByDigest(1, "sha256:aaaa") if err == nil { t.Fatal("manifest should have been deleted") } } func TestManifestDeleteByTagUnsupported(t *testing.T) { fdb := newFakeDB() fdb.addRepo("myrepo", 1) content := []byte(`{"schemaVersion":2}`) fdb.addManifest(1, "latest", "sha256:aaaa", "application/vnd.oci.image.manifest.v1+json", content) h := NewHandler(fdb, newFakeBlobs(), allowAll(), nil) router := testRouter(h) req := authedRequest("DELETE", "/v2/myrepo/manifests/latest", nil) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusMethodNotAllowed { t.Fatalf("status: got %d, want %d", rr.Code, http.StatusMethodNotAllowed) } var body ociErrorResponse if err := json.NewDecoder(rr.Body).Decode(&body); err != nil { t.Fatalf("decode error: %v", err) } if len(body.Errors) != 1 || body.Errors[0].Code != "UNSUPPORTED" { t.Fatalf("error code: got %+v, want UNSUPPORTED", body.Errors) } } func TestManifestDeleteNotFound(t *testing.T) { fdb := newFakeDB() fdb.addRepo("myrepo", 1) // No manifests added. h := NewHandler(fdb, newFakeBlobs(), allowAll(), nil) router := testRouter(h) req := authedRequest("DELETE", "/v2/myrepo/manifests/sha256:nonexistent", nil) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusNotFound { t.Fatalf("status: got %d, want %d", rr.Code, http.StatusNotFound) } var body ociErrorResponse if err := json.NewDecoder(rr.Body).Decode(&body); err != nil { t.Fatalf("decode error: %v", err) } if len(body.Errors) != 1 || body.Errors[0].Code != "MANIFEST_UNKNOWN" { t.Fatalf("error code: got %+v, want MANIFEST_UNKNOWN", body.Errors) } } func TestManifestDeleteRepoNotFound(t *testing.T) { fdb := newFakeDB() h := NewHandler(fdb, newFakeBlobs(), allowAll(), nil) router := testRouter(h) req := authedRequest("DELETE", "/v2/nosuchrepo/manifests/sha256:aaaa", nil) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusNotFound { t.Fatalf("status: got %d, want %d", rr.Code, http.StatusNotFound) } var body ociErrorResponse if err := json.NewDecoder(rr.Body).Decode(&body); err != nil { t.Fatalf("decode error: %v", err) } if len(body.Errors) != 1 || body.Errors[0].Code != "NAME_UNKNOWN" { t.Fatalf("error code: got %+v, want NAME_UNKNOWN", body.Errors) } } func TestManifestDeleteCascadesTag(t *testing.T) { fdb := newFakeDB() fdb.addRepo("myrepo", 1) content := []byte(`{"schemaVersion":2}`) fdb.addManifest(1, "latest", "sha256:aaaa", "application/vnd.oci.image.manifest.v1+json", content) h := NewHandler(fdb, newFakeBlobs(), allowAll(), nil) router := testRouter(h) req := authedRequest("DELETE", "/v2/myrepo/manifests/sha256:aaaa", nil) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusAccepted { t.Fatalf("status: got %d, want %d", rr.Code, http.StatusAccepted) } // The tag "latest" should also be gone (cascading delete in fakeDB). _, err := fdb.GetManifestByTag(1, "latest") if err == nil { t.Fatal("tag 'latest' should have been cascaded on manifest delete") } } func TestBlobDelete(t *testing.T) { fdb := newFakeDB() fdb.addRepo("myrepo", 1) fdb.addBlob(1, "sha256:b1") h := NewHandler(fdb, newFakeBlobs(), allowAll(), nil) router := testRouter(h) req := authedRequest("DELETE", "/v2/myrepo/blobs/sha256:b1", nil) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusAccepted { t.Fatalf("status: got %d, want %d", rr.Code, http.StatusAccepted) } // Blob should be removed from repo in fakeDB. exists, _ := fdb.BlobExistsInRepo(1, "sha256:b1") if exists { t.Fatal("blob should have been removed from repo") } } func TestBlobDeleteNotInRepo(t *testing.T) { fdb := newFakeDB() fdb.addRepo("myrepo", 1) // Blob not added to this repo. h := NewHandler(fdb, newFakeBlobs(), allowAll(), nil) router := testRouter(h) req := authedRequest("DELETE", "/v2/myrepo/blobs/sha256:nonexistent", nil) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusNotFound { t.Fatalf("status: got %d, want %d", rr.Code, http.StatusNotFound) } var body ociErrorResponse if err := json.NewDecoder(rr.Body).Decode(&body); err != nil { t.Fatalf("decode error: %v", err) } if len(body.Errors) != 1 || body.Errors[0].Code != "BLOB_UNKNOWN" { t.Fatalf("error code: got %+v, want BLOB_UNKNOWN", body.Errors) } } func TestBlobDeleteRepoNotFound(t *testing.T) { fdb := newFakeDB() h := NewHandler(fdb, newFakeBlobs(), allowAll(), nil) router := testRouter(h) req := authedRequest("DELETE", "/v2/nosuchrepo/blobs/sha256:b1", nil) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusNotFound { t.Fatalf("status: got %d, want %d", rr.Code, http.StatusNotFound) } }