cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm leaflet readability golang
29
fork

Configure Feed

Select the types of activity you want to include in your feed.

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