all repos

rss-tools @ master

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

rss-tools/sources/moviefeed/moviefeed_test.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
imrpove movies id parsing, 9 days ago
1
package moviefeed
2
3
import (
4
	"encoding/xml"
5
	"errors"
6
	"fmt"
7
	"net/http"
8
	"net/http/httptest"
9
	"strings"
10
	"testing"
11
	"time"
12
13
	"olexsmir.xyz/rss-tools/app/atom"
14
	"olexsmir.xyz/x/is"
15
)
16
17
type fakeEpisodeAPI struct {
18
	episodes map[string][]TMDBEpisode
19
	errs     map[string]error
20
	searches map[string]tmdbShow
21
}
22
23
func (f fakeEpisodeAPI) FetchEpisodesForShow(showID string) ([]TMDBEpisode, error) {
24
	if err, ok := f.errs[showID]; ok {
25
		return nil, err
26
	}
27
	if episodes, ok := f.episodes[showID]; ok {
28
		return episodes, nil
29
	}
30
	return nil, nil
31
}
32
33
func (f fakeEpisodeAPI) SearchShow(query string) (*tmdbShow, error) {
34
	if f.searches == nil {
35
		return nil, fmt.Errorf("no search results for %q", query)
36
	}
37
	s, ok := f.searches[query]
38
	if !ok {
39
		return nil, fmt.Errorf("no search results for %q", query)
40
	}
41
	return &s, nil
42
}
43
44
func TestHandleMoviesRendersFeedFromConfiguredShows(t *testing.T) {
45
	episodes := []TMDBEpisode{
46
		{
47
			ID:            1001,
48
			Name:          "Episode 1",
49
			Overview:      "E1",
50
			AirDate:       "2026-04-20",
51
			EpisodeNumber: 1,
52
			SeasonNumber:  1,
53
			StillPath:     "/e1.jpg",
54
			ShowName:      "Test Show",
55
			ShowID:        "101",
56
		},
57
		{
58
			ID:            1002,
59
			Name:          "Episode 2",
60
			Overview:      "E2",
61
			AirDate:       "2026-04-21",
62
			EpisodeNumber: 2,
63
			SeasonNumber:  1,
64
			StillPath:     "",
65
			ShowName:      "Test Show",
66
			ShowID:        "101",
67
		},
68
	}
69
70
	mf := &moviefeed{
71
		api: fakeEpisodeAPI{
72
			episodes: map[string][]TMDBEpisode{
73
				"tt123": episodes,
74
			},
75
		},
76
		shows:     []string{"tt123"},
77
		nameCache: map[string]string{},
78
	}
79
80
	mux := http.NewServeMux()
81
	mux.HandleFunc("GET /movies", mf.handleMovies)
82
83
	req := httptest.NewRequest(http.MethodGet, "/movies", nil)
84
	rr := httptest.NewRecorder()
85
	mux.ServeHTTP(rr, req)
86
87
	is.Equal(t, rr.Code, http.StatusOK)
88
	if got := rr.Header().Get("Content-Type"); !strings.Contains(got, "application/atom+xml") {
89
		t.Fatalf("expected atom response content-type, got %q", got)
90
	}
91
92
	var feed atom.Feed
93
	is.Err(t, xml.NewDecoder(rr.Body).Decode(&feed), nil)
94
	is.Equal(t, feed.Title, "moviefeed")
95
	is.Equal(t, len(feed.Entry), 2)
96
	is.Equal(t, strings.Contains(feed.Entry[0].Title, "S1E2"), true)
97
	is.Equal(t, feed.Entry[0].Content.Type, "text")
98
	is.Equal(t, len(feed.Entry[1].Link), 2)
99
	is.Equal(t, feed.Entry[1].Link[1].Rel, "enclosure")
100
	is.Equal(t, feed.Entry[1].Link[1].Type, "image/jpeg")
101
	is.Equal(t, feed.Entry[1].Link[1].Length, uint(0))
102
	is.Equal(t, feed.Entry[1].Link[1].Href, "https://image.tmdb.org/t/p/w500/e1.jpg")
103
	is.Equal(t, feed.Entry[1].Content.Type, "xhtml")
104
}
105
106
func TestHandleMoviesContinuesWhenOneShowFails(t *testing.T) {
107
	episodes := []TMDBEpisode{
108
		{
109
			ID:            1001,
110
			Name:          "Episode 1",
111
			Overview:      "E1",
112
			AirDate:       "2026-04-20",
113
			EpisodeNumber: 1,
114
			SeasonNumber:  1,
115
			StillPath:     "/e1.jpg",
116
			ShowName:      "Test Show",
117
			ShowID:        "101",
118
		},
119
		{
120
			ID:            1002,
121
			Name:          "Episode 2",
122
			Overview:      "E2",
123
			AirDate:       "2026-04-21",
124
			EpisodeNumber: 2,
125
			SeasonNumber:  1,
126
			StillPath:     "",
127
			ShowName:      "Test Show",
128
			ShowID:        "101",
129
		},
130
	}
131
132
	mf := &moviefeed{
133
		api: fakeEpisodeAPI{
134
			episodes: map[string][]TMDBEpisode{
135
				"tt123": episodes,
136
			},
137
			errs: map[string]error{
138
				"bad-show": errors.New("boom"),
139
			},
140
		},
141
		shows:     []string{"bad-show", "tt123"},
142
		nameCache: map[string]string{},
143
	}
144
145
	mux := http.NewServeMux()
146
	mux.HandleFunc("GET /movies", mf.handleMovies)
147
148
	req := httptest.NewRequest(http.MethodGet, "/movies", nil)
149
	rr := httptest.NewRecorder()
150
	mux.ServeHTTP(rr, req)
151
152
	is.Equal(t, rr.Code, http.StatusOK)
153
154
	var feed atom.Feed
155
	is.Err(t, xml.NewDecoder(rr.Body).Decode(&feed), nil)
156
	is.Equal(t, len(feed.Entry), 2)
157
}
158
159
func TestFilterRecentEpisodes(t *testing.T) {
160
	now := time.Now()
161
	recent := now.AddDate(0, 0, -5).Format(dateFormat)
162
	old := now.AddDate(0, 0, -40).Format(dateFormat)
163
164
	episodes := filterRecentEpisodes([]TMDBEpisode{
165
		{AirDate: recent, Name: "recent"},
166
		{AirDate: old, Name: "old"},
167
		{AirDate: "", Name: "missing"},
168
	})
169
170
	is.Equal(t, 1, len(episodes))
171
	is.Equal(t, "recent", episodes[0].Name)
172
}
173
174
func TestEpisodeContentIncludesImageInBody(t *testing.T) {
175
	content, contentType := episodeContent(TMDBEpisode{
176
		Name:      "Episode 1",
177
		Overview:  "E1",
178
		StillPath: "/e1.jpg",
179
	})
180
181
	is.Equal(t, contentType, "xhtml")
182
	is.Equal(t, strings.Contains(content, "<body>"), true)
183
	is.Equal(t, strings.Contains(content, `<img src="https://image.tmdb.org/t/p/w500/e1.jpg" alt="Episode 1"`), true)
184
	is.Equal(t, strings.Contains(content, "</body>"), true)
185
}
186
187
func TestIsDirectID(t *testing.T) {
188
	is.Equal(t, true, isDirectID("tt1190634"))
189
	is.Equal(t, true, isDirectID("101"))
190
	is.Equal(t, true, isDirectID("1"))
191
	is.Equal(t, false, isDirectID(""))
192
	is.Equal(t, false, isDirectID("The Boys"))
193
	is.Equal(t, false, isDirectID("101a"))
194
}
195
196
func TestResolveShowIDDirectPassthrough(t *testing.T) {
197
	mf := &moviefeed{nameCache: map[string]string{}, api: fakeEpisodeAPI{}}
198
	id, err := mf.resolveShowID("tt123")
199
	is.Err(t, err, nil)
200
	is.Equal(t, "tt123", id)
201
202
	id, err = mf.resolveShowID("42")
203
	is.Err(t, err, nil)
204
	is.Equal(t, "42", id)
205
}
206
207
func TestResolveShowIDNameIDSkipsSearch(t *testing.T) {
208
	mf := &moviefeed{nameCache: map[string]string{}, api: fakeEpisodeAPI{}}
209
	id, err := mf.resolveShowID("The Boys::101")
210
	is.Err(t, err, nil)
211
	is.Equal(t, "101", id)
212
}
213
214
func TestResolveShowIDNameIDSkipsSearchWithIMDB(t *testing.T) {
215
	mf := &moviefeed{nameCache: map[string]string{}, api: fakeEpisodeAPI{}}
216
	id, err := mf.resolveShowID("The Boys::tt1190634")
217
	is.Err(t, err, nil)
218
	is.Equal(t, "tt1190634", id)
219
}
220
221
func TestResolveShowIDSearchesAndCaches(t *testing.T) {
222
	mf := &moviefeed{
223
		nameCache: map[string]string{},
224
		api: fakeEpisodeAPI{
225
			searches: map[string]tmdbShow{
226
				"The Boys": {ID: 1398, Name: "The Boys"},
227
			},
228
		},
229
	}
230
231
	id, err := mf.resolveShowID("The Boys")
232
	is.Err(t, err, nil)
233
	is.Equal(t, "1398", id)
234
235
	cached, ok := mf.nameCache["The Boys"]
236
	is.Equal(t, true, ok)
237
	is.Equal(t, "1398", cached)
238
}
239
240
func TestResolveShowIDCacheHit(t *testing.T) {
241
	mf := &moviefeed{
242
		nameCache: map[string]string{"The Boys": "1398"},
243
		api:       fakeEpisodeAPI{}, // empty — would fail if search called
244
	}
245
246
	id, err := mf.resolveShowID("The Boys")
247
	is.Err(t, err, nil)
248
	is.Equal(t, "1398", id)
249
}
250
251
func TestResolveShowIDSearchFails(t *testing.T) {
252
	mf := &moviefeed{
253
		nameCache: map[string]string{},
254
		api:       fakeEpisodeAPI{}, // no searches registered
255
	}
256
257
	_, err := mf.resolveShowID("Nonexistent Show")
258
	if err == nil {
259
		t.Fatal("expected error for unresolvable name")
260
	}
261
}
262
263
func TestFetchNewEpisodesResolvesNames(t *testing.T) {
264
	episodes := []TMDBEpisode{
265
		{
266
			ID:            1001,
267
			Name:          "E1",
268
			AirDate:       time.Now().AddDate(0, 0, -2).Format(dateFormat),
269
			EpisodeNumber: 1,
270
			SeasonNumber:  1,
271
			ShowName:      "The Boys",
272
			ShowID:        "1398",
273
		},
274
	}
275
276
	mf := &moviefeed{
277
		nameCache: map[string]string{},
278
		api: fakeEpisodeAPI{
279
			searches: map[string]tmdbShow{
280
				"The Boys": {ID: 1398, Name: "The Boys"},
281
			},
282
			episodes: map[string][]TMDBEpisode{
283
				"1398": episodes,
284
			},
285
		},
286
		shows: []string{"The Boys"},
287
	}
288
289
	got, err := mf.fetchNewEpisodes()
290
	is.Err(t, err, nil)
291
	is.Equal(t, 1, len(got))
292
	is.Equal(t, 1001, got[0].ID)
293
}
294
295
func TestFetchNewEpisodesNameIDFormat(t *testing.T) {
296
	episodes := []TMDBEpisode{
297
		{
298
			ID:            1001,
299
			Name:          "E1",
300
			AirDate:       time.Now().AddDate(0, 0, -2).Format(dateFormat),
301
			EpisodeNumber: 1,
302
			SeasonNumber:  1,
303
			ShowName:      "The Boys",
304
			ShowID:        "1398",
305
		},
306
	}
307
308
	mf := &moviefeed{
309
		nameCache: map[string]string{},
310
		api: fakeEpisodeAPI{
311
			episodes: map[string][]TMDBEpisode{
312
				"1398": episodes,
313
			},
314
		},
315
		shows: []string{"The Boys::1398"},
316
	}
317
318
	got, err := mf.fetchNewEpisodes()
319
	is.Err(t, err, nil)
320
	is.Equal(t, 1, len(got))
321
	is.Equal(t, 1001, got[0].ID)
322
}
323
324
func TestFetchNewEpisodesContinuesOnResolveError(t *testing.T) {
325
	mf := &moviefeed{
326
		nameCache: map[string]string{},
327
		api: fakeEpisodeAPI{
328
			episodes: map[string][]TMDBEpisode{},
329
		},
330
		shows: []string{"Does Not Exist", "tt1190634"},
331
	}
332
333
	got, err := mf.fetchNewEpisodes()
334
	is.Err(t, err, nil)
335
	is.Equal(t, 0, len(got))
336
}