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/server/package_routes_test.go
simon chrzanowski 832a441781
Add package routes for private Composer and npm registries (#426)
* Add Composer package routes for private repositories

- Add upstream.composer_routes mapping package name patterns to repositories
- Serve matching packages only from their route, with no fallback to the default repository
- Qualify metadata and artifact cache keys with the route's upstream
- Give routed versions an empty notification-url so installs are not reported to notify-batch
- Document routes in configuration docs, example config and README

* Add npm package routes for private registries

- Add upstream.npm_routes mapping package name patterns to registries
- Serve matching packages only from their route, with no fallback to upstream.npm
- Resolve and validate tarball URLs against the route's registry
- Qualify metadata and artifact cache keys with the route's upstream
- Document npm routes and the audit endpoint limitation

* Share routed artifact filename qualification between handlers

* Match Composer routes without the ~dev suffix

Packagist serves a package's dev versions from {name}~dev.json, and the
handler matched routes against the name with ~dev still attached. An exact
route for example/library therefore missed /p2/example/library~dev.json, and
the private package's dev metadata came from the default repository.

- Match routes against the package name without ~dev, keeping it in the
  upstream URL and the cache key so the two files stay apart
- Add a regression test with an exact route

* Keep routed npm publish times out of the shared versions row

The download cooldown check stores publish times under the plain version
PURL, which does not say which registry a version came from. A time read
from the default registry was trusted for a private version of the same
name, so a private release published an hour ago passed a seven-day
cooldown when the public one was a month old.

- Skip the stored row for routed packages, both reading and writing, and
  take the publish time from the route's metadata, which is cached under
  the route
- This also stops a private time from holding back the public version, and
  a route's previous upstream from lending its times to the new one
- Cover adding a route after a public download, the reverse order, and
  moving a route to another upstream
2026-10-11 18:06:18 +01:00

53 lines
1.7 KiB
Go

package server
import (
"io"
"log/slog"
"net/http"
"net/http/httptest"
"testing"
"github.com/git-pkgs/proxy/internal/config"
"github.com/git-pkgs/proxy/internal/handler"
"github.com/git-pkgs/registries/fetch"
"github.com/go-chi/chi/v5"
)
func TestMountProtocolHandlersAppliesPackageRoutes(t *testing.T) {
routed := make(chan string, 2)
private := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
routed <- r.URL.Path
http.NotFound(w, r)
}))
defer private.Close()
cfg := config.Default()
cfg.BaseURL = "http://proxy.test"
cfg.Upstream.Composer = "http://default.invalid"
cfg.Upstream.ComposerRepository = "http://default.invalid"
cfg.Upstream.NPM = "http://default.invalid"
cfg.Upstream.ComposerRoutes = map[string]string{"example/*": private.URL + "/composer"}
cfg.Upstream.NPMRoutes = map[string]string{"@example/*": private.URL + "/npm"}
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
proxy := handler.NewProxy(nil, nil, fetch.NewFetcher(), fetch.NewResolver(), logger)
proxy.HTTPClient = private.Client()
s := &Server{cfg: cfg, logger: logger}
r := chi.NewRouter()
s.mountProtocolHandlers(r, proxy)
for path, wantUpstreamPath := range map[string]string{
"/composer/p2/example/library.json": "/composer/p2/example/library.json",
"/npm/@example%2fwidgets": "/npm/@example/widgets",
} {
r.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, path, nil))
select {
case got := <-routed:
if got != wantUpstreamPath {
t.Errorf("%s reached route upstream at %q, want %q", path, got, wantUpstreamPath)
}
default:
t.Errorf("%s did not reach the configured route", path)
}
}
}