92 lines
2.8 KiB
Go
92 lines
2.8 KiB
Go
package config
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
// Kinds that LoadFromViper knows how to read.
|
|
var viperKinds = map[reflect.Kind]bool{
|
|
reflect.String: true, reflect.Int: true, reflect.Bool: true,
|
|
}
|
|
|
|
// Every key round-trips through TypeOf/ValueOf/SetValue and has a type that
|
|
// LoadFromViper can read.
|
|
func TestKeysRoundTrip(t *testing.T) {
|
|
var conf JobSearchConfig
|
|
if err := applyDefaults(&conf); err != nil {
|
|
t.Fatalf("defaults: %v", err)
|
|
}
|
|
for key, want := range conf.Flatten() {
|
|
kind, err := conf.TypeOf(key)
|
|
if err != nil {
|
|
t.Errorf("TypeOf(%q): %v", key, err)
|
|
} else if !viperKinds[kind] {
|
|
t.Errorf("TypeOf(%q) = %v, which LoadFromViper does not handle", key, kind)
|
|
}
|
|
got, err := conf.ValueOf(key)
|
|
if err != nil || !reflect.DeepEqual(got, want) {
|
|
t.Errorf("ValueOf(%q) = %#v, %v; want %#v", key, got, err, want)
|
|
}
|
|
if err := conf.SetValue(key, got); err != nil {
|
|
t.Errorf("SetValue(%q, %#v): %v", key, got, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// sectionType is the Section interface type.
|
|
var sectionType = reflect.TypeOf((*Section)(nil)).Elem()
|
|
|
|
// Every section, including embedded ones, implements Section, and its
|
|
// Flatten keys (which use the section's JSCK* constant) line up with its cfg
|
|
// tag in the parent. Sections are found by walking JobSearchConfig, so a new
|
|
// section is checked without being listed anywhere.
|
|
func TestSections(t *testing.T) {
|
|
var conf JobSearchConfig
|
|
if !reflect.PointerTo(reflect.TypeOf(conf)).Implements(sectionType) {
|
|
t.Error("*JobSearchConfig does not implement Section")
|
|
}
|
|
all := conf.Flatten()
|
|
checkSections(t, all, "", reflect.ValueOf(&conf).Elem())
|
|
}
|
|
|
|
func checkSections(t *testing.T, all map[string]any, prefix string, section reflect.Value) {
|
|
for _, f := range fieldsOf(section.Type()) {
|
|
if !f.section {
|
|
continue
|
|
}
|
|
fv := section.FieldByIndex(f.index)
|
|
name := fv.Type().Name()
|
|
if !fv.Addr().Type().Implements(sectionType) {
|
|
t.Errorf("*%s does not implement Section", name)
|
|
}
|
|
checkEmbedded(t, fv.Type())
|
|
if flatten := fv.MethodByName("Flatten"); flatten.IsValid() {
|
|
keys := flatten.Call(nil)[0].Interface().(map[string]any)
|
|
for k := range keys {
|
|
if _, ok := all[joinKey(prefix, k)]; !ok {
|
|
t.Errorf("%s.Flatten() key %q does not match its cfg tag %q",
|
|
name, k, joinKey(prefix, f.key))
|
|
}
|
|
}
|
|
}
|
|
checkSections(t, all, joinKey(prefix, f.key), fv)
|
|
}
|
|
}
|
|
|
|
// checkEmbedded checks that structs embedded in a section, such as
|
|
// JobSearchConfigSource, implement Section too.
|
|
func checkEmbedded(t *testing.T, section reflect.Type) {
|
|
for i := 0; i < section.NumField(); i++ {
|
|
sf := section.Field(i)
|
|
if !sf.Anonymous || sf.Type.Kind() != reflect.Struct {
|
|
continue
|
|
}
|
|
if !reflect.PointerTo(sf.Type).Implements(sectionType) {
|
|
t.Errorf("*%s (embedded in %s) does not implement Section",
|
|
sf.Type.Name(), section.Name())
|
|
}
|
|
checkEmbedded(t, sf.Type)
|
|
}
|
|
}
|