mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-09 07:39:19 -05:00
201 lines
7.5 KiB
Go
201 lines
7.5 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/git-pkgs/registries"
|
|
"github.com/git-pkgs/registries/client"
|
|
"github.com/git-pkgs/registries/fetch"
|
|
)
|
|
|
|
type artifactTestRegistry struct {
|
|
calls atomic.Int64
|
|
err error
|
|
artifacts []registries.Artifact
|
|
}
|
|
|
|
func (*artifactTestRegistry) Ecosystem() string { return "maven" }
|
|
func (*artifactTestRegistry) URLs() client.URLBuilder { //nolint:ireturn // required by fetch.Registry
|
|
return &client.BaseURLs{}
|
|
}
|
|
func (r *artifactTestRegistry) FetchVersions(ctx context.Context, _ string) ([]registries.Version, error) {
|
|
r.calls.Add(1)
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
if r.err != nil {
|
|
return nil, r.err
|
|
}
|
|
return []registries.Version{{Number: "1.0.0", Artifacts: r.artifacts}}, nil
|
|
}
|
|
|
|
func TestGetOrFetchArtifactResolvedCacheHit(t *testing.T) {
|
|
p, db, store, fetcher := setupTestProxy(t)
|
|
fetcher.fetchErr = errors.New("unexpected artifact fetch on a cache hit")
|
|
seedPackage(t, db, store, "maven", "org.example:demo", "1.0.0", "demo-1.0.0.pom", "pom")
|
|
seedPackage(t, db, store, "maven", "org.example:demo", "1.0.0", "demo-1.0.0.jar", "jar")
|
|
registry := &artifactTestRegistry{artifacts: []registries.Artifact{{URL: "https://registry.example/demo-1.0.0.jar", Filename: "demo-1.0.0.jar"}}}
|
|
p.Resolver.RegisterRegistry(registry)
|
|
result, err := p.GetOrFetchArtifact(context.Background(), "maven", "org.example:demo", "1.0.0", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = result.Reader.Close() }()
|
|
body, err := io.ReadAll(result.Reader)
|
|
if err != nil || string(body) != "jar" || !result.Cached || result.Artifact.Filename != "demo-1.0.0.jar" {
|
|
t.Fatalf("result=%+v body=%q err=%v", result, body, err)
|
|
}
|
|
if fetcher.fetchCalled || registry.calls.Load() != 1 {
|
|
t.Fatalf("fetchCalled=%t resolutions=%d", fetcher.fetchCalled, registry.calls.Load())
|
|
}
|
|
}
|
|
|
|
func TestGetOrFetchArtifactResolvedMiss(t *testing.T) {
|
|
p, db, store, _ := setupTestProxy(t)
|
|
seedPackage(t, db, store, "maven", "org.example:demo", "1.0.0", "demo-1.0.0.pom", "pom")
|
|
registry := &artifactTestRegistry{artifacts: []registries.Artifact{{URL: "https://registry.example/demo-1.0.0.jar", Filename: "demo-1.0.0.jar"}}}
|
|
p.Resolver.RegisterRegistry(registry)
|
|
fetcher := &countingFetcher{content: "jar"}
|
|
p.Fetcher = fetcher
|
|
for i, cached := range []bool{false, true} {
|
|
result, err := p.GetOrFetchArtifact(context.Background(), "maven", "org.example:demo", "1.0.0", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
body, readErr := io.ReadAll(result.Reader)
|
|
_ = result.Reader.Close()
|
|
if readErr != nil || string(body) != "jar" || result.Cached != cached {
|
|
t.Fatalf("result=%+v body=%q err=%v; want cached=%t", result, body, readErr, cached)
|
|
}
|
|
if registry.calls.Load() != int64(i+1) {
|
|
t.Fatalf("resolutions=%d after request %d; want one per request", registry.calls.Load(), i+1)
|
|
}
|
|
}
|
|
if fetcher.calls.Load() != 1 || registry.calls.Load() != 2 {
|
|
t.Fatalf("downloads=%d resolutions=%d; want 1 and 2", fetcher.calls.Load(), registry.calls.Load())
|
|
}
|
|
}
|
|
|
|
func TestGetOrFetchArtifactNamedCacheHitDoesNotResolve(t *testing.T) {
|
|
p, db, store, _ := setupTestProxy(t)
|
|
seedPackage(t, db, store, "maven", "org.example:demo", "1.0.0", "demo-1.0.0.pom", "pom")
|
|
p.Resolver = nil // A named cache hit must not need a resolver.
|
|
result, err := p.GetOrFetchArtifact(context.Background(), "maven", "org.example:demo", "1.0.0", "demo-1.0.0.pom")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = result.Reader.Close() }()
|
|
if !result.Cached {
|
|
t.Fatal("expected cache hit")
|
|
}
|
|
}
|
|
|
|
func TestGetOrFetchArtifactResolvedErrors(t *testing.T) {
|
|
for _, want := range []error{fetch.ErrNotFound, context.Canceled, errors.New("registry unavailable")} {
|
|
t.Run(want.Error(), func(t *testing.T) {
|
|
p, _, _, fetcher := setupTestProxy(t)
|
|
registry := &artifactTestRegistry{err: want}
|
|
p.Resolver.RegisterRegistry(registry)
|
|
_, err := p.GetOrFetchArtifact(context.Background(), "maven", "org.example:demo", "1.0.0", "")
|
|
if !errors.Is(err, want) || fetcher.fetchCalled {
|
|
t.Fatalf("err=%v fetched=%t", err, fetcher.fetchCalled)
|
|
}
|
|
if errors.Is(want, fetch.ErrNotFound) && !errors.Is(err, ErrUpstreamNotFound) {
|
|
t.Fatalf("missing upstream not-found classification: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetOrFetchArtifactResolvedMissingStorage(t *testing.T) {
|
|
p, db, store, fetcher := setupTestProxy(t)
|
|
seedPackage(t, db, store, "npm", "demo", "1.0.0", "demo-1.0.0.tgz", "old")
|
|
clear(store.files)
|
|
fetcher.artifact = &fetch.Artifact{Body: io.NopCloser(strings.NewReader("new"))}
|
|
result, err := p.GetOrFetchArtifact(context.Background(), "npm", "demo", "1.0.0", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = result.Reader.Close() }()
|
|
if result.Cached || !fetcher.fetchCalled {
|
|
t.Fatalf("expected refetch: result=%+v fetched=%t", result, fetcher.fetchCalled)
|
|
}
|
|
}
|
|
|
|
func TestGetOrFetchArtifactResolvedDenyBeforeResolution(t *testing.T) {
|
|
p, _, _, fetcher := setupTestProxy(t)
|
|
registry := &artifactTestRegistry{err: errors.New("must not resolve")}
|
|
p.Resolver.RegisterRegistry(registry)
|
|
setTestDenylist(t, p, "pkg:maven/org.example/demo@1.0.0")
|
|
_, err := p.GetOrFetchArtifact(context.Background(), "maven", "org.example:demo", "1.0.0", "")
|
|
if !errors.Is(err, ErrVersionDenied) || registry.calls.Load() != 0 || fetcher.fetchCalled {
|
|
t.Fatalf("err=%v resolutions=%d fetched=%t", err, registry.calls.Load(), fetcher.fetchCalled)
|
|
}
|
|
}
|
|
|
|
func TestGetOrFetchArtifactResolvedConcurrentMisses(t *testing.T) {
|
|
p, _, _, _ := setupTestProxy(t)
|
|
fetcher := &countingFetcher{content: "tarball", delay: fetchHoldTime}
|
|
p.Fetcher = fetcher
|
|
start := make(chan struct{})
|
|
var wg sync.WaitGroup
|
|
for range 8 {
|
|
wg.Go(func() {
|
|
<-start
|
|
result, err := p.GetOrFetchArtifact(context.Background(), "npm", "demo", "1.0.0", "")
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
defer func() { _ = result.Reader.Close() }()
|
|
body, err := io.ReadAll(result.Reader)
|
|
if err != nil || string(body) != "tarball" {
|
|
t.Errorf("body=%q err=%v", body, err)
|
|
}
|
|
})
|
|
}
|
|
close(start)
|
|
wg.Wait()
|
|
if fetcher.calls.Load() != 1 {
|
|
t.Fatalf("downloads=%d, want 1", fetcher.calls.Load())
|
|
}
|
|
}
|
|
|
|
func TestGetOrFetchArtifactResolvedWithoutFilename(t *testing.T) {
|
|
p, _, _, fetcher := setupTestProxy(t)
|
|
registry := &artifactTestRegistry{artifacts: []registries.Artifact{{URL: "https://registry.example/"}}}
|
|
p.Resolver.RegisterRegistry(registry)
|
|
_, err := p.GetOrFetchArtifact(context.Background(), "maven", "org.example:demo", "1.0.0", "")
|
|
if err == nil || !strings.Contains(err.Error(), "no filename") || fetcher.fetchCalled {
|
|
t.Fatalf("err=%v fetched=%t; expected rejection before download", err, fetcher.fetchCalled)
|
|
}
|
|
}
|
|
|
|
func TestGetOrFetchArtifactResolvedInvalidCacheHash(t *testing.T) {
|
|
p, db, store, fetcher := setupTestProxy(t)
|
|
seedPackage(t, db, store, "npm", "demo", "1.0.0", "demo-1.0.0.tgz", "old")
|
|
art, err := db.GetArtifact("pkg:npm/demo@1.0.0", "demo-1.0.0.tgz")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
art.ContentHash.String = "invalid-hash"
|
|
if err := db.UpsertArtifact(art); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fetcher.artifact = &fetch.Artifact{Body: io.NopCloser(strings.NewReader("new"))}
|
|
result, err := p.GetOrFetchArtifact(context.Background(), "npm", "demo", "1.0.0", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = result.Reader.Close() }()
|
|
body, err := io.ReadAll(result.Reader)
|
|
if err != nil || string(body) != "new" || result.Cached || !fetcher.fetchCalled {
|
|
t.Fatalf("result=%+v body=%q err=%v fetched=%t", result, body, err, fetcher.fetchCalled)
|
|
}
|
|
}
|