mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-09 15:49:16 -05:00
174 lines
6.1 KiB
Go
174 lines
6.1 KiB
Go
package server
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/git-pkgs/proxy/internal/config"
|
|
"github.com/git-pkgs/proxy/internal/handler"
|
|
)
|
|
|
|
const (
|
|
apkBusyboxPath = "/v3.20/main/x86_64/busybox-1.36.1-r29.apk"
|
|
apkHtopPath = "/v3.20/community/x86_64/htop-3.3.0-r0.apk"
|
|
apkMainIndex = "/v3.20/main/x86_64/APKINDEX.tar.gz"
|
|
apkCommIndex = "/v3.20/community/x86_64/APKINDEX.tar.gz"
|
|
)
|
|
|
|
// apkUpstream serves an Alpine mirror with busybox in v3.20/main and htop in
|
|
// v3.20/community, for x86_64. It counts requests by path.
|
|
type apkUpstream struct {
|
|
*httptest.Server
|
|
mu sync.Mutex
|
|
files map[string]string
|
|
hits map[string]int
|
|
}
|
|
|
|
func apkIndexArchive(t *testing.T, index string) string {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
zw := gzip.NewWriter(&buf)
|
|
tw := tar.NewWriter(zw)
|
|
if err := tw.WriteHeader(&tar.Header{Name: "APKINDEX", Mode: 0o644, Size: int64(len(index))}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, _ = io.WriteString(tw, index)
|
|
_ = tw.Close()
|
|
_ = zw.Close()
|
|
return buf.String()
|
|
}
|
|
|
|
func newAPKUpstream(t *testing.T) *apkUpstream {
|
|
t.Helper()
|
|
u := &apkUpstream{
|
|
files: map[string]string{
|
|
apkMainIndex: apkIndexArchive(t, "P:busybox\nV:1.36.1-r29\nA:x86_64\nS:18\n\nP:musl\nV:1.2.5-r0\nA:x86_64\nS:15\n\n"),
|
|
apkCommIndex: apkIndexArchive(t, "P:htop\nV:3.3.0-r0\nA:x86_64\nS:15\n\n"),
|
|
apkBusyboxPath: "busybox 1.36.1-r29",
|
|
apkHtopPath: "htop 3.3.0-r0 x86",
|
|
"/v3.20/main/x86_64/musl-1.2.5-r0.apk": "musl 1.2.5-r0",
|
|
},
|
|
hits: map[string]int{},
|
|
}
|
|
u.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
u.mu.Lock()
|
|
body, ok := u.files[r.URL.Path]
|
|
u.hits[r.URL.Path]++
|
|
u.mu.Unlock()
|
|
if !ok {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
_, _ = io.WriteString(w, body)
|
|
}))
|
|
t.Cleanup(u.Close)
|
|
return u
|
|
}
|
|
|
|
func (u *apkUpstream) hitsFor(path string) int {
|
|
u.mu.Lock()
|
|
defer u.mu.Unlock()
|
|
return u.hits[path]
|
|
}
|
|
|
|
func apkMirrorConfig(upstream *apkUpstream) *config.Config {
|
|
cfg := config.Default()
|
|
cfg.Upstream.APK = map[string]string{"alpine": upstream.URL}
|
|
return cfg
|
|
}
|
|
|
|
func getAPK(proxy *handler.Proxy, upstream *apkUpstream, path string) *httptest.ResponseRecorder {
|
|
rec := httptest.NewRecorder()
|
|
h := handler.NewAPKHandler(proxy, "http://proxy.test", map[string]string{"alpine": upstream.URL})
|
|
h.Routes().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/alpine"+path, nil))
|
|
return rec
|
|
}
|
|
|
|
func TestMirrorAPKPackagesAreServedFromCache(t *testing.T) {
|
|
upstream := newAPKUpstream(t)
|
|
|
|
progress, proxy := mirrorWithConfig(t, apkMirrorConfig(upstream),
|
|
"pkg:apk/alpine/busybox@1.36.1-r29?arch=x86_64&distro=alpine-3.20.3",
|
|
"pkg:apk/alpine/htop@3.3.0-r0?arch=x86_64&distro=alpine-3.20.3",
|
|
"pkg:apk/alpine/musl@1.2.5-r0?arch=x86_64&distro=alpine-3.20.3")
|
|
|
|
if progress.Completed != 3 || progress.Failed != 0 {
|
|
t.Fatalf("completed = %d, failed = %d, errors = %v", progress.Completed, progress.Failed, progress.Errors)
|
|
}
|
|
for _, path := range []string{apkBusyboxPath, apkHtopPath, apkMainIndex, apkCommIndex} {
|
|
if got := upstream.hitsFor(path); got != 1 {
|
|
t.Errorf("%s fetched %d times, want 1", path, got)
|
|
}
|
|
}
|
|
|
|
// apk gets the packages from the cache once the upstream is down.
|
|
upstream.Close()
|
|
for path, want := range map[string]string{
|
|
apkBusyboxPath: "busybox 1.36.1-r29",
|
|
apkHtopPath: "htop 3.3.0-r0 x86",
|
|
} {
|
|
if rec := getAPK(proxy, upstream, path); rec.Code != http.StatusOK || rec.Body.String() != want {
|
|
t.Errorf("%s from cache: status = %d, body = %q, want 200 and %q", path, rec.Code, rec.Body, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMirrorAPKReadsV2IndexBesideV3(t *testing.T) {
|
|
// A repository moving to apk v3 serves Packages.adb next to
|
|
// APKINDEX.tar.gz; the mirror reads the v2 index.
|
|
upstream := newAPKUpstream(t)
|
|
upstream.files["/v3.20/main/x86_64/Packages.adb"] = "ADB.v3-index"
|
|
|
|
progress, _ := mirrorWithConfig(t, apkMirrorConfig(upstream),
|
|
"pkg:apk/alpine/busybox@1.36.1-r29?arch=x86_64&distro=alpine-3.20.3")
|
|
|
|
if progress.Completed != 1 || progress.Failed != 0 {
|
|
t.Fatalf("completed = %d, failed = %d, errors = %v", progress.Completed, progress.Failed, progress.Errors)
|
|
}
|
|
}
|
|
|
|
func TestMirrorAPKRefusesIncompletePURLs(t *testing.T) {
|
|
upstream := newAPKUpstream(t)
|
|
|
|
tests := map[string]string{
|
|
"pkg:apk/alpine/busybox@1.36.1-r29?arch=x86_64": "distro",
|
|
"pkg:apk/alpine/busybox@1.36.1-r29?distro=alpine-3.20.3": "arch",
|
|
"pkg:apk/alpine/busybox@1.36.1-r29?arch=x86_64&distro=alpine-3.20-evil": "distro",
|
|
"pkg:apk/alpine/busybox@1.36.1-r29?arch=x86_64&distro=alpine-3.21.0_alpha20240807": "distro",
|
|
"pkg:apk/alpine/busybox@1.36.1-r29?arch=x86_64&distro=debian-12": "distro",
|
|
"pkg:apk/other/busybox@1.36.1-r29?arch=x86_64&distro=alpine-3.20.3": "upstream.apk",
|
|
"pkg:apk/alpine/busybox@9.9.9-r0?arch=x86_64&distro=alpine-3.20.3": "isn't in main or community of alpine v3.20",
|
|
"pkg:apk/alpine/busybox@1.36.1-r29?arch=../../etc&distro=alpine-3.20.3": "arch",
|
|
}
|
|
for p, want := range tests {
|
|
t.Run(p, func(t *testing.T) {
|
|
progress, _ := mirrorWithConfig(t, apkMirrorConfig(upstream), p)
|
|
if progress.Failed != 1 || len(progress.Errors) != 1 || !strings.Contains(progress.Errors[0].Error, want) {
|
|
t.Errorf("failed = %d, errors = %v; want one error mentioning %q", progress.Failed, progress.Errors, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMirrorAPKIndexErrorsLeaveOutTheURL(t *testing.T) {
|
|
upstream := newAPKUpstream(t)
|
|
upstream.files[apkMainIndex] = "not an index"
|
|
|
|
progress, _ := mirrorWithConfig(t, apkMirrorConfig(upstream),
|
|
"pkg:apk/alpine/busybox@1.36.1-r29?arch=x86_64&distro=alpine-3.20.3")
|
|
|
|
if progress.Failed != 1 || len(progress.Errors) != 1 {
|
|
t.Fatalf("failed = %d, errors = %v; want one error", progress.Failed, progress.Errors)
|
|
}
|
|
msg := progress.Errors[0].Error
|
|
if !strings.Contains(msg, "alpine v3.20/main/x86_64") || strings.Contains(msg, upstream.URL) {
|
|
t.Errorf("error = %q; want the repository and directory without %s", msg, upstream.URL)
|
|
}
|
|
}
|