1package cef
2
3import (
4 "bytes"
5 "os"
6 "path/filepath"
7 "testing"
8
9 "github.com/bnema/dumber/internal/application/port"
10 "github.com/bnema/dumber/internal/infrastructure/audio/factory"
11 "github.com/bnema/dumber/internal/infrastructure/runtimeprofile"
12 "github.com/rs/zerolog"
13 "github.com/stretchr/testify/require"
14)
15
16func TestResolvedStateRoot_PrefersCacheDir(t *testing.T) {
17 profile := testCEFDevProfile(t)
18 root := resolvedStateRoot(profile.CEFUserDataDir(), port.EngineOptions{DataDir: "/tmp/data", CacheDir: "/tmp/cache"})
19 require.Equal(t, "/tmp/cache", root)
20}
21
22func TestResolvedStateRoot_UsesEnvOverride(t *testing.T) {
23 profile := testCEFDevProfile(t)
24 t.Setenv(CEFRootCachePathEnvVar, "/tmp/override")
25
26 root := resolvedStateRoot(profile.CEFUserDataDir(), port.EngineOptions{})
27 require.Equal(t, "/tmp/override", root)
28
29 root = resolvedStateRoot(profile.CEFUserDataDir(), port.EngineOptions{DataDir: "/tmp/data", CacheDir: "/tmp/cache"})
30 require.Equal(t, "/tmp/override", root)
31}
32
33func TestResolvedStateRoot_UsesProfileDefault(t *testing.T) {
34 profile := testCEFProdProfile(t)
35 root := resolvedStateRoot(profile.CEFUserDataDir(), port.EngineOptions{})
36 require.Equal(t, profile.CEFUserDataDir(), root)
37}
38
39func TestConfigureCEFSubprocessRuntime_PropagatesConfiguredRuntimeBeforeInit(t *testing.T) {
40 t.Setenv("CEF_DIR", "")
41
42 runtime := filepath.Join(t.TempDir(), "cef-147")
43 require.Equal(t, runtime, configureCEFSubprocessRuntime(runtime))
44 require.Equal(t, runtime, os.Getenv("CEF_DIR"))
45}
46
47func TestConfigureCEFSubprocessRuntime_PreservesExplicitEnvironmentOverride(t *testing.T) {
48 explicitRuntime := filepath.Join(t.TempDir(), "cef-env")
49 t.Setenv("CEF_DIR", explicitRuntime)
50
51 require.Equal(t, explicitRuntime, configureCEFSubprocessRuntime(filepath.Join(t.TempDir(), "cef-config")))
52 require.Equal(t, explicitRuntime, os.Getenv("CEF_DIR"))
53}
54
55func TestPrepareCEFSettings_UsesResolvedProfilePaths(t *testing.T) {
56 logger := zerolog.Nop()
57 profile := testCEFDevProfile(t)
58 settings, err := prepareCEFSettings(port.EngineOptions{}, RuntimePaths{StateRoot: profile.CEFUserDataDir(), LogFile: profile.CEFLogFile()}, RuntimeConfig{}, &logger)
59 require.NoError(t, err)
60 require.Equal(t, profile.CEFUserDataDir(), settings.RootCachePath)
61 require.Equal(t, profile.CEFLogFile(), settings.LogFile)
62 require.Empty(t, settings.BrowserSubprocessPath)
63}
64
65func TestParseLoadedLibCEFPath(t *testing.T) {
66 maps := "7f5fd4000000-7f5fd4200000 r--p 00000000 00:00 0 /usr/lib/libvulkan.so.1\n" +
67 "7f5fe0000000-7f5fe8200000 r-xp 00000000 00:00 0 /opt/cef-vaapi-runtime/usr/lib/cef/libcef.so\n" +
68 "7f5fe8200000-7f5fe8400000 r--p 08200000 00:00 0 /opt/cef-vaapi-runtime/usr/lib/cef/libcef.so\n"
69
70 require.Equal(t, "/opt/cef-vaapi-runtime/usr/lib/cef/libcef.so", parseLoadedLibCEFPath(maps))
71}
72
73func TestParseLoadedLibCEFPath_ReturnsEmptyWhenAbsent(t *testing.T) {
74 require.Empty(t, parseLoadedLibCEFPath("7f5fd4000000-7f5fd4200000 r--p 00000000 00:00 0 /usr/lib/libvulkan.so.1\n"))
75}
76
77func TestPrepareCEFSettings_RejectsNonDefaultCookiePolicy(t *testing.T) {
78 logger := zerolog.Nop()
79 profile := testCEFDevProfile(t)
80 _, err := prepareCEFSettings(port.EngineOptions{CookiePolicy: port.CookiePolicyNever}, RuntimePaths{StateRoot: profile.CEFUserDataDir(), LogFile: profile.CEFLogFile()}, RuntimeConfig{}, &logger)
81 require.ErrorIs(t, err, ErrCookiePolicyUnsupported)
82}
83
84func TestPrepareCEFSettings_AllowsNoThirdPartyCookiePolicy(t *testing.T) {
85 logger := zerolog.Nop()
86 profile := testCEFDevProfile(t)
87 _, err := prepareCEFSettings(port.EngineOptions{CookiePolicy: port.CookiePolicyNoThirdParty}, RuntimePaths{StateRoot: profile.CEFUserDataDir(), LogFile: profile.CEFLogFile()}, RuntimeConfig{}, &logger)
88 require.NoError(t, err)
89}
90
91func TestResolveCEFInitTraceFile_EmptyWithoutLogPath(t *testing.T) {
92 require.Empty(t, resolveCEFInitTraceFile("", ""))
93}
94
95func TestPrepareCEFLogFile_CreatesPrivateDirectoryAndFile(t *testing.T) {
96 logFile := filepath.Join(t.TempDir(), "private", "cef_runtime.log")
97
98 path, err := prepareCEFLogFile("", logFile)
99 require.NoError(t, err)
100 require.Equal(t, logFile, path)
101
102 dirInfo, err := os.Stat(filepath.Dir(logFile))
103 require.NoError(t, err)
104 require.Equal(t, os.FileMode(dirPerm), dirInfo.Mode().Perm())
105
106 fileInfo, err := os.Stat(logFile)
107 require.NoError(t, err)
108 require.Equal(t, os.FileMode(filePerm), fileInfo.Mode().Perm())
109}
110
111func TestPrepareCEFLogFile_TightensExistingFilePermissions(t *testing.T) {
112 logFile := filepath.Join(t.TempDir(), "private", "cef_runtime.log")
113 require.NoError(t, os.MkdirAll(filepath.Dir(logFile), dirPerm))
114 require.NoError(t, os.WriteFile(logFile, []byte("existing"), 0o644))
115
116 path, err := prepareCEFLogFile("", logFile)
117 require.NoError(t, err)
118 require.Equal(t, logFile, path)
119
120 fileInfo, err := os.Stat(logFile)
121 require.NoError(t, err)
122 require.Equal(t, os.FileMode(filePerm), fileInfo.Mode().Perm())
123}
124
125func TestPrepareCEFLogFile_RejectsSymlink(t *testing.T) {
126 target := filepath.Join(t.TempDir(), "target.log")
127 require.NoError(t, os.WriteFile(target, []byte("existing"), 0o644))
128
129 logFile := filepath.Join(t.TempDir(), "private", "cef_runtime.log")
130 require.NoError(t, os.MkdirAll(filepath.Dir(logFile), dirPerm))
131 require.NoError(t, os.Symlink(target, logFile))
132
133 path, err := prepareCEFLogFile("", logFile)
134 require.ErrorContains(t, err, "must not be a symlink")
135 require.Empty(t, path)
136
137 targetInfo, statErr := os.Stat(target)
138 require.NoError(t, statErr)
139 require.Equal(t, os.FileMode(0o644), targetInfo.Mode().Perm())
140}
141
142func TestPrepareCEFLogFile_RejectsDirectoryPath(t *testing.T) {
143 logFile := filepath.Join(t.TempDir(), "private", "cef_runtime.log")
144 require.NoError(t, os.MkdirAll(logFile, dirPerm))
145
146 path, err := prepareCEFLogFile("", logFile)
147 require.ErrorContains(t, err, "not a regular file")
148 require.Empty(t, path)
149}
150
151func TestResolveCEFInitTraceFile_UsesExplicitLogFile(t *testing.T) {
152 logFile := filepath.Join(t.TempDir(), "custom", "cef_runtime.log")
153
154 path := resolveCEFInitTraceFile("", logFile)
155 require.Equal(t, filepath.Join(filepath.Dir(logFile), "cef_runtime.bootstrap.log"), path)
156}
157
158func TestResolveCEFInitTraceFile_UsesResolvedProfileLogDirByDefault(t *testing.T) {
159 profile := testCEFDevProfile(t)
160
161 path := resolveCEFInitTraceFile(profile.CEFLogFile(), "")
162 require.Equal(t, filepath.Join(profile.EnginePaths.LogDir, "cef_runtime.bootstrap.log"), path)
163}
164
165func TestPrepareCEFSettings_IgnoresInitTraceRequestWithoutTouchingFilesystem(t *testing.T) {
166 t.Setenv(puregoCEFInitTraceEnvVar, "1")
167 profile := testCEFDevProfile(t)
168 bootstrapLogFile := resolveCEFInitTraceFile(profile.CEFLogFile(), "")
169
170 var logs bytes.Buffer
171 logger := zerolog.New(&logs)
172
173 settings, err := prepareCEFSettings(
174 port.EngineOptions{},
175 RuntimePaths{StateRoot: profile.CEFUserDataDir(), LogFile: profile.CEFLogFile()},
176 RuntimeConfig{},
177 &logger,
178 )
179 require.NoError(t, err)
180 require.Equal(t, profile.CEFLogFile(), settings.LogFile)
181
182 _, statErr := os.Stat(bootstrapLogFile)
183 require.ErrorIs(t, statErr, os.ErrNotExist)
184 require.Contains(t, logs.String(), puregoCEFInitTraceEnvVar)
185 require.Contains(t, logs.String(), bootstrapLogFile)
186}
187
188// TestCreateAudioOutputFactory_CreatesUsableFactory verifies that the audio
189// factory can be created and produces working streams. This tests the runtime
190// wiring decision to create the factory during CEF engine setup.
191func TestCreateAudioOutputFactory_ReturnsFactory(t *testing.T) {
192 audioFactory := factory.NewAudioOutputFactory()
193
194 require.NotNil(t, audioFactory, "Audio factory should not be nil")
195}
196
197func testCEFDevProfile(t *testing.T) runtimeprofile.Profile {
198 t.Helper()
199 cwd := t.TempDir()
200 runtimeDir := shortRuntimeDir(t)
201 t.Setenv("XDG_RUNTIME_DIR", runtimeDir)
202 profile, err := runtimeprofile.Resolve(runtimeprofile.ResolveInput{
203 Env: func(key string) string {
204 switch key {
205 case "ENV":
206 return "dev"
207 case "XDG_RUNTIME_DIR":
208 return runtimeDir
209 default:
210 return ""
211 }
212 },
213 Engine: "cef",
214 CWD: func() (string, error) { return cwd, nil },
215 })
216 require.NoError(t, err)
217 return profile
218}
219
220func shortRuntimeDir(t *testing.T) string {
221 t.Helper()
222
223 dir, err := os.MkdirTemp("", "dbr-")
224 require.NoError(t, err)
225 t.Cleanup(func() {
226 _ = os.RemoveAll(dir)
227 })
228 return dir
229}
230
231func testCEFProdProfile(t *testing.T) runtimeprofile.Profile {
232 t.Helper()
233 root := t.TempDir()
234 profile, err := runtimeprofile.Resolve(runtimeprofile.ResolveInput{
235 Env: func(string) string { return "" },
236 Engine: "cef",
237 CWD: func() (string, error) { return root, nil },
238 Base: runtimeprofile.BasePaths{
239 ConfigHome: filepath.Join(root, "config"),
240 DataHome: filepath.Join(root, "data"),
241 StateHome: filepath.Join(root, "state"),
242 CacheHome: filepath.Join(root, "cache"),
243 },
244 })
245 require.NoError(t, err)
246 return profile
247}