tangled
alpha
login
or
join now
desertthunder.dev
/
noteleaf
cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists ๐
charm
leaflet
readability
golang
29
fork
atom
overview
issues
2
pulls
pipelines
build: handler & UI tests
desertthunder.dev
5 months ago
e58f6280
5bd45ba7
+492
2 changed files
expand all
collapse all
unified
split
cmd
handlers
handlers_test.go
internal
ui
ui_test.go
+365
cmd/handlers/handlers_test.go
···
4
4
"context"
5
5
"os"
6
6
"path/filepath"
7
7
+
"runtime"
8
8
+
"strings"
7
9
"testing"
8
10
"time"
9
11
···
211
213
}
212
214
213
215
})
216
216
+
}
217
217
+
218
218
+
func TestSetupErrorHandling(t *testing.T) {
219
219
+
t.Run("handles GetConfigDir error", func(t *testing.T) {
220
220
+
originalXDG := os.Getenv("XDG_CONFIG_HOME")
221
221
+
originalHome := os.Getenv("HOME")
222
222
+
223
223
+
if runtime.GOOS == "windows" {
224
224
+
originalAppData := os.Getenv("APPDATA")
225
225
+
os.Unsetenv("APPDATA")
226
226
+
defer os.Setenv("APPDATA", originalAppData)
227
227
+
} else {
228
228
+
os.Unsetenv("XDG_CONFIG_HOME")
229
229
+
os.Unsetenv("HOME")
230
230
+
}
231
231
+
232
232
+
defer func() {
233
233
+
os.Setenv("XDG_CONFIG_HOME", originalXDG)
234
234
+
os.Setenv("HOME", originalHome)
235
235
+
}()
236
236
+
237
237
+
ctx := context.Background()
238
238
+
err := Setup(ctx, []string{})
239
239
+
240
240
+
if err == nil {
241
241
+
t.Error("Setup should fail when GetConfigDir fails")
242
242
+
}
243
243
+
if !strings.Contains(err.Error(), "failed to get config directory") {
244
244
+
t.Errorf("Expected config directory error, got: %v", err)
245
245
+
}
246
246
+
})
247
247
+
248
248
+
t.Run("handles database creation error", func(t *testing.T) {
249
249
+
tempDir, err := os.MkdirTemp("", "noteleaf-readonly-test-*")
250
250
+
if err != nil {
251
251
+
t.Fatalf("Failed to create temp dir: %v", err)
252
252
+
}
253
253
+
defer os.RemoveAll(tempDir)
254
254
+
255
255
+
noteleafDir := filepath.Join(tempDir, "noteleaf")
256
256
+
if err := os.MkdirAll(noteleafDir, 0755); err != nil {
257
257
+
t.Fatalf("Failed to create noteleaf dir: %v", err)
258
258
+
}
259
259
+
260
260
+
if err := os.Chmod(noteleafDir, 0444); err != nil {
261
261
+
t.Fatalf("Failed to make directory read-only: %v", err)
262
262
+
}
263
263
+
264
264
+
defer os.Chmod(noteleafDir, 0755)
265
265
+
266
266
+
oldConfigHome := os.Getenv("XDG_CONFIG_HOME")
267
267
+
os.Setenv("XDG_CONFIG_HOME", tempDir)
268
268
+
defer os.Setenv("XDG_CONFIG_HOME", oldConfigHome)
269
269
+
270
270
+
ctx := context.Background()
271
271
+
err = Setup(ctx, []string{})
272
272
+
273
273
+
if err == nil {
274
274
+
t.Error("Setup should fail when database creation fails")
275
275
+
}
276
276
+
if !strings.Contains(err.Error(), "failed to initialize database") {
277
277
+
t.Errorf("Expected database initialization error, got: %v", err)
278
278
+
}
279
279
+
})
280
280
+
281
281
+
t.Run("handles config loading error", func(t *testing.T) {
282
282
+
tempDir, err := os.MkdirTemp("", "noteleaf-config-error-test-*")
283
283
+
if err != nil {
284
284
+
t.Fatalf("Failed to create temp dir: %v", err)
285
285
+
}
286
286
+
defer os.RemoveAll(tempDir)
287
287
+
288
288
+
noteleafDir := filepath.Join(tempDir, "noteleaf")
289
289
+
if err := os.MkdirAll(noteleafDir, 0755); err != nil {
290
290
+
t.Fatalf("Failed to create noteleaf dir: %v", err)
291
291
+
}
292
292
+
293
293
+
configPath := filepath.Join(noteleafDir, ".noteleaf.conf.toml")
294
294
+
invalidTOML := `[invalid toml content`
295
295
+
if err := os.WriteFile(configPath, []byte(invalidTOML), 0644); err != nil {
296
296
+
t.Fatalf("Failed to write invalid config: %v", err)
297
297
+
}
298
298
+
299
299
+
oldConfigHome := os.Getenv("XDG_CONFIG_HOME")
300
300
+
os.Setenv("XDG_CONFIG_HOME", tempDir)
301
301
+
defer os.Setenv("XDG_CONFIG_HOME", oldConfigHome)
302
302
+
303
303
+
ctx := context.Background()
304
304
+
err = Setup(ctx, []string{})
305
305
+
306
306
+
if err == nil {
307
307
+
t.Error("Setup should fail when config loading fails")
308
308
+
}
309
309
+
if !strings.Contains(err.Error(), "failed to create configuration") {
310
310
+
t.Errorf("Expected configuration error, got: %v", err)
311
311
+
}
312
312
+
})
313
313
+
}
314
314
+
315
315
+
func TestResetErrorHandling(t *testing.T) {
316
316
+
t.Run("handles GetConfigDir error", func(t *testing.T) {
317
317
+
originalXDG := os.Getenv("XDG_CONFIG_HOME")
318
318
+
originalHome := os.Getenv("HOME")
319
319
+
320
320
+
if runtime.GOOS == "windows" {
321
321
+
originalAppData := os.Getenv("APPDATA")
322
322
+
os.Unsetenv("APPDATA")
323
323
+
defer os.Setenv("APPDATA", originalAppData)
324
324
+
} else {
325
325
+
os.Unsetenv("XDG_CONFIG_HOME")
326
326
+
os.Unsetenv("HOME")
327
327
+
}
328
328
+
329
329
+
defer func() {
330
330
+
os.Setenv("XDG_CONFIG_HOME", originalXDG)
331
331
+
os.Setenv("HOME", originalHome)
332
332
+
}()
333
333
+
334
334
+
ctx := context.Background()
335
335
+
err := Reset(ctx, []string{})
336
336
+
337
337
+
if err == nil {
338
338
+
t.Error("Reset should fail when GetConfigDir fails")
339
339
+
}
340
340
+
if !strings.Contains(err.Error(), "failed to get config directory") {
341
341
+
t.Errorf("Expected config directory error, got: %v", err)
342
342
+
}
343
343
+
})
344
344
+
345
345
+
t.Run("handles GetConfigPath error", func(t *testing.T) {
346
346
+
tempDir, err := os.MkdirTemp("", "noteleaf-reset-error-test-*")
347
347
+
if err != nil {
348
348
+
t.Fatalf("Failed to create temp dir: %v", err)
349
349
+
}
350
350
+
defer os.RemoveAll(tempDir)
351
351
+
352
352
+
// Set up a scenario where GetConfigPath might fail
353
353
+
// This is tricky since GetConfigPath internally calls GetConfigDir
354
354
+
// We'll create a scenario where the config dir exists but GetConfigPath fails
355
355
+
oldConfigHome := os.Getenv("XDG_CONFIG_HOME")
356
356
+
os.Setenv("XDG_CONFIG_HOME", tempDir)
357
357
+
defer os.Setenv("XDG_CONFIG_HOME", oldConfigHome)
358
358
+
359
359
+
noteleafDir := filepath.Join(tempDir, "noteleaf")
360
360
+
if err := os.MkdirAll(noteleafDir, 0755); err != nil {
361
361
+
t.Fatalf("Failed to create noteleaf dir: %v", err)
362
362
+
}
363
363
+
364
364
+
dbPath := filepath.Join(noteleafDir, "noteleaf.db")
365
365
+
if err := os.WriteFile(dbPath, []byte("test"), 0644); err != nil {
366
366
+
t.Fatalf("Failed to create test db file: %v", err)
367
367
+
}
368
368
+
369
369
+
if err := os.Chmod(noteleafDir, 0444); err != nil {
370
370
+
t.Fatalf("Failed to make directory read-only: %v", err)
371
371
+
}
372
372
+
373
373
+
defer os.Chmod(noteleafDir, 0755)
374
374
+
375
375
+
ctx := context.Background()
376
376
+
err = Reset(ctx, []string{})
377
377
+
378
378
+
if err == nil {
379
379
+
t.Error("Reset should fail when file removal fails")
380
380
+
}
381
381
+
if !strings.Contains(err.Error(), "failed to remove") && !strings.Contains(err.Error(), "failed to get config path") {
382
382
+
t.Errorf("Expected removal or config path error, got: %v", err)
383
383
+
}
384
384
+
})
385
385
+
}
386
386
+
387
387
+
func TestStatusErrorHandling(t *testing.T) {
388
388
+
t.Run("handles GetConfigDir error", func(t *testing.T) {
389
389
+
originalXDG := os.Getenv("XDG_CONFIG_HOME")
390
390
+
originalHome := os.Getenv("HOME")
391
391
+
392
392
+
if runtime.GOOS == "windows" {
393
393
+
originalAppData := os.Getenv("APPDATA")
394
394
+
os.Unsetenv("APPDATA")
395
395
+
defer os.Setenv("APPDATA", originalAppData)
396
396
+
} else {
397
397
+
os.Unsetenv("XDG_CONFIG_HOME")
398
398
+
os.Unsetenv("HOME")
399
399
+
}
400
400
+
401
401
+
defer func() {
402
402
+
os.Setenv("XDG_CONFIG_HOME", originalXDG)
403
403
+
os.Setenv("HOME", originalHome)
404
404
+
}()
405
405
+
406
406
+
ctx := context.Background()
407
407
+
err := Status(ctx, []string{})
408
408
+
409
409
+
if err == nil {
410
410
+
t.Error("Status should fail when GetConfigDir fails")
411
411
+
}
412
412
+
if !strings.Contains(err.Error(), "failed to get config directory") {
413
413
+
t.Errorf("Expected config directory error, got: %v", err)
414
414
+
}
415
415
+
})
416
416
+
417
417
+
t.Run("handles GetConfigPath error after database exists", func(t *testing.T) {
418
418
+
_ = createTestDir(t)
419
419
+
ctx := context.Background()
420
420
+
421
421
+
err := Setup(ctx, []string{})
422
422
+
if err != nil {
423
423
+
t.Fatalf("Setup failed: %v", err)
424
424
+
}
425
425
+
426
426
+
// Now we have a database, but let's create a scenario where GetConfigPath fails
427
427
+
// This is challenging since GetConfigPath uses GetConfigDir which we already tested
428
428
+
// Instead, let's test the database connection error scenario
429
429
+
430
430
+
// Remove the database to cause NewDatabase to fail in Status
431
431
+
configDir, _ := store.GetConfigDir()
432
432
+
dbPath := filepath.Join(configDir, "noteleaf.db")
433
433
+
os.Remove(dbPath)
434
434
+
435
435
+
// Create a directory with the same name as the database file
436
436
+
// This will cause database connection to fail
437
437
+
if err := os.MkdirAll(dbPath, 0755); err != nil {
438
438
+
t.Fatalf("Failed to create directory: %v", err)
439
439
+
}
440
440
+
441
441
+
err = Status(ctx, []string{})
442
442
+
if err == nil {
443
443
+
t.Error("Status should fail when database connection fails")
444
444
+
}
445
445
+
if !strings.Contains(err.Error(), "failed to connect to database") {
446
446
+
t.Errorf("Expected database connection error, got: %v", err)
447
447
+
}
448
448
+
})
449
449
+
450
450
+
t.Run("handles migration errors", func(t *testing.T) {
451
451
+
_ = createTestDir(t)
452
452
+
ctx := context.Background()
453
453
+
454
454
+
err := Setup(ctx, []string{})
455
455
+
if err != nil {
456
456
+
t.Fatalf("Setup failed: %v", err)
457
457
+
}
458
458
+
459
459
+
// Corrupt the migrations table to cause GetAppliedMigrations to fail
460
460
+
db, err := store.NewDatabase()
461
461
+
if err != nil {
462
462
+
t.Fatalf("Failed to connect to database: %v", err)
463
463
+
}
464
464
+
465
465
+
_, err = db.Exec("DROP TABLE migrations")
466
466
+
if err != nil {
467
467
+
t.Fatalf("Failed to drop migrations table: %v", err)
468
468
+
}
469
469
+
470
470
+
_, err = db.Exec("CREATE TABLE migrations (invalid_schema TEXT)")
471
471
+
if err != nil {
472
472
+
t.Fatalf("Failed to create corrupted migrations table: %v", err)
473
473
+
}
474
474
+
db.Close()
475
475
+
476
476
+
err = Status(ctx, []string{})
477
477
+
if err == nil {
478
478
+
t.Error("Status should fail when migration queries fail")
479
479
+
}
480
480
+
if !strings.Contains(err.Error(), "failed to get") && !strings.Contains(err.Error(), "migrations") {
481
481
+
t.Errorf("Expected migration-related error, got: %v", err)
482
482
+
}
483
483
+
})
484
484
+
}
485
485
+
486
486
+
func TestErrorScenarios(t *testing.T) {
487
487
+
errorTests := []struct {
488
488
+
name string
489
489
+
setupFunc func(t *testing.T) (cleanup func())
490
490
+
handlerFunc func(ctx context.Context, args []string) error
491
491
+
expectError bool
492
492
+
errorSubstr string
493
493
+
}{
494
494
+
{
495
495
+
name: "Setup with invalid environment",
496
496
+
setupFunc: func(t *testing.T) func() {
497
497
+
if runtime.GOOS == "windows" {
498
498
+
original := os.Getenv("APPDATA")
499
499
+
os.Unsetenv("APPDATA")
500
500
+
return func() { os.Setenv("APPDATA", original) }
501
501
+
} else {
502
502
+
originalXDG := os.Getenv("XDG_CONFIG_HOME")
503
503
+
originalHome := os.Getenv("HOME")
504
504
+
os.Unsetenv("XDG_CONFIG_HOME")
505
505
+
os.Unsetenv("HOME")
506
506
+
return func() {
507
507
+
os.Setenv("XDG_CONFIG_HOME", originalXDG)
508
508
+
os.Setenv("HOME", originalHome)
509
509
+
}
510
510
+
}
511
511
+
},
512
512
+
handlerFunc: Setup,
513
513
+
expectError: true,
514
514
+
errorSubstr: "config directory",
515
515
+
},
516
516
+
{
517
517
+
name: "Reset with invalid environment",
518
518
+
setupFunc: func(t *testing.T) func() {
519
519
+
if runtime.GOOS == "windows" {
520
520
+
original := os.Getenv("APPDATA")
521
521
+
os.Unsetenv("APPDATA")
522
522
+
return func() { os.Setenv("APPDATA", original) }
523
523
+
} else {
524
524
+
originalXDG := os.Getenv("XDG_CONFIG_HOME")
525
525
+
originalHome := os.Getenv("HOME")
526
526
+
os.Unsetenv("XDG_CONFIG_HOME")
527
527
+
os.Unsetenv("HOME")
528
528
+
return func() {
529
529
+
os.Setenv("XDG_CONFIG_HOME", originalXDG)
530
530
+
os.Setenv("HOME", originalHome)
531
531
+
}
532
532
+
}
533
533
+
},
534
534
+
handlerFunc: Reset,
535
535
+
expectError: true,
536
536
+
errorSubstr: "config directory",
537
537
+
},
538
538
+
{
539
539
+
name: "Status with invalid environment",
540
540
+
setupFunc: func(t *testing.T) func() {
541
541
+
if runtime.GOOS == "windows" {
542
542
+
original := os.Getenv("APPDATA")
543
543
+
os.Unsetenv("APPDATA")
544
544
+
return func() { os.Setenv("APPDATA", original) }
545
545
+
} else {
546
546
+
originalXDG := os.Getenv("XDG_CONFIG_HOME")
547
547
+
originalHome := os.Getenv("HOME")
548
548
+
os.Unsetenv("XDG_CONFIG_HOME")
549
549
+
os.Unsetenv("HOME")
550
550
+
return func() {
551
551
+
os.Setenv("XDG_CONFIG_HOME", originalXDG)
552
552
+
os.Setenv("HOME", originalHome)
553
553
+
}
554
554
+
}
555
555
+
},
556
556
+
handlerFunc: Status,
557
557
+
expectError: true,
558
558
+
errorSubstr: "config directory",
559
559
+
},
560
560
+
}
561
561
+
562
562
+
for _, tt := range errorTests {
563
563
+
t.Run(tt.name, func(t *testing.T) {
564
564
+
cleanup := tt.setupFunc(t)
565
565
+
defer cleanup()
566
566
+
567
567
+
ctx := context.Background()
568
568
+
err := tt.handlerFunc(ctx, []string{})
569
569
+
570
570
+
if tt.expectError && err == nil {
571
571
+
t.Errorf("Expected error containing %q, got nil", tt.errorSubstr)
572
572
+
} else if !tt.expectError && err != nil {
573
573
+
t.Errorf("Expected no error, got: %v", err)
574
574
+
} else if tt.expectError && err != nil && !strings.Contains(err.Error(), tt.errorSubstr) {
575
575
+
t.Errorf("Expected error containing %q, got: %v", tt.errorSubstr, err)
576
576
+
}
577
577
+
})
578
578
+
}
214
579
}
215
580
216
581
func TestIntegration(t *testing.T) {
+127
internal/ui/ui_test.go
···
35
35
expected string
36
36
}{
37
37
{Cumin, "#BF976F"},
38
38
+
{Tang, "#FF985A"},
39
39
+
{Yam, "#FFB587"},
40
40
+
{Paprika, "#D36C64"},
41
41
+
{Bengal, "#FF6E63"},
42
42
+
{Uni, "#FF937D"},
43
43
+
{Sriracha, "#EB4268"},
44
44
+
{Coral, "#FF577D"},
45
45
+
{Salmon, "#FF7F90"},
46
46
+
{Chili, "#E23080"},
38
47
{Cherry, "#FF388B"},
48
48
+
{Tuna, "#FF6DAA"},
49
49
+
{Macaron, "#E940B0"},
50
50
+
{Pony, "#FF4FBF"},
51
51
+
{Cheeky, "#FF79D0"},
52
52
+
{Flamingo, "#F947E3"},
53
53
+
{Dolly, "#FF60FF"},
54
54
+
{Blush, "#FF84FF"},
55
55
+
{Urchin, "#C337E0"},
56
56
+
{Mochi, "#EB5DFF"},
57
57
+
{Lilac, "#F379FF"},
58
58
+
{Prince, "#9C35E1"},
59
59
+
{Violet, "#C259FF"},
60
60
+
{Mauve, "#D46EFF"},
61
61
+
{Grape, "#7134DD"},
62
62
+
{Plum, "#9953FF"},
63
63
+
{Orchid, "#AD6EFF"},
64
64
+
{Jelly, "#4A30D9"},
65
65
+
{Charple, "#6B50FF"},
66
66
+
{Hazy, "#8B75FF"},
67
67
+
{Ox, "#3331B2"},
68
68
+
{Sapphire, "#4949FF"},
69
69
+
{Guppy, "#7272FF"},
70
70
+
{Oceania, "#2B55B3"},
71
71
+
{Thunder, "#4776FF"},
72
72
+
{Anchovy, "#719AFC"},
73
73
+
{Damson, "#007AB8"},
74
74
+
{Malibu, "#00A4FF"},
75
75
+
{Sardine, "#4FBEFE"},
76
76
+
{Zinc, "#10B1AE"},
77
77
+
{Turtle, "#0ADCD9"},
78
78
+
{Lichen, "#5CDFEA"},
79
79
+
{Guac, "#12C78F"},
39
80
{Julep, "#00FFB2"},
81
81
+
{Bok, "#68FFD6"},
82
82
+
{Mustard, "#F5EF34"},
83
83
+
{Citron, "#E8FF27"},
84
84
+
{Zest, "#E8FE96"},
85
85
+
{Pepper, "#201F26"},
86
86
+
{BBQ, "#2d2c35"},
87
87
+
{Charcoal, "#3A3943"},
88
88
+
{Iron, "#4D4C57"},
89
89
+
{Oyster, "#605F6B"},
90
90
+
{Squid, "#858392"},
91
91
+
{Smoke, "#BFBCC8"},
92
92
+
{Ash, "#DFDBDD"},
93
93
+
{Salt, "#F1EFEF"},
40
94
{Butter, "#FFFAF1"},
95
95
+
// Diffs: additions
96
96
+
{Pickle, "#00A475"},
97
97
+
{Gator, "#18463D"},
98
98
+
{Spinach, "#1C3634"},
99
99
+
{Pom, "#AB2454"},
100
100
+
{Steak, "#582238"},
101
101
+
{Toast, "#412130"},
102
102
+
{NeueGuac, "#00b875"},
103
103
+
{NeueZinc, "#0e9996"},
41
104
{Key(-1), ""},
42
105
}
43
106
···
56
119
expected string
57
120
}{
58
121
{Cumin, "Cumin"},
122
122
+
{Tang, "Tang"},
123
123
+
{Yam, "Yam"},
124
124
+
{Paprika, "Paprika"},
125
125
+
{Bengal, "Bengal"},
126
126
+
{Uni, "Uni"},
127
127
+
{Sriracha, "Sriracha"},
128
128
+
{Coral, "Coral"},
129
129
+
{Salmon, "Salmon"},
130
130
+
{Chili, "Chili"},
59
131
{Cherry, "Cherry"},
132
132
+
{Tuna, "Tuna"},
133
133
+
{Macaron, "Macaron"},
134
134
+
{Pony, "Pony"},
135
135
+
{Cheeky, "Cheeky"},
136
136
+
{Flamingo, "Flamingo"},
137
137
+
{Dolly, "Dolly"},
138
138
+
{Blush, "Blush"},
139
139
+
{Urchin, "Urchin"},
140
140
+
{Mochi, "Mochi"},
141
141
+
{Lilac, "Lilac"},
142
142
+
{Prince, "Prince"},
143
143
+
{Violet, "Violet"},
144
144
+
{Mauve, "Mauve"},
145
145
+
{Grape, "Grape"},
146
146
+
{Plum, "Plum"},
147
147
+
{Orchid, "Orchid"},
148
148
+
{Jelly, "Jelly"},
149
149
+
{Charple, "Charple"},
150
150
+
{Hazy, "Hazy"},
151
151
+
{Ox, "Ox"},
152
152
+
{Sapphire, "Sapphire"},
153
153
+
{Guppy, "Guppy"},
154
154
+
{Oceania, "Oceania"},
155
155
+
{Thunder, "Thunder"},
156
156
+
{Anchovy, "Anchovy"},
157
157
+
{Damson, "Damson"},
158
158
+
{Malibu, "Malibu"},
159
159
+
{Sardine, "Sardine"},
160
160
+
{Zinc, "Zinc"},
161
161
+
{Turtle, "Turtle"},
162
162
+
{Lichen, "Lichen"},
163
163
+
{Guac, "Guac"},
164
164
+
{Julep, "Julep"},
165
165
+
{Bok, "Bok"},
166
166
+
{Mustard, "Mustard"},
167
167
+
{Citron, "Citron"},
168
168
+
{Zest, "Zest"},
169
169
+
{Pepper, "Pepper"},
170
170
+
{BBQ, "BBQ"},
171
171
+
{Charcoal, "Charcoal"},
172
172
+
{Iron, "Iron"},
173
173
+
{Oyster, "Oyster"},
174
174
+
{Squid, "Squid"},
175
175
+
{Smoke, "Smoke"},
176
176
+
{Ash, "Ash"},
177
177
+
{Salt, "Salt"},
178
178
+
{Butter, "Butter"},
179
179
+
{Pickle, "Pickle"},
180
180
+
{Gator, "Gator"},
181
181
+
{Spinach, "Spinach"},
182
182
+
{Pom, "Pom"},
183
183
+
{Steak, "Steak"},
184
184
+
{Toast, "Toast"},
185
185
+
{NeueGuac, "NeueGuac"},
186
186
+
{NeueZinc, "NeueZinc"},
60
187
{Key(-1), "Key(-1)"},
61
188
}
62
189