bluesky viewer in the terminal
at main 21 kB view raw
1package export 2 3import ( 4 "encoding/csv" 5 "encoding/json" 6 "os" 7 "path/filepath" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/stormlightlabs/skypanel/cli/internal/store" 13) 14 15// createTestPosts generates sample posts for testing 16func createTestPosts() []*store.PostModel { 17 now := time.Now() 18 posts := []*store.PostModel{ 19 { 20 URI: "at://did:plc:test1/app.bsky.feed.post/1", 21 AuthorDID: "did:plc:author1", 22 Text: "First test post", 23 FeedID: "feed-1", 24 IndexedAt: now.Add(-2 * time.Hour), 25 }, 26 { 27 URI: "at://did:plc:test2/app.bsky.feed.post/2", 28 AuthorDID: "did:plc:author2", 29 Text: "Second test post", 30 FeedID: "feed-1", 31 IndexedAt: now.Add(-1 * time.Hour), 32 }, 33 { 34 URI: "at://did:plc:test3/app.bsky.feed.post/3", 35 AuthorDID: "did:plc:author3", 36 Text: "Third test post with special chars: \"quotes\", commas, and\nnewlines", 37 FeedID: "feed-2", 38 IndexedAt: now, 39 }, 40 } 41 42 for i, post := range posts { 43 post.SetID(string(rune('a' + i))) 44 post.SetCreatedAt(now.Add(time.Duration(-i) * time.Hour)) 45 post.SetUpdatedAt(now) 46 } 47 48 return posts 49} 50 51// TestToJSON_Success verifies JSON export with valid data 52func TestToJSON_Success(t *testing.T) { 53 posts := createTestPosts() 54 55 tmpDir := t.TempDir() 56 filename := filepath.Join(tmpDir, "test.json") 57 58 err := ToJSON(filename, posts) 59 if err != nil { 60 t.Fatalf("ToJSON failed: %v", err) 61 } 62 63 if _, err := os.Stat(filename); os.IsNotExist(err) { 64 t.Fatal("exported file does not exist") 65 } 66 67 data, err := os.ReadFile(filename) 68 if err != nil { 69 t.Fatalf("failed to read exported file: %v", err) 70 } 71 72 var exportedPosts []ExportPost 73 if err := json.Unmarshal(data, &exportedPosts); err != nil { 74 t.Fatalf("failed to parse JSON: %v", err) 75 } 76 77 if len(exportedPosts) != 3 { 78 t.Errorf("expected 3 posts, got %d", len(exportedPosts)) 79 } 80 81 if exportedPosts[0].URI != "at://did:plc:test1/app.bsky.feed.post/1" { 82 t.Errorf("unexpected URI: %s", exportedPosts[0].URI) 83 } 84 if exportedPosts[0].Text != "First test post" { 85 t.Errorf("unexpected text: %s", exportedPosts[0].Text) 86 } 87} 88 89// TestToJSON_EmptyPosts verifies JSON export with empty slice 90func TestToJSON_EmptyPosts(t *testing.T) { 91 tmpDir := t.TempDir() 92 filename := filepath.Join(tmpDir, "empty.json") 93 94 err := ToJSON(filename, []*store.PostModel{}) 95 if err != nil { 96 t.Fatalf("ToJSON failed: %v", err) 97 } 98 99 data, err := os.ReadFile(filename) 100 if err != nil { 101 t.Fatalf("failed to read exported file: %v", err) 102 } 103 104 var exportedPosts []ExportPost 105 if err := json.Unmarshal(data, &exportedPosts); err != nil { 106 t.Fatalf("failed to parse JSON: %v", err) 107 } 108 109 if len(exportedPosts) != 0 { 110 t.Errorf("expected 0 posts, got %d", len(exportedPosts)) 111 } 112} 113 114// TestToJSON_InvalidPath verifies error handling for invalid file paths 115func TestToJSON_InvalidPath(t *testing.T) { 116 posts := createTestPosts() 117 118 err := ToJSON("/invalid/path/that/does/not/exist/test.json", posts) 119 if err == nil { 120 t.Error("expected error for invalid path, got nil") 121 } 122} 123 124// TestToCSV_Success verifies CSV export with valid data 125func TestToCSV_Success(t *testing.T) { 126 posts := createTestPosts() 127 128 tmpDir := t.TempDir() 129 filename := filepath.Join(tmpDir, "test.csv") 130 131 err := ToCSV(filename, posts) 132 if err != nil { 133 t.Fatalf("ToCSV failed: %v", err) 134 } 135 136 if _, err := os.Stat(filename); os.IsNotExist(err) { 137 t.Fatal("exported file does not exist") 138 } 139 140 file, err := os.Open(filename) 141 if err != nil { 142 t.Fatalf("failed to open exported file: %v", err) 143 } 144 defer file.Close() 145 146 reader := csv.NewReader(file) 147 records, err := reader.ReadAll() 148 if err != nil { 149 t.Fatalf("failed to parse CSV: %v", err) 150 } 151 152 if len(records) != 4 { 153 t.Errorf("expected 4 rows (header + 3 data), got %d", len(records)) 154 } 155 156 expectedHeader := []string{"ID", "URI", "AuthorDID", "Text", "FeedID", "IndexedAt", "CreatedAt"} 157 for i, col := range expectedHeader { 158 if records[0][i] != col { 159 t.Errorf("header column %d: expected %s, got %s", i, col, records[0][i]) 160 } 161 } 162 163 if records[1][1] != "at://did:plc:test1/app.bsky.feed.post/1" { 164 t.Errorf("unexpected URI in row 1: %s", records[1][1]) 165 } 166 if records[1][3] != "First test post" { 167 t.Errorf("unexpected text in row 1: %s", records[1][3]) 168 } 169} 170 171// TestToCSV_SpecialCharacters verifies CSV escaping of special characters 172func TestToCSV_SpecialCharacters(t *testing.T) { 173 posts := createTestPosts() 174 175 tmpDir := t.TempDir() 176 filename := filepath.Join(tmpDir, "special.csv") 177 178 err := ToCSV(filename, posts) 179 if err != nil { 180 t.Fatalf("ToCSV failed: %v", err) 181 } 182 183 file, err := os.Open(filename) 184 if err != nil { 185 t.Fatalf("failed to open exported file: %v", err) 186 } 187 defer file.Close() 188 189 reader := csv.NewReader(file) 190 records, err := reader.ReadAll() 191 if err != nil { 192 t.Fatalf("failed to parse CSV: %v", err) 193 } 194 195 specialText := records[3][3] 196 if !strings.Contains(specialText, "quotes") { 197 t.Error("special characters not properly preserved in CSV") 198 } 199 if !strings.Contains(specialText, "newlines") { 200 t.Error("newlines not properly preserved in CSV") 201 } 202} 203 204// TestToCSV_EmptyPosts verifies CSV export with empty slice 205func TestToCSV_EmptyPosts(t *testing.T) { 206 tmpDir := t.TempDir() 207 filename := filepath.Join(tmpDir, "empty.csv") 208 209 err := ToCSV(filename, []*store.PostModel{}) 210 if err != nil { 211 t.Fatalf("ToCSV failed: %v", err) 212 } 213 214 file, err := os.Open(filename) 215 if err != nil { 216 t.Fatalf("failed to open exported file: %v", err) 217 } 218 defer file.Close() 219 220 reader := csv.NewReader(file) 221 records, err := reader.ReadAll() 222 if err != nil { 223 t.Fatalf("failed to parse CSV: %v", err) 224 } 225 226 if len(records) != 1 { 227 t.Errorf("expected 1 row (header only), got %d", len(records)) 228 } 229} 230 231// TestToCSV_InvalidPath verifies error handling for invalid file paths 232func TestToCSV_InvalidPath(t *testing.T) { 233 posts := createTestPosts() 234 235 err := ToCSV("/invalid/path/that/does/not/exist/test.csv", posts) 236 if err == nil { 237 t.Error("expected error for invalid path, got nil") 238 } 239} 240 241// TestToTXT_Success verifies TXT export with valid data 242func TestToTXT_Success(t *testing.T) { 243 posts := createTestPosts() 244 245 tmpDir := t.TempDir() 246 filename := filepath.Join(tmpDir, "test.txt") 247 248 err := ToTXT(filename, posts) 249 if err != nil { 250 t.Fatalf("ToTXT failed: %v", err) 251 } 252 253 if _, err := os.Stat(filename); os.IsNotExist(err) { 254 t.Fatal("exported file does not exist") 255 } 256 257 data, err := os.ReadFile(filename) 258 if err != nil { 259 t.Fatalf("failed to read exported file: %v", err) 260 } 261 262 content := string(data) 263 264 if !strings.Contains(content, "Post #1") { 265 t.Error("missing post number") 266 } 267 if !strings.Contains(content, "at://did:plc:test1/app.bsky.feed.post/1") { 268 t.Error("missing URI") 269 } 270 if !strings.Contains(content, "First test post") { 271 t.Error("missing post text") 272 } 273 if !strings.Contains(content, "did:plc:author1") { 274 t.Error("missing author DID") 275 } 276 if !strings.Contains(content, strings.Repeat("-", 80)) { 277 t.Error("missing separator") 278 } 279} 280 281// TestToTXT_EmptyPosts verifies TXT export with empty slice 282func TestToTXT_EmptyPosts(t *testing.T) { 283 tmpDir := t.TempDir() 284 filename := filepath.Join(tmpDir, "empty.txt") 285 286 err := ToTXT(filename, []*store.PostModel{}) 287 if err != nil { 288 t.Fatalf("ToTXT failed: %v", err) 289 } 290 291 data, err := os.ReadFile(filename) 292 if err != nil { 293 t.Fatalf("failed to read exported file: %v", err) 294 } 295 296 if len(data) != 0 { 297 t.Errorf("expected empty file, got %d bytes", len(data)) 298 } 299} 300 301// TestToTXT_InvalidPath verifies error handling for invalid file paths 302func TestToTXT_InvalidPath(t *testing.T) { 303 posts := createTestPosts() 304 305 err := ToTXT("/invalid/path/that/does/not/exist/test.txt", posts) 306 if err == nil { 307 t.Error("expected error for invalid path, got nil") 308 } 309} 310 311// TestToTXT_MultiplePostsFormatting verifies proper formatting of multiple posts 312func TestToTXT_MultiplePostsFormatting(t *testing.T) { 313 posts := createTestPosts() 314 315 tmpDir := t.TempDir() 316 filename := filepath.Join(tmpDir, "multiple.txt") 317 318 err := ToTXT(filename, posts) 319 if err != nil { 320 t.Fatalf("ToTXT failed: %v", err) 321 } 322 323 data, err := os.ReadFile(filename) 324 if err != nil { 325 t.Fatalf("failed to read exported file: %v", err) 326 } 327 328 content := string(data) 329 330 for i := 1; i <= 3; i++ { 331 postNum := "Post #" + string(rune('0'+i)) 332 if !strings.Contains(content, postNum) { 333 t.Errorf("missing %s", postNum) 334 } 335 } 336 337 separatorCount := strings.Count(content, strings.Repeat("-", 80)) 338 if separatorCount != 3 { 339 t.Errorf("expected 3 separators, got %d", separatorCount) 340 } 341} 342 343// TestConvertPosts verifies post model conversion 344func TestConvertPosts(t *testing.T) { 345 posts := createTestPosts() 346 347 exportPosts := convertPosts(posts) 348 349 if len(exportPosts) != len(posts) { 350 t.Errorf("expected %d posts, got %d", len(posts), len(exportPosts)) 351 } 352 353 for i := range posts { 354 if exportPosts[i].ID != posts[i].ID() { 355 t.Errorf("post %d: ID mismatch", i) 356 } 357 if exportPosts[i].URI != posts[i].URI { 358 t.Errorf("post %d: URI mismatch", i) 359 } 360 if exportPosts[i].Text != posts[i].Text { 361 t.Errorf("post %d: Text mismatch", i) 362 } 363 if exportPosts[i].AuthorDID != posts[i].AuthorDID { 364 t.Errorf("post %d: AuthorDID mismatch", i) 365 } 366 if exportPosts[i].FeedID != posts[i].FeedID { 367 t.Errorf("post %d: FeedID mismatch", i) 368 } 369 } 370} 371 372// TestExportPost_JSONTags verifies JSON struct tags 373func TestExportPost_JSONTags(t *testing.T) { 374 now := time.Now() 375 post := ExportPost{ 376 ID: "test-id", 377 URI: "at://test/uri", 378 AuthorDID: "did:plc:test", 379 Text: "test text", 380 FeedID: "feed-id", 381 IndexedAt: now, 382 CreatedAt: now, 383 } 384 385 data, err := json.Marshal(post) 386 if err != nil { 387 t.Fatalf("failed to marshal: %v", err) 388 } 389 390 content := string(data) 391 392 expectedFields := []string{ 393 "\"id\":", 394 "\"uri\":", 395 "\"author_did\":", 396 "\"text\":", 397 "\"feed_id\":", 398 "\"indexed_at\":", 399 "\"created_at\":", 400 } 401 402 for _, field := range expectedFields { 403 if !strings.Contains(content, field) { 404 t.Errorf("missing expected JSON field: %s", field) 405 } 406 } 407} 408 409// TestToJSON_SinglePost verifies export with single post 410func TestToJSON_SinglePost(t *testing.T) { 411 now := time.Now() 412 post := &store.PostModel{ 413 URI: "at://test/single", 414 AuthorDID: "did:plc:single", 415 Text: "Single post", 416 FeedID: "feed-1", 417 IndexedAt: now, 418 } 419 post.SetID("single-id") 420 post.SetCreatedAt(now) 421 422 tmpDir := t.TempDir() 423 filename := filepath.Join(tmpDir, "single.json") 424 425 err := ToJSON(filename, []*store.PostModel{post}) 426 if err != nil { 427 t.Fatalf("ToJSON failed: %v", err) 428 } 429 430 data, err := os.ReadFile(filename) 431 if err != nil { 432 t.Fatalf("failed to read file: %v", err) 433 } 434 435 var exportedPosts []ExportPost 436 if err := json.Unmarshal(data, &exportedPosts); err != nil { 437 t.Fatalf("failed to parse JSON: %v", err) 438 } 439 440 if len(exportedPosts) != 1 { 441 t.Errorf("expected 1 post, got %d", len(exportedPosts)) 442 } 443 444 if exportedPosts[0].Text != "Single post" { 445 t.Errorf("unexpected text: %s", exportedPosts[0].Text) 446 } 447} 448 449// createTestProfile generates a sample profile for testing 450func createTestProfile() *store.ActorProfile { 451 return &store.ActorProfile{ 452 Did: "did:plc:test123", 453 Handle: "testuser.bsky.social", 454 DisplayName: "Test User", 455 Description: "This is a test profile description", 456 FollowersCount: 100, 457 FollowsCount: 50, 458 PostsCount: 25, 459 CreatedAt: "2024-01-01T00:00:00Z", 460 } 461} 462 463// TestProfileToJSON_Success verifies profile JSON export with valid data 464func TestProfileToJSON_Success(t *testing.T) { 465 profile := createTestProfile() 466 467 tmpDir := t.TempDir() 468 filename := filepath.Join(tmpDir, "profile.json") 469 470 err := ProfileToJSON(filename, profile) 471 if err != nil { 472 t.Fatalf("ProfileToJSON failed: %v", err) 473 } 474 475 if _, err := os.Stat(filename); os.IsNotExist(err) { 476 t.Fatal("exported file does not exist") 477 } 478 479 data, err := os.ReadFile(filename) 480 if err != nil { 481 t.Fatalf("failed to read exported file: %v", err) 482 } 483 484 var exportedProfile store.ActorProfile 485 if err := json.Unmarshal(data, &exportedProfile); err != nil { 486 t.Fatalf("failed to parse JSON: %v", err) 487 } 488 489 if exportedProfile.Did != profile.Did { 490 t.Errorf("expected DID %s, got %s", profile.Did, exportedProfile.Did) 491 } 492 if exportedProfile.Handle != profile.Handle { 493 t.Errorf("expected handle %s, got %s", profile.Handle, exportedProfile.Handle) 494 } 495 if exportedProfile.DisplayName != profile.DisplayName { 496 t.Errorf("expected display name %s, got %s", profile.DisplayName, exportedProfile.DisplayName) 497 } 498 if exportedProfile.FollowersCount != profile.FollowersCount { 499 t.Errorf("expected followers %d, got %d", profile.FollowersCount, exportedProfile.FollowersCount) 500 } 501} 502 503// TestProfileToJSON_InvalidPath verifies error handling for invalid file paths 504func TestProfileToJSON_InvalidPath(t *testing.T) { 505 profile := createTestProfile() 506 507 err := ProfileToJSON("/invalid/path/that/does/not/exist/profile.json", profile) 508 if err == nil { 509 t.Error("expected error for invalid path, got nil") 510 } 511} 512 513// TestProfileToTXT_Success verifies profile TXT export with valid data 514func TestProfileToTXT_Success(t *testing.T) { 515 profile := createTestProfile() 516 517 tmpDir := t.TempDir() 518 filename := filepath.Join(tmpDir, "profile.txt") 519 520 err := ProfileToTXT(filename, profile) 521 if err != nil { 522 t.Fatalf("ProfileToTXT failed: %v", err) 523 } 524 525 if _, err := os.Stat(filename); os.IsNotExist(err) { 526 t.Fatal("exported file does not exist") 527 } 528 529 data, err := os.ReadFile(filename) 530 if err != nil { 531 t.Fatalf("failed to read exported file: %v", err) 532 } 533 534 content := string(data) 535 536 if !strings.Contains(content, "@"+profile.Handle) { 537 t.Error("missing handle") 538 } 539 if !strings.Contains(content, profile.DisplayName) { 540 t.Error("missing display name") 541 } 542 if !strings.Contains(content, profile.Did) { 543 t.Error("missing DID") 544 } 545 if !strings.Contains(content, profile.Description) { 546 t.Error("missing description") 547 } 548 if !strings.Contains(content, "Followers: 100") { 549 t.Error("missing followers count") 550 } 551 if !strings.Contains(content, "Following: 50") { 552 t.Error("missing following count") 553 } 554 if !strings.Contains(content, "Posts: 25") { 555 t.Error("missing posts count") 556 } 557 if !strings.Contains(content, strings.Repeat("=", 80)) { 558 t.Error("missing separator") 559 } 560} 561 562// TestProfileToTXT_MinimalProfile verifies TXT export with minimal profile data 563func TestProfileToTXT_MinimalProfile(t *testing.T) { 564 profile := &store.ActorProfile{ 565 Did: "did:plc:minimal", 566 Handle: "minimal.bsky.social", 567 } 568 569 tmpDir := t.TempDir() 570 filename := filepath.Join(tmpDir, "minimal.txt") 571 572 err := ProfileToTXT(filename, profile) 573 if err != nil { 574 t.Fatalf("ProfileToTXT failed: %v", err) 575 } 576 577 data, err := os.ReadFile(filename) 578 if err != nil { 579 t.Fatalf("failed to read exported file: %v", err) 580 } 581 582 content := string(data) 583 584 if !strings.Contains(content, "did:plc:minimal") { 585 t.Error("missing DID") 586 } 587 if !strings.Contains(content, "@minimal.bsky.social") { 588 t.Error("missing handle") 589 } 590 591 if strings.Contains(content, "Display Name:") && profile.DisplayName == "" { 592 t.Error("should not show empty display name label") 593 } 594} 595 596// TestProfileToTXT_InvalidPath verifies error handling for invalid file paths 597func TestProfileToTXT_InvalidPath(t *testing.T) { 598 profile := createTestProfile() 599 600 err := ProfileToTXT("/invalid/path/that/does/not/exist/profile.txt", profile) 601 if err == nil { 602 t.Error("expected error for invalid path, got nil") 603 } 604} 605 606// createTestFeedViewPost generates a sample FeedViewPost for testing 607func createTestFeedViewPost() *store.FeedViewPost { 608 return &store.FeedViewPost{ 609 Post: &store.PostView{ 610 Uri: "at://did:plc:test123/app.bsky.feed.post/abc123", 611 Cid: "bafyreic3test", 612 Author: &store.ActorProfile{ 613 Did: "did:plc:author123", 614 Handle: "testauthor.bsky.social", 615 DisplayName: "Test Author", 616 }, 617 Record: map[string]any{ 618 "text": "This is a test post with some content", 619 "createdAt": "2024-01-01T12:00:00Z", 620 }, 621 LikeCount: 42, 622 RepostCount: 10, 623 ReplyCount: 5, 624 QuoteCount: 2, 625 IndexedAt: "2024-01-01T12:00:00Z", 626 }, 627 } 628} 629 630// TestFeedViewPostToJSON_Success verifies FeedViewPost JSON export with valid data 631func TestFeedViewPostToJSON_Success(t *testing.T) { 632 post := createTestFeedViewPost() 633 634 tmpDir := t.TempDir() 635 filename := filepath.Join(tmpDir, "post.json") 636 637 err := FeedViewPostToJSON(filename, post) 638 if err != nil { 639 t.Fatalf("FeedViewPostToJSON failed: %v", err) 640 } 641 642 if _, err := os.Stat(filename); os.IsNotExist(err) { 643 t.Fatal("exported file does not exist") 644 } 645 646 data, err := os.ReadFile(filename) 647 if err != nil { 648 t.Fatalf("failed to read exported file: %v", err) 649 } 650 651 var exportedPost store.FeedViewPost 652 if err := json.Unmarshal(data, &exportedPost); err != nil { 653 t.Fatalf("failed to parse JSON: %v", err) 654 } 655 656 if exportedPost.Post.Uri != post.Post.Uri { 657 t.Errorf("expected URI %s, got %s", post.Post.Uri, exportedPost.Post.Uri) 658 } 659 if exportedPost.Post.Author.Handle != post.Post.Author.Handle { 660 t.Errorf("expected handle %s, got %s", post.Post.Author.Handle, exportedPost.Post.Author.Handle) 661 } 662 if exportedPost.Post.LikeCount != post.Post.LikeCount { 663 t.Errorf("expected like count %d, got %d", post.Post.LikeCount, exportedPost.Post.LikeCount) 664 } 665} 666 667// TestFeedViewPostToJSON_InvalidPath verifies error handling for invalid file paths 668func TestFeedViewPostToJSON_InvalidPath(t *testing.T) { 669 post := createTestFeedViewPost() 670 671 err := FeedViewPostToJSON("/invalid/path/that/does/not/exist/post.json", post) 672 if err == nil { 673 t.Error("expected error for invalid path, got nil") 674 } 675} 676 677// TestFeedViewPostToTXT_Success verifies FeedViewPost TXT export with valid data 678func TestFeedViewPostToTXT_Success(t *testing.T) { 679 post := createTestFeedViewPost() 680 681 tmpDir := t.TempDir() 682 filename := filepath.Join(tmpDir, "post.txt") 683 684 err := FeedViewPostToTXT(filename, post) 685 if err != nil { 686 t.Fatalf("FeedViewPostToTXT failed: %v", err) 687 } 688 689 if _, err := os.Stat(filename); os.IsNotExist(err) { 690 t.Fatal("exported file does not exist") 691 } 692 693 data, err := os.ReadFile(filename) 694 if err != nil { 695 t.Fatalf("failed to read exported file: %v", err) 696 } 697 698 content := string(data) 699 700 if !strings.Contains(content, "@"+post.Post.Author.Handle) { 701 t.Error("missing author handle") 702 } 703 if !strings.Contains(content, post.Post.Author.DisplayName) { 704 t.Error("missing author display name") 705 } 706 if !strings.Contains(content, post.Post.Uri) { 707 t.Error("missing post URI") 708 } 709 if !strings.Contains(content, post.Post.Cid) { 710 t.Error("missing post CID") 711 } 712 if !strings.Contains(content, "This is a test post") { 713 t.Error("missing post text") 714 } 715 if !strings.Contains(content, "Likes: 42") { 716 t.Error("missing like count") 717 } 718 if !strings.Contains(content, "Reposts: 10") { 719 t.Error("missing repost count") 720 } 721 if !strings.Contains(content, "Replies: 5") { 722 t.Error("missing reply count") 723 } 724 if !strings.Contains(content, "Quotes: 2") { 725 t.Error("missing quote count") 726 } 727 if !strings.Contains(content, strings.Repeat("=", 80)) { 728 t.Error("missing separator") 729 } 730} 731 732// TestFeedViewPostToTXT_WithRepost verifies TXT export with repost reason 733func TestFeedViewPostToTXT_WithRepost(t *testing.T) { 734 post := createTestFeedViewPost() 735 post.Reason = &store.ReasonView{ 736 Type: "app.bsky.feed.defs#reasonRepost", 737 By: &store.ActorProfile{ 738 Did: "did:plc:reposter", 739 Handle: "reposter.bsky.social", 740 DisplayName: "Reposter", 741 }, 742 IndexedAt: "2024-01-01T12:00:00Z", 743 } 744 745 tmpDir := t.TempDir() 746 filename := filepath.Join(tmpDir, "repost.txt") 747 748 err := FeedViewPostToTXT(filename, post) 749 if err != nil { 750 t.Fatalf("FeedViewPostToTXT failed: %v", err) 751 } 752 753 data, err := os.ReadFile(filename) 754 if err != nil { 755 t.Fatalf("failed to read exported file: %v", err) 756 } 757 758 content := string(data) 759 760 if !strings.Contains(content, "Reposted by: @reposter.bsky.social") { 761 t.Error("missing repost information") 762 } 763} 764 765// TestFeedViewPostToTXT_MinimalPost verifies TXT export with minimal post data 766func TestFeedViewPostToTXT_MinimalPost(t *testing.T) { 767 post := &store.FeedViewPost{ 768 Post: &store.PostView{ 769 Uri: "at://did:plc:minimal/app.bsky.feed.post/xyz", 770 Cid: "bafyreicminimal", 771 Author: &store.ActorProfile{ 772 Did: "did:plc:minimal", 773 Handle: "minimal.bsky.social", 774 }, 775 Record: map[string]any{}, 776 IndexedAt: "2024-01-01T00:00:00Z", 777 }, 778 } 779 780 tmpDir := t.TempDir() 781 filename := filepath.Join(tmpDir, "minimal.txt") 782 783 err := FeedViewPostToTXT(filename, post) 784 if err != nil { 785 t.Fatalf("FeedViewPostToTXT failed: %v", err) 786 } 787 788 data, err := os.ReadFile(filename) 789 if err != nil { 790 t.Fatalf("failed to read exported file: %v", err) 791 } 792 793 content := string(data) 794 795 if !strings.Contains(content, "@minimal.bsky.social") { 796 t.Error("missing handle") 797 } 798 if !strings.Contains(content, "at://did:plc:minimal") { 799 t.Error("missing URI") 800 } 801} 802 803// TestFeedViewPostToTXT_InvalidPath verifies error handling for invalid file paths 804func TestFeedViewPostToTXT_InvalidPath(t *testing.T) { 805 post := createTestFeedViewPost() 806 807 err := FeedViewPostToTXT("/invalid/path/that/does/not/exist/post.txt", post) 808 if err == nil { 809 t.Error("expected error for invalid path, got nil") 810 } 811}