arnaucube
arnaucube/configsPublic Self-hosted repository
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}