mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-24 19:24:16 +00:00
- diff view gains a Packages tab with added/removed/changed/unchanged package tables and purl-derived type/license/upstream links - captain records verified against the DID's atcr_hold service before caching (processor + batch backfill), preventing forged holds - fix empty-handle updates clobbering cached handles and colliding on the UNIQUE constraint - move fillPrevCIDs into repo.go; DirectRepoOperator is now canonical, repomgr kept as a test oracle - surface read-only crew status in hold selector - reconcile docs
179 lines
4.6 KiB
Go
179 lines
4.6 KiB
Go
package handlers
|
|
|
|
import "testing"
|
|
|
|
func TestParsePurl(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want *purlInfo
|
|
}{
|
|
{
|
|
name: "deb with qualifiers",
|
|
in: "pkg:deb/ubuntu/libssl3t64@3.0.13-0ubuntu3.9?arch=amd64&distro=ubuntu-24.04&upstream=openssl",
|
|
want: &purlInfo{Type: "deb", Namespace: "ubuntu", Name: "libssl3t64", Version: "3.0.13-0ubuntu3.9"},
|
|
},
|
|
{
|
|
name: "scoped npm",
|
|
in: "pkg:npm/%40discordjs/builders@1.14.1",
|
|
want: &purlInfo{Type: "npm", Namespace: "@discordjs", Name: "builders", Version: "1.14.1"},
|
|
},
|
|
{
|
|
name: "unscoped npm",
|
|
in: "pkg:npm/lodash@4.18.1",
|
|
want: &purlInfo{Type: "npm", Namespace: "", Name: "lodash", Version: "4.18.1"},
|
|
},
|
|
{
|
|
name: "golang multi-segment namespace",
|
|
in: "pkg:golang/github.com/moby/sys/user@v0.1.0",
|
|
want: &purlInfo{Type: "golang", Namespace: "github.com/moby/sys", Name: "user", Version: "v0.1.0"},
|
|
},
|
|
{
|
|
name: "no version",
|
|
in: "pkg:gem/rails",
|
|
want: &purlInfo{Type: "gem", Namespace: "", Name: "rails", Version: ""},
|
|
},
|
|
{
|
|
name: "subpath stripped",
|
|
in: "pkg:golang/golang.org/x/sys@v0.1.0#unix",
|
|
want: &purlInfo{Type: "golang", Namespace: "golang.org/x", Name: "sys", Version: "v0.1.0"},
|
|
},
|
|
{name: "not a purl", in: "https://example.com/foo", want: nil},
|
|
{name: "missing name", in: "pkg:npm", want: nil},
|
|
{name: "empty", in: "", want: nil},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := parsePurl(tt.in)
|
|
if tt.want == nil {
|
|
if got != nil {
|
|
t.Fatalf("expected nil, got %+v", got)
|
|
}
|
|
return
|
|
}
|
|
if got == nil {
|
|
t.Fatalf("expected %+v, got nil", tt.want)
|
|
}
|
|
if got.Type != tt.want.Type || got.Namespace != tt.want.Namespace ||
|
|
got.Name != tt.want.Name || got.Version != tt.want.Version {
|
|
t.Errorf("expected %+v, got %+v", tt.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParsePurl_Qualifiers(t *testing.T) {
|
|
p := parsePurl("pkg:deb/debian/libpam0g@1.7.0-5?arch=amd64&upstream=pam%401.7.0")
|
|
if p == nil {
|
|
t.Fatal("expected parse to succeed")
|
|
}
|
|
if p.Qualifiers["upstream"] != "pam@1.7.0" {
|
|
t.Errorf("expected upstream qualifier pam@1.7.0, got %q", p.Qualifiers["upstream"])
|
|
}
|
|
if p.Qualifiers["arch"] != "amd64" {
|
|
t.Errorf("expected arch qualifier amd64, got %q", p.Qualifiers["arch"])
|
|
}
|
|
}
|
|
|
|
func TestPurlDisplayType(t *testing.T) {
|
|
tests := map[string]string{
|
|
"golang": "go",
|
|
"pypi": "python",
|
|
"cargo": "rust",
|
|
"maven": "java",
|
|
"composer": "php",
|
|
"generic": "",
|
|
"deb": "deb",
|
|
"npm": "npm",
|
|
"nuget": "nuget",
|
|
"apk": "apk",
|
|
}
|
|
for in, want := range tests {
|
|
if got := purlDisplayType(in); got != want {
|
|
t.Errorf("purlDisplayType(%q): expected %q, got %q", in, want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPurlURL(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in string
|
|
want string
|
|
}{
|
|
{
|
|
name: "scoped npm",
|
|
in: "pkg:npm/%40discordjs/builders@1.14.1",
|
|
want: "https://npmx.dev/package/@discordjs/builders/v/1.14.1",
|
|
},
|
|
{
|
|
name: "ubuntu deb uses upstream source",
|
|
in: "pkg:deb/ubuntu/libssl3t64@3.0.13?upstream=openssl",
|
|
want: "https://launchpad.net/ubuntu/+source/openssl",
|
|
},
|
|
{
|
|
name: "debian deb without upstream falls back to binary name",
|
|
in: "pkg:deb/debian/bash@5.2.37-2",
|
|
want: "https://tracker.debian.org/pkg/bash",
|
|
},
|
|
{
|
|
name: "debian upstream with version suffix",
|
|
in: "pkg:deb/debian/libpam0g@1.7.0-5?upstream=pam%401.7.0",
|
|
want: "https://tracker.debian.org/pkg/pam",
|
|
},
|
|
{
|
|
name: "golang",
|
|
in: "pkg:golang/github.com/moby/sys/user@v0.1.0",
|
|
want: "https://pkg.go.dev/github.com/moby/sys/user@v0.1.0",
|
|
},
|
|
{
|
|
name: "nuget",
|
|
in: "pkg:nuget/Serilog@4.3.0",
|
|
want: "https://www.nuget.org/packages/Serilog/4.3.0",
|
|
},
|
|
{
|
|
name: "pypi",
|
|
in: "pkg:pypi/requests@2.31.0",
|
|
want: "https://pypi.org/project/requests/2.31.0/",
|
|
},
|
|
{
|
|
name: "apk",
|
|
in: "pkg:apk/alpine/musl@1.2.4-r2",
|
|
want: "https://pkgs.alpinelinux.org/packages?name=musl",
|
|
},
|
|
{
|
|
name: "deb with unknown distro namespace",
|
|
in: "pkg:deb/somedistro/foo@1.0",
|
|
want: "",
|
|
},
|
|
{
|
|
name: "unknown type",
|
|
in: "pkg:conan/openssl@3.0.0",
|
|
want: "",
|
|
},
|
|
{
|
|
name: "UNKNOWN version gets no link",
|
|
in: "pkg:npm/web-streams-ponyfill-es6@UNKNOWN",
|
|
want: "",
|
|
},
|
|
{
|
|
name: "lowercase unknown version also gets no link",
|
|
in: "pkg:pypi/somepkg@unknown",
|
|
want: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
p := parsePurl(tt.in)
|
|
if p == nil {
|
|
t.Fatal("expected parse to succeed")
|
|
}
|
|
if got := purlURL(p); got != tt.want {
|
|
t.Errorf("expected %q, got %q", tt.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|