Send Subsonic formatted response on marshalling errors

This commit is contained in:
Deluan 2024-02-17 10:39:29 -05:00
parent 97c7e5daaf
commit 176329343a
7 changed files with 71 additions and 52 deletions

View File

@ -39,12 +39,14 @@ var _ = Describe("Helpers", func() {
m := &Model{ID: "123", AlbumId: "456", CreatedAt: now, UpdatedAt: &now, PlayCount: 2}
args, err := toSQLArgs(m)
Expect(err).To(BeNil())
Expect(args).To(HaveKeyWithValue("id", "123"))
Expect(args).To(HaveKeyWithValue("album_id", "456"))
Expect(args).To(HaveKeyWithValue("play_count", 2))
Expect(args).To(HaveKeyWithValue("updated_at", now.Format(time.RFC3339Nano)))
Expect(args).To(HaveKeyWithValue("created_at", now.Format(time.RFC3339Nano)))
Expect(args).ToNot(HaveKey("Embed"))
Expect(args).To(SatisfyAll(
HaveKeyWithValue("id", "123"),
HaveKeyWithValue("album_id", "456"),
HaveKeyWithValue("play_count", 2),
HaveKeyWithValue("updated_at", now.Format(time.RFC3339Nano)),
HaveKeyWithValue("created_at", now.Format(time.RFC3339Nano)),
Not(HaveKey("Embed")),
))
})
})

View File

@ -31,13 +31,15 @@ Input #0, ape, from './Capture/02 01 - Symphony No. 5 in C minor, Op. 67 I. Alle
CatalogNumber : PLD 1201
`
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
Expect(md).To(HaveKeyWithValue("catalognumber", []string{"PLD 1201"}))
Expect(md).To(HaveKeyWithValue("musicbrainz_trackid", []string{"ffe06940-727a-415a-b608-b7e45737f9d8"}))
Expect(md).To(HaveKeyWithValue("musicbrainz_albumid", []string{"71eb5e4a-90e2-4a31-a2d1-a96485fcb667"}))
Expect(md).To(HaveKeyWithValue("musicbrainz_artistid", []string{"1f9df192-a621-4f54-8850-2c5373b7eac9"}))
Expect(md).To(HaveKeyWithValue("musicbrainz_albumartistid", []string{"89ad4ac3-39f7-470e-963a-56509c546377"}))
Expect(md).To(HaveKeyWithValue("musicbrainz_albumtype", []string{"album"}))
Expect(md).To(HaveKeyWithValue("musicbrainz_albumcomment", []string{"MP3"}))
Expect(md).To(SatisfyAll(
HaveKeyWithValue("catalognumber", []string{"PLD 1201"}),
HaveKeyWithValue("musicbrainz_trackid", []string{"ffe06940-727a-415a-b608-b7e45737f9d8"}),
HaveKeyWithValue("musicbrainz_albumid", []string{"71eb5e4a-90e2-4a31-a2d1-a96485fcb667"}),
HaveKeyWithValue("musicbrainz_artistid", []string{"1f9df192-a621-4f54-8850-2c5373b7eac9"}),
HaveKeyWithValue("musicbrainz_albumartistid", []string{"89ad4ac3-39f7-470e-963a-56509c546377"}),
HaveKeyWithValue("musicbrainz_albumtype", []string{"album"}),
HaveKeyWithValue("musicbrainz_albumcomment", []string{"MP3"}),
))
})
It("detects embedded cover art correctly", func() {
@ -244,14 +246,16 @@ Input #0, mp3, from '/Users/deluan/Downloads/椎名林檎 - 加爾基 精液 栗
ALBUMARTISTSORT : Shiina, Ringo
`
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
Expect(md).To(HaveKeyWithValue("title", []string{"ドツペルゲンガー"}))
Expect(md).To(HaveKeyWithValue("album", []string{"加爾基 精液 栗ノ花"}))
Expect(md).To(HaveKeyWithValue("artist", []string{"椎名林檎"}))
Expect(md).To(HaveKeyWithValue("album_artist", []string{"椎名林檎"}))
Expect(md).To(HaveKeyWithValue("title-sort", []string{"Dopperugengā"}))
Expect(md).To(HaveKeyWithValue("albumsort", []string{"Kalk Samen Kuri No Hana"}))
Expect(md).To(HaveKeyWithValue("artist_sort", []string{"Shiina, Ringo"}))
Expect(md).To(HaveKeyWithValue("albumartistsort", []string{"Shiina, Ringo"}))
Expect(md).To(SatisfyAll(
HaveKeyWithValue("title", []string{"ドツペルゲンガー"}),
HaveKeyWithValue("album", []string{"加爾基 精液 栗ノ花"}),
HaveKeyWithValue("artist", []string{"椎名林檎"}),
HaveKeyWithValue("album_artist", []string{"椎名林檎"}),
HaveKeyWithValue("title-sort", []string{"Dopperugengā"}),
HaveKeyWithValue("albumsort", []string{"Kalk Samen Kuri No Hana"}),
HaveKeyWithValue("artist_sort", []string{"Shiina, Ringo"}),
HaveKeyWithValue("albumartistsort", []string{"Shiina, Ringo"}),
))
})
It("ignores cover comment", func() {
@ -310,11 +314,12 @@ Input #0, mp3, from '/Users/deluan/Music/Music/Media/_/Wyclef Jean - From the Hu
replaygain: track gain - -1.480000, track peak - 0.000011, album gain - 3.215180, album peak - 0.000021,
`
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
Expect(md).To(HaveKeyWithValue("replaygain_track_gain", []string{"-1.48 dB"}))
Expect(md).To(HaveKeyWithValue("replaygain_track_peak", []string{"0.4512"}))
Expect(md).To(HaveKeyWithValue("replaygain_album_gain", []string{"+3.21518 dB"}))
Expect(md).To(HaveKeyWithValue("replaygain_album_peak", []string{"0.9125"}))
Expect(md).To(SatisfyAll(
HaveKeyWithValue("replaygain_track_gain", []string{"-1.48 dB"}),
HaveKeyWithValue("replaygain_track_peak", []string{"0.4512"}),
HaveKeyWithValue("replaygain_album_gain", []string{"+3.21518 dB"}),
HaveKeyWithValue("replaygain_album_peak", []string{"0.9125"}),
))
})
It("parses lyrics with language code", func() {
@ -327,12 +332,14 @@ Input #0, mp3, from '/Users/deluan/Music/Music/Media/_/Wyclef Jean - From the Hu
: [00:02.50]unspecified
`
md, _ := e.extractMetadata("tests/fixtures/test.mp3", output)
Expect(md).To(HaveKeyWithValue("lyrics-eng", []string{
"[00:00.00]This is\n[00:02.50]English",
}))
Expect(md).To(HaveKeyWithValue("lyrics-xxx", []string{
"[00:00.00]This is\n[00:02.50]unspecified",
}))
Expect(md).To(SatisfyAll(
HaveKeyWithValue("lyrics-eng", []string{
"[00:00.00]This is\n[00:02.50]English",
}),
HaveKeyWithValue("lyrics-xxx", []string{
"[00:00.00]This is\n[00:02.50]unspecified",
}),
))
})
It("parses normal LYRICS tag", func() {

View File

@ -233,7 +233,7 @@ func h501(r chi.Router, paths ...string) {
for _, path := range paths {
handle := func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Cache-Control", "no-cache")
w.WriteHeader(501)
w.WriteHeader(http.StatusNotImplemented)
_, _ = w.Write([]byte("This endpoint is not implemented, but may be in future releases"))
}
addHandler(r, path, handle)
@ -244,7 +244,7 @@ func h501(r chi.Router, paths ...string) {
func h410(r chi.Router, paths ...string) {
for _, path := range paths {
handle := func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(410)
w.WriteHeader(http.StatusGone)
_, _ = w.Write([]byte("This endpoint will not be implemented"))
}
addHandler(r, path, handle)
@ -276,7 +276,7 @@ func mapToSubsonicError(err error) subError {
func sendError(w http.ResponseWriter, r *http.Request, err error) {
subErr := mapToSubsonicError(err)
response := newResponse()
response.Status = "failed"
response.Status = responses.StatusFailed
response.Error = &responses.Error{Code: int32(subErr.code), Message: subErr.Error()}
sendResponse(w, r, response)
@ -305,11 +305,10 @@ func sendResponse(w http.ResponseWriter, r *http.Request, payload *responses.Sub
// This should never happen, but if it does, we need to know
if err != nil {
log.Error(r.Context(), "Error marshalling response", "format", f, err)
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte("Internal Server Error: " + err.Error()))
sendError(w, r, err)
return
}
if payload.Status == "ok" {
if payload.Status == responses.StatusOK {
if log.IsGreaterOrEqualTo(log.LevelTrace) {
log.Debug(r.Context(), "API: Successful response", "endpoint", r.URL.Path, "status", "OK", "body", string(response))
} else {

View File

@ -24,12 +24,12 @@ var _ = Describe("sendResponse", func() {
w = httptest.NewRecorder()
r = httptest.NewRequest("GET", "/somepath", nil)
payload = &responses.Subsonic{
Status: "ok",
Status: responses.StatusOK,
Version: "1.16.1",
}
})
Context("when format is JSON", func() {
When("format is JSON", func() {
It("should set Content-Type to application/json and return the correct body", func() {
q := r.URL.Query()
q.Add("f", "json")
@ -48,7 +48,7 @@ var _ = Describe("sendResponse", func() {
})
})
Context("when format is JSONP", func() {
When("format is JSONP", func() {
It("should set Content-Type to application/javascript and return the correct callback body", func() {
q := r.URL.Query()
q.Add("f", "jsonp")
@ -60,8 +60,8 @@ var _ = Describe("sendResponse", func() {
Expect(w.Header().Get("Content-Type")).To(Equal("application/javascript"))
body := w.Body.String()
Expect(body).To(SatisfyAll(
ContainSubstring("testCallback("),
ContainSubstring(")"),
HavePrefix("testCallback("),
HaveSuffix(")"),
))
// Extract JSON from the JSONP response
@ -73,7 +73,7 @@ var _ = Describe("sendResponse", func() {
})
})
Context("when format is XML or unspecified", func() {
When("format is XML or unspecified", func() {
It("should set Content-Type to application/xml and return the correct body", func() {
// No format specified, expecting XML by default
sendResponse(w, r, payload)
@ -87,19 +87,25 @@ var _ = Describe("sendResponse", func() {
})
})
Context("when an error occurs during marshalling", func() {
It("should return HTTP 500", func() {
When("an error occurs during marshalling", func() {
It("should return a fail response", func() {
payload.Song = &responses.Child{
// An +Inf value will cause an error when marshalling to JSON
ReplayGain: responses.ReplayGain{TrackGain: math.Inf(1)},
} // This will cause an error when marshalling to JSON
}
q := r.URL.Query()
q.Add("f", "json")
r.URL.RawQuery = q.Encode()
sendResponse(w, r, payload)
Expect(w.Code).To(Equal(http.StatusInternalServerError))
body := w.Body.String()
Expect(body).To(ContainSubstring("Internal Server Error"))
Expect(w.Code).To(Equal(http.StatusOK))
var wrapper responses.JsonWrapper
err := json.Unmarshal(w.Body.Bytes(), &wrapper)
Expect(err).NotTo(HaveOccurred())
Expect(wrapper.Subsonic.Status).To(Equal(responses.StatusFailed))
Expect(wrapper.Subsonic.Version).To(Equal(payload.Version))
Expect(wrapper.Subsonic.Error.Message).To(ContainSubstring("json: unsupported value: +Inf"))
})
})

View File

@ -19,7 +19,7 @@ import (
func newResponse() *responses.Subsonic {
return &responses.Subsonic{
Status: "ok",
Status: responses.StatusOK,
Version: Version,
Type: consts.AppName,
ServerVersion: consts.Version,

View File

@ -61,6 +61,11 @@ type Subsonic struct {
LyricsList *LyricsList `xml:"lyricsList,omitempty" json:"lyricsList,omitempty"`
}
const (
StatusOK = "ok"
StatusFailed = "failed"
)
type JsonWrapper struct {
Subsonic Subsonic `json:"subsonic-response"`
}

View File

@ -20,7 +20,7 @@ var _ = Describe("Responses", func() {
var response *Subsonic
BeforeEach(func() {
response = &Subsonic{
Status: "ok",
Status: StatusOK,
Version: "1.8.0",
Type: consts.AppName,
ServerVersion: "v0.0.0",