detached 41c038a

test(config): guard schema metadata against default drift

commit
41c038a41edc0172189544ecf7ada903c6b6fb89
author
brice · · 2026-06-25 12:12 UTC
parent
f7c186f
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · 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 (
1818 SectionContentFiltering = "Content Filtering"
1919 SectionRendering = "Rendering"
2020 SectionMedia = "Media"
21+ SectionPrivacy = "Privacy"
2122 SectionUpdate = "Update"
2223 SectionDmenu = "Dmenu"
2324 SectionDebug = "Debug"
@@ -78,6 +79,9 @@ func (p *SchemaProvider) GetSchema() []entity.ConfigKeyInfo {
7879 // Media section
7980 keys = append(keys, p.getMediaKeys(defaults)...)
8081
82+ // Privacy section
83+ keys = append(keys, p.getPrivacyKeys(defaults)...)
84+
8185 // Update section
8286 keys = append(keys, p.getUpdateKeys(defaults)...)
8387
@@ -271,6 +275,13 @@ func (*SchemaProvider) getLoggingKeys(defaults *Config) []entity.ConfigKeyInfo {
271275 Description: "Capture browser console messages to log",
272276 Section: SectionLogging,
273277 },
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+ },
274285 }
275286 }
276287
@@ -776,6 +787,14 @@ func (*SchemaProvider) getClipboardKeys(defaults *Config) []entity.ConfigKeyInfo
776787
777788 func (*SchemaProvider) getRenderingKeys(defaults *Config) []entity.ConfigKeyInfo {
778789 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+ },
779798 {
780799 Key: "engine.webkit.gsk_renderer",
781800 Type: "string",
@@ -874,6 +893,49 @@ func (*SchemaProvider) getMediaKeys(defaults *Config) []entity.ConfigKeyInfo {
874893 Description: "Show media capability warnings at startup",
875894 Section: SectionMedia,
876895 },
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+ },
877939 }
878940 }
879941
@@ -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 section79 // 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 section85 // 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+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close