navidrome/server/apiv1/api_test.go

88 lines
3.3 KiB
Go

package apiv1
import (
"net/http"
"net/http/httptest"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Router", func() {
var router *Router
BeforeEach(func() {
router = New(&tests.MockDataStore{})
})
It("returns a 404 problem for unknown paths", func() {
w := serve(router, httptest.NewRequest(http.MethodGet, "/api/v1/nope", nil))
Expect(w.Code).To(Equal(http.StatusNotFound))
Expect(w.Header().Get("Content-Type")).To(Equal(problemContentType))
Expect(decodeProblem(w).Code).To(Equal(ProblemCodeNotFound))
})
It("returns a 405 problem listing the allowed methods for a wrong method on a known path", func() {
w := serve(router, httptest.NewRequest(http.MethodPost, "/api/v1/server", nil))
Expect(w.Code).To(Equal(http.StatusMethodNotAllowed))
Expect(w.Header().Get("Allow")).To(Equal("GET, HEAD"))
Expect(decodeProblem(w).Code).To(Equal(ProblemCodeMethodNotAllowed))
})
DescribeTable("answers HEAD wherever GET is routed",
func(path, contentType string) {
w := serve(router, httptest.NewRequest(http.MethodHead, path, nil))
Expect(w.Code).To(Equal(http.StatusOK))
Expect(w.Header().Get("Content-Type")).To(Equal(contentType))
},
Entry("server info", "/api/v1/server", "application/json"),
Entry("JSON spec", "/api/v1/openapi.json", "application/json"),
Entry("YAML spec", "/api/v1/openapi.yaml", "application/yaml"),
)
It("revalidates HEAD requests with If-None-Match", func() {
etag := serve(router, httptest.NewRequest(http.MethodHead, "/api/v1/openapi.json", nil)).Header().Get("ETag")
req := httptest.NewRequest(http.MethodHead, "/api/v1/openapi.json", nil)
req.Header.Set("If-None-Match", etag)
Expect(serve(router, req).Code).To(Equal(http.StatusNotModified))
})
It("returns a 404 problem for HEAD on unknown paths", func() {
w := serve(router, httptest.NewRequest(http.MethodHead, "/api/v1/nope", nil))
Expect(w.Code).To(Equal(http.StatusNotFound))
Expect(w.Header().Get("Allow")).To(BeEmpty())
})
panicking := func(v any) http.Handler {
return problemRecoverer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { panic(v) }))
}
It("turns a handler panic into a 500 problem", func() {
w := httptest.NewRecorder()
panicking("kaboom").ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/boom", nil))
Expect(w.Code).To(Equal(http.StatusInternalServerError))
p := decodeProblem(w)
Expect(p.Code).To(Equal(ProblemCodeInternal))
Expect(p.Detail).To(BeNil())
})
It("tags internal errors with a referenceId that is also on the request's log lines", func() {
logs := captureLogs()
w := httptest.NewRecorder()
h := referenceIDMiddleware(problemRecoverer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
panic("kaboom")
})))
h.ServeHTTP(w, httptest.NewRequestWithContext(GinkgoT().Context(), http.MethodGet, "/boom", nil))
p := decodeProblem(w)
Expect(p.ReferenceId).ToNot(BeNil())
Expect(*p.ReferenceId).To(MatchRegexp(`^[0-9A-Za-z]{22}$`))
Expect(logs.String()).To(ContainSubstring(*p.ReferenceId))
})
It("re-panics http.ErrAbortHandler so the server can drop the connection", func() {
Expect(func() {
panicking(http.ErrAbortHandler).ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/abort", nil))
}).To(PanicWith(http.ErrAbortHandler))
})
})