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) } }