Add namespace and collection inventories

This commit is contained in:
2026-10-17 06:25:14 +00:00
parent c9b623b374
commit 8669294cf6
9 changed files with 295 additions and 3 deletions

View File

@@ -239,6 +239,105 @@ func newNamespaceListingEngine(t *testing.T) *promptkit.Engine {
return engine
}
func TestResourceAndCollectionInventories(t *testing.T) {
underlay := configkit.Input{Source: configkit.Mapping("inventory defaults", map[string]any{
"backends": map[string]any{"root-backend": map[string]any{"endpoint": "https://root.example.test/v1"}},
"profiles": map[string]any{"root-profile": map[string]any{"endpoint": "https://root.example.test/v1", "model": "root-model"}},
"prompts": map[string]any{"root-prompt": listingContractPrompt()},
"collections": map[string]any{
"alpha": map[string]any{
"backends": map[string]any{"api": map[string]any{"endpoint": "https://alpha.example.test/v1"}},
"profiles": map[string]any{"profile": map[string]any{"backend": "alpha/api", "model": "alpha-model"}},
"prompts": map[string]any{"prompt": listingContractPrompt()},
},
"alpha-beta": map[string]any{"prompts": map[string]any{"prompt": listingContractPrompt()}},
"beta": map[string]any{"backends": map[string]any{"api": map[string]any{"endpoint": "https://beta.example.test/v1"}}},
"resource-only": map[string]any{"profiles": map[string]any{"profile": map[string]any{"endpoint": "https://resource.example.test/v1", "model": "resource-model"}}},
"metadata-only": map[string]any{"release_version": "v1"},
"empty-label": map[string]any{"release_version": ""},
"null-label": map[string]any{"release_version": nil},
"override": map[string]any{"release_version": "old"},
},
})}
main := configkit.Input{Source: configkit.Mapping("inventory overlay", map[string]any{
"collections": map[string]any{"override": map[string]any{"release_version": "current"}},
})}
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{underlay}}}, Main: &main,
}})
if err != nil {
var loadErr *configkit.LoadError
if errors.As(err, &loadErr) {
t.Fatalf("construct inventory engine: %v: %+v", err, loadErr.Diagnostics())
}
t.Fatalf("construct inventory engine: %v", err)
}
scoped, err := engine.WithNamespaces(promptkit.Namespaces{Prompt: "missing", Profile: "beta", Backend: "alpha"})
if err != nil {
t.Fatal(err)
}
for _, check := range []struct {
name string
kinds []promptkit.ResourceKind
want []string
}{
{name: "union", want: []string{"alpha", "alpha-beta", "beta", "resource-only"}},
{name: "prompt", kinds: []promptkit.ResourceKind{promptkit.ResourcePrompt}, want: []string{"alpha", "alpha-beta"}},
{name: "profile", kinds: []promptkit.ResourceKind{promptkit.ResourceProfile}, want: []string{"alpha", "resource-only"}},
{name: "backend", kinds: []promptkit.ResourceKind{promptkit.ResourceBackend}, want: []string{"alpha", "beta"}},
{name: "repeated union", kinds: []promptkit.ResourceKind{promptkit.ResourcePrompt, promptkit.ResourceBackend, promptkit.ResourcePrompt}, want: []string{"alpha", "alpha-beta", "beta"}},
} {
t.Run(check.name, func(t *testing.T) {
got, err := scoped.ListNamespaces(context.Background(), check.kinds...)
if err != nil || !reflect.DeepEqual(got, check.want) {
t.Fatalf("namespace inventory = %#v, %v; want %#v", got, err, check.want)
}
got[0] = "caller change"
again, err := scoped.ListNamespaces(context.Background(), check.kinds...)
if err != nil || !reflect.DeepEqual(again, check.want) {
t.Fatalf("namespace inventory mutation escaped: %#v, %v", again, err)
}
})
}
if _, err := scoped.ListNamespaces(context.Background(), promptkit.ResourceKind("schema")); !errors.Is(err, promptkit.ErrInvalidRequest) {
t.Fatalf("unknown resource kind error = %v, want ErrInvalidRequest", err)
}
collections, err := scoped.ListCollections(context.Background())
if err != nil {
t.Fatal(err)
}
wantCollections := []promptkit.CollectionInfo{
{Namespace: "alpha"},
{Namespace: "alpha-beta"},
{Namespace: "beta"},
{Namespace: "empty-label", ReleaseVersion: stringPointerForTest("")},
{Namespace: "metadata-only", ReleaseVersion: stringPointerForTest("v1")},
{Namespace: "null-label"},
{Namespace: "override", ReleaseVersion: stringPointerForTest("current")},
{Namespace: "resource-only"},
}
if !reflect.DeepEqual(collections, wantCollections) {
t.Fatalf("collection inventory = %#v, want %#v", collections, wantCollections)
}
*collections[3].ReleaseVersion = "caller change"
collections[0].Namespace = "caller change"
collections, err = scoped.ListCollections(context.Background())
if err != nil || !reflect.DeepEqual(collections, wantCollections) {
t.Fatalf("collection inventory mutation escaped: %#v, %v", collections, err)
}
canceled, cancel := context.WithCancel(context.Background())
cancel()
if got, err := scoped.ListNamespaces(canceled); !errors.Is(err, context.Canceled) || len(got) != 0 {
t.Errorf("canceled namespace inventory = %#v, %v", got, err)
}
if got, err := scoped.ListCollections(canceled); !errors.Is(err, context.Canceled) || len(got) != 0 {
t.Errorf("canceled collection inventory = %#v, %v", got, err)
}
}
func TestListIdentitiesInvalidEngine(t *testing.T) {
var nilEngine *promptkit.Engine
var zeroEngine promptkit.Engine
@@ -252,6 +351,12 @@ func TestListIdentitiesInvalidEngine(t *testing.T) {
if _, err := engine.ListBackends(context.Background()); !errors.Is(err, promptkit.ErrInvalidConfig) {
t.Errorf("ListBackends on invalid engine: %v", err)
}
if _, err := engine.ListNamespaces(context.Background()); !errors.Is(err, promptkit.ErrInvalidConfig) {
t.Errorf("ListNamespaces on invalid engine: %v", err)
}
if _, err := engine.ListCollections(context.Background()); !errors.Is(err, promptkit.ErrInvalidConfig) {
t.Errorf("ListCollections on invalid engine: %v", err)
}
}
}