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/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

60 lines
2.1 KiB
Go

package handler
import "testing"
func TestPackageRoutesMatch(t *testing.T) {
routes := newPackageRoutes(map[string]string{
"@example/*": "https://npm.example.com/scoped/",
"@example/tool-*": "https://npm.example.com/tools",
"example/*": "https://composer.example.com/packages",
"single-package": "https://registry.example.com/single",
})
tests := []struct {
name string
pkg string
wantURL string
wantOK bool
}{
{"scoped npm package", "@example/widgets", "https://npm.example.com/scoped", true},
{"longest pattern wins", "@example/tool-kit", "https://npm.example.com/tools", true},
{"composer vendor", "example/library", "https://composer.example.com/packages", true},
{"exact name", "single-package", "https://registry.example.com/single", true},
{"names are case-insensitive", "Example/Library", "https://composer.example.com/packages", true},
{"wildcard does not cross slash", "example/library/extra", "", false},
{"similar vendor does not match", "example-fork/library", "", false},
{"unrouted package", "lodash", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
route, ok := routes.match(tt.pkg)
if ok != tt.wantOK {
t.Fatalf("match(%q) ok = %t, want %t", tt.pkg, ok, tt.wantOK)
}
if ok && route.url != tt.wantURL {
t.Errorf("match(%q) url = %q, want %q", tt.pkg, route.url, tt.wantURL)
}
})
}
}
func TestPackageRoutesCacheKeysAreDistinct(t *testing.T) {
routes := newPackageRoutes(map[string]string{
"example/*": "https://one.example.com",
"other-example/*": "https://two.example.com",
})
first, _ := routes.match("example/library")
second, _ := routes.match("other-example/library")
if first.cacheKey("example/library") == "example/library" {
t.Error("routed cache key must differ from the unrouted package name")
}
if first.cacheKey("example/library") == second.cacheKey("example/library") {
t.Error("routes with different upstreams must not share cache keys")
}
if containsPathTraversal(first.cacheKey("example/library")) {
t.Error("routed cache key must be usable as a storage path")
}
}