Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 15:49:16 -05:00
pkg-proxy/internal/server/mirror_julia_test.go
Simon Chrzanowski 3f83d9c0ce Mirror Julia packages
proxy mirror and mirror jobs now take pkg:julia PURLs with the uuid
qualifier. The tree hash of the version comes from the General registry,
after checking the uuid is the one General lists for the name, and the
tarball is fetched from upstream.julia at /package/{uuid}/{tree-hash} and
cached under the name and tree hash the Julia handler uses.

The handler names a package from the registry's Registry.toml, which it
loads from the upstream. Without the upstream it fell back to caching and
looking up by uuid, so a package cached under its name, mirrored or
fetched before a restart, was not served offline. It now looks the
tarball up by its tree hash in that case.

Julia versions are listed from General whatever upstream.julia is, as
packages are identified by uuid and tree hash on every Pkg server serving
General.
2026-10-25 19:37:46 +01:00

162 lines
6 KiB
Go

package server
import (
"archive/tar"
"bytes"
"compress/gzip"
"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/handler"
"github.com/git-pkgs/proxy/internal/mirror"
"github.com/git-pkgs/registries"
)
const (
juliaExampleUUID = "7876af07-990d-54b4-ab0e-23690620f79a"
juliaExampleTree = "e1f0e1a832ccd8e97d6d0348dec33ee139a5aeaf"
juliaGeneralUUID = "23338594-aafe-5451-b93e-139f81909106"
juliaRegistryTree = "465d1e55da75e226473a37c40bf2c20aee19234d"
)
// juliaUpstream serves the General registry's files for Example, which has
// version 0.5.5, and as a Pkg server /registries, the General registry
// tarball and Example's 0.5.5 source tarball. It counts package downloads.
type juliaUpstream struct {
*httptest.Server
tarball string
packageHits atomic.Int32
registryHits atomic.Int32
}
func newJuliaUpstream(t *testing.T) *juliaUpstream {
t.Helper()
u := &juliaUpstream{tarball: "Example 0.5.5 source"}
registry := juliaRegistryTarball(t, "[packages]\n"+juliaExampleUUID+` = { name = "Example", path = "E/Example" }`+"\n")
u.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/E/Example/Package.toml":
_, _ = io.WriteString(w, "name = \"Example\"\nuuid = \""+juliaExampleUUID+"\"\nrepo = \"https://github.com/JuliaLang/Example.jl.git\"\n")
case "/E/Example/Versions.toml":
_, _ = io.WriteString(w, "[\"0.5.4\"]\ngit-tree-sha1 = \"11820aa9c229fd3833d4bd69e5e75ef4e7273bf1\"\n\n[\"0.5.5\"]\ngit-tree-sha1 = \""+juliaExampleTree+"\"\n")
case "/registries":
_, _ = io.WriteString(w, "/registry/"+juliaGeneralUUID+"/"+juliaRegistryTree+"\n")
case "/registry/" + juliaGeneralUUID + "/" + juliaRegistryTree:
u.registryHits.Add(1)
_, _ = w.Write(registry)
case "/package/" + juliaExampleUUID + "/" + juliaExampleTree:
u.packageHits.Add(1)
_, _ = io.WriteString(w, u.tarball)
default:
http.NotFound(w, r)
}
}))
t.Cleanup(u.Close)
return u
}
// juliaRegistryTarball returns a gzipped registry tarball holding registryToml.
func juliaRegistryTarball(t *testing.T, registryToml string) []byte {
t.Helper()
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
tw := tar.NewWriter(gz)
if err := tw.WriteHeader(&tar.Header{Name: "Registry.toml", Mode: 0o644, Size: int64(len(registryToml))}); err != nil {
t.Fatal(err)
}
if _, err := io.WriteString(tw, registryToml); err != nil {
t.Fatal(err)
}
if err := tw.Close(); err != nil {
t.Fatal(err)
}
if err := gz.Close(); err != nil {
t.Fatal(err)
}
return buf.Bytes()
}
// mirrorJulia mirrors purls from upstream as the Pkg server, with the
// registry's files read from it too.
func mirrorJulia(t *testing.T, upstream *juliaUpstream, purls ...string) (*mirror.Progress, *handler.Proxy) {
t.Helper()
cfg := config.Default()
cfg.Upstream.Julia = upstream.URL
versions := mirror.RegistryVersions{
Client: registries.NewClient(registries.WithHTTPClient(upstream.Client())),
URL: func(string, string) (string, error) { return upstream.URL, nil },
}
return mirrorWithVersions(t, cfg, versions, purls...)
}
func getJuliaPackage(julia *handler.JuliaHandler) *httptest.ResponseRecorder {
rec := httptest.NewRecorder()
julia.Routes().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/package/"+juliaExampleUUID+"/"+juliaExampleTree, nil))
return rec
}
func TestMirrorJuliaPackage(t *testing.T) {
upstream := newJuliaUpstream(t)
progress, proxy := mirrorJulia(t, upstream, "pkg:julia/Example@0.5.5?uuid="+juliaExampleUUID)
if progress.Completed != 1 || progress.Failed != 0 || upstream.packageHits.Load() != 1 {
t.Fatalf("completed = %d, failed = %d, package hits = %d, errors = %v", progress.Completed, progress.Failed, upstream.packageHits.Load(), progress.Errors)
}
if cached, _ := proxy.GetCachedArtifact(t.Context(), "julia", "Example", juliaExampleTree, juliaExampleTree+".tar.gz"); cached == nil {
t.Fatal("the tarball isn't cached under Example and its tree hash")
} else {
_ = cached.Reader.Close()
}
// With the registry's names loaded, the handler finds the tarball under
// the package's name without fetching it again.
julia := handler.NewJuliaHandlerWithUpstream(proxy, upstream.URL)
if rec := getJuliaPackage(julia); rec.Code != http.StatusOK || rec.Body.String() != upstream.tarball {
t.Errorf("status = %d, body = %q, want 200 and the tarball", rec.Code, rec.Body)
}
if upstream.registryHits.Load() == 0 {
t.Error("the handler didn't load the registry's names")
}
// Without the upstream, a new handler can't load the names, and finds
// the tarball by its tree hash.
upstream.Close()
if rec := getJuliaPackage(handler.NewJuliaHandlerWithUpstream(proxy, upstream.URL)); rec.Code != http.StatusOK || rec.Body.String() != upstream.tarball {
t.Errorf("from cache: status = %d, body = %q, want 200 and the tarball", rec.Code, rec.Body)
}
if got := upstream.packageHits.Load(); got != 1 {
t.Errorf("package fetched %d times, want 1", got)
}
}
func TestMirrorJuliaRefusesOtherUUID(t *testing.T) {
upstream := newJuliaUpstream(t)
progress, _ := mirrorJulia(t, upstream, "pkg:julia/Example@0.5.5?uuid=00000000-0000-0000-0000-000000000000")
if progress.Failed != 1 || len(progress.Errors) != 1 || !strings.Contains(progress.Errors[0].Error, juliaExampleUUID) {
t.Errorf("failed = %d, errors = %v; want a failure naming General's uuid", progress.Failed, progress.Errors)
}
if upstream.packageHits.Load() != 0 {
t.Error("a tarball was fetched for a package General doesn't have")
}
}
func TestServerMirrorFetchesJuliaFromUpstream(t *testing.T) {
cfg := config.Default()
cfg.Upstream.Julia = "https://julia.example.com"
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
proxy, _ := NewMirrorProxy(cfg, nil, nil, logger)
s := &Server{cfg: cfg, logger: logger, ecr: newECRTokens(logger)}
if got := s.newMirror(proxy, 1).JuliaUpstream; got != cfg.Upstream.Julia {
t.Errorf("JuliaUpstream = %q, want upstream.julia", got)
}
}