Verify identified resource composition and inheritance
This commit is contained in:
@@ -647,6 +647,13 @@ configuration fixtures not yet converted.
|
|||||||
Validation: real-loader tests assert effective behavior and structured diagnostic
|
Validation: real-loader tests assert effective behavior and structured diagnostic
|
||||||
identity/paths as appropriate, not complete upstream error wording or ordering.
|
identity/paths as appropriate, not complete upstream error wording or ordering.
|
||||||
|
|
||||||
|
Completion: loader fixtures now exercise identified lists, inherited records,
|
||||||
|
strict duplicates, invalid identities and parent references, all six registered
|
||||||
|
destinations, final identity enforcement, layered upsert/append/replace, and
|
||||||
|
request completion of partial profiles. The focused real-loader composition,
|
||||||
|
target, validation, and syntax tests passed on Go 1.27.0. Header and remaining
|
||||||
|
adapter fixtures are handled in the next assignment.
|
||||||
|
|
||||||
## Stage 14: Complete Header, Origin, And Adapter Fixture Coverage
|
## Stage 14: Complete Header, Origin, And Adapter Fixture Coverage
|
||||||
|
|
||||||
Scope: remaining `internal/configuration` prompt, inspection, listing, and
|
Scope: remaining `internal/configuration` prompt, inspection, listing, and
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package configuration
|
package configuration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -33,6 +34,265 @@ func TestLoadIdentifiedResourceLists(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIdentifiedResourcesComposeAcrossNamespacesAndKinds(t *testing.T) {
|
||||||
|
resources := func() map[string]any {
|
||||||
|
return map[string]any{
|
||||||
|
"backends": []any{
|
||||||
|
map[string]any{"id": "shared", "endpoint": "https://example.invalid/v1"},
|
||||||
|
map[string]any{"id": "child", "extends": map[string]any{"id": "shared"}},
|
||||||
|
},
|
||||||
|
"profiles": []any{
|
||||||
|
map[string]any{"id": "shared", "model": "model"},
|
||||||
|
map[string]any{"id": "child", "extends": map[string]any{"id": "shared"}},
|
||||||
|
},
|
||||||
|
"prompts": []any{
|
||||||
|
map[string]any{"id": "shared", "messages": []any{map[string]any{"role": "user", "content": "inherited"}},
|
||||||
|
"output": map[string]any{"format": "text", "validation_mode": "basic"}},
|
||||||
|
map[string]any{"id": "child", "extends": map[string]any{"id": "shared"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values := resources()
|
||||||
|
values["collections"] = map[string]any{"*": resources(), "123": resources()}
|
||||||
|
state, err := load(mapping("identified namespaces", values), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, prefix := range []string{"", "*/", "123/"} {
|
||||||
|
backend, err := state.registry.GetBackend(prefix + "child")
|
||||||
|
if err != nil || backend.ID != prefix+"child" || backend.Endpoint != "https://example.invalid/v1" {
|
||||||
|
t.Fatalf("inherited backend %q = %+v, %v", prefix, backend, err)
|
||||||
|
}
|
||||||
|
profile, err := state.GetProfileIdentity(context.Background(), prefix+"child")
|
||||||
|
if err != nil || profile != prefix+"child" {
|
||||||
|
t.Fatalf("inherited profile %q = %q, %v", prefix, profile, err)
|
||||||
|
}
|
||||||
|
definition, _, err := state.prompt(prefix + "child")
|
||||||
|
if err != nil || definition.ID != prefix+"child" || len(definition.Templates) != 1 || definition.Templates[0].Content != "inherited" {
|
||||||
|
t.Fatalf("inherited prompt %q = %+v, %v", prefix, definition, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIdentifiedResourcesRejectInvalidInheritance(t *testing.T) {
|
||||||
|
for _, kind := range []string{"backends", "profiles", "prompts"} {
|
||||||
|
for name, records := range map[string][]any{
|
||||||
|
"missing": {map[string]any{"id": "child", "extends": map[string]any{"id": "missing"}}},
|
||||||
|
"self": {map[string]any{"id": "child", "extends": map[string]any{"id": "child"}}},
|
||||||
|
"cycle": {
|
||||||
|
map[string]any{"id": "first", "extends": map[string]any{"id": "second"}},
|
||||||
|
map[string]any{"id": "second", "extends": map[string]any{"id": "first"}},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(kind+"/"+name, func(t *testing.T) {
|
||||||
|
_, err := load(mapping("invalid inheritance", map[string]any{kind: records}), nil)
|
||||||
|
var loadErr *configkit.LoadError
|
||||||
|
if !errors.As(err, &loadErr) {
|
||||||
|
t.Fatalf("load error = %v, want an upstream inheritance diagnostic", err)
|
||||||
|
}
|
||||||
|
for _, diagnostic := range loadErr.Diagnostics() {
|
||||||
|
if diagnostic.Code == configkit.CodeInheritanceMissing || diagnostic.Code == configkit.CodeInheritanceCycle {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("missing inheritance diagnostic: %+v", loadErr.Diagnostics())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIdentifiedResourcesRejectInvalidLocalIDs(t *testing.T) {
|
||||||
|
for _, kind := range []string{"backends", "profiles", "prompts"} {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
record map[string]any
|
||||||
|
}{
|
||||||
|
{name: "missing", record: map[string]any{}},
|
||||||
|
{name: "null", record: map[string]any{"id": nil}},
|
||||||
|
{name: "non-string", record: map[string]any{"id": 42}},
|
||||||
|
{name: "blank", record: map[string]any{"id": " "}},
|
||||||
|
{name: "slash", record: map[string]any{"id": "not/local"}},
|
||||||
|
} {
|
||||||
|
t.Run(kind+"/"+test.name, func(t *testing.T) {
|
||||||
|
_, err := load(mapping("invalid resource ID", map[string]any{kind: []any{test.record}}), nil)
|
||||||
|
var loadErr *configkit.LoadError
|
||||||
|
if !errors.As(err, &loadErr) || len(loadErr.Diagnostics()) == 0 {
|
||||||
|
t.Fatalf("load error = %v, want a path-aware error", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIdentifiedResourcesRejectDuplicateStrictPeers(t *testing.T) {
|
||||||
|
for _, kind := range []string{"backends", "profiles", "prompts"} {
|
||||||
|
for _, namespace := range []string{"", "*"} {
|
||||||
|
t.Run(kind+"/"+namespace, func(t *testing.T) {
|
||||||
|
values := func() map[string]any {
|
||||||
|
records := []any{map[string]any{"id": "duplicate"}}
|
||||||
|
if namespace == "" {
|
||||||
|
return map[string]any{kind: records}
|
||||||
|
}
|
||||||
|
return map[string]any{"collections": map[string]any{namespace: map[string]any{kind: records}}}
|
||||||
|
}
|
||||||
|
_, err := load(configkit.Input{Source: configkit.NoSource()}, []configkit.Underlay{{Inputs: []configkit.Input{
|
||||||
|
mapping("first", values()), mapping("second", values()),
|
||||||
|
}}})
|
||||||
|
var loadErr *configkit.LoadError
|
||||||
|
if !errors.As(err, &loadErr) {
|
||||||
|
t.Fatalf("load error = %v, want an upstream duplicate diagnostic", err)
|
||||||
|
}
|
||||||
|
for _, diagnostic := range loadErr.Diagnostics() {
|
||||||
|
if diagnostic.Code == configkit.CodeComposeConflict {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("missing duplicate conflict: %+v", loadErr.Diagnostics())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFinalIdentitiesAreCheckedAfterApplicationResolution(t *testing.T) {
|
||||||
|
for _, kind := range []string{"backends", "profiles", "prompts"} {
|
||||||
|
for _, namespace := range []string{"", "*"} {
|
||||||
|
t.Run(kind+"/"+namespace, func(t *testing.T) {
|
||||||
|
definition := configurationDefinition(&loaded{})
|
||||||
|
definition.Resolve = func(config *document, resolution *configkit.Resolution) error {
|
||||||
|
path := configkit.Keys(kind)
|
||||||
|
if namespace == "" {
|
||||||
|
switch kind {
|
||||||
|
case "backends":
|
||||||
|
config.Backends = []backendConfig{{ID: "same"}, {ID: "same"}}
|
||||||
|
case "profiles":
|
||||||
|
config.Profiles = []profileConfig{{ID: "same"}, {ID: "same"}}
|
||||||
|
case "prompts":
|
||||||
|
config.Prompts = []promptConfig{{ID: "same"}, {ID: "same"}}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
collection := collectionConfig{}
|
||||||
|
switch kind {
|
||||||
|
case "backends":
|
||||||
|
collection.Backends = []backendConfig{{ID: "same"}, {ID: "same"}}
|
||||||
|
case "profiles":
|
||||||
|
collection.Profiles = []profileConfig{{ID: "same"}, {ID: "same"}}
|
||||||
|
case "prompts":
|
||||||
|
collection.Prompts = []promptConfig{{ID: "same"}, {ID: "same"}}
|
||||||
|
}
|
||||||
|
config.Collections = map[string]collectionConfig{namespace: collection}
|
||||||
|
path = configkit.Keys("collections", namespace, kind)
|
||||||
|
}
|
||||||
|
resolution.RecordNormalization(path, "supply resolved records")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
input := configkit.Input{Source: configkit.NoSource()}
|
||||||
|
if namespace != "" {
|
||||||
|
input = mapping("existing collection", map[string]any{"collections": map[string]any{
|
||||||
|
namespace: map[string]any{kind: []any{}},
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
_, err := configkit.LoadLayered(input, configkit.LayeredOptions[document]{Definition: definition})
|
||||||
|
var loadErr *configkit.LoadError
|
||||||
|
if !errors.As(err, &loadErr) {
|
||||||
|
t.Fatalf("load error = %v, want a final identity diagnostic", err)
|
||||||
|
}
|
||||||
|
for _, diagnostic := range loadErr.Diagnostics() {
|
||||||
|
if diagnostic.Code == configkit.CodeSequenceConflict {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("missing final identity conflict: %+v", loadErr.Diagnostics())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIdentifiedResourceLayerOperations(t *testing.T) {
|
||||||
|
base := mapping("base profiles", map[string]any{"profiles": []any{
|
||||||
|
map[string]any{"id": "first", "model": "original", "service_tier": "standard"},
|
||||||
|
map[string]any{"id": "second", "model": "retained"},
|
||||||
|
}})
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
mode string
|
||||||
|
records []any
|
||||||
|
models map[string]string
|
||||||
|
}{
|
||||||
|
{name: "whole record upsert", records: []any{map[string]any{"id": "first", "model": "updated"}}, models: map[string]string{"first": "updated", "second": "retained"}},
|
||||||
|
{name: "empty upsert", records: []any{}, models: map[string]string{"first": "original", "second": "retained"}},
|
||||||
|
{name: "append", mode: "append", records: []any{map[string]any{"id": "third", "model": "added"}}, models: map[string]string{"first": "original", "second": "retained", "third": "added"}},
|
||||||
|
{name: "replace", mode: "replace", records: []any{map[string]any{"id": "third", "model": "replacement"}}, models: map[string]string{"third": "replacement"}},
|
||||||
|
{name: "clear", mode: "replace", records: []any{}, models: map[string]string{}},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
values := map[string]any{"profiles": test.records}
|
||||||
|
if test.mode != "" {
|
||||||
|
values["config"] = map[string]any{"sequences": []any{map[string]any{
|
||||||
|
"destination": []any{"profiles"}, "mode": test.mode,
|
||||||
|
}}}
|
||||||
|
}
|
||||||
|
state, err := load(mapping("overlay profiles", values), []configkit.Underlay{{Inputs: []configkit.Input{base}}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
profiles := indexedProfiles(&state.result.Config)
|
||||||
|
if len(profiles) != len(test.models) {
|
||||||
|
t.Fatalf("profiles = %+v, want IDs and models %+v", profiles, test.models)
|
||||||
|
}
|
||||||
|
for id, model := range test.models {
|
||||||
|
profile, ok := profiles[id]
|
||||||
|
if !ok || profile.Model == nil || *profile.Model != model {
|
||||||
|
t.Fatalf("profile %q = %+v, want model %q", id, profile, model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if test.name == "whole record upsert" && profiles["first"].ServiceTier != nil {
|
||||||
|
t.Fatalf("upsert retained a displaced field: %+v", profiles["first"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIdentifiedResourceLayersRejectDisplacedParentAndDuplicateAppend(t *testing.T) {
|
||||||
|
base := mapping("base", map[string]any{"profiles": []any{
|
||||||
|
map[string]any{"id": "shared", "model": "original"},
|
||||||
|
}})
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
values map[string]any
|
||||||
|
code configkit.Code
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "self extension cannot recover displaced record",
|
||||||
|
values: map[string]any{"profiles": []any{
|
||||||
|
map[string]any{"id": "shared", "extends": map[string]any{"id": "shared"}},
|
||||||
|
}},
|
||||||
|
code: configkit.CodeInheritanceCycle,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "append cannot duplicate an identity",
|
||||||
|
values: map[string]any{
|
||||||
|
"config": map[string]any{"sequences": []any{map[string]any{"destination": []any{"profiles"}, "mode": "append"}}},
|
||||||
|
"profiles": []any{map[string]any{"id": "shared", "model": "other"}},
|
||||||
|
},
|
||||||
|
code: configkit.CodeSequenceConflict,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
_, err := load(mapping("overlay", test.values), []configkit.Underlay{{Inputs: []configkit.Input{base}}})
|
||||||
|
var loadErr *configkit.LoadError
|
||||||
|
if !errors.As(err, &loadErr) {
|
||||||
|
t.Fatalf("load error = %v, want structured diagnostic", err)
|
||||||
|
}
|
||||||
|
for _, diagnostic := range loadErr.Diagnostics() {
|
||||||
|
if diagnostic.Code == test.code {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("missing %s diagnostic: %+v", test.code, loadErr.Diagnostics())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadChecksOptionalConfigurationVersion(t *testing.T) {
|
func TestLoadChecksOptionalConfigurationVersion(t *testing.T) {
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
name string
|
name string
|
||||||
@@ -97,7 +357,7 @@ func TestLoadConsumesAcceptedUnderlayVersionAndValidatesHeaderlessPayload(t *tes
|
|||||||
}
|
}
|
||||||
|
|
||||||
invalidFragment := mapping("headerless prompt fragment", map[string]any{
|
invalidFragment := mapping("headerless prompt fragment", map[string]any{
|
||||||
"prompts": map[string]any{"incomplete": map[string]any{"description": "still validated"}},
|
"prompts": []any{map[string]any{"id": "incomplete", "description": "still validated"}},
|
||||||
})
|
})
|
||||||
if _, err := load(configkit.Input{Source: configkit.NoSource()}, []configkit.Underlay{{Inputs: []configkit.Input{invalidFragment}}}); err == nil || !strings.Contains(err.Error(), "at least one message is required") {
|
if _, err := load(configkit.Input{Source: configkit.NoSource()}, []configkit.Underlay{{Inputs: []configkit.Input{invalidFragment}}}); err == nil || !strings.Contains(err.Error(), "at least one message is required") {
|
||||||
t.Fatalf("headerless fragment validation error = %v, want final prompt validation", err)
|
t.Fatalf("headerless fragment validation error = %v, want final prompt validation", err)
|
||||||
@@ -106,30 +366,25 @@ func TestLoadConsumesAcceptedUnderlayVersionAndValidatesHeaderlessPayload(t *tes
|
|||||||
|
|
||||||
func TestLoadComposesLiteralKeysAndEffectivePresence(t *testing.T) {
|
func TestLoadComposesLiteralKeysAndEffectivePresence(t *testing.T) {
|
||||||
base := mapping("defaults", map[string]any{
|
base := mapping("defaults", map[string]any{
|
||||||
"version": int64(1),
|
"version": int64(2),
|
||||||
"backends": map[string]any{
|
"backends": []any{map[string]any{"id": "local.v1", "endpoint": "http://localhost:8080/v1", "concurrency_limit": 2}},
|
||||||
"local.v1": map[string]any{"endpoint": "http://localhost:8080/v1", "concurrency_limit": 2},
|
|
||||||
},
|
|
||||||
"collections": map[string]any{"base": map[string]any{
|
"collections": map[string]any{"base": map[string]any{
|
||||||
"release_version": "",
|
"release_version": "",
|
||||||
"backends": map[string]any{
|
"backends": []any{map[string]any{"id": "local.v1", "endpoint": "http://localhost:8080/v1", "concurrency_limit": 3}},
|
||||||
"local.v1": map[string]any{"endpoint": "http://localhost:8080/v1", "concurrency_limit": 3},
|
"profiles": []any{
|
||||||
},
|
map[string]any{"id": "one", "backend": "local.v1", "temperature": 0.8, "top_p": 0.9, "api_key_required": true,
|
||||||
"profiles": map[string]any{
|
|
||||||
"one": map[string]any{"backend": "local.v1", "temperature": 0.8, "top_p": 0.9, "api_key_required": true,
|
|
||||||
"extra_params": map[string]any{"provider": map[string]any{"first": 1, "second": 2}}},
|
"extra_params": map[string]any{"provider": map[string]any{"first": 1, "second": 2}}},
|
||||||
"child.v1": map[string]any{"extends": "one"},
|
map[string]any{"id": "child.v1", "extends": map[string]any{"id": "one"}},
|
||||||
"namespaced": map[string]any{"backend": "base/local.v1", "model": "collection-model"},
|
map[string]any{"id": "namespaced", "backend": "base/local.v1", "model": "collection-model"},
|
||||||
},
|
},
|
||||||
}, "invoice": map[string]any{
|
}, "invoice": map[string]any{
|
||||||
"release_version": "r7",
|
"release_version": "r7",
|
||||||
"prompts": map[string]any{
|
"prompts": []any{
|
||||||
"summary": map[string]any{
|
map[string]any{"id": "summary",
|
||||||
"version": "v1.0",
|
|
||||||
"messages": []any{map[string]any{"role": "user", "content": "Summarize."}},
|
"messages": []any{map[string]any{"role": "user", "content": "Summarize."}},
|
||||||
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
||||||
},
|
},
|
||||||
"version": map[string]any{
|
map[string]any{"id": "version",
|
||||||
"messages": []any{map[string]any{"role": "user", "content": "Nested ID."}},
|
"messages": []any{map[string]any{"role": "user", "content": "Nested ID."}},
|
||||||
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
||||||
},
|
},
|
||||||
@@ -137,19 +392,23 @@ func TestLoadComposesLiteralKeysAndEffectivePresence(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
overlay := mapping("operator", map[string]any{
|
overlay := mapping("operator", map[string]any{
|
||||||
"backends": map[string]any{"local.v1": map[string]any{"queue_capacity": 0}},
|
"backends": []any{map[string]any{"id": "local.v1", "endpoint": "http://localhost:8080/v1", "concurrency_limit": 2, "queue_capacity": 0}},
|
||||||
"collections": map[string]any{"base": map[string]any{"profiles": map[string]any{"child.v1": map[string]any{
|
"collections": map[string]any{"base": map[string]any{"profiles": []any{map[string]any{"id": "child.v1", "extends": map[string]any{"id": "one"},
|
||||||
"temperature": 0, "top_p": nil, "api_key_required": false,
|
"temperature": 0, "top_p": nil, "api_key_required": false,
|
||||||
"extra_params": map[string]any{"provider": map[string]any{"second": 3}},
|
"extra_params": map[string]any{"provider": map[string]any{"second": 3}},
|
||||||
}}}},
|
}}}},
|
||||||
})
|
})
|
||||||
loaded, err := load(mapping("main", map[string]any{"version": "1"}), []configkit.Underlay{
|
loaded, err := load(mapping("main", map[string]any{"version": "2"}), []configkit.Underlay{
|
||||||
{Inputs: []configkit.Input{base}}, {Inputs: []configkit.Input{overlay}},
|
{Inputs: []configkit.Input{base}}, {Inputs: []configkit.Input{overlay}},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
var loadErr *configkit.LoadError
|
||||||
|
if errors.As(err, &loadErr) {
|
||||||
|
t.Fatalf("load: %v; diagnostics: %+v", err, loadErr.Diagnostics())
|
||||||
|
}
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
profile := loaded.result.Config.Collections["base"].Profiles["child.v1"]
|
profile := loaded.result.Config.Collections["base"].Profiles[1]
|
||||||
if profile.Temperature == nil || *profile.Temperature != 0 || profile.TopP != nil || profile.APIKeyRequired == nil || *profile.APIKeyRequired {
|
if profile.Temperature == nil || *profile.Temperature != 0 || profile.TopP != nil || profile.APIKeyRequired == nil || *profile.APIKeyRequired {
|
||||||
t.Fatalf("effective values were not retained: %+v", profile)
|
t.Fatalf("effective values were not retained: %+v", profile)
|
||||||
}
|
}
|
||||||
@@ -160,7 +419,7 @@ func TestLoadComposesLiteralKeysAndEffectivePresence(t *testing.T) {
|
|||||||
if provider["first"] != int64(1) || provider["second"] != int64(3) {
|
if provider["first"] != int64(1) || provider["second"] != int64(3) {
|
||||||
t.Fatalf("recursive overlay failed: %+v", provider)
|
t.Fatalf("recursive overlay failed: %+v", provider)
|
||||||
}
|
}
|
||||||
backend := loaded.result.Config.Backends["local.v1"]
|
backend := loaded.result.Config.Backends[0]
|
||||||
if backend.QueueCapacity == nil || *backend.QueueCapacity != 0 || loaded.backends["local.v1"]["queue_capacity"] != fieldValue {
|
if backend.QueueCapacity == nil || *backend.QueueCapacity != 0 || loaded.backends["local.v1"]["queue_capacity"] != fieldValue {
|
||||||
t.Fatalf("explicit zero queue capacity was lost: %+v", backend)
|
t.Fatalf("explicit zero queue capacity was lost: %+v", backend)
|
||||||
}
|
}
|
||||||
@@ -171,10 +430,10 @@ func TestLoadComposesLiteralKeysAndEffectivePresence(t *testing.T) {
|
|||||||
if !exists || collectionProfile.ID != "base/namespaced" || collectionProfile.ResolvedTarget == nil || collectionProfile.ResolvedTarget.BackendID != "base/local.v1" {
|
if !exists || collectionProfile.ID != "base/namespaced" || collectionProfile.ResolvedTarget == nil || collectionProfile.ResolvedTarget.BackendID != "base/local.v1" {
|
||||||
t.Fatalf("collection profile snapshot = %+v, exists %t", collectionProfile, exists)
|
t.Fatalf("collection profile snapshot = %+v, exists %t", collectionProfile, exists)
|
||||||
}
|
}
|
||||||
if loaded.result.Config.Collections["invoice"].Prompts["summary"].Messages[0].Content == nil {
|
if loaded.result.Config.Collections["invoice"].Prompts[0].Messages[0].Content == nil {
|
||||||
t.Fatal("literal prompt content was not decoded")
|
t.Fatal("literal prompt content was not decoded")
|
||||||
}
|
}
|
||||||
if loaded.result.Config.Version == nil || *loaded.result.Config.Version != "1" ||
|
if loaded.result.Config.Version == nil || *loaded.result.Config.Version != "2" ||
|
||||||
loaded.result.Config.Collections["base"].ReleaseVersion == nil || *loaded.result.Config.Collections["base"].ReleaseVersion != "" ||
|
loaded.result.Config.Collections["base"].ReleaseVersion == nil || *loaded.result.Config.Collections["base"].ReleaseVersion != "" ||
|
||||||
loaded.result.Config.Collections["invoice"].ReleaseVersion == nil || *loaded.result.Config.Collections["invoice"].ReleaseVersion != "r7" {
|
loaded.result.Config.Collections["invoice"].ReleaseVersion == nil || *loaded.result.Config.Collections["invoice"].ReleaseVersion != "r7" {
|
||||||
t.Fatalf("root or collection version metadata was not retained: %+v", loaded.result.Config)
|
t.Fatalf("root or collection version metadata was not retained: %+v", loaded.result.Config)
|
||||||
@@ -197,20 +456,20 @@ func TestLoadRejectsInvalidCollectionAndResourceKeys(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "root resource with separator",
|
name: "root resource with separator",
|
||||||
values: map[string]any{"backends": map[string]any{
|
values: map[string]any{"backends": []any{
|
||||||
"bad/name": map[string]any{"endpoint": "https://example.test/v1"},
|
map[string]any{"id": "bad/name", "endpoint": "https://example.test/v1"},
|
||||||
}},
|
}},
|
||||||
message: "backend key must be a nonblank local key without a slash",
|
message: "backend ID must be a nonblank local key without a slash",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "collection resource with separator",
|
name: "collection resource with separator",
|
||||||
values: map[string]any{"collections": map[string]any{"good": map[string]any{
|
values: map[string]any{"collections": map[string]any{"good": map[string]any{
|
||||||
"prompts": map[string]any{"bad/name": map[string]any{
|
"prompts": []any{map[string]any{"id": "bad/name",
|
||||||
"messages": []any{map[string]any{"role": "user", "content": "Hello."}},
|
"messages": []any{map[string]any{"role": "user", "content": "Hello."}},
|
||||||
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
||||||
}},
|
}},
|
||||||
}}},
|
}}},
|
||||||
message: "prompt key must be a nonblank local key without a slash",
|
message: "prompt ID must be a nonblank local key without a slash",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
@@ -267,17 +526,17 @@ func TestLoadBackendSmokeTestsOmissionAndNull(t *testing.T) {
|
|||||||
{name: "null", smokeTests: nil, include: true, inherited: true},
|
{name: "null", smokeTests: nil, include: true, inherited: true},
|
||||||
} {
|
} {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
backend := map[string]any{"endpoint": "https://backend.example/v1"}
|
backend := map[string]any{"id": "local", "endpoint": "https://backend.example/v1"}
|
||||||
if test.include {
|
if test.include {
|
||||||
backend["smoke_tests"] = test.smokeTests
|
backend["smoke_tests"] = test.smokeTests
|
||||||
}
|
}
|
||||||
main := mapping("backend", map[string]any{
|
main := mapping("backend", map[string]any{
|
||||||
"backends": map[string]any{"local": backend},
|
"backends": []any{backend},
|
||||||
})
|
})
|
||||||
var underlays []configkit.Underlay
|
var underlays []configkit.Underlay
|
||||||
if test.inherited {
|
if test.inherited {
|
||||||
underlays = []configkit.Underlay{{Inputs: []configkit.Input{mapping("defaults", map[string]any{
|
underlays = []configkit.Underlay{{Inputs: []configkit.Input{mapping("defaults", map[string]any{
|
||||||
"backends": map[string]any{"local": map[string]any{
|
"backends": []any{map[string]any{"id": "local",
|
||||||
"endpoint": "https://backend.example/v1",
|
"endpoint": "https://backend.example/v1",
|
||||||
"smoke_tests": map[string]any{"default_profile": "inherited-profile"},
|
"smoke_tests": map[string]any{"default_profile": "inherited-profile"},
|
||||||
}},
|
}},
|
||||||
@@ -287,7 +546,7 @@ func TestLoadBackendSmokeTestsOmissionAndNull(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if got := loaded.result.Config.Backends["local"].SmokeTests; got != nil {
|
if got := loaded.result.Config.Backends[0].SmokeTests; got != nil {
|
||||||
t.Fatalf("smoke_tests = %+v, want nil", got)
|
t.Fatalf("smoke_tests = %+v, want nil", got)
|
||||||
}
|
}
|
||||||
selected, err := loaded.registry.GetBackend("local")
|
selected, err := loaded.registry.GetBackend("local")
|
||||||
@@ -303,7 +562,7 @@ func TestLoadBackendSmokeTestsOmissionAndNull(t *testing.T) {
|
|||||||
|
|
||||||
func TestLoadMergesBackendSmokeTestsAndRetainsFallbacks(t *testing.T) {
|
func TestLoadMergesBackendSmokeTestsAndRetainsFallbacks(t *testing.T) {
|
||||||
base := mapping("defaults", map[string]any{
|
base := mapping("defaults", map[string]any{
|
||||||
"backends": map[string]any{"local": map[string]any{
|
"backends": []any{map[string]any{"id": "catalog",
|
||||||
"endpoint": "https://backend.example/v1",
|
"endpoint": "https://backend.example/v1",
|
||||||
"smoke_tests": map[string]any{
|
"smoke_tests": map[string]any{
|
||||||
"default_profile": "catalog-profile",
|
"default_profile": "catalog-profile",
|
||||||
@@ -316,7 +575,7 @@ func TestLoadMergesBackendSmokeTestsAndRetainsFallbacks(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
overlay := mapping("operator", map[string]any{
|
overlay := mapping("operator", map[string]any{
|
||||||
"backends": map[string]any{"local": map[string]any{
|
"backends": []any{map[string]any{"id": "local", "extends": map[string]any{"id": "catalog"},
|
||||||
"smoke_tests": map[string]any{
|
"smoke_tests": map[string]any{
|
||||||
"default_profile": "not-yet-listed",
|
"default_profile": "not-yet-listed",
|
||||||
"authentication": map[string]any{"kind": " NONE ", "endpoint": nil},
|
"authentication": map[string]any{"kind": " NONE ", "endpoint": nil},
|
||||||
@@ -360,37 +619,37 @@ func TestLoadRejectsInvalidFinalBackendSmokeTests(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "blank default profile",
|
name: "blank default profile",
|
||||||
data: map[string]any{"default_profile": " \t "},
|
data: map[string]any{"default_profile": " \t "},
|
||||||
path: "/backends/local/smoke_tests/default_profile",
|
path: "/backends/0/smoke_tests/default_profile",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "authentication kind required",
|
name: "authentication kind required",
|
||||||
data: map[string]any{"authentication": map[string]any{}},
|
data: map[string]any{"authentication": map[string]any{}},
|
||||||
path: "/backends/local/smoke_tests/authentication/kind",
|
path: "/backends/0/smoke_tests/authentication/kind",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unknown authentication kind",
|
name: "unknown authentication kind",
|
||||||
data: map[string]any{"authentication": map[string]any{"kind": "bearer"}},
|
data: map[string]any{"authentication": map[string]any{"kind": "bearer"}},
|
||||||
path: "/backends/local/smoke_tests/authentication/kind",
|
path: "/backends/0/smoke_tests/authentication/kind",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-http reachability URL",
|
name: "non-http reachability URL",
|
||||||
data: map[string]any{"reachability": map[string]any{"endpoint": "ftp://probe.example/ready"}},
|
data: map[string]any{"reachability": map[string]any{"endpoint": "ftp://probe.example/ready"}},
|
||||||
path: "/backends/local/smoke_tests/reachability/endpoint",
|
path: "/backends/0/smoke_tests/reachability/endpoint",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "blank authentication URL",
|
name: "blank authentication URL",
|
||||||
data: map[string]any{"authentication": map[string]any{"kind": "none", "endpoint": " "}},
|
data: map[string]any{"authentication": map[string]any{"kind": "none", "endpoint": " "}},
|
||||||
path: "/backends/local/smoke_tests/authentication/endpoint",
|
path: "/backends/0/smoke_tests/authentication/endpoint",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "relative authentication URL",
|
name: "relative authentication URL",
|
||||||
data: map[string]any{"authentication": map[string]any{"kind": "none", "endpoint": "/key"}},
|
data: map[string]any{"authentication": map[string]any{"kind": "none", "endpoint": "/key"}},
|
||||||
path: "/backends/local/smoke_tests/authentication/endpoint",
|
path: "/backends/0/smoke_tests/authentication/endpoint",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
_, err := load(mapping("backend", map[string]any{
|
_, err := load(mapping("backend", map[string]any{
|
||||||
"backends": map[string]any{"local": map[string]any{
|
"backends": []any{map[string]any{"id": "local",
|
||||||
"endpoint": "https://backend.example/v1",
|
"endpoint": "https://backend.example/v1",
|
||||||
"smoke_tests": test.data,
|
"smoke_tests": test.data,
|
||||||
}},
|
}},
|
||||||
@@ -455,33 +714,33 @@ func TestLoadRejectsStrictGroupConflict(t *testing.T) {
|
|||||||
|
|
||||||
func TestLoadValidatesOnlyFinalDomainValues(t *testing.T) {
|
func TestLoadValidatesOnlyFinalDomainValues(t *testing.T) {
|
||||||
underlay := mapping("defaults", map[string]any{
|
underlay := mapping("defaults", map[string]any{
|
||||||
"backends": map[string]any{"local": map[string]any{"endpoint": "not a URL"}},
|
"backends": []any{map[string]any{"id": "local", "endpoint": "not a URL"}},
|
||||||
"profiles": map[string]any{"partial": map[string]any{"backend": "local"}},
|
"profiles": []any{map[string]any{"id": "partial", "backend": "local"}},
|
||||||
})
|
})
|
||||||
main := mapping("operator", map[string]any{"backends": map[string]any{"local": map[string]any{"endpoint": "http://localhost:8080/v1"}}})
|
main := mapping("operator", map[string]any{"backends": []any{map[string]any{"id": "local", "endpoint": "http://localhost:8080/v1"}}})
|
||||||
loaded, err := load(main, []configkit.Underlay{{Inputs: []configkit.Input{underlay}}})
|
loaded, err := load(main, []configkit.Underlay{{Inputs: []configkit.Input{underlay}}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("replaced lower-layer domain value should not fail: %v", err)
|
t.Fatalf("replaced lower-layer domain value should not fail: %v", err)
|
||||||
}
|
}
|
||||||
if loaded.result.Config.Profiles["partial"].Model != nil {
|
if loaded.result.Config.Profiles[0].Model != nil {
|
||||||
t.Fatal("unused partial profile unexpectedly gained a model")
|
t.Fatal("unused partial profile unexpectedly gained a model")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = load(configkit.Input{Source: configkit.NoSource()}, []configkit.Underlay{{Inputs: []configkit.Input{
|
_, err = load(configkit.Input{Source: configkit.NoSource()}, []configkit.Underlay{{Inputs: []configkit.Input{
|
||||||
mapping("invalid", map[string]any{"backends": map[string]any{"unused": map[string]any{"endpoint": "not a URL"}}}),
|
mapping("invalid", map[string]any{"backends": []any{map[string]any{"id": "unused", "endpoint": "not a URL"}}}),
|
||||||
}}})
|
}}})
|
||||||
var loadError *configkit.LoadError
|
var loadError *configkit.LoadError
|
||||||
if !errors.As(err, &loadError) {
|
if !errors.As(err, &loadError) {
|
||||||
t.Fatalf("expected path-aware effective backend failure, got %v", err)
|
t.Fatalf("expected path-aware effective backend failure, got %v", err)
|
||||||
}
|
}
|
||||||
if diagnostics := loadError.Diagnostics(); len(diagnostics) != 1 || diagnostics[0].Path.String() != "/backends/unused/endpoint" {
|
if diagnostics := loadError.Diagnostics(); len(diagnostics) != 1 || diagnostics[0].Path.String() != "/backends/0/endpoint" {
|
||||||
t.Fatalf("expected endpoint diagnostic, got %+v", diagnostics)
|
t.Fatalf("expected endpoint diagnostic, got %+v", diagnostics)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadValidatesOnlyFinalBackendSmokeTestValues(t *testing.T) {
|
func TestLoadValidatesOnlyFinalBackendSmokeTestValues(t *testing.T) {
|
||||||
underlay := mapping("defaults", map[string]any{
|
underlay := mapping("defaults", map[string]any{
|
||||||
"backends": map[string]any{"local": map[string]any{
|
"backends": []any{map[string]any{"id": "local",
|
||||||
"endpoint": "https://backend.example/v1",
|
"endpoint": "https://backend.example/v1",
|
||||||
"smoke_tests": map[string]any{
|
"smoke_tests": map[string]any{
|
||||||
"default_profile": " ",
|
"default_profile": " ",
|
||||||
@@ -491,7 +750,7 @@ func TestLoadValidatesOnlyFinalBackendSmokeTestValues(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
main := mapping("operator", map[string]any{
|
main := mapping("operator", map[string]any{
|
||||||
"backends": map[string]any{"local": map[string]any{
|
"backends": []any{map[string]any{"id": "local", "endpoint": "https://backend.example/v1",
|
||||||
"smoke_tests": map[string]any{
|
"smoke_tests": map[string]any{
|
||||||
"default_profile": "dangling-profile",
|
"default_profile": "dangling-profile",
|
||||||
"reachability": map[string]any{"endpoint": nil},
|
"reachability": map[string]any{"endpoint": nil},
|
||||||
@@ -514,7 +773,7 @@ func TestLoadValidatesOnlyFinalBackendSmokeTestValues(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadKeepsSyntaxErrorsFromLowerTiers(t *testing.T) {
|
func TestLoadKeepsSyntaxErrorsFromLowerTiers(t *testing.T) {
|
||||||
_, err := load(mapping("operator", map[string]any{"backends": map[string]any{"local": map[string]any{"endpoint": "http://localhost:8080"}}}),
|
_, err := load(mapping("operator", map[string]any{"backends": []any{map[string]any{"id": "local", "endpoint": "http://localhost:8080"}}}),
|
||||||
[]configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.Bytes("broken", []byte("backends: ["))}}}})
|
[]configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.Bytes("broken", []byte("backends: ["))}}}})
|
||||||
var loadError *configkit.LoadError
|
var loadError *configkit.LoadError
|
||||||
if !errors.As(err, &loadError) {
|
if !errors.As(err, &loadError) {
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
|
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
|
||||||
"gitea.maximumdirect.net/eric/promptkit/internal/llm"
|
"gitea.maximumdirect.net/eric/promptkit/internal/llm"
|
||||||
profilepkg "gitea.maximumdirect.net/eric/promptkit/internal/profile"
|
profilepkg "gitea.maximumdirect.net/eric/promptkit/internal/profile"
|
||||||
|
"gitea.maximumdirect.net/eric/promptkit/internal/prompt"
|
||||||
|
"gitea.maximumdirect.net/eric/promptkit/internal/usecase"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadIndexesIdentifiedTargets(t *testing.T) {
|
func TestLoadIndexesIdentifiedTargets(t *testing.T) {
|
||||||
@@ -40,6 +42,31 @@ func TestLoadIndexesIdentifiedTargets(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRequestCompletesLoadedPartialProfile(t *testing.T) {
|
||||||
|
state, err := load(mapping("partial profile", map[string]any{
|
||||||
|
"profiles": []any{map[string]any{"id": "partial"}},
|
||||||
|
"prompts": []any{map[string]any{
|
||||||
|
"id": "example", "messages": []any{map[string]any{"role": "user", "content": "hello"}},
|
||||||
|
"output": map[string]any{"format": "text", "validation_mode": "basic"},
|
||||||
|
}},
|
||||||
|
}), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
runner := usecase.NewRunner(state, state, nil, prompt.NewGoRenderer(), nil, nil, nil)
|
||||||
|
prepared, err := runner.Prepare(context.Background(), domain.RunRequest{
|
||||||
|
PromptID: "example", ProfileID: "partial",
|
||||||
|
Execution: &domain.ExecutionTargetOverride{Endpoint: "https://example.invalid/v1", Model: "request-model"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if prepared.SelectedProfileID != "partial" || prepared.EffectiveModelParams.Endpoint != "https://example.invalid/v1" ||
|
||||||
|
prepared.EffectiveModelParams.Model != "request-model" {
|
||||||
|
t.Fatalf("request did not complete the loaded profile: %+v", prepared)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadBuildsEffectiveTargetsAfterInheritance(t *testing.T) {
|
func TestLoadBuildsEffectiveTargetsAfterInheritance(t *testing.T) {
|
||||||
state, err := load(mapping("effective-targets", map[string]any{
|
state, err := load(mapping("effective-targets", map[string]any{
|
||||||
"backends": []any{
|
"backends": []any{
|
||||||
|
|||||||
Reference in New Issue
Block a user