Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-10 08:09:17 -05:00
pkg-proxy/internal/server/mirror_fetch_test.go
Simon Chrzanowski 3e2a84a88a Mirror Alpine packages
The PURL namespace names the upstream.apk repository, and the distro and
arch qualifiers syft writes give the release and architecture. The package
is looked up in the main and community indexes of that release and cached
under the path the APK handler serves it from.
2026-11-04 15:44:01 +01:00

151 lines
5.7 KiB
Go

package server
import (
"context"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"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/mirror"
"github.com/git-pkgs/proxy/internal/storage"
)
// newTarballUpstream serves body for path, compared case-insensitively since
// scoped npm names arrive with an escaped slash, and counts the requests.
func newTarballUpstream(t *testing.T, path, body string) (*httptest.Server, *atomic.Int32) {
t.Helper()
var hits atomic.Int32
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !strings.EqualFold(r.URL.EscapedPath(), path) && !strings.EqualFold(r.URL.Path, path) {
http.NotFound(w, r)
return
}
hits.Add(1)
_, _ = io.WriteString(w, body)
}))
t.Cleanup(upstream.Close)
return upstream, &hits
}
// mirrorWithConfig mirrors purls with the proxy proxy mirror builds for cfg.
func mirrorWithConfig(t *testing.T, cfg *config.Config, purls ...string) (*mirror.Progress, *handler.Proxy) {
t.Helper()
return mirrorWithVersions(t, cfg, nil, purls...)
}
// mirrorWithVersions mirrors purls, listing versions and files with versions,
// or from the configured upstreams when it is nil.
func mirrorWithVersions(t *testing.T, cfg *config.Config, versions mirror.VersionLister, purls ...string) (*mirror.Progress, *handler.Proxy) {
t.Helper()
cfg.Upstream.AllowLoopback = true
db, err := database.Create(t.TempDir() + "/test.db")
if err != nil {
t.Fatal(err)
}
if err := db.MigrateSchema(); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = db.Close() })
store, err := storage.OpenBucket(context.Background(), "file://"+t.TempDir())
if err != nil {
t.Fatal(err)
}
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
proxy, _ := NewMirrorProxy(cfg, db, store, logger)
proxy.CacheMetadata = true // as proxy mirror does
m := mirror.New(proxy, db, store, logger, 1)
m.Charts = handler.NewHelmHandler(proxy, cfg.BaseURL, cfg.Upstream.Helm)
m.Versions = versions
m.JuliaUpstream = cfg.Upstream.Julia
m.APKRepositories = cfg.Upstream.APK
if versions == nil {
m.Versions = NewMirrorVersions(cfg.Upstream, proxy.HTTPClient)
}
progress, err := m.Run(context.Background(), &mirror.PURLSource{PURLs: purls, Versions: m.Versions})
if err != nil {
t.Fatalf("Run() error = %v", err)
}
return progress, proxy
}
func TestMirrorFetchesFromConfiguredUpstream(t *testing.T) {
upstream, hits := newTarballUpstream(t, "/left-pad/-/left-pad-1.3.0.tgz", "tarball")
cfg := config.Default()
cfg.Upstream.NPM = upstream.URL
progress, _ := mirrorWithConfig(t, cfg, "pkg:npm/left-pad@1.3.0")
if progress.Completed != 1 || hits.Load() != 1 {
t.Errorf("completed = %d, upstream hits = %d, errors = %v; want the tarball from upstream.npm", progress.Completed, hits.Load(), progress.Errors)
}
}
func TestMirrorFetchesRoutedNPMPackagesFromTheirRoute(t *testing.T) {
const tarballPath = "/@acme/widget/-/widget-1.0.0.tgz"
public, publicHits := newTarballUpstream(t, tarballPath, "public")
private, privateHits := newTarballUpstream(t, tarballPath, "private")
cfg := config.Default()
cfg.Upstream.NPM = public.URL
cfg.Upstream.NPMRoutes = map[string]string{"@acme/*": private.URL}
progress, proxy := mirrorWithConfig(t, cfg, "pkg:npm/%40acme/widget@1.0.0")
if progress.Completed != 1 || privateHits.Load() != 1 {
t.Errorf("completed = %d, route hits = %d, errors = %v; want the tarball from the route", progress.Completed, privateHits.Load(), progress.Errors)
}
if publicHits.Load() != 0 {
t.Error("a routed package was mirrored from upstream.npm")
}
// A client gets the mirrored tarball from the cache once the route is down.
private.Close()
npm := handler.NewNPMHandler(proxy, "http://proxy.test", cfg.Upstream.NPM).WithPackageRoutes(cfg.Upstream.NPMRoutes)
rec := httptest.NewRecorder()
npm.Routes().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, tarballPath, nil))
if rec.Code != http.StatusOK || rec.Body.String() != "private" {
t.Errorf("tarball from cache: status = %d, body = %q, want 200 and the mirrored tarball", rec.Code, rec.Body)
}
}
func TestMirrorResolverBaseURLs(t *testing.T) {
u := config.Default().Upstream
u.NPM = "https://npm.example.com/"
u.CargoDownload = "https://crates.example.com/crates"
u.Gem = "https://gems.example.com"
u.Go = "https://goproxy.example.com"
u.Hex = "https://hex.example.com"
u.Pub = "https://pub.example.com"
u.Maven = "https://maven.example.com/maven2"
u.NuGet = "https://nuget.example.com"
resolver := newMirrorResolver(u)
tests := []struct {
ecosystem, name, version, want string
}{
{"npm", "left-pad", "1.3.0", "https://npm.example.com/left-pad/-/left-pad-1.3.0.tgz"},
{"cargo", "serde", "1.0.0", "https://crates.example.com/crates/serde/serde-1.0.0.crate"},
{"gem", "rake", "13.2.1", "https://gems.example.com/downloads/rake-13.2.1.gem"},
{"golang", "golang.org/x/mod", "v0.20.0", "https://goproxy.example.com/golang.org/x/mod/@v/v0.20.0.zip"},
{"hex", "jason", "1.4.4", "https://hex.example.com/tarballs/jason-1.4.4.tar"},
{"pub", "http", "1.2.2", "https://pub.example.com/packages/http/versions/1.2.2.tar.gz"},
{"maven", "org.example:lib", "1.0", "https://maven.example.com/maven2/org/example/lib/1.0/lib-1.0.jar"},
{"nuget", "Newtonsoft.Json", "13.0.3", "https://nuget.example.com/v3-flatcontainer/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg"},
}
for _, tt := range tests {
info, err := resolver.Resolve(context.Background(), tt.ecosystem, tt.name, tt.version)
if err != nil {
t.Errorf("%s: Resolve() error = %v", tt.ecosystem, err)
continue
}
if info.URL != tt.want {
t.Errorf("%s: URL = %q, want %q", tt.ecosystem, info.URL, tt.want)
}
}
}