inital commit
This commit is contained in:
196
notifications_test.go
Normal file
196
notifications_test.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package fa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// journalNotifRow renders one /msg/others/ journal-notification <li>. FA's
|
||||
// real markup carries no avatar image on these rows that is the whole
|
||||
// point of issue #14 so this helper deliberately omits one.
|
||||
func journalNotifRow(journalID int, title, authorName, authorDisplay string) string {
|
||||
return fmt.Sprintf(`<li>
|
||||
<div class="table">
|
||||
<div class="cell"><input type="checkbox" name="journals[]" value="%d"></div>
|
||||
<div class="cell">
|
||||
<a href="/journal/%d/"><em class="journal_subject">%s</em></a>
|
||||
(<span class="c-contentRating--general">G</span>)
|
||||
posted by
|
||||
<span class="c-usernameBlockSimple"><a href="/user/%s/"><span class="c-usernameBlockSimple__displayName" title=" %s ">%s</span></a></span>
|
||||
<span class="popup_date" data-time="1779179727">recently</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>`, journalID, journalID, title, authorName, authorName, authorDisplay)
|
||||
}
|
||||
|
||||
// fakeMsgOthersPage wraps journal rows in the section markup parseNotifications
|
||||
// expects.
|
||||
func fakeMsgOthersPage(rows ...string) string {
|
||||
return `<html><head><title>Notifications</title></head><body>
|
||||
<section class="section_container" id="messages-journals">
|
||||
<ul class="message-stream">` + strings.Join(rows, "\n") + `</ul>
|
||||
</section>
|
||||
</body></html>`
|
||||
}
|
||||
|
||||
// fakeUserAvatarPage renders the minimal /user/{name}/ markup fetchUserAvatar
|
||||
// reads just the <userpage-nav-avatar> header element.
|
||||
func fakeUserAvatarPage(name, avatarTS string) string {
|
||||
return `<html><head><title>` + name + `</title></head><body>
|
||||
<userpage-nav-avatar>
|
||||
<a href="/user/` + name + `/"><img alt="` + name + `" src="//a.furaffinity.net/` + avatarTS + `/` + name + `.gif"/></a>
|
||||
</userpage-nav-avatar>
|
||||
</body></html>`
|
||||
}
|
||||
|
||||
func TestNotifications_ResolvesAvatars(t *testing.T) {
|
||||
var userHits sync.Map // name -> *atomic.Int32
|
||||
hit := func(name string) int32 {
|
||||
v, _ := userHits.LoadOrStore(name, &atomic.Int32{})
|
||||
return v.(*atomic.Int32).Add(1)
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/msg/others/", func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(fakeMsgOthersPage(
|
||||
journalNotifRow(101, "First", "authora", "AuthorA"),
|
||||
journalNotifRow(102, "Second", "authora", "AuthorA"), // same author must dedup
|
||||
journalNotifRow(103, "Third", "authorb", "AuthorB"),
|
||||
)))
|
||||
})
|
||||
mux.HandleFunc("/user/authora/", func(w http.ResponseWriter, r *http.Request) {
|
||||
hit("authora")
|
||||
_, _ = w.Write([]byte(fakeUserAvatarPage("authora", "100")))
|
||||
})
|
||||
mux.HandleFunc("/user/authorb/", func(w http.ResponseWriter, r *http.Request) {
|
||||
hit("authorb")
|
||||
_, _ = w.Write([]byte(fakeUserAvatarPage("authorb", "200")))
|
||||
})
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
|
||||
client := newE2EClient(t, srv)
|
||||
n, err := client.Notifications(context.Background(), WithResolvedAvatars(0))
|
||||
if err != nil {
|
||||
t.Fatalf("Notifications: %v", err)
|
||||
}
|
||||
if len(n.Journals) != 3 {
|
||||
t.Fatalf("Journals = %d; want 3", len(n.Journals))
|
||||
}
|
||||
|
||||
want := map[string]string{
|
||||
"authora": "https://a.furaffinity.net/100/authora.gif",
|
||||
"authorb": "https://a.furaffinity.net/200/authorb.gif",
|
||||
}
|
||||
for i, j := range n.Journals {
|
||||
if got := j.Author.AvatarURL; got != want[j.Author.Name] {
|
||||
t.Errorf("Journals[%d] (%s): AvatarURL = %q; want %q",
|
||||
i, j.Author.Name, got, want[j.Author.Name])
|
||||
}
|
||||
}
|
||||
|
||||
// authora appears on two journals but must be fetched exactly once.
|
||||
if v, ok := userHits.Load("authora"); !ok || v.(*atomic.Int32).Load() != 1 {
|
||||
got := int32(0)
|
||||
if ok {
|
||||
got = v.(*atomic.Int32).Load()
|
||||
}
|
||||
t.Errorf("/user/authora/ fetched %d times; want 1 (dedup)", got)
|
||||
}
|
||||
if v, ok := userHits.Load("authorb"); !ok || v.(*atomic.Int32).Load() != 1 {
|
||||
t.Error("/user/authorb/ not fetched exactly once")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifications_ResolvedAvatarsRespectsLimit(t *testing.T) {
|
||||
var userPageHits atomic.Int32
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/msg/others/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// Four journals, three distinct authors. authora appears twice.
|
||||
_, _ = w.Write([]byte(fakeMsgOthersPage(
|
||||
journalNotifRow(101, "First", "authora", "AuthorA"),
|
||||
journalNotifRow(102, "Second", "authora", "AuthorA"),
|
||||
journalNotifRow(103, "Third", "authorb", "AuthorB"),
|
||||
journalNotifRow(104, "Fourth", "authorc", "AuthorC"),
|
||||
)))
|
||||
})
|
||||
for _, name := range []string{"authora", "authorb", "authorc"} {
|
||||
name := name
|
||||
mux.HandleFunc("/user/"+name+"/", func(w http.ResponseWriter, r *http.Request) {
|
||||
userPageHits.Add(1)
|
||||
_, _ = w.Write([]byte(fakeUserAvatarPage(name, "100")))
|
||||
})
|
||||
}
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
|
||||
client := newE2EClient(t, srv)
|
||||
// Budget of 2 distinct authors. Journals resolve in document order, so
|
||||
// authora + authorb get fetched; authorc is past the budget.
|
||||
n, err := client.Notifications(context.Background(), WithResolvedAvatars(2))
|
||||
if err != nil {
|
||||
t.Fatalf("Notifications: %v", err)
|
||||
}
|
||||
if len(n.Journals) != 4 {
|
||||
t.Fatalf("Journals = %d; want 4", len(n.Journals))
|
||||
}
|
||||
|
||||
// Exactly 2 profile fetches the limit caps *fetches*, not applications.
|
||||
if got := userPageHits.Load(); got != 2 {
|
||||
t.Errorf("/user/ fetched %d times; want 2 (limit)", got)
|
||||
}
|
||||
|
||||
byName := map[string]string{}
|
||||
for _, j := range n.Journals {
|
||||
// Both authora rows must agree (cache hit applies past the limit).
|
||||
if prev, seen := byName[j.Author.Name]; seen && prev != j.Author.AvatarURL {
|
||||
t.Errorf("author %s: inconsistent AvatarURL %q vs %q", j.Author.Name, prev, j.Author.AvatarURL)
|
||||
}
|
||||
byName[j.Author.Name] = j.Author.AvatarURL
|
||||
}
|
||||
if byName["authora"] == "" || byName["authorb"] == "" {
|
||||
t.Errorf("authora/authorb should be resolved within budget; got %q / %q",
|
||||
byName["authora"], byName["authorb"])
|
||||
}
|
||||
if byName["authorc"] != "" {
|
||||
t.Errorf("authorc is past the budget; AvatarURL = %q, want empty", byName["authorc"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifications_NoAvatarResolutionByDefault(t *testing.T) {
|
||||
var userPageHits atomic.Int32
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/msg/others/", func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(fakeMsgOthersPage(
|
||||
journalNotifRow(101, "First", "authora", "AuthorA"),
|
||||
)))
|
||||
})
|
||||
mux.HandleFunc("/user/", func(w http.ResponseWriter, r *http.Request) {
|
||||
userPageHits.Add(1)
|
||||
})
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
|
||||
client := newE2EClient(t, srv)
|
||||
n, err := client.Notifications(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("Notifications: %v", err)
|
||||
}
|
||||
if len(n.Journals) != 1 {
|
||||
t.Fatalf("Journals = %d; want 1", len(n.Journals))
|
||||
}
|
||||
// Without WithResolvedAvatars the SDK must not touch /user/ pages, and
|
||||
// the avatar stays empty (FA does not provide it on /msg/others/).
|
||||
if n.Journals[0].Author.AvatarURL != "" {
|
||||
t.Errorf("AvatarURL = %q; want empty without WithResolvedAvatars", n.Journals[0].Author.AvatarURL)
|
||||
}
|
||||
if userPageHits.Load() != 0 {
|
||||
t.Errorf("/user/ fetched %d times; want 0 (no resolution requested)", userPageHits.Load())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user