mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-09 15:49:16 -05:00
186 lines
6.1 KiB
Go
186 lines
6.1 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/sha256"
|
||
|
|
"encoding/hex"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"sync/atomic"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/git-pkgs/proxy/internal/database"
|
||
|
|
)
|
||
|
|
|
||
|
|
const swiftWidgetArchive = "acme widget archive"
|
||
|
|
|
||
|
|
// swiftMirrorUpstream serves acme.widget 1.0.0 the way a Swift package
|
||
|
|
// registry does, with https://github.com/acme/widget as its source
|
||
|
|
// repository, and counts archive downloads and identifier lookups. The
|
||
|
|
// repository's identifiers are ids, or only acme.widget.
|
||
|
|
func swiftMirrorUpstream(t *testing.T, ids ...string) (srv *httptest.Server, downloads, lookups *atomic.Int64) {
|
||
|
|
t.Helper()
|
||
|
|
if len(ids) == 0 {
|
||
|
|
ids = []string{"acme.widget"}
|
||
|
|
}
|
||
|
|
identifiers, err := json.Marshal(map[string][]string{"identifiers": ids})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
downloads, lookups = new(atomic.Int64), new(atomic.Int64)
|
||
|
|
sum := sha256.Sum256([]byte(swiftWidgetArchive))
|
||
|
|
srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.Header().Set("Content-Version", "1")
|
||
|
|
switch r.URL.Path {
|
||
|
|
case "/identifiers":
|
||
|
|
lookups.Add(1)
|
||
|
|
if r.URL.Query().Get("url") != "https://github.com/acme/widget" {
|
||
|
|
http.NotFound(w, r)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
_, _ = w.Write(identifiers)
|
||
|
|
case "/acme/widget":
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
_, _ = io.WriteString(w, `{"releases":{"1.0.0":{}}}`)
|
||
|
|
case "/acme/widget/1.0.0/Package.swift":
|
||
|
|
w.Header().Set("Content-Type", "text/x-swift")
|
||
|
|
_, _ = io.WriteString(w, "// swift-tools-version:5.9\n")
|
||
|
|
case "/acme/widget/1.0.0":
|
||
|
|
w.Header().Set("Content-Type", "application/json")
|
||
|
|
_, _ = fmt.Fprintf(w, `{"id":"acme.widget","version":"1.0.0","resources":[{"name":"source-archive","type":"application/zip","checksum":%q}]}`, hex.EncodeToString(sum[:]))
|
||
|
|
case "/acme/widget/1.0.0.zip":
|
||
|
|
downloads.Add(1)
|
||
|
|
w.Header().Set("Content-Type", "application/zip")
|
||
|
|
_, _ = io.WriteString(w, swiftWidgetArchive)
|
||
|
|
default:
|
||
|
|
http.NotFound(w, r)
|
||
|
|
}
|
||
|
|
}))
|
||
|
|
t.Cleanup(srv.Close)
|
||
|
|
return srv, downloads, lookups
|
||
|
|
}
|
||
|
|
|
||
|
|
// writeSwiftMirrorConfig writes a config using upstream as the Swift
|
||
|
|
// registry, denying the denied package PURLs.
|
||
|
|
func writeSwiftMirrorConfig(t *testing.T, dir, upstream string, denied ...string) string {
|
||
|
|
t.Helper()
|
||
|
|
cfg := "storage:\n" +
|
||
|
|
" url: file://" + filepath.Join(dir, "artifacts") + "\n" +
|
||
|
|
"database:\n" +
|
||
|
|
" path: " + filepath.Join(dir, "proxy.db") + "\n" +
|
||
|
|
"upstream:\n" +
|
||
|
|
" swift: " + upstream + "\n" +
|
||
|
|
" allow_loopback: true\n"
|
||
|
|
if len(denied) > 0 {
|
||
|
|
cfg += "denylist:\n packages:\n"
|
||
|
|
for _, p := range denied {
|
||
|
|
cfg += " - " + p + "\n"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
path := filepath.Join(dir, "config.yaml")
|
||
|
|
if err := os.WriteFile(path, []byte(cfg), 0o600); err != nil {
|
||
|
|
t.Fatalf("writing config: %v", err)
|
||
|
|
}
|
||
|
|
return path
|
||
|
|
}
|
||
|
|
|
||
|
|
func cachedArtifacts(t *testing.T, dir string) int64 {
|
||
|
|
t.Helper()
|
||
|
|
db, err := database.Open(filepath.Join(dir, "proxy.db"))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("opening database: %v", err)
|
||
|
|
}
|
||
|
|
defer func() { _ = db.Close() }()
|
||
|
|
stats, err := db.GetCacheStats()
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("GetCacheStats: %v", err)
|
||
|
|
}
|
||
|
|
return stats.TotalArtifacts
|
||
|
|
}
|
||
|
|
|
||
|
|
// Without a denylist the fake registry mirrors the release, so the tests
|
||
|
|
// below fail for the denylist and not for the setup.
|
||
|
|
func TestMirrorSwiftRelease(t *testing.T) {
|
||
|
|
upstream, downloads, _ := swiftMirrorUpstream(t)
|
||
|
|
dir := t.TempDir()
|
||
|
|
configPath := writeSwiftMirrorConfig(t, dir, upstream.URL)
|
||
|
|
|
||
|
|
output := runMirrorProcess(t, 0, "-config", configPath, "pkg:swift/acme/widget@1.0.0")
|
||
|
|
|
||
|
|
if !strings.Contains(output, "1 downloaded") {
|
||
|
|
t.Errorf("output:\n%s", output)
|
||
|
|
}
|
||
|
|
if n := downloads.Load(); n != 1 {
|
||
|
|
t.Errorf("archive downloads = %d, want 1", n)
|
||
|
|
}
|
||
|
|
if n := cachedArtifacts(t, dir); n != 1 {
|
||
|
|
t.Errorf("cached artifacts = %d, want 1", n)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// A denylist entry naming the package by its source repository blocks the
|
||
|
|
// mirror download of the registry identity it resolves to.
|
||
|
|
func TestMirrorDenylistSwiftSourceEntry(t *testing.T) {
|
||
|
|
upstream, downloads, _ := swiftMirrorUpstream(t)
|
||
|
|
dir := t.TempDir()
|
||
|
|
configPath := writeSwiftMirrorConfig(t, dir, upstream.URL, "pkg:swift/github.com/acme/widget@1.0.0")
|
||
|
|
|
||
|
|
output := runMirrorProcess(t, 1, "-config", configPath, "pkg:swift/acme/widget@1.0.0")
|
||
|
|
|
||
|
|
if !strings.Contains(output, "0 downloaded") || !strings.Contains(output, "1 failed") {
|
||
|
|
t.Errorf("output:\n%s", output)
|
||
|
|
}
|
||
|
|
if n := downloads.Load(); n != 0 {
|
||
|
|
t.Errorf("archive downloads = %d, want 0", n)
|
||
|
|
}
|
||
|
|
if n := cachedArtifacts(t, dir); n != 0 {
|
||
|
|
t.Errorf("cached artifacts = %d, want 0", n)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The registry may list several identities for one repository. A source
|
||
|
|
// entry denies all of them, not only the first.
|
||
|
|
func TestMirrorDenylistSwiftSourceEntryEveryIdentity(t *testing.T) {
|
||
|
|
upstream, downloads, _ := swiftMirrorUpstream(t, "other.widget", "acme.widget")
|
||
|
|
dir := t.TempDir()
|
||
|
|
configPath := writeSwiftMirrorConfig(t, dir, upstream.URL, "pkg:swift/github.com/acme/widget@1.0.0")
|
||
|
|
|
||
|
|
output := runMirrorProcess(t, 1, "-config", configPath, "pkg:swift/acme/widget@1.0.0")
|
||
|
|
|
||
|
|
if !strings.Contains(output, "0 downloaded") || !strings.Contains(output, "1 failed") {
|
||
|
|
t.Errorf("output:\n%s", output)
|
||
|
|
}
|
||
|
|
if n := downloads.Load(); n != 0 {
|
||
|
|
t.Errorf("archive downloads = %d, want 0", n)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// A source repository entry the registry can't resolve stops the mirror
|
||
|
|
// before it downloads anything, as it stops serve: left out, it would let
|
||
|
|
// the version through.
|
||
|
|
func TestMirrorDenylistUnresolvedSwiftSource(t *testing.T) {
|
||
|
|
upstream, downloads, lookups := swiftMirrorUpstream(t)
|
||
|
|
dir := t.TempDir()
|
||
|
|
configPath := writeSwiftMirrorConfig(t, dir, upstream.URL, "pkg:swift/github.com/acme/unknown@1.0.0")
|
||
|
|
|
||
|
|
output := runMirrorProcess(t, 1, "-config", configPath, "pkg:swift/acme/widget@1.0.0")
|
||
|
|
|
||
|
|
if !strings.Contains(output, "configuring denylist") || !strings.Contains(output, "github.com/acme/unknown") {
|
||
|
|
t.Errorf("output does not report the unresolved entry:\n%s", output)
|
||
|
|
}
|
||
|
|
if strings.Contains(output, "Mirror complete") {
|
||
|
|
t.Errorf("mirror ran:\n%s", output)
|
||
|
|
}
|
||
|
|
if lookups.Load() == 0 {
|
||
|
|
t.Error("the entry was not looked up")
|
||
|
|
}
|
||
|
|
if n := downloads.Load(); n != 0 {
|
||
|
|
t.Errorf("archive downloads = %d, want 0", n)
|
||
|
|
}
|
||
|
|
}
|