mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-09 15:49:16 -05:00
A cached rewrite made with cooldown on stays right until a version it held back leaves cooldown, so it now expires then instead of after a fixed time. Handlers ask cooldown through Proxy.cooldown, which reports each withheld version to the rewrites running at that moment. A rewrite that held nothing back is kept for the shortest cooldown, at most a day.
159 lines
5.8 KiB
Go
159 lines
5.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/git-pkgs/cooldown"
|
|
"github.com/git-pkgs/proxy/internal/metrics"
|
|
"github.com/prometheus/client_golang/prometheus/testutil"
|
|
)
|
|
|
|
// TestMetadataServesRewriteWithoutReadingStorage checks that a repeat
|
|
// request inside the metadata TTL is answered from the rewrite cache by the
|
|
// stored digest alone, without reading the cached document back.
|
|
func TestMetadataServesRewriteWithoutReadingStorage(t *testing.T) {
|
|
var upstreamCalls atomic.Int64
|
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
upstreamCalls.Add(1)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
switch r.URL.Path {
|
|
case "/left-pad":
|
|
_, _ = w.Write([]byte(`{"name":"left-pad","versions":{"1.3.0":{"dist":{"tarball":"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"}}}}`))
|
|
case "/p2/vendor/pkg.json":
|
|
_, _ = w.Write([]byte(`{"packages":{"vendor/pkg":[{"version":"1.0.0","dist":{"type":"zip","url":"https://example.com/pkg-1.0.0.zip"}}]}}`))
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}))
|
|
defer upstream.Close()
|
|
|
|
cases := []struct {
|
|
ecosystem, name, path string
|
|
handler func(*Proxy) http.Handler
|
|
}{
|
|
{"npm", "left-pad", "/left-pad", func(p *Proxy) http.Handler {
|
|
return NewNPMHandler(p, "http://proxy.example", upstream.URL).Routes()
|
|
}},
|
|
{"composer", "vendor/pkg", "/p2/vendor/pkg.json", func(p *Proxy) http.Handler {
|
|
return NewComposerHandlerWithUpstreams(p, "http://proxy.example", upstream.URL, upstream.URL).Routes()
|
|
}},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.ecosystem, func(t *testing.T) {
|
|
upstreamCalls.Store(0)
|
|
proxy, db, store, _ := setupTestProxy(t)
|
|
proxy.HTTPClient = upstream.Client()
|
|
proxy.CacheMetadata = true
|
|
proxy.MetadataTTL = time.Hour
|
|
proxy.SetMetadataRewriteCacheSize(1 << 20)
|
|
handler := c.handler(proxy)
|
|
|
|
get := func() string {
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, c.path, nil))
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
return rec.Body.String()
|
|
}
|
|
|
|
first := get()
|
|
entry, err := db.GetMetadataCache(c.ecosystem, c.name)
|
|
if err != nil || entry == nil || !strings.HasPrefix(entry.ContentDigest.String, "sha256:") {
|
|
t.Fatalf("metadata row = %+v, %v; want a sha256 content digest", entry, err)
|
|
}
|
|
|
|
store.mu.Lock()
|
|
store.openErr = errors.New("storage must not be read")
|
|
store.mu.Unlock()
|
|
|
|
if second := get(); second != first {
|
|
t.Errorf("second response %q differs from first %q", second, first)
|
|
}
|
|
if got := upstreamCalls.Load(); got != 1 {
|
|
t.Errorf("upstream calls = %d, want 1", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestNPMStoredRewriteSkippedWhenStale checks that the digest fast path is
|
|
// only taken inside the metadata TTL.
|
|
func TestNPMStoredRewriteSkippedWhenStale(t *testing.T) {
|
|
proxy, db, _, _ := setupTestProxy(t)
|
|
proxy.CacheMetadata = true
|
|
proxy.MetadataTTL = time.Hour
|
|
proxy.SetMetadataRewriteCacheSize(1 << 20)
|
|
|
|
body := []byte(`{}`)
|
|
proxy.cacheMetadataBlob(t.Context(), "npm", "demo", metadataStoragePath("npm", "demo"), &upstreamMetadata{body: body})
|
|
key := rewriteCacheKey("npm", "http://proxy", "demo", body)
|
|
if _, err := proxy.rewrites.rewrite(t.Context(), key, body, nil, func(b []byte) ([]byte, error) { return b, nil }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := proxy.storedRewrite("npm", "demo", "http://proxy", "demo"); !ok {
|
|
t.Fatal("fresh row: expected a stored rewrite")
|
|
}
|
|
|
|
entry, _ := db.GetMetadataCache("npm", "demo")
|
|
entry.FetchedAt.Time = time.Now().Add(-2 * time.Hour)
|
|
if err := db.UpsertMetadataCache(entry); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, ok := proxy.storedRewrite("npm", "demo", "http://proxy", "demo"); ok {
|
|
t.Error("stale row: expected no stored rewrite")
|
|
}
|
|
|
|
proxy.Cooldown = &cooldown.Config{Default: "3d"}
|
|
entry.FetchedAt.Time = time.Now()
|
|
_ = db.UpsertMetadataCache(entry)
|
|
if _, ok := proxy.storedRewrite("npm", "demo", "http://proxy", "demo"); !ok {
|
|
t.Error("cooldown on, fresh row: expected the stored rewrite")
|
|
}
|
|
}
|
|
|
|
// TestNPMStoredRewriteRecordsMetadataHit checks the digest fast path counts as
|
|
// a metadata cache hit with the bytes it serves, never as an artifact hit.
|
|
func TestNPMStoredRewriteRecordsMetadataHit(t *testing.T) {
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
proxy.CacheMetadata = true
|
|
proxy.MetadataTTL = time.Hour
|
|
proxy.SetMetadataRewriteCacheSize(1 << 20)
|
|
|
|
body := []byte(`{"name":"demo"}`)
|
|
proxy.cacheMetadataBlob(t.Context(), "npm", "hit-demo", metadataStoragePath("npm", "hit-demo"), &upstreamMetadata{body: body})
|
|
key := rewriteCacheKey("npm", "http://proxy", "hit-demo", body)
|
|
if _, err := proxy.rewrites.rewrite(t.Context(), key, body, nil, func(b []byte) ([]byte, error) { return b, nil }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
metadataHits := func() float64 {
|
|
return testutil.ToFloat64(metrics.CacheHits.WithLabelValues("npm", metrics.CacheKindMetadata))
|
|
}
|
|
artifactHits := func() float64 {
|
|
return testutil.ToFloat64(metrics.CacheHits.WithLabelValues("npm", metrics.CacheKindArtifact))
|
|
}
|
|
served := func() float64 { return testutil.ToFloat64(metrics.MetadataServedBytes.WithLabelValues("npm")) }
|
|
metadataBefore, artifactBefore, servedBefore := metadataHits(), artifactHits(), served()
|
|
|
|
m, ok := proxy.storedRewrite("npm", "hit-demo", "http://proxy", "hit-demo")
|
|
if !ok {
|
|
t.Fatal("expected a stored rewrite")
|
|
}
|
|
|
|
if diff := metadataHits() - metadataBefore; diff != 1 {
|
|
t.Errorf("metadata hits delta = %.0f, want 1", diff)
|
|
}
|
|
if diff := artifactHits() - artifactBefore; diff != 0 {
|
|
t.Errorf("artifact hits delta = %.0f, want 0", diff)
|
|
}
|
|
if diff := served() - servedBefore; diff != float64(len(m.content())) {
|
|
t.Errorf("metadata served bytes delta = %.0f, want %d", diff, len(m.content()))
|
|
}
|
|
}
|