arnaucube
arnaucube/jellyfin-toolsPublic Self-hosted repository
jellyfin-tools/jellyfin-year-review/scripts/e2e-server.go
jellyfin-year-review/scripts/e2e-server.goRaw
  1// This controlled test service binds loopback only and uses disposable synthetic data.
  2// Production has no demo login or test-service mode.
  3package main
  4
  5import (
  6	"bytes"
  7	"context"
  8	"encoding/json"
  9	"fmt"
 10	"image"
 11	"image/jpeg"
 12	"net/http"
 13	"net/http/httptest"
 14	"os"
 15	"path/filepath"
 16	"reelreview/internal/artwork"
 17	"reelreview/internal/auth"
 18	"strings"
 19
 20	"reelreview/internal/generate"
 21	"reelreview/internal/server"
 22	"reelreview/internal/source"
 23)
 24
 25func main() {
 26	root, e := os.MkdirTemp("", "reel-e2e-")
 27	if e != nil {
 28		panic(e)
 29	}
 30	defer os.RemoveAll(root)
 31	data := filepath.Join(root, "data")
 32	snapshot, e := source.Load("testdata/snapshot.json")
 33	if e != nil {
 34		panic(e)
 35	}
 36	// Give the synthetic snapshot real UUID-shaped media IDs and fetch posters
 37	// from a controlled generation-only source that closes before serving starts.
 38	ids := map[string]string{}
 39	for i, item := range snapshot.Items {
 40		ids[item.ID] = fmt.Sprintf("%032x", i+1)
 41	}
 42	for i := range snapshot.Items {
 43		item := &snapshot.Items[i]
 44		item.ID = ids[item.ID]
 45		item.SeriesID = ids[item.SeriesID]
 46		item.ImageTag = "fixture-image-v1"
 47	}
 48	for i := range snapshot.Playback {
 49		snapshot.Playback[i].ItemID = ids[snapshot.Playback[i].ItemID]
 50	}
 51	var jpegBytes bytes.Buffer
 52	if e = jpeg.Encode(&jpegBytes, image.NewRGBA(image.Rect(0, 0, 2, 3)), nil); e != nil {
 53		panic(e)
 54	}
 55	images := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 56		if !strings.Contains(r.Header.Get("Authorization"), `Token="fixture-art-key"`) {
 57			w.WriteHeader(401)
 58			return
 59		}
 60		w.Header().Set("Content-Type", "image/jpeg")
 61		_, _ = w.Write(jpegBytes.Bytes())
 62	}))
 63	imageSource, e := source.NewJellyfin(images.URL)
 64	if e != nil {
 65		panic(e)
 66	}
 67	posters := artwork.New(imageSource, "fixture-art-key", filepath.Join(root, "cache"))
 68
 69	for _, year := range []int{2024, 2025} {
 70		c := generate.DefaultConfig(year)
 71		rs, e := generate.Compute(snapshot, c)
 72		if e != nil {
 73			panic(e)
 74		}
 75		assets, warnings := posters.Resolve(context.Background(), snapshot.Items, rs)
 76		if len(warnings) > 0 {
 77			panic("synthetic posters unavailable")
 78		}
 79		if e = generate.Publish(data, rs, assets); e != nil {
 80			panic(e)
 81		}
 82	}
 83	images.Close()
 84	if os.Getenv("REEL_E2E_AUTH_MODE") == "offline" {
 85		b, e := os.ReadFile("testdata/offline-auth.json")
 86		if e != nil {
 87			panic(e)
 88		}
 89		path := filepath.Join(root, "users.json")
 90		if e = os.WriteFile(path, b, 0600); e != nil {
 91			panic(e)
 92		}
 93		p, e := auth.NewOffline(path)
 94		if e != nil {
 95			panic(e)
 96		}
 97		s, e := server.NewWithAuth(data, "web/build", "http://127.0.0.1:18180", p)
 98		if e != nil {
 99			panic(e)
100		}
101		defer s.Close()
102		fmt.Println("SYNTHETIC OFFLINE TEST SERVICE: no Jellyfin client or server")
103		if e = server.HTTPServer("127.0.0.1:18180", s).ListenAndServe(); e != nil {
104			panic(e)
105		}
106		return
107	}
108
109	revoked := false
110	jf := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
111		switch r.URL.Path {
112		case "/Users/AuthenticateByName":
113			var b map[string]string
114			json.NewDecoder(r.Body).Decode(&b)
115			if b["Username"] != "fixture" || b["Pw"] != "fixture-only" {
116				w.WriteHeader(401)
117				return
118			}
119			revoked = false
120			json.NewEncoder(w).Encode(map[string]string{"AccessToken": "test-service-token"})
121		case "/Users/Me":
122			if revoked || !strings.Contains(r.Header.Get("Authorization"), `Token="test-service-token"`) {
123				w.WriteHeader(401)
124				return
125			}
126			json.NewEncoder(w).Encode(map[string]any{"Id": snapshot.Users[0].ID, "Name": "Fixture Viewer"})
127		case "/Sessions/Logout":
128			revoked = true
129			w.WriteHeader(204)
130		default:
131			w.WriteHeader(404)
132		}
133	}))
134	defer jf.Close()
135	j, e := source.NewJellyfin(jf.URL)
136	if e != nil {
137		panic(e)
138	}
139	s, e := server.New(data, "web/build", "http://127.0.0.1:18180", j)
140	if e != nil {
141		panic(e)
142	}
143	defer s.Close()
144	fmt.Println("SYNTHETIC TEST SERVICE on loopback :18180")
145	h := server.HTTPServer("127.0.0.1:18180", s)
146	if e = h.ListenAndServe(); e != nil {
147		panic(e)
148	}
149}