diff --git a/internal/handler/download_test.go b/internal/handler/download_test.go index a6e0cb3..d560b82 100644 --- a/internal/handler/download_test.go +++ b/internal/handler/download_test.go @@ -522,6 +522,11 @@ func TestCondaHandler_CacheMiss(t *testing.T) { if !fetcher.fetchCalled { t.Error("expected fetcher to be called on cache miss") } + + want := upstream.URL + "/conda-forge/linux-64/pandas-2.0.0-py311h320fe9a_0.conda" + if fetcher.fetchedURL != want { + t.Errorf("upstream URL = %q, want %q", fetcher.fetchedURL, want) + } } func TestCRANHandler_SourceDownloadCacheHit(t *testing.T) { @@ -615,7 +620,7 @@ func TestCRANHandler_CacheMiss(t *testing.T) { } h := NewCRANHandler(proxy, "http://localhost") - h.upstreamURL = "http://should-not-be-reached" + h.upstreamURL = "https://cran.r-project.org" srv := httptest.NewServer(h.Routes()) defer srv.Close() @@ -629,6 +634,40 @@ func TestCRANHandler_CacheMiss(t *testing.T) { if !fetcher.fetchCalled { t.Error("expected fetcher to be called on cache miss") } + + want := "https://cran.r-project.org/src/contrib/tidyr_1.3.0.tar.gz" + if fetcher.fetchedURL != want { + t.Errorf("upstream URL = %q, want %q", fetcher.fetchedURL, want) + } +} + +func TestCRANHandler_BinaryDownloadCacheMiss(t *testing.T) { + proxy, _, _, fetcher := setupTestProxy(t) + fetcher.artifact = &fetch.Artifact{ + Body: io.NopCloser(strings.NewReader("fetched binary")), + ContentType: "application/zip", + } + + h := NewCRANHandler(proxy, "http://localhost") + h.upstreamURL = "https://cran.r-project.org" + + srv := httptest.NewServer(h.Routes()) + defer srv.Close() + + resp, err := http.Get(srv.URL + "/bin/windows/contrib/4.3/dplyr_1.1.0.zip") + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer func() { _ = resp.Body.Close() }() + + if !fetcher.fetchCalled { + t.Error("expected fetcher to be called on cache miss") + } + + want := "https://cran.r-project.org/bin/windows/contrib/4.3/dplyr_1.1.0.zip" + if fetcher.fetchedURL != want { + t.Errorf("upstream URL = %q, want %q", fetcher.fetchedURL, want) + } } func TestMavenHandler_DownloadCacheHit(t *testing.T) { @@ -764,4 +803,144 @@ func TestMavenHandler_CacheMiss(t *testing.T) { if !fetcher.fetchCalled { t.Error("expected fetcher to be called on cache miss") } + + want := "https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.14.0/commons-lang3-3.14.0.jar" + if fetcher.fetchedURL != want { + t.Errorf("upstream URL = %q, want %q", fetcher.fetchedURL, want) + } +} + +func TestNuGetHandler_DownloadCacheMiss(t *testing.T) { + proxy, _, _, fetcher := setupTestProxy(t) + fetcher.artifact = &fetch.Artifact{ + Body: io.NopCloser(strings.NewReader("fetched nupkg")), + ContentType: "application/octet-stream", + } + + h := NewNuGetHandler(proxy, "http://localhost") + srv := httptest.NewServer(h.Routes()) + defer srv.Close() + + resp, err := http.Get(srv.URL + "/v3-flatcontainer/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg") + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer func() { _ = resp.Body.Close() }() + + if !fetcher.fetchCalled { + t.Error("expected fetcher to be called on cache miss") + } + + want := "https://api.nuget.org/v3-flatcontainer/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg" + if fetcher.fetchedURL != want { + t.Errorf("upstream URL = %q, want %q", fetcher.fetchedURL, want) + } +} + +func TestConanHandler_RecipeFileCacheMiss(t *testing.T) { + proxy, _, _, fetcher := setupTestProxy(t) + fetcher.artifact = &fetch.Artifact{ + Body: io.NopCloser(strings.NewReader("conan export")), + ContentType: "application/octet-stream", + } + + h := NewConanHandler(proxy, "http://localhost") + srv := httptest.NewServer(h.Routes()) + defer srv.Close() + + resp, err := http.Get(srv.URL + "/v2/files/zlib/1.3/_/_/abc123/recipe/conan_export.tgz") + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer func() { _ = resp.Body.Close() }() + + if !fetcher.fetchCalled { + t.Error("expected fetcher to be called on cache miss") + } + + want := "https://center.conan.io/v2/files/zlib/1.3/_/_/abc123/recipe/conan_export.tgz" + if fetcher.fetchedURL != want { + t.Errorf("upstream URL = %q, want %q", fetcher.fetchedURL, want) + } +} + +func TestConanHandler_PackageFileCacheMiss(t *testing.T) { + proxy, _, _, fetcher := setupTestProxy(t) + fetcher.artifact = &fetch.Artifact{ + Body: io.NopCloser(strings.NewReader("conan package")), + ContentType: "application/octet-stream", + } + + h := NewConanHandler(proxy, "http://localhost") + srv := httptest.NewServer(h.Routes()) + defer srv.Close() + + resp, err := http.Get(srv.URL + "/v2/files/zlib/1.3/_/_/abc123/package/def456/ghi789/conan_package.tgz") + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer func() { _ = resp.Body.Close() }() + + if !fetcher.fetchCalled { + t.Error("expected fetcher to be called on cache miss") + } + + want := "https://center.conan.io/v2/files/zlib/1.3/_/_/abc123/package/def456/ghi789/conan_package.tgz" + if fetcher.fetchedURL != want { + t.Errorf("upstream URL = %q, want %q", fetcher.fetchedURL, want) + } +} + +func TestDebianHandler_DownloadCacheMiss(t *testing.T) { + proxy, _, _, fetcher := setupTestProxy(t) + fetcher.artifact = &fetch.Artifact{ + Body: io.NopCloser(strings.NewReader("fetched deb")), + ContentType: "application/vnd.debian.binary-package", + } + + h := NewDebianHandler(proxy, "http://localhost") + srv := httptest.NewServer(h.Routes()) + defer srv.Close() + + resp, err := http.Get(srv.URL + "/pool/main/n/nginx/nginx_1.18.0-6_amd64.deb") + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer func() { _ = resp.Body.Close() }() + + if !fetcher.fetchCalled { + t.Error("expected fetcher to be called on cache miss") + } + + want := "http://deb.debian.org/debian/pool/main/n/nginx/nginx_1.18.0-6_amd64.deb" + if fetcher.fetchedURL != want { + t.Errorf("upstream URL = %q, want %q", fetcher.fetchedURL, want) + } +} + +func TestRPMHandler_DownloadCacheMiss(t *testing.T) { + proxy, _, _, fetcher := setupTestProxy(t) + fetcher.artifact = &fetch.Artifact{ + Body: io.NopCloser(strings.NewReader("fetched rpm")), + ContentType: "application/x-rpm", + } + + h := NewRPMHandler(proxy, "http://localhost") + srv := httptest.NewServer(h.Routes()) + defer srv.Close() + + resp, err := http.Get(srv.URL + "/releases/39/Everything/x86_64/os/Packages/n/nginx-1.24.0-1.fc39.x86_64.rpm") + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer func() { _ = resp.Body.Close() }() + + if !fetcher.fetchCalled { + t.Error("expected fetcher to be called on cache miss") + } + + want := "https://dl.fedoraproject.org/pub/fedora/linux/releases/39/Everything/x86_64/os/Packages/n/nginx-1.24.0-1.fc39.x86_64.rpm" + if fetcher.fetchedURL != want { + t.Errorf("upstream URL = %q, want %q", fetcher.fetchedURL, want) + } } diff --git a/internal/handler/pypi_test.go b/internal/handler/pypi_test.go index 18d2637..9e2ade0 100644 --- a/internal/handler/pypi_test.go +++ b/internal/handler/pypi_test.go @@ -2,11 +2,16 @@ package handler import ( "encoding/json" + "io" "log/slog" + "net/http" + "net/http/httptest" + "strings" "testing" "time" "github.com/git-pkgs/proxy/internal/cooldown" + "github.com/git-pkgs/registries/fetch" ) func TestPyPIParseFilename(t *testing.T) { @@ -111,3 +116,79 @@ func TestIsPythonTag(t *testing.T) { } } } + +func TestPyPIHandler_DownloadUpstreamURL(t *testing.T) { + proxy, _, _, fetcher := setupTestProxy(t) + fetcher.artifact = &fetch.Artifact{ + Body: io.NopCloser(strings.NewReader("wheel data")), + ContentType: "application/octet-stream", + } + + h := NewPyPIHandler(proxy, "http://localhost") + srv := httptest.NewServer(h.Routes()) + defer srv.Close() + + // The path wildcard {path...} captures everything after /packages/, + // which includes "packages/" from the rewritten URL. The upstream URL + // must not double the "packages" segment. + resp, err := http.Get(srv.URL + "/packages/packages/ab/cd/ef0123456789/requests-2.31.0-py3-none-any.whl") + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer func() { _ = resp.Body.Close() }() + + if !fetcher.fetchCalled { + t.Fatal("expected fetcher to be called on cache miss") + } + + want := "https://files.pythonhosted.org/packages/ab/cd/ef0123456789/requests-2.31.0-py3-none-any.whl" + if fetcher.fetchedURL != want { + t.Errorf("upstream URL = %q, want %q", fetcher.fetchedURL, want) + } +} + +func TestPyPIHandler_DownloadCacheHit(t *testing.T) { + proxy, db, store, _ := setupTestProxy(t) + seedPackage(t, db, store, "pypi", "requests", "2.31.0", + "requests-2.31.0-py3-none-any.whl", "wheel binary data") + + h := NewPyPIHandler(proxy, "http://localhost") + srv := httptest.NewServer(h.Routes()) + defer srv.Close() + + resp, err := http.Get(srv.URL + "/packages/packages/ab/cd/ef0123456789/requests-2.31.0-py3-none-any.whl") + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + t.Errorf("status = %d, want %d", resp.StatusCode, http.StatusOK) + } + body, _ := io.ReadAll(resp.Body) + if string(body) != "wheel binary data" { + t.Errorf("body = %q, want %q", body, "wheel binary data") + } +} + +func TestPyPIHandler_DownloadCacheMiss(t *testing.T) { + proxy, _, _, fetcher := setupTestProxy(t) + fetcher.artifact = &fetch.Artifact{ + Body: io.NopCloser(strings.NewReader("fetched wheel")), + ContentType: "application/octet-stream", + } + + h := NewPyPIHandler(proxy, "http://localhost") + srv := httptest.NewServer(h.Routes()) + defer srv.Close() + + resp, err := http.Get(srv.URL + "/packages/packages/ab/cd/ef0123456789/newpkg-1.0.0.tar.gz") + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer func() { _ = resp.Body.Close() }() + + if !fetcher.fetchCalled { + t.Error("expected fetcher to be called on cache miss") + } +}