mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-09 15:49:16 -05:00
88 lines
3 KiB
Go
88 lines
3 KiB
Go
|
|
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)
|
||
|
|
}
|
||
|
|
}
|