158 lines
4.3 KiB
Go
158 lines
4.3 KiB
Go
package engine
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"strconv"
|
|
"testing"
|
|
)
|
|
|
|
// Secret is a test-local facade secret. Production engine code must not depend
|
|
// on this type; each test load supplies the binding below explicitly.
|
|
type Secret struct {
|
|
name string
|
|
value string
|
|
specified bool
|
|
resolved bool
|
|
}
|
|
|
|
func SecretEnv(name string) Secret {
|
|
return Secret{name: name, specified: true}
|
|
}
|
|
|
|
func (s Secret) EnvName() string {
|
|
return s.name
|
|
}
|
|
|
|
func (s Secret) Value() string {
|
|
if !s.resolved {
|
|
return ""
|
|
}
|
|
return s.value
|
|
}
|
|
|
|
func (s Secret) IsResolved() bool {
|
|
return s.resolved
|
|
}
|
|
|
|
func (s Secret) Format(state fmt.State, _ rune) {
|
|
_, _ = io.WriteString(state, "configkit.SecretEnv("+strconv.Quote(s.name)+")")
|
|
}
|
|
|
|
func (s Secret) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(s.name)
|
|
}
|
|
|
|
func testSecretBinding() SecretBinding {
|
|
return SecretBinding{
|
|
Type: reflect.TypeFor[Secret](),
|
|
Read: func(value reflect.Value) SecretState {
|
|
secret := value.Interface().(Secret)
|
|
return SecretState{Name: secret.name, Value: secret.value, Specified: secret.specified, Resolved: secret.resolved}
|
|
},
|
|
Make: func(state SecretState) reflect.Value {
|
|
return reflect.ValueOf(Secret{name: state.Name, value: state.Value, specified: state.Specified, resolved: state.Resolved})
|
|
},
|
|
}
|
|
}
|
|
|
|
func testLoad[T any](source Source, supplied ...Options[T]) (*testResult[T], error) {
|
|
loaded, err := Load(source, testSecretBinding(), supplied...)
|
|
return testLoaded(loaded), err
|
|
}
|
|
|
|
func testLoadWithSecretLookup[T any](source Source, lookup secretLookup, supplied ...Options[T]) (*testResult[T], error) {
|
|
loaded, err := loadWithSecretLookup(source, testSecretBinding(), lookup, supplied...)
|
|
return testLoaded(loaded), err
|
|
}
|
|
|
|
type testResult[T any] struct {
|
|
Config T
|
|
*Inspection
|
|
}
|
|
|
|
func testLoaded[T any](loaded *Loaded[T]) *testResult[T] {
|
|
if loaded == nil {
|
|
return nil
|
|
}
|
|
return &testResult[T]{Config: loaded.Config, Inspection: loaded.Inspection}
|
|
}
|
|
func (r *testResult[T]) Metadata() Metadata {
|
|
if r == nil {
|
|
return Metadata{}
|
|
}
|
|
return r.Inspection.Metadata()
|
|
}
|
|
func (r *testResult[T]) Sources() []SourceInfo {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
return r.Inspection.Sources()
|
|
}
|
|
func (r *testResult[T]) Diagnostics() []Diagnostic {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
return r.Inspection.Diagnostics()
|
|
}
|
|
func (r *testResult[T]) Explain(path Path) (Explanation, bool) {
|
|
if r == nil {
|
|
return Explanation{}, false
|
|
}
|
|
return r.Inspection.Explain(path)
|
|
}
|
|
func (r *testResult[T]) WriteEffective(output io.Writer) error {
|
|
if r == nil {
|
|
return errEffectiveUnavailable
|
|
}
|
|
return r.Inspection.WriteEffective(output)
|
|
}
|
|
func (r *testResult[T]) WriteExplanation(output io.Writer, path Path) error {
|
|
if r == nil {
|
|
return errExplanationUnknownPath
|
|
}
|
|
return r.Inspection.WriteExplanation(output, path)
|
|
}
|
|
|
|
func testBuildSchema(root reflect.Type) (*schemaDescriptor, *schemaIssue) {
|
|
return buildSchema(root, testSecretBinding())
|
|
}
|
|
|
|
func TestSecretBindingsRemainExactAndIndependent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
type otherSecret struct {
|
|
name string
|
|
}
|
|
other := SecretBinding{
|
|
Type: reflect.TypeFor[otherSecret](),
|
|
Read: func(value reflect.Value) SecretState {
|
|
return SecretState{Name: value.Interface().(otherSecret).name}
|
|
},
|
|
Make: func(state SecretState) reflect.Value {
|
|
return reflect.ValueOf(otherSecret{name: state.Name})
|
|
},
|
|
}
|
|
type config struct {
|
|
First Secret `yaml:"first"`
|
|
Second otherSecret `yaml:"second"`
|
|
}
|
|
firstSchema, firstIssue := buildSchema(reflect.TypeFor[config](), testSecretBinding())
|
|
if firstIssue != nil {
|
|
t.Fatal(firstIssue)
|
|
}
|
|
secondSchema, secondIssue := buildSchema(reflect.TypeFor[config](), other)
|
|
if secondIssue != nil {
|
|
t.Fatal(secondIssue)
|
|
}
|
|
firstField := firstSchema.root.fields[firstSchema.root.fieldByName["first"]].descriptor
|
|
firstOther := firstSchema.root.fields[firstSchema.root.fieldByName["second"]].descriptor
|
|
secondField := secondSchema.root.fields[secondSchema.root.fieldByName["first"]].descriptor
|
|
secondOther := secondSchema.root.fields[secondSchema.root.fieldByName["second"]].descriptor
|
|
if firstField.kind != descriptorSecret || firstOther.kind == descriptorSecret || secondField.kind == descriptorSecret || secondOther.kind != descriptorSecret {
|
|
t.Fatalf("secret descriptor kinds = %v, %v, %v, %v", firstField.kind, firstOther.kind, secondField.kind, secondOther.kind)
|
|
}
|
|
}
|