package fa import ( "bytes" "fmt" "strings" "testing" "github.com/PuerkitoBio/goquery" ) // syntheticSubmissionHTML is a minimal hand-rolled page that exercises every // selector the parser cares about. Real FA HTML differs in ways that // fixture-driven tests will catch; this synthetic input pins the parser // against a stable, deterministic input independent of FA's mood. const syntheticSubmissionHTML = `

My Test Submission

by SomeFurry
Posted 5 hours ago

Hello world.

1,234
Views
7
Comments
56
Favorites
General
Rating
CategoryThemeSpeciesGenderResolution Artwork (Digital)DigitalWolfMale1920 x 1080
Keywords
wolf art s_wolf c_artwork_digital t_general_furry_art u_somefurry
« Newer Older »
` func TestParseSubmission_Synthetic(t *testing.T) { doc, err := goquery.NewDocumentFromReader(strings.NewReader(syntheticSubmissionHTML)) if err != nil { t.Fatalf("setup: %v", err) } sub, err := parseSubmission(1234, doc) if err != nil { t.Fatalf("parseSubmission: %v", err) } checks := []struct { name string got any want any }{ {"ID", sub.ID, SubmissionID(1234)}, {"Title", sub.Title, "My Test Submission"}, {"Author.Name", sub.Author.Name, "somefurry"}, {"Author.DisplayName", sub.Author.DisplayName, "SomeFurry"}, {"Author.AvatarURL", sub.Author.AvatarURL, "https://d.example/avatars/somefurry.png"}, {"Rating", sub.Rating, RatingGeneral}, {"Category", sub.Category, Category("Artwork (Digital)")}, {"Type", sub.Type, Type("Digital")}, {"Species", sub.Species, Species("Wolf")}, {"Gender", sub.Gender, Gender("Male")}, {"FileURL", sub.FileURL, "https://d.example/art/somefurry/1234_full.png"}, {"ThumbURL", sub.ThumbURL, "https://d.example/art/somefurry/1234_thumb.png"}, {"Width", sub.Width, 1920}, {"Height", sub.Height, 1080}, {"Stats.Views", sub.Stats.Views, 1234}, {"Stats.Favorites", sub.Stats.Favorites, 56}, {"Stats.Comments", sub.Stats.Comments, 7}, {"Prev (Newer)", sub.Prev, SubmissionID(1235)}, {"Next (Older)", sub.Next, SubmissionID(1233)}, {"len(Tags)", len(sub.Tags), 2}, } for _, c := range checks { if c.got != c.want { t.Errorf("%s = %v; want %v", c.name, c.got, c.want) } } if !sub.PostedAt.IsZero() && sub.PostedAt.Year() != 2026 { t.Errorf("PostedAt year = %d; want 2026", sub.PostedAt.Year()) } if !strings.Contains(sub.Description, "world") { t.Errorf("Description missing expected content: %q", sub.Description) } catChecks := []struct { name string got []string want []string }{ {"Species", sub.CategorizedTags.Species, []string{"wolf"}}, {"Characters", sub.CategorizedTags.Characters, []string{"artwork_digital"}}, {"Types", sub.CategorizedTags.Types, []string{"general_furry_art"}}, {"Artists", sub.CategorizedTags.Artists, []string{"somefurry"}}, } for _, c := range catChecks { if !equalStrings(c.got, c.want) { t.Errorf("CategorizedTags.%s = %v; want %v", c.name, c.got, c.want) } } } func equalStrings(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true } // TestParseSubmission_FavoritedState verifies parseSubmission reports the // authenticated viewer's favorite state. FA renders exactly one of the // "+Fav" (/fav/) or "−Fav" (/unfav/) anchors, matching the viewer's current // state; an anonymous fetch shows neither. func TestParseSubmission_FavoritedState(t *testing.T) { const tmpl = `

T

%s
` cases := []struct { name string link string want bool }{ {"favorited shows unfav link", `−Fav`, true}, {"not favorited shows fav link", `+Fav`, false}, {"anonymous shows neither", ``, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { doc, err := goquery.NewDocumentFromReader(strings.NewReader(fmt.Sprintf(tmpl, c.link))) if err != nil { t.Fatalf("setup: %v", err) } sub, err := parseSubmission(1234, doc) if err != nil { t.Fatalf("parseSubmission: %v", err) } if sub.Favorited != c.want { t.Errorf("Favorited = %v; want %v", sub.Favorited, c.want) } }) } } func TestParseSubmission_MissingTitleErrors(t *testing.T) { doc, err := goquery.NewDocumentFromReader(strings.NewReader("")) if err != nil { t.Fatalf("setup: %v", err) } if _, err := parseSubmission(1, doc); err == nil { t.Fatal("expected parse error for missing title") } } // TestParseSubmission_RealFixture runs the parser against a real FA HTML // dump captured by the `fixtures` build tag. Skips cleanly if no fixture // has been recorded. func TestParseSubmission_RealFixture(t *testing.T) { raw := loadFixture(t, "submission.html") doc, err := goquery.NewDocumentFromReader(bytes.NewReader(raw)) if err != nil { t.Fatalf("read doc: %v", err) } sub, err := parseSubmission(0, doc) if err != nil { t.Fatalf("parseSubmission(real): %v", err) } // We can't assert exact values against a fixture whose contents we don't // pin in this repo. Instead assert that the load-bearing fields populated. if sub.Title == "" { t.Error("real fixture: Title is empty") } if sub.Author.Name == "" { t.Error("real fixture: Author.Name is empty") } if sub.FileURL == "" { t.Error("real fixture: FileURL is empty") } }