1package plugin
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "reflect"
8 "runtime"
9 "strings"
10 "sync"
11 "testing"
12)
13
14// setTestHome makes t.TempDir() the effective home directory for the duration
15// of the test on both Unix and Windows. Go's os.UserHomeDir() reads $HOME on
16// Unix but %USERPROFILE% on Windows, so we set both.
17func setTestHome(t *testing.T) string {
18 t.Helper()
19 dir := t.TempDir()
20 t.Setenv("HOME", dir)
21 if runtime.GOOS == "windows" {
22 t.Setenv("USERPROFILE", dir)
23 }
24 return dir
25}
26
27func TestPluginStoreSetGet(t *testing.T) {
28 setTestHome(t)
29
30 store, err := newPluginStore("test_plugin")
31 if err != nil {
32 t.Fatal(err)
33 }
34
35 if err := store.Set("token", "abc123"); err != nil {
36 t.Fatal(err)
37 }
38
39 got, ok := store.Get("token")
40 if !ok {
41 t.Fatal("expected stored key")
42 }
43 if got != "abc123" {
44 t.Fatalf("expected abc123, got %q", got)
45 }
46}
47
48func TestPluginStoreDelete(t *testing.T) {
49 setTestHome(t)
50
51 store, err := newPluginStore("test_plugin")
52 if err != nil {
53 t.Fatal(err)
54 }
55 if err := store.Set("token", "abc123"); err != nil {
56 t.Fatal(err)
57 }
58 if err := store.Delete("token"); err != nil {
59 t.Fatal(err)
60 }
61
62 if got, ok := store.Get("token"); ok {
63 t.Fatalf("expected key to be deleted, got %q", got)
64 }
65}
66
67func TestPluginStoreKeys(t *testing.T) {
68 setTestHome(t)
69
70 store, err := newPluginStore("test_plugin")
71 if err != nil {
72 t.Fatal(err)
73 }
74 if err := store.Set("a", "1"); err != nil {
75 t.Fatal(err)
76 }
77 if err := store.Set("b", "2"); err != nil {
78 t.Fatal(err)
79 }
80
81 got := map[string]bool{}
82 for _, key := range store.Keys() {
83 got[key] = true
84 }
85
86 want := map[string]bool{"a": true, "b": true}
87 if !reflect.DeepEqual(got, want) {
88 t.Fatalf("expected keys %v, got %v", want, got)
89 }
90}
91
92func TestPluginStoreKeysSortedOrder(t *testing.T) {
93 setTestHome(t)
94
95 store, err := newPluginStore("test_plugin")
96 if err != nil {
97 t.Fatal(err)
98 }
99 // Insert in non-sorted order so map iteration order won't accidentally
100 // produce the expected result.
101 for _, k := range []string{"c", "a", "b", "z", "m"} {
102 if err := store.Set(k, k); err != nil {
103 t.Fatal(err)
104 }
105 }
106
107 got := store.Keys()
108 want := []string{"a", "b", "c", "m", "z"}
109 if !reflect.DeepEqual(got, want) {
110 t.Fatalf("expected sorted keys %v, got %v", want, got)
111 }
112}
113
114func TestPluginStoreKeysEmpty(t *testing.T) {
115 setTestHome(t)
116
117 store, err := newPluginStore("test_plugin")
118 if err != nil {
119 t.Fatal(err)
120 }
121
122 if keys := store.Keys(); len(keys) != 0 {
123 t.Fatalf("expected no keys, got %v", keys)
124 }
125}
126
127func TestPluginStoreConcurrentSets(t *testing.T) {
128 setTestHome(t)
129
130 store, err := newPluginStore("test_plugin")
131 if err != nil {
132 t.Fatal(err)
133 }
134
135 var wg sync.WaitGroup
136 for i := 0; i < 20; i++ {
137 wg.Add(1)
138 go func(i int) {
139 defer wg.Done()
140 if err := store.Set(fmt.Sprintf("key%d", i), fmt.Sprintf("value%d", i)); err != nil {
141 t.Errorf("set key%d: %v", i, err)
142 }
143 }(i)
144 }
145 wg.Wait()
146
147 for i := 0; i < 20; i++ {
148 key := fmt.Sprintf("key%d", i)
149 want := fmt.Sprintf("value%d", i)
150 got, ok := store.Get(key)
151 if !ok {
152 t.Fatalf("expected %s to be stored", key)
153 }
154 if got != want {
155 t.Fatalf("expected %s, got %q", want, got)
156 }
157 }
158}
159
160func TestPluginStorePersistence(t *testing.T) {
161 setTestHome(t)
162
163 store, err := newPluginStore("test_plugin")
164 if err != nil {
165 t.Fatal(err)
166 }
167 if err := store.Set("token", "abc123"); err != nil {
168 t.Fatal(err)
169 }
170
171 reloaded, err := newPluginStore("test_plugin")
172 if err != nil {
173 t.Fatal(err)
174 }
175
176 got, ok := reloaded.Get("token")
177 if !ok {
178 t.Fatal("expected persisted key")
179 }
180 if got != "abc123" {
181 t.Fatalf("expected abc123, got %q", got)
182 }
183}
184
185func TestPluginStoreFileMode(t *testing.T) {
186 setTestHome(t)
187
188 store, err := newPluginStore("test_plugin")
189 if err != nil {
190 t.Fatal(err)
191 }
192 if err := store.Set("token", "abc123"); err != nil {
193 t.Fatal(err)
194 }
195
196 info, err := os.Stat(store.path)
197 if err != nil {
198 t.Fatal(err)
199 }
200 if runtime.GOOS != "windows" {
201 if got := info.Mode().Perm(); got != 0o600 {
202 t.Fatalf("expected mode 0600, got %o", got)
203 }
204 }
205}
206
207func TestPluginStoreFileModeAfterOverwrite(t *testing.T) {
208 setTestHome(t)
209
210 store, err := newPluginStore("test_plugin")
211 if err != nil {
212 t.Fatal(err)
213 }
214 if err := store.Set("token", "abc123"); err != nil {
215 t.Fatal(err)
216 }
217 if err := os.Chmod(store.path, 0o666); err != nil {
218 t.Fatal(err)
219 }
220 if err := store.Set("token", "def456"); err != nil {
221 t.Fatal(err)
222 }
223
224 info, err := os.Stat(store.path)
225 if err != nil {
226 t.Fatal(err)
227 }
228 if runtime.GOOS != "windows" {
229 if got := info.Mode().Perm(); got != 0o600 {
230 t.Fatalf("expected mode 0600 after overwrite, got %o", got)
231 }
232 }
233}
234
235func TestNewPluginStoreRejectsInvalidPluginName(t *testing.T) {
236 setTestHome(t)
237
238 for _, name := range []string{"", ".", "..", "../etc", "foo/bar", `foo\bar`, "foo.bar"} {
239 t.Run(name, func(t *testing.T) {
240 if _, err := newPluginStore(name); err == nil {
241 t.Fatal("expected invalid plugin name error")
242 }
243 })
244 }
245}
246
247func TestLuaStoreInitErrorPropagates(t *testing.T) {
248 home := setTestHome(t)
249
250 dir := filepath.Join(home, ".config", "matcha", "plugins", "test_plugin")
251 if err := os.MkdirAll(dir, 0o700); err != nil {
252 t.Fatal(err)
253 }
254 if err := os.WriteFile(filepath.Join(dir, "data.json"), []byte("{"), 0o600); err != nil {
255 t.Fatal(err)
256 }
257
258 m := newTestManager()
259 defer m.Close()
260 m.currentPlugin = "test_plugin"
261
262 err := m.state.DoString(`
263 local matcha = require("matcha")
264 matcha.store_get("token")
265 `)
266 if err == nil {
267 t.Fatal("expected store_get to fail on store init error")
268 }
269 if !strings.Contains(err.Error(), "store_get:") {
270 t.Fatalf("expected store_get error, got %v", err)
271 }
272}