410 lines
12 KiB
Go
410 lines
12 KiB
Go
package plex
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// newTestClient returns a Client pointed at the given test server.
|
|
func newTestClient(srv *httptest.Server) *Client {
|
|
return NewClient(srv.URL, "test-token")
|
|
}
|
|
|
|
func TestPing_OK(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Query().Get("X-Plex-Token") != "test-token" {
|
|
t.Errorf("expected token in query, got %q", r.URL.Query().Get("X-Plex-Token"))
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"MediaContainer":{"friendlyName":"My Plex"}}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
if err := newTestClient(srv).Ping(); err != nil {
|
|
t.Fatalf("Ping() unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPing_Unauthorized(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
err := newTestClient(srv).Ping()
|
|
if err == nil {
|
|
t.Fatal("Ping() expected error on 401, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "token invalid") {
|
|
t.Errorf("expected 'token invalid' in error, got %q", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestPing_ServerError(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
err := newTestClient(srv).Ping()
|
|
if err == nil {
|
|
t.Fatal("Ping() expected error on 500, got nil")
|
|
}
|
|
}
|
|
|
|
func TestMusicSections_FiltersByType(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{
|
|
"MediaContainer": {
|
|
"Directory": [
|
|
{"key": "1", "type": "artist", "title": "Music"},
|
|
{"key": "2", "type": "movie", "title": "Movies"},
|
|
{"key": "3", "type": "artist", "title": "Jazz"}
|
|
]
|
|
}
|
|
}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
sections, err := newTestClient(srv).MusicSections()
|
|
if err != nil {
|
|
t.Fatalf("MusicSections() error: %v", err)
|
|
}
|
|
if len(sections) != 2 {
|
|
t.Fatalf("expected 2 music sections, got %d", len(sections))
|
|
}
|
|
if sections[0].Key != "1" || sections[0].Title != "Music" {
|
|
t.Errorf("unexpected section[0]: %+v", sections[0])
|
|
}
|
|
if sections[1].Key != "3" || sections[1].Title != "Jazz" {
|
|
t.Errorf("unexpected section[1]: %+v", sections[1])
|
|
}
|
|
}
|
|
|
|
func TestMusicSections_Empty(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{"MediaContainer":{"Directory":[{"key":"1","type":"movie","title":"Movies"}]}}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
sections, err := newTestClient(srv).MusicSections()
|
|
if err != nil {
|
|
t.Fatalf("MusicSections() unexpected error: %v", err)
|
|
}
|
|
if len(sections) != 0 {
|
|
t.Errorf("expected 0 music sections, got %d", len(sections))
|
|
}
|
|
}
|
|
|
|
func TestMusicSections_LibraryFilter(t *testing.T) {
|
|
serverJSON := `{"MediaContainer":{"Directory":[
|
|
{"key":"1","type":"artist","title":"Music"},
|
|
{"key":"2","type":"artist","title":"Jazz"},
|
|
{"key":"3","type":"artist","title":"Classical"}
|
|
]}}`
|
|
|
|
tests := []struct {
|
|
name string
|
|
libraries []string
|
|
wantLen int
|
|
wantTitles []string
|
|
wantKeys []string
|
|
}{
|
|
{
|
|
name: "filter to subset",
|
|
libraries: []string{"Jazz", "Classical"},
|
|
wantLen: 2,
|
|
wantTitles: []string{"Jazz", "Classical"},
|
|
},
|
|
{
|
|
name: "case-insensitive match",
|
|
libraries: []string{"MUSIC"},
|
|
wantLen: 1,
|
|
wantKeys: []string{"1"},
|
|
},
|
|
{
|
|
name: "no match returns empty",
|
|
libraries: []string{"DoesNotExist"},
|
|
wantLen: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(serverJSON))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
sections, err := NewClient(srv.URL, "test-token", tt.libraries...).MusicSections()
|
|
if err != nil {
|
|
t.Fatalf("MusicSections() error: %v", err)
|
|
}
|
|
if len(sections) != tt.wantLen {
|
|
t.Fatalf("got %d sections, want %d: %v", len(sections), tt.wantLen, sections)
|
|
}
|
|
for i, title := range tt.wantTitles {
|
|
if sections[i].Title != title {
|
|
t.Errorf("sections[%d].Title = %q, want %q", i, sections[i].Title, title)
|
|
}
|
|
}
|
|
for i, key := range tt.wantKeys {
|
|
if sections[i].Key != key {
|
|
t.Errorf("sections[%d].Key = %q, want %q", i, sections[i].Key, key)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAlbums_RequestsType9(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Query().Get("type") != "9" {
|
|
t.Errorf("expected type=9 in request, got %q", r.URL.Query().Get("type"))
|
|
}
|
|
if !strings.HasSuffix(r.URL.Path, "/library/sections/3/all") {
|
|
t.Errorf("unexpected path %q", r.URL.Path)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{
|
|
"MediaContainer": {
|
|
"totalSize": 2,
|
|
"Metadata": [
|
|
{
|
|
"ratingKey": "456",
|
|
"title": "Kind of Blue",
|
|
"parentTitle": "Miles Davis",
|
|
"year": 1959,
|
|
"leafCount": 5
|
|
},
|
|
{
|
|
"ratingKey": "457",
|
|
"title": "Bitches Brew",
|
|
"parentTitle": "Miles Davis",
|
|
"year": 1970,
|
|
"leafCount": 4
|
|
}
|
|
]
|
|
}
|
|
}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
albums, err := newTestClient(srv).Albums("3")
|
|
if err != nil {
|
|
t.Fatalf("Albums() error: %v", err)
|
|
}
|
|
if len(albums) != 2 {
|
|
t.Fatalf("expected 2 albums, got %d", len(albums))
|
|
}
|
|
a := albums[0]
|
|
if a.RatingKey != "456" || a.Title != "Kind of Blue" || a.ArtistName != "Miles Davis" || a.Year != 1959 || a.TrackCount != 5 {
|
|
t.Errorf("unexpected album[0]: %+v", a)
|
|
}
|
|
}
|
|
|
|
func TestAlbums_Paginates(t *testing.T) {
|
|
callCount := 0
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
callCount++
|
|
start := r.URL.Query().Get("X-Plex-Container-Start")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
switch start {
|
|
case "0", "":
|
|
w.Write([]byte(`{"MediaContainer":{"totalSize":2,"Metadata":[{"ratingKey":"1","title":"A","parentTitle":"Art","year":2000,"leafCount":1}]}}`))
|
|
case "300":
|
|
// Second page — but totalSize=2 means we should never reach here with pageSize=300.
|
|
// This tests that we stop after the first page when totalSize <= pageSize.
|
|
t.Errorf("unexpected second page request (offset=%s)", start)
|
|
w.Write([]byte(`{"MediaContainer":{"totalSize":2,"Metadata":[]}}`))
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
|
|
albums, err := newTestClient(srv).Albums("1")
|
|
if err != nil {
|
|
t.Fatalf("Albums() error: %v", err)
|
|
}
|
|
if len(albums) != 1 {
|
|
t.Errorf("expected 1 album, got %d", len(albums))
|
|
}
|
|
if callCount != 1 {
|
|
t.Errorf("expected 1 API call, got %d", callCount)
|
|
}
|
|
}
|
|
|
|
func TestTracks_MapsAllFields(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !strings.HasSuffix(r.URL.Path, "/library/metadata/456/children") {
|
|
t.Errorf("unexpected path %q", r.URL.Path)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{
|
|
"MediaContainer": {
|
|
"Metadata": [
|
|
{
|
|
"ratingKey": "789",
|
|
"title": "So What",
|
|
"grandparentTitle": "Miles Davis",
|
|
"parentTitle": "Kind of Blue",
|
|
"year": 1959,
|
|
"index": 1,
|
|
"duration": 565000,
|
|
"Media": [{"Part": [{"key": "/library/parts/100/111/So_What.flac"}]}]
|
|
},
|
|
{
|
|
"ratingKey": "790",
|
|
"title": "Freddie Freeloader",
|
|
"grandparentTitle": "Miles Davis",
|
|
"parentTitle": "Kind of Blue",
|
|
"year": 1959,
|
|
"index": 2,
|
|
"duration": 586000,
|
|
"Media": [{"Part": [{"key": "/library/parts/101/222/Freddie.flac"}]}]
|
|
}
|
|
]
|
|
}
|
|
}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
tracks, err := newTestClient(srv).Tracks("456")
|
|
if err != nil {
|
|
t.Fatalf("Tracks() error: %v", err)
|
|
}
|
|
if len(tracks) != 2 {
|
|
t.Fatalf("expected 2 tracks, got %d", len(tracks))
|
|
}
|
|
tr := tracks[0]
|
|
if tr.RatingKey != "789" {
|
|
t.Errorf("RatingKey: got %q, want %q", tr.RatingKey, "789")
|
|
}
|
|
if tr.Title != "So What" {
|
|
t.Errorf("Title: got %q, want %q", tr.Title, "So What")
|
|
}
|
|
if tr.ArtistName != "Miles Davis" {
|
|
t.Errorf("ArtistName: got %q, want %q", tr.ArtistName, "Miles Davis")
|
|
}
|
|
if tr.AlbumName != "Kind of Blue" {
|
|
t.Errorf("AlbumName: got %q, want %q", tr.AlbumName, "Kind of Blue")
|
|
}
|
|
if tr.Year != 1959 {
|
|
t.Errorf("Year: got %d, want 1959", tr.Year)
|
|
}
|
|
if tr.TrackNumber != 1 {
|
|
t.Errorf("TrackNumber: got %d, want 1", tr.TrackNumber)
|
|
}
|
|
if tr.Duration != 565000 {
|
|
t.Errorf("Duration: got %d, want 565000", tr.Duration)
|
|
}
|
|
if tr.PartKey != "/library/parts/100/111/So_What.flac" {
|
|
t.Errorf("PartKey: got %q, want /library/parts/100/111/So_What.flac", tr.PartKey)
|
|
}
|
|
}
|
|
|
|
func TestTracks_MissingMedia(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{
|
|
"MediaContainer": {
|
|
"Metadata": [
|
|
{"ratingKey": "1", "title": "Track Without Media", "Media": []}
|
|
]
|
|
}
|
|
}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
tracks, err := newTestClient(srv).Tracks("42")
|
|
if err != nil {
|
|
t.Fatalf("Tracks() error: %v", err)
|
|
}
|
|
if len(tracks) != 1 {
|
|
t.Fatalf("expected 1 track, got %d", len(tracks))
|
|
}
|
|
if tracks[0].PartKey != "" {
|
|
t.Errorf("expected empty PartKey for track with no media, got %q", tracks[0].PartKey)
|
|
}
|
|
}
|
|
|
|
func TestStreamURL_Format(t *testing.T) {
|
|
c := NewClient("http://192.168.1.10:32400", "mytoken")
|
|
got := c.StreamURL("/library/parts/100/111/file.flac")
|
|
want := "http://192.168.1.10:32400/library/parts/100/111/file.flac?X-Plex-Token=mytoken"
|
|
if got != want {
|
|
t.Errorf("StreamURL:\n got %q\n want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestStreamURL_TokenEncoded(t *testing.T) {
|
|
c := NewClient("http://192.168.1.10:32400", "tok en+special")
|
|
got := c.StreamURL("/library/parts/1/2/file.mp3")
|
|
if !strings.Contains(got, "X-Plex-Token=tok+en%2Bspecial") {
|
|
t.Errorf("StreamURL token not URL-encoded: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestSearch_SendsCorrectParams(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Query().Get("query") != "Miles Davis" {
|
|
t.Errorf("expected query=Miles Davis, got %q", r.URL.Query().Get("query"))
|
|
}
|
|
if r.URL.Query().Get("type") != "10" {
|
|
t.Errorf("expected type=10, got %q", r.URL.Query().Get("type"))
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{"MediaContainer":{"Metadata":[]}}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
tracks, err := newTestClient(srv).Search("Miles Davis")
|
|
if err != nil {
|
|
t.Fatalf("Search() error: %v", err)
|
|
}
|
|
if len(tracks) != 0 {
|
|
t.Errorf("expected 0 tracks, got %d", len(tracks))
|
|
}
|
|
}
|
|
|
|
func TestSearch_EmptyQueryNoRequest(t *testing.T) {
|
|
called := false
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
called = true
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
tracks, err := newTestClient(srv).Search("")
|
|
if err != nil {
|
|
t.Fatalf("Search(\"\") unexpected error: %v", err)
|
|
}
|
|
if tracks != nil {
|
|
t.Errorf("expected nil tracks for empty query, got %v", tracks)
|
|
}
|
|
if called {
|
|
t.Error("Search(\"\") should not make an HTTP request")
|
|
}
|
|
}
|
|
|
|
func TestRequestHeaders(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Header.Get("Accept") != "application/json" {
|
|
t.Errorf("expected Accept: application/json, got %q", r.Header.Get("Accept"))
|
|
}
|
|
if r.Header.Get("X-Plex-Product") != "cliamp" {
|
|
t.Errorf("expected X-Plex-Product: cliamp, got %q", r.Header.Get("X-Plex-Product"))
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{"MediaContainer":{}}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
_ = newTestClient(srv).Ping()
|
|
}
|