Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 23:59:16 -05:00
pkg-proxy/internal/server/retention_generic_test.go

156 lines
5.4 KiB
Go
Raw Permalink Normal View History

package server
import (
"context"
"io"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"time"
"github.com/git-pkgs/proxy/internal/config"
"github.com/git-pkgs/proxy/internal/database"
"github.com/git-pkgs/proxy/internal/handler"
"github.com/git-pkgs/proxy/internal/retention"
"github.com/git-pkgs/registries/fetch"
)
const genericReleaseAssetPath = "/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64"
func genericRetentionUpstream(t *testing.T) (*httptest.Server, *atomic.Int64) {
t.Helper()
var downloads atomic.Int64
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != genericReleaseAssetPath {
http.NotFound(w, r)
return
}
downloads.Add(1)
_, _ = io.WriteString(w, "release asset")
}))
t.Cleanup(srv.Close)
return srv, &downloads
}
func newGenericRetentionProxy(t *testing.T, upstream *httptest.Server) (*httptest.Server, *database.DB) {
t.Helper()
db, store := setupEvictionTest(t)
fetcher := fetch.NewFetcher(fetch.WithHTTPClient(upstream.Client()), fetch.WithMaxRetries(0))
t.Cleanup(func() { _ = fetcher.Close() })
proxy := handler.NewProxy(db, store, fetcher, fetch.NewResolver(), discardLogger())
proxy.HTTPClient = upstream.Client()
routes := handler.NewGenericHandler(proxy, map[string]string{"github": upstream.URL}).Routes()
srv := httptest.NewServer(http.StripPrefix("/generic", routes))
t.Cleanup(srv.Close)
return srv, db
}
func genericReleaseArtifact(t *testing.T, db *database.DB) database.Artifact {
t.Helper()
const versionPURL = "pkg:generic/jqlang%2Fjq@jq-1.7.1"
var art database.Artifact
query := db.Rebind(`SELECT * FROM artifacts WHERE version_purl = ?`)
if err := db.Get(&art, query, versionPURL); err != nil {
t.Fatalf("reading release artifact %s: %v", versionPURL, err)
}
return art
}
func TestGenericRetentionRequiresExplicitRule(t *testing.T) {
const packagePURL = "pkg:generic/jqlang%2Fjq"
tests := []struct {
name string
ecosystems map[string]string
packages map[string]string
wantCached bool
}{
{"global default keeps generic", nil, nil, true},
{"ecosystem opts in", map[string]string{"generic": "1d"}, nil, false},
{"package opts in", nil, map[string]string{packagePURL: "1d"}, false},
{"other package stays cached", nil, map[string]string{"pkg:generic/other%2Frepo": "1d"}, true},
{"package disables ecosystem rule", map[string]string{"generic": "1d"}, map[string]string{packagePURL: "0"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
upstream, _ := genericRetentionUpstream(t)
srv, db := newGenericRetentionProxy(t, upstream)
getOK(t, srv.URL+"/generic/github"+genericReleaseAssetPath)
versionPURL := packagePURL + "@jq-1.7.1"
now := time.Now()
old := now.Add(-30 * retentionDay)
backdate(t, db, "fetched_at", versionPURL, old)
backdate(t, db, "last_accessed_at", versionPURL, old)
cfg := config.Default()
cfg.Storage.Retention.Default = "1d"
cfg.Storage.Retention.Ecosystems = tt.ecosystems
cfg.Storage.Retention.Packages = tt.packages
rules, err := cfg.RetentionRules(retention.Default)
if err != nil {
t.Fatalf("RetentionRules() error = %v", err)
}
sweepRetention(context.Background(), db, discardLogger(), retention.Default, rules, now)
if art := genericReleaseArtifact(t, db); art.StoragePath.Valid != tt.wantCached {
t.Errorf("release cached = %v, want %v", art.StoragePath.Valid, tt.wantCached)
}
})
}
}
func TestGenericRetentionEvictsAndRefetchesReleaseAsset(t *testing.T) {
upstream, downloads := genericRetentionUpstream(t)
srv, db := newGenericRetentionProxy(t, upstream)
const (
packagePURL = "pkg:generic/jqlang%2Fjq"
versionPURL = packagePURL + "@jq-1.7.1"
)
assetURL := srv.URL + "/generic/github" + genericReleaseAssetPath
getOK(t, assetURL)
getOK(t, assetURL)
if got := downloads.Load(); got != 1 {
t.Fatalf("upstream downloads = %d, want 1 (second request from cache)", got)
}
art := genericReleaseArtifact(t, db)
if !art.LastAccessedAt.Valid {
t.Fatalf("cached release artifact = %+v; want a recorded cache hit", art)
}
cfg := config.Default()
cfg.Storage.Retention.Packages = map[string]string{packagePURL: "1d"}
rules, err := cfg.RetentionRules(retention.Default)
if err != nil {
t.Fatalf("RetentionRules() error = %v", err)
}
now := time.Now()
old := now.Add(-30 * retentionDay)
backdate(t, db, "fetched_at", versionPURL, old)
backdate(t, db, "last_accessed_at", versionPURL, now)
sweepRetention(context.Background(), db, discardLogger(), retention.Default, rules, now)
art = genericReleaseArtifact(t, db)
if !art.StoragePath.Valid {
t.Fatalf("recently accessed release was evicted: %+v", art)
}
backdate(t, db, "last_accessed_at", versionPURL, old)
sweepRetention(context.Background(), db, discardLogger(), retention.Default, rules, now)
art = genericReleaseArtifact(t, db)
if art.StoragePath.Valid {
t.Fatalf("unused release was not evicted: %+v", art)
}
getOK(t, assetURL)
if got := downloads.Load(); got != 2 {
t.Errorf("upstream downloads = %d, want 2 after retention eviction", got)
}
art = genericReleaseArtifact(t, db)
if !art.StoragePath.Valid {
t.Fatalf("refetched release artifact = %+v; want cached artifact", art)
}
sweepRetention(context.Background(), db, discardLogger(), retention.Default, rules, time.Now())
art = genericReleaseArtifact(t, db)
if !art.StoragePath.Valid {
t.Errorf("refetched release was evicted using its old access time: %+v", art)
}
}