mcp-framework/cli/resolve_test.go

292 lines
7.1 KiB
Go
Raw Normal View History

package cli
import (
"bytes"
"errors"
"strings"
"testing"
)
func TestResolveFieldsUsesDefaultOrderAndTracksSource(t *testing.T) {
resolution, err := ResolveFields(ResolveOptions{
Fields: []FieldSpec{
{
Name: "base_url",
Required: true,
FlagKey: "base-url",
},
{
Name: "api_token",
Required: true,
EnvKey: "API_TOKEN",
SecretKey: "my-api-token",
},
{
Name: "stream_id",
},
},
Lookup: StaticLookup{
Flags: map[string]string{
"base-url": "https://flag.example.com",
},
Env: map[string]string{
"base_url": "https://env.example.com",
"API_TOKEN": "",
},
Config: map[string]string{
"stream_id": "stream-from-config",
},
Secrets: map[string]string{
"my-api-token": "secret-token",
},
}.Lookup,
})
if err != nil {
t.Fatalf("ResolveFields returned error: %v", err)
}
baseURL, ok := resolution.Get("base_url")
if !ok {
t.Fatalf("base_url was not resolved")
}
if !baseURL.Found {
t.Fatalf("base_url must be found")
}
if baseURL.Source != SourceFlag {
t.Fatalf("base_url source = %q, want %q", baseURL.Source, SourceFlag)
}
if baseURL.Value != "https://flag.example.com" {
t.Fatalf("base_url value = %q", baseURL.Value)
}
apiToken, ok := resolution.Get("api_token")
if !ok {
t.Fatalf("api_token was not resolved")
}
if apiToken.Source != SourceSecret {
t.Fatalf("api_token source = %q, want %q", apiToken.Source, SourceSecret)
}
if apiToken.Value != "secret-token" {
t.Fatalf("api_token value = %q", apiToken.Value)
}
streamID, ok := resolution.Get("stream_id")
if !ok {
t.Fatalf("stream_id was not resolved")
}
if streamID.Source != SourceConfig {
t.Fatalf("stream_id source = %q, want %q", streamID.Source, SourceConfig)
}
if streamID.Value != "stream-from-config" {
t.Fatalf("stream_id value = %q", streamID.Value)
}
}
func TestResolveFieldsSupportsCustomGlobalOrder(t *testing.T) {
resolution, err := ResolveFields(ResolveOptions{
Fields: []FieldSpec{
{
Name: "base_url",
Required: true,
},
},
Order: []ValueSource{SourceConfig, SourceEnv, SourceFlag},
Lookup: StaticLookup{
Flags: map[string]string{
"base_url": "https://flag.example.com",
},
Env: map[string]string{
"base_url": "https://env.example.com",
},
Config: map[string]string{
"base_url": "https://config.example.com",
},
}.Lookup,
})
if err != nil {
t.Fatalf("ResolveFields returned error: %v", err)
}
baseURL, _ := resolution.Get("base_url")
if baseURL.Source != SourceConfig {
t.Fatalf("base_url source = %q, want %q", baseURL.Source, SourceConfig)
}
}
func TestResolveFieldsSupportsPerFieldOrderAndDefaultValues(t *testing.T) {
resolution, err := ResolveFields(ResolveOptions{
Fields: []FieldSpec{
{
Name: "api_token",
Required: true,
Sources: []ValueSource{SourceSecret, SourceEnv},
SecretKey: "API_TOKEN_SECRET",
DefaultValue: "default-token",
},
{
Name: "region",
DefaultValue: "eu-west",
},
},
Lookup: StaticLookup{
Env: map[string]string{
"api_token": "env-token",
},
Secrets: map[string]string{
"API_TOKEN_SECRET": "secret-token",
},
}.Lookup,
})
if err != nil {
t.Fatalf("ResolveFields returned error: %v", err)
}
apiToken, _ := resolution.Get("api_token")
if apiToken.Source != SourceSecret {
t.Fatalf("api_token source = %q, want %q", apiToken.Source, SourceSecret)
}
if apiToken.Value != "secret-token" {
t.Fatalf("api_token value = %q", apiToken.Value)
}
region, _ := resolution.Get("region")
if region.Source != SourceDefault {
t.Fatalf("region source = %q, want %q", region.Source, SourceDefault)
}
if region.Value != "eu-west" {
t.Fatalf("region value = %q, want eu-west", region.Value)
}
}
func TestResolveFieldsReturnsHomogeneousMissingRequiredError(t *testing.T) {
resolution, err := ResolveFields(ResolveOptions{
Fields: []FieldSpec{
{Name: "base_url", Required: true},
{Name: "api_token", Required: true},
{Name: "region"},
},
Lookup: StaticLookup{}.Lookup,
})
var missingErr *MissingRequiredValuesError
if !errors.As(err, &missingErr) {
t.Fatalf("ResolveFields error = %v, want MissingRequiredValuesError", err)
}
if got := strings.Join(missingErr.Fields, ","); got != "base_url,api_token" {
t.Fatalf("missing fields = %q, want base_url,api_token", got)
}
region, ok := resolution.Get("region")
if !ok {
t.Fatalf("region should exist in partial resolution")
}
if region.Found {
t.Fatalf("region should not be found")
}
}
func TestResolveFieldsWrapsLookupErrorsWithContext(t *testing.T) {
lookupErr := errors.New("secret backend unavailable")
_, err := ResolveFields(ResolveOptions{
Fields: []FieldSpec{
{
Name: "api_token",
Sources: []ValueSource{SourceSecret},
},
},
Lookup: func(source ValueSource, key string) (string, bool, error) {
return "", false, lookupErr
},
})
var sourceErr *SourceLookupError
if !errors.As(err, &sourceErr) {
t.Fatalf("ResolveFields error = %v, want SourceLookupError", err)
}
if sourceErr.Field != "api_token" {
t.Fatalf("sourceErr.Field = %q, want api_token", sourceErr.Field)
}
if sourceErr.Source != SourceSecret {
t.Fatalf("sourceErr.Source = %q, want %q", sourceErr.Source, SourceSecret)
}
if !errors.Is(err, lookupErr) {
t.Fatalf("ResolveFields error should wrap the original lookup error")
}
}
func TestResolveFieldsRejectsInvalidDefinitions(t *testing.T) {
tests := []struct {
name string
options ResolveOptions
}{
{
name: "missing lookup",
options: ResolveOptions{
Fields: []FieldSpec{{Name: "base_url"}},
},
},
{
name: "empty field name",
options: ResolveOptions{
Fields: []FieldSpec{{Name: " "}},
Lookup: StaticLookup{}.Lookup,
},
},
{
name: "duplicate field names",
options: ResolveOptions{
Fields: []FieldSpec{{Name: "base_url"}, {Name: "base_url"}},
Lookup: StaticLookup{}.Lookup,
},
},
{
name: "unknown source in order",
options: ResolveOptions{
Fields: []FieldSpec{{Name: "base_url"}},
Order: []ValueSource{"kv"},
Lookup: StaticLookup{}.Lookup,
},
},
{
name: "default source forbidden in order",
options: ResolveOptions{
Fields: []FieldSpec{{Name: "base_url"}},
Order: []ValueSource{SourceDefault},
Lookup: StaticLookup{}.Lookup,
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, err := ResolveFields(tc.options)
if !errors.Is(err, ErrInvalidResolverInput) {
t.Fatalf("ResolveFields error = %v, want ErrInvalidResolverInput", err)
}
})
}
}
func TestRenderResolutionProvenance(t *testing.T) {
var out bytes.Buffer
err := RenderResolutionProvenance(&out, Resolution{
Fields: []ResolvedField{
{Name: "base_url", Source: SourceFlag, Found: true},
{Name: "api_token", Found: false},
},
})
if err != nil {
t.Fatalf("RenderResolutionProvenance returned error: %v", err)
}
text := out.String()
if !strings.Contains(text, "base_url: flag") {
t.Fatalf("output = %q, want base_url provenance", text)
}
if !strings.Contains(text, "api_token: missing") {
t.Fatalf("output = %q, want missing provenance", text)
}
}