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}