Fix bug where navidrome doesnt always send range headers

This commit is contained in:
simojenki
2021-03-13 12:22:37 +11:00
parent f432d5b11f
commit 1683c86ee5
5 changed files with 151 additions and 29 deletions

View File

@@ -815,6 +815,67 @@ describe("Navidrome", () => {
describe("streaming a track", () => {
const trackId = uuid();
describe("when navidrome doesnt return a content-range, accept-ranges or content-length", () => {
it("should return undefined values", async () => {
const streamResponse = {
status: 200,
headers: {
"content-type": "audio/mpeg",
},
data: Buffer.from("the track", "ascii"),
};
mockGET
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
.mockImplementationOnce(() => Promise.resolve(streamResponse));
const result = await navidrome
.generateToken({ username, password })
.then((it) => it as AuthSuccess)
.then((it) => navidrome.login(it.authToken))
.then((it) => it.stream({ trackId, range: undefined }));
expect(result.headers).toEqual({
"content-type": "audio/mpeg",
"content-length": undefined,
"content-range": undefined,
"accept-ranges": undefined,
});
});
});
describe("when navidrome returns a undefined for content-range, accept-ranges or content-length", () => {
it("should return undefined values", async () => {
const streamResponse = {
status: 200,
headers: {
"content-type": "audio/mpeg",
"content-length": undefined,
"content-range": undefined,
"accept-ranges": undefined,
},
data: Buffer.from("the track", "ascii"),
};
mockGET
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
.mockImplementationOnce(() => Promise.resolve(streamResponse));
const result = await navidrome
.generateToken({ username, password })
.then((it) => it as AuthSuccess)
.then((it) => navidrome.login(it.authToken))
.then((it) => it.stream({ trackId, range: undefined }));
expect(result.headers).toEqual({
"content-type": "audio/mpeg",
"content-length": undefined,
"content-range": undefined,
"accept-ranges": undefined,
});
});
});
describe("with no range specified", () => {
describe("navidrome returns a 200", () => {
it("should return the content", async () => {
@@ -935,7 +996,7 @@ describe("Navidrome", () => {
describe("fetching cover art", () => {
describe("fetching album art", () => {
describe("when no size is specified", async () => {
describe("when no size is specified", () => {
it("should fetch the image", async () => {
const streamResponse = {
status: 200,
@@ -972,7 +1033,7 @@ describe("Navidrome", () => {
});
});
describe("when size is specified", async () => {
describe("when size is specified", () => {
it("should fetch the image", async () => {
const streamResponse = {
status: 200,

View File

@@ -239,6 +239,59 @@ describe("server", () => {
});
describe("when sonos does not ask for a range", () => {
describe("when the music service does not return a content-range, content-length or accept-ranges", () => {
it("should return a 200 with the data, without adding the undefined headers", async () => {
const stream = {
status: 200,
headers: {
"content-type": "audio/mp3",
},
data: Buffer.from("some track", "ascii"),
};
musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(stream);
const res = await request(server)
.get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(stream.status);
expect(res.headers["content-type"]).toEqual("audio/mp3")
expect(res.headers["content-length"]).toEqual(`${stream.data.length}`)
expect(Object.keys(res.headers)).not.toContain("content-range")
expect(Object.keys(res.headers)).not.toContain("accept-ranges")
});
});
describe("when the music service returns undefined values for content-range, content-length or accept-ranges", () => {
it("should return a 200 with the data, without adding the undefined headers", async () => {
const stream = {
status: 200,
headers: {
"content-type": "audio/mp3",
"content-length": undefined,
"accept-ranges": undefined,
"content-range": undefined,
},
data: Buffer.from("some track", "ascii"),
};
musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(stream);
const res = await request(server)
.get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(stream.status);
expect(res.headers["content-type"]).toEqual("audio/mp3")
expect(res.headers["content-length"]).toEqual(`${stream.data.length}`)
expect(Object.keys(res.headers)).not.toContain("content-range")
expect(Object.keys(res.headers)).not.toContain("accept-ranges")
});
});
describe("when the music service returns a 200", () => {
it("should return a 200 with the data", async () => {
const stream = {

View File

@@ -18,6 +18,7 @@ import {
PRESENTATION_MAP_ROUTE,
SONOS_RECOMMENDED_IMAGE_SIZES,
track,
defaultAlbumArtURI,
} from "../src/smapi";
import {
@@ -163,6 +164,17 @@ describe("track", () => {
});
});
describe("defaultAlbumArtURI", () => {
it("should create the correct URI", () => {
const webAddress = "http://localhost:1234";
const accessToken = uuid();
const album = anAlbum();
expect(defaultAlbumArtURI(webAddress, accessToken, album)).toEqual(
`${webAddress}/album/${album.id}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
);
});
});
class Base64AccessTokens implements AccessTokens {
mint(authToken: string) {
return Buffer.from(authToken).toString("base64");
@@ -427,6 +439,7 @@ describe("api", () => {
const password = "validPassword";
let token: AuthSuccess;
let ws: Client;
let accessToken: string;
beforeEach(async () => {
musicService.hasUser({ username, password });
@@ -434,6 +447,9 @@ describe("api", () => {
username,
password,
})) as AuthSuccess;
accessToken = accessTokens.mint(token.authToken);
ws = await createClientAsync(`${service.uri}?wsdl`, {
endpoint: service.uri,
httpClient: supersoap(server, rootUrl),
@@ -545,11 +561,7 @@ describe("api", () => {
itemType: "album",
id: `album:${it.id}`,
title: it.name,
albumArtURI: `${rootUrl}/album/${
it.id
}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessTokens.mint(
token.authToken
)}`,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
})),
index: 0,
total: artistWithManyAlbums.albums.length,
@@ -574,11 +586,7 @@ describe("api", () => {
itemType: "album",
id: `album:${it.id}`,
title: it.name,
albumArtURI: `${rootUrl}/album/${
it.id
}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessTokens.mint(
token.authToken
)}`,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
})),
index: 2,
total: artistWithManyAlbums.albums.length,
@@ -683,11 +691,7 @@ describe("api", () => {
itemType: "album",
id: `album:${it.id}`,
title: it.name,
albumArtURI: `${rootUrl}/album/${
it.id
}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessTokens.mint(
token.authToken
)}`,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
})),
index: 0,
total: 6,
@@ -713,11 +717,7 @@ describe("api", () => {
itemType: "album",
id: `album:${it.id}`,
title: it.name,
albumArtURI: `${rootUrl}/album/${
it.id
}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessTokens.mint(
token.authToken
)}`,
albumArtURI: defaultAlbumArtURI(rootUrl, accessToken, it),
})),
index: 2,
total: 6,
@@ -779,7 +779,9 @@ describe("api", () => {
});
expect(result[0]).toEqual(
getMetadataResult2({
mediaMetadata: [track3, track4].map(it => track(rootUrl, accessTokens.mint(token.authToken), it)),
mediaMetadata: [track3, track4].map((it) =>
track(rootUrl, accessTokens.mint(token.authToken), it)
),
index: 2,
total: 5,
})
@@ -986,7 +988,11 @@ describe("api", () => {
id: `track:${someTrack.id}`,
});
expect(root[0]).toEqual({
getMediaMetadataResult: track(rootUrl, accessTokens.mint(token.authToken), someTrack),
getMediaMetadataResult: track(
rootUrl,
accessTokens.mint(token.authToken),
someTrack
),
});
});
});