mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-10 08:09:17 -05:00
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.
151 lines
5.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|