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