Add immutable namespace views
This commit is contained in:
217
namespaces_contract_test.go
Normal file
217
namespaces_contract_test.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package promptkit_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/ConfigKit"
|
||||
"gitea.maximumdirect.net/eric/promptkit"
|
||||
)
|
||||
|
||||
func TestWithNamespacesRoutesOrdinarySelectionsAndPreservesViews(t *testing.T) {
|
||||
client := &fakeLLMClient{response: &promptkit.GenerateResponse{Content: "reply"}}
|
||||
engine := newNamespacedContractEngine(t, client)
|
||||
alpha, err := engine.WithNamespaces(promptkit.Namespaces{Prompt: "alpha", Profile: "alpha"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
beta, err := engine.WithNamespaces(promptkit.Namespaces{Prompt: "beta", Profile: "beta"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
request := promptkit.RunRequest{PromptID: "shared", ProfileID: "shared"}
|
||||
prepared, err := alpha.Prepare(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Fatalf("prepare scoped request: %v", err)
|
||||
}
|
||||
assertNamespacedSelection(t, prepared.PromptID, prepared.SelectedProfileID, prepared.SelectedBackendID, "alpha")
|
||||
|
||||
betaPrepared, err := beta.Prepare(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Fatalf("prepare independent view: %v", err)
|
||||
}
|
||||
assertNamespacedSelection(t, betaPrepared.PromptID, betaPrepared.SelectedProfileID, betaPrepared.SelectedBackendID, "beta")
|
||||
if err := alpha.ValidateRequest(context.Background(), request); err != nil {
|
||||
t.Fatalf("validate scoped request: %v", err)
|
||||
}
|
||||
|
||||
qualified, err := alpha.Prepare(context.Background(), promptkit.RunRequest{PromptID: "beta/shared", ProfileID: "beta/shared"})
|
||||
if err != nil {
|
||||
t.Fatalf("prepare qualified bypass: %v", err)
|
||||
}
|
||||
assertNamespacedSelection(t, qualified.PromptID, qualified.SelectedProfileID, qualified.SelectedBackendID, "beta")
|
||||
|
||||
preparedExecution, err := alpha.PrepareExecution(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Fatalf("prepare execution: %v", err)
|
||||
}
|
||||
details := preparedExecution.Details()
|
||||
assertNamespacedSelection(t, details.PromptID, details.SelectedProfileID, details.SelectedBackendID, "alpha")
|
||||
// A handle captures its qualified selection and remains runnable through the
|
||||
// original facade because the views share the same assembled runner.
|
||||
result, err := engine.RunPrepared(context.Background(), preparedExecution)
|
||||
if err != nil {
|
||||
t.Fatalf("run scoped prepared handle through original view: %v", err)
|
||||
}
|
||||
assertNamespacedSelection(t, result.PromptID, result.SelectedProfileID, result.SelectedBackendID, "alpha")
|
||||
|
||||
var events []promptkit.ExecutionEvent
|
||||
runResult, err := alpha.Run(context.Background(), promptkit.RunRequest{
|
||||
PromptID: "shared", ProfileID: "shared",
|
||||
Observer: func(event promptkit.ExecutionEvent) { events = append(events, event) },
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("run scoped request: %v", err)
|
||||
}
|
||||
assertNamespacedSelection(t, runResult.PromptID, runResult.SelectedProfileID, runResult.SelectedBackendID, "alpha")
|
||||
var sawScopedEvent bool
|
||||
for _, event := range events {
|
||||
if event.PromptID == "alpha/shared" && event.ProfileID == "alpha/shared" && event.BackendID == "alpha/backend" {
|
||||
sawScopedEvent = true
|
||||
}
|
||||
}
|
||||
if !sawScopedEvent {
|
||||
t.Fatalf("observer events did not retain qualified identities: %+v", events)
|
||||
}
|
||||
|
||||
// The original engine remains unscoped and can still select the exact IDs.
|
||||
original, err := engine.Prepare(context.Background(), promptkit.RunRequest{PromptID: "root-only", ProfileID: "root-only"})
|
||||
if err != nil {
|
||||
t.Fatalf("original engine was changed by scoped views: %v", err)
|
||||
}
|
||||
if original.PromptID != "root-only" || original.SelectedProfileID != "root-only" || original.EffectiveModelParams.Model != "root-model" {
|
||||
t.Fatalf("original engine selection = %+v, want unqualified root resources", original)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithNamespacesHasNoFallbackAndValidatesAtTheCorrectBoundary(t *testing.T) {
|
||||
engine := newNamespacedContractEngine(t, &fakeLLMClient{response: &promptkit.GenerateResponse{Content: "reply"}})
|
||||
scoped, err := engine.WithNamespaces(promptkit.Namespaces{Prompt: "alpha", Profile: "alpha"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := scoped.Prepare(context.Background(), promptkit.RunRequest{PromptID: "root-only", ProfileID: "alpha/shared"}); !errors.Is(err, promptkit.ErrPromptNotFound) {
|
||||
t.Fatalf("root prompt fallback error = %v, want ErrPromptNotFound", err)
|
||||
}
|
||||
if _, err := scoped.Prepare(context.Background(), promptkit.RunRequest{PromptID: "alpha/shared", ProfileID: "root-only"}); !errors.Is(err, promptkit.ErrProfileNotFound) {
|
||||
t.Fatalf("root profile fallback error = %v, want ErrProfileNotFound", err)
|
||||
}
|
||||
unknown, err := engine.WithNamespaces(promptkit.Namespaces{Prompt: "not-configured"})
|
||||
if err != nil {
|
||||
t.Fatalf("well-formed absent namespace rejected at view creation: %v", err)
|
||||
}
|
||||
if _, err := unknown.Prepare(context.Background(), promptkit.RunRequest{PromptID: "root-only", ProfileID: "alpha/shared"}); !errors.Is(err, promptkit.ErrPromptNotFound) {
|
||||
t.Fatalf("selection from absent namespace error = %v, want ErrPromptNotFound", err)
|
||||
}
|
||||
if _, err := scoped.Prepare(context.Background(), promptkit.RunRequest{PromptID: "alpha/beta/gamma", ProfileID: "alpha/shared"}); !errors.Is(err, promptkit.ErrInvalidRequest) {
|
||||
t.Fatalf("malformed caller prompt ID error = %v, want ErrInvalidRequest", err)
|
||||
}
|
||||
for _, invalid := range []promptkit.Namespaces{
|
||||
{Prompt: "alpha/beta"},
|
||||
{Profile: "/alpha"},
|
||||
{Backend: " "},
|
||||
} {
|
||||
if _, err := engine.WithNamespaces(invalid); !errors.Is(err, promptkit.ErrInvalidConfig) {
|
||||
t.Errorf("invalid namespaces %+v error = %v, want ErrInvalidConfig", invalid, err)
|
||||
}
|
||||
}
|
||||
if _, err := (*promptkit.Engine)(nil).WithNamespaces(promptkit.Namespaces{}); !errors.Is(err, promptkit.ErrInvalidConfig) {
|
||||
t.Fatalf("nil engine error = %v, want ErrInvalidConfig", err)
|
||||
}
|
||||
cleared, err := scoped.WithNamespaces(promptkit.Namespaces{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := cleared.Prepare(context.Background(), promptkit.RunRequest{PromptID: "shared", ProfileID: "alpha/shared"}); !errors.Is(err, promptkit.ErrPromptNotFound) {
|
||||
t.Fatalf("cleared view unexpectedly retained prior scope: %v", err)
|
||||
}
|
||||
if _, err := scoped.Prepare(context.Background(), promptkit.RunRequest{PromptID: "shared", ProfileID: "shared"}); err != nil {
|
||||
t.Fatalf("replacing scopes mutated the prior view: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithNamespacesViewsCanPrepareConcurrently(t *testing.T) {
|
||||
engine := newNamespacedContractEngine(t, &fakeLLMClient{response: &promptkit.GenerateResponse{Content: "reply"}})
|
||||
alpha, err := engine.WithNamespaces(promptkit.Namespaces{Prompt: "alpha", Profile: "alpha"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
beta, err := engine.WithNamespaces(promptkit.Namespaces{Prompt: "beta", Profile: "beta"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var wait sync.WaitGroup
|
||||
for _, view := range []*promptkit.Engine{alpha, beta} {
|
||||
view := view
|
||||
wait.Add(1)
|
||||
go func() {
|
||||
defer wait.Done()
|
||||
prepared, err := view.Prepare(context.Background(), promptkit.RunRequest{PromptID: "shared", ProfileID: "shared"})
|
||||
if err != nil {
|
||||
t.Errorf("concurrent prepare: %v", err)
|
||||
return
|
||||
}
|
||||
want := "alpha"
|
||||
if view == beta {
|
||||
want = "beta"
|
||||
}
|
||||
assertNamespacedSelection(t, prepared.PromptID, prepared.SelectedProfileID, prepared.SelectedBackendID, want)
|
||||
}()
|
||||
}
|
||||
wait.Wait()
|
||||
}
|
||||
|
||||
func newNamespacedContractEngine(t *testing.T, client promptkit.LLMClient) *promptkit.Engine {
|
||||
t.Helper()
|
||||
var inputs []configkit.Input
|
||||
for _, namespace := range []string{"alpha", "beta"} {
|
||||
endpoint := "https://" + namespace + ".example/v1"
|
||||
input, err := promptkit.CollectionInput(namespace, configkit.Mapping(namespace+" resources", map[string]any{
|
||||
"backends": map[string]any{"backend": map[string]any{"endpoint": endpoint}},
|
||||
"profiles": map[string]any{"shared": map[string]any{
|
||||
"backend": namespace + "/backend", "model": namespace + "-model",
|
||||
}},
|
||||
"prompts": map[string]any{"shared": map[string]any{
|
||||
"version": "1",
|
||||
"messages": []any{map[string]any{"role": "user", "content": "Hello"}},
|
||||
"output": map[string]any{"format": "text", "validation_mode": "none"},
|
||||
}},
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
input.Imports = false
|
||||
inputs = append(inputs, input)
|
||||
}
|
||||
inputs = append(inputs, configkit.Input{Source: configkit.Mapping("root resources", map[string]any{
|
||||
"profiles": map[string]any{"root-only": map[string]any{
|
||||
"endpoint": "https://root.example/v1", "model": "root-model",
|
||||
}},
|
||||
"prompts": map[string]any{"root-only": map[string]any{
|
||||
"version": "1",
|
||||
"messages": []any{map[string]any{"role": "user", "content": "Root prompt"}},
|
||||
"output": map[string]any{"format": "text", "validation_mode": "none"},
|
||||
}},
|
||||
})})
|
||||
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
|
||||
Underlays: []configkit.Underlay{{Inputs: inputs}},
|
||||
}}, promptkit.WithLLMClient(client))
|
||||
if err != nil {
|
||||
var loadErr *configkit.LoadError
|
||||
if errors.As(err, &loadErr) {
|
||||
t.Fatalf("construct namespaced engine: %v: %+v", err, loadErr.Diagnostics())
|
||||
}
|
||||
t.Fatalf("construct namespaced engine: %v", err)
|
||||
}
|
||||
return engine
|
||||
}
|
||||
|
||||
func assertNamespacedSelection(t *testing.T, promptID, profileID, backendID, namespace string) {
|
||||
t.Helper()
|
||||
if promptID != namespace+"/shared" || profileID != namespace+"/shared" || backendID != namespace+"/backend" {
|
||||
t.Fatalf("selected IDs = (%q, %q, %q), want namespace %q", promptID, profileID, backendID, namespace)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user