113 lines
3.7 KiB
Go
113 lines
3.7 KiB
Go
package engine
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"os/exec"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type pointerOnlyLoop *pointerOnlyLoop
|
|
|
|
type pointerOnlyConfig struct {
|
|
Loop pointerOnlyLoop `yaml:"loop"`
|
|
}
|
|
|
|
func TestPointerOnlyDescriptorsTerminate(t *testing.T) {
|
|
if mode := os.Getenv("CONFIGKIT_POINTER_DESCRIPTOR_TEST_MODE"); mode != "" {
|
|
switch mode {
|
|
case "decode":
|
|
_, err := testLoad[pointerOnlyConfig](Bytes("config.yml", []byte("loop: {}\n")))
|
|
var loadErr *LoadError
|
|
if !errors.As(err, &loadErr) {
|
|
t.Fatalf("load error = %v, want a safe decode failure", err)
|
|
}
|
|
for _, diagnostic := range loadErr.Diagnostics() {
|
|
if diagnostic.Code == CodeDecodeType && diagnostic.Path.String() == "/loop" {
|
|
return
|
|
}
|
|
}
|
|
t.Fatalf("diagnostics = %#v, want decode type at /loop", loadErr.Diagnostics())
|
|
case "paths":
|
|
schema, issue := testBuildSchema(reflect.TypeFor[pointerOnlyConfig]())
|
|
if issue != nil {
|
|
t.Fatalf("schema issue = %#v", issue)
|
|
}
|
|
if _, ok := resolveSchemaPath(schema, Keys("loop", "child")); ok {
|
|
t.Fatal("descendant below a pointer-only loop was accepted")
|
|
}
|
|
if _, ok := schemaSecretRoot(schema, Keys("loop", "child")); ok {
|
|
t.Fatal("secret path below a pointer-only loop was accepted")
|
|
}
|
|
if acceptsMapping(schema.root.fields[schema.root.fieldByName["loop"]].descriptor) {
|
|
t.Fatal("pointer-only loop was accepted as a mapping")
|
|
}
|
|
default:
|
|
t.Fatalf("unknown test mode %q", mode)
|
|
}
|
|
return
|
|
}
|
|
|
|
for _, mode := range []string{"decode", "paths"} {
|
|
t.Run(mode, func(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
command := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestPointerOnlyDescriptorsTerminate$")
|
|
command.Env = append(os.Environ(), "CONFIGKIT_POINTER_DESCRIPTOR_TEST_MODE="+mode)
|
|
output, err := command.CombinedOutput()
|
|
if ctx.Err() != nil {
|
|
t.Fatalf("pointer descriptor %s did not terminate: %s", mode, output)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("pointer descriptor %s failed: %v, %s", mode, err, output)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPointerOnlyNullableFieldAndRecursiveValues(t *testing.T) {
|
|
for name, source := range map[string]Source{
|
|
"omitted": NoSource(),
|
|
"null": Bytes("config.yml", []byte("loop: null\n")),
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
result, err := testLoad[pointerOnlyConfig](source)
|
|
if err != nil || result.Config.Loop != nil {
|
|
t.Fatalf("result = %#v, error = %v", result, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
result, err := testLoad[recursiveConfig](Bytes("config.yml", []byte("name: root\nnext:\n name: child\n next:\n name: leaf\n")))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Config.Name != "root" || result.Config.Next == nil || result.Config.Next.Name != "child" || result.Config.Next.Next == nil || result.Config.Next.Next.Name != "leaf" {
|
|
t.Fatalf("recursive value = %#v", result.Config)
|
|
}
|
|
|
|
schema, issue := testBuildSchema(reflect.TypeFor[pointerOnlyConfig]())
|
|
if issue != nil {
|
|
t.Fatal(issue.message)
|
|
}
|
|
resolved, ok := resolveSchemaPath(schema, Keys("loop"))
|
|
if !ok || resolved.descriptor.kind != descriptorPointer {
|
|
t.Fatalf("nullable pointer field resolution = %#v, %v", resolved, ok)
|
|
}
|
|
|
|
called := false
|
|
nullableResult, err := testLoad[pointerOnlyConfig](NoSource(), Options[pointerOnlyConfig]{Definition: Definition[pointerOnlyConfig]{
|
|
Resolve: func(config *pointerOnlyConfig, resolution *Resolution) error {
|
|
called = true
|
|
Normalize(resolution, Keys("loop"), &config.Loop, nil, "clear nullable field")
|
|
return nil
|
|
},
|
|
}})
|
|
if err != nil || !called || nullableResult.Config.Loop != nil {
|
|
t.Fatalf("nullable field operation = %#v, called=%v, error=%v", nullableResult, called, err)
|
|
}
|
|
}
|