Every 500 response in the OCI package silently discarded the actual error, making production debugging impossible. Add slog.Error before each 500 response with the error and relevant context (repo, digest, tag, uuid). Add slog.Info for state-mutating successes (manifest push, blob upload complete, deletions). Logger is injected into the OCI Handler via constructor, falling back to slog.Default() if nil. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
188 lines
5.6 KiB
Go
188 lines
5.6 KiB
Go
package oci
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestManifestGetByTag(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, nil)
|
|
router := testRouter(h)
|
|
|
|
req := authedRequest("GET", "/v2/myrepo/manifests/latest", nil)
|
|
rr := httptest.NewRecorder()
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status: got %d, want %d", rr.Code, http.StatusOK)
|
|
}
|
|
if ct := rr.Header().Get("Content-Type"); ct != "application/vnd.oci.image.manifest.v1+json" {
|
|
t.Fatalf("Content-Type: got %q", ct)
|
|
}
|
|
if dcd := rr.Header().Get("Docker-Content-Digest"); dcd != "sha256:aaaa" {
|
|
t.Fatalf("Docker-Content-Digest: got %q", dcd)
|
|
}
|
|
if cl := rr.Header().Get("Content-Length"); cl != "19" {
|
|
t.Fatalf("Content-Length: got %q, want %q", cl, "19")
|
|
}
|
|
if rr.Body.String() != `{"schemaVersion":2}` {
|
|
t.Fatalf("body: got %q", rr.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestManifestGetByDigest(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, nil)
|
|
router := testRouter(h)
|
|
|
|
req := authedRequest("GET", "/v2/myrepo/manifests/sha256:aaaa", nil)
|
|
rr := httptest.NewRecorder()
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status: got %d, want %d", rr.Code, http.StatusOK)
|
|
}
|
|
if dcd := rr.Header().Get("Docker-Content-Digest"); dcd != "sha256:aaaa" {
|
|
t.Fatalf("Docker-Content-Digest: got %q", dcd)
|
|
}
|
|
if rr.Body.String() != `{"schemaVersion":2}` {
|
|
t.Fatalf("body: got %q", rr.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestManifestHead(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, nil)
|
|
router := testRouter(h)
|
|
|
|
req := authedRequest("HEAD", "/v2/myrepo/manifests/latest", nil)
|
|
rr := httptest.NewRecorder()
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status: got %d, want %d", rr.Code, http.StatusOK)
|
|
}
|
|
if ct := rr.Header().Get("Content-Type"); ct != "application/vnd.oci.image.manifest.v1+json" {
|
|
t.Fatalf("Content-Type: got %q", ct)
|
|
}
|
|
if dcd := rr.Header().Get("Docker-Content-Digest"); dcd != "sha256:aaaa" {
|
|
t.Fatalf("Docker-Content-Digest: got %q", dcd)
|
|
}
|
|
if cl := rr.Header().Get("Content-Length"); cl != "19" {
|
|
t.Fatalf("Content-Length: got %q, want %q", cl, "19")
|
|
}
|
|
if rr.Body.Len() != 0 {
|
|
t.Fatalf("HEAD body should be empty, got %d bytes", rr.Body.Len())
|
|
}
|
|
}
|
|
|
|
func TestManifestGetRepoNotFound(t *testing.T) {
|
|
fdb := newFakeDB()
|
|
// No repos added.
|
|
|
|
h := NewHandler(fdb, newFakeBlobs(), allowAll(), nil, nil)
|
|
router := testRouter(h)
|
|
|
|
req := authedRequest("GET", "/v2/nosuchrepo/manifests/latest", 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 body: %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 TestManifestGetManifestNotFoundByTag(t *testing.T) {
|
|
fdb := newFakeDB()
|
|
fdb.addRepo("myrepo", 1)
|
|
// No manifests added.
|
|
|
|
h := NewHandler(fdb, newFakeBlobs(), allowAll(), nil, nil)
|
|
router := testRouter(h)
|
|
|
|
req := authedRequest("GET", "/v2/myrepo/manifests/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 body: %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 TestManifestGetManifestNotFoundByDigest(t *testing.T) {
|
|
fdb := newFakeDB()
|
|
fdb.addRepo("myrepo", 1)
|
|
// No manifests added.
|
|
|
|
h := NewHandler(fdb, newFakeBlobs(), allowAll(), nil, nil)
|
|
router := testRouter(h)
|
|
|
|
req := authedRequest("GET", "/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 body: %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 TestManifestGetMultiSegmentRepo(t *testing.T) {
|
|
fdb := newFakeDB()
|
|
fdb.addRepo("org/team/app", 1)
|
|
content := []byte(`{"layers":[]}`)
|
|
fdb.addManifest(1, "v1.0", "sha256:cccc", "application/vnd.oci.image.manifest.v1+json", content)
|
|
|
|
h := NewHandler(fdb, newFakeBlobs(), allowAll(), nil, nil)
|
|
router := testRouter(h)
|
|
|
|
req := authedRequest("GET", "/v2/org/team/app/manifests/v1.0", nil)
|
|
rr := httptest.NewRecorder()
|
|
router.ServeHTTP(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status: got %d, want %d", rr.Code, http.StatusOK)
|
|
}
|
|
if dcd := rr.Header().Get("Docker-Content-Digest"); dcd != "sha256:cccc" {
|
|
t.Fatalf("Docker-Content-Digest: got %q", dcd)
|
|
}
|
|
}
|