Load backend smoke-test metadata

This commit is contained in:
2026-10-13 15:53:27 +00:00
parent f91a02548e
commit ece764f19e
9 changed files with 473 additions and 26 deletions

View File

@@ -17,8 +17,9 @@ const BackendLocal = "local"
// BackendConfig is a presence-aware convenience value for a keyed backend // BackendConfig is a presence-aware convenience value for a keyed backend
// definition. Nil pointer fields are omitted, while non-nil pointers preserve // definition. Nil pointer fields are omitted, while non-nil pointers preserve
// explicit zero or blank values. A nil ExtraParams map is omitted; a non-nil // explicit zero or blank values. A nil ExtraParams map is omitted; a non-nil
// empty map supplies an empty mapping. Use raw ConfigKit Mapping inputs for // empty map supplies an empty mapping. A nil SmokeTests pointer is omitted; a
// explicit null. BackendConfig has no stable JSON representation. // non-nil pointer supplies a smoke_tests mapping. Use raw ConfigKit Mapping
// inputs for explicit null. BackendConfig has no stable JSON representation.
type BackendConfig struct { type BackendConfig struct {
// Endpoint is the required absolute HTTP(S) base URL after all layers merge. // Endpoint is the required absolute HTTP(S) base URL after all layers merge.
Endpoint *string Endpoint *string
@@ -31,6 +32,43 @@ type BackendConfig struct {
QueueCapacity *int QueueCapacity *int
// ExtraParams supplies JSON-compatible provider request defaults. // ExtraParams supplies JSON-compatible provider request defaults.
ExtraParams map[string]any ExtraParams map[string]any
// SmokeTests supplies optional diagnostic defaults and HTTP probe settings.
SmokeTests *BackendSmokeTestConfig
}
// BackendSmokeTestConfig contains optional diagnostic settings associated with
// a backend. Nil nested fields are omitted by BackendSource.
type BackendSmokeTestConfig struct {
// DefaultProfile is the exact profile ID selected as the backend's
// diagnostic inference default. A supplied value must not be blank; the ID
// is resolved only when that default is selected.
DefaultProfile *string
// Reachability optionally selects a full HTTP destination for reachability
// diagnostics. A nil endpoint uses the effective backend endpoint.
Reachability *ReachabilityProbeConfig
// Authentication optionally declares a named authentication diagnostic.
// A supplied object requires Kind.
Authentication *AuthenticationProbeConfig
}
// ReachabilityProbeConfig configures an optional reachability destination.
type ReachabilityProbeConfig struct {
// Endpoint is an absolute HTTP(S) URL, including any desired path or query.
// Nil selects the effective backend endpoint; an explicit blank value is
// invalid.
Endpoint *string
}
// AuthenticationProbeConfig configures an optional named authentication
// diagnostic.
type AuthenticationProbeConfig struct {
// Kind selects the authentication adapter. Supported values are "none" and
// "openrouter_key"; surrounding whitespace and case are normalized.
Kind *string
// Endpoint is an absolute HTTP(S) URL, including any desired path or query.
// Nil selects the adapter's backend-relative destination; an explicit blank
// value is invalid.
Endpoint *string
} }
// BackendSource creates a labeled ConfigKit mapping from keyed backend values. // BackendSource creates a labeled ConfigKit mapping from keyed backend values.
@@ -57,6 +95,30 @@ func BackendSource(label string, backends map[string]BackendConfig) configkit.So
if config.ExtraParams != nil { if config.ExtraParams != nil {
fields["extra_params"] = providerMapForMapping(config.ExtraParams) fields["extra_params"] = providerMapForMapping(config.ExtraParams)
} }
if config.SmokeTests != nil {
smokeTests := make(map[string]any)
if config.SmokeTests.DefaultProfile != nil {
smokeTests["default_profile"] = *config.SmokeTests.DefaultProfile
}
if config.SmokeTests.Reachability != nil {
reachability := make(map[string]any)
if config.SmokeTests.Reachability.Endpoint != nil {
reachability["endpoint"] = *config.SmokeTests.Reachability.Endpoint
}
smokeTests["reachability"] = reachability
}
if config.SmokeTests.Authentication != nil {
authentication := make(map[string]any)
if config.SmokeTests.Authentication.Kind != nil {
authentication["kind"] = *config.SmokeTests.Authentication.Kind
}
if config.SmokeTests.Authentication.Endpoint != nil {
authentication["endpoint"] = *config.SmokeTests.Authentication.Endpoint
}
smokeTests["authentication"] = authentication
}
fields["smoke_tests"] = smokeTests
}
definitions[id] = fields definitions[id] = fields
} }
return configkit.Mapping(label, map[string]any{"backends": definitions}) return configkit.Mapping(label, map[string]any{"backends": definitions})

View File

@@ -1,6 +1,7 @@
package promptkit_test package promptkit_test
import ( import (
"bytes"
"context" "context"
"errors" "errors"
"io" "io"
@@ -208,6 +209,66 @@ func TestPublicConstructionCanOverlayMaintainedBackend(t *testing.T) {
} }
} }
func TestBackendSourceOwnsSmokeTestValuesAndDefersDefaultLookup(t *testing.T) {
backendEndpoint := "https://backend.example/v1"
defaultProfile := "not-yet-listed"
reachabilityEndpoint := " https://probe.example/ready?check=1 "
authenticationKind := " OPENROUTER_KEY "
authenticationEndpoint := "https://probe.example/key?scope=current"
source := promptkit.BackendSource("diagnostic backend", map[string]promptkit.BackendConfig{
"local": {
Endpoint: &backendEndpoint,
SmokeTests: &promptkit.BackendSmokeTestConfig{
DefaultProfile: &defaultProfile,
Reachability: &promptkit.ReachabilityProbeConfig{Endpoint: &reachabilityEndpoint},
Authentication: &promptkit.AuthenticationProbeConfig{
Kind: &authenticationKind, Endpoint: &authenticationEndpoint,
},
},
},
})
backendEndpoint = "https://mutated.example/v1"
defaultProfile = "mutated-profile"
reachabilityEndpoint = "https://mutated.example/ready"
authenticationKind = "none"
authenticationEndpoint = "https://mutated.example/key"
engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: source}}}},
}})
if err != nil {
t.Fatalf("construct engine with dangling diagnostic default: %v", err)
}
inspection, err := engine.InspectConfiguration()
if err != nil {
t.Fatal(err)
}
var effective bytes.Buffer
if err := inspection.WriteEffective(&effective); err != nil {
t.Fatal(err)
}
contents := effective.String()
for _, want := range []string{
"https://backend.example/v1",
"not-yet-listed",
"https://probe.example/ready?check=1",
"openrouter_key",
"https://probe.example/key?scope=current",
} {
if !strings.Contains(contents, want) {
t.Errorf("effective configuration does not contain %q:\n%s", want, contents)
}
}
for _, unwanted := range []string{
"mutated.example",
"mutated-profile",
} {
if strings.Contains(contents, unwanted) {
t.Errorf("effective configuration unexpectedly contains %q:\n%s", unwanted, contents)
}
}
}
func TestPreparationRejectsAmbiguousPromptVersion(t *testing.T) { func TestPreparationRejectsAmbiguousPromptVersion(t *testing.T) {
definition := func(content string) map[string]any { definition := func(content string) map[string]any {
return map[string]any{ return map[string]any{

View File

@@ -168,16 +168,46 @@ are not reserved against configuration changes.
| `rakestrawhome` | `https://inference.ai.rakestrawhome.com/v1` | `RAKESTRAWHOME_INFERENCE_API_KEY` | 4 | | `rakestrawhome` | `https://inference.ai.rakestrawhome.com/v1` | `RAKESTRAWHOME_INFERENCE_API_KEY` | 4 |
A backend definition may have `endpoint`, `api_key_env`, A backend definition may have `endpoint`, `api_key_env`,
`concurrency_limit`, `queue_capacity`, and `extra_params`. The final endpoint `concurrency_limit`, `queue_capacity`, `extra_params`, and `smoke_tests`. The
is required and must be an absolute HTTP(S) URL with a host, no user final endpoint is required and must be an absolute HTTP(S) URL with a host, no
information, query, or fragment. An omitted or null API-key environment name user information, query, or fragment. An omitted or null API-key environment
means no lookup. Omitted/null concurrency is zero (unlimited); a supplied name means no lookup. Omitted/null concurrency is zero (unlimited); a supplied
negative value is invalid. For positive concurrency, omitted/null queue negative value is invalid. For positive concurrency, omitted/null queue
capacity defaults to 1024. An explicit queue capacity, including zero, capacity defaults to 1024. An explicit queue capacity, including zero,
requires positive concurrency and must be nonnegative. Unlimited concurrency requires positive concurrency and must be nonnegative. Unlimited concurrency
has no queue. Provider maps contain JSON-compatible values; null entries are has no queue. Provider maps contain JSON-compatible values; null entries are
ordinary JSON null. Unknown fields, including a raw `api_key`, fail. ordinary JSON null. Unknown fields, including a raw `api_key`, fail.
`smoke_tests` is optional backend metadata for explicit diagnostic operations.
It may contain `default_profile`, `reachability`, and `authentication`:
```yaml
backends:
openrouter:
smoke_tests:
default_profile: promptkit-openrouter-standard
reachability:
endpoint: https://status.example/ready?check=1
authentication:
kind: openrouter_key
endpoint: https://openrouter.ai/api/v1/key
```
An omitted or null `smoke_tests` block disables these metadata defaults.
`default_profile` is preserved as an exact, nonblank profile ID; its existence
is checked only if a diagnostic selects that default. Omitted/null
`reachability` or its endpoint uses the effective backend endpoint. An explicit
reachability endpoint is trimmed and must be an absolute HTTP(S) URL with a
host; unlike the backend base endpoint, its path and query are allowed. Null
means fallback, while an explicit blank value is invalid.
Omitted/null `authentication` means no authentication adapter is declared. A
non-null object requires `kind`, which is trimmed and case-normalized; the
recognized values are `none` and `openrouter_key`. Its omitted/null endpoint
uses the adapter's backend-relative destination; an explicit endpoint follows
the same full-URL rules as reachability. These values are configuration only:
engine construction does not contact a backend.
## Profile Definitions ## Profile Definitions
Profiles are keyed by literal ID and may inherit a named parent with Profiles are keyed by literal ID and may inherit a named parent with

View File

@@ -264,6 +264,10 @@ Validate: `go test ./internal/configuration ./internal/backend` and focused root
backend-source tests. Update backend-field GoDoc and `docs/formats.md` as these backend-source tests. Update backend-field GoDoc and `docs/formats.md` as these
configuration fields land; runtime smoke-test guidance waits for Stage 18. configuration fields land; runtime smoke-test guidance waits for Stage 18.
**Completed: 2026-10-13.** Validation passed:
`go test ./internal/configuration ./internal/backend`, `go test .`, and
`git diff --check`.
## Stage 3: Resolve Diagnostic Targets Without Preparing Applications ## Stage 3: Resolve Diagnostic Targets Without Preparing Applications
Scope: selection helpers in `internal/usecase`; no transport. Scope: selection helpers in `internal/usecase`; no transport.

View File

@@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"gitea.maximumdirect.net/eric/promptkit/internal/domain" "gitea.maximumdirect.net/eric/promptkit/internal/domain"
"gitea.maximumdirect.net/eric/promptkit/internal/jsonvalue"
) )
const ( const (
@@ -31,13 +30,12 @@ type Registry struct {
func NewEffectiveRegistry(definitions map[string]domain.Backend) (*Registry, error) { func NewEffectiveRegistry(definitions map[string]domain.Backend) (*Registry, error) {
registry := &Registry{backends: make(map[string]domain.Backend, len(definitions))} registry := &Registry{backends: make(map[string]domain.Backend, len(definitions))}
for id, definition := range definitions { for id, definition := range definitions {
params, err := jsonvalue.CopyMap(definition.ExtraParams)
if err != nil {
return nil, fmt.Errorf("backend %q extra parameters: %w", id, err)
}
definition.ID = id definition.ID = id
definition.ExtraParams = params cloned, err := domain.CloneBackend(definition)
registry.backends[id] = definition if err != nil {
return nil, fmt.Errorf("backend %q: %w", id, err)
}
registry.backends[id] = cloned
} }
return registry, nil return registry, nil
} }
@@ -51,12 +49,11 @@ func (r *Registry) GetBackend(id string) (domain.Backend, error) {
if !ok { if !ok {
return domain.Backend{}, fmt.Errorf("%w: %q", ErrBackendNotFound, id) return domain.Backend{}, fmt.Errorf("%w: %q", ErrBackendNotFound, id)
} }
extraParams, err := jsonvalue.CopyMap(definition.ExtraParams) cloned, err := domain.CloneBackend(definition)
if err != nil { if err != nil {
return domain.Backend{}, fmt.Errorf("copy backend %q: %w", id, err) return domain.Backend{}, fmt.Errorf("copy backend %q: %w", id, err)
} }
definition.ExtraParams = extraParams return cloned, nil
return definition, nil
} }
// CapacityPolicies returns a copy of the normalized policies for limited // CapacityPolicies returns a copy of the normalized policies for limited

View File

@@ -11,25 +11,61 @@ import (
func TestEffectiveRegistryOwnsDefinitionsAndCapacityPolicies(t *testing.T) { func TestEffectiveRegistryOwnsDefinitionsAndCapacityPolicies(t *testing.T) {
params := map[string]any{"nested": map[string]any{"enabled": true}} params := map[string]any{"nested": map[string]any{"enabled": true}}
defaultProfile := "catalog-profile"
reachabilityEndpoint := "https://probe.example/ready"
authenticationKind := "openrouter_key"
authenticationEndpoint := "https://probe.example/key"
smokeTests := &domain.BackendSmokeTestConfig{
DefaultProfile: &defaultProfile,
Reachability: &domain.ReachabilityProbeConfig{Endpoint: &reachabilityEndpoint},
Authentication: &domain.AuthenticationProbeConfig{
Kind: &authenticationKind, Endpoint: &authenticationEndpoint,
},
}
registry, err := backend.NewEffectiveRegistry(map[string]domain.Backend{ registry, err := backend.NewEffectiveRegistry(map[string]domain.Backend{
" local.v1 ": { " local.v1 ": {
Endpoint: "https://backend.example/v1", ExtraParams: params, Endpoint: "https://backend.example/v1", ExtraParams: params,
ConcurrencyLimit: 2, QueueCapacity: 3, QueueCapacitySet: true, ConcurrencyLimit: 2, QueueCapacity: 3, QueueCapacitySet: true,
SmokeTests: smokeTests,
}, },
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
params["nested"].(map[string]any)["enabled"] = false params["nested"].(map[string]any)["enabled"] = false
defaultProfile = "mutated-profile"
reachabilityEndpoint = "https://mutated.example/ready"
authenticationKind = "none"
authenticationEndpoint = "https://mutated.example/key"
selected, err := registry.GetBackend(" local.v1 ") selected, err := registry.GetBackend(" local.v1 ")
if err != nil || selected.ID != " local.v1 " || selected.ExtraParams["nested"].(map[string]any)["enabled"] != true { if err != nil || selected.ID != " local.v1 " || selected.ExtraParams["nested"].(map[string]any)["enabled"] != true {
t.Fatalf("effective backend was not captured under its literal ID: %+v, %v", selected, err) t.Fatalf("effective backend was not captured under its literal ID: %+v, %v", selected, err)
} }
if selected.SmokeTests == nil || selected.SmokeTests.DefaultProfile == nil ||
*selected.SmokeTests.DefaultProfile != "catalog-profile" || selected.SmokeTests.Reachability == nil ||
selected.SmokeTests.Reachability.Endpoint == nil || *selected.SmokeTests.Reachability.Endpoint != "https://probe.example/ready" ||
selected.SmokeTests.Authentication == nil || selected.SmokeTests.Authentication.Kind == nil ||
*selected.SmokeTests.Authentication.Kind != "openrouter_key" || selected.SmokeTests.Authentication.Endpoint == nil ||
*selected.SmokeTests.Authentication.Endpoint != "https://probe.example/key" {
t.Fatalf("registry did not capture nested diagnostic metadata: %+v", selected.SmokeTests)
}
selected.ExtraParams["nested"].(map[string]any)["enabled"] = false selected.ExtraParams["nested"].(map[string]any)["enabled"] = false
*selected.SmokeTests.DefaultProfile = "mutated-return"
*selected.SmokeTests.Reachability.Endpoint = "https://mutated-return.example/ready"
*selected.SmokeTests.Authentication.Kind = "none"
*selected.SmokeTests.Authentication.Endpoint = "https://mutated-return.example/key"
again, err := registry.GetBackend(" local.v1 ") again, err := registry.GetBackend(" local.v1 ")
if err != nil || again.ExtraParams["nested"].(map[string]any)["enabled"] != true { if err != nil || again.ExtraParams["nested"].(map[string]any)["enabled"] != true {
t.Fatalf("lookup exposed registry values: %+v, %v", again, err) t.Fatalf("lookup exposed registry values: %+v, %v", again, err)
} }
if again.SmokeTests == nil || again.SmokeTests.DefaultProfile == nil ||
*again.SmokeTests.DefaultProfile != "catalog-profile" || again.SmokeTests.Reachability == nil ||
again.SmokeTests.Reachability.Endpoint == nil || *again.SmokeTests.Reachability.Endpoint != "https://probe.example/ready" ||
again.SmokeTests.Authentication == nil || again.SmokeTests.Authentication.Kind == nil ||
*again.SmokeTests.Authentication.Kind != "openrouter_key" || again.SmokeTests.Authentication.Endpoint == nil ||
*again.SmokeTests.Authentication.Endpoint != "https://probe.example/key" {
t.Fatalf("lookup exposed nested diagnostic metadata: %+v", again.SmokeTests)
}
policies := registry.CapacityPolicies() policies := registry.CapacityPolicies()
if !reflect.DeepEqual(policies, map[string]domain.BackendCapacityPolicy{ if !reflect.DeepEqual(policies, map[string]domain.BackendCapacityPolicy{
" local.v1 ": {ConcurrencyLimit: 2, QueueCapacity: 3}, " local.v1 ": {ConcurrencyLimit: 2, QueueCapacity: 3},

View File

@@ -4,6 +4,7 @@ package configuration
import ( import (
"fmt" "fmt"
"net/http"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
@@ -27,6 +28,22 @@ type backendConfig struct {
ConcurrencyLimit *int `yaml:"concurrency_limit"` ConcurrencyLimit *int `yaml:"concurrency_limit"`
QueueCapacity *int `yaml:"queue_capacity"` QueueCapacity *int `yaml:"queue_capacity"`
ExtraParams map[string]any `yaml:"extra_params"` ExtraParams map[string]any `yaml:"extra_params"`
SmokeTests *backendSmokeTestConfig `yaml:"smoke_tests"`
}
type backendSmokeTestConfig struct {
DefaultProfile *string `yaml:"default_profile"`
Reachability *reachabilityProbeConfig `yaml:"reachability"`
Authentication *authenticationProbeConfig `yaml:"authentication"`
}
type reachabilityProbeConfig struct {
Endpoint *string `yaml:"endpoint"`
}
type authenticationProbeConfig struct {
Kind *string `yaml:"kind"`
Endpoint *string `yaml:"endpoint"`
} }
type profileConfig struct { type profileConfig struct {
@@ -104,7 +121,7 @@ type loaded struct {
var envName = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) var envName = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
var backendFields = []string{"endpoint", "api_key_env", "concurrency_limit", "queue_capacity", "extra_params"} var backendFields = []string{"endpoint", "api_key_env", "concurrency_limit", "queue_capacity", "extra_params", "smoke_tests"}
var profileFields = []string{"backend", "endpoint", "model", "temperature", "top_p", "max_tokens", "timeout_seconds", "service_tier", "reasoning_effort", "api_key_env", "api_key_required", "extra_params"} var profileFields = []string{"backend", "endpoint", "model", "temperature", "top_p", "max_tokens", "timeout_seconds", "service_tier", "reasoning_effort", "api_key_env", "api_key_required", "extra_params"}
// Load captures one immutable effective configuration for internal consumers. // Load captures one immutable effective configuration for internal consumers.
@@ -242,6 +259,21 @@ func resolveBackends(backends map[string]backendConfig, resolution *configkit.Re
if backend.APIKeyEnv != nil { if backend.APIKeyEnv != nil {
normalizeString(&backend.APIKeyEnv, base.Key("api_key_env"), resolution, strings.TrimSpace(*backend.APIKeyEnv)) normalizeString(&backend.APIKeyEnv, base.Key("api_key_env"), resolution, strings.TrimSpace(*backend.APIKeyEnv))
} }
if smokeTests := backend.SmokeTests; smokeTests != nil {
smokePath := base.Key("smoke_tests")
if smokeTests.Reachability != nil {
normalizeDiagnosticEndpoint(&smokeTests.Reachability.Endpoint, smokePath.Key("reachability").Key("endpoint"), resolution)
}
if smokeTests.Authentication != nil {
if smokeTests.Authentication.Kind != nil {
normalizeString(&smokeTests.Authentication.Kind,
smokePath.Key("authentication").Key("kind"), resolution,
strings.ToLower(strings.TrimSpace(*smokeTests.Authentication.Kind)))
}
normalizeDiagnosticEndpoint(&smokeTests.Authentication.Endpoint,
smokePath.Key("authentication").Key("endpoint"), resolution)
}
}
if backend.ConcurrencyLimit != nil && *backend.ConcurrencyLimit > 0 && backend.QueueCapacity == nil { if backend.ConcurrencyLimit != nil && *backend.ConcurrencyLimit > 0 && backend.QueueCapacity == nil {
capacity := 1024 capacity := 1024
if resolution.IsExplicit(base.Key("queue_capacity")) { if resolution.IsExplicit(base.Key("queue_capacity")) {
@@ -254,6 +286,21 @@ func resolveBackends(backends map[string]backendConfig, resolution *configkit.Re
} }
} }
func normalizeDiagnosticEndpoint(value **string, path configkit.Path, resolution *configkit.Resolution) {
if value == nil || *value == nil {
return
}
normalized := strings.TrimSpace(**value)
request, err := http.NewRequest(http.MethodGet, normalized, nil)
if normalized == "" || err != nil || request.URL == nil ||
(strings.ToLower(request.URL.Scheme) != "http" && strings.ToLower(request.URL.Scheme) != "https") ||
request.URL.Hostname() == "" {
resolution.Error(path, "diagnostic endpoint must be an absolute HTTP(S) URL with a host")
return
}
normalizeString(value, path, resolution, normalized)
}
func resolveProfiles(profiles map[string]profileConfig, resolution *configkit.Resolution) { func resolveProfiles(profiles map[string]profileConfig, resolution *configkit.Resolution) {
for _, id := range sortedKeys(profiles) { for _, id := range sortedKeys(profiles) {
profile := profiles[id] profile := profiles[id]
@@ -309,6 +356,20 @@ func validateEffective(config *document, validation *configkit.Validation) {
} }
} }
validateExtraParams(backend.ExtraParams, base.Key("extra_params"), true, validation) validateExtraParams(backend.ExtraParams, base.Key("extra_params"), true, validation)
if smokeTests := backend.SmokeTests; smokeTests != nil {
smokePath := base.Key("smoke_tests")
if smokeTests.DefaultProfile != nil && strings.TrimSpace(*smokeTests.DefaultProfile) == "" {
validation.Error(smokePath.Key("default_profile"), "default profile must not be blank")
}
if authentication := smokeTests.Authentication; authentication != nil {
kindPath := smokePath.Key("authentication").Key("kind")
if authentication.Kind == nil {
validation.Error(kindPath, "authentication kind is required")
} else if *authentication.Kind != "none" && *authentication.Kind != "openrouter_key" {
validation.Error(kindPath, "authentication kind must be none or openrouter_key")
}
}
}
} }
for _, id := range sortedKeys(config.Profiles) { for _, id := range sortedKeys(config.Profiles) {
profile := config.Profiles[id] profile := config.Profiles[id]

View File

@@ -78,6 +78,159 @@ func TestLoadRecordsBackendNormalizationAndNullDefault(t *testing.T) {
} }
} }
func TestLoadBackendSmokeTestsOmissionAndNull(t *testing.T) {
for _, test := range []struct {
name string
smokeTests any
include bool
inherited bool
}{
{name: "omitted"},
{name: "null", smokeTests: nil, include: true, inherited: true},
} {
t.Run(test.name, func(t *testing.T) {
backend := map[string]any{"endpoint": "https://backend.example/v1"}
if test.include {
backend["smoke_tests"] = test.smokeTests
}
main := mapping("backend", map[string]any{
"backends": map[string]any{"local": backend},
})
var underlays []configkit.Underlay
if test.inherited {
underlays = []configkit.Underlay{{Inputs: []configkit.Input{mapping("defaults", map[string]any{
"backends": map[string]any{"local": map[string]any{
"endpoint": "https://backend.example/v1",
"smoke_tests": map[string]any{"default_profile": "inherited-profile"},
}},
})}}}
}
loaded, err := load(main, underlays)
if err != nil {
t.Fatal(err)
}
if got := loaded.result.Config.Backends["local"].SmokeTests; got != nil {
t.Fatalf("smoke_tests = %+v, want nil", got)
}
selected, err := loaded.registry.GetBackend("local")
if err != nil {
t.Fatal(err)
}
if selected.SmokeTests != nil {
t.Fatalf("indexed smoke_tests = %+v, want nil", selected.SmokeTests)
}
})
}
}
func TestLoadMergesBackendSmokeTestsAndRetainsFallbacks(t *testing.T) {
base := mapping("defaults", map[string]any{
"backends": map[string]any{"local": map[string]any{
"endpoint": "https://backend.example/v1",
"smoke_tests": map[string]any{
"default_profile": "catalog-profile",
"reachability": map[string]any{"endpoint": " https://probe.example/ready?check=1 "},
"authentication": map[string]any{
"kind": " OPENROUTER_KEY ",
"endpoint": "https://probe.example/key?scope=current",
},
},
}},
})
overlay := mapping("operator", map[string]any{
"backends": map[string]any{"local": map[string]any{
"smoke_tests": map[string]any{
"default_profile": "not-yet-listed",
"authentication": map[string]any{"kind": " NONE ", "endpoint": nil},
},
}},
})
loaded, err := load(configkit.Input{Source: configkit.NoSource()}, []configkit.Underlay{
{Inputs: []configkit.Input{base}},
{Inputs: []configkit.Input{overlay}},
})
if err != nil {
t.Fatalf("load effective diagnostic metadata: %v", err)
}
selected, err := loaded.registry.GetBackend("local")
if err != nil {
t.Fatal(err)
}
metadata := selected.SmokeTests
if selected.Endpoint != "https://backend.example/v1" {
t.Fatalf("effective backend endpoint = %q", selected.Endpoint)
}
if metadata == nil || metadata.DefaultProfile == nil || *metadata.DefaultProfile != "not-yet-listed" {
t.Fatalf("default profile overlay was not retained: %+v", metadata)
}
if metadata.Reachability == nil || metadata.Reachability.Endpoint == nil ||
*metadata.Reachability.Endpoint != "https://probe.example/ready?check=1" {
t.Fatalf("full reachability destination was not normalized: %+v", metadata.Reachability)
}
if metadata.Authentication == nil || metadata.Authentication.Kind == nil || *metadata.Authentication.Kind != "none" ||
metadata.Authentication.Endpoint != nil {
t.Fatalf("authentication overlay or null fallback was not retained: %+v", metadata.Authentication)
}
}
func TestLoadRejectsInvalidFinalBackendSmokeTests(t *testing.T) {
for _, test := range []struct {
name string
data map[string]any
path string
}{
{
name: "blank default profile",
data: map[string]any{"default_profile": " \t "},
path: "/backends/local/smoke_tests/default_profile",
},
{
name: "authentication kind required",
data: map[string]any{"authentication": map[string]any{}},
path: "/backends/local/smoke_tests/authentication/kind",
},
{
name: "unknown authentication kind",
data: map[string]any{"authentication": map[string]any{"kind": "bearer"}},
path: "/backends/local/smoke_tests/authentication/kind",
},
{
name: "non-http reachability URL",
data: map[string]any{"reachability": map[string]any{"endpoint": "ftp://probe.example/ready"}},
path: "/backends/local/smoke_tests/reachability/endpoint",
},
{
name: "blank authentication URL",
data: map[string]any{"authentication": map[string]any{"kind": "none", "endpoint": " "}},
path: "/backends/local/smoke_tests/authentication/endpoint",
},
{
name: "relative authentication URL",
data: map[string]any{"authentication": map[string]any{"kind": "none", "endpoint": "/key"}},
path: "/backends/local/smoke_tests/authentication/endpoint",
},
} {
t.Run(test.name, func(t *testing.T) {
_, err := load(mapping("backend", map[string]any{
"backends": map[string]any{"local": map[string]any{
"endpoint": "https://backend.example/v1",
"smoke_tests": test.data,
}},
}), nil)
var loadError *configkit.LoadError
if !errors.As(err, &loadError) {
t.Fatalf("expected final-value validation error, got %v", err)
}
for _, diagnostic := range loadError.Diagnostics() {
if diagnostic.Path.String() == test.path {
return
}
}
t.Fatalf("expected diagnostic at %s, got %+v", test.path, loadError.Diagnostics())
})
}
}
func TestLoadDefaultsAbsentQueueCapacity(t *testing.T) { func TestLoadDefaultsAbsentQueueCapacity(t *testing.T) {
loaded, err := load(mapping("backend", map[string]any{"backends": map[string]any{ loaded, err := load(mapping("backend", map[string]any{"backends": map[string]any{
"local": map[string]any{"endpoint": "http://localhost:8080/v1", "concurrency_limit": 1}, "local": map[string]any{"endpoint": "http://localhost:8080/v1", "concurrency_limit": 1},
@@ -128,6 +281,40 @@ func TestLoadValidatesOnlyFinalDomainValues(t *testing.T) {
} }
} }
func TestLoadValidatesOnlyFinalBackendSmokeTestValues(t *testing.T) {
underlay := mapping("defaults", map[string]any{
"backends": map[string]any{"local": map[string]any{
"endpoint": "https://backend.example/v1",
"smoke_tests": map[string]any{
"default_profile": " ",
"reachability": map[string]any{"endpoint": "ftp://invalid.example/"},
"authentication": map[string]any{"kind": "unsupported"},
},
}},
})
main := mapping("operator", map[string]any{
"backends": map[string]any{"local": map[string]any{
"smoke_tests": map[string]any{
"default_profile": "dangling-profile",
"reachability": map[string]any{"endpoint": nil},
"authentication": map[string]any{"kind": "none"},
},
}},
})
loaded, err := load(main, []configkit.Underlay{{Inputs: []configkit.Input{underlay}}})
if err != nil {
t.Fatalf("replaced lower metadata should not fail: %v", err)
}
selected, err := loaded.registry.GetBackend("local")
if err != nil {
t.Fatal(err)
}
if selected.SmokeTests == nil || selected.SmokeTests.DefaultProfile == nil ||
*selected.SmokeTests.DefaultProfile != "dangling-profile" {
t.Fatalf("final dangling default profile was not retained: %+v", selected.SmokeTests)
}
}
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": map[string]any{"local": map[string]any{"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: ["))}}}})

View File

@@ -15,11 +15,20 @@ func (state *loaded) buildExecutionProfiles() error {
definitions := make(map[string]domain.Backend, len(state.result.Config.Backends)) definitions := make(map[string]domain.Backend, len(state.result.Config.Backends))
for _, id := range sortedKeys(state.result.Config.Backends) { for _, id := range sortedKeys(state.result.Config.Backends) {
config := state.result.Config.Backends[id] config := state.result.Config.Backends[id]
params, err := jsonvalue.CopyMap(config.ExtraParams) definition := domain.Backend{ID: id, ExtraParams: config.ExtraParams}
if err != nil { if config.SmokeTests != nil {
return fmt.Errorf("backend %q extra parameters: %w", id, err) smokeTests := &domain.BackendSmokeTestConfig{DefaultProfile: config.SmokeTests.DefaultProfile}
if config.SmokeTests.Reachability != nil {
smokeTests.Reachability = &domain.ReachabilityProbeConfig{Endpoint: config.SmokeTests.Reachability.Endpoint}
}
if config.SmokeTests.Authentication != nil {
smokeTests.Authentication = &domain.AuthenticationProbeConfig{
Kind: config.SmokeTests.Authentication.Kind,
Endpoint: config.SmokeTests.Authentication.Endpoint,
}
}
definition.SmokeTests = smokeTests
} }
definition := domain.Backend{ID: id, ExtraParams: params}
if config.Endpoint != nil { if config.Endpoint != nil {
definition.Endpoint = *config.Endpoint definition.Endpoint = *config.Endpoint
} }