Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 15:49:16 -05:00
pkg-proxy/internal/handler/head_cache_hit_test.go

88 lines
3 KiB
Go
Raw Permalink Normal View History

package handler
import (
"context"
"fmt"
"maps"
"net/http"
"net/http/httptest"
"testing"
"github.com/git-pkgs/purl"
)
// A HEAD on a cache hit is answered from the artifact's database record. It
// must not open the stored object (a GET on S3 or GCS), and its headers must
// be the ones the GET sends.
func TestHeadCacheHitSkipsStorageOpen(t *testing.T) {
const digest = "sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abcd"
for _, seekable := range []bool{false, true} {
t.Run(fmt.Sprintf("seekable=%t", seekable), func(t *testing.T) {
proxy, db, store, fetcher := setupTestProxy(t)
store.seekable = seekable
seedPackage(t, db, store, "oci", "library/nginx", digest, digest, "cached blob")
h := &ContainerHandler{
proxy: proxy,
registryURL: "https://registry.invalid",
proxyURL: "http://localhost:8080",
}
target := "/library/nginx/blobs/" + digest
get := httptest.NewRecorder()
h.Routes().ServeHTTP(get, httptest.NewRequest(http.MethodGet, target, nil))
if get.Code != http.StatusOK || get.Body.String() != "cached blob" {
t.Fatalf("GET: status %d, body %q", get.Code, get.Body.String())
}
opensAfterGet := store.opens
req := httptest.NewRequest(http.MethodHead, target, nil)
req = req.WithContext(ServingHead(req.Context()))
head := httptest.NewRecorder()
h.Routes().ServeHTTP(head, req)
if head.Code != http.StatusOK {
t.Fatalf("HEAD: status %d, body %q", head.Code, head.Body.String())
}
if got := store.opens - opensAfterGet; got != 0 {
t.Errorf("HEAD opened the stored object %d times, want 0", got)
}
if head.Body.Len() != 0 {
t.Errorf("HEAD body length = %d, want 0", head.Body.Len())
}
if !maps.EqualFunc(get.Header(), head.Header(), func(a, b []string) bool {
return fmt.Sprint(a) == fmt.Sprint(b)
}) {
t.Errorf("HEAD headers differ from GET:\n GET %v\n HEAD %v", get.Header(), head.Header())
}
if got := get.Header().Get("Accept-Ranges") == "bytes"; got != seekable {
t.Errorf("GET Accept-Ranges set = %t, want %t", got, seekable)
}
if fetcher.fetchCalled {
t.Error("fetcher called on a cache hit")
}
// A HEAD still counts as a hit, as it did when it opened the object.
art, err := db.GetArtifact(purl.MakePURLString("oci", "library/nginx", digest), digest)
if err != nil || art == nil {
t.Fatalf("artifact record: %v", err)
}
if art.HitCount != 2 {
t.Errorf("hit count = %d, want 2 (GET and HEAD)", art.HitCount)
}
})
}
}
// A request that is not marked as HEAD reads the object as before.
func TestCacheHitWithoutHeadMarkOpensObject(t *testing.T) {
proxy, db, store, _ := setupTestProxy(t)
seedPackage(t, db, store, "npm", "demo", "1.0.0", "demo.tgz", "content")
result, err := proxy.GetCachedArtifact(context.Background(), "npm", "demo", "1.0.0", "demo.tgz")
if err != nil || result == nil || result.Reader == nil {
t.Fatalf("result=%+v err=%v; want an open reader", result, err)
}
_ = result.Reader.Close()
if store.opens != 1 {
t.Errorf("opens = %d, want 1", store.opens)
}
}