|
4 | 4 | "context" |
5 | 5 | "net/http" |
6 | 6 | "net/http/httptest" |
| 7 | + "strings" |
7 | 8 | "testing" |
8 | 9 | ) |
9 | 10 |
|
@@ -468,3 +469,149 @@ func TestWithOptions(t *testing.T) { |
468 | 469 | } |
469 | 470 | }) |
470 | 471 | } |
| 472 | + |
| 473 | +func TestParseProfileFromHTML(t *testing.T) { |
| 474 | + ctx := context.Background() |
| 475 | + client, err := New(ctx) |
| 476 | + if err != nil { |
| 477 | + t.Fatalf("New() error = %v", err) |
| 478 | + } |
| 479 | + |
| 480 | + // Sample HTML based on github.com/tstromberg profile structure |
| 481 | + sampleHTML := ` |
| 482 | +<html> |
| 483 | +<head><title>tstromberg</title></head> |
| 484 | +<body> |
| 485 | +<img style="height:auto;" src="https://avatars.githubusercontent.com/u/101424?v=4" width="260" height="260" class="avatar avatar-user width-full border color-bg-default" /> |
| 486 | +<span class="p-name vcard-fullname d-block overflow-hidden" itemprop="name"> |
| 487 | +Thomas Stromberg |
| 488 | +</span> |
| 489 | +<span class="p-nickname vcard-username d-block" itemprop="additionalName">tstromberg</span> |
| 490 | +<div class="p-note user-profile-bio mb-3 js-user-profile-bio f4" data-bio-text="CEO @ codeGROOVE"> |
| 491 | +CEO @ codeGROOVE |
| 492 | +</div> |
| 493 | +<li class="vcard-detail pt-1" itemprop="homeLocation" aria-label="Home location: McMurdo Station, Antarctica"> |
| 494 | +<svg class="octicon octicon-location"></svg> |
| 495 | +<span>McMurdo Station, Antarctica</span> |
| 496 | +</li> |
| 497 | +<li itemprop="url" data-test-selector="profile-website-url" class="vcard-detail pt-1"> |
| 498 | +<svg class="octicon octicon-link"></svg> |
| 499 | +<a rel="nofollow me" class="Link--primary" href="http://localhost:8080/">http://localhost:8080/</a> |
| 500 | +</li> |
| 501 | +</body> |
| 502 | +</html> |
| 503 | +` |
| 504 | + |
| 505 | + tests := []struct { |
| 506 | + name string |
| 507 | + html string |
| 508 | + urlStr string |
| 509 | + username string |
| 510 | + wantName string |
| 511 | + wantBio string |
| 512 | + wantLoc string |
| 513 | + wantWeb string |
| 514 | + }{ |
| 515 | + { |
| 516 | + name: "full_profile", |
| 517 | + html: sampleHTML, |
| 518 | + urlStr: "https://github.com/tstromberg", |
| 519 | + username: "tstromberg", |
| 520 | + wantName: "Thomas Stromberg", |
| 521 | + wantBio: "CEO @ codeGROOVE", |
| 522 | + wantLoc: "McMurdo Station, Antarctica", |
| 523 | + wantWeb: "http://localhost:8080/", |
| 524 | + }, |
| 525 | + { |
| 526 | + name: "minimal_profile", |
| 527 | + html: ` |
| 528 | +<span class="p-name vcard-fullname" itemprop="name">Jane Doe</span> |
| 529 | +<div data-bio-text="Developer">Developer</div> |
| 530 | +`, |
| 531 | + urlStr: "https://github.com/janedoe", |
| 532 | + username: "janedoe", |
| 533 | + wantName: "Jane Doe", |
| 534 | + wantBio: "Developer", |
| 535 | + wantLoc: "", |
| 536 | + wantWeb: "", |
| 537 | + }, |
| 538 | + { |
| 539 | + name: "empty_html", |
| 540 | + html: "<html><body></body></html>", |
| 541 | + urlStr: "https://github.com/nobody", |
| 542 | + username: "nobody", |
| 543 | + wantName: "", |
| 544 | + wantBio: "", |
| 545 | + wantLoc: "", |
| 546 | + wantWeb: "", |
| 547 | + }, |
| 548 | + { |
| 549 | + name: "website_without_protocol", |
| 550 | + html: ` |
| 551 | +<li itemprop="url" data-test-selector="profile-website-url"> |
| 552 | +<a href="example.com">example.com</a> |
| 553 | +</li> |
| 554 | +`, |
| 555 | + urlStr: "https://github.com/user", |
| 556 | + username: "user", |
| 557 | + wantName: "", |
| 558 | + wantBio: "", |
| 559 | + wantLoc: "", |
| 560 | + wantWeb: "https://example.com", |
| 561 | + }, |
| 562 | + } |
| 563 | + |
| 564 | + for _, tt := range tests { |
| 565 | + t.Run(tt.name, func(t *testing.T) { |
| 566 | + prof := client.parseProfileFromHTML(ctx, tt.html, tt.urlStr, tt.username) |
| 567 | + |
| 568 | + if prof.Platform != "github" { |
| 569 | + t.Errorf("Platform = %q, want %q", prof.Platform, "github") |
| 570 | + } |
| 571 | + if prof.URL != tt.urlStr { |
| 572 | + t.Errorf("URL = %q, want %q", prof.URL, tt.urlStr) |
| 573 | + } |
| 574 | + if prof.Username != tt.username { |
| 575 | + t.Errorf("Username = %q, want %q", prof.Username, tt.username) |
| 576 | + } |
| 577 | + if prof.Name != tt.wantName { |
| 578 | + t.Errorf("Name = %q, want %q", prof.Name, tt.wantName) |
| 579 | + } |
| 580 | + if prof.Bio != tt.wantBio { |
| 581 | + t.Errorf("Bio = %q, want %q", prof.Bio, tt.wantBio) |
| 582 | + } |
| 583 | + if prof.Location != tt.wantLoc { |
| 584 | + t.Errorf("Location = %q, want %q", prof.Location, tt.wantLoc) |
| 585 | + } |
| 586 | + if prof.Website != tt.wantWeb { |
| 587 | + t.Errorf("Website = %q, want %q", prof.Website, tt.wantWeb) |
| 588 | + } |
| 589 | + }) |
| 590 | + } |
| 591 | +} |
| 592 | + |
| 593 | +func TestAPIError(t *testing.T) { |
| 594 | + t.Run("rate_limit_error", func(t *testing.T) { |
| 595 | + err := &APIError{ |
| 596 | + StatusCode: 403, |
| 597 | + IsRateLimit: true, |
| 598 | + Message: "rate limit exceeded", |
| 599 | + } |
| 600 | + errStr := err.Error() |
| 601 | + if !strings.Contains(errStr, "rate limited") { |
| 602 | + t.Errorf("Error() = %q, want to contain 'rate limited'", errStr) |
| 603 | + } |
| 604 | + }) |
| 605 | + |
| 606 | + t.Run("other_error", func(t *testing.T) { |
| 607 | + err := &APIError{ |
| 608 | + StatusCode: 401, |
| 609 | + IsRateLimit: false, |
| 610 | + Message: "bad credentials", |
| 611 | + } |
| 612 | + errStr := err.Error() |
| 613 | + if !strings.Contains(errStr, "401") { |
| 614 | + t.Errorf("Error() = %q, want to contain '401'", errStr) |
| 615 | + } |
| 616 | + }) |
| 617 | +} |
0 commit comments