1package config
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "syscall"
8 "testing"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12)
13
14func configRoot(t *testing.T) string {
15 t.Helper()
16 root := t.TempDir()
17 t.Setenv("XDG_CONFIG_HOME", root)
18 return filepath.Join(root, "nefercap", "config")
19}
20
21func saveConfig(t *testing.T, path, text string) {
22 t.Helper()
23 require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700))
24 require.NoError(t, os.WriteFile(path, []byte(text), 0o600))
25}
26
27func TestLoadDefault(t *testing.T) {
28 configRoot(t)
29 got, err := Load()
30 require.NoError(t, err)
31 assert.True(t, got.Grid)
32}
33
34func TestLoadGrid(t *testing.T) {
35 for _, tc := range []struct {
36 name, text string
37 grid bool
38 }{
39 {"empty", "", true},
40 {"comments", "# selector settings\n\n", true},
41 {"on", "selector.grid = on\n", true},
42 {"off", "selector.grid = off\n", false},
43 {"whitespace", " selector.grid\t= off \n", false},
44 {"CRLF", "# config\r\nselector.grid = off\r\n", false},
45 {"BOM", "\ufeffselector.grid = off\n", false},
46 } {
47 t.Run(tc.name, func(t *testing.T) {
48 path := configRoot(t)
49 saveConfig(t, path, tc.text)
50 got, err := Load()
51 require.NoError(t, err)
52 assert.Equal(t, tc.grid, got.Grid)
53 })
54 }
55}
56
57func TestLoadRejectsInvalid(t *testing.T) {
58 for _, tc := range []struct{ text, want string }{
59 {"selector.grid = maybe", "line 1: selector.grid must be on or off"},
60 {"selector.grid", "line 1: expected each of"},
61 {"selector.grid =", "line 1: selector.grid must be on or off"},
62 {"selector.other = on", "line 1: expected each of"},
63 {"selector.grid = on\nselector.grid = off", "line 2: expected each of"},
64 {strings.Repeat("#", maxBytes+1), "exceeds 4096 bytes"},
65 } {
66 t.Run(tc.text[:min(len(tc.text), 40)], func(t *testing.T) {
67 path := configRoot(t)
68 saveConfig(t, path, tc.text)
69 _, err := Load()
70 require.ErrorContains(t, err, tc.want)
71 assert.Contains(t, err.Error(), path)
72 })
73 }
74}
75
76func TestLoadHomeFallback(t *testing.T) {
77 home := t.TempDir()
78 t.Setenv("HOME", home)
79 t.Setenv("XDG_CONFIG_HOME", "")
80 saveConfig(t, filepath.Join(home, ".config", "nefercap", "config"), "selector.grid = off\n")
81 got, err := Load()
82 require.NoError(t, err)
83 assert.False(t, got.Grid)
84}
85
86func TestLoadRelativeXDGUsesHome(t *testing.T) {
87 home := t.TempDir()
88 t.Setenv("HOME", home)
89 t.Setenv("XDG_CONFIG_HOME", "relative-config")
90 saveConfig(t, filepath.Join(home, ".config", "nefercap", "config"), "selector.grid = off\n")
91 got, err := Load()
92 require.NoError(t, err)
93 assert.False(t, got.Grid)
94}
95
96func TestLoadNoConfigDirectoryUsesDefaults(t *testing.T) {
97 t.Setenv("HOME", "")
98 t.Setenv("XDG_CONFIG_HOME", "")
99 got, err := Load()
100 require.NoError(t, err)
101 assert.True(t, got.Grid)
102}
103
104func TestLoadFIFORejectedWithoutBlocking(t *testing.T) {
105 path := configRoot(t)
106 require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700))
107 require.NoError(t, syscall.Mkfifo(path, 0o600))
108 _, err := Load()
109 require.ErrorContains(t, err, "expected a regular file")
110}
111
112func TestLoadSizeBoundary(t *testing.T) {
113 path := configRoot(t)
114 saveConfig(t, path, strings.Repeat("#", maxBytes))
115 got, err := Load()
116 require.NoError(t, err)
117 assert.True(t, got.Grid)
118}
119
120func TestLoadDirectoryRejected(t *testing.T) {
121 path := configRoot(t)
122 require.NoError(t, os.MkdirAll(path, 0o700))
123 _, err := Load()
124 require.ErrorContains(t, err, "expected a regular file")
125}
126
127func TestLoadPathsAndOutput(t *testing.T) {
128 home := t.TempDir()
129 t.Setenv("HOME", home)
130 path := configRoot(t)
131 saveConfig(t, path, "# c\nscreenshot.dir = ~/Pics/Shots\nvideo.dir=/var/tmp/vids/\nscreenshot.output = file+clipboard\nselector.grid = off\n")
132 got, err := Load()
133 require.NoError(t, err)
134 assert.Equal(t, Settings{Grid: false, ScreenshotDir: filepath.Join(home, "Pics", "Shots"), VideoDir: "/var/tmp/vids", Output: OutputFileClipboard}, got)
135
136 saveConfig(t, path, "screenshot.dir = ~/\nscreenshot.output = clipboard\n")
137 got, err = Load()
138 require.NoError(t, err)
139 assert.Equal(t, home, got.ScreenshotDir)
140 assert.Equal(t, OutputClipboard, got.Output)
141 assert.Empty(t, got.VideoDir)
142}
143
144func TestLoadDefaultsOutputFile(t *testing.T) {
145 configRoot(t)
146 got, err := Load()
147 require.NoError(t, err)
148 assert.Equal(t, Settings{Grid: true, Output: OutputFile}, got)
149}
150
151func TestLoadRejectsInvalidNewKeys(t *testing.T) {
152 for _, tc := range []struct{ text, want string }{
153 {"screenshot.dir = Pictures", "line 1: screenshot.dir must be an absolute path or start with ~/"},
154 {"video.dir = ./v", "line 1: video.dir must be an absolute path"},
155 {"video.dir = ~user/v", "line 1: video.dir must be an absolute path"},
156 {"video.dir = ~", "line 1: video.dir must be an absolute path"},
157 {"video.dir =", "line 1: video.dir must be a non-empty path"},
158 {"screenshot.output = both", "line 1: screenshot.output must be file, file+clipboard or clipboard"},
159 {"screenshot.output = File", "screenshot.output must be"},
160 {"screenshot.dir = /a\nscreenshot.dir = /b", "line 2: expected each of"},
161 {"screenshot.output = file\n\nscreenshot.output = file", "line 3: expected each of"},
162 {"video.dir = /a\nvideo.dir = /a", "line 2: expected each of"},
163 {"video.output = file", "line 1: expected each of"},
164 } {
165 t.Run(tc.text, func(t *testing.T) {
166 t.Setenv("HOME", t.TempDir())
167 path := configRoot(t)
168 saveConfig(t, path, tc.text)
169 _, err := Load()
170 require.ErrorContains(t, err, tc.want)
171 assert.Contains(t, err.Error(), path)
172 })
173 }
174}