test(config): guard schema metadata against default drift
- commit
- 41c038a41edc0172189544ecf7ada903c6b6fb89
- author
- brice · · 2026-06-25 12:12 UTC
- parent
- f7c186f
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +110 -0
M internal/infrastructure/config/schema_provider.go +62 -0
| @@ -18,6 +18,7 @@ const ( | |||
| 18 | 18 | SectionContentFiltering = "Content Filtering" | |
| 19 | 19 | SectionRendering = "Rendering" | |
| 20 | 20 | SectionMedia = "Media" | |
| 21 | + | SectionPrivacy = "Privacy" | |
| 21 | 22 | SectionUpdate = "Update" | |
| 22 | 23 | SectionDmenu = "Dmenu" | |
| 23 | 24 | SectionDebug = "Debug" | |
| @@ -78,6 +79,9 @@ func (p *SchemaProvider) GetSchema() []entity.ConfigKeyInfo { | |||
| 78 | 79 | // Media section | |
| 79 | 80 | keys = append(keys, p.getMediaKeys(defaults)...) | |
| 80 | 81 | ||
| 82 | + | // Privacy section | |
| 83 | + | keys = append(keys, p.getPrivacyKeys(defaults)...) | |
| 84 | + | ||
| 81 | 85 | // Update section | |
| 82 | 86 | keys = append(keys, p.getUpdateKeys(defaults)...) | |
| 83 | 87 | ||
| @@ -271,6 +275,13 @@ func (*SchemaProvider) getLoggingKeys(defaults *Config) []entity.ConfigKeyInfo { | |||
| 271 | 275 | Description: "Capture browser console messages to log", | |
| 272 | 276 | Section: SectionLogging, | |
| 273 | 277 | }, | |
| 278 | + | { | |
| 279 | + | Key: "logging.capture_gtk_logs", | |
| 280 | + | Type: "bool", | |
| 281 | + | Default: fmt.Sprintf("%t", defaults.Logging.CaptureGTKLogs), | |
| 282 | + | Description: "Capture GTK and GLib log messages", | |
| 283 | + | Section: SectionLogging, | |
| 284 | + | }, | |
| 274 | 285 | } | |
| 275 | 286 | } | |
| 276 | 287 | ||
| @@ -776,6 +787,14 @@ func (*SchemaProvider) getClipboardKeys(defaults *Config) []entity.ConfigKeyInfo | |||
| 776 | 787 | ||
| 777 | 788 | func (*SchemaProvider) getRenderingKeys(defaults *Config) []entity.ConfigKeyInfo { | |
| 778 | 789 | return []entity.ConfigKeyInfo{ | |
| 790 | + | { | |
| 791 | + | Key: "engine.type", | |
| 792 | + | Type: "string", | |
| 793 | + | Default: defaults.Engine.Type, | |
| 794 | + | Description: "Browser engine selection; CEF is the default and WebKitGTK is the fallback", | |
| 795 | + | Values: []string{"cef", "webkit"}, | |
| 796 | + | Section: SectionRendering, | |
| 797 | + | }, | |
| 779 | 798 | { | |
| 780 | 799 | Key: "engine.webkit.gsk_renderer", | |
| 781 | 800 | Type: "string", | |
| @@ -874,6 +893,49 @@ func (*SchemaProvider) getMediaKeys(defaults *Config) []entity.ConfigKeyInfo { | |||
| 874 | 893 | Description: "Show media capability warnings at startup", | |
| 875 | 894 | Section: SectionMedia, | |
| 876 | 895 | }, | |
| 896 | + | { | |
| 897 | + | Key: "engine.webkit.force_vsync", | |
| 898 | + | Type: "bool", | |
| 899 | + | Default: fmt.Sprintf("%t", defaults.Engine.WebKit.ForceVSync), | |
| 900 | + | Description: "Force VSync for WebKit fallback video playback", | |
| 901 | + | Section: SectionMedia, | |
| 902 | + | }, | |
| 903 | + | { | |
| 904 | + | Key: "engine.webkit.gl_rendering_mode", | |
| 905 | + | Type: "string", | |
| 906 | + | Default: string(defaults.Engine.WebKit.GLRenderingMode), | |
| 907 | + | Description: "WebKit fallback GStreamer/OpenGL API selection", | |
| 908 | + | Values: []string{"auto", "gles2", "gl3", "none"}, | |
| 909 | + | Section: SectionMedia, | |
| 910 | + | }, | |
| 911 | + | { | |
| 912 | + | Key: "engine.webkit.gstreamer_debug_level", | |
| 913 | + | Type: "int", | |
| 914 | + | Default: fmt.Sprintf("%d", defaults.Engine.WebKit.GStreamerDebugLevel), | |
| 915 | + | Description: "WebKit fallback GStreamer debug verbosity", | |
| 916 | + | Range: "0-5", | |
| 917 | + | Section: SectionMedia, | |
| 918 | + | }, | |
| 919 | + | } | |
| 920 | + | } | |
| 921 | + | ||
| 922 | + | func (*SchemaProvider) getPrivacyKeys(defaults *Config) []entity.ConfigKeyInfo { | |
| 923 | + | return []entity.ConfigKeyInfo{ | |
| 924 | + | { | |
| 925 | + | Key: "engine.cookie_policy", | |
| 926 | + | Type: "string", | |
| 927 | + | Default: string(defaults.Engine.CookiePolicy), | |
| 928 | + | Description: "Cookie acceptance policy", | |
| 929 | + | Values: []string{"always", "no_third_party", "never"}, | |
| 930 | + | Section: SectionPrivacy, | |
| 931 | + | }, | |
| 932 | + | { | |
| 933 | + | Key: "engine.webkit.itp_enabled", | |
| 934 | + | Type: "bool", | |
| 935 | + | Default: fmt.Sprintf("%t", defaults.Engine.WebKit.ITPEnabled), | |
| 936 | + | Description: "Enable WebKit fallback Intelligent Tracking Prevention", | |
| 937 | + | Section: SectionPrivacy, | |
| 938 | + | }, | |
| 877 | 939 | } | |
| 878 | 940 | } | |
| 879 | 941 | ||
| @@ -18,6 +18,7 @@ const ( | |||||
| 18 | SectionContentFiltering = "Content Filtering" | 18 | SectionContentFiltering = "Content Filtering" | ||
| 19 | SectionRendering = "Rendering" | 19 | SectionRendering = "Rendering" | ||
| 20 | SectionMedia = "Media" | 20 | SectionMedia = "Media" | ||
| 21 | + | SectionPrivacy = "Privacy" | |||
| 21 | SectionUpdate = "Update" | 22 | SectionUpdate = "Update" | ||
| 22 | SectionDmenu = "Dmenu" | 23 | SectionDmenu = "Dmenu" | ||
| 23 | SectionDebug = "Debug" | 24 | SectionDebug = "Debug" | ||
| @@ -78,6 +79,9 @@ func (p *SchemaProvider) GetSchema() []entity.ConfigKeyInfo { | |||||
| 78 | // Media section | 79 | // Media section | ||
| 79 | keys = append(keys, p.getMediaKeys(defaults)...) | 80 | keys = append(keys, p.getMediaKeys(defaults)...) | ||
| 80 | 81 | ||||
| 82 | + | // Privacy section | |||
| 83 | + | keys = append(keys, p.getPrivacyKeys(defaults)...) | |||
| 84 | + | ||||
| 81 | // Update section | 85 | // Update section | ||
| 82 | keys = append(keys, p.getUpdateKeys(defaults)...) | 86 | keys = append(keys, p.getUpdateKeys(defaults)...) | ||
| 83 | 87 | ||||
| @@ -271,6 +275,13 @@ func (*SchemaProvider) getLoggingKeys(defaults *Config) []entity.ConfigKeyInfo { | |||||
| 271 | Description: "Capture browser console messages to log", | 275 | Description: "Capture browser console messages to log", | ||
| 272 | Section: SectionLogging, | 276 | Section: SectionLogging, | ||
| 273 | }, | 277 | }, | ||
| 278 | + | { | |||
| 279 | + | Key: "logging.capture_gtk_logs", | |||
| 280 | + | Type: "bool", | |||
| 281 | + | Default: fmt.Sprintf("%t", defaults.Logging.CaptureGTKLogs), | |||
| 282 | + | Description: "Capture GTK and GLib log messages", | |||
| 283 | + | Section: SectionLogging, | |||
| 284 | + | }, | |||
| 274 | } | 285 | } | ||
| 275 | } | 286 | } | ||
| 276 | 287 | ||||
| @@ -776,6 +787,14 @@ func (*SchemaProvider) getClipboardKeys(defaults *Config) []entity.ConfigKeyInfo | |||||
| 776 | 787 | ||||
| 777 | func (*SchemaProvider) getRenderingKeys(defaults *Config) []entity.ConfigKeyInfo { | 788 | func (*SchemaProvider) getRenderingKeys(defaults *Config) []entity.ConfigKeyInfo { | ||
| 778 | return []entity.ConfigKeyInfo{ | 789 | return []entity.ConfigKeyInfo{ | ||
| 790 | + | { | |||
| 791 | + | Key: "engine.type", | |||
| 792 | + | Type: "string", | |||
| 793 | + | Default: defaults.Engine.Type, | |||
| 794 | + | Description: "Browser engine selection; CEF is the default and WebKitGTK is the fallback", | |||
| 795 | + | Values: []string{"cef", "webkit"}, | |||
| 796 | + | Section: SectionRendering, | |||
| 797 | + | }, | |||
| 779 | { | 798 | { | ||
| 780 | Key: "engine.webkit.gsk_renderer", | 799 | Key: "engine.webkit.gsk_renderer", | ||
| 781 | Type: "string", | 800 | Type: "string", | ||
| @@ -874,6 +893,49 @@ func (*SchemaProvider) getMediaKeys(defaults *Config) []entity.ConfigKeyInfo { | |||||
| 874 | Description: "Show media capability warnings at startup", | 893 | Description: "Show media capability warnings at startup", | ||
| 875 | Section: SectionMedia, | 894 | Section: SectionMedia, | ||
| 876 | }, | 895 | }, | ||
| 896 | + | { | |||
| 897 | + | Key: "engine.webkit.force_vsync", | |||
| 898 | + | Type: "bool", | |||
| 899 | + | Default: fmt.Sprintf("%t", defaults.Engine.WebKit.ForceVSync), | |||
| 900 | + | Description: "Force VSync for WebKit fallback video playback", | |||
| 901 | + | Section: SectionMedia, | |||
| 902 | + | }, | |||
| 903 | + | { | |||
| 904 | + | Key: "engine.webkit.gl_rendering_mode", | |||
| 905 | + | Type: "string", | |||
| 906 | + | Default: string(defaults.Engine.WebKit.GLRenderingMode), | |||
| 907 | + | Description: "WebKit fallback GStreamer/OpenGL API selection", | |||
| 908 | + | Values: []string{"auto", "gles2", "gl3", "none"}, | |||
| 909 | + | Section: SectionMedia, | |||
| 910 | + | }, | |||
| 911 | + | { | |||
| 912 | + | Key: "engine.webkit.gstreamer_debug_level", | |||
| 913 | + | Type: "int", | |||
| 914 | + | Default: fmt.Sprintf("%d", defaults.Engine.WebKit.GStreamerDebugLevel), | |||
| 915 | + | Description: "WebKit fallback GStreamer debug verbosity", | |||
| 916 | + | Range: "0-5", | |||
| 917 | + | Section: SectionMedia, | |||
| 918 | + | }, | |||
| 919 | + | } | |||
| 920 | + | } | |||
| 921 | + | ||||
| 922 | + | func (*SchemaProvider) getPrivacyKeys(defaults *Config) []entity.ConfigKeyInfo { | |||
| 923 | + | return []entity.ConfigKeyInfo{ | |||
| 924 | + | { | |||
| 925 | + | Key: "engine.cookie_policy", | |||
| 926 | + | Type: "string", | |||
| 927 | + | Default: string(defaults.Engine.CookiePolicy), | |||
| 928 | + | Description: "Cookie acceptance policy", | |||
| 929 | + | Values: []string{"always", "no_third_party", "never"}, | |||
| 930 | + | Section: SectionPrivacy, | |||
| 931 | + | }, | |||
| 932 | + | { | |||
| 933 | + | Key: "engine.webkit.itp_enabled", | |||
| 934 | + | Type: "bool", | |||
| 935 | + | Default: fmt.Sprintf("%t", defaults.Engine.WebKit.ITPEnabled), | |||
| 936 | + | Description: "Enable WebKit fallback Intelligent Tracking Prevention", | |||
| 937 | + | Section: SectionPrivacy, | |||
| 938 | + | }, | |||
| 877 | } | 939 | } | ||
| 878 | } | 940 | } | ||
| 879 | 941 | ||||
A internal/infrastructure/config/schema_provider_test.go +48 -0
| @@ -0,0 +1,48 @@ | |||
| 1 | + | package config | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "sort" | |
| 5 | + | "testing" | |
| 6 | + | ||
| 7 | + | "github.com/spf13/viper" | |
| 8 | + | "github.com/stretchr/testify/require" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | func TestSchemaProviderCoversViperDefaults(t *testing.T) { | |
| 12 | + | m := &Manager{viper: viper.New()} | |
| 13 | + | m.setDefaults() | |
| 14 | + | ||
| 15 | + | schemaKeys := make(map[string]struct{}) | |
| 16 | + | for _, key := range NewSchemaProvider().GetSchema() { | |
| 17 | + | require.NotEmpty(t, key.Key, "schema key should not be empty") | |
| 18 | + | require.NotContains(t, schemaKeys, key.Key, "duplicate schema key %s", key.Key) | |
| 19 | + | schemaKeys[key.Key] = struct{}{} | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | defaultsCoveredByAggregateSchemaKeys := map[string]string{ | |
| 23 | + | "appearance.light_palette": "appearance.light_palette.*", | |
| 24 | + | "appearance.dark_palette": "appearance.dark_palette.*", | |
| 25 | + | "search_shortcuts": "search_shortcuts.<key>", | |
| 26 | + | "workspace.pane_mode.actions": "workspace.pane_mode.actions.<action>", | |
| 27 | + | "workspace.tab_mode.actions": "workspace.tab_mode.actions.<action>", | |
| 28 | + | "workspace.resize_mode.actions": "workspace.resize_mode.actions.<action>", | |
| 29 | + | "workspace.floating_pane.profiles": "workspace.floating_pane.profiles.<name>", | |
| 30 | + | "session.session_mode.actions": "session.session_mode.actions.<action>", | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | var missing []string | |
| 34 | + | for _, key := range m.viper.AllKeys() { | |
| 35 | + | if _, ok := schemaKeys[key]; ok { | |
| 36 | + | continue | |
| 37 | + | } | |
| 38 | + | if schemaKey, ok := defaultsCoveredByAggregateSchemaKeys[key]; ok { | |
| 39 | + | if _, hasAggregate := schemaKeys[schemaKey]; hasAggregate { | |
| 40 | + | continue | |
| 41 | + | } | |
| 42 | + | } | |
| 43 | + | missing = append(missing, key) | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | sort.Strings(missing) | |
| 47 | + | require.Empty(t, missing, "every Viper default should have SchemaProvider metadata or an explicit aggregate schema key") | |
| 48 | + | } | |
| @@ -0,0 +1,48 @@ | |||||
| 1 | + | package config | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "sort" | |||
| 5 | + | "testing" | |||
| 6 | + | ||||
| 7 | + | "github.com/spf13/viper" | |||
| 8 | + | "github.com/stretchr/testify/require" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | func TestSchemaProviderCoversViperDefaults(t *testing.T) { | |||
| 12 | + | m := &Manager{viper: viper.New()} | |||
| 13 | + | m.setDefaults() | |||
| 14 | + | ||||
| 15 | + | schemaKeys := make(map[string]struct{}) | |||
| 16 | + | for _, key := range NewSchemaProvider().GetSchema() { | |||
| 17 | + | require.NotEmpty(t, key.Key, "schema key should not be empty") | |||
| 18 | + | require.NotContains(t, schemaKeys, key.Key, "duplicate schema key %s", key.Key) | |||
| 19 | + | schemaKeys[key.Key] = struct{}{} | |||
| 20 | + | } | |||
| 21 | + | ||||
| 22 | + | defaultsCoveredByAggregateSchemaKeys := map[string]string{ | |||
| 23 | + | "appearance.light_palette": "appearance.light_palette.*", | |||
| 24 | + | "appearance.dark_palette": "appearance.dark_palette.*", | |||
| 25 | + | "search_shortcuts": "search_shortcuts.<key>", | |||
| 26 | + | "workspace.pane_mode.actions": "workspace.pane_mode.actions.<action>", | |||
| 27 | + | "workspace.tab_mode.actions": "workspace.tab_mode.actions.<action>", | |||
| 28 | + | "workspace.resize_mode.actions": "workspace.resize_mode.actions.<action>", | |||
| 29 | + | "workspace.floating_pane.profiles": "workspace.floating_pane.profiles.<name>", | |||
| 30 | + | "session.session_mode.actions": "session.session_mode.actions.<action>", | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | var missing []string | |||
| 34 | + | for _, key := range m.viper.AllKeys() { | |||
| 35 | + | if _, ok := schemaKeys[key]; ok { | |||
| 36 | + | continue | |||
| 37 | + | } | |||
| 38 | + | if schemaKey, ok := defaultsCoveredByAggregateSchemaKeys[key]; ok { | |||
| 39 | + | if _, hasAggregate := schemaKeys[schemaKey]; hasAggregate { | |||
| 40 | + | continue | |||
| 41 | + | } | |||
| 42 | + | } | |||
| 43 | + | missing = append(missing, key) | |||
| 44 | + | } | |||
| 45 | + | ||||
| 46 | + | sort.Strings(missing) | |||
| 47 | + | require.Empty(t, missing, "every Viper default should have SchemaProvider metadata or an explicit aggregate schema key") | |||
| 48 | + | } | |||