Files
vibesonic/internal/subsonic/opensubsonic_misc_test.go
T

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")
}
}