all repos

rss-tools @ 7f0afbb

get rss feed from sources that(i need and) dont provide one

rss-tools/app/atom/atom_test.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
refactor atom feed builder, 14 days ago
1
package atom
2
3
import (
4
	"encoding/xml"
5
	"net/http"
6
	"net/http/httptest"
7
	"strings"
8
	"testing"
9
	"time"
10
11
	"olexsmir.xyz/x/is"
12
)
13
14
func TestNewFeedDefaults(t *testing.T) {
15
	feed := NewFeed("test", "feed-id")
16
17
	is.Equal(t, "test", feed.Title)
18
	is.Equal(t, "feed-id", feed.ID)
19
	is.NotEqual(t, "", feed.Updated)
20
	is.Equal(t, 1, len(feed.Author))
21
	is.Equal(t, "rss-tools", feed.Author[0].Name)
22
}
23
24
func TestFeedAddAppendsEntry(t *testing.T) {
25
	feed := NewFeed("test", "feed-id")
26
	entry := &Entry{
27
		Title:   "entry",
28
		ID:      "entry-id",
29
		Updated: Time(time.Date(2026, 4, 20, 8, 0, 0, 0, time.UTC)),
30
		Content: NewText("body", ""),
31
	}
32
	feed.Add(entry)
33
34
	is.Equal(t, 1, len(feed.Entry))
35
	is.Equal(t, "entry-id", feed.Entry[0].ID)
36
}
37
38
func TestFeedBytesAndWriteTo(t *testing.T) {
39
	updated := time.Date(2026, 4, 20, 12, 30, 0, 0, time.UTC)
40
	feed := NewFeed("test", "feed-id").
41
		Add(&Entry{
42
			Title:   "entry",
43
			ID:      "entry-id",
44
			Updated: Time(updated),
45
			Content: NewText("content", ""),
46
		})
47
48
	raw, err := feed.Bytes()
49
	is.Err(t, err, nil)
50
51
	var parsed Feed
52
	is.Err(t, xml.Unmarshal(raw, &parsed), nil)
53
	is.Equal(t, "test", parsed.Title)
54
}
55
56
func TestFeedWithAuthor(t *testing.T) {
57
	feed := NewFeed("test", "feed-id").WithAuthor("moviefeed")
58
	raw, err := feed.Bytes()
59
	is.Err(t, err, nil)
60
	var parsed Feed
61
	is.Err(t, xml.Unmarshal(raw, &parsed), nil)
62
	is.Equal(t, 1, len(parsed.Author))
63
	is.Equal(t, "moviefeed", parsed.Author[0].Name)
64
}
65
66
func TestFeedRender(t *testing.T) {
67
	r := httptest.NewRecorder()
68
	err := NewFeed("test", "feed-id").
69
		Add(&Entry{
70
			Title:   "entry",
71
			ID:      "entry-id",
72
			Content: NewText("content", ""),
73
			Updated: Time(time.Date(2026, 4, 20, 8, 0, 0, 0, time.UTC)),
74
		}).
75
		Render(r)
76
	is.Err(t, err, nil)
77
78
	is.Equal(t, http.StatusOK, r.Code)
79
	if got := r.Header().Get("Content-Type"); !strings.Contains(got, "application/atom+xml") {
80
		t.Fatalf("unexpected content type: %q", got)
81
	}
82
}
83
84
func TestFeedEntryTextContent(t *testing.T) {
85
	feed := NewFeed("test", "feed-id").
86
		Add(&Entry{
87
			Title:   "text entry",
88
			ID:      "entry-id",
89
			Content: NewText("plain text content", "text"),
90
			Updated: Time(time.Date(2026, 4, 20, 12, 0, 0, 0, time.UTC)),
91
		})
92
93
	raw, err := feed.Bytes()
94
	is.Err(t, err, nil)
95
	if !strings.Contains(string(raw), `<content type="text">plain text content</content>`) {
96
		t.Fatalf("expected text content with type attribute in serialized feed")
97
	}
98
99
	var parsed Feed
100
	is.Err(t, xml.Unmarshal(raw, &parsed), nil)
101
	is.Equal(t, 1, len(parsed.Entry))
102
103
	entry := parsed.Entry[0]
104
	if entry.Content == nil {
105
		t.Fatalf("expected content element in entry")
106
	}
107
	is.Equal(t, "text", entry.Content.Type)
108
	is.Equal(t, "plain text content", entry.Content.Body)
109
}
110
111
func TestFeedEntryHtmlContent(t *testing.T) {
112
	htmlContent := "<p>Hello <strong>World</strong></p>"
113
	feed := NewFeed("test", "feed-id").
114
		Add(&Entry{
115
			Title:   "html entry",
116
			ID:      "entry-id",
117
			Content: NewText(htmlContent, "html"),
118
			Updated: Time(time.Date(2026, 4, 20, 12, 0, 0, 0, time.UTC)),
119
		})
120
121
	raw, err := feed.Bytes()
122
	is.Err(t, err, nil)
123
	if !strings.Contains(string(raw), `<content type="html">`) {
124
		t.Fatalf("expected HTML content with type='html' attribute in serialized feed")
125
	}
126
127
	var parsed Feed
128
	is.Err(t, xml.Unmarshal(raw, &parsed), nil)
129
	is.Equal(t, 1, len(parsed.Entry))
130
131
	entry := parsed.Entry[0]
132
	if entry.Content == nil {
133
		t.Fatalf("expected content element in entry")
134
	}
135
	is.Equal(t, "html", entry.Content.Type)
136
	is.Equal(t, htmlContent, entry.Content.Body)
137
}
138
139
func TestFeedEntryXHTMLContent(t *testing.T) {
140
	xhtmlContent := `<body><p>Hello <strong>World</strong></p></body>`
141
	feed := NewFeed("test", "feed-id").
142
		Add(&Entry{
143
			Title:   "xhtml entry",
144
			ID:      "entry-id",
145
			Content: NewText(xhtmlContent, "xhtml"),
146
			Updated: Time(time.Date(2026, 4, 20, 12, 0, 0, 0, time.UTC)),
147
		})
148
149
	raw, err := feed.Bytes()
150
	is.Err(t, err, nil)
151
	if !strings.Contains(string(raw), `<content type="xhtml">`) {
152
		t.Fatalf("expected XHTML content with type='xhtml' attribute in serialized feed")
153
	}
154
	if !strings.Contains(string(raw), `<div xmlns="http://www.w3.org/1999/xhtml"><body><p>Hello <strong>World</strong></p></body></div>`) {
155
		t.Fatalf("expected XHTML div wrapper for content")
156
	}
157
}
158
159
func TestFeedEntryLinks(t *testing.T) {
160
	feed := NewFeed("test", "feed-id").
161
		Add(&Entry{
162
			Title:   "entry",
163
			ID:      "entry-id",
164
			Content: NewText("hello", ""),
165
			Link: []Link{
166
				{Rel: "alternate", Type: "text/html", Href: "https://example.com/item"},
167
			},
168
			Updated: Time(time.Date(2026, 4, 20, 12, 0, 0, 0, time.UTC)),
169
		})
170
171
	raw, err := feed.Bytes()
172
	is.Err(t, err, nil)
173
174
	var parsed Feed
175
	is.Err(t, xml.Unmarshal(raw, &parsed), nil)
176
	is.Equal(t, 1, len(parsed.Entry))
177
	is.Equal(t, 1, len(parsed.Entry[0].Link))
178
	is.Equal(t, "https://example.com/item", parsed.Entry[0].Link[0].Href)
179
}
180
181
func TestFeedEntryLinksWithLength(t *testing.T) {
182
	feed := NewFeed("test", "feed-id").
183
		Add(&Entry{
184
			Title:   "entry",
185
			ID:      "entry-id",
186
			Content: NewText("hello", ""),
187
			Link: []Link{
188
				{Rel: "enclosure", Type: "image/jpeg", Length: 0, Href: "https://example.com/item.jpg"},
189
			},
190
			Updated: Time(time.Date(2026, 4, 20, 12, 0, 0, 0, time.UTC)),
191
		})
192
193
	raw, err := feed.Bytes()
194
	is.Err(t, err, nil)
195
	var parsed Feed
196
	is.Err(t, xml.Unmarshal(raw, &parsed), nil)
197
	if len(parsed.Entry) == 0 || len(parsed.Entry[0].Link) == 0 {
198
		t.Fatalf("expected enclosure link in parsed feed")
199
	}
200
	is.Equal(t, uint(0), parsed.Entry[0].Link[0].Length)
201
}
202
203
func TestFeedMultipleEntriesWithMixedContentTypes(t *testing.T) {
204
	updated := time.Date(2026, 4, 20, 12, 0, 0, 0, time.UTC)
205
	feed := NewFeed("test", "feed-id").
206
		Add(&Entry{
207
			Title:   "text entry",
208
			ID:      "entry-text",
209
			Content: NewText("plain text", "text"),
210
			Updated: Time(updated),
211
		}).
212
		Add(&Entry{
213
			Title:   "html entry",
214
			ID:      "entry-html",
215
			Content: NewText("<p>html content</p>", "html"),
216
			Updated: Time(updated),
217
		}).
218
		Add(&Entry{
219
			Title:   "default entry",
220
			ID:      "entry-default",
221
			Content: NewText("default content", ""),
222
			Updated: Time(updated),
223
		})
224
225
	raw, err := feed.Bytes()
226
	is.Err(t, err, nil)
227
228
	var parsed Feed
229
	is.Err(t, xml.Unmarshal(raw, &parsed), nil)
230
	is.Equal(t, 3, len(parsed.Entry))
231
232
	tests := []struct {
233
		name         string
234
		expectedType string
235
		expectedText string
236
	}{
237
		{"text entry", "text", "plain text"},
238
		{"html entry", "html", "<p>html content</p>"},
239
		{"default entry", "text", "default content"},
240
	}
241
	for i, tc := range tests {
242
		entry := parsed.Entry[i]
243
		if entry.Content == nil {
244
			t.Fatalf("expected content element in entry %d", i)
245
		}
246
		is.Equal(t, tc.name, entry.Title)
247
		is.Equal(t, tc.expectedText, entry.Content.Body)
248
		is.Equal(t, tc.expectedType, entry.Content.Type)
249
	}
250
}