Test literal wildcards, pagination and handler passthrough for object prefix
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful

This commit is contained in:
2026-10-04 15:53:43 +11:00
parent f0c27d4e07
commit b0a145b7bb
2 changed files with 107 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
package v2
import (
"context"
"encoding/json"
"net/http/httptest"
"testing"
"github.com/go-chi/chi/v5"
"git.unkin.net/unkin/artifactapi/internal/database"
"git.unkin.net/unkin/artifactapi/pkg/models"
)
// TestObjectsListPrefix verifies the remote objects listing passes ?prefix=
// through to the database filter.
func TestObjectsListPrefix(t *testing.T) {
if testDSN == "" {
t.Skip("Docker unavailable")
}
ctx := context.Background()
db, err := database.New(testDSN)
if err != nil {
t.Fatal(err)
}
defer db.Close()
const remote = "generic-objs-prefix"
if err := db.CreateRemote(ctx, &models.Remote{
Name: remote, PackageType: models.PackageGeneric, RepoType: models.RepoTypeRemote,
BaseURL: "https://example.com", MutableTTL: 3600,
}); err != nil {
t.Fatal(err)
}
const hash = "sha256:bb22"
if err := db.UpsertBlob(ctx, hash, "blobs/bb/22", 10, "text/plain"); err != nil {
t.Fatal(err)
}
for _, p := range []string{"a/one.txt", "b/two.txt"} {
if err := db.UpsertArtifact(ctx, remote, p, hash, ""); err != nil {
t.Fatal(err)
}
}
router := chi.NewRouter()
router.Mount("/remotes/{name}/objects", NewObjectsHandler(db).Routes())
list := func(query string) []models.Artifact {
t.Helper()
w := httptest.NewRecorder()
router.ServeHTTP(w, httptest.NewRequest("GET", "/remotes/"+remote+"/objects"+query, nil))
if w.Code != 200 {
t.Fatalf("list%s = %d, want 200", query, w.Code)
}
var got []models.Artifact
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
t.Fatalf("decode: %v", err)
}
return got
}
if got := list(""); len(got) != 2 {
t.Fatalf("unfiltered listing returned %d objects, want 2", len(got))
}
if got := list("?prefix=b/"); len(got) != 1 || got[0].Path != "b/two.txt" {
t.Fatalf("prefix=b/ listing = %+v, want only b/two.txt", got)
}
}
+39
View File
@@ -195,6 +195,45 @@ func TestArtifactsAndBlobs(t *testing.T) {
}
}
func TestListArtifactsPrefix(t *testing.T) {
requireDB(t)
seedRemote(t, "r-prefix")
seedBlob(t, "prefixhash")
for _, p := range []string{"a%b/y", "a1b/y", "a_b/x", "aXb/x", "pkg/1", "pkg/2", "pkg/3", "pkgx/4"} {
if err := testDB.UpsertArtifact(ctx(), "r-prefix", p, "sha256:prefixhash", ""); err != nil {
t.Fatal(err)
}
}
paths := func(prefix string, limit, offset int) []string {
t.Helper()
arts, err := testDB.ListArtifacts(ctx(), "r-prefix", prefix, limit, offset)
if err != nil {
t.Fatalf("list %q: %v", prefix, err)
}
out := make([]string, len(arts))
for i, a := range arts {
out[i] = a.Path
}
return out
}
// LIKE wildcards in the prefix must match literally.
if got := paths("a_b/", 10, 0); len(got) != 1 || got[0] != "a_b/x" {
t.Fatalf("prefix a_b/ = %v, want [a_b/x]", got)
}
if got := paths("a%", 10, 0); len(got) != 1 || got[0] != "a%b/y" {
t.Fatalf("prefix a%% = %v, want [a%%b/y]", got)
}
// limit/offset page the filtered set, not the whole remote.
if got := paths("pkg/", 2, 0); len(got) != 2 || got[0] != "pkg/1" || got[1] != "pkg/2" {
t.Fatalf("prefix pkg/ page 1 = %v, want [pkg/1 pkg/2]", got)
}
if got := paths("pkg/", 2, 2); len(got) != 1 || got[0] != "pkg/3" {
t.Fatalf("prefix pkg/ page 2 = %v, want [pkg/3]", got)
}
}
func TestOrphanAndColdCleanup(t *testing.T) {
requireDB(t)
seedBlob(t, "orphanhash")