package jellyfin
import (
"net/http"
"net/http/httptest"
"strings"
"time"
"github.com/gorilla/websocket"
"github.com/navidrome/navidrome/core/auth"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("handleSocket", func() {
var api *Router
BeforeEach(func() {
api = &Router{}
})
It("upgrades the connection and sends ForceKeepAlive", func() {
srv := httptest.NewServer(http.HandlerFunc(api.handleSocket))
defer srv.Close()
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
Expect(err).ToNot(HaveOccurred())
defer conn.Close()
Expect(conn.SetReadDeadline(time.Now().Add(2 * time.Second))).To(Succeed())
var msg map[string]any
Expect(conn.ReadJSON(&msg)).To(Succeed())
Expect(msg["MessageType"]).To(Equal("ForceKeepAlive"))
Expect(msg["Data"]).To(BeNumerically("==", 60))
})
It("replies to a KeepAlive message with a KeepAlive of its own", func() {
srv := httptest.NewServer(http.HandlerFunc(api.handleSocket))
defer srv.Close()
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
Expect(err).ToNot(HaveOccurred())
defer conn.Close()
Expect(conn.SetReadDeadline(time.Now().Add(2 * time.Second))).To(Succeed())
var handshake map[string]any
Expect(conn.ReadJSON(&handshake)).To(Succeed())
Expect(handshake["MessageType"]).To(Equal("ForceKeepAlive"))
Expect(conn.WriteJSON(map[string]any{"MessageType": "KeepAlive"})).To(Succeed())
Expect(conn.SetReadDeadline(time.Now().Add(2 * time.Second))).To(Succeed())
var reply map[string]any
Expect(conn.ReadJSON(&reply)).To(Succeed())
Expect(reply["MessageType"]).To(Equal("KeepAlive"))
})
It("closes the connection when the client disconnects, without leaving the handler hanging", func() {
srv := httptest.NewServer(http.HandlerFunc(api.handleSocket))
defer srv.Close()
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
Expect(err).ToNot(HaveOccurred())
Expect(conn.SetReadDeadline(time.Now().Add(2 * time.Second))).To(Succeed())
var handshake map[string]any
Expect(conn.ReadJSON(&handshake)).To(Succeed())
Expect(conn.Close()).To(Succeed())
})
Context("mounted behind the full router and auth middleware", func() {
var ds *tests.MockDataStore
var token string
BeforeEach(func() {
ds = &tests.MockDataStore{}
auth.Init(ds)
ur := ds.User().(*tests.MockedUserRepo)
Expect(ur.Put(GinkgoT().Context(), &model.User{ID: testID("u1"), UserName: "alice", NewPassword: "secret"})).To(Succeed())
t, err := auth.CreateToken(&model.User{ID: testID("u1"), UserName: "alice"})
Expect(err).ToNot(HaveOccurred())
token = t
api = New(ds, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
})
It("upgrades when authenticated via the api_key query parameter", func() {
srv := httptest.NewServer(api)
defer srv.Close()
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") + "/socket?api_key=" + token
conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil)
Expect(err).ToNot(HaveOccurred())
defer conn.Close()
Expect(conn.SetReadDeadline(time.Now().Add(2 * time.Second))).To(Succeed())
var msg map[string]any
Expect(conn.ReadJSON(&msg)).To(Succeed())
Expect(msg["MessageType"]).To(Equal("ForceKeepAlive"))
})
It("rejects the upgrade with no api_key", func() {
srv := httptest.NewServer(api)
defer srv.Close()
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") + "/socket"
_, resp, err := websocket.DefaultDialer.Dial(wsURL, nil)
Expect(err).To(HaveOccurred())
Expect(resp.StatusCode).To(Equal(http.StatusUnauthorized))
})
})
})