mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-08-01 01:58:09 -04:00
539 lines
17 KiB
Go
539 lines
17 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
|
|
upstreamhttp "github.com/git-pkgs/proxy/internal/httpclient"
|
|
"github.com/git-pkgs/registries/fetch"
|
|
)
|
|
|
|
func TestContainerHandler_parseBlobPath(t *testing.T) {
|
|
h := &ContainerHandler{}
|
|
|
|
tests := []struct {
|
|
path string
|
|
wantName string
|
|
wantDigest string
|
|
}{
|
|
{
|
|
path: "library/nginx/blobs/sha256:abc123def456",
|
|
wantName: "library/nginx",
|
|
wantDigest: "sha256:abc123def456",
|
|
},
|
|
{
|
|
path: "myorg/myrepo/blobs/sha256:0123456789abcdef",
|
|
wantName: "myorg/myrepo",
|
|
wantDigest: "sha256:0123456789abcdef",
|
|
},
|
|
{
|
|
path: "deep/nested/repo/name/blobs/sha256:fedcba9876543210",
|
|
wantName: "deep/nested/repo/name",
|
|
wantDigest: "sha256:fedcba9876543210",
|
|
},
|
|
{
|
|
path: "invalid/path",
|
|
wantName: "",
|
|
wantDigest: "",
|
|
},
|
|
{
|
|
path: "repo/blobs/md5:invalid",
|
|
wantName: "",
|
|
wantDigest: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.path, func(t *testing.T) {
|
|
name, digest := h.parseBlobPath(tt.path)
|
|
if name != tt.wantName {
|
|
t.Errorf("parseBlobPath() name = %q, want %q", name, tt.wantName)
|
|
}
|
|
if digest != tt.wantDigest {
|
|
t.Errorf("parseBlobPath() digest = %q, want %q", digest, tt.wantDigest)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContainerHandler_parseManifestPath(t *testing.T) {
|
|
h := &ContainerHandler{}
|
|
|
|
tests := []struct {
|
|
path string
|
|
wantName string
|
|
wantReference string
|
|
}{
|
|
{
|
|
path: "library/nginx/manifests/latest",
|
|
wantName: "library/nginx",
|
|
wantReference: "latest",
|
|
},
|
|
{
|
|
path: "myorg/myrepo/manifests/v1.0.0",
|
|
wantName: "myorg/myrepo",
|
|
wantReference: "v1.0.0",
|
|
},
|
|
{
|
|
path: "repo/manifests/sha256:abc123",
|
|
wantName: "repo",
|
|
wantReference: "sha256:abc123",
|
|
},
|
|
{
|
|
path: "invalid/path",
|
|
wantName: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.path, func(t *testing.T) {
|
|
name, ref := h.parseManifestPath(tt.path)
|
|
if name != tt.wantName {
|
|
t.Errorf("parseManifestPath() name = %q, want %q", name, tt.wantName)
|
|
}
|
|
if ref != tt.wantReference {
|
|
t.Errorf("parseManifestPath() reference = %q, want %q", ref, tt.wantReference)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContainerHandler_parseTagsListPath(t *testing.T) {
|
|
h := &ContainerHandler{}
|
|
|
|
tests := []struct {
|
|
path string
|
|
wantName string
|
|
}{
|
|
{
|
|
path: "library/nginx/tags/list",
|
|
wantName: "library/nginx",
|
|
},
|
|
{
|
|
path: "myorg/myrepo/tags/list",
|
|
wantName: "myorg/myrepo",
|
|
},
|
|
{
|
|
path: "invalid/path",
|
|
wantName: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.path, func(t *testing.T) {
|
|
name := h.parseTagsListPath(tt.path)
|
|
if name != tt.wantName {
|
|
t.Errorf("parseTagsListPath() = %q, want %q", name, tt.wantName)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContainerHandler_BlobDownload_DiscoversBearerChallenge(t *testing.T) {
|
|
digest := "sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abcd"
|
|
registryRequests := 0
|
|
tokenRequests := 0
|
|
var upstream *httptest.Server
|
|
upstream = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/token":
|
|
tokenRequests++
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"token": "discovered-token",
|
|
"expires_in": 3600,
|
|
})
|
|
case "/v2/library/nginx/blobs/" + digest:
|
|
registryRequests++
|
|
if r.Header.Get("Authorization") != "Bearer discovered-token" {
|
|
w.Header().Set("WWW-Authenticate", `Bearer realm="`+upstream.URL+`/token",service="registry.test",scope="repository:library/nginx:pull"`)
|
|
http.Error(w, "authentication required", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
_, _ = io.WriteString(w, "upstream blob")
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}))
|
|
defer upstream.Close()
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
authTransport := upstreamhttp.NewTransport(http.DefaultTransport, nil)
|
|
client := &http.Client{Transport: authTransport}
|
|
artifactFetcher := fetch.NewFetcher(
|
|
fetch.WithHTTPClient(client),
|
|
fetch.WithMaxRetries(0),
|
|
)
|
|
t.Cleanup(func() { _ = artifactFetcher.Close() })
|
|
proxy.Fetcher = artifactFetcher
|
|
proxy.HTTPClient = client
|
|
|
|
h := &ContainerHandler{
|
|
proxy: proxy,
|
|
registryURL: upstream.URL,
|
|
proxyURL: "http://localhost:8080",
|
|
}
|
|
|
|
for range 2 {
|
|
req := httptest.NewRequest(http.MethodGet, "/library/nginx/blobs/"+digest, nil)
|
|
w := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want %d; body: %s", w.Code, http.StatusOK, w.Body.String())
|
|
}
|
|
if got := w.Body.String(); got != "upstream blob" {
|
|
t.Errorf("body = %q, want %q", got, "upstream blob")
|
|
}
|
|
}
|
|
|
|
if tokenRequests != 1 {
|
|
t.Errorf("token requests = %d, want 1", tokenRequests)
|
|
}
|
|
if registryRequests != 2 {
|
|
t.Errorf("registry requests = %d, want 2", registryRequests)
|
|
}
|
|
}
|
|
|
|
func TestContainerHandler_BlobDownload_CacheHitSkipsAuth(t *testing.T) {
|
|
proxy, db, store, fetcher := setupTestProxy(t)
|
|
digest := "sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abcd"
|
|
seedPackage(t, db, store, "oci", "library/nginx", digest, digest, "cached blob")
|
|
|
|
upstreamRequests := 0
|
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
upstreamRequests++
|
|
http.Error(w, "upstream unavailable", http.StatusServiceUnavailable)
|
|
}))
|
|
defer upstream.Close()
|
|
|
|
h := &ContainerHandler{
|
|
proxy: proxy,
|
|
registryURL: upstream.URL,
|
|
proxyURL: "http://localhost:8080",
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/library/nginx/blobs/"+digest, nil)
|
|
w := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want %d; body: %s", w.Code, http.StatusOK, w.Body.String())
|
|
}
|
|
if got := w.Body.String(); got != "cached blob" {
|
|
t.Errorf("body = %q, want %q", got, "cached blob")
|
|
}
|
|
if upstreamRequests != 0 {
|
|
t.Errorf("upstream requests = %d, want 0", upstreamRequests)
|
|
}
|
|
if fetcher.fetchCalled {
|
|
t.Error("fetcher should not be called on cache hit")
|
|
}
|
|
}
|
|
|
|
func TestContainerHandler_BlobHead_CacheHitSkipsUpstreamAndAuth(t *testing.T) {
|
|
proxy, db, store, fetcher := setupTestProxy(t)
|
|
digest := "sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abcd"
|
|
seedPackage(t, db, store, "oci", "library/nginx", digest, digest, "cached blob")
|
|
|
|
upstreamRequests := 0
|
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
upstreamRequests++
|
|
http.Error(w, "upstream unavailable", http.StatusServiceUnavailable)
|
|
}))
|
|
defer upstream.Close()
|
|
proxy.HTTPClient = upstream.Client()
|
|
|
|
h := &ContainerHandler{
|
|
proxy: proxy,
|
|
registryURL: upstream.URL,
|
|
proxyURL: "http://localhost:8080",
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodHead, "/library/nginx/blobs/"+digest, nil)
|
|
w := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want %d; body: %s", w.Code, http.StatusOK, w.Body.String())
|
|
}
|
|
if got := w.Header().Get("Docker-Content-Digest"); got != digest {
|
|
t.Errorf("Docker-Content-Digest = %q, want %q", got, digest)
|
|
}
|
|
if got := w.Header().Get("Content-Length"); got != "11" {
|
|
t.Errorf("Content-Length = %q, want %q", got, "11")
|
|
}
|
|
if w.Body.Len() != 0 {
|
|
t.Errorf("HEAD response body length = %d, want 0", w.Body.Len())
|
|
}
|
|
if upstreamRequests != 0 {
|
|
t.Errorf("upstream requests = %d, want 0", upstreamRequests)
|
|
}
|
|
if fetcher.fetchCalled {
|
|
t.Error("fetcher should not be called on cache hit")
|
|
}
|
|
}
|
|
|
|
func TestContainerHandler_BlobHead_DirectServeRedirects(t *testing.T) {
|
|
proxy, db, store, fetcher := setupTestProxy(t)
|
|
digest := "sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abcd"
|
|
seedPackage(t, db, store, "oci", "library/nginx", digest, digest, "cached blob")
|
|
store.signedURL = "https://storage.example.test/cached-blob?signature=test"
|
|
proxy.DirectServe = true
|
|
|
|
h := &ContainerHandler{
|
|
proxy: proxy,
|
|
registryURL: "https://registry.example.test",
|
|
proxyURL: "http://localhost:8080",
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodHead, "/library/nginx/blobs/"+digest, nil)
|
|
w := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusFound {
|
|
t.Fatalf("status = %d, want %d", w.Code, http.StatusFound)
|
|
}
|
|
if got := w.Header().Get("Location"); got != store.signedURL {
|
|
t.Errorf("Location = %q, want %q", got, store.signedURL)
|
|
}
|
|
if got := w.Header().Get("ETag"); got != `"abc123"` {
|
|
t.Errorf("ETag = %q, want %q", got, `"abc123"`)
|
|
}
|
|
if w.Body.Len() != 0 {
|
|
t.Errorf("HEAD response body length = %d, want 0", w.Body.Len())
|
|
}
|
|
if fetcher.fetchCalled {
|
|
t.Error("fetcher should not be called on cache hit")
|
|
}
|
|
}
|
|
|
|
func TestContainerHandler_ManifestByDigest_CacheHitSkipsUpstream(t *testing.T) {
|
|
digest := "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
manifest := `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json"}`
|
|
upstreamAvailable := true
|
|
upstreamRequests := 0
|
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
upstreamRequests++
|
|
if !upstreamAvailable {
|
|
http.Error(w, "upstream unavailable", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
if r.URL.Path != "/v2/library/nginx/manifests/"+digest {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/vnd.oci.image.manifest.v1+json")
|
|
w.Header().Set("Docker-Content-Digest", digest)
|
|
w.Header().Set("ETag", `"manifest-etag"`)
|
|
if r.Method != http.MethodHead {
|
|
_, _ = io.WriteString(w, manifest)
|
|
}
|
|
}))
|
|
defer upstream.Close()
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
proxy.HTTPClient = upstream.Client()
|
|
h := &ContainerHandler{proxy: proxy, registryURL: upstream.URL, proxyURL: "http://localhost:8080"}
|
|
|
|
first := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(first, httptest.NewRequest(http.MethodGet, "/library/nginx/manifests/"+digest, nil))
|
|
if first.Code != http.StatusOK {
|
|
t.Fatalf("initial status = %d, want %d; body: %s", first.Code, http.StatusOK, first.Body.String())
|
|
}
|
|
if first.Body.String() != manifest {
|
|
t.Fatalf("initial body = %q, want %q", first.Body.String(), manifest)
|
|
}
|
|
|
|
upstreamAvailable = false
|
|
second := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(second, httptest.NewRequest(http.MethodGet, "/library/nginx/manifests/"+digest, nil))
|
|
if second.Code != http.StatusOK {
|
|
t.Fatalf("cached status = %d, want %d; body: %s", second.Code, http.StatusOK, second.Body.String())
|
|
}
|
|
if second.Body.String() != manifest {
|
|
t.Errorf("cached body = %q, want %q", second.Body.String(), manifest)
|
|
}
|
|
if got := second.Header().Get("Docker-Content-Digest"); got != digest {
|
|
t.Errorf("cached Docker-Content-Digest = %q, want %q", got, digest)
|
|
}
|
|
|
|
head := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(head, httptest.NewRequest(http.MethodHead, "/library/nginx/manifests/"+digest, nil))
|
|
if head.Code != http.StatusOK {
|
|
t.Fatalf("cached HEAD status = %d, want %d", head.Code, http.StatusOK)
|
|
}
|
|
wantLength := strconv.Itoa(len(manifest))
|
|
if got := head.Header().Get("Content-Length"); got != wantLength {
|
|
t.Errorf("cached HEAD Content-Length = %q, want %q", got, wantLength)
|
|
}
|
|
if head.Body.Len() != 0 {
|
|
t.Errorf("cached HEAD body length = %d, want 0", head.Body.Len())
|
|
}
|
|
if upstreamRequests != 1 {
|
|
t.Errorf("upstream requests = %d, want 1", upstreamRequests)
|
|
}
|
|
}
|
|
|
|
func TestContainerHandler_ManifestByTag_UsesStaleCacheOnUpstreamFailure(t *testing.T) {
|
|
digest := "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
|
manifest := `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json"}`
|
|
upstreamAvailable := true
|
|
upstreamRequests := 0
|
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
upstreamRequests++
|
|
if !upstreamAvailable {
|
|
http.Error(w, "upstream unavailable", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/vnd.oci.image.index.v1+json")
|
|
w.Header().Set("Docker-Content-Digest", digest)
|
|
_, _ = io.WriteString(w, manifest)
|
|
}))
|
|
defer upstream.Close()
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
proxy.HTTPClient = upstream.Client()
|
|
proxy.MetadataTTL = 0
|
|
h := &ContainerHandler{proxy: proxy, registryURL: upstream.URL, proxyURL: "http://localhost:8080"}
|
|
|
|
first := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(first, httptest.NewRequest(http.MethodGet, "/library/nginx/manifests/latest", nil))
|
|
if first.Code != http.StatusOK {
|
|
t.Fatalf("initial status = %d, want %d; body: %s", first.Code, http.StatusOK, first.Body.String())
|
|
}
|
|
|
|
upstreamAvailable = false
|
|
second := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(second, httptest.NewRequest(http.MethodGet, "/library/nginx/manifests/latest", nil))
|
|
if second.Code != http.StatusOK {
|
|
t.Fatalf("stale status = %d, want %d; body: %s", second.Code, http.StatusOK, second.Body.String())
|
|
}
|
|
if second.Body.String() != manifest {
|
|
t.Errorf("stale body = %q, want %q", second.Body.String(), manifest)
|
|
}
|
|
if got := second.Header().Get("Warning"); got != `110 - "Response is Stale"` {
|
|
t.Errorf("Warning = %q, want stale warning", got)
|
|
}
|
|
if got := second.Header().Get("Docker-Content-Digest"); got != digest {
|
|
t.Errorf("stale Docker-Content-Digest = %q, want %q", got, digest)
|
|
}
|
|
if upstreamRequests != 2 {
|
|
t.Errorf("upstream requests = %d, want 2", upstreamRequests)
|
|
}
|
|
}
|
|
|
|
func TestContainerHandler_ManifestByTag_CachesDigestAlias(t *testing.T) {
|
|
digest := "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
|
|
manifest := `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json"}`
|
|
upstreamAvailable := true
|
|
upstreamRequests := 0
|
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
upstreamRequests++
|
|
if !upstreamAvailable {
|
|
http.Error(w, "upstream unavailable", http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
if r.URL.Path != "/v2/library/nginx/manifests/latest" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/vnd.oci.image.manifest.v1+json")
|
|
w.Header().Set("Docker-Content-Digest", digest)
|
|
_, _ = io.WriteString(w, manifest)
|
|
}))
|
|
defer upstream.Close()
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
proxy.HTTPClient = upstream.Client()
|
|
h := &ContainerHandler{proxy: proxy, registryURL: upstream.URL, proxyURL: "http://localhost:8080"}
|
|
|
|
first := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(first, httptest.NewRequest(http.MethodGet, "/library/nginx/manifests/latest", nil))
|
|
if first.Code != http.StatusOK {
|
|
t.Fatalf("tag status = %d, want %d; body: %s", first.Code, http.StatusOK, first.Body.String())
|
|
}
|
|
|
|
upstreamAvailable = false
|
|
byDigest := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(byDigest, httptest.NewRequest(http.MethodGet, "/library/nginx/manifests/"+digest, nil))
|
|
if byDigest.Code != http.StatusOK {
|
|
t.Fatalf("digest status = %d, want %d; body: %s", byDigest.Code, http.StatusOK, byDigest.Body.String())
|
|
}
|
|
if byDigest.Body.String() != manifest {
|
|
t.Errorf("digest body = %q, want %q", byDigest.Body.String(), manifest)
|
|
}
|
|
if got := byDigest.Header().Get("Docker-Content-Digest"); got != digest {
|
|
t.Errorf("Docker-Content-Digest = %q, want %q", got, digest)
|
|
}
|
|
if upstreamRequests != 1 {
|
|
t.Errorf("upstream requests = %d, want 1", upstreamRequests)
|
|
}
|
|
}
|
|
|
|
func TestContainerHandler_ManifestByTag_StaleHeadChecksUpstream(t *testing.T) {
|
|
oldDigest := "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
|
|
newDigest := "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
|
|
currentDigest := oldDigest
|
|
upstreamRequests := 0
|
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
upstreamRequests++
|
|
w.Header().Set("Content-Type", "application/vnd.oci.image.manifest.v1+json")
|
|
w.Header().Set("Docker-Content-Digest", currentDigest)
|
|
w.Header().Set("ETag", `"`+currentDigest+`"`)
|
|
if r.Method != http.MethodHead {
|
|
_, _ = io.WriteString(w, `{"schemaVersion":2}`)
|
|
}
|
|
}))
|
|
defer upstream.Close()
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
proxy.HTTPClient = upstream.Client()
|
|
proxy.MetadataTTL = 0
|
|
h := &ContainerHandler{proxy: proxy, registryURL: upstream.URL, proxyURL: "http://localhost:8080"}
|
|
|
|
first := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(first, httptest.NewRequest(http.MethodGet, "/library/nginx/manifests/latest", nil))
|
|
if first.Code != http.StatusOK {
|
|
t.Fatalf("initial status = %d, want %d", first.Code, http.StatusOK)
|
|
}
|
|
|
|
currentDigest = newDigest
|
|
head := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(head, httptest.NewRequest(http.MethodHead, "/library/nginx/manifests/latest", nil))
|
|
if head.Code != http.StatusOK {
|
|
t.Fatalf("HEAD status = %d, want %d", head.Code, http.StatusOK)
|
|
}
|
|
if got := head.Header().Get("Docker-Content-Digest"); got != newDigest {
|
|
t.Errorf("Docker-Content-Digest = %q, want %q", got, newDigest)
|
|
}
|
|
if upstreamRequests != 2 {
|
|
t.Errorf("upstream requests = %d, want 2", upstreamRequests)
|
|
}
|
|
}
|
|
|
|
func TestContainerHandler_Routes_VersionCheck(t *testing.T) {
|
|
h := NewContainerHandler(nil, "http://localhost:8080")
|
|
|
|
handler := h.Routes()
|
|
if handler == nil {
|
|
t.Fatal("Routes() returned nil")
|
|
}
|
|
|
|
// Test /v2/ version check endpoint
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("version check: got status %d, want %d", w.Code, http.StatusOK)
|
|
}
|
|
|
|
if got := w.Header().Get("Docker-Distribution-Api-Version"); got != "registry/2.0" {
|
|
t.Errorf("Docker-Distribution-Api-Version = %q, want %q", got, "registry/2.0")
|
|
}
|
|
}
|