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/handler/hosted_test.go
Simon Chrzanowski 674528a25e Skip the pushed artifact lookup when hosting is disabled
Every npm and Composer download ran GetLocalArtifact, a full artifact
row lookup, even with hosted packages disabled. localVersions already
returns early in that case, so metadata does not list pushes, and
/generic/hosted/ is only served while hosting is enabled.

GetLocalArtifact now returns nil without a lookup when hosting is off.
Downloads then take the normal proxy path. An archive pushed earlier is
still answered from storage by that path and is never replaced by the
upstream copy; only cooldown now applies to it, as metadata no longer
lists it either. The hosted docs say so.
2026-10-25 17:32:22 +01:00

562 lines
22 KiB
Go

package handler
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"strings"
"sync"
"testing"
"github.com/git-pkgs/proxy/internal/database"
"github.com/git-pkgs/registries/fetch"
)
func TestHostedTargetForFileInPath(t *testing.T) {
got, err := HostedTargetFor("generic", "tools", "1.2.0", "tool.tar.gz")
if err != nil || got != (HostedTarget{Name: "tools", Version: "1.2.0", Filename: "tool.tar.gz"}) {
t.Errorf("generic target = %+v, %v", got, err)
}
for _, tt := range []struct{ ecosystem, name, version, file string }{
{"generic", "tools", "1.2.0", ""},
{"generic", "tools", "1.2.0", ".."},
{"generic", "acme/tools", "1.2.0", "tool.tar.gz"},
{"npm", "request", "2.88.3", "request-2.88.3.tgz"},
} {
if _, err := HostedTargetFor(tt.ecosystem, tt.name, tt.version, tt.file); !errors.Is(err, ErrHostedInvalidName) {
t.Errorf("HostedTargetFor(%q, %q, %q, %q) error = %v, want ErrHostedInvalidName",
tt.ecosystem, tt.name, tt.version, tt.file, err)
}
}
if !HostedFileInPath("generic") || HostedFileInPath("npm") || HostedFileInPath("cargo") {
t.Error("HostedFileInPath should hold for generic only")
}
}
func TestHostedTargetFor(t *testing.T) {
tests := []struct {
name string
ecosystem, pkg, version string
want HostedTarget
wantErr error
}{
{name: "npm", ecosystem: "npm", pkg: "request", version: "2.88.3",
want: HostedTarget{Name: "request", Version: "2.88.3", Filename: "request-2.88.3.tgz"}},
{name: "npm scoped", ecosystem: "npm", pkg: "@types/node", version: "20.1.0",
want: HostedTarget{Name: "@types/node", Version: "20.1.0", Filename: "node-20.1.0.tgz"}},
{name: "composer", ecosystem: "composer", pkg: "monolog/monolog", version: "3.5.0",
want: HostedTarget{Name: "monolog/monolog", Version: "3.5.0", Filename: "monolog-3.5.0.zip"}},
{name: "composer without vendor", ecosystem: "composer", pkg: "monolog", version: "3.5.0",
wantErr: ErrHostedInvalidName},
{name: "composer with extra segment", ecosystem: "composer", pkg: "a/b/c", version: "1.0.0",
wantErr: ErrHostedInvalidName},
{name: "unsupported ecosystem", ecosystem: "cargo", pkg: "serde", version: "1.0.200",
wantErr: ErrHostedUnsupported},
{name: "unknown ecosystem", ecosystem: "maven", pkg: "junit", version: "4.13",
wantErr: ErrHostedUnsupported},
{name: "npm scope without @", ecosystem: "npm", pkg: "types/node", version: "1.0.0",
wantErr: ErrHostedInvalidName},
{name: "dot-dot name segment", ecosystem: "npm", pkg: "@scope/..", version: "1.0.0",
wantErr: ErrHostedInvalidName},
{name: "dot-dot version", ecosystem: "npm", pkg: "request", version: "..",
wantErr: ErrHostedInvalidName},
{name: "control character", ecosystem: "npm", pkg: "request", version: "1.0.0\n",
wantErr: ErrHostedInvalidName},
{name: "empty version", ecosystem: "npm", pkg: "request", version: "",
wantErr: ErrHostedInvalidName},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := HostedTargetFor(tt.ecosystem, tt.pkg, tt.version, "")
if tt.wantErr != nil {
if !errors.Is(err, tt.wantErr) {
t.Fatalf("HostedTargetFor() error = %v, want %v", err, tt.wantErr)
}
return
}
if err != nil {
t.Fatalf("HostedTargetFor() error = %v", err)
}
if got != tt.want {
t.Errorf("HostedTargetFor() = %+v, want %+v", got, tt.want)
}
})
}
}
// npmTarball builds an npm package tarball holding manifest as
// package/package.json. Building an archive in memory cannot fail, so errors
// panic.
func npmTarball(manifest string) []byte {
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
tw := tar.NewWriter(gz)
for _, f := range [][2]string{{"package/package.json", manifest}, {"package/index.js", "module.exports = 1\n"}} {
must(tw.WriteHeader(&tar.Header{Name: f[0], Mode: 0o644, Size: int64(len(f[1]))}))
_, err := io.WriteString(tw, f[1])
must(err)
}
must(tw.Close())
must(gz.Close())
return buf.Bytes()
}
// composerZip builds a Composer dist archive holding manifest as composer.json
// under dir, or at the root when dir is empty.
func composerZip(dir, manifest string) []byte {
var buf bytes.Buffer
zw := zip.NewWriter(&buf)
prefix := ""
if dir != "" {
prefix = dir + "/"
}
for _, f := range [][2]string{{prefix + "composer.json", manifest}, {prefix + "src/Logger.php", "<?php\n"}} {
w, err := zw.Create(f[0])
must(err)
_, err = io.WriteString(w, f[1])
must(err)
}
must(zw.Close())
return buf.Bytes()
}
func must(err error) {
if err != nil {
panic(err)
}
}
const requestManifest = `{"name":"request","version":"2.88.3","dependencies":{"qs":"~6.5.2"}}`
var pushedTarball = string(npmTarball(requestManifest))
func pushRequest(t *testing.T, proxy *Proxy) HostedArtifact {
t.Helper()
target, err := HostedTargetFor("npm", "request", "2.88.3", "")
if err != nil {
t.Fatalf("HostedTargetFor: %v", err)
}
pushed, err := proxy.PushLocalArtifact(context.Background(), "npm", target, strings.NewReader(pushedTarball), "application/gzip", "")
if err != nil {
t.Fatalf("PushLocalArtifact: %v", err)
}
return pushed
}
func TestPushLocalArtifactIsServedWithoutUpstream(t *testing.T) {
proxy, db, _, fetcher := setupTestProxy(t)
pushed := pushRequest(t, proxy)
if pushed.Version != "2.88.3" || pushed.Filename != "request-2.88.3.tgz" || pushed.Size != int64(len(pushedTarball)) || len(pushed.SHA256) != 64 {
t.Errorf("pushed artifact = %+v", pushed)
}
result, err := proxy.GetOrFetchArtifactFromURL(context.Background(), "npm", "request", "2.88.3", "request-2.88.3.tgz",
"https://registry.npmjs.org/request/-/request-2.88.3.tgz")
if err != nil {
t.Fatalf("GetOrFetchArtifactFromURL: %v", err)
}
if got := readResult(result); got != pushedTarball {
t.Errorf("served body = %q, want the pushed tarball", got)
}
if fetcher.fetchCalled {
t.Error("serving a pushed artifact contacted the upstream")
}
art, err := db.GetArtifact("pkg:npm/request@2.88.3", "request-2.88.3.tgz")
if err != nil || art == nil {
t.Fatalf("GetArtifact: %+v, %v", art, err)
}
if art.Source != database.SourceLocal || art.UpstreamURL != "" {
t.Errorf("artifact source/upstream_url = %q/%q, want local/empty", art.Source, art.UpstreamURL)
}
}
func TestPushLocalArtifactReplacesCachedUpstreamCopy(t *testing.T) {
proxy, _, _, fetcher := setupTestProxy(t)
fetcher.artifact = &fetch.Artifact{Body: io.NopCloser(strings.NewReader("upstream tarball")), ContentType: "application/gzip"}
const upstreamURL = "https://registry.npmjs.org/request/-/request-2.88.3.tgz"
first, err := proxy.GetOrFetchArtifactFromURL(context.Background(), "npm", "request", "2.88.3", "request-2.88.3.tgz", upstreamURL)
if err != nil {
t.Fatalf("caching the upstream copy: %v", err)
}
_ = readResult(first)
pushRequest(t, proxy)
result, err := proxy.GetOrFetchArtifactFromURL(context.Background(), "npm", "request", "2.88.3", "request-2.88.3.tgz", upstreamURL)
if err != nil {
t.Fatalf("GetOrFetchArtifactFromURL: %v", err)
}
if got := readResult(result); got != pushedTarball {
t.Errorf("served body = %q, want the pushed tarball", got)
}
}
func TestLocalArtifactsAndDeleteLocalVersion(t *testing.T) {
proxy, _, _, _ := setupTestProxy(t)
pushed := pushRequest(t, proxy)
listed, err := proxy.LocalArtifacts("npm", "request")
if err != nil {
t.Fatalf("LocalArtifacts: %v", err)
}
if len(listed) != 1 || listed[0].Version != pushed.Version || listed[0].SHA256 != pushed.SHA256 {
t.Fatalf("LocalArtifacts = %+v, want the pushed artifact %+v", listed, pushed)
}
if err := proxy.DeleteLocalVersion("npm", "request", "2.88.3"); err != nil {
t.Fatalf("DeleteLocalVersion: %v", err)
}
if cached, err := proxy.GetCachedArtifact(context.Background(), "npm", "request", "2.88.3", "request-2.88.3.tgz"); err != nil || cached != nil {
t.Errorf("GetCachedArtifact after delete = %+v, %v, want nil, nil", cached, err)
}
if listed, err := proxy.LocalArtifacts("npm", "request"); err != nil || len(listed) != 0 {
t.Errorf("LocalArtifacts after delete = %+v, %v, want none", listed, err)
}
if err := proxy.DeleteLocalVersion("npm", "request", "2.88.3"); !errors.Is(err, ErrHostedVersionNotFound) {
t.Errorf("second DeleteLocalVersion error = %v, want %v", err, ErrHostedVersionNotFound)
}
}
func TestGetLocalArtifactIgnoresUpstreamCopies(t *testing.T) {
proxy, _, _, fetcher := setupTestProxy(t)
proxy.HostedEnabled = true
fetcher.artifact = &fetch.Artifact{Body: io.NopCloser(strings.NewReader("upstream tarball")), ContentType: "application/gzip"}
cached, err := proxy.GetOrFetchArtifactFromURL(context.Background(), "npm", "request", "2.88.3", "request-2.88.3.tgz",
"https://registry.npmjs.org/request/-/request-2.88.3.tgz")
if err != nil {
t.Fatalf("caching the upstream copy: %v", err)
}
_ = readResult(cached)
local, err := proxy.GetLocalArtifact(context.Background(), "npm", "request", "2.88.3", "request-2.88.3.tgz")
if err != nil || local != nil {
t.Fatalf("GetLocalArtifact for an upstream copy = %+v, %v, want nil, nil", local, err)
}
pushRequest(t, proxy)
local, err = proxy.GetLocalArtifact(context.Background(), "npm", "request", "2.88.3", "request-2.88.3.tgz")
if err != nil || local == nil {
t.Fatalf("GetLocalArtifact for a pushed artifact = %+v, %v", local, err)
}
if got := readResult(local); got != pushedTarball {
t.Errorf("served body = %q, want the pushed tarball", got)
}
}
// With hosted packages disabled, downloads don't look for a pushed artifact,
// as metadata requests don't.
func TestGetLocalArtifactNeedsHostedEnabled(t *testing.T) {
proxy, db, _, _ := setupTestProxy(t)
proxy.HostedEnabled = true
pushRequest(t, proxy)
if local, err := proxy.GetLocalArtifact(context.Background(), "npm", "request", "2.88.3", "request-2.88.3.tgz"); err != nil || local == nil {
t.Fatalf("GetLocalArtifact with hosted enabled = %+v, %v, want the pushed artifact", local, err)
} else {
_ = local.Reader.Close()
}
proxy.HostedEnabled = false
if local, err := proxy.GetLocalArtifact(context.Background(), "npm", "request", "2.88.3", "request-2.88.3.tgz"); err != nil || local != nil {
t.Fatalf("GetLocalArtifact with hosted disabled = %+v, %v, want nil, nil", local, err)
}
// A closed database fails every query, so a nil error shows no lookup ran.
_ = db.Close()
if local, err := proxy.GetLocalArtifact(context.Background(), "npm", "request", "2.88.3", "request-2.88.3.tgz"); err != nil || local != nil {
t.Errorf("GetLocalArtifact with hosted disabled = %+v, %v, want nil, nil without a database lookup", local, err)
}
proxy.HostedEnabled = true
if _, err := proxy.GetLocalArtifact(context.Background(), "npm", "request", "2.88.3", "request-2.88.3.tgz"); err == nil {
t.Error("GetLocalArtifact with hosted enabled did not query the closed database")
}
}
func TestPushLocalArtifactRecordsManifest(t *testing.T) {
proxy, _, _, _ := setupTestProxy(t)
proxy.HostedEnabled = true
pushed := pushRequest(t, proxy)
versions, err := proxy.localVersions("npm", "request")
if err != nil || len(versions) != 1 {
t.Fatalf("localVersions = %+v, %v", versions, err)
}
got := versions[0]
if got.Version != "2.88.3" || got.Filename != "request-2.88.3.tgz" || got.SHA256 != pushed.SHA256 {
t.Errorf("local version = %+v", got)
}
if !strings.Contains(string(got.Manifest), `"qs":"~6.5.2"`) {
t.Errorf("manifest = %s, want the dependencies from package.json", got.Manifest)
}
if !strings.HasPrefix(got.Integrity, "sha512-") {
t.Errorf("integrity = %q, want sha512", got.Integrity)
}
}
func TestLocalVersionsNeedHostedEnabled(t *testing.T) {
proxy, _, _, _ := setupTestProxy(t)
pushRequest(t, proxy)
if versions, err := proxy.localVersions("npm", "request"); err != nil || versions != nil {
t.Errorf("localVersions with hosted disabled = %+v, %v, want nil", versions, err)
}
}
func TestPushLocalArtifactChecksManifest(t *testing.T) {
tests := []struct {
name string
ecosystem string
pkg string
version string
archive []byte
wantErr error
}{
{name: "npm other version", ecosystem: "npm", pkg: "request", version: "2.88.3",
archive: npmTarball(`{"name":"request","version":"2.88.2"}`), wantErr: ErrHostedManifestMismatch},
{name: "npm other name", ecosystem: "npm", pkg: "request", version: "2.88.3",
archive: npmTarball(`{"name":"requests","version":"2.88.3"}`), wantErr: ErrHostedManifestMismatch},
{name: "npm manifest not an object", ecosystem: "npm", pkg: "request", version: "2.88.3",
archive: npmTarball(`["request"]`), wantErr: ErrHostedInvalidArchive},
{name: "not an archive", ecosystem: "npm", pkg: "request", version: "2.88.3",
archive: []byte("plain text"), wantErr: ErrHostedInvalidArchive},
{name: "npm tarball without package.json", ecosystem: "npm", pkg: "request", version: "2.88.3",
archive: composerZip("", `{}`), wantErr: ErrHostedInvalidArchive},
{name: "composer other name", ecosystem: "composer", pkg: "monolog/monolog", version: "3.5.0",
archive: composerZip("", `{"name":"monolog/other"}`), wantErr: ErrHostedManifestMismatch},
{name: "composer other version", ecosystem: "composer", pkg: "monolog/monolog", version: "3.5.0",
archive: composerZip("", `{"name":"monolog/monolog","version":"3.4.0"}`), wantErr: ErrHostedManifestMismatch},
{name: "composer zip without composer.json", ecosystem: "composer", pkg: "monolog/monolog", version: "3.5.0",
archive: npmTarball(`{}`), wantErr: ErrHostedInvalidArchive},
{name: "composer manifest at the root", ecosystem: "composer", pkg: "monolog/monolog", version: "3.5.0",
archive: composerZip("", `{"name":"monolog/monolog","require":{"php":">=8.1"}}`)},
{name: "composer manifest in a top directory", ecosystem: "composer", pkg: "monolog/monolog", version: "3.5.0",
archive: composerZip("Seldaek-monolog-abc123", `{"name":"Monolog/Monolog","version":"3.5.0"}`)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
proxy, _, store, _ := setupTestProxy(t)
target, err := HostedTargetFor(tt.ecosystem, tt.pkg, tt.version, "")
if err != nil {
t.Fatalf("HostedTargetFor: %v", err)
}
_, err = proxy.PushLocalArtifact(context.Background(), tt.ecosystem, target, bytes.NewReader(tt.archive), "", "")
if tt.wantErr == nil {
if err != nil {
t.Fatalf("PushLocalArtifact: %v", err)
}
return
}
if !errors.Is(err, tt.wantErr) {
t.Fatalf("PushLocalArtifact error = %v, want %v", err, tt.wantErr)
}
if listed, _ := proxy.LocalArtifacts(tt.ecosystem, tt.pkg); len(listed) != 0 {
t.Errorf("rejected archive was recorded: %+v", listed)
}
if len(store.files) != 0 {
t.Errorf("rejected archive left %d objects in storage", len(store.files))
}
})
}
}
// removeStored deletes the stored object of an artifact behind the cache's back.
func removeStored(t *testing.T, proxy *Proxy, versionPURL, filename string) {
t.Helper()
record, err := proxy.DB.GetArtifact(versionPURL, filename)
if err != nil || record == nil {
t.Fatalf("GetArtifact: %+v, %v", record, err)
}
if err := proxy.Storage.Delete(context.Background(), record.StoragePath.String); err != nil {
t.Fatalf("deleting stored object: %v", err)
}
}
func TestPushedArtifactIsNeverReplacedFromUpstream(t *testing.T) {
const versionPURL, filename = "pkg:npm/request@2.88.3", "request-2.88.3.tgz"
tests := []struct {
name string
damage func(t *testing.T, proxy *Proxy)
wantErr error
}{
{name: "stored object missing", wantErr: ErrHostedArtifactUnavailable,
damage: func(t *testing.T, proxy *Proxy) { removeStored(t, proxy, versionPURL, filename) }},
{name: "record cleared after an integrity failure", wantErr: ErrHostedArtifactMissing,
damage: func(t *testing.T, proxy *Proxy) {
record, err := proxy.DB.GetArtifact(versionPURL, filename)
if err != nil || record == nil {
t.Fatalf("GetArtifact: %+v, %v", record, err)
}
if err := proxy.DB.DiscardArtifact(versionPURL, filename, record.StoragePath.String); err != nil {
t.Fatalf("DiscardArtifact: %v", err)
}
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
proxy, db, _, fetcher := setupTestProxy(t)
fetcher.artifact = &fetch.Artifact{Body: io.NopCloser(strings.NewReader("upstream tarball")), ContentType: "application/gzip"}
pushRequest(t, proxy)
tt.damage(t, proxy)
_, err := proxy.GetOrFetchArtifactFromURL(context.Background(), "npm", "request", "2.88.3", filename,
"https://registry.npmjs.org/request/-/request-2.88.3.tgz")
if !errors.Is(err, tt.wantErr) {
t.Errorf("GetOrFetchArtifactFromURL error = %v, want %v", err, tt.wantErr)
}
if fetcher.fetchCalled {
t.Error("a miss on a pushed artifact fetched the upstream copy")
}
local, err := db.GetLocalArtifacts("pkg:npm/request")
if err != nil || len(local) != 1 || !local[0].Manifest.Valid {
t.Errorf("pushed records after the miss = %+v, %v, want the pushed one with its manifest", local, err)
}
})
}
}
// zipWith builds a zip holding files in the given order.
func zipWith(files ...[2]string) []byte {
var buf bytes.Buffer
zw := zip.NewWriter(&buf)
for _, f := range files {
w, err := zw.Create(f[0])
must(err)
_, err = io.WriteString(w, f[1])
must(err)
}
must(zw.Close())
return buf.Bytes()
}
func TestComposerManifestLookup(t *testing.T) {
const monolog = `{"name":"monolog/monolog"}`
const fixture = `{"name":"acme/test-fixture"}`
tests := []struct {
name string
archive []byte
wantErr error
}{
{name: "root manifest after a nested one",
archive: zipWith([2]string{"tests/composer.json", fixture}, [2]string{"composer.json", monolog})},
{name: "top directory manifest next to a deeper one",
archive: zipWith([2]string{"monolog-abc/tests/composer.json", fixture}, [2]string{"monolog-abc/composer.json", monolog})},
{name: "two top directories with manifests",
archive: zipWith([2]string{"tests/composer.json", fixture}, [2]string{"monolog-abc/composer.json", monolog}),
wantErr: ErrHostedInvalidArchive},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
target, err := HostedTargetFor("composer", "monolog/monolog", "3.5.0", "")
if err != nil {
t.Fatalf("HostedTargetFor: %v", err)
}
_, err = readHostedManifest("composer", bytes.NewReader(tt.archive), target)
if tt.wantErr == nil && err != nil {
t.Errorf("readHostedManifest error = %v, want the root or single top directory manifest", err)
}
if tt.wantErr != nil && !errors.Is(err, tt.wantErr) {
t.Errorf("readHostedManifest error = %v, want %v", err, tt.wantErr)
}
})
}
}
func TestConcurrentPushesOfTheSameFileHaveOneWinner(t *testing.T) {
proxy, _, _, _ := setupTestProxy(t)
proxy.HostedEnabled = true
target, err := HostedTargetFor("generic", "tools", "1.2.0", "tool.tar.gz")
if err != nil {
t.Fatalf("HostedTargetFor: %v", err)
}
const pushes = 8
start := make(chan struct{})
results := make([]error, pushes)
pushed := make([]HostedArtifact, pushes)
var wg sync.WaitGroup
for i := range pushes {
wg.Add(1)
go func() {
defer wg.Done()
<-start
body := strings.NewReader(fmt.Sprintf("build %d", i))
pushed[i], results[i] = proxy.PushLocalArtifact(context.Background(), "generic", target, body, "", "")
}()
}
close(start)
wg.Wait()
winner := -1
for i, err := range results {
switch {
case err == nil && winner >= 0:
t.Fatalf("pushes %d and %d both succeeded", winner, i)
case err == nil:
winner = i
case !errors.Is(err, ErrHostedExists):
t.Fatalf("push %d error = %v, want ErrHostedExists", i, err)
}
}
if winner < 0 {
t.Fatal("no push succeeded")
}
result, err := proxy.GetLocalArtifact(context.Background(), "generic", "tools", "1.2.0", "tool.tar.gz")
if err != nil || result == nil {
t.Fatalf("GetLocalArtifact = %v, %v", result, err)
}
served, _ := io.ReadAll(result.Reader)
_ = result.Reader.Close()
if string(served) != fmt.Sprintf("build %d", winner) {
t.Errorf("served %q, want the winning push %q", served, fmt.Sprintf("build %d", winner))
}
}
// A fetch that started before a push and records after it must not replace
// the pushed artifact or its version's integrity.
func TestCacheFillAfterPushKeepsThePush(t *testing.T) {
proxy, _, store, _ := setupTestProxy(t)
proxy.HostedEnabled = true
pushRequest(t, proxy)
const fetchedPath = "npm/request/2.88.3/fetched/request-2.88.3.tgz"
if _, _, err := store.Store(context.Background(), fetchedPath, strings.NewReader("upstream bytes")); err != nil {
t.Fatalf("storing fetched copy: %v", err)
}
fetched := testArtifact("upstream bytes", "pkg:npm/request@2.88.3", "request-2.88.3.tgz", "application/gzip")
err := proxy.updateCacheDB("npm", "request", "pkg:npm/request", "https://registry.npmjs.org/request/-/request-2.88.3.tgz",
fetchedPath, fetched, "sha512-upstream")
if !errors.Is(err, database.ErrLocalArtifactExists) {
t.Fatalf("updateCacheDB over a push error = %v, want ErrLocalArtifactExists", err)
}
result, err := proxy.GetLocalArtifact(context.Background(), "npm", "request", "2.88.3", "request-2.88.3.tgz")
if err != nil || result == nil {
t.Fatalf("GetLocalArtifact = %v, %v, want the pushed copy", result, err)
}
served, err := io.ReadAll(result.Reader)
_ = result.Reader.Close()
if err != nil || string(served) != pushedTarball {
t.Errorf("served %d bytes, %v, want the pushed tarball, integrity intact", len(served), err)
}
}
func TestGenericPushDoesNotReadTheStoredFile(t *testing.T) {
proxy, _, store, _ := setupTestProxy(t)
proxy.HostedEnabled = true
target, err := HostedTargetFor("generic", "tools", "1.2.0", "tool.tar.gz")
if err != nil {
t.Fatalf("HostedTargetFor: %v", err)
}
store.openErr = errors.New("storage opened")
if _, err := proxy.PushLocalArtifact(context.Background(), "generic", target, strings.NewReader("bytes"), "", ""); err != nil {
t.Fatalf("PushLocalArtifact: %v", err)
}
}