2026-01-29 19:35:15 +00:00
|
|
|
package handler
|
|
|
|
|
|
|
|
|
|
import (
|
feat(debian): serve additional APT archives at /debian/{name}/
A Debian release is served by more than one archive. Security updates live on
a different host than the main archive, so a single upstream.debian URL cannot
serve a complete suite set and -security suites are unreachable.
Add upstream.debian_repositories, a name-to-URL map served at /debian/{name}/,
modelled on upstream.apk. The field is additive: upstream.debian keeps serving
/debian/pool/... and /debian/dists/... with unchanged cache identities, so
existing deployments and their warm caches are unaffected, and the scalar
field and PROXY_UPSTREAM_DEBIAN are untouched.
Named repositories scope both caches by name, since the same filename can hold
different bytes in different archives. Metadata keys are hashed over name,
upstream URL, and path, as APKHandler does. Names are validated through
validateNamedUpstreams, and "pool" and "dists" are refused because they would
shadow the main archive's own paths.
An unconfigured first path segment stays a main-archive path rather than
returning 404 as the APK handler does: the main archive is unnamed and serves
paths of its own at the root.
2026-09-14 15:41:13 -07:00
|
|
|
"fmt"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"strings"
|
2026-01-29 19:35:15 +00:00
|
|
|
"testing"
|
feat(debian): serve additional APT archives at /debian/{name}/
A Debian release is served by more than one archive. Security updates live on
a different host than the main archive, so a single upstream.debian URL cannot
serve a complete suite set and -security suites are unreachable.
Add upstream.debian_repositories, a name-to-URL map served at /debian/{name}/,
modelled on upstream.apk. The field is additive: upstream.debian keeps serving
/debian/pool/... and /debian/dists/... with unchanged cache identities, so
existing deployments and their warm caches are unaffected, and the scalar
field and PROXY_UPSTREAM_DEBIAN are untouched.
Named repositories scope both caches by name, since the same filename can hold
different bytes in different archives. Metadata keys are hashed over name,
upstream URL, and path, as APKHandler does. Names are validated through
validateNamedUpstreams, and "pool" and "dists" are refused because they would
shadow the main archive's own paths.
An unconfigured first path segment stays a main-archive path rather than
returning 404 as the APK handler does: the main archive is unnamed and serves
paths of its own at the root.
2026-09-14 15:41:13 -07:00
|
|
|
"time"
|
|
|
|
|
|
2026-10-11 01:12:41 +02:00
|
|
|
"github.com/git-pkgs/purl"
|
feat(debian): serve additional APT archives at /debian/{name}/
A Debian release is served by more than one archive. Security updates live on
a different host than the main archive, so a single upstream.debian URL cannot
serve a complete suite set and -security suites are unreachable.
Add upstream.debian_repositories, a name-to-URL map served at /debian/{name}/,
modelled on upstream.apk. The field is additive: upstream.debian keeps serving
/debian/pool/... and /debian/dists/... with unchanged cache identities, so
existing deployments and their warm caches are unaffected, and the scalar
field and PROXY_UPSTREAM_DEBIAN are untouched.
Named repositories scope both caches by name, since the same filename can hold
different bytes in different archives. Metadata keys are hashed over name,
upstream URL, and path, as APKHandler does. Names are validated through
validateNamedUpstreams, and "pool" and "dists" are refused because they would
shadow the main archive's own paths.
An unconfigured first path segment stays a main-archive path rather than
returning 404 as the APK handler does: the main archive is unnamed and serves
paths of its own at the root.
2026-09-14 15:41:13 -07:00
|
|
|
"github.com/git-pkgs/registries/fetch"
|
2026-01-29 19:35:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestDebianHandler_parsePoolPath(t *testing.T) {
|
|
|
|
|
h := &DebianHandler{}
|
|
|
|
|
|
2026-03-18 10:59:29 +00:00
|
|
|
assertPathParser(t, "parsePoolPath", h.parsePoolPath, []pathParseCase{
|
|
|
|
|
{"pool/main/n/nginx/nginx_1.18.0-6_amd64.deb", "nginx", "1.18.0-6", "amd64"},
|
|
|
|
|
{"pool/main/libn/libncurses/libncurses6_6.2-1_amd64.deb", "libncurses6", "6.2-1", "amd64"},
|
|
|
|
|
{"pool/contrib/v/virtualbox/virtualbox_6.1.38-1_amd64.deb", "virtualbox", "6.1.38-1", "amd64"},
|
|
|
|
|
{"pool/main/g/git/git_2.39.2-1_arm64.deb", "git", "2.39.2-1", "arm64"},
|
2026-08-14 11:38:08 +02:00
|
|
|
{
|
|
|
|
|
"pool/universe/n/nmap/nmap_7.91+dfsg1+really7.80+dfsg1-2ubuntu0.1_amd64.deb",
|
|
|
|
|
"nmap", "7.91+dfsg1+really7.80+dfsg1-2ubuntu0.1", "amd64",
|
|
|
|
|
},
|
|
|
|
|
{"pool/main/o/openssl/openssl_3.0.2-0ubuntu1.15~build1_amd64.deb", "openssl", "3.0.2-0ubuntu1.15~build1", "amd64"},
|
2026-03-18 10:59:29 +00:00
|
|
|
{"invalid/path", "", "", ""},
|
|
|
|
|
{"pool/main/n/nginx/nginx.deb", "", "", ""},
|
|
|
|
|
})
|
2026-01-29 19:35:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestDebianHandler_Routes(t *testing.T) {
|
feat(debian): serve additional APT archives at /debian/{name}/
A Debian release is served by more than one archive. Security updates live on
a different host than the main archive, so a single upstream.debian URL cannot
serve a complete suite set and -security suites are unreachable.
Add upstream.debian_repositories, a name-to-URL map served at /debian/{name}/,
modelled on upstream.apk. The field is additive: upstream.debian keeps serving
/debian/pool/... and /debian/dists/... with unchanged cache identities, so
existing deployments and their warm caches are unaffected, and the scalar
field and PROXY_UPSTREAM_DEBIAN are untouched.
Named repositories scope both caches by name, since the same filename can hold
different bytes in different archives. Metadata keys are hashed over name,
upstream URL, and path, as APKHandler does. Names are validated through
validateNamedUpstreams, and "pool" and "dists" are refused because they would
shadow the main archive's own paths.
An unconfigured first path segment stays a main-archive path rather than
returning 404 as the APK handler does: the main archive is unnamed and serves
paths of its own at the root.
2026-09-14 15:41:13 -07:00
|
|
|
h := NewDebianHandler(nil, "http://localhost:8080", "", nil)
|
2026-03-18 10:59:29 +00:00
|
|
|
assertRoutesBasics(t, h.Routes(), "/dists/stable/Release", "/pool/../../../etc/passwd")
|
2026-01-29 19:35:15 +00:00
|
|
|
}
|
feat(debian): serve additional APT archives at /debian/{name}/
A Debian release is served by more than one archive. Security updates live on
a different host than the main archive, so a single upstream.debian URL cannot
serve a complete suite set and -security suites are unreachable.
Add upstream.debian_repositories, a name-to-URL map served at /debian/{name}/,
modelled on upstream.apk. The field is additive: upstream.debian keeps serving
/debian/pool/... and /debian/dists/... with unchanged cache identities, so
existing deployments and their warm caches are unaffected, and the scalar
field and PROXY_UPSTREAM_DEBIAN are untouched.
Named repositories scope both caches by name, since the same filename can hold
different bytes in different archives. Metadata keys are hashed over name,
upstream URL, and path, as APKHandler does. Names are validated through
validateNamedUpstreams, and "pool" and "dists" are refused because they would
shadow the main archive's own paths.
An unconfigured first path segment stays a main-archive path rather than
returning 404 as the APK handler does: the main archive is unnamed and serves
paths of its own at the root.
2026-09-14 15:41:13 -07:00
|
|
|
|
|
|
|
|
// TestDebianHandler_LegacyCacheKeysUnchanged pins the cache identities the
|
|
|
|
|
// main archive used before named repositories existed. Deployments carry warm
|
2026-09-16 10:15:59 -07:00
|
|
|
// caches across upgrades, so a changed key here silently discards them.
|
feat(debian): serve additional APT archives at /debian/{name}/
A Debian release is served by more than one archive. Security updates live on
a different host than the main archive, so a single upstream.debian URL cannot
serve a complete suite set and -security suites are unreachable.
Add upstream.debian_repositories, a name-to-URL map served at /debian/{name}/,
modelled on upstream.apk. The field is additive: upstream.debian keeps serving
/debian/pool/... and /debian/dists/... with unchanged cache identities, so
existing deployments and their warm caches are unaffected, and the scalar
field and PROXY_UPSTREAM_DEBIAN are untouched.
Named repositories scope both caches by name, since the same filename can hold
different bytes in different archives. Metadata keys are hashed over name,
upstream URL, and path, as APKHandler does. Names are validated through
validateNamedUpstreams, and "pool" and "dists" are refused because they would
shadow the main archive's own paths.
An unconfigured first path segment stays a main-archive path rather than
returning 404 as the APK handler does: the main archive is unnamed and serves
paths of its own at the root.
2026-09-14 15:41:13 -07:00
|
|
|
func TestDebianHandler_LegacyCacheKeysUnchanged(t *testing.T) {
|
|
|
|
|
const (
|
|
|
|
|
poolPath = "pool/main/h/hello/hello_2.10-3_amd64.deb"
|
|
|
|
|
distPath = "dists/trixie/InRelease"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
h := NewDebianHandler(nil, "http://proxy.example", "https://archive.test", map[string]string{
|
|
|
|
|
"security": "https://security.test",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Artifact cache: the bare filename, with no repository prefix.
|
|
|
|
|
filename := poolPath[strings.LastIndex(poolPath, "/")+1:]
|
|
|
|
|
if got := h.artifactCacheFilename("", filename); got != "hello_2.10-3_amd64.deb" {
|
|
|
|
|
t.Errorf("legacy artifact cache filename = %q, want %q", got, "hello_2.10-3_amd64.deb")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Metadata cache: path separators replaced with underscores.
|
|
|
|
|
if got := h.metadataCacheKeyFor("", h.upstreamURL, distPath); got != "dists_trixie_InRelease" {
|
|
|
|
|
t.Errorf("legacy metadata cache key = %q, want %q", got, "dists_trixie_InRelease")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A named repository must not reuse either identity.
|
|
|
|
|
if got := h.artifactCacheFilename("security", filename); got == "hello_2.10-3_amd64.deb" {
|
|
|
|
|
t.Error("named repository reused the legacy artifact cache filename")
|
|
|
|
|
}
|
|
|
|
|
if got := h.metadataCacheKeyFor("security", "https://security.test", distPath); got == "dists_trixie_InRelease" {
|
|
|
|
|
t.Error("named repository reused the legacy metadata cache key")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestDebianHandler_NamedRepositoryRouting checks that a named repository
|
|
|
|
|
// reaches its own archive while the main archive keeps serving /dists/ and
|
2026-09-16 10:15:59 -07:00
|
|
|
// /pool/ unchanged.
|
feat(debian): serve additional APT archives at /debian/{name}/
A Debian release is served by more than one archive. Security updates live on
a different host than the main archive, so a single upstream.debian URL cannot
serve a complete suite set and -security suites are unreachable.
Add upstream.debian_repositories, a name-to-URL map served at /debian/{name}/,
modelled on upstream.apk. The field is additive: upstream.debian keeps serving
/debian/pool/... and /debian/dists/... with unchanged cache identities, so
existing deployments and their warm caches are unaffected, and the scalar
field and PROXY_UPSTREAM_DEBIAN are untouched.
Named repositories scope both caches by name, since the same filename can hold
different bytes in different archives. Metadata keys are hashed over name,
upstream URL, and path, as APKHandler does. Names are validated through
validateNamedUpstreams, and "pool" and "dists" are refused because they would
shadow the main archive's own paths.
An unconfigured first path segment stays a main-archive path rather than
returning 404 as the APK handler does: the main archive is unnamed and serves
paths of its own at the root.
2026-09-14 15:41:13 -07:00
|
|
|
func TestDebianHandler_NamedRepositoryRouting(t *testing.T) {
|
|
|
|
|
mainRelease := "main archive InRelease"
|
|
|
|
|
mainArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
if r.URL.Path != "/dists/trixie/InRelease" {
|
|
|
|
|
http.NotFound(w, r)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
_, _ = fmt.Fprint(w, mainRelease)
|
|
|
|
|
}))
|
|
|
|
|
defer mainArchive.Close()
|
|
|
|
|
|
|
|
|
|
securityRelease := "security archive InRelease"
|
|
|
|
|
securityArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
if r.URL.Path != "/dists/trixie-security/InRelease" {
|
|
|
|
|
http.NotFound(w, r)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
_, _ = fmt.Fprint(w, securityRelease)
|
|
|
|
|
}))
|
|
|
|
|
defer securityArchive.Close()
|
|
|
|
|
|
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
|
|
|
proxy.CacheMetadata = true
|
|
|
|
|
proxy.MetadataTTL = time.Hour
|
|
|
|
|
proxy.HTTPClient = http.DefaultClient
|
|
|
|
|
|
|
|
|
|
h := NewDebianHandler(proxy, "http://proxy.example", mainArchive.URL, map[string]string{
|
|
|
|
|
"security": securityArchive.URL,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
got := serveDebianRequest(h, "/security/dists/trixie-security/InRelease")
|
|
|
|
|
if got.Code != http.StatusOK || got.Body.String() != securityRelease {
|
|
|
|
|
t.Errorf("named repository: status = %d, body = %q, want 200 %q",
|
|
|
|
|
got.Code, got.Body.String(), securityRelease)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
got = serveDebianRequest(h, "/dists/trixie/InRelease")
|
|
|
|
|
if got.Code != http.StatusOK || got.Body.String() != mainRelease {
|
|
|
|
|
t.Errorf("main archive: status = %d, body = %q, want 200 %q",
|
|
|
|
|
got.Code, got.Body.String(), mainRelease)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// An unconfigured name is not a repository, so it addresses the main
|
2026-09-16 10:15:59 -07:00
|
|
|
// archive as a plain path.
|
feat(debian): serve additional APT archives at /debian/{name}/
A Debian release is served by more than one archive. Security updates live on
a different host than the main archive, so a single upstream.debian URL cannot
serve a complete suite set and -security suites are unreachable.
Add upstream.debian_repositories, a name-to-URL map served at /debian/{name}/,
modelled on upstream.apk. The field is additive: upstream.debian keeps serving
/debian/pool/... and /debian/dists/... with unchanged cache identities, so
existing deployments and their warm caches are unaffected, and the scalar
field and PROXY_UPSTREAM_DEBIAN are untouched.
Named repositories scope both caches by name, since the same filename can hold
different bytes in different archives. Metadata keys are hashed over name,
upstream URL, and path, as APKHandler does. Names are validated through
validateNamedUpstreams, and "pool" and "dists" are refused because they would
shadow the main archive's own paths.
An unconfigured first path segment stays a main-archive path rather than
returning 404 as the APK handler does: the main archive is unnamed and serves
paths of its own at the root.
2026-09-14 15:41:13 -07:00
|
|
|
got = serveDebianRequest(h, "/unknown/dists/trixie/InRelease")
|
|
|
|
|
if got.Code == http.StatusOK {
|
|
|
|
|
t.Errorf("unknown repository: status = 200, want the main archive's 404")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-18 16:48:18 -07:00
|
|
|
// TestDebianHandler_UnknownRepositoryReportsConfiguredNames covers a misspelled
|
|
|
|
|
// repository name. {name}/dists/ is never a main-archive path, so the handler
|
|
|
|
|
// answers directly rather than forwarding upstream, where the reply would be an
|
|
|
|
|
// opaque HTML 404 that does not mention the repository at all.
|
|
|
|
|
func TestDebianHandler_UnknownRepositoryReportsConfiguredNames(t *testing.T) {
|
|
|
|
|
var upstreamHits int
|
|
|
|
|
mainArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
|
|
|
upstreamHits++
|
|
|
|
|
http.Error(w, "<html>404 Not Found</html>", http.StatusNotFound)
|
|
|
|
|
}))
|
|
|
|
|
defer mainArchive.Close()
|
|
|
|
|
|
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
|
|
|
proxy.HTTPClient = http.DefaultClient
|
|
|
|
|
|
|
|
|
|
h := NewDebianHandler(proxy, "http://proxy.example", mainArchive.URL, map[string]string{
|
|
|
|
|
"security": "http://security.example",
|
|
|
|
|
"ghcli": "http://ghcli.example",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
got := serveDebianRequest(h, "/secuirty/dists/trixie-security/InRelease")
|
|
|
|
|
if got.Code != http.StatusNotFound {
|
|
|
|
|
t.Errorf("status = %d, want %d", got.Code, http.StatusNotFound)
|
|
|
|
|
}
|
|
|
|
|
body := got.Body.String()
|
|
|
|
|
if !strings.Contains(body, `"secuirty"`) {
|
|
|
|
|
t.Errorf("body = %q, want it to name the misspelled repository", body)
|
|
|
|
|
}
|
|
|
|
|
// Sorted, so the message is stable across map iteration order.
|
|
|
|
|
if !strings.Contains(body, "ghcli, security") {
|
|
|
|
|
t.Errorf("body = %q, want it to list the configured repositories", body)
|
|
|
|
|
}
|
|
|
|
|
if upstreamHits != 0 {
|
|
|
|
|
t.Errorf("upstream hits = %d, want 0: the handler should answer without forwarding", upstreamHits)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A root-level main-archive path still falls through, since the main
|
|
|
|
|
// archive really does serve files of its own there.
|
|
|
|
|
got = serveDebianRequest(h, "/project/trace/README")
|
|
|
|
|
if got.Code != http.StatusNotFound || upstreamHits != 1 {
|
|
|
|
|
t.Errorf("main-archive path: status = %d, upstream hits = %d, want 404 and 1",
|
|
|
|
|
got.Code, upstreamHits)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(debian): serve additional APT archives at /debian/{name}/
A Debian release is served by more than one archive. Security updates live on
a different host than the main archive, so a single upstream.debian URL cannot
serve a complete suite set and -security suites are unreachable.
Add upstream.debian_repositories, a name-to-URL map served at /debian/{name}/,
modelled on upstream.apk. The field is additive: upstream.debian keeps serving
/debian/pool/... and /debian/dists/... with unchanged cache identities, so
existing deployments and their warm caches are unaffected, and the scalar
field and PROXY_UPSTREAM_DEBIAN are untouched.
Named repositories scope both caches by name, since the same filename can hold
different bytes in different archives. Metadata keys are hashed over name,
upstream URL, and path, as APKHandler does. Names are validated through
validateNamedUpstreams, and "pool" and "dists" are refused because they would
shadow the main archive's own paths.
An unconfigured first path segment stays a main-archive path rather than
returning 404 as the APK handler does: the main archive is unnamed and serves
paths of its own at the root.
2026-09-14 15:41:13 -07:00
|
|
|
// TestDebianHandler_MetadataCacheKeysDoNotCollideAcrossRepositories guards the
|
|
|
|
|
// hashed metadata cache key. The main archive's key replaces '/' with '_', so
|
|
|
|
|
// a repository named "security" serving dists/trixie/InRelease would otherwise
|
2026-09-16 10:15:59 -07:00
|
|
|
// collide with the main archive's own security_dists_trixie_InRelease entry,
|
|
|
|
|
// serving one archive's signed metadata to clients of the other.
|
feat(debian): serve additional APT archives at /debian/{name}/
A Debian release is served by more than one archive. Security updates live on
a different host than the main archive, so a single upstream.debian URL cannot
serve a complete suite set and -security suites are unreachable.
Add upstream.debian_repositories, a name-to-URL map served at /debian/{name}/,
modelled on upstream.apk. The field is additive: upstream.debian keeps serving
/debian/pool/... and /debian/dists/... with unchanged cache identities, so
existing deployments and their warm caches are unaffected, and the scalar
field and PROXY_UPSTREAM_DEBIAN are untouched.
Named repositories scope both caches by name, since the same filename can hold
different bytes in different archives. Metadata keys are hashed over name,
upstream URL, and path, as APKHandler does. Names are validated through
validateNamedUpstreams, and "pool" and "dists" are refused because they would
shadow the main archive's own paths.
An unconfigured first path segment stays a main-archive path rather than
returning 404 as the APK handler does: the main archive is unnamed and serves
paths of its own at the root.
2026-09-14 15:41:13 -07:00
|
|
|
func TestDebianHandler_MetadataCacheKeysDoNotCollideAcrossRepositories(t *testing.T) {
|
|
|
|
|
mainRelease := "main archive InRelease"
|
|
|
|
|
mainArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
_, _ = fmt.Fprint(w, mainRelease)
|
|
|
|
|
}))
|
|
|
|
|
defer mainArchive.Close()
|
|
|
|
|
|
|
|
|
|
securityRelease := "security archive InRelease"
|
|
|
|
|
securityArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
_, _ = fmt.Fprint(w, securityRelease)
|
|
|
|
|
}))
|
|
|
|
|
defer securityArchive.Close()
|
|
|
|
|
|
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
|
|
|
proxy.CacheMetadata = true
|
|
|
|
|
proxy.MetadataTTL = time.Hour
|
|
|
|
|
proxy.HTTPClient = http.DefaultClient
|
|
|
|
|
|
|
|
|
|
h := NewDebianHandler(proxy, "http://proxy.example", mainArchive.URL, map[string]string{
|
|
|
|
|
"security": securityArchive.URL,
|
|
|
|
|
})
|
|
|
|
|
|
2026-09-16 10:18:56 -07:00
|
|
|
// Main archive path whose separator-based key is "security_dists_...".
|
feat(debian): serve additional APT archives at /debian/{name}/
A Debian release is served by more than one archive. Security updates live on
a different host than the main archive, so a single upstream.debian URL cannot
serve a complete suite set and -security suites are unreachable.
Add upstream.debian_repositories, a name-to-URL map served at /debian/{name}/,
modelled on upstream.apk. The field is additive: upstream.debian keeps serving
/debian/pool/... and /debian/dists/... with unchanged cache identities, so
existing deployments and their warm caches are unaffected, and the scalar
field and PROXY_UPSTREAM_DEBIAN are untouched.
Named repositories scope both caches by name, since the same filename can hold
different bytes in different archives. Metadata keys are hashed over name,
upstream URL, and path, as APKHandler does. Names are validated through
validateNamedUpstreams, and "pool" and "dists" are refused because they would
shadow the main archive's own paths.
An unconfigured first path segment stays a main-archive path rather than
returning 404 as the APK handler does: the main archive is unnamed and serves
paths of its own at the root.
2026-09-14 15:41:13 -07:00
|
|
|
first := serveDebianRequest(h, "/dists/security/dists/trixie/InRelease")
|
|
|
|
|
if first.Code != http.StatusOK || first.Body.String() != mainRelease {
|
|
|
|
|
t.Fatalf("main archive: status = %d, body = %q, want 200 %q",
|
|
|
|
|
first.Code, first.Body.String(), mainRelease)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Served within the metadata TTL: a colliding key would return
|
|
|
|
|
// mainRelease here.
|
|
|
|
|
second := serveDebianRequest(h, "/security/dists/trixie/InRelease")
|
|
|
|
|
if second.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("named repository: status = %d, want 200: %s", second.Code, second.Body.String())
|
|
|
|
|
}
|
|
|
|
|
if second.Body.String() != securityRelease {
|
|
|
|
|
t.Errorf("named repository served %q, want %q (cache key collision)",
|
|
|
|
|
second.Body.String(), securityRelease)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestDebianHandler_PackageCacheScopedPerRepository checks that the same
|
|
|
|
|
// package filename in two archives does not resolve to one cached artifact.
|
|
|
|
|
func TestDebianHandler_PackageCacheScopedPerRepository(t *testing.T) {
|
|
|
|
|
const pkgPath = "/pool/main/h/hello/hello_2.10-3_amd64.deb"
|
|
|
|
|
|
|
|
|
|
mainPkg := "main archive package bytes"
|
|
|
|
|
mainArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
_, _ = fmt.Fprint(w, mainPkg)
|
|
|
|
|
}))
|
|
|
|
|
defer mainArchive.Close()
|
|
|
|
|
|
|
|
|
|
securityPkg := "security archive package bytes"
|
|
|
|
|
securityArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
_, _ = fmt.Fprint(w, securityPkg)
|
|
|
|
|
}))
|
|
|
|
|
defer securityArchive.Close()
|
|
|
|
|
|
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
|
|
|
fetcher := fetch.NewFetcher(fetch.WithHTTPClient(mainArchive.Client()), fetch.WithMaxRetries(0))
|
|
|
|
|
proxy.Fetcher = fetcher
|
|
|
|
|
t.Cleanup(func() { _ = fetcher.Close() })
|
|
|
|
|
|
|
|
|
|
h := NewDebianHandler(proxy, "http://proxy.example", mainArchive.URL, map[string]string{
|
|
|
|
|
"security": securityArchive.URL,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
first := serveDebianRequest(h, pkgPath)
|
|
|
|
|
if first.Code != http.StatusOK || first.Body.String() != mainPkg {
|
|
|
|
|
t.Fatalf("main archive: status = %d, body = %q, want 200 %q",
|
|
|
|
|
first.Code, first.Body.String(), mainPkg)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
second := serveDebianRequest(h, "/security"+pkgPath)
|
|
|
|
|
if second.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("named repository: status = %d, want 200: %s", second.Code, second.Body.String())
|
|
|
|
|
}
|
|
|
|
|
if second.Body.String() != securityPkg {
|
|
|
|
|
t.Errorf("named repository served %q, want %q (artifact cache collision)",
|
|
|
|
|
second.Body.String(), securityPkg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func serveDebianRequest(h *DebianHandler, target string) *httptest.ResponseRecorder {
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
|
h.Routes().ServeHTTP(w, httptest.NewRequest(http.MethodGet, target, nil))
|
|
|
|
|
return w
|
|
|
|
|
}
|
2026-10-11 01:12:41 +02:00
|
|
|
|
|
|
|
|
func TestDebRetentionKey(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
key string
|
|
|
|
|
want string
|
|
|
|
|
ok bool
|
|
|
|
|
}{
|
|
|
|
|
{"pkg:deb/curl", "pkg:deb/curl", true},
|
|
|
|
|
{"pkg:deb/debian/curl", "pkg:deb/curl", true},
|
|
|
|
|
{"pkg:deb/ubuntu/Curl", "pkg:deb/curl", true},
|
|
|
|
|
{"pkg:deb/debian/libstdc++6", "pkg:deb/libstdc%2B%2B6", true},
|
|
|
|
|
{"pkg:deb/debian/main/curl", "", false},
|
|
|
|
|
{"pkg:deb/debian/pool%2Fcurl", "", false},
|
|
|
|
|
{"pkg:apk/debian/curl", "", false},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
p, err := purl.Parse(tt.key)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Parse(%s): %v", tt.key, err)
|
|
|
|
|
}
|
|
|
|
|
got, ok := debRetentionKey(p)
|
|
|
|
|
if ok != tt.ok || (ok && got != tt.want) {
|
|
|
|
|
t.Errorf("debRetentionKey(%s) = %q, %v, want %q, %v", tt.key, got, ok, tt.want, tt.ok)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if _, ok := debRetentionKey(nil); ok {
|
|
|
|
|
t.Error("debRetentionKey(nil) accepted")
|
|
|
|
|
}
|
|
|
|
|
}
|