148 lines
4.4 KiB
Go
148 lines
4.4 KiB
Go
package subsonic
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"git.noroutes.net/NoRoutes/vibetunes/internal/database"
|
|
)
|
|
|
|
func TestGetPodcastEpisode(t *testing.T) {
|
|
s, db := newTestServer(t)
|
|
u := createTestUser(t, s, "alice", "password123", false)
|
|
perms := u.Permissions
|
|
perms.Podcast = true
|
|
if err := database.SetUserPermissions(db, u.ID, perms); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
channel, err := database.CreatePodcastChannel(db, "http://example.com/feed.xml")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ep, err := database.UpsertPodcastEpisode(db, channel.ID, "guid1", "Episode One", "desc", "", "http://example.com/a.mp3", 100)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
srv := newSubsonicTestServer(t, s)
|
|
q := baseParams("alice", "password123")
|
|
q.Set("id", ep.ID)
|
|
q.Set("f", "json")
|
|
resp := getPath(t, srv, "/rest/getPodcastEpisode.view", q)
|
|
j := decodeJSON(t, resp)
|
|
got, ok := j["podcastEpisode"].(map[string]any)
|
|
if !ok || got["title"] != "Episode One" {
|
|
t.Fatalf("getPodcastEpisode = %v", j)
|
|
}
|
|
if got["channelId"] != channel.ID {
|
|
t.Fatalf("channelId = %v, want %v", got["channelId"], channel.ID)
|
|
}
|
|
}
|
|
|
|
func TestGetPodcastEpisodeNotFound(t *testing.T) {
|
|
s, db := newTestServer(t)
|
|
u := createTestUser(t, s, "alice", "password123", false)
|
|
perms := u.Permissions
|
|
perms.Podcast = true
|
|
if err := database.SetUserPermissions(db, u.ID, perms); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
srv := newSubsonicTestServer(t, s)
|
|
q := baseParams("alice", "password123")
|
|
q.Set("id", "pe_doesnotexist")
|
|
resp := getPath(t, srv, "/rest/getPodcastEpisode.view", q)
|
|
x := decodeXML(t, resp)
|
|
if x.Status != "failed" || x.Error == nil || x.Error.Code != ErrNotFound {
|
|
t.Fatalf("expected error 70, got %+v", x)
|
|
}
|
|
}
|
|
|
|
func TestGetPodcastEpisodeRequiresPermission(t *testing.T) {
|
|
s, db := newTestServer(t)
|
|
u := createTestUser(t, s, "alice", "password123", false)
|
|
perms := u.Permissions
|
|
perms.Podcast = false
|
|
if err := database.SetUserPermissions(db, u.ID, perms); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
srv := newSubsonicTestServer(t, s)
|
|
q := baseParams("alice", "password123")
|
|
q.Set("id", "pe_whatever")
|
|
resp := getPath(t, srv, "/rest/getPodcastEpisode.view", q)
|
|
x := decodeXML(t, resp)
|
|
if x.Status != "failed" || x.Error == nil || x.Error.Code != ErrUnauthorized {
|
|
t.Fatalf("expected error 50, got %+v", x)
|
|
}
|
|
}
|
|
|
|
func TestTopSongsByArtistID(t *testing.T) {
|
|
s, db := newTestServer(t)
|
|
createTestUser(t, s, "alice", "password123", false)
|
|
seedLibrary(t, s)
|
|
|
|
artists, err := database.ListArtists(db)
|
|
if err != nil || len(artists) == 0 {
|
|
t.Fatalf("ListArtists: %v %v", artists, err)
|
|
}
|
|
|
|
srv := newSubsonicTestServer(t, s)
|
|
q := baseParams("alice", "password123")
|
|
q.Set("id", artists[0].ID)
|
|
q.Set("f", "json")
|
|
resp := getPath(t, srv, "/rest/getTopSongs.view", q)
|
|
j := decodeJSON(t, resp)
|
|
topSongs, ok := j["topSongs"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("topSongs missing: %v", j)
|
|
}
|
|
songs, ok := topSongs["song"].([]any)
|
|
if !ok || len(songs) == 0 {
|
|
t.Fatalf("expected songs from getTopSongs by artist id, got %v", topSongs)
|
|
}
|
|
}
|
|
|
|
func TestTopSongsByArtistIDUnknown(t *testing.T) {
|
|
s, srv := newTestRecorderServer(t)
|
|
createTestUser(t, s, "alice", "password123", false)
|
|
|
|
q := baseParams("alice", "password123")
|
|
q.Set("id", "ar_doesnotexist")
|
|
resp := getPath(t, srv.URL, "/rest/getTopSongs.view", q)
|
|
x := decodeXML(t, resp)
|
|
if x.Status != "ok" {
|
|
t.Fatalf("unknown artist id should return ok with empty list, got %+v", x)
|
|
}
|
|
}
|
|
|
|
func TestStreamTranscodeOffsetSeeksReal(t *testing.T) {
|
|
s, srv := newTestRecorderServer(t)
|
|
createTestUser(t, s, "alice", "password123", false)
|
|
seedLibrary(t, s)
|
|
song := firstSong(t, s)
|
|
|
|
q := baseParams("alice", "password123")
|
|
q.Set("id", song.ID)
|
|
// The seeded fixture songs are ~1s long; ask to start after 1s so a
|
|
// naive "decode from zero" implementation would still succeed but a
|
|
// real seek should produce a very short (near-silent) stream.
|
|
q.Set("timeOffset", "1")
|
|
resp := getPath(t, srv.URL, "/rest/stream.view", q)
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("status = %d", resp.StatusCode)
|
|
}
|
|
// A transcode was actually triggered (not a direct byte stream) —
|
|
// confirmed by the response being a compressed audio Content-Type
|
|
// rather than a plain file passthrough with Accept-Ranges.
|
|
if resp.Header.Get("Accept-Ranges") == "bytes" {
|
|
t.Error("expected a transcoded (non-range-servable) response when timeOffset is set")
|
|
}
|
|
body, _ := io.ReadAll(resp.Body)
|
|
if len(body) == 0 {
|
|
t.Fatal("transcoded+offset stream returned no bytes")
|
|
}
|
|
}
|