app/atom_test.go

v1.5.6
skunky-art/app/atom_test.go history · blame · raw

107 lines · 3323 bytes

  1package app
  2
  3import (
  4	"encoding/xml"
  5	"net/http/httptest"
  6	"net/url"
  7	"strings"
  8	"testing"
  9	"time"
 10
 11	"github.com/krazywarez/devianter"
 12)
 13
 14// atomFeed is the subset of an Atom document the tests check. Decoding through
 15// encoding/xml also proves the output is well-formed XML.
 16type atomFeed struct {
 17	XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
 18	ID      string   `xml:"id"`
 19	Updated string   `xml:"updated"`
 20	Links   []struct {
 21		Rel  string `xml:"rel,attr"`
 22		Href string `xml:"href,attr"`
 23	} `xml:"link"`
 24	Entries []struct {
 25		ID        string `xml:"id"`
 26		Published string `xml:"published"`
 27		Updated   string `xml:"updated"`
 28		Thumbnail struct {
 29			URL string `xml:"url,attr"`
 30		} `xml:"http://search.yahoo.com/mrss/ group>thumbnail"`
 31	} `xml:"entry"`
 32}
 33
 34func renderFeed(t *testing.T, devs []devianter.Deviation) atomFeed {
 35	t.Helper()
 36	rec := httptest.NewRecorder()
 37	s := skunkyart{Writer: rec, Host: "http://localhost", Atom: true, _pth: "/dd", Args: url.Values{"atom": {"true"}}}
 38	s.DeviationList(devs, true)
 39
 40	var feed atomFeed
 41	if err := xml.Unmarshal(rec.Body.Bytes(), &feed); err != nil {
 42		t.Fatalf("feed is not well-formed: %v\n%s", err, rec.Body.String())
 43	}
 44	return feed
 45}
 46
 47func selfLink(f atomFeed) string {
 48	for _, l := range f.Links {
 49		if l.Rel == "self" {
 50			return l.Href
 51		}
 52	}
 53	return ""
 54}
 55
 56// TestAtomFeedHasTheRequiredElements pins what the spec demands and readers
 57// reject a feed without: a feed id and updated time, IRI entry ids, RFC 3339
 58// timestamps, and the correctly spelled media thumbnail.
 59func TestAtomFeedHasTheRequiredElements(t *testing.T) {
 60	proxy, nsfw := CFG.Proxy, CFG.Nsfw
 61	CFG.Proxy, CFG.Nsfw = true, true
 62	defer func() { CFG.Proxy, CFG.Nsfw = proxy, nsfw }()
 63
 64	d := *fullviewDeviation()
 65	d.ID = 123
 66	d.Title = "T"
 67	d.Author.Username = "alice"
 68	d.PublishedTime.Time = time.Date(2026, 8, 11, 18, 36, 34, 0, time.UTC)
 69
 70	feed := renderFeed(t, []devianter.Deviation{d})
 71
 72	if !strings.HasPrefix(feed.ID, "http://localhost/dd?") {
 73		t.Errorf("feed id %q, want the feed's own URL", feed.ID)
 74	}
 75	if feed.Updated != "2026-08-11T18:36:34Z" {
 76		t.Errorf("feed updated %q, want the newest entry's RFC 3339 time", feed.Updated)
 77	}
 78	if selfLink(feed) != feed.ID {
 79		t.Errorf("self link %q, want %q", selfLink(feed), feed.ID)
 80	}
 81	if len(feed.Entries) != 1 {
 82		t.Fatalf("%d entries, want 1", len(feed.Entries))
 83	}
 84	e := feed.Entries[0]
 85	if !strings.HasPrefix(e.ID, "http://localhost/post/alice/") {
 86		t.Errorf("entry id %q, want the post's URL", e.ID)
 87	}
 88	if e.Published != "2026-08-11T18:36:34Z" || e.Updated != e.Published {
 89		t.Errorf("entry published %q updated %q, want RFC 3339 and equal", e.Published, e.Updated)
 90	}
 91	if e.Thumbnail.URL == "" {
 92		t.Error("entry has no media:thumbnail, want one (the element used to be misspelled)")
 93	}
 94}
 95
 96// TestAtomFeedIsValidWhenEmpty pins that an empty listing still yields a feed
 97// with an updated time, so a reader polling an empty gallery gets a document
 98// rather than an error.
 99func TestAtomFeedIsValidWhenEmpty(t *testing.T) {
100	feed := renderFeed(t, nil)
101	if feed.ID == "" || feed.Updated == "" {
102		t.Errorf("empty feed id %q updated %q, want both set", feed.ID, feed.Updated)
103	}
104	if _, err := time.Parse(time.RFC3339, feed.Updated); err != nil {
105		t.Errorf("empty feed updated %q is not RFC 3339: %v", feed.Updated, err)
106	}
107}