arnaucube
arnaucube/configsPublic Self-hosted repository
configs/server-dockers/jellyfin-news/main_test.go
server-dockers/jellyfin-news/main_test.goRaw
  1package main
  2
  3import (
  4	"context"
  5	"fmt"
  6	"net/http"
  7	"net/http/httptest"
  8	"strings"
  9	"testing"
 10	"time"
 11)
 12
 13type fakeSource struct {
 14	start time.Time
 15	end   time.Time
 16	items []Item
 17	err   error
 18}
 19
 20func (f *fakeSource) AddedBetween(_ context.Context, start, end time.Time) ([]Item, error) {
 21	f.start, f.end = start, end
 22	return f.items, f.err
 23}
 24
 25func testApp(source *fakeSource) *app {
 26	return &app{
 27		jellyfin: source,
 28		location: time.UTC,
 29		labels:   DefaultLabels(),
 30		now: func() time.Time {
 31			return time.Date(2026, 9, 2, 12, 0, 0, 0, time.UTC)
 32		},
 33	}
 34}
 35
 36func TestCurrentWeek(t *testing.T) {
 37	source := &fakeSource{}
 38	request := httptest.NewRequest(http.MethodGet, "/api/current-week", nil)
 39	response := httptest.NewRecorder()
 40	testApp(source).routes().ServeHTTP(response, request)
 41
 42	if response.Code != http.StatusOK {
 43		t.Fatalf("status = %d, body = %q", response.Code, response.Body.String())
 44	}
 45	if got := response.Body.String(); !strings.HasPrefix(got, "2026-08-31 - 2026-09-02 jellyfin weekly update:") {
 46		t.Fatalf("body = %q", got)
 47	}
 48	wantStart := time.Date(2026, 8, 31, 0, 0, 0, 0, time.UTC)
 49	if !source.start.Equal(wantStart) || !source.end.Equal(testApp(source).now()) {
 50		t.Fatalf("range = %v to %v", source.start, source.end)
 51	}
 52}
 53
 54func TestStartOfWeekUsesMondayMidnight(t *testing.T) {
 55	location := time.FixedZone("test", 2*60*60)
 56	now := time.Date(2026, 9, 6, 23, 45, 0, 0, location) // Sunday
 57	want := time.Date(2026, 8, 31, 0, 0, 0, 0, location)
 58	if got := startOfWeek(now); !got.Equal(want) {
 59		t.Fatalf("startOfWeek() = %v, want %v", got, want)
 60	}
 61}
 62
 63func TestSelectedWeek(t *testing.T) {
 64	source := &fakeSource{}
 65	request := httptest.NewRequest(http.MethodGet, "/api/week?offset=-2", nil)
 66	response := httptest.NewRecorder()
 67	testApp(source).routes().ServeHTTP(response, request)
 68
 69	if response.Code != http.StatusOK {
 70		t.Fatalf("status = %d, body = %q", response.Code, response.Body.String())
 71	}
 72	if got := response.Body.String(); !strings.HasPrefix(got, "2026-08-17 - 2026-08-24 jellyfin weekly update:") {
 73		t.Fatalf("body = %q", got)
 74	}
 75}
 76
 77func TestSelectedWeekRejectsInvalidOffset(t *testing.T) {
 78	request := httptest.NewRequest(http.MethodGet, "/api/week?offset=0", nil)
 79	response := httptest.NewRecorder()
 80	testApp(&fakeSource{}).routes().ServeHTTP(response, request)
 81	if response.Code != http.StatusBadRequest {
 82		t.Fatalf("status = %d", response.Code)
 83	}
 84}
 85
 86func TestSelectedMonth(t *testing.T) {
 87	source := &fakeSource{}
 88	request := httptest.NewRequest(http.MethodGet, "/api/month?offset=-2", nil)
 89	response := httptest.NewRecorder()
 90	testApp(source).routes().ServeHTTP(response, request)
 91
 92	if response.Code != http.StatusOK {
 93		t.Fatalf("status = %d, body = %q", response.Code, response.Body.String())
 94	}
 95	if got := response.Body.String(); !strings.HasPrefix(got, "2026-07 jellyfin monthly update:") {
 96		t.Fatalf("body = %q", got)
 97	}
 98	if source.start.Month() != time.July || source.end.Month() != time.August {
 99		t.Fatalf("range = %v to %v", source.start, source.end)
100	}
101}
102
103func TestSelectedMonthRejectsInvalidOffset(t *testing.T) {
104	request := httptest.NewRequest(http.MethodGet, "/api/month?offset=0", nil)
105	response := httptest.NewRecorder()
106	testApp(&fakeSource{}).routes().ServeHTTP(response, request)
107	if response.Code != http.StatusBadRequest {
108		t.Fatalf("status = %d", response.Code)
109	}
110}
111
112func TestIndex(t *testing.T) {
113	request := httptest.NewRequest(http.MethodGet, "/", nil)
114	response := httptest.NewRecorder()
115	testApp(&fakeSource{}).routes().ServeHTTP(response, request)
116	if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), "Jellyfin updates") {
117		t.Fatalf("status = %d, body = %q", response.Code, response.Body.String())
118	}
119}
120
121type fakeWatched struct {
122	start, end time.Time
123	err        error
124}
125
126func (f *fakeWatched) WatchedBetween(_ context.Context, start, end time.Time) ([]WatchedTitle, error) {
127	f.start, f.end = start, end
128	return []WatchedTitle{{ID: "movie", Name: "Example", Type: "Movie", Users: 2}}, f.err
129}
130
131func TestWatchedPeriods(t *testing.T) {
132	for _, tc := range []struct{ query, start, end string }{
133		{"period=current-week", "2026-08-31T00:00:00Z", "2026-09-02T12:00:00Z"},
134		{"period=week&offset=-2", "2026-08-17T00:00:00Z", "2026-08-24T00:00:00Z"},
135		{"period=month&offset=-1", "2026-08-01T00:00:00Z", "2026-09-01T00:00:00Z"},
136	} {
137		t.Run(tc.query, func(t *testing.T) {
138			a := testApp(&fakeSource{})
139			source := &fakeWatched{}
140			a.jellystat = source
141			response := httptest.NewRecorder()
142			a.routes().ServeHTTP(response, httptest.NewRequest("GET", "/api/watched?"+tc.query, nil))
143			if response.Code != 200 || !strings.Contains(response.Body.String(), "Example — 2 users") {
144				t.Fatalf("response: %d %s", response.Code, response.Body.String())
145			}
146			if source.start.Format(time.RFC3339) != tc.start || source.end.Format(time.RFC3339) != tc.end {
147				t.Fatalf("range: %v to %v", source.start, source.end)
148			}
149			if response.Header().Get("Cache-Control") != "no-store" {
150				t.Fatal("missing no-store")
151			}
152		})
153	}
154}
155
156func TestWatchedErrors(t *testing.T) {
157	for _, tc := range []struct {
158		query  string
159		source watchedSource
160		status int
161	}{
162		{"period=current-week", nil, 503},
163		{"period=week&offset=0", nil, 400},
164		{"period=invalid", nil, 400},
165		{"period=current-week", &fakeWatched{err: fmt.Errorf("upstream failure")}, 502},
166	} {
167		a := testApp(&fakeSource{})
168		a.jellystat = tc.source
169		response := httptest.NewRecorder()
170		a.routes().ServeHTTP(response, httptest.NewRequest("GET", "/api/watched?"+tc.query, nil))
171		if response.Code != tc.status {
172			t.Fatalf("status = %d, want %d", response.Code, tc.status)
173		}
174	}
175}
176
177func TestWatchedButtonEscapesTranslation(t *testing.T) {
178	a := testApp(&fakeSource{})
179	a.labels.WatchedButton = "<Watch & copy>"
180	response := httptest.NewRecorder()
181	a.routes().ServeHTTP(response, httptest.NewRequest("GET", "/", nil))
182	if !strings.Contains(response.Body.String(), "&lt;Watch &amp; copy&gt;") {
183		t.Fatal("button translation not escaped")
184	}
185}