Files
memby/server/internal/api/trailers_test.go
T

102 lines
3.7 KiB
Go
Raw Normal View History

2026-08-12 11:05:07 +12:00
package api
import (
"bytes"
"encoding/json"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"sort"
"strings"
"testing"
"time"
"github.com/ponzischeme89/memby/server/internal/config"
"github.com/ponzischeme89/memby/server/internal/emby"
"github.com/ponzischeme89/memby/server/internal/store"
"github.com/ponzischeme89/memby/server/internal/trailer"
)
type trailerRoundTripFunc func(*http.Request) (*http.Response, error)
func (fn trailerRoundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
return fn(request)
}
func TestResolveTrailerFallsThroughProviders(t *testing.T) {
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case strings.HasSuffix(r.URL.Path, "/LocalTrailers"):
writeJSON(w, http.StatusOK, []any{})
case strings.Contains(r.URL.Path, "/Items/film-1"):
writeJSON(w, http.StatusOK, map[string]any{
"Id": "film-1", "Name": "A Film",
"RemoteTrailers": []map[string]string{
{"Name": "Official trailer", "Url": "https://trailers.apple.com/missing.mov"},
{"Name": "Official trailer", "Url": "https://youtu.be/dQw4w9WgXcQ"},
},
})
default:
http.NotFound(w, r)
}
}))
defer upstream.Close()
resolverClient := &http.Client{Transport: trailerRoundTripFunc(func(request *http.Request) (*http.Response, error) {
status := http.StatusOK
body := ""
headers := http.Header{}
switch request.URL.Host {
case "trailers.apple.com":
status = http.StatusNotFound
case "www.youtube.com":
body = `{"playabilityStatus":{"status":"OK"},"streamingData":{"formats":[` +
`{"url":"https://media.example/trailer.mp4","mimeType":"video/mp4; codecs=avc1,mp4a","height":720}]}}`
headers.Set("Content-Type", "application/json")
case "media.example":
status = http.StatusPartialContent
headers.Set("Content-Type", "video/mp4")
default:
t.Fatalf("unexpected trailer request: %s", request.URL)
}
return &http.Response{
StatusCode: status, Header: headers,
Body: io.NopCloser(strings.NewReader(body)), Request: request,
}, nil
})}
server := &Server{
cfg: config.Config{ItemTTL: time.Minute},
emby: emby.New(upstream.URL, upstream.URL, "MbyATV", time.Second),
trailers: trailer.New(resolverClient),
log: slog.New(slog.NewTextHandler(io.Discard, nil)),
}
request := httptest.NewRequest(http.MethodPost, "/v1/items/film-1/trailers/resolve", bytes.NewBufferString(`{}`))
request.SetPathValue("id", "film-1")
recorder := httptest.NewRecorder()
server.handleResolveTrailer(recorder, request, store.Session{EmbyUserID: "user", EmbyToken: "token"})
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, body = %s", recorder.Code, recorder.Body.String())
}
var response trailerPlaybackResponse
if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil {
t.Fatal(err)
}
if response.Provider != "youtube" || response.URL != "https://media.example/trailer.mp4" {
t.Fatalf("unexpected response: %+v", response)
}
}
func TestRemoteTrailerPriorityPrefersOfficialAppleThenYouTube(t *testing.T) {
candidates := []trailerCandidate{
{ID: "youtube-other", Provider: "youtube", Priority: remoteTrailerPriority("youtube", "Trailer")},
{ID: "apple-official", Provider: "apple", Priority: remoteTrailerPriority("apple", "Trailer")},
{ID: "youtube-official", Provider: "youtube", Priority: remoteTrailerPriority("youtube", "Official trailer")},
{ID: "local", Provider: "local", Priority: 20},
}
sort.SliceStable(candidates, func(i, j int) bool { return candidates[i].Priority < candidates[j].Priority })
if candidates[0].ID != "apple-official" || candidates[1].ID != "youtube-official" || candidates[2].ID != "local" {
t.Fatalf("unexpected order: %+v", candidates)
}
}