Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 07:39:19 -05:00
pkg-proxy/internal/config/retention_test.go
profiling d643836649
Add opt-in retention for generic artifacts (#513)
* Add opt-in retention for generic artifacts

* Remove constant parameter from generic retention test helper

---------

Co-authored-by: ztzhou <ztzhou@bjwgby.com>
2026-10-19 08:43:57 +01:00

174 lines
7.3 KiB
Go

package config
import (
"strings"
"testing"
"time"
"github.com/git-pkgs/proxy/internal/retention"
)
const retentionDay = 24 * time.Hour
func retentionTestRegistry() *retention.Registry {
reg := retention.NewRegistry()
reg.Register(retention.Spec{Key: "generic", CanonicalPackage: retention.DefaultCanonical("generic")})
reg.Register(retention.Spec{Key: "npm", CanonicalPackage: retention.DefaultCanonical("npm")})
reg.Register(retention.Spec{Key: "julia"})
reg.Register(retention.Spec{Key: "helm"})
return reg
}
func TestRetentionRulesUnsetIsDisabled(t *testing.T) {
rules, err := Default().RetentionRules(retentionTestRegistry())
if err != nil {
t.Fatalf("RetentionRules() error = %v", err)
}
if got := rules.MinPositive(); got != 0 {
t.Errorf("MinPositive() = %v, want 0 for an unset retention block", got)
}
}
func TestRetentionRulesResolves(t *testing.T) {
cfg := Default()
cfg.Storage.Retention = RetentionConfig{
Default: "30d",
Ecosystems: map[string]string{"npm": "14d"},
Packages: map[string]string{
"pkg:npm/lodash": "0",
"pkg:npm/@babel/core": "90d",
"pkg:generic/jqlang%2Fjq": "7d",
},
SweepInterval: "5m",
}
rules, err := cfg.RetentionRules(retentionTestRegistry())
if err != nil {
t.Fatalf("RetentionRules() error = %v", err)
}
if rules.Default != 30*retentionDay {
t.Errorf("Default = %v, want 30d", rules.Default)
}
if got := rules.For("npm", "pkg:npm/left-pad"); got != 14*retentionDay {
t.Errorf("npm rule = %v, want 14d", got)
}
if got, ok := rules.Packages["pkg:npm/lodash"]; !ok || got != 0 {
t.Errorf("lodash rule = %v, %v, want 0, true", got, ok)
}
if got := rules.For("npm", "pkg:npm/%40babel/core"); got != 90*retentionDay {
t.Errorf("babel rule = %v, want 90d under its canonical key", got)
}
if got := rules.For("generic", "pkg:generic/jqlang%2Fjq"); got != 7*retentionDay {
t.Errorf("generic package rule = %v, want 7d", got)
}
if got := rules.For("generic", "pkg:generic/other%2Frepo"); got != 0 {
t.Errorf("unconfigured generic package rule = %v, want 0 despite the global default", got)
}
if got := cfg.ParseRetentionSweepInterval(); got != 5*time.Minute {
t.Errorf("ParseRetentionSweepInterval() = %v, want 5m", got)
}
}
func TestRetentionRulesErrors(t *testing.T) {
tests := []struct {
name string
modify func(*RetentionConfig)
wantErr string
}{
{"bad default", func(r *RetentionConfig) { r.Default = "7x" }, "storage.retention.default"},
{"negative default", func(r *RetentionConfig) { r.Default = "-1d" }, "must not be negative"},
{"NaN days", func(r *RetentionConfig) { r.Default = "NaNd" }, "out of range"},
{"infinite days", func(r *RetentionConfig) { r.Default = "Infd" }, "out of range"},
{"overflowing days", func(r *RetentionConfig) { r.Default = "1e9d" }, "out of range"},
{"zero sweep interval", func(r *RetentionConfig) { r.SweepInterval = "0s" }, "must be at least 1m0s"},
{"sweep interval below a minute", func(r *RetentionConfig) { r.SweepInterval = "30s" }, "must be at least 1m0s"},
{"bad sweep interval", func(r *RetentionConfig) { r.SweepInterval = "often" }, "sweep_interval"},
{"unknown ecosystem", func(r *RetentionConfig) { r.Ecosystems = map[string]string{"nmp": "1d"} }, "unknown ecosystem"},
{"ecosystem not registered", func(r *RetentionConfig) { r.Ecosystems = map[string]string{"oci": "14d"} },
`storage.retention.ecosystems.oci: retention is not supported for "oci"`},
{"bad ecosystem duration", func(r *RetentionConfig) { r.Ecosystems = map[string]string{"npm": "soon"} }, "storage.retention.ecosystems.npm"},
{"package of unregistered ecosystem", func(r *RetentionConfig) { r.Packages = map[string]string{"pkg:cargo/serde": "1d"} },
"not supported for cargo packages"},
{"julia package", func(r *RetentionConfig) { r.Packages = map[string]string{"pkg:julia/Example": "1d"} },
"not supported for julia"},
{"package of ecosystem without package rules", func(r *RetentionConfig) { r.Packages = map[string]string{"pkg:helm/nginx": "1d"} },
"package rules are not supported for helm; use storage.retention.ecosystems.helm"},
{"invalid package PURL", func(r *RetentionConfig) { r.Packages = map[string]string{"lodash": "1d"} }, "not a valid package PURL"},
{"npm package without its scope marker", func(r *RetentionConfig) { r.Packages = map[string]string{"pkg:npm/babel/core": "1d"} },
"does not match how npm packages are stored"},
{"npm package with an encoded scope separator", func(r *RetentionConfig) { r.Packages = map[string]string{"pkg:npm/%40babel%2Fcore": "1d"} },
"does not match how npm packages are stored"},
{"versioned package PURL", func(r *RetentionConfig) { r.Packages = map[string]string{"pkg:npm/lodash@4.17.21": "1d"} },
"must not carry a version"},
{"conflicting package keys", func(r *RetentionConfig) {
r.Packages = map[string]string{"pkg:npm/%40babel/core": "1d", "pkg:npm/@babel/core": "2d"}
}, "conflicts with another key"},
{"package PURL with qualifiers", func(r *RetentionConfig) { r.Packages = map[string]string{"pkg:npm/lodash?type=tgz": "1d"} },
"must not carry qualifiers or a subpath"},
{"package PURL with subpath", func(r *RetentionConfig) { r.Packages = map[string]string{"pkg:npm/lodash#lib": "1d"} },
"must not carry qualifiers or a subpath"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := Default()
tt.modify(&cfg.Storage.Retention)
_, err := cfg.RetentionRules(retentionTestRegistry())
if err == nil {
t.Fatalf("RetentionRules() succeeded, want error containing %q", tt.wantErr)
}
if !strings.Contains(err.Error(), tt.wantErr) {
t.Errorf("RetentionRules() error = %q, want it to contain %q", err, tt.wantErr)
}
})
}
}
// Validate checks retention against the handlers' registry, which this
// package's tests leave empty, so any ecosystem rule fails while a bare
// default passes.
func TestValidateRetention(t *testing.T) {
cfg := Default()
cfg.Storage.Retention.Default = "30d"
if err := cfg.Validate(); err != nil {
t.Errorf("Validate() with only a default = %v, want nil", err)
}
cfg.Storage.Retention.Ecosystems = map[string]string{"npm": "14d"}
err := cfg.Validate()
if err == nil || !strings.Contains(err.Error(), "not supported") {
t.Errorf("Validate() with an unsupported ecosystem = %v, want a not supported error", err)
}
}
func TestRetentionEnvOverrides(t *testing.T) {
t.Setenv("PROXY_STORAGE_RETENTION_DEFAULT", "21d")
t.Setenv("PROXY_STORAGE_RETENTION_SWEEP_INTERVAL", "1h")
cfg := Default()
cfg.LoadFromEnv()
if cfg.Storage.Retention.Default != "21d" {
t.Errorf("Retention.Default = %q, want 21d", cfg.Storage.Retention.Default)
}
if got := cfg.ParseRetentionSweepInterval(); got != time.Hour {
t.Errorf("ParseRetentionSweepInterval() = %v, want 1h", got)
}
}
func TestRetentionSweepIntervalDefault(t *testing.T) {
cfg := Default()
if got := cfg.ParseRetentionSweepInterval(); got != 10*time.Minute {
t.Errorf("ParseRetentionSweepInterval() = %v, want 10m", got)
}
}
func TestGradleBuildCacheMaxAgeAcceptsDays(t *testing.T) {
cfg := Default()
cfg.Gradle.BuildCache.MaxAge = "7d"
if err := cfg.Validate(); err != nil {
t.Fatalf("Validate() with max_age 7d = %v, want nil", err)
}
if got := cfg.ParseGradleBuildCacheMaxAge(); got != 7*retentionDay {
t.Errorf("ParseGradleBuildCacheMaxAge() = %v, want 168h", got)
}
}