configs/server-dockers/jellyfin-news/jellystat_test.go
server-dockers/jellyfin-news/jellystat_test.goRaw
1package main
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11 "time"
12)
13
14func TestJellystatHistoryRepeatsPaginationAndTypes(t *testing.T) {
15 start := time.Date(2026, 8, 1, 0, 0, 0, 0, time.UTC)
16 end := start.AddDate(0, 1, 0)
17 play := func(id, user, date string) playback { return playback{ItemID: id, UserID: user, Date: date} }
18 calls := map[string]int{}
19 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
20 if r.Header.Get("x-api-token") != "test-key" {
21 t.Error("missing API key")
22 }
23 if r.URL.Path == "/api/getHistory" {
24 if r.URL.Query().Get("filters") != "" {
25 t.Error("date filtering would hide historical repeats")
26 }
27 var rows []playback
28 switch r.URL.Query().Get("page") {
29 case "1":
30 rows = []playback{
31 {ItemID: "movie", UserID: "private-user-a", Date: end.Format(time.RFC3339), Results: []playback{play("movie", "private-user-a", end.Format(time.RFC3339)), play("movie", "private-user-a", start.Format(time.RFC3339))}},
32 play("show", "private-user-a", "2026-08-10T12:00:00Z"),
33 play("excluded", "private-user-a", end.Format(time.RFC3339)),
34 }
35 case "2":
36 rows = []playback{play("movie", "private-user-b", "2026-08-15T12:00:00Z"), play("show", "private-user-a", "2026-08-11T12:00:00Z"), play("music", "private-user-a", "2026-08-10T12:00:00Z")}
37 default:
38 t.Error("unexpected page")
39 }
40 for i := range rows {
41 if rows[i].Results == nil {
42 rows[i].Results = []playback{rows[i]}
43 }
44 }
45 json.NewEncoder(w).Encode(map[string]any{"pages": 2, "results": rows})
46 return
47 }
48 if r.Method != http.MethodPost || r.URL.Path != "/api/getItemDetails" {
49 t.Errorf("unexpected request %s", r.URL.Path)
50 }
51 var body map[string]string
52 json.NewDecoder(r.Body).Decode(&body)
53 id := body["Id"]
54 calls[id]++
55 kind := map[string]string{"movie": "Movie", "show": "Series", "music": "Audio"}[id]
56 json.NewEncoder(w).Encode([]map[string]string{{"Type": kind, "Name": id}})
57 }))
58 defer server.Close()
59 client, err := NewJellystat(server.URL, "test-key")
60 if err != nil {
61 t.Fatal(err)
62 }
63 titles, err := client.WatchedBetween(context.Background(), start, end)
64 if err != nil {
65 t.Fatal(err)
66 }
67 got := FormatWatched(start, end, true, titles, DefaultLabels())
68 want := "2026-08 jellyfin watched recap:\n\nMovies:\n• movie — 2 users\n\nShows:\n• show"
69 if got != want {
70 t.Fatalf("got %q, want %q", got, want)
71 }
72 if calls["show"] != 1 || calls["excluded"] != 0 {
73 t.Fatalf("metadata calls = %v", calls)
74 }
75}
76
77func TestJellystatFailures(t *testing.T) {
78 for _, tc := range []struct {
79 name, body string
80 status int
81 }{
82 {"unauthorized", "secret upstream body", 401},
83 {"invalid JSON", "not JSON", 200},
84 {"missing results", `{}`, 200},
85 {"bad timestamp", `{"pages":1,"results":[{"ActivityDateInserted":"bad"}]}`, 200},
86 {"incomplete pagination", `{"pages":2,"results":[]}`, 200},
87 } {
88 t.Run(tc.name, func(t *testing.T) {
89 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(tc.status); fmt.Fprint(w, tc.body) }))
90 defer server.Close()
91 client, _ := NewJellystat(server.URL, "test-key")
92 _, err := client.WatchedBetween(context.Background(), time.Now().Add(-time.Hour), time.Now())
93 if err == nil || strings.Contains(err.Error(), "secret") {
94 t.Fatalf("error = %v", err)
95 }
96 })
97 }
98}
99
100func TestWatchedTranslations(t *testing.T) {
101 labels := DefaultLabels()
102 labels.WatchedUpdate = "Vistos"
103 labels.Users = "usuarios"
104 labels.Movies = "Películas"
105 labels.NoWatchedItems = "Nada"
106 now := time.Now()
107 got := FormatWatched(now, now, false, []WatchedTitle{{Name: "Example", Type: "Movie", Users: 2}}, labels)
108 for _, text := range []string{"Vistos:", "Películas:", "2 usuarios"} {
109 if !strings.Contains(got, text) {
110 t.Fatal(got)
111 }
112 }
113 if got := FormatWatched(now, now, false, nil, labels); !strings.HasSuffix(got, "\n\nNada") {
114 t.Fatal(got)
115 }
116}
117
118func TestJellystatMissingGroupedHistory(t *testing.T) {
119 start := time.Date(2026, 8, 1, 0, 0, 0, 0, time.UTC)
120 end := start.AddDate(0, 1, 0)
121 pages := 0
122 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
123 switch r.URL.Path {
124 case "/api/getHistory":
125 fmt.Fprint(w, `{"pages":1,"results":[{"NowPlayingItemId":"movie","UserId":"a","ActivityDateInserted":"2026-09-02T00:00:00Z","results":null}]}`)
126 case "/api/getItemHistory":
127 pages++
128 if r.Method != "POST" || !strings.Contains(r.URL.Query().Get("filters"), "ActivityDateInserted") {
129 t.Error("missing raw history filters")
130 }
131 switch r.URL.Query().Get("page") {
132 case "1":
133 fmt.Fprint(w, `{"pages":2,"results":[{"NowPlayingItemId":"movie","UserId":"a","ActivityDateInserted":"2026-08-01T00:00:00Z"}]}`)
134 case "2":
135 fmt.Fprint(w, `{"pages":2,"results":[{"NowPlayingItemId":"movie","UserId":"b","ActivityDateInserted":"2026-08-15T00:00:00Z"},{"NowPlayingItemId":"movie","UserId":"c","ActivityDateInserted":"2026-09-01T00:00:00Z"}]}`)
136 default:
137 t.Error("unexpected page")
138 }
139 case "/api/getItemDetails":
140 fmt.Fprint(w, `[{"Name":"Movie","Type":"Movie"}]`)
141 default:
142 t.Error("unexpected endpoint")
143 }
144 }))
145 defer server.Close()
146 client, _ := NewJellystat(server.URL, "test-key")
147 titles, err := client.WatchedBetween(context.Background(), start, end)
148 if err != nil {
149 t.Fatal(err)
150 }
151 if len(titles) != 1 || titles[0].Users != 2 || pages != 2 {
152 t.Fatalf("titles=%v pages=%d", titles, pages)
153 }
154}