1package destination
2
3import (
4 "os"
5 "path/filepath"
6 "regexp"
7 "strings"
8 "testing"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12)
13
14// env gives each test an isolated home and config with no XDG overrides.
15func env(t *testing.T) (home string) {
16 t.Helper()
17 home = t.TempDir()
18 t.Setenv("HOME", home)
19 t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, "cfg"))
20 t.Setenv("XDG_PICTURES_DIR", "")
21 t.Setenv("XDG_VIDEOS_DIR", "")
22 return home
23}
24
25func writeDirs(t *testing.T, content string) {
26 t.Helper()
27 cfg := os.Getenv("XDG_CONFIG_HOME")
28 require.NoError(t, os.MkdirAll(cfg, 0o700))
29 require.NoError(t, os.WriteFile(filepath.Join(cfg, "user-dirs.dirs"), []byte(content), 0o600))
30}
31
32var nameRE = regexp.MustCompile(`^nefercap-\d{8}-\d{6}\.\d{9}\.(png|mp4)$`)
33
34func TestFallbackDefaults(t *testing.T) {
35 home := env(t)
36
37 shot, err := Screenshot()
38 require.NoError(t, err)
39 assert.Equal(t, filepath.Join(home, "Pictures", "Screenshots"), filepath.Dir(shot))
40 assert.Regexp(t, nameRE, filepath.Base(shot))
41 assert.Equal(t, ".png", filepath.Ext(shot))
42
43 vid, err := Video()
44 require.NoError(t, err)
45 assert.Equal(t, filepath.Join(home, "Videos"), filepath.Dir(vid))
46 assert.Equal(t, ".mp4", filepath.Ext(vid))
47 assert.Regexp(t, nameRE, filepath.Base(vid))
48
49 for _, d := range []string{filepath.Dir(shot), filepath.Dir(vid)} {
50 fi, err := os.Stat(d)
51 require.NoError(t, err)
52 assert.True(t, fi.IsDir())
53 }
54 _, err = os.Lstat(shot)
55 assert.True(t, os.IsNotExist(err), "the file itself is not created")
56}
57
58func TestDirectoryMode(t *testing.T) {
59 home := env(t)
60 old := umaskZero()
61 defer old()
62 shot, err := Screenshot()
63 require.NoError(t, err)
64 fi, err := os.Stat(filepath.Dir(shot))
65 require.NoError(t, err)
66 assert.Equal(t, os.FileMode(0o700), fi.Mode().Perm())
67 fi, err = os.Stat(filepath.Join(home, "Pictures"))
68 require.NoError(t, err)
69 assert.Equal(t, os.FileMode(0o700), fi.Mode().Perm())
70}
71
72func TestEnvironmentDirectories(t *testing.T) {
73 home := env(t)
74 pics := t.TempDir()
75 vids := filepath.Join(home, "Movies")
76 t.Setenv("XDG_PICTURES_DIR", pics)
77 t.Setenv("XDG_VIDEOS_DIR", "$HOME/Movies")
78
79 shot, err := Screenshot()
80 require.NoError(t, err)
81 assert.Equal(t, filepath.Join(pics, "Screenshots"), filepath.Dir(shot))
82
83 vid, err := Video()
84 require.NoError(t, err)
85 assert.Equal(t, vids, filepath.Dir(vid))
86
87 t.Setenv("XDG_VIDEOS_DIR", "${HOME}/Clips/../Movies2")
88 vid, err = Video()
89 require.NoError(t, err)
90 assert.Equal(t, filepath.Join(home, "Movies2"), filepath.Dir(vid))
91}
92
93func TestConfigFile(t *testing.T) {
94 home := env(t)
95 writeDirs(t, `# comment
96XDG_DESKTOP_DIR="$HOME/Desktop"
97XDG_PICTURES_DIR="$HOME/My \"Pics\""
98XDG_VIDEOS_DIR="$HOME/Films"
99`)
100 shot, err := Screenshot()
101 require.NoError(t, err)
102 assert.Equal(t, filepath.Join(home, `My "Pics"`, "Screenshots"), filepath.Dir(shot))
103 vid, err := Video()
104 require.NoError(t, err)
105 assert.Equal(t, filepath.Join(home, "Films"), filepath.Dir(vid))
106
107 // The environment variable wins over the file.
108 other := t.TempDir()
109 t.Setenv("XDG_VIDEOS_DIR", other)
110 vid, err = Video()
111 require.NoError(t, err)
112 assert.Equal(t, other, filepath.Dir(vid))
113}
114
115func TestConfigFileDefaultLocation(t *testing.T) {
116 home := env(t)
117 t.Setenv("XDG_CONFIG_HOME", "")
118 require.NoError(t, os.MkdirAll(filepath.Join(home, ".config"), 0o700))
119 require.NoError(t, os.WriteFile(filepath.Join(home, ".config", "user-dirs.dirs"),
120 []byte("XDG_VIDEOS_DIR=\"$HOME/Reels\"\n"), 0o600))
121 vid, err := Video()
122 require.NoError(t, err)
123 assert.Equal(t, filepath.Join(home, "Reels"), filepath.Dir(vid))
124}
125
126func TestUnsafeConfiguredValuesFallBack(t *testing.T) {
127 home := env(t)
128 for _, v := range []string{
129 "relative/dir",
130 "$OTHER/dir",
131 "$(id)/x",
132 "`id`",
133 "$HOME", // xdg-user-dirs uses HOME to mean "disabled"
134 "$HOME/",
135 "/",
136 "$HOMEDIR/x",
137 } {
138 t.Setenv("XDG_VIDEOS_DIR", v)
139 vid, err := Video()
140 require.NoError(t, err, v)
141 assert.Equal(t, filepath.Join(home, "Videos"), filepath.Dir(vid), v)
142 }
143 // Same for the file, including an unterminated quote.
144 t.Setenv("XDG_VIDEOS_DIR", "")
145 writeDirs(t, "XDG_VIDEOS_DIR=\"$HOME/Broken\n")
146 vid, err := Video()
147 require.NoError(t, err)
148 assert.Equal(t, filepath.Join(home, "Videos"), filepath.Dir(vid))
149 // Nothing outside the home was created.
150 _, err = os.Lstat(filepath.Join(home, "Broken"))
151 assert.True(t, os.IsNotExist(err))
152}
153
154func TestOversizedConfigIgnored(t *testing.T) {
155 home := env(t)
156 big := make([]byte, maxConfig)
157 for i := range big {
158 big[i] = '#'
159 }
160 writeDirs(t, string(big)+"\nXDG_VIDEOS_DIR=\"$HOME/Late\"\n")
161 vid, err := Video()
162 require.NoError(t, err)
163 assert.Equal(t, filepath.Join(home, "Videos"), filepath.Dir(vid))
164}
165
166func TestConfiguredNonDirectoryFails(t *testing.T) {
167 env(t)
168 file := filepath.Join(t.TempDir(), "f")
169 require.NoError(t, os.WriteFile(file, nil, 0o600))
170 t.Setenv("XDG_VIDEOS_DIR", file)
171 _, err := Video()
172 assert.Error(t, err)
173 t.Setenv("XDG_VIDEOS_DIR", filepath.Join(file, "sub"))
174 _, err = Video()
175 assert.Error(t, err)
176}
177
178func TestNoHome(t *testing.T) {
179 t.Setenv("HOME", "")
180 t.Setenv("XDG_PICTURES_DIR", "")
181 _, err := Screenshot()
182 assert.ErrorIs(t, err, ErrNoHome)
183 t.Setenv("HOME", "relative")
184 _, err = Video()
185 assert.ErrorIs(t, err, ErrNoHome)
186}
187
188func TestNamesDoNotCollide(t *testing.T) {
189 env(t)
190 seen := map[string]bool{}
191 for i := 0; i < 200; i++ {
192 p, err := Screenshot()
193 require.NoError(t, err)
194 seen[p] = true
195 }
196 assert.Len(t, seen, 200)
197}
198
199func TestInExplicitDirectory(t *testing.T) {
200 root := filepath.Join(t.TempDir(), "a", "b")
201 shot, err := ScreenshotIn(root)
202 require.NoError(t, err)
203 assert.Equal(t, root, filepath.Dir(shot))
204 assert.True(t, strings.HasSuffix(shot, ".png"))
205 fi, err := os.Stat(root)
206 require.NoError(t, err)
207 assert.Equal(t, os.FileMode(0o700), fi.Mode().Perm())
208 vid, err := VideoIn(root)
209 require.NoError(t, err)
210 assert.Equal(t, root, filepath.Dir(vid))
211 assert.True(t, strings.HasSuffix(vid, ".mp4"))
212}