cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
1package main
2
3import (
4 "context"
5 "strings"
6 "testing"
7
8 "github.com/stormlightlabs/noteleaf/internal/handlers"
9)
10
11func createTestPublicationHandler(t *testing.T) (*handlers.PublicationHandler, func()) {
12 cleanup := setupCommandTest(t)
13 handler, err := handlers.NewPublicationHandler()
14 if err != nil {
15 cleanup()
16 t.Fatalf("Failed to create test publication handler: %v", err)
17 }
18 return handler, func() {
19 handler.Close()
20 cleanup()
21 }
22}
23
24func TestPublicationCommand(t *testing.T) {
25 t.Run("CommandGroup Interface", func(t *testing.T) {
26 handler, cleanup := createTestPublicationHandler(t)
27 defer cleanup()
28
29 var _ CommandGroup = NewPublicationCommand(handler)
30 })
31
32 t.Run("Create", func(t *testing.T) {
33 t.Run("creates command with correct structure", func(t *testing.T) {
34 handler, cleanup := createTestPublicationHandler(t)
35 defer cleanup()
36
37 cmd := NewPublicationCommand(handler).Create()
38
39 if cmd == nil {
40 t.Fatal("Create returned nil")
41 }
42 if cmd.Use != "pub" {
43 t.Errorf("Expected Use to be 'pub', got '%s'", cmd.Use)
44 }
45 if cmd.Short != "Manage leaflet publication sync" {
46 t.Errorf("Expected Short to be 'Manage leaflet publication sync', got '%s'", cmd.Short)
47 }
48 if !cmd.HasSubCommands() {
49 t.Error("Expected command to have subcommands")
50 }
51 })
52
53 t.Run("has all expected subcommands", func(t *testing.T) {
54 handler, cleanup := createTestPublicationHandler(t)
55 defer cleanup()
56
57 cmd := NewPublicationCommand(handler).Create()
58 subcommands := cmd.Commands()
59 subcommandNames := make([]string, len(subcommands))
60 for i, subcmd := range subcommands {
61 subcommandNames[i] = subcmd.Use
62 }
63
64 expectedSubcommands := []string{
65 "auth [handle]",
66 "pull",
67 "list [--published|--draft|--all]",
68 "status",
69 "post [note-id]",
70 "patch [note-id]",
71 }
72
73 for _, expected := range expectedSubcommands {
74 if !findSubcommand(subcommandNames, expected) {
75 t.Errorf("Expected subcommand '%s' not found in %v", expected, subcommandNames)
76 }
77 }
78 })
79 })
80
81 t.Run("Status Command", func(t *testing.T) {
82 t.Run("shows not authenticated initially", func(t *testing.T) {
83 handler, cleanup := createTestPublicationHandler(t)
84 defer cleanup()
85
86 cmd := NewPublicationCommand(handler).Create()
87 cmd.SetArgs([]string{"status"})
88 err := cmd.Execute()
89
90 if err != nil {
91 t.Errorf("status command failed: %v", err)
92 }
93 })
94 })
95
96 t.Run("List Command", func(t *testing.T) {
97 t.Run("default filter", func(t *testing.T) {
98 handler, cleanup := createTestPublicationHandler(t)
99 defer cleanup()
100
101 cmd := NewPublicationCommand(handler).Create()
102 cmd.SetArgs([]string{"list"})
103 err := cmd.Execute()
104
105 if err != nil {
106 t.Errorf("list command failed: %v", err)
107 }
108 })
109
110 t.Run("with published flag", func(t *testing.T) {
111 handler, cleanup := createTestPublicationHandler(t)
112 defer cleanup()
113
114 cmd := NewPublicationCommand(handler).Create()
115 cmd.SetArgs([]string{"list", "--published"})
116 err := cmd.Execute()
117
118 if err != nil {
119 t.Errorf("list --published failed: %v", err)
120 }
121 })
122
123 t.Run("with draft flag", func(t *testing.T) {
124 handler, cleanup := createTestPublicationHandler(t)
125 defer cleanup()
126
127 cmd := NewPublicationCommand(handler).Create()
128 cmd.SetArgs([]string{"list", "--draft"})
129 err := cmd.Execute()
130
131 if err != nil {
132 t.Errorf("list --draft failed: %v", err)
133 }
134 })
135
136 t.Run("with all flag", func(t *testing.T) {
137 handler, cleanup := createTestPublicationHandler(t)
138 defer cleanup()
139
140 cmd := NewPublicationCommand(handler).Create()
141 cmd.SetArgs([]string{"list", "--all"})
142 err := cmd.Execute()
143
144 if err != nil {
145 t.Errorf("list --all failed: %v", err)
146 }
147 })
148
149 t.Run("published takes precedence over draft", func(t *testing.T) {
150 handler, cleanup := createTestPublicationHandler(t)
151 defer cleanup()
152
153 cmd := NewPublicationCommand(handler).Create()
154 cmd.SetArgs([]string{"list", "--published", "--draft"})
155 err := cmd.Execute()
156
157 if err != nil {
158 t.Errorf("list with multiple flags failed: %v", err)
159 }
160 })
161 })
162
163 t.Run("Pull Command", func(t *testing.T) {
164 t.Run("fails when not authenticated", func(t *testing.T) {
165 handler, cleanup := createTestPublicationHandler(t)
166 defer cleanup()
167
168 cmd := NewPublicationCommand(handler).Create()
169 cmd.SetArgs([]string{"pull"})
170 err := cmd.Execute()
171
172 if err == nil {
173 t.Error("Expected pull to fail when not authenticated")
174 }
175 if err != nil && !strings.Contains(err.Error(), "not authenticated") {
176 t.Errorf("Expected 'not authenticated' error, got: %v", err)
177 }
178 })
179 })
180
181 t.Run("Post Command", func(t *testing.T) {
182 t.Run("requires note ID argument", func(t *testing.T) {
183 handler, cleanup := createTestPublicationHandler(t)
184 defer cleanup()
185
186 cmd := NewPublicationCommand(handler).Create()
187 cmd.SetArgs([]string{"post"})
188 err := cmd.Execute()
189
190 if err == nil {
191 t.Error("Expected error for missing note ID")
192 }
193 })
194
195 t.Run("rejects invalid note ID", func(t *testing.T) {
196 handler, cleanup := createTestPublicationHandler(t)
197 defer cleanup()
198
199 cmd := NewPublicationCommand(handler).Create()
200 cmd.SetArgs([]string{"post", "not-a-number"})
201 err := cmd.Execute()
202
203 if err == nil {
204 t.Error("Expected error for invalid note ID")
205 }
206 if !strings.Contains(err.Error(), "invalid note ID") {
207 t.Errorf("Expected 'invalid note ID' error, got: %v", err)
208 }
209 })
210
211 t.Run("fails when not authenticated", func(t *testing.T) {
212 handler, cleanup := createTestPublicationHandler(t)
213 defer cleanup()
214
215 cmd := NewPublicationCommand(handler).Create()
216 cmd.SetArgs([]string{"post", "123"})
217 err := cmd.Execute()
218
219 if err == nil {
220 t.Error("Expected post to fail when not authenticated")
221 }
222 if !strings.Contains(err.Error(), "not authenticated") {
223 t.Errorf("Expected 'not authenticated' error, got: %v", err)
224 }
225 })
226
227 t.Run("preview mode fails when not authenticated", func(t *testing.T) {
228 handler, cleanup := createTestPublicationHandler(t)
229 defer cleanup()
230
231 cmd := NewPublicationCommand(handler).Create()
232 cmd.SetArgs([]string{"post", "123", "--preview"})
233 err := cmd.Execute()
234
235 if err == nil {
236 t.Error("Expected post --preview to fail when not authenticated")
237 }
238 if !strings.Contains(err.Error(), "not authenticated") {
239 t.Errorf("Expected 'not authenticated' error, got: %v", err)
240 }
241 })
242
243 t.Run("validate mode fails when not authenticated", func(t *testing.T) {
244 handler, cleanup := createTestPublicationHandler(t)
245 defer cleanup()
246
247 cmd := NewPublicationCommand(handler).Create()
248 cmd.SetArgs([]string{"post", "123", "--validate"})
249 err := cmd.Execute()
250
251 if err == nil {
252 t.Error("Expected post --validate to fail when not authenticated")
253 }
254 if !strings.Contains(err.Error(), "not authenticated") {
255 t.Errorf("Expected 'not authenticated' error, got: %v", err)
256 }
257 })
258
259 t.Run("accepts draft flag", func(t *testing.T) {
260 handler, cleanup := createTestPublicationHandler(t)
261 defer cleanup()
262
263 cmd := NewPublicationCommand(handler).Create()
264 cmd.SetArgs([]string{"post", "123", "--draft"})
265 err := cmd.Execute()
266
267 if err == nil {
268 t.Error("Expected post --draft to fail when not authenticated")
269 }
270 if !strings.Contains(err.Error(), "not authenticated") {
271 t.Errorf("Expected 'not authenticated' error, got: %v", err)
272 }
273 })
274
275 t.Run("accepts preview and draft flags together", func(t *testing.T) {
276 handler, cleanup := createTestPublicationHandler(t)
277 defer cleanup()
278
279 cmd := NewPublicationCommand(handler).Create()
280 cmd.SetArgs([]string{"post", "123", "--preview", "--draft"})
281 err := cmd.Execute()
282
283 if err == nil {
284 t.Error("Expected post --preview --draft to fail when not authenticated")
285 }
286 if !strings.Contains(err.Error(), "not authenticated") {
287 t.Errorf("Expected 'not authenticated' error, got: %v", err)
288 }
289 })
290 })
291
292 t.Run("Patch Command", func(t *testing.T) {
293 t.Run("requires note ID argument", func(t *testing.T) {
294 handler, cleanup := createTestPublicationHandler(t)
295 defer cleanup()
296
297 cmd := NewPublicationCommand(handler).Create()
298 cmd.SetArgs([]string{"patch"})
299 err := cmd.Execute()
300
301 if err == nil {
302 t.Error("Expected error for missing note ID")
303 }
304 })
305
306 t.Run("rejects invalid note ID", func(t *testing.T) {
307 handler, cleanup := createTestPublicationHandler(t)
308 defer cleanup()
309
310 cmd := NewPublicationCommand(handler).Create()
311 cmd.SetArgs([]string{"patch", "not-a-number"})
312 err := cmd.Execute()
313
314 if err == nil {
315 t.Error("Expected error for invalid note ID")
316 }
317 if !strings.Contains(err.Error(), "invalid note ID") {
318 t.Errorf("Expected 'invalid note ID' error, got: %v", err)
319 }
320 })
321
322 t.Run("fails when not authenticated", func(t *testing.T) {
323 handler, cleanup := createTestPublicationHandler(t)
324 defer cleanup()
325
326 cmd := NewPublicationCommand(handler).Create()
327 cmd.SetArgs([]string{"patch", "123"})
328 err := cmd.Execute()
329
330 if err == nil {
331 t.Error("Expected patch to fail when not authenticated")
332 }
333 if !strings.Contains(err.Error(), "not authenticated") {
334 t.Errorf("Expected 'not authenticated' error, got: %v", err)
335 }
336 })
337
338 t.Run("preview mode fails when not authenticated", func(t *testing.T) {
339 handler, cleanup := createTestPublicationHandler(t)
340 defer cleanup()
341
342 cmd := NewPublicationCommand(handler).Create()
343 cmd.SetArgs([]string{"patch", "123", "--preview"})
344 err := cmd.Execute()
345
346 if err == nil {
347 t.Error("Expected patch --preview to fail when not authenticated")
348 }
349 if !strings.Contains(err.Error(), "not authenticated") {
350 t.Errorf("Expected 'not authenticated' error, got: %v", err)
351 }
352 })
353
354 t.Run("validate mode fails when not authenticated", func(t *testing.T) {
355 handler, cleanup := createTestPublicationHandler(t)
356 defer cleanup()
357
358 cmd := NewPublicationCommand(handler).Create()
359 cmd.SetArgs([]string{"patch", "123", "--validate"})
360 err := cmd.Execute()
361
362 if err == nil {
363 t.Error("Expected patch --validate to fail when not authenticated")
364 }
365 if !strings.Contains(err.Error(), "not authenticated") {
366 t.Errorf("Expected 'not authenticated' error, got: %v", err)
367 }
368 })
369 })
370
371 t.Run("Command Help", func(t *testing.T) {
372 t.Run("root help", func(t *testing.T) {
373 handler, cleanup := createTestPublicationHandler(t)
374 defer cleanup()
375
376 cmd := NewPublicationCommand(handler).Create()
377 cmd.SetArgs([]string{"help"})
378 err := cmd.Execute()
379
380 if err != nil {
381 t.Errorf("help command failed: %v", err)
382 }
383 })
384
385 t.Run("auth help", func(t *testing.T) {
386 handler, cleanup := createTestPublicationHandler(t)
387 defer cleanup()
388
389 cmd := NewPublicationCommand(handler).Create()
390 cmd.SetArgs([]string{"auth", "--help"})
391 err := cmd.Execute()
392
393 if err != nil {
394 t.Errorf("auth help failed: %v", err)
395 }
396 })
397 })
398
399 t.Run("Command Aliases", func(t *testing.T) {
400 t.Run("list alias ls works", func(t *testing.T) {
401 handler, cleanup := createTestPublicationHandler(t)
402 defer cleanup()
403
404 cmd := NewPublicationCommand(handler).Create()
405 cmd.SetArgs([]string{"ls"})
406 err := cmd.Execute()
407
408 if err != nil {
409 t.Errorf("list alias 'ls' failed: %v", err)
410 }
411 })
412 })
413
414 t.Run("Handler Validation", func(t *testing.T) {
415 t.Run("auth validates empty handle", func(t *testing.T) {
416 handler, cleanup := createTestPublicationHandler(t)
417 defer cleanup()
418
419 ctx := context.Background()
420 err := handler.Auth(ctx, "", "password")
421
422 if err == nil {
423 t.Error("Expected error for empty handle")
424 }
425 if !strings.Contains(err.Error(), "handle is required") {
426 t.Errorf("Expected 'handle is required' error, got: %v", err)
427 }
428 })
429
430 t.Run("auth validates empty password", func(t *testing.T) {
431 handler, cleanup := createTestPublicationHandler(t)
432 defer cleanup()
433
434 ctx := context.Background()
435 err := handler.Auth(ctx, "test.bsky.social", "")
436
437 if err == nil {
438 t.Error("Expected error for empty password")
439 }
440 if !strings.Contains(err.Error(), "password is required") {
441 t.Errorf("Expected 'password is required' error, got: %v", err)
442 }
443 })
444 })
445}