detached d702cff

refactor(cef): honor engine options and state root

commit
d702cff6800ad0260a918afad0421c633f4493bc
author
brice · · 2026-04-11 12:46 UTC
parent
5222707
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +125 -23
M internal/bootstrap/engine.go +21 -2
@@ -42,9 +42,28 @@ func BuildEngine(input EngineInput) (port.Engine, error) {
4242 input.ThemeManager, input.ColorResolver, input.Logger,
4343 )
4444 case config.EngineTypeCEF:
45- cefCfg := cfg.Engine.CEF
45+ opts := port.EngineOptions{
46+ DataDir: input.DataDir,
47+ CacheDir: input.CacheDir,
48+ CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy),
49+ }
50+ cefCfg := cef.RuntimeConfig{
51+ CEFDir: cfg.Engine.CEF.CEFDir,
52+ LogFile: cfg.Engine.CEF.LogFile,
53+ LogSeverity: cfg.Engine.CEF.LogSeverity,
54+ WindowlessFrameRate: cfg.Engine.CEF.WindowlessFrameRate,
55+ EnableAudioHandler: cfg.Engine.CEF.EnableAudioHandler,
56+ EnableContextMenuHandler: cfg.Engine.CEF.EnableContextMenuHandler,
57+ TraceHandlers: cfg.Engine.CEF.TraceHandlers,
58+ }
59+ transcodingCfg := cef.TranscodingRuntimeConfig{
60+ Enabled: cfg.Transcoding.Enabled,
61+ HWAccel: cfg.Transcoding.HWAccel,
62+ MaxConcurrent: cfg.Transcoding.MaxConcurrent,
63+ Quality: cfg.Transcoding.Quality,
64+ }
4665 audioFactory := audiofactory.NewAudioOutputFactory()
47- return cef.NewEngine(input.Ctx, cefCfg, cfg.Transcoding, audioFactory)
66+ return cef.NewEngine(input.Ctx, opts, cefCfg, transcodingCfg, audioFactory, cef.EngineDependencies{})
4867 default:
4968 return nil, fmt.Errorf("unknown engine type: %q", engineType)
5069 }
@@ -42,9 +42,28 @@ func BuildEngine(input EngineInput) (port.Engine, error) {
42 input.ThemeManager, input.ColorResolver, input.Logger,42 input.ThemeManager, input.ColorResolver, input.Logger,
43 )43 )
44 case config.EngineTypeCEF:44 case config.EngineTypeCEF:
45- cefCfg := cfg.Engine.CEF45+ opts := port.EngineOptions{
46+ DataDir: input.DataDir,
47+ CacheDir: input.CacheDir,
48+ CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy),
49+ }
50+ cefCfg := cef.RuntimeConfig{
51+ CEFDir: cfg.Engine.CEF.CEFDir,
52+ LogFile: cfg.Engine.CEF.LogFile,
53+ LogSeverity: cfg.Engine.CEF.LogSeverity,
54+ WindowlessFrameRate: cfg.Engine.CEF.WindowlessFrameRate,
55+ EnableAudioHandler: cfg.Engine.CEF.EnableAudioHandler,
56+ EnableContextMenuHandler: cfg.Engine.CEF.EnableContextMenuHandler,
57+ TraceHandlers: cfg.Engine.CEF.TraceHandlers,
58+ }
59+ transcodingCfg := cef.TranscodingRuntimeConfig{
60+ Enabled: cfg.Transcoding.Enabled,
61+ HWAccel: cfg.Transcoding.HWAccel,
62+ MaxConcurrent: cfg.Transcoding.MaxConcurrent,
63+ Quality: cfg.Transcoding.Quality,
64+ }
46 audioFactory := audiofactory.NewAudioOutputFactory()65 audioFactory := audiofactory.NewAudioOutputFactory()
47- return cef.NewEngine(input.Ctx, cefCfg, cfg.Transcoding, audioFactory)66+ return cef.NewEngine(input.Ctx, opts, cefCfg, transcodingCfg, audioFactory, cef.EngineDependencies{})
48 default:67 default:
49 return nil, fmt.Errorf("unknown engine type: %q", engineType)68 return nil, fmt.Errorf("unknown engine type: %q", engineType)
50 }69 }
M internal/bootstrap/engine_test.go +9 -0
@@ -4,6 +4,7 @@ import (
44 "context"
55 "testing"
66
7+ "github.com/bnema/dumber/internal/infrastructure/cef"
78 "github.com/bnema/dumber/internal/infrastructure/config"
89 "github.com/stretchr/testify/require"
910 )
@@ -24,3 +25,11 @@ func TestBuildEngine_CEF_ReturnsErrorWhenRuntimeUnavailable(t *testing.T) {
2425 require.Error(t, err)
2526 require.Contains(t, err.Error(), "cef.InitWithApp")
2627 }
28+
29+func TestBuildEngine_CEF_ReturnsErrorForUnsupportedCookiePolicy(t *testing.T) {
30+ cfg := config.DefaultConfig()
31+ cfg.Engine.Type = "cef"
32+ cfg.Engine.CookiePolicy = config.CookiePolicyNever
33+ _, err := BuildEngine(EngineInput{Config: cfg, Ctx: context.Background()})
34+ require.ErrorIs(t, err, cef.ErrCookiePolicyUnsupported)
35+}
@@ -4,6 +4,7 @@ import (
4 "context"4 "context"
5 "testing"5 "testing"
6 6
7+ "github.com/bnema/dumber/internal/infrastructure/cef"
7 "github.com/bnema/dumber/internal/infrastructure/config"8 "github.com/bnema/dumber/internal/infrastructure/config"
8 "github.com/stretchr/testify/require"9 "github.com/stretchr/testify/require"
9 )10 )
@@ -24,3 +25,11 @@ func TestBuildEngine_CEF_ReturnsErrorWhenRuntimeUnavailable(t *testing.T) {
24 require.Error(t, err)25 require.Error(t, err)
25 require.Contains(t, err.Error(), "cef.InitWithApp")26 require.Contains(t, err.Error(), "cef.InitWithApp")
26 }27 }
28+
29+func TestBuildEngine_CEF_ReturnsErrorForUnsupportedCookiePolicy(t *testing.T) {
30+ cfg := config.DefaultConfig()
31+ cfg.Engine.Type = "cef"
32+ cfg.Engine.CookiePolicy = config.CookiePolicyNever
33+ _, err := BuildEngine(EngineInput{Config: cfg, Ctx: context.Background()})
34+ require.ErrorIs(t, err, cef.ErrCookiePolicyUnsupported)
35+}
M internal/infrastructure/cef/engine_init.go +67 -19
@@ -26,16 +26,37 @@ const puregoCEFInitTraceEnvVar = "PUREGO_CEF_INIT_TRACE"
2626 // NewEngine initializes the CEF runtime and returns a ready-to-use Engine.
2727 func NewEngine(
2828 ctx context.Context,
29- cfg config.CEFEngineConfig,
30- transcodingCfg config.TranscodingConfig,
29+ opts port.EngineOptions,
30+ cfg RuntimeConfig,
31+ transcodingCfg TranscodingRuntimeConfig,
3132 audioFactory port.AudioOutputFactory,
33+ deps EngineDependencies,
3234 ) (*Engine, error) {
3335 logger := logging.FromContext(ctx)
34- cleanStaleSingletonLocks(logger)
35-
36- windowlessFrameRate := cfg.CEFWindowlessFrameRate()
37-
38- settings := prepareCEFSettings(cfg, logger)
36+ stateRoot := resolvedStateRoot(opts)
37+ cleanStaleSingletonLocks(logger, stateRoot)
38+
39+ legacyCfg := config.CEFEngineConfig{
40+ CEFDir: cfg.CEFDir,
41+ LogFile: cfg.LogFile,
42+ LogSeverity: cfg.LogSeverity,
43+ WindowlessFrameRate: cfg.WindowlessFrameRate,
44+ EnableAudioHandler: cfg.EnableAudioHandler,
45+ EnableContextMenuHandler: cfg.EnableContextMenuHandler,
46+ TraceHandlers: cfg.TraceHandlers,
47+ }
48+ legacyTranscodingCfg := config.TranscodingConfig{
49+ Enabled: transcodingCfg.Enabled,
50+ HWAccel: transcodingCfg.HWAccel,
51+ MaxConcurrent: transcodingCfg.MaxConcurrent,
52+ Quality: transcodingCfg.Quality,
53+ }
54+ windowlessFrameRate := legacyCfg.CEFWindowlessFrameRate()
55+
56+ settings, err := prepareCEFSettings(opts, cfg, logger)
57+ if err != nil {
58+ return nil, err
59+ }
3960
4061 // Inject --no-zygote temporarily for cef_initialize, then restore.
4162 // Safe: runs during single-threaded startup before concurrent goroutines.
@@ -60,24 +81,49 @@ func NewEngine(
6081 }
6182 os.Args = savedArgs
6283
63- return wireEngine(ctx, eng, cfg, transcodingCfg, windowlessFrameRate, audioFactory, logger)
84+ return wireEngine(ctx, eng, legacyCfg, legacyTranscodingCfg, windowlessFrameRate, audioFactory, logger)
85+}
86+
87+func resolvedStateRoot(opts port.EngineOptions) string {
88+ switch {
89+ case opts.DataDir != "":
90+ return opts.DataDir
91+ case opts.CacheDir != "":
92+ return opts.CacheDir
93+ default:
94+ return defaultCEFUserDataDir()
95+ }
6496 }
6597
6698 // prepareCEFSettings builds purecef.Settings from the engine config.
67-func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) purecef.Settings {
99+func prepareCEFSettings(opts port.EngineOptions, cfg RuntimeConfig, logger *zerolog.Logger) (purecef.Settings, error) {
100+ if opts.CookiePolicy != "" && opts.CookiePolicy != port.CookiePolicyAlways {
101+ return purecef.Settings{}, fmt.Errorf("%w: %s", ErrCookiePolicyUnsupported, opts.CookiePolicy)
102+ }
103+
104+ legacyCfg := config.CEFEngineConfig{
105+ CEFDir: cfg.CEFDir,
106+ LogFile: cfg.LogFile,
107+ LogSeverity: cfg.LogSeverity,
108+ WindowlessFrameRate: cfg.WindowlessFrameRate,
109+ EnableAudioHandler: cfg.EnableAudioHandler,
110+ EnableContextMenuHandler: cfg.EnableContextMenuHandler,
111+ TraceHandlers: cfg.TraceHandlers,
112+ }
113+
68114 settings := purecef.DefaultSettings()
69115 settings.MultiThreadedMessageLoop = true
70116 settings.ExternalMessagePump = false
71- settings.RootCachePath = defaultCEFUserDataDir()
72- if cfg.CEFDir != "" {
73- settings.CEFDir = cfg.CEFDir
117+ settings.RootCachePath = resolvedStateRoot(opts)
118+ if legacyCfg.CEFDir != "" {
119+ settings.CEFDir = legacyCfg.CEFDir
74120 }
75- if runtimeLogFile, err := prepareCEFLogFile(cfg); err != nil {
121+ if runtimeLogFile, err := prepareCEFLogFile(legacyCfg); err != nil {
76122 logger.Warn().Err(err).Msg("cef: failed to prepare runtime log file")
77123 } else {
78124 settings.LogFile = runtimeLogFile
79125 }
80- if bootstrapLogFile, err := prepareCEFInitTraceFile(cfg); err != nil {
126+ if bootstrapLogFile, err := prepareCEFInitTraceFile(legacyCfg); err != nil {
81127 logger.Warn().Err(err).Msg("cef: failed to prepare init trace file")
82128 } else if bootstrapLogFile != "" {
83129 settings.InitTraceFile = bootstrapLogFile
@@ -86,8 +132,8 @@ func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) pure
86132 Str("env_var", puregoCEFInitTraceEnvVar).
87133 Msg("cef: init bootstrap diagnostics enabled")
88134 }
89- if cfg.LogSeverity != 0 {
90- settings.LogSeverity = cfg.LogSeverity
135+ if legacyCfg.LogSeverity != 0 {
136+ settings.LogSeverity = legacyCfg.LogSeverity
91137 }
92138 if helperPath := findHelperBinary(); helperPath != "" {
93139 settings.BrowserSubprocessPath = helperPath
@@ -95,7 +141,7 @@ func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) pure
95141 } else {
96142 logger.Warn().Msg("cef: subprocess helper not found, falling back to main binary")
97143 }
98- return settings
144+ return settings, nil
99145 }
100146
101147 func defaultCEFUserDataDir() string {
@@ -302,8 +348,10 @@ func appendIfMissing(args []string, flag string) []string {
302348 // if the owning process is no longer running. CEF leaves these behind on
303349 // unclean shutdown (SIGKILL, SIGSEGV) and the next instance crashes trying
304350 // to connect to the dead process.
305-func cleanStaleSingletonLocks(logger *zerolog.Logger) {
306- dir := defaultCEFUserDataDir()
351+func cleanStaleSingletonLocks(logger *zerolog.Logger, dir string) {
352+ if dir == "" {
353+ return
354+ }
307355
308356 lockPath := filepath.Join(dir, "SingletonLock")
309357 target, err := os.Readlink(lockPath)
@@ -26,16 +26,37 @@ const puregoCEFInitTraceEnvVar = "PUREGO_CEF_INIT_TRACE"
26 // NewEngine initializes the CEF runtime and returns a ready-to-use Engine.26 // NewEngine initializes the CEF runtime and returns a ready-to-use Engine.
27 func NewEngine(27 func NewEngine(
28 ctx context.Context,28 ctx context.Context,
29- cfg config.CEFEngineConfig,29+ opts port.EngineOptions,
30- transcodingCfg config.TranscodingConfig,30+ cfg RuntimeConfig,
31+ transcodingCfg TranscodingRuntimeConfig,
31 audioFactory port.AudioOutputFactory,32 audioFactory port.AudioOutputFactory,
33+ deps EngineDependencies,
32 ) (*Engine, error) {34 ) (*Engine, error) {
33 logger := logging.FromContext(ctx)35 logger := logging.FromContext(ctx)
34- cleanStaleSingletonLocks(logger)36+ stateRoot := resolvedStateRoot(opts)
35-37+ cleanStaleSingletonLocks(logger, stateRoot)
36- windowlessFrameRate := cfg.CEFWindowlessFrameRate()38+
37-39+ legacyCfg := config.CEFEngineConfig{
38- settings := prepareCEFSettings(cfg, logger)40+ CEFDir: cfg.CEFDir,
41+ LogFile: cfg.LogFile,
42+ LogSeverity: cfg.LogSeverity,
43+ WindowlessFrameRate: cfg.WindowlessFrameRate,
44+ EnableAudioHandler: cfg.EnableAudioHandler,
45+ EnableContextMenuHandler: cfg.EnableContextMenuHandler,
46+ TraceHandlers: cfg.TraceHandlers,
47+ }
48+ legacyTranscodingCfg := config.TranscodingConfig{
49+ Enabled: transcodingCfg.Enabled,
50+ HWAccel: transcodingCfg.HWAccel,
51+ MaxConcurrent: transcodingCfg.MaxConcurrent,
52+ Quality: transcodingCfg.Quality,
53+ }
54+ windowlessFrameRate := legacyCfg.CEFWindowlessFrameRate()
55+
56+ settings, err := prepareCEFSettings(opts, cfg, logger)
57+ if err != nil {
58+ return nil, err
59+ }
39 60
40 // Inject --no-zygote temporarily for cef_initialize, then restore.61 // Inject --no-zygote temporarily for cef_initialize, then restore.
41 // Safe: runs during single-threaded startup before concurrent goroutines.62 // Safe: runs during single-threaded startup before concurrent goroutines.
@@ -60,24 +81,49 @@ func NewEngine(
60 }81 }
61 os.Args = savedArgs82 os.Args = savedArgs
62 83
63- return wireEngine(ctx, eng, cfg, transcodingCfg, windowlessFrameRate, audioFactory, logger)84+ return wireEngine(ctx, eng, legacyCfg, legacyTranscodingCfg, windowlessFrameRate, audioFactory, logger)
85+}
86+
87+func resolvedStateRoot(opts port.EngineOptions) string {
88+ switch {
89+ case opts.DataDir != "":
90+ return opts.DataDir
91+ case opts.CacheDir != "":
92+ return opts.CacheDir
93+ default:
94+ return defaultCEFUserDataDir()
95+ }
64 }96 }
65 97
66 // prepareCEFSettings builds purecef.Settings from the engine config.98 // prepareCEFSettings builds purecef.Settings from the engine config.
67-func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) purecef.Settings {99+func prepareCEFSettings(opts port.EngineOptions, cfg RuntimeConfig, logger *zerolog.Logger) (purecef.Settings, error) {
100+ if opts.CookiePolicy != "" && opts.CookiePolicy != port.CookiePolicyAlways {
101+ return purecef.Settings{}, fmt.Errorf("%w: %s", ErrCookiePolicyUnsupported, opts.CookiePolicy)
102+ }
103+
104+ legacyCfg := config.CEFEngineConfig{
105+ CEFDir: cfg.CEFDir,
106+ LogFile: cfg.LogFile,
107+ LogSeverity: cfg.LogSeverity,
108+ WindowlessFrameRate: cfg.WindowlessFrameRate,
109+ EnableAudioHandler: cfg.EnableAudioHandler,
110+ EnableContextMenuHandler: cfg.EnableContextMenuHandler,
111+ TraceHandlers: cfg.TraceHandlers,
112+ }
113+
68 settings := purecef.DefaultSettings()114 settings := purecef.DefaultSettings()
69 settings.MultiThreadedMessageLoop = true115 settings.MultiThreadedMessageLoop = true
70 settings.ExternalMessagePump = false116 settings.ExternalMessagePump = false
71- settings.RootCachePath = defaultCEFUserDataDir()117+ settings.RootCachePath = resolvedStateRoot(opts)
72- if cfg.CEFDir != "" {118+ if legacyCfg.CEFDir != "" {
73- settings.CEFDir = cfg.CEFDir119+ settings.CEFDir = legacyCfg.CEFDir
74 }120 }
75- if runtimeLogFile, err := prepareCEFLogFile(cfg); err != nil {121+ if runtimeLogFile, err := prepareCEFLogFile(legacyCfg); err != nil {
76 logger.Warn().Err(err).Msg("cef: failed to prepare runtime log file")122 logger.Warn().Err(err).Msg("cef: failed to prepare runtime log file")
77 } else {123 } else {
78 settings.LogFile = runtimeLogFile124 settings.LogFile = runtimeLogFile
79 }125 }
80- if bootstrapLogFile, err := prepareCEFInitTraceFile(cfg); err != nil {126+ if bootstrapLogFile, err := prepareCEFInitTraceFile(legacyCfg); err != nil {
81 logger.Warn().Err(err).Msg("cef: failed to prepare init trace file")127 logger.Warn().Err(err).Msg("cef: failed to prepare init trace file")
82 } else if bootstrapLogFile != "" {128 } else if bootstrapLogFile != "" {
83 settings.InitTraceFile = bootstrapLogFile129 settings.InitTraceFile = bootstrapLogFile
@@ -86,8 +132,8 @@ func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) pure
86 Str("env_var", puregoCEFInitTraceEnvVar).132 Str("env_var", puregoCEFInitTraceEnvVar).
87 Msg("cef: init bootstrap diagnostics enabled")133 Msg("cef: init bootstrap diagnostics enabled")
88 }134 }
89- if cfg.LogSeverity != 0 {135+ if legacyCfg.LogSeverity != 0 {
90- settings.LogSeverity = cfg.LogSeverity136+ settings.LogSeverity = legacyCfg.LogSeverity
91 }137 }
92 if helperPath := findHelperBinary(); helperPath != "" {138 if helperPath := findHelperBinary(); helperPath != "" {
93 settings.BrowserSubprocessPath = helperPath139 settings.BrowserSubprocessPath = helperPath
@@ -95,7 +141,7 @@ func prepareCEFSettings(cfg config.CEFEngineConfig, logger *zerolog.Logger) pure
95 } else {141 } else {
96 logger.Warn().Msg("cef: subprocess helper not found, falling back to main binary")142 logger.Warn().Msg("cef: subprocess helper not found, falling back to main binary")
97 }143 }
98- return settings144+ return settings, nil
99 }145 }
100 146
101 func defaultCEFUserDataDir() string {147 func defaultCEFUserDataDir() string {
@@ -302,8 +348,10 @@ func appendIfMissing(args []string, flag string) []string {
302 // if the owning process is no longer running. CEF leaves these behind on348 // if the owning process is no longer running. CEF leaves these behind on
303 // unclean shutdown (SIGKILL, SIGSEGV) and the next instance crashes trying349 // unclean shutdown (SIGKILL, SIGSEGV) and the next instance crashes trying
304 // to connect to the dead process.350 // to connect to the dead process.
305-func cleanStaleSingletonLocks(logger *zerolog.Logger) {351+func cleanStaleSingletonLocks(logger *zerolog.Logger, dir string) {
306- dir := defaultCEFUserDataDir()352+ if dir == "" {
353+ return
354+ }
307 355
308 lockPath := filepath.Join(dir, "SingletonLock")356 lockPath := filepath.Join(dir, "SingletonLock")
309 target, err := os.Readlink(lockPath)357 target, err := os.Readlink(lockPath)
M internal/infrastructure/cef/engine_init_test.go +28 -2
@@ -5,12 +5,36 @@ import (
55 "path/filepath"
66 "testing"
77
8+ "github.com/bnema/dumber/internal/application/port"
89 "github.com/bnema/dumber/internal/infrastructure/audio/factory"
910 "github.com/bnema/dumber/internal/infrastructure/config"
1011 "github.com/rs/zerolog"
1112 "github.com/stretchr/testify/require"
1213 )
1314
15+func TestResolvedStateRoot_PrefersDataDir(t *testing.T) {
16+ root := resolvedStateRoot(port.EngineOptions{DataDir: "/tmp/data", CacheDir: "/tmp/cache"})
17+ require.Equal(t, "/tmp/data", root)
18+}
19+
20+func TestResolvedStateRoot_FallsBackToCacheDir(t *testing.T) {
21+ root := resolvedStateRoot(port.EngineOptions{CacheDir: "/tmp/cache"})
22+ require.Equal(t, "/tmp/cache", root)
23+}
24+
25+func TestPrepareCEFSettings_UsesResolvedStateRoot(t *testing.T) {
26+ logger := zerolog.Nop()
27+ settings, err := prepareCEFSettings(port.EngineOptions{CacheDir: "/tmp/cache"}, RuntimeConfig{}, &logger)
28+ require.NoError(t, err)
29+ require.Equal(t, "/tmp/cache", settings.RootCachePath)
30+}
31+
32+func TestPrepareCEFSettings_RejectsNonDefaultCookiePolicy(t *testing.T) {
33+ logger := zerolog.Nop()
34+ _, err := prepareCEFSettings(port.EngineOptions{CookiePolicy: port.CookiePolicyNever}, RuntimeConfig{}, &logger)
35+ require.ErrorIs(t, err, ErrCookiePolicyUnsupported)
36+}
37+
1438 func TestPrepareCEFInitTraceFile_DisabledByDefault(t *testing.T) {
1539 t.Setenv(puregoCEFInitTraceEnvVar, "")
1640
@@ -37,7 +61,8 @@ func TestPrepareCEFSettings_RootCachePath_UsesLegacyCEFUserDataDir(t *testing.T)
3761 t.Setenv("ENV", "")
3862
3963 logger := zerolog.Nop()
40- settings := prepareCEFSettings(config.CEFEngineConfig{}, &logger)
64+ settings, err := prepareCEFSettings(port.EngineOptions{}, RuntimeConfig{}, &logger)
65+ require.NoError(t, err)
4166
4267 require.Equal(t, filepath.Join(homeDir, ".config", "cef_user_data"), settings.RootCachePath)
4368 }
@@ -58,7 +83,8 @@ func TestPrepareCEFSettings_RootCachePath_DevMode(t *testing.T) {
5883 t.Setenv("ENV", "dev")
5984
6085 logger := zerolog.Nop()
61- settings := prepareCEFSettings(config.CEFEngineConfig{}, &logger)
86+ settings, err := prepareCEFSettings(port.EngineOptions{}, RuntimeConfig{}, &logger)
87+ require.NoError(t, err)
6288
6389 // In dev mode, should resolve under .dev/dumber/cef_user_data
6490 require.Equal(t, filepath.Join(tempDir, ".dev", "dumber", "cef_user_data"), settings.RootCachePath)
@@ -5,12 +5,36 @@ import (
5 "path/filepath"5 "path/filepath"
6 "testing"6 "testing"
7 7
8+ "github.com/bnema/dumber/internal/application/port"
8 "github.com/bnema/dumber/internal/infrastructure/audio/factory"9 "github.com/bnema/dumber/internal/infrastructure/audio/factory"
9 "github.com/bnema/dumber/internal/infrastructure/config"10 "github.com/bnema/dumber/internal/infrastructure/config"
10 "github.com/rs/zerolog"11 "github.com/rs/zerolog"
11 "github.com/stretchr/testify/require"12 "github.com/stretchr/testify/require"
12 )13 )
13 14
15+func TestResolvedStateRoot_PrefersDataDir(t *testing.T) {
16+ root := resolvedStateRoot(port.EngineOptions{DataDir: "/tmp/data", CacheDir: "/tmp/cache"})
17+ require.Equal(t, "/tmp/data", root)
18+}
19+
20+func TestResolvedStateRoot_FallsBackToCacheDir(t *testing.T) {
21+ root := resolvedStateRoot(port.EngineOptions{CacheDir: "/tmp/cache"})
22+ require.Equal(t, "/tmp/cache", root)
23+}
24+
25+func TestPrepareCEFSettings_UsesResolvedStateRoot(t *testing.T) {
26+ logger := zerolog.Nop()
27+ settings, err := prepareCEFSettings(port.EngineOptions{CacheDir: "/tmp/cache"}, RuntimeConfig{}, &logger)
28+ require.NoError(t, err)
29+ require.Equal(t, "/tmp/cache", settings.RootCachePath)
30+}
31+
32+func TestPrepareCEFSettings_RejectsNonDefaultCookiePolicy(t *testing.T) {
33+ logger := zerolog.Nop()
34+ _, err := prepareCEFSettings(port.EngineOptions{CookiePolicy: port.CookiePolicyNever}, RuntimeConfig{}, &logger)
35+ require.ErrorIs(t, err, ErrCookiePolicyUnsupported)
36+}
37+
14 func TestPrepareCEFInitTraceFile_DisabledByDefault(t *testing.T) {38 func TestPrepareCEFInitTraceFile_DisabledByDefault(t *testing.T) {
15 t.Setenv(puregoCEFInitTraceEnvVar, "")39 t.Setenv(puregoCEFInitTraceEnvVar, "")
16 40
@@ -37,7 +61,8 @@ func TestPrepareCEFSettings_RootCachePath_UsesLegacyCEFUserDataDir(t *testing.T)
37 t.Setenv("ENV", "")61 t.Setenv("ENV", "")
38 62
39 logger := zerolog.Nop()63 logger := zerolog.Nop()
40- settings := prepareCEFSettings(config.CEFEngineConfig{}, &logger)64+ settings, err := prepareCEFSettings(port.EngineOptions{}, RuntimeConfig{}, &logger)
65+ require.NoError(t, err)
41 66
42 require.Equal(t, filepath.Join(homeDir, ".config", "cef_user_data"), settings.RootCachePath)67 require.Equal(t, filepath.Join(homeDir, ".config", "cef_user_data"), settings.RootCachePath)
43 }68 }
@@ -58,7 +83,8 @@ func TestPrepareCEFSettings_RootCachePath_DevMode(t *testing.T) {
58 t.Setenv("ENV", "dev")83 t.Setenv("ENV", "dev")
59 84
60 logger := zerolog.Nop()85 logger := zerolog.Nop()
61- settings := prepareCEFSettings(config.CEFEngineConfig{}, &logger)86+ settings, err := prepareCEFSettings(port.EngineOptions{}, RuntimeConfig{}, &logger)
87+ require.NoError(t, err)
62 88
63 // In dev mode, should resolve under .dev/dumber/cef_user_data89 // In dev mode, should resolve under .dev/dumber/cef_user_data
64 require.Equal(t, filepath.Join(tempDir, ".dev", "dumber", "cef_user_data"), settings.RootCachePath)90 require.Equal(t, filepath.Join(tempDir, ".dev", "dumber", "cef_user_data"), settings.RootCachePath)

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