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