Re-captures testdata/html/*.html against the live site with valid session cookies; the previous user.html was the logged-out interstitial, which broke TestParseUser_RealFixture entirely. Bumps the expected Stats.Views in that test to match the new fixture.
244 lines
8.9 KiB
Go
244 lines
8.9 KiB
Go
package fa
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/PuerkitoBio/goquery"
|
|
)
|
|
|
|
// syntheticUserHTML mirrors the real /user/{name}/ markup closely enough to
|
|
// catch the bugs the fictitious old fixture hid: a logged-in viewer's avatar
|
|
// in the site nav (which must NOT be picked as the profile avatar), the
|
|
// `<span class="highlight">Label:</span> value` stats strip, and the
|
|
// watcher/watching counts that live in the section headers rather than the
|
|
// stats box.
|
|
const syntheticUserHTML = `<html><body>
|
|
<!-- site navigation: this is the logged-in VIEWER, not the profile owner -->
|
|
<img class="loggedin_user_avatar avatar" alt="Viewer" src="//a.furaffinity.net/0/viewer.gif"/>
|
|
|
|
<userpage-nav-header>
|
|
<userpage-nav-avatar>
|
|
<a class="current" href="/user/somefurry/"><img alt="somefurry" src="//a.furaffinity.net/123/somefurry.gif"/></a>
|
|
</userpage-nav-avatar>
|
|
<userpage-nav-user-details>
|
|
<div class="top-bar"><username>
|
|
<div class="c-usernameBlock username-in-nav-bar">
|
|
<a class="c-usernameBlock__displayName js-displayName-block" href="/user/somefurry/">
|
|
<span class="js-displayName">SomeFurry</span>
|
|
</a>
|
|
</div>
|
|
</username></div>
|
|
<div class="font-small"><span class="user-title">
|
|
Artist | <span class="hideonmobile">Registered:</span>
|
|
<span class="popup_date" data-time="1519896600" title="Mar 1, 2018 09:30 AM">8 years ago</span>
|
|
</span></div>
|
|
</userpage-nav-user-details>
|
|
<userpage-nav-interface-buttons>
|
|
<a class="button standard samewidth stop" id="watch-button" href="/unwatch/somefurry/?key=abc">Unwatch</a>
|
|
</userpage-nav-interface-buttons>
|
|
</userpage-nav-header>
|
|
|
|
<div class="userpage-profile"><p>Welcome to my profile.</p></div>
|
|
|
|
<section class="userpage-right-column">
|
|
<div class="userpage-section-right">
|
|
<div class="section-header"><h2>Stats</h2></div>
|
|
<div class="section-body"><div class="table">
|
|
<div class="cell">
|
|
<span class="highlight">Views:</span> 1,176 <br/>
|
|
<span class="highlight">Submissions:</span> 1,234<br/>
|
|
<span class="highlight">Favs:</span> 567
|
|
</div>
|
|
<div class="cell">
|
|
<span class="highlight">Comments Earned:</span> 85<br/>
|
|
<span class="highlight">Comments Made:</span> 83<br/>
|
|
<span class="highlight">Journals:</span> 12
|
|
</div>
|
|
</div></div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="userpage-left-column watched-by-block">
|
|
<div class="userpage-section-left"><div class="section-header">
|
|
<div class="floatright"><h3><a href="/watchlist/to/somefurry/">View List (Watched by 89)</a></h3></div>
|
|
<h2>Recent Watchers</h2>
|
|
</div></div>
|
|
</section>
|
|
<section class="userpage-left-column is-watching-block">
|
|
<div class="userpage-section-left"><div class="section-header">
|
|
<div class="floatright"><h3><a href="/watchlist/by/somefurry/">View List (Watching 10)</a></h3></div>
|
|
<h2>Recently Watched</h2>
|
|
</div></div>
|
|
</section>
|
|
</body></html>`
|
|
|
|
func TestParseUser_Synthetic(t *testing.T) {
|
|
doc, err := goquery.NewDocumentFromReader(strings.NewReader(syntheticUserHTML))
|
|
if err != nil {
|
|
t.Fatalf("setup: %v", err)
|
|
}
|
|
u, err := parseUser("SomeFurry", doc)
|
|
if err != nil {
|
|
t.Fatalf("parseUser: %v", err)
|
|
}
|
|
if u.DisplayName != "SomeFurry" {
|
|
t.Errorf("DisplayName = %q; want SomeFurry", u.DisplayName)
|
|
}
|
|
if u.Name != "somefurry" {
|
|
t.Errorf("Name = %q; want somefurry (lowercased)", u.Name)
|
|
}
|
|
if u.AvatarURL != "https://a.furaffinity.net/123/somefurry.gif" {
|
|
t.Errorf("AvatarURL = %q; want the profile owner's avatar, not the logged-in viewer's", u.AvatarURL)
|
|
}
|
|
if u.Stats.Submissions != 1234 {
|
|
t.Errorf("Stats.Submissions = %d; want 1234", u.Stats.Submissions)
|
|
}
|
|
if u.Stats.Favorites != 567 {
|
|
t.Errorf("Stats.Favorites = %d; want 567", u.Stats.Favorites)
|
|
}
|
|
if u.Stats.Views != 1176 {
|
|
t.Errorf("Stats.Views = %d; want 1176", u.Stats.Views)
|
|
}
|
|
if u.Stats.Comments != 85 {
|
|
t.Errorf("Stats.Comments = %d; want 85", u.Stats.Comments)
|
|
}
|
|
if u.Stats.Journals != 12 {
|
|
t.Errorf("Stats.Journals = %d; want 12", u.Stats.Journals)
|
|
}
|
|
if u.Stats.Watchers != 89 {
|
|
t.Errorf("Stats.Watchers = %d; want 89", u.Stats.Watchers)
|
|
}
|
|
if u.Stats.Watching != 10 {
|
|
t.Errorf("Stats.Watching = %d; want 10", u.Stats.Watching)
|
|
}
|
|
if !u.Watched {
|
|
t.Error("Watched = false; want true (page shows an Unwatch button)")
|
|
}
|
|
if !strings.Contains(u.BioText, "Welcome") {
|
|
t.Errorf("BioText missing expected content: %q", u.BioText)
|
|
}
|
|
if u.Joined.Year() != 2018 {
|
|
t.Errorf("Joined.Year = %d; want 2018", u.Joined.Year())
|
|
}
|
|
}
|
|
|
|
func TestParseUser_RealFixture(t *testing.T) {
|
|
raw := loadFixture(t, "user.html")
|
|
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(raw))
|
|
if err != nil {
|
|
t.Fatalf("read doc: %v", err)
|
|
}
|
|
u, err := parseUser("fixture", doc)
|
|
if err != nil {
|
|
t.Fatalf("parseUser(real): %v", err)
|
|
}
|
|
// Values below are read directly from testdata/html/user.html. They guard
|
|
// against FA markup drift a zero/empty here means a selector went stale.
|
|
if u.DisplayName != "SoXX-TheFennec" {
|
|
t.Errorf("DisplayName = %q; want SoXX-TheFennec", u.DisplayName)
|
|
}
|
|
if u.AvatarURL != "https://a.furaffinity.net/1515442832/soxx-thefennec.gif" {
|
|
t.Errorf("AvatarURL = %q; want the profile owner's avatar", u.AvatarURL)
|
|
}
|
|
if u.Stats.Submissions != 30 {
|
|
t.Errorf("Stats.Submissions = %d; want 30", u.Stats.Submissions)
|
|
}
|
|
if u.Stats.Favorites != 180 {
|
|
t.Errorf("Stats.Favorites = %d; want 180", u.Stats.Favorites)
|
|
}
|
|
if u.Stats.Views != 1184 {
|
|
t.Errorf("Stats.Views = %d; want 1184", u.Stats.Views)
|
|
}
|
|
if u.Stats.Comments != 85 {
|
|
t.Errorf("Stats.Comments = %d; want 85", u.Stats.Comments)
|
|
}
|
|
if u.Stats.Watchers != 50 {
|
|
t.Errorf("Stats.Watchers = %d; want 50", u.Stats.Watchers)
|
|
}
|
|
if u.Stats.Watching != 315 {
|
|
t.Errorf("Stats.Watching = %d; want 315", u.Stats.Watching)
|
|
}
|
|
if u.SiteBanner == nil {
|
|
t.Fatal("SiteBanner = nil; want the default site banner populated")
|
|
}
|
|
if u.SiteBanner.IsCustom {
|
|
t.Errorf("SiteBanner.IsCustom = true; want false (user.html shows FA's default site banner)")
|
|
}
|
|
if !strings.Contains(u.SiteBanner.ImageURL, "/media/banners/") {
|
|
t.Errorf("SiteBanner.ImageURL = %q; want a /media/banners/ URL", u.SiteBanner.ImageURL)
|
|
}
|
|
}
|
|
|
|
// syntheticUserWithCustomBannerHTML reproduces FA's <site-banner> markup for a
|
|
// user who has uploaded their own profile banner via /controls/profilebanner/.
|
|
// The image lives under /art/<name>/ rather than /media/banners/, which is the
|
|
// signal the parser uses to set IsCustom.
|
|
const syntheticUserWithCustomBannerHTML = `<html><body>
|
|
<site-banner>
|
|
<a href="/">
|
|
<picture>
|
|
<source media="(max-width: 799px)" srcset="//d.furaffinity.net/art/somefurry/1716929854/profile_banner_mobile.jpg">
|
|
<source media="(min-width: 800px)" srcset="//d.furaffinity.net/art/somefurry/1716929854/profile_banner.jpg">
|
|
<img src="//d.furaffinity.net/art/somefurry/1716929854/profile_banner.jpg" alt="Profile Banner image">
|
|
</picture>
|
|
</a>
|
|
</site-banner>
|
|
|
|
<userpage-nav-header>
|
|
<userpage-nav-avatar>
|
|
<a class="current" href="/user/somefurry/"><img alt="somefurry" src="//a.furaffinity.net/123/somefurry.gif"/></a>
|
|
</userpage-nav-avatar>
|
|
<userpage-nav-user-details>
|
|
<div class="c-usernameBlock"><span class="c-usernameBlock__displayName">SomeFurry</span></div>
|
|
</userpage-nav-user-details>
|
|
</userpage-nav-header>
|
|
</body></html>`
|
|
|
|
// TestParseUser_SiteBanner_RealFixture validates the custom-banner case
|
|
// against a real captured profile (gillpanda, who has uploaded a profile
|
|
// banner). The fixture is captured by the `fixtures` build-tagged refresh
|
|
// test using FA_TEST_USER_WITH_BANNER; the test skips cleanly when absent.
|
|
func TestParseUser_SiteBanner_RealFixture(t *testing.T) {
|
|
raw := loadFixture(t, "user_with_banner.html")
|
|
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(raw))
|
|
if err != nil {
|
|
t.Fatalf("read doc: %v", err)
|
|
}
|
|
u, err := parseUser("gillpanda", doc)
|
|
if err != nil {
|
|
t.Fatalf("parseUser(real): %v", err)
|
|
}
|
|
if u.SiteBanner == nil {
|
|
t.Fatal("SiteBanner = nil; want a populated custom banner")
|
|
}
|
|
if !u.SiteBanner.IsCustom {
|
|
t.Errorf("SiteBanner.IsCustom = false; want true (gillpanda has a custom banner)")
|
|
}
|
|
if !strings.Contains(u.SiteBanner.ImageURL, "/art/gillpanda/") {
|
|
t.Errorf("SiteBanner.ImageURL = %q; want a /art/gillpanda/ URL", u.SiteBanner.ImageURL)
|
|
}
|
|
}
|
|
|
|
func TestParseUser_SiteBanner_Custom(t *testing.T) {
|
|
doc, err := goquery.NewDocumentFromReader(strings.NewReader(syntheticUserWithCustomBannerHTML))
|
|
if err != nil {
|
|
t.Fatalf("setup: %v", err)
|
|
}
|
|
u, err := parseUser("SomeFurry", doc)
|
|
if err != nil {
|
|
t.Fatalf("parseUser: %v", err)
|
|
}
|
|
if u.SiteBanner == nil {
|
|
t.Fatal("SiteBanner = nil; want the custom banner populated")
|
|
}
|
|
if !u.SiteBanner.IsCustom {
|
|
t.Errorf("SiteBanner.IsCustom = false; want true (URL is under /art/<name>/)")
|
|
}
|
|
want := "https://d.furaffinity.net/art/somefurry/1716929854/profile_banner.jpg"
|
|
if u.SiteBanner.ImageURL != want {
|
|
t.Errorf("SiteBanner.ImageURL = %q; want %q", u.SiteBanner.ImageURL, want)
|
|
}
|
|
}
|