package config import ( "os" "path/filepath" "regexp" "runtime" "sort" "strings" "testing" "github.com/bnema/dumber/internal/domain/entity" "github.com/spf13/viper" "github.com/stretchr/testify/require" ) func TestModeLegendSchemaDefaults(t *testing.T) { keys := make(map[string]entity.ConfigKeyInfo) for _, key := range NewSchemaProvider().GetSchema() { keys[key.Key] = key } for _, tc := range []struct{ key, kind, value, valueRange string }{ {"workspace.styling.mode_legend", "string", "delay", ""}, {"workspace.styling.mode_legend_delay_ms", "int", "500", ">=0"}, {"workspace.styling.mode_legend_linger", "bool", "true", ""}, {"workspace.styling.vim_mode_color", "string", "#F7768E", ""}, {"workspace.styling.mode_legend_animations", "bool", "true", ""}, } { entry, ok := keys[tc.key] require.True(t, ok, "missing schema key %s", tc.key) require.Equal(t, tc.kind, entry.Type) require.Equal(t, tc.value, entry.Default) require.Equal(t, tc.valueRange, entry.Range) } require.Equal(t, []string{"always", "delay", "off"}, keys["workspace.styling.mode_legend"].Values) } func TestConfigurationReferenceCoversSchemaKeys(t *testing.T) { docKeys := configurationReferenceKeys(t) var missing []string for _, key := range NewSchemaProvider().GetSchema() { if configurationReferenceCoversKey(docKeys, key.Key) { continue } missing = append(missing, key.Key) } sort.Strings(missing) require.Empty(t, missing, "docs/reference/configuration.md should cover every SchemaProvider key") } func configurationReferenceKeys(t *testing.T) map[string]struct{} { t.Helper() _, filename, _, ok := runtime.Caller(0) require.True(t, ok, "locate current test file") docPath := filepath.Join(filepath.Dir(filename), "..", "..", "..", "docs", "reference", "configuration.md") data, err := os.ReadFile(docPath) require.NoError(t, err) // Keep this split point aligned with the markdown heading so legacy-key rows // stay out of the canonical schema key coverage set. canonicalReference, _, _ := strings.Cut(string(data), "Legacy key migration:") rowRE := regexp.MustCompile(`(?m)^\| ` + "`" + `([^` + "`" + `]+)` + "`" + ` \|`) keys := make(map[string]struct{}) for _, match := range rowRE.FindAllStringSubmatch(canonicalReference, -1) { keys[match[1]] = struct{}{} } return keys } func configurationReferenceCoversKey(docKeys map[string]struct{}, schemaKey string) bool { if _, ok := docKeys[schemaKey]; ok { return true } schemaSegments := strings.Split(schemaKey, ".") for docKey := range docKeys { if configurationReferencePatternCoversKey(strings.Split(docKey, "."), schemaSegments) { return true } } return false } func configurationReferencePatternCoversKey(docSegments, schemaSegments []string) bool { for idx, schemaSegment := range schemaSegments { if schemaSegment == "*" { return idx == len(schemaSegments)-1 && len(docSegments) == idx+1 } if idx >= len(docSegments) { return false } if isPlaceholderSegment(schemaSegment) && isPlaceholderSegment(docSegments[idx]) { continue } if docSegments[idx] != schemaSegment { return false } } if len(docSegments) == len(schemaSegments) { return true } return configurationReferenceAllowsObjectFields(strings.Join(schemaSegments, ".")) && len(docSegments) > len(schemaSegments) } func isPlaceholderSegment(segment string) bool { return strings.HasPrefix(segment, "<") && strings.HasSuffix(segment, ">") } func configurationReferenceAllowsObjectFields(schemaKey string) bool { switch schemaKey { case "search_shortcuts.", "workspace.floating_pane.profiles.": return true default: return false } } func TestConfigurationReferenceCoversKeyRequiresPlaceholderSegmentMatches(t *testing.T) { docKeys := map[string]struct{}{ "workspace.floating_pane.profiles..keys_extra": {}, } require.False(t, configurationReferenceCoversKey( docKeys, "workspace.floating_pane.profiles..keys", )) } func TestConfigurationReferenceCoversKeyAllowsDocumentedPlaceholderSegments(t *testing.T) { docKeys := map[string]struct{}{ "workspace.floating_pane.profiles..keys": {}, } require.True(t, configurationReferenceCoversKey( docKeys, "workspace.floating_pane.profiles..keys", )) } func TestConfigurationReferenceCoversKeyAllowsDocumentedObjectFields(t *testing.T) { docKeys := map[string]struct{}{ "search_shortcuts..url": {}, } require.True(t, configurationReferenceCoversKey(docKeys, "search_shortcuts.")) } func TestSchemaProviderCoversViperDefaults(t *testing.T) { m := &Manager{viper: viper.New()} m.setDefaults() schemaKeys := make(map[string]struct{}) for _, key := range NewSchemaProvider().GetSchema() { require.NotEmpty(t, key.Key, "schema key should not be empty") require.NotContains(t, schemaKeys, key.Key, "duplicate schema key %s", key.Key) schemaKeys[key.Key] = struct{}{} } defaultsCoveredByAggregateSchemaKeys := map[string]string{ "appearance.light_palette": "appearance.light_palette.*", "appearance.dark_palette": "appearance.dark_palette.*", "search_shortcuts": "search_shortcuts.", "workspace.pane_mode.actions": "workspace.pane_mode.actions.", "workspace.tab_mode.actions": "workspace.tab_mode.actions.", "workspace.vim_mode.actions": "workspace.vim_mode.actions.", "workspace.resize_mode.actions": "workspace.resize_mode.actions.", "workspace.floating_pane.profiles": "workspace.floating_pane.profiles.", "session.session_mode.actions": "session.session_mode.actions.", } var missing []string for _, key := range m.viper.AllKeys() { if _, ok := schemaKeys[key]; ok { continue } if schemaKey, ok := defaultsCoveredByAggregateSchemaKeys[key]; ok { if _, hasAggregate := schemaKeys[schemaKey]; hasAggregate { continue } } missing = append(missing, key) } sort.Strings(missing) require.Empty(t, missing, "every Viper default should have SchemaProvider metadata or an explicit aggregate schema key") }