mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Migrate Navidrome support to generic subsonic clone support (#55)
Renaming BONOB_* env vars to BNB_*
This commit is contained in:
@@ -141,6 +141,7 @@ export function aTrack(fields: Partial<Track> = {}): Track {
|
||||
genre,
|
||||
artist: artistToArtistSummary(artist),
|
||||
album: albumToAlbumSummary(anAlbum({ artistId: artist.id, artistName: artist.name, genre })),
|
||||
coverArt: `coverArt:${uuid()}`,
|
||||
...fields,
|
||||
};
|
||||
}
|
||||
@@ -154,6 +155,7 @@ export function anAlbum(fields: Partial<Album> = {}): Album {
|
||||
year: `19${randomInt(99)}`,
|
||||
artistId: `Artist ${uuid()}`,
|
||||
artistName: `Artist ${randomString()}`,
|
||||
coverArt: `coverArt:${uuid()}`,
|
||||
...fields,
|
||||
};
|
||||
}
|
||||
@@ -170,7 +172,8 @@ export const BLONDIE: Artist = {
|
||||
year: "1976",
|
||||
genre: NEW_WAVE,
|
||||
artistId: BLONDIE_ID,
|
||||
artistName: BLONDIE_NAME
|
||||
artistName: BLONDIE_NAME,
|
||||
coverArt: `coverArt:${uuid()}`
|
||||
},
|
||||
{
|
||||
id: uuid(),
|
||||
@@ -178,7 +181,8 @@ export const BLONDIE: Artist = {
|
||||
year: "1978",
|
||||
genre: POP_ROCK,
|
||||
artistId: BLONDIE_ID,
|
||||
artistName: BLONDIE_NAME
|
||||
artistName: BLONDIE_NAME,
|
||||
coverArt: `coverArt:${uuid()}`
|
||||
},
|
||||
],
|
||||
image: {
|
||||
@@ -195,9 +199,9 @@ export const BOB_MARLEY: Artist = {
|
||||
id: BOB_MARLEY_ID,
|
||||
name: BOB_MARLEY_NAME,
|
||||
albums: [
|
||||
{ id: uuid(), name: "Burin'", year: "1973", genre: REGGAE, artistId: BOB_MARLEY_ID, artistName: BOB_MARLEY_NAME },
|
||||
{ id: uuid(), name: "Exodus", year: "1977", genre: REGGAE, artistId: BOB_MARLEY_ID, artistName: BOB_MARLEY_NAME },
|
||||
{ id: uuid(), name: "Kaya", year: "1978", genre: SKA, artistId: BOB_MARLEY_ID, artistName: BOB_MARLEY_NAME },
|
||||
{ id: uuid(), name: "Burin'", year: "1973", genre: REGGAE, artistId: BOB_MARLEY_ID, artistName: BOB_MARLEY_NAME, coverArt: `coverArt:${uuid()}` },
|
||||
{ id: uuid(), name: "Exodus", year: "1977", genre: REGGAE, artistId: BOB_MARLEY_ID, artistName: BOB_MARLEY_NAME, coverArt: `coverArt:${uuid()}` },
|
||||
{ id: uuid(), name: "Kaya", year: "1978", genre: SKA, artistId: BOB_MARLEY_ID, artistName: BOB_MARLEY_NAME, coverArt: `coverArt:${uuid()}` },
|
||||
],
|
||||
image: {
|
||||
small: "http://localhost/BOB_MARLEY/sml",
|
||||
@@ -234,6 +238,7 @@ export const METALLICA: Artist = {
|
||||
genre: METAL,
|
||||
artistId: METALLICA_ID,
|
||||
artistName: METALLICA_NAME,
|
||||
coverArt: `coverArt:${uuid()}`
|
||||
},
|
||||
{
|
||||
id: uuid(),
|
||||
@@ -241,7 +246,8 @@ export const METALLICA: Artist = {
|
||||
year: "1986",
|
||||
genre: METAL,
|
||||
artistId: METALLICA_ID,
|
||||
artistName: METALLICA_NAME,
|
||||
artistName: METALLICA_NAME,
|
||||
coverArt: `coverArt:${uuid()}`
|
||||
},
|
||||
],
|
||||
image: {
|
||||
|
||||
@@ -1,5 +1,79 @@
|
||||
import { hostname } from "os";
|
||||
import config from "../src/config";
|
||||
import config, { envVar, WORD } from "../src/config";
|
||||
|
||||
describe("envVar", () => {
|
||||
const OLD_ENV = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
process.env = { ...OLD_ENV };
|
||||
|
||||
process.env["bnb-var"] = "bnb-var-value";
|
||||
process.env["bnb-legacy2"] = "bnb-legacy2-value";
|
||||
process.env["bnb-legacy3"] = "bnb-legacy3-value";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = OLD_ENV;
|
||||
});
|
||||
|
||||
describe("when the env var exists", () => {
|
||||
describe("and there are no legacy env vars that match", () => {
|
||||
it("should return the env var", () => {
|
||||
expect(envVar("bnb-var")).toEqual("bnb-var-value");
|
||||
});
|
||||
});
|
||||
|
||||
describe("and there are legacy env vars that match", () => {
|
||||
it("should return the env var", () => {
|
||||
expect(
|
||||
envVar("bnb-var", {
|
||||
default: "not valid",
|
||||
legacy: ["bnb-legacy1", "bnb-legacy2", "bnb-legacy3"],
|
||||
})
|
||||
).toEqual("bnb-var-value");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the env var doesnt exist", () => {
|
||||
describe("and there are no legacy env vars specified", () => {
|
||||
describe("and there is no default value specified", () => {
|
||||
it("should be undefined", () => {
|
||||
expect(envVar("bnb-not-set")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and there is a default value specified", () => {
|
||||
it("should return the default", () => {
|
||||
expect(envVar("bnb-not-set", { default: "widget" })).toEqual(
|
||||
"widget"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there are legacy env vars specified", () => {
|
||||
it("should return the value from the first matched legacy env var", () => {
|
||||
expect(
|
||||
envVar("bnb-not-set", {
|
||||
legacy: ["bnb-legacy1", "bnb-legacy2", "bnb-legacy3"],
|
||||
})
|
||||
).toEqual("bnb-legacy2-value");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("validationPattern", () => {
|
||||
it("should fail when the value does not match the pattern", () => {
|
||||
expect(
|
||||
() => envVar("bnb-var", {
|
||||
validationPattern: /^foobar$/,
|
||||
})
|
||||
).toThrowError(`Invalid value specified for 'bnb-var', must match ${/^foobar$/}`)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("config", () => {
|
||||
const OLD_ENV = process.env;
|
||||
@@ -43,26 +117,22 @@ describe("config", () => {
|
||||
}
|
||||
|
||||
describe("bonobUrl", () => {
|
||||
describe("when BONOB_URL is specified", () => {
|
||||
it("should be used", () => {
|
||||
const url = "http://bonob1.example.com:8877/";
|
||||
process.env["BONOB_URL"] = url;
|
||||
["BNB_URL", "BONOB_URL", "BONOB_WEB_ADDRESS"].forEach(key => {
|
||||
describe(`when ${key} is specified`, () => {
|
||||
it("should be used", () => {
|
||||
const url = "http://bonob1.example.com:8877/";
|
||||
|
||||
expect(config().bonobUrl.href()).toEqual(url);
|
||||
process.env["BNB_URL"] = "";
|
||||
process.env["BONOB_URL"] = "";
|
||||
process.env["BONOB_WEB_ADDRESS"] = "";
|
||||
process.env[key] = url;
|
||||
|
||||
expect(config().bonobUrl.href()).toEqual(url);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when BONOB_URL is not specified, however legacy BONOB_WEB_ADDRESS is specified", () => {
|
||||
it("should be used", () => {
|
||||
const url = "http://bonob2.example.com:9988/";
|
||||
process.env["BONOB_URL"] = "";
|
||||
process.env["BONOB_WEB_ADDRESS"] = url;
|
||||
|
||||
expect(config().bonobUrl.href()).toEqual(url);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when neither BONOB_URL nor BONOB_WEB_ADDRESS are specified", () => {
|
||||
describe("when none of BNB_URL, BONOB_URL, BONOB_WEB_ADDRESS are specified", () => {
|
||||
describe("when BONOB_PORT is not specified", () => {
|
||||
it(`should default to http://${hostname()}:4534`, () => {
|
||||
expect(config().bonobUrl.href()).toEqual(
|
||||
@@ -71,6 +141,15 @@ describe("config", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when BNB_PORT is specified as 3322", () => {
|
||||
it(`should default to http://${hostname()}:3322`, () => {
|
||||
process.env["BNB_PORT"] = "3322";
|
||||
expect(config().bonobUrl.href()).toEqual(
|
||||
`http://${hostname()}:3322/`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when BONOB_PORT is specified as 3322", () => {
|
||||
it(`should default to http://${hostname()}:3322`, () => {
|
||||
process.env["BONOB_PORT"] = "3322";
|
||||
@@ -82,90 +161,69 @@ describe("config", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("navidrome", () => {
|
||||
describe("url", () => {
|
||||
describe("when BONOB_NAVIDROME_URL is not specified", () => {
|
||||
it(`should default to http://${hostname()}:4533`, () => {
|
||||
expect(config().navidrome.url).toEqual(`http://${hostname()}:4533`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when BONOB_NAVIDROME_URL is ''", () => {
|
||||
it(`should default to http://${hostname()}:4533`, () => {
|
||||
process.env["BONOB_NAVIDROME_URL"] = "";
|
||||
expect(config().navidrome.url).toEqual(`http://${hostname()}:4533`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when BONOB_NAVIDROME_URL is specified", () => {
|
||||
it(`should use it`, () => {
|
||||
const url = "http://navidrome.example.com:1234";
|
||||
process.env["BONOB_NAVIDROME_URL"] = url;
|
||||
expect(config().navidrome.url).toEqual(url);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("icons", () => {
|
||||
describe("foregroundColor", () => {
|
||||
describe("when BONOB_ICON_FOREGROUND_COLOR is not specified", () => {
|
||||
it(`should default to undefined`, () => {
|
||||
expect(config().icons.foregroundColor).toEqual(undefined);
|
||||
["BNB_ICON_FOREGROUND_COLOR", "BONOB_ICON_FOREGROUND_COLOR"].forEach(k => {
|
||||
describe(`when ${k} is not specified`, () => {
|
||||
it(`should default to undefined`, () => {
|
||||
expect(config().icons.foregroundColor).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when BONOB_ICON_FOREGROUND_COLOR is ''", () => {
|
||||
it(`should default to undefined`, () => {
|
||||
process.env["BONOB_ICON_FOREGROUND_COLOR"] = "";
|
||||
expect(config().icons.foregroundColor).toEqual(undefined);
|
||||
|
||||
describe(`when ${k} is ''`, () => {
|
||||
it(`should default to undefined`, () => {
|
||||
process.env[k] = "";
|
||||
expect(config().icons.foregroundColor).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when BONOB_ICON_FOREGROUND_COLOR is specified", () => {
|
||||
it(`should use it`, () => {
|
||||
process.env["BONOB_ICON_FOREGROUND_COLOR"] = "pink";
|
||||
expect(config().icons.foregroundColor).toEqual("pink");
|
||||
|
||||
describe(`when ${k} is specified`, () => {
|
||||
it(`should use it`, () => {
|
||||
process.env[k] = "pink";
|
||||
expect(config().icons.foregroundColor).toEqual("pink");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when BONOB_ICON_FOREGROUND_COLOR is an invalid string", () => {
|
||||
it(`should blow up`, () => {
|
||||
process.env["BONOB_ICON_FOREGROUND_COLOR"] = "#dfasd";
|
||||
expect(() => config()).toThrow(
|
||||
"Invalid color specified for BONOB_ICON_FOREGROUND_COLOR"
|
||||
);
|
||||
|
||||
describe(`when ${k} is an invalid string`, () => {
|
||||
it(`should blow up`, () => {
|
||||
process.env[k] = "#dfasd";
|
||||
expect(() => config()).toThrow(
|
||||
`Invalid value specified for 'BNB_ICON_FOREGROUND_COLOR', must match ${WORD}`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("backgroundColor", () => {
|
||||
describe("when BONOB_ICON_BACKGROUND_COLOR is not specified", () => {
|
||||
it(`should default to undefined`, () => {
|
||||
expect(config().icons.backgroundColor).toEqual(undefined);
|
||||
["BNB_ICON_BACKGROUND_COLOR", "BONOB_ICON_BACKGROUND_COLOR"].forEach(k => {
|
||||
describe(`when ${k} is not specified`, () => {
|
||||
it(`should default to undefined`, () => {
|
||||
expect(config().icons.backgroundColor).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when BONOB_ICON_BACKGROUND_COLOR is ''", () => {
|
||||
it(`should default to undefined`, () => {
|
||||
process.env["BONOB_ICON_BACKGROUND_COLOR"] = "";
|
||||
expect(config().icons.backgroundColor).toEqual(undefined);
|
||||
|
||||
describe(`when ${k} is ''`, () => {
|
||||
it(`should default to undefined`, () => {
|
||||
process.env[k] = "";
|
||||
expect(config().icons.backgroundColor).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when BONOB_ICON_BACKGROUND_COLOR is specified", () => {
|
||||
it(`should use it`, () => {
|
||||
process.env["BONOB_ICON_BACKGROUND_COLOR"] = "blue";
|
||||
expect(config().icons.backgroundColor).toEqual("blue");
|
||||
|
||||
describe(`when ${k} is specified`, () => {
|
||||
it(`should use it`, () => {
|
||||
process.env[k] = "blue";
|
||||
expect(config().icons.backgroundColor).toEqual("blue");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when BONOB_ICON_BACKGROUND_COLOR is an invalid string", () => {
|
||||
it(`should blow up`, () => {
|
||||
process.env["BONOB_ICON_BACKGROUND_COLOR"] = "#red";
|
||||
expect(() => config()).toThrow(
|
||||
"Invalid color specified for BONOB_ICON_BACKGROUND_COLOR"
|
||||
);
|
||||
|
||||
describe(`when ${k} is an invalid string`, () => {
|
||||
it(`should blow up`, () => {
|
||||
process.env[k] = "#red";
|
||||
expect(() => config()).toThrow(
|
||||
`Invalid value specified for 'BNB_ICON_BACKGROUND_COLOR', must match ${WORD}`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -176,9 +234,11 @@ describe("config", () => {
|
||||
expect(config().secret).toEqual("bonob");
|
||||
});
|
||||
|
||||
it("should be overridable", () => {
|
||||
process.env["BONOB_SECRET"] = "new secret";
|
||||
expect(config().secret).toEqual("new secret");
|
||||
["BNB_SECRET", "BONOB_SECRET"].forEach(key => {
|
||||
it(`should be overridable using ${key}`, () => {
|
||||
process.env[key] = "new secret";
|
||||
expect(config().secret).toEqual("new secret");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -188,83 +248,116 @@ describe("config", () => {
|
||||
expect(config().sonos.serviceName).toEqual("bonob");
|
||||
});
|
||||
|
||||
it("should be overridable", () => {
|
||||
process.env["BONOB_SONOS_SERVICE_NAME"] = "foobar1000";
|
||||
expect(config().sonos.serviceName).toEqual("foobar1000");
|
||||
["BNB_SONOS_SERVICE_NAME", "BONOB_SONOS_SERVICE_NAME"].forEach(k => {
|
||||
it("should be overridable", () => {
|
||||
process.env[k] = "foobar1000";
|
||||
expect(config().sonos.serviceName).toEqual("foobar1000");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describeBooleanConfigValue(
|
||||
"deviceDiscovery",
|
||||
"BONOB_SONOS_DEVICE_DISCOVERY",
|
||||
true,
|
||||
(config) => config.sonos.discovery.enabled
|
||||
);
|
||||
["BNB_SONOS_DEVICE_DISCOVERY", "BONOB_SONOS_DEVICE_DISCOVERY"].forEach(k => {
|
||||
describeBooleanConfigValue(
|
||||
"deviceDiscovery",
|
||||
k,
|
||||
true,
|
||||
(config) => config.sonos.discovery.enabled
|
||||
);
|
||||
});
|
||||
|
||||
describe("seedHost", () => {
|
||||
it("should default to undefined", () => {
|
||||
expect(config().sonos.discovery.seedHost).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should be overridable", () => {
|
||||
process.env["BONOB_SONOS_SEED_HOST"] = "123.456.789.0";
|
||||
expect(config().sonos.discovery.seedHost).toEqual("123.456.789.0");
|
||||
["BNB_SONOS_SEED_HOST", "BONOB_SONOS_SEED_HOST"].forEach(k => {
|
||||
it("should be overridable", () => {
|
||||
process.env[k] = "123.456.789.0";
|
||||
expect(config().sonos.discovery.seedHost).toEqual("123.456.789.0");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describeBooleanConfigValue(
|
||||
"autoRegister",
|
||||
"BONOB_SONOS_AUTO_REGISTER",
|
||||
false,
|
||||
(config) => config.sonos.autoRegister
|
||||
);
|
||||
["BNB_SONOS_AUTO_REGISTER", "BONOB_SONOS_AUTO_REGISTER"].forEach(k => {
|
||||
describeBooleanConfigValue(
|
||||
"autoRegister",
|
||||
k,
|
||||
false,
|
||||
(config) => config.sonos.autoRegister
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
describe("sid", () => {
|
||||
it("should default to 246", () => {
|
||||
expect(config().sonos.sid).toEqual(246);
|
||||
});
|
||||
|
||||
it("should be overridable", () => {
|
||||
process.env["BONOB_SONOS_SERVICE_ID"] = "786";
|
||||
expect(config().sonos.sid).toEqual(786);
|
||||
["BNB_SONOS_SERVICE_ID", "BONOB_SONOS_SERVICE_ID"].forEach(k => {
|
||||
it("should be overridable", () => {
|
||||
process.env[k] = "786";
|
||||
expect(config().sonos.sid).toEqual(786);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("navidrome", () => {
|
||||
describe("subsonic", () => {
|
||||
describe("url", () => {
|
||||
it("should default to http://${hostname()}:4533", () => {
|
||||
expect(config().navidrome.url).toEqual(`http://${hostname()}:4533`);
|
||||
});
|
||||
|
||||
it("should be overridable", () => {
|
||||
process.env["BONOB_NAVIDROME_URL"] = "http://farfaraway.com";
|
||||
expect(config().navidrome.url).toEqual("http://farfaraway.com");
|
||||
["BNB_SUBSONIC_URL", "BONOB_SUBSONIC_URL", "BONOB_NAVIDROME_URL"].forEach(k => {
|
||||
describe(`when ${k} is not specified`, () => {
|
||||
it(`should default to http://${hostname()}:4533`, () => {
|
||||
expect(config().subsonic.url).toEqual(`http://${hostname()}:4533`);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`when ${k} is ''`, () => {
|
||||
it(`should default to http://${hostname()}:4533`, () => {
|
||||
process.env[k] = "";
|
||||
expect(config().subsonic.url).toEqual(`http://${hostname()}:4533`);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`when ${k} is specified`, () => {
|
||||
it(`should use it for ${k}`, () => {
|
||||
const url = "http://navidrome.example.com:1234";
|
||||
process.env[k] = url;
|
||||
expect(config().subsonic.url).toEqual(url);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("customClientsFor", () => {
|
||||
it("should default to undefined", () => {
|
||||
expect(config().navidrome.customClientsFor).toBeUndefined();
|
||||
expect(config().subsonic.customClientsFor).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should be overridable", () => {
|
||||
process.env["BONOB_NAVIDROME_CUSTOM_CLIENTS"] = "whoop/whoop";
|
||||
expect(config().navidrome.customClientsFor).toEqual("whoop/whoop");
|
||||
["BNB_SUBSONIC_CUSTOM_CLIENTS", "BONOB_SUBSONIC_CUSTOM_CLIENTS", "BONOB_NAVIDROME_CUSTOM_CLIENTS"].forEach(k => {
|
||||
it(`should be overridable for ${k}`, () => {
|
||||
process.env[k] = "whoop/whoop";
|
||||
expect(config().subsonic.customClientsFor).toEqual("whoop/whoop");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
["BNB_SCROBBLE_TRACKS", "BONOB_SCROBBLE_TRACKS"].forEach(k => {
|
||||
describeBooleanConfigValue(
|
||||
"scrobbleTracks",
|
||||
k,
|
||||
true,
|
||||
(config) => config.scrobbleTracks
|
||||
);
|
||||
});
|
||||
|
||||
describeBooleanConfigValue(
|
||||
"scrobbleTracks",
|
||||
"BONOB_SCROBBLE_TRACKS",
|
||||
true,
|
||||
(config) => config.scrobbleTracks
|
||||
);
|
||||
describeBooleanConfigValue(
|
||||
"reportNowPlaying",
|
||||
"BONOB_REPORT_NOW_PLAYING",
|
||||
true,
|
||||
(config) => config.reportNowPlaying
|
||||
);
|
||||
["BNB_REPORT_NOW_PLAYING", "BONOB_REPORT_NOW_PLAYING"].forEach(k => {
|
||||
describeBooleanConfigValue(
|
||||
"reportNowPlaying",
|
||||
k,
|
||||
true,
|
||||
(config) => config.reportNowPlaying
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,7 +125,7 @@ export class InMemoryMusicService implements MusicService {
|
||||
),
|
||||
stream: (_: { trackId: string; range: string | undefined }) =>
|
||||
Promise.reject("unsupported operation"),
|
||||
coverArt: (id: string, _: "album" | "artist", size?: number) =>
|
||||
coverArt: (id: string, size?: number) =>
|
||||
Promise.reject(`Cannot retrieve coverArt for ${id}, size ${size}`),
|
||||
scrobble: async (_: string) => {
|
||||
return Promise.resolve(true);
|
||||
|
||||
@@ -774,7 +774,8 @@ describe("server", () => {
|
||||
const trackStream = {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": "audio/mp3; charset=utf-8",
|
||||
// audio/x-flac should be mapped to x-flac
|
||||
"content-type": "audio/x-flac; whoop; foo-bar",
|
||||
"content-length": "123",
|
||||
},
|
||||
stream: streamContent(""),
|
||||
@@ -793,7 +794,7 @@ describe("server", () => {
|
||||
|
||||
expect(res.status).toEqual(trackStream.status);
|
||||
expect(res.headers["content-type"]).toEqual(
|
||||
"audio/mp3; charset=utf-8"
|
||||
"audio/flac; whoop; foo-bar"
|
||||
);
|
||||
expect(res.headers["content-length"]).toEqual("123");
|
||||
expect(res.body).toEqual({});
|
||||
@@ -883,7 +884,8 @@ describe("server", () => {
|
||||
const stream = {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": "audio/mp3",
|
||||
// audio/x-flac should be mapped to audio/flac
|
||||
"content-type": "audio/x-flac; charset=utf-8",
|
||||
},
|
||||
stream: streamContent(content),
|
||||
};
|
||||
@@ -902,7 +904,7 @@ describe("server", () => {
|
||||
|
||||
expect(res.status).toEqual(stream.status);
|
||||
expect(res.headers["content-type"]).toEqual(
|
||||
"audio/mp3; charset=utf-8"
|
||||
"audio/flac; charset=utf-8"
|
||||
);
|
||||
expect(res.header["accept-ranges"]).toBeUndefined();
|
||||
expect(res.headers["content-length"]).toEqual(
|
||||
@@ -1173,7 +1175,7 @@ describe("server", () => {
|
||||
|
||||
describe("when there is no access-token", () => {
|
||||
it("should return a 401", async () => {
|
||||
const res = await request(server).get(`/art/album/123/size/180`);
|
||||
const res = await request(server).get(`/art/coverArt:123/size/180`);
|
||||
|
||||
expect(res.status).toEqual(401);
|
||||
});
|
||||
@@ -1184,7 +1186,7 @@ describe("server", () => {
|
||||
now = now.add(1, "day");
|
||||
|
||||
const res = await request(server).get(
|
||||
`/art/album/123/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
`/art/coverArt:123/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
);
|
||||
|
||||
expect(res.status).toEqual(401);
|
||||
@@ -1192,18 +1194,6 @@ describe("server", () => {
|
||||
});
|
||||
|
||||
describe("when there is a valid access token", () => {
|
||||
describe("some invalid art type", () => {
|
||||
it("should return a 400", async () => {
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/foo/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
expect(res.status).toEqual(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe("artist art", () => {
|
||||
["0", "-1", "foo"].forEach((size) => {
|
||||
describe(`invalid size of ${size}`, () => {
|
||||
@@ -1211,7 +1201,7 @@ describe("server", () => {
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${albumId}/size/${size}?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
`/art/artist:${albumId}/size/${size}?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
@@ -1231,7 +1221,7 @@ describe("server", () => {
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
`/art/artist:${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
@@ -1242,8 +1232,7 @@ describe("server", () => {
|
||||
|
||||
expect(musicService.login).toHaveBeenCalledWith(authToken);
|
||||
expect(musicLibrary.coverArt).toHaveBeenCalledWith(
|
||||
albumId,
|
||||
"artist",
|
||||
`artist:${albumId}`,
|
||||
180
|
||||
);
|
||||
});
|
||||
@@ -1257,7 +1246,7 @@ describe("server", () => {
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
`/art/artist:${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
@@ -1271,7 +1260,7 @@ describe("server", () => {
|
||||
|
||||
describe("fetching a collage of 4 when all are available", () => {
|
||||
it("should return the image and a 200", async () => {
|
||||
const ids = ["1", "2", "3", "4"];
|
||||
const ids = ["artist:1", "artist:2", "coverArt:3", "coverArt:4"];
|
||||
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
@@ -1283,11 +1272,10 @@ describe("server", () => {
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${ids.join(
|
||||
"&"
|
||||
)}/size/200?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
`/art/${ids.join("&")}/size/200?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
@@ -1298,7 +1286,6 @@ describe("server", () => {
|
||||
ids.forEach((id) => {
|
||||
expect(musicLibrary.coverArt).toHaveBeenCalledWith(
|
||||
id,
|
||||
"artist",
|
||||
200
|
||||
);
|
||||
});
|
||||
@@ -1311,7 +1298,7 @@ describe("server", () => {
|
||||
|
||||
describe("fetching a collage of 4, however only 1 is available", () => {
|
||||
it("should return the single image", async () => {
|
||||
const ids = ["1", "2", "3", "4"];
|
||||
const ids = ["artist:1", "artist:2", "artist:3", "artist:4"];
|
||||
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
@@ -1327,7 +1314,7 @@ describe("server", () => {
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${ids.join(
|
||||
`/art/${ids.join(
|
||||
"&"
|
||||
)}/size/200?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
@@ -1340,7 +1327,7 @@ describe("server", () => {
|
||||
|
||||
describe("fetching a collage of 4 and all are missing", () => {
|
||||
it("should return a 404", async () => {
|
||||
const ids = ["1", "2", "3", "4"];
|
||||
const ids = ["artist:1", "artist:2", "artist:3", "artist:4"];
|
||||
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
@@ -1350,7 +1337,7 @@ describe("server", () => {
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${ids.join(
|
||||
`/art/${ids.join(
|
||||
"&"
|
||||
)}/size/200?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
@@ -1362,7 +1349,7 @@ describe("server", () => {
|
||||
|
||||
describe("fetching a collage of 9 when all are available", () => {
|
||||
it("should return the image and a 200", async () => {
|
||||
const ids = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
|
||||
const ids = ["artist:1", "artist:2", "coverArt:3", "artist:4", "artist:5", "artist:6", "artist:7", "artist:8", "artist:9"];
|
||||
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
@@ -1376,7 +1363,7 @@ describe("server", () => {
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${ids.join(
|
||||
`/art/${ids.join(
|
||||
"&"
|
||||
)}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
@@ -1389,7 +1376,6 @@ describe("server", () => {
|
||||
ids.forEach((id) => {
|
||||
expect(musicLibrary.coverArt).toHaveBeenCalledWith(
|
||||
id,
|
||||
"artist",
|
||||
180
|
||||
);
|
||||
});
|
||||
@@ -1402,7 +1388,7 @@ describe("server", () => {
|
||||
|
||||
describe("fetching a collage of 9 when only 2 are available", () => {
|
||||
it("should still return an image and a 200", async () => {
|
||||
const ids = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
|
||||
const ids = ["artist:1", "artist:2", "artist:3", "artist:4", "artist:5", "artist:6", "artist:7", "artist:8", "artist:9"];
|
||||
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
@@ -1426,7 +1412,7 @@ describe("server", () => {
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${ids.join(
|
||||
`/art/${ids.join(
|
||||
"&"
|
||||
)}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
@@ -1439,7 +1425,6 @@ describe("server", () => {
|
||||
ids.forEach((id) => {
|
||||
expect(musicLibrary.coverArt).toHaveBeenCalledWith(
|
||||
id,
|
||||
"artist",
|
||||
180
|
||||
);
|
||||
});
|
||||
@@ -1452,7 +1437,7 @@ describe("server", () => {
|
||||
|
||||
describe("fetching a collage of 11", () => {
|
||||
it("should still return an image and a 200, though will only display 9", async () => {
|
||||
const ids = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"];
|
||||
const ids = ["artist:1", "artist:2", "artist:3", "artist:4", "artist:5", "artist:6", "artist:7", "artist:8", "artist:9", "artist:10", "artist:11"];
|
||||
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
@@ -1466,7 +1451,7 @@ describe("server", () => {
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${ids.join(
|
||||
`/art/${ids.join(
|
||||
"&"
|
||||
)}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
@@ -1479,7 +1464,6 @@ describe("server", () => {
|
||||
ids.forEach((id) => {
|
||||
expect(musicLibrary.coverArt).toHaveBeenCalledWith(
|
||||
id,
|
||||
"artist",
|
||||
180
|
||||
);
|
||||
});
|
||||
@@ -1498,7 +1482,7 @@ describe("server", () => {
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
`/art/coverArt:${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
@@ -1515,7 +1499,7 @@ describe("server", () => {
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
`/art/artist:${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
@@ -1531,7 +1515,7 @@ describe("server", () => {
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/album/${albumId}/size/${size}?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
`/art/coverArt:${albumId}/size/${size}?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
@@ -1553,7 +1537,7 @@ describe("server", () => {
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/album/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
`/art/coverArt:${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
@@ -1564,8 +1548,7 @@ describe("server", () => {
|
||||
|
||||
expect(musicService.login).toHaveBeenCalledWith(authToken);
|
||||
expect(musicLibrary.coverArt).toHaveBeenCalledWith(
|
||||
albumId,
|
||||
"album",
|
||||
`coverArt:${albumId}`,
|
||||
180
|
||||
);
|
||||
});
|
||||
@@ -1578,7 +1561,7 @@ describe("server", () => {
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/album/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
`/art/album:${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
@@ -1593,7 +1576,7 @@ describe("server", () => {
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/album/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
`/art/album:${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
searchResult,
|
||||
iconArtURI,
|
||||
playlistAlbumArtURL,
|
||||
sonosifyMimeType,
|
||||
} from "../src/smapi";
|
||||
|
||||
import {
|
||||
@@ -48,7 +49,7 @@ import {
|
||||
} from "../src/music_service";
|
||||
import { AccessTokens } from "../src/access_tokens";
|
||||
import dayjs from "dayjs";
|
||||
import url from "../src/url_builder";
|
||||
import url, { URLBuilder } from "../src/url_builder";
|
||||
import { iconForGenre } from "../src/icon";
|
||||
|
||||
const parseXML = (value: string) => new DOMParserImpl().parseFromString(value);
|
||||
@@ -252,7 +253,8 @@ describe("track", () => {
|
||||
const bonobUrl = url("http://localhost:4567/foo?access-token=1234");
|
||||
const someTrack = aTrack({
|
||||
id: uuid(),
|
||||
mimeType: "audio/something",
|
||||
// audio/x-flac should be mapped to audio/flac
|
||||
mimeType: "audio/x-flac",
|
||||
name: "great song",
|
||||
duration: randomInt(1000),
|
||||
number: randomInt(100),
|
||||
@@ -262,22 +264,23 @@ describe("track", () => {
|
||||
genre: { id: "genre101", name: "some genre" },
|
||||
}),
|
||||
artist: anArtist({ name: "great artist", id: uuid() }),
|
||||
coverArt:"coverArt:887766"
|
||||
});
|
||||
|
||||
expect(track(bonobUrl, someTrack)).toEqual({
|
||||
itemType: "track",
|
||||
id: `track:${someTrack.id}`,
|
||||
mimeType: someTrack.mimeType,
|
||||
mimeType: 'audio/flac',
|
||||
title: someTrack.name,
|
||||
|
||||
trackMetadata: {
|
||||
album: someTrack.album.name,
|
||||
albumId: someTrack.album.id,
|
||||
albumId: `album:${someTrack.album.id}`,
|
||||
albumArtist: someTrack.artist.name,
|
||||
albumArtistId: someTrack.artist.id,
|
||||
albumArtURI: `http://localhost:4567/foo/art/album/${someTrack.album.id}/size/180?access-token=1234`,
|
||||
albumArtistId: `artist:${someTrack.artist.id}`,
|
||||
albumArtURI: `http://localhost:4567/foo/art/${someTrack.coverArt}/size/180?access-token=1234`,
|
||||
artist: someTrack.artist.name,
|
||||
artistId: someTrack.artist.id,
|
||||
artistId: `artist:${someTrack.artist.id}`,
|
||||
duration: someTrack.duration,
|
||||
genre: someTrack.album.genre?.name,
|
||||
genreId: someTrack.album.genre?.id,
|
||||
@@ -304,12 +307,28 @@ describe("album", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("sonosifyMimeType", () => {
|
||||
describe("when is audio/x-flac", () => {
|
||||
it("should be mapped to audio/flac", () => {
|
||||
expect(sonosifyMimeType("audio/x-flac")).toEqual("audio/flac");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when it is not audio/x-flac", () => {
|
||||
it("should be returned as is", () => {
|
||||
expect(sonosifyMimeType("audio/flac")).toEqual("audio/flac");
|
||||
expect(sonosifyMimeType("audio/mpeg")).toEqual("audio/mpeg");
|
||||
expect(sonosifyMimeType("audio/whoop")).toEqual("audio/whoop");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("playlistAlbumArtURL", () => {
|
||||
describe("when the playlist has no albumIds", () => {
|
||||
describe("when the playlist has no coverArt ids", () => {
|
||||
it("should return question mark icon", () => {
|
||||
const bonobUrl = url("http://localhost:1234/context-path?search=yes");
|
||||
const playlist = aPlaylist({
|
||||
entries: [aTrack({ album: undefined }), aTrack({ album: undefined })],
|
||||
entries: [aTrack({ coverArt: undefined }), aTrack({ coverArt: undefined })],
|
||||
});
|
||||
|
||||
expect(playlistAlbumArtURL(bonobUrl, playlist).href()).toEqual(
|
||||
@@ -318,20 +337,20 @@ describe("playlistAlbumArtURL", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the playlist has 2 distinct albumIds", () => {
|
||||
describe("when the playlist has 2 distinct coverArt ids", () => {
|
||||
it("should return them on the url to the image", () => {
|
||||
const bonobUrl = url("http://localhost:1234/context-path?search=yes");
|
||||
const playlist = aPlaylist({
|
||||
entries: [
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "1" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "2" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "1" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "2" })) }),
|
||||
aTrack({ coverArt: "1" }),
|
||||
aTrack({ coverArt: "2" }),
|
||||
aTrack({ coverArt: "1" }),
|
||||
aTrack({ coverArt: "2" }),
|
||||
],
|
||||
});
|
||||
|
||||
expect(playlistAlbumArtURL(bonobUrl, playlist).href()).toEqual(
|
||||
`http://localhost:1234/context-path/art/album/1&2/size/180?search=yes`
|
||||
`http://localhost:1234/context-path/art/1&2/size/180?search=yes`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -341,52 +360,75 @@ describe("playlistAlbumArtURL", () => {
|
||||
const bonobUrl = url("http://localhost:1234/context-path?search=yes");
|
||||
const playlist = aPlaylist({
|
||||
entries: [
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "1" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "2" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "2" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "3" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "4" })) }),
|
||||
aTrack({ coverArt: "1" }),
|
||||
aTrack({ coverArt: "2" }),
|
||||
aTrack({ coverArt: "2" }),
|
||||
aTrack({ coverArt: "3" }),
|
||||
aTrack({ coverArt: "4" }),
|
||||
],
|
||||
});
|
||||
|
||||
expect(playlistAlbumArtURL(bonobUrl, playlist).href()).toEqual(
|
||||
`http://localhost:1234/context-path/art/album/1&2&3&4/size/180?search=yes`
|
||||
`http://localhost:1234/context-path/art/1&2&3&4/size/180?search=yes`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the playlist has 9 distinct albumIds", () => {
|
||||
it("should return 9 of the ids on the url", () => {
|
||||
describe("when the playlist has at least 9 distinct albumIds", () => {
|
||||
it("should return the first 9 of the ids on the url", () => {
|
||||
const bonobUrl = url("http://localhost:1234/context-path?search=yes");
|
||||
const playlist = aPlaylist({
|
||||
entries: [
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "1" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "2" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "3" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "4" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "5" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "6" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "7" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "8" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "9" })) }),
|
||||
aTrack({ coverArt: "1" }),
|
||||
aTrack({ coverArt: "2" }),
|
||||
aTrack({ coverArt: "2" }),
|
||||
aTrack({ coverArt: "2" }),
|
||||
aTrack({ coverArt: "3" }),
|
||||
aTrack({ coverArt: "4" }),
|
||||
aTrack({ coverArt: "5" }),
|
||||
aTrack({ coverArt: "6" }),
|
||||
aTrack({ coverArt: "7" }),
|
||||
aTrack({ coverArt: "8" }),
|
||||
aTrack({ coverArt: "9" }),
|
||||
aTrack({ coverArt: "10" }),
|
||||
aTrack({ coverArt: "11" }),
|
||||
],
|
||||
});
|
||||
|
||||
expect(playlistAlbumArtURL(bonobUrl, playlist).href()).toEqual(
|
||||
`http://localhost:1234/context-path/art/album/1&2&3&4&5&6&7&8&9/size/180?search=yes`
|
||||
`http://localhost:1234/context-path/art/1&2&3&4&5&6&7&8&9/size/180?search=yes`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("defaultAlbumArtURI", () => {
|
||||
it("should create the correct URI", () => {
|
||||
const bonobUrl = url("http://localhost:1234/context-path?search=yes");
|
||||
const album = anAlbum();
|
||||
const bonobUrl = new URLBuilder("http://bonob.example.com:8080/context?search=yes");
|
||||
|
||||
expect(defaultAlbumArtURI(bonobUrl, album).href()).toEqual(
|
||||
`http://localhost:1234/context-path/art/album/${album.id}/size/180?search=yes`
|
||||
);
|
||||
describe("when there is an album coverArt", () => {
|
||||
it("should use it in the image url", () => {
|
||||
expect(
|
||||
defaultAlbumArtURI(
|
||||
bonobUrl,
|
||||
anAlbum({ coverArt: "coverArt:123" })
|
||||
).href()
|
||||
).toEqual(
|
||||
"http://bonob.example.com:8080/context/art/coverArt:123/size/180?search=yes"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is no album coverArt", () => {
|
||||
it("should return a vinly icon image", () => {
|
||||
expect(
|
||||
defaultAlbumArtURI(
|
||||
bonobUrl,
|
||||
anAlbum({ coverArt: undefined })
|
||||
).href()
|
||||
).toEqual(
|
||||
"http://bonob.example.com:8080/context/icon/vinyl/size/legacy?search=yes"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -396,7 +438,7 @@ describe("defaultArtistArtURI", () => {
|
||||
const artist = anArtist();
|
||||
|
||||
expect(defaultArtistArtURI(bonobUrl, artist).href()).toEqual(
|
||||
`http://localhost:1234/something/art/artist/${artist.id}/size/180?s=123`
|
||||
`http://localhost:1234/something/art/artist:${artist.id}/size/180?s=123`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -448,7 +490,7 @@ describe("api", () => {
|
||||
const accessToken = `accessToken-${uuid()}`;
|
||||
|
||||
const bonobUrlWithAccessToken = bonobUrl.append({
|
||||
searchParams: { "bat": accessToken },
|
||||
searchParams: { bat: accessToken },
|
||||
});
|
||||
|
||||
const service = bonobService("test-api", 133, bonobUrl, "AppLink");
|
||||
@@ -1020,7 +1062,7 @@ describe("api", () => {
|
||||
title: genre.name,
|
||||
albumArtURI: iconArtURI(
|
||||
bonobUrl,
|
||||
iconForGenre(genre.name),
|
||||
iconForGenre(genre.name)
|
||||
).href(),
|
||||
})),
|
||||
index: 0,
|
||||
@@ -1045,7 +1087,7 @@ describe("api", () => {
|
||||
title: genre.name,
|
||||
albumArtURI: iconArtURI(
|
||||
bonobUrl,
|
||||
iconForGenre(genre.name),
|
||||
iconForGenre(genre.name)
|
||||
).href(),
|
||||
})),
|
||||
index: 1,
|
||||
@@ -2302,14 +2344,17 @@ describe("api", () => {
|
||||
artistId: `artist:${track.artist.id}`,
|
||||
artist: track.artist.name,
|
||||
albumId: `album:${track.album.id}`,
|
||||
albumArtist: track.artist.name,
|
||||
albumArtistId: `artist:${track.artist.id}`,
|
||||
album: track.album.name,
|
||||
genre: track.genre?.name,
|
||||
genreId: track.genre?.id,
|
||||
duration: track.duration,
|
||||
albumArtURI: defaultAlbumArtURI(
|
||||
bonobUrlWithAccessToken,
|
||||
track.album
|
||||
track
|
||||
).href(),
|
||||
trackNumber: track.number,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -2510,7 +2555,7 @@ describe("api", () => {
|
||||
expect(root[0]).toEqual({
|
||||
getMediaMetadataResult: track(
|
||||
bonobUrl.with({
|
||||
searchParams: { "bat": accessToken },
|
||||
searchParams: { bat: accessToken },
|
||||
}),
|
||||
someTrack
|
||||
),
|
||||
|
||||
@@ -3,14 +3,15 @@ import { v4 as uuid } from "uuid";
|
||||
|
||||
import {
|
||||
isDodgyImage,
|
||||
Navidrome,
|
||||
Subsonic,
|
||||
t,
|
||||
BROWSER_HEADERS,
|
||||
DODGY_IMAGE_NAME,
|
||||
asGenre,
|
||||
appendMimeTypeToClientFor,
|
||||
asURLSearchParams,
|
||||
} from "../src/navidrome";
|
||||
splitCoverArtId,
|
||||
} from "../src/subsonic";
|
||||
import encryption from "../src/encryption";
|
||||
|
||||
import axios from "axios";
|
||||
@@ -44,6 +45,8 @@ import {
|
||||
aPlaylist,
|
||||
aPlaylistSummary,
|
||||
aTrack,
|
||||
POP,
|
||||
ROCK,
|
||||
} from "./builders";
|
||||
import { b64Encode } from "../src/b64";
|
||||
|
||||
@@ -181,6 +184,8 @@ const getArtistInfoXml = (
|
||||
</artistInfo2>
|
||||
</subsonic-response>`;
|
||||
|
||||
const maybeIdFromCoverArtId = (coverArt: string | undefined) => coverArt ? splitCoverArtId(coverArt)[1] : "";
|
||||
|
||||
const albumXml = (
|
||||
artist: Artist,
|
||||
album: AlbumSummary,
|
||||
@@ -191,7 +196,7 @@ const albumXml = (
|
||||
title="${album.name}" name="${album.name}" album="${album.name}"
|
||||
artist="${artist.name}"
|
||||
genre="${album.genre?.name}"
|
||||
coverArt="foo"
|
||||
coverArt="${maybeIdFromCoverArtId(album.coverArt)}"
|
||||
duration="123"
|
||||
playCount="4"
|
||||
year="${album.year}"
|
||||
@@ -209,7 +214,7 @@ const songXml = (track: Track) => `<song
|
||||
track="${track.number}"
|
||||
genre="${track.genre?.name}"
|
||||
isDir="false"
|
||||
coverArt="71381"
|
||||
coverArt="${maybeIdFromCoverArtId(track.coverArt)}"
|
||||
created="2004-11-08T23:36:11"
|
||||
duration="${track.duration}"
|
||||
bitRate="128"
|
||||
@@ -341,7 +346,7 @@ const getPlayList = (
|
||||
track="${it.number}"
|
||||
year="${it.album.year}"
|
||||
genre="${it.album.genre?.name}"
|
||||
coverArt="..."
|
||||
coverArt="${splitCoverArtId(it.coverArt!)[1]}"
|
||||
size="123"
|
||||
contentType="${it.mimeType}"
|
||||
suffix="mp3"
|
||||
@@ -430,14 +435,28 @@ const EMPTY = `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok
|
||||
|
||||
const PING_OK = `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)"></subsonic-response>`;
|
||||
|
||||
describe("Navidrome", () => {
|
||||
describe("splitCoverArtId", () => {
|
||||
it("should split correctly", () => {
|
||||
expect(splitCoverArtId("foo:bar")).toEqual(["foo", "bar"])
|
||||
expect(splitCoverArtId("foo:bar:car:jar")).toEqual(["foo", "bar:car:jar"])
|
||||
});
|
||||
|
||||
it("should blow up when the id is invalid", () => {
|
||||
expect(() => splitCoverArtId("")).toThrow(`'' is an invalid coverArt id`)
|
||||
expect(() => splitCoverArtId("foo:")).toThrow(`'foo:' is an invalid coverArt id`)
|
||||
expect(() => splitCoverArtId("foo:")).toThrow(`'foo:' is an invalid coverArt id`)
|
||||
expect(() => splitCoverArtId(":dog")).toThrow(`':dog' is an invalid coverArt id`)
|
||||
});
|
||||
});
|
||||
|
||||
describe("Subsonic", () => {
|
||||
const url = "http://127.0.0.22:4567";
|
||||
const username = "user1";
|
||||
const password = "pass1";
|
||||
const salt = "saltysalty";
|
||||
|
||||
const streamClientApplication = jest.fn();
|
||||
const navidrome = new Navidrome(
|
||||
const navidrome = new Subsonic(
|
||||
url,
|
||||
encryption("secret"),
|
||||
streamClientApplication
|
||||
@@ -500,7 +519,7 @@ describe("Navidrome", () => {
|
||||
|
||||
const token = await navidrome.generateToken({ username, password });
|
||||
expect(token).toEqual({
|
||||
message: "Navidrome error:Wrong username or password",
|
||||
message: "Subsonic error:Wrong username or password",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2483,7 +2502,7 @@ describe("Navidrome", () => {
|
||||
|
||||
return expect(
|
||||
musicLibrary.stream({ trackId, range: undefined })
|
||||
).rejects.toEqual(`Navidrome failed with a 400 status`);
|
||||
).rejects.toEqual(`Subsonic failed with a 400 status`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2660,7 +2679,7 @@ describe("Navidrome", () => {
|
||||
.generateToken({ username, password })
|
||||
.then((it) => it as AuthSuccess)
|
||||
.then((it) => navidrome.login(it.authToken))
|
||||
.then((it) => it.coverArt(coverArtId, "album"));
|
||||
.then((it) => it.coverArt(`coverArt:${coverArtId}`));
|
||||
|
||||
expect(result).toEqual({
|
||||
contentType: streamResponse.headers["content-type"],
|
||||
@@ -2698,7 +2717,7 @@ describe("Navidrome", () => {
|
||||
.generateToken({ username, password })
|
||||
.then((it) => it as AuthSuccess)
|
||||
.then((it) => navidrome.login(it.authToken))
|
||||
.then((it) => it.coverArt(coverArtId, "album", size));
|
||||
.then((it) => it.coverArt(`coverArt:${coverArtId}`, size));
|
||||
|
||||
expect(result).toEqual({
|
||||
contentType: streamResponse.headers["content-type"],
|
||||
@@ -2754,7 +2773,7 @@ describe("Navidrome", () => {
|
||||
.generateToken({ username, password })
|
||||
.then((it) => it as AuthSuccess)
|
||||
.then((it) => navidrome.login(it.authToken))
|
||||
.then((it) => it.coverArt(artistId, "artist"));
|
||||
.then((it) => it.coverArt(`artist:${artistId}`));
|
||||
|
||||
expect(result).toEqual({
|
||||
contentType: streamResponse.headers["content-type"],
|
||||
@@ -2783,85 +2802,206 @@ describe("Navidrome", () => {
|
||||
|
||||
describe("when the artist doest not have a valid artist uri", () => {
|
||||
describe("however has some albums", () => {
|
||||
it("should fetch the artists first album image", async () => {
|
||||
const artistId = "someArtist123";
|
||||
const artistId = "someArtist123";
|
||||
|
||||
const images: Images = {
|
||||
small: undefined,
|
||||
medium: undefined,
|
||||
large: undefined,
|
||||
};
|
||||
const images: Images = {
|
||||
small: undefined,
|
||||
medium: undefined,
|
||||
large: undefined,
|
||||
};
|
||||
|
||||
const streamResponse = {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": "image/jpeg",
|
||||
},
|
||||
data: Buffer.from("the image", "ascii"),
|
||||
};
|
||||
const streamResponse = {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": "image/jpeg",
|
||||
},
|
||||
data: Buffer.from("the image", "ascii"),
|
||||
};
|
||||
|
||||
const album1 = anAlbum();
|
||||
const album2 = anAlbum();
|
||||
|
||||
const artist = anArtist({
|
||||
id: artistId,
|
||||
albums: [album1, album2],
|
||||
image: images,
|
||||
});
|
||||
|
||||
mockGET
|
||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve(ok(getArtistXml(artist)))
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve(ok(getArtistInfoXml(artist)))
|
||||
)
|
||||
.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.coverArt(artistId, "artist"));
|
||||
|
||||
expect(result).toEqual({
|
||||
contentType: streamResponse.headers["content-type"],
|
||||
data: streamResponse.data,
|
||||
});
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtist`, {
|
||||
params: asURLSearchParams({
|
||||
...authParams,
|
||||
describe("all albums have coverArt", () => {
|
||||
it("should fetch the coverArt from the first album", async () => {
|
||||
const album1 = anAlbum({ coverArt: `coverArt:album1CoverArt` });
|
||||
const album2 = anAlbum({ coverArt: `coverArt:album2CoverArt` });
|
||||
|
||||
const artist = anArtist({
|
||||
id: artistId,
|
||||
}),
|
||||
headers,
|
||||
});
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(
|
||||
`${url}/rest/getArtistInfo2`,
|
||||
{
|
||||
albums: [album1, album2],
|
||||
image: images,
|
||||
});
|
||||
|
||||
mockGET
|
||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve(ok(getArtistXml(artist)))
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve(ok(getArtistInfoXml(artist)))
|
||||
)
|
||||
.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.coverArt(`artist:${artistId}`));
|
||||
|
||||
expect(result).toEqual({
|
||||
contentType: streamResponse.headers["content-type"],
|
||||
data: streamResponse.data,
|
||||
});
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtist`, {
|
||||
params: asURLSearchParams({
|
||||
...authParams,
|
||||
id: artistId,
|
||||
count: 50,
|
||||
includeNotPresent: true,
|
||||
}),
|
||||
headers,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(
|
||||
`${url}/rest/getArtistInfo2`,
|
||||
{
|
||||
params: asURLSearchParams({
|
||||
...authParams,
|
||||
id: artistId,
|
||||
count: 50,
|
||||
includeNotPresent: true,
|
||||
}),
|
||||
headers,
|
||||
}
|
||||
);
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(
|
||||
`${url}/rest/getCoverArt`,
|
||||
{
|
||||
params: asURLSearchParams({
|
||||
...authParams,
|
||||
id: splitCoverArtId(album1.coverArt!)[1],
|
||||
}),
|
||||
headers,
|
||||
responseType: "arraybuffer",
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(
|
||||
`${url}/rest/getCoverArt`,
|
||||
{
|
||||
describe("the first album does not have coverArt", () => {
|
||||
it("should fetch the coverArt from the first album with coverArt", async () => {
|
||||
const album1 = anAlbum({ coverArt: undefined });
|
||||
const album2 = anAlbum({ coverArt: `coverArt:album2CoverArt` });
|
||||
|
||||
const artist = anArtist({
|
||||
id: artistId,
|
||||
albums: [album1, album2],
|
||||
image: images,
|
||||
});
|
||||
|
||||
mockGET
|
||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve(ok(getArtistXml(artist)))
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve(ok(getArtistInfoXml(artist)))
|
||||
)
|
||||
.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.coverArt(`artist:${artistId}`));
|
||||
|
||||
expect(result).toEqual({
|
||||
contentType: streamResponse.headers["content-type"],
|
||||
data: streamResponse.data,
|
||||
});
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtist`, {
|
||||
params: asURLSearchParams({
|
||||
...authParams,
|
||||
id: album1.id,
|
||||
id: artistId,
|
||||
}),
|
||||
headers,
|
||||
responseType: "arraybuffer",
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(
|
||||
`${url}/rest/getArtistInfo2`,
|
||||
{
|
||||
params: asURLSearchParams({
|
||||
...authParams,
|
||||
id: artistId,
|
||||
count: 50,
|
||||
includeNotPresent: true,
|
||||
}),
|
||||
headers,
|
||||
}
|
||||
);
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(
|
||||
`${url}/rest/getCoverArt`,
|
||||
{
|
||||
params: asURLSearchParams({
|
||||
...authParams,
|
||||
id: splitCoverArtId(album2.coverArt!)[1],
|
||||
}),
|
||||
headers,
|
||||
responseType: "arraybuffer",
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("no albums have coverArt", () => {
|
||||
it("should return undefined", async () => {
|
||||
const album1 = anAlbum({ coverArt: undefined });
|
||||
const album2 = anAlbum({ coverArt: undefined });
|
||||
|
||||
const artist = anArtist({
|
||||
id: artistId,
|
||||
albums: [album1, album2],
|
||||
image: images,
|
||||
});
|
||||
|
||||
mockGET
|
||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve(ok(getArtistXml(artist)))
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve(ok(getArtistInfoXml(artist)))
|
||||
)
|
||||
.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.coverArt(`artist:${artistId}`));
|
||||
|
||||
expect(result).toEqual(undefined);
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtist`, {
|
||||
params: asURLSearchParams({
|
||||
...authParams,
|
||||
id: artistId,
|
||||
}),
|
||||
headers,
|
||||
});
|
||||
|
||||
expect(axios.get).toHaveBeenCalledWith(
|
||||
`${url}/rest/getArtistInfo2`,
|
||||
{
|
||||
params: asURLSearchParams({
|
||||
...authParams,
|
||||
id: artistId,
|
||||
count: 50,
|
||||
includeNotPresent: true,
|
||||
}),
|
||||
headers,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2903,7 +3043,7 @@ describe("Navidrome", () => {
|
||||
.generateToken({ username, password })
|
||||
.then((it) => it as AuthSuccess)
|
||||
.then((it) => navidrome.login(it.authToken))
|
||||
.then((it) => it.coverArt(artistId, "artist"));
|
||||
.then((it) => it.coverArt(`artist:${artistId}`));
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
@@ -2978,7 +3118,7 @@ describe("Navidrome", () => {
|
||||
.generateToken({ username, password })
|
||||
.then((it) => it as AuthSuccess)
|
||||
.then((it) => navidrome.login(it.authToken))
|
||||
.then((it) => it.coverArt(artistId, "artist", size));
|
||||
.then((it) => it.coverArt(`artist:${artistId}`, size));
|
||||
|
||||
expect(result).toEqual({
|
||||
contentType: streamResponse.headers["content-type"],
|
||||
@@ -3050,7 +3190,7 @@ describe("Navidrome", () => {
|
||||
.generateToken({ username, password })
|
||||
.then((it) => it as AuthSuccess)
|
||||
.then((it) => navidrome.login(it.authToken))
|
||||
.then((it) => it.coverArt(artistId, "artist", size));
|
||||
.then((it) => it.coverArt(`artist:${artistId}`, size));
|
||||
|
||||
expect(result).toEqual({
|
||||
contentType: streamResponse.headers["content-type"],
|
||||
@@ -3083,7 +3223,7 @@ describe("Navidrome", () => {
|
||||
{
|
||||
params: asURLSearchParams({
|
||||
...authParams,
|
||||
id: album1.id,
|
||||
id: splitCoverArtId(album1.coverArt!)[1],
|
||||
size,
|
||||
}),
|
||||
headers,
|
||||
@@ -3131,7 +3271,7 @@ describe("Navidrome", () => {
|
||||
.generateToken({ username, password })
|
||||
.then((it) => it as AuthSuccess)
|
||||
.then((it) => navidrome.login(it.authToken))
|
||||
.then((it) => it.coverArt(artistId, "artist"));
|
||||
.then((it) => it.coverArt(`artist:${artistId}`));
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
@@ -3178,8 +3318,8 @@ describe("Navidrome", () => {
|
||||
data: Buffer.from("the image", "ascii"),
|
||||
};
|
||||
|
||||
const album1 = anAlbum({ id: "album1Id" });
|
||||
const album2 = anAlbum({ id: "album2Id" });
|
||||
const album1 = anAlbum({ id: "album1Id", coverArt: "coverArt:album1CoverArt" });
|
||||
const album2 = anAlbum({ id: "album2Id", coverArt: "coverArt:album2CoverArt" });
|
||||
|
||||
const artist = anArtist({
|
||||
id: artistId,
|
||||
@@ -3201,7 +3341,7 @@ describe("Navidrome", () => {
|
||||
.generateToken({ username, password })
|
||||
.then((it) => it as AuthSuccess)
|
||||
.then((it) => navidrome.login(it.authToken))
|
||||
.then((it) => it.coverArt(artistId, "artist", size));
|
||||
.then((it) => it.coverArt(`artist:${artistId}`, size));
|
||||
|
||||
expect(result).toEqual({
|
||||
contentType: streamResponse.headers["content-type"],
|
||||
@@ -3234,7 +3374,7 @@ describe("Navidrome", () => {
|
||||
{
|
||||
params: asURLSearchParams({
|
||||
...authParams,
|
||||
id: album1.id,
|
||||
id: splitCoverArtId(album1.coverArt!)[1],
|
||||
size,
|
||||
}),
|
||||
headers,
|
||||
@@ -3282,7 +3422,7 @@ describe("Navidrome", () => {
|
||||
.generateToken({ username, password })
|
||||
.then((it) => it as AuthSuccess)
|
||||
.then((it) => navidrome.login(it.authToken))
|
||||
.then((it) => it.coverArt(artistId, "artist"));
|
||||
.then((it) => it.coverArt(`artist:${artistId}`));
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
|
||||
@@ -3896,7 +4036,7 @@ describe("Navidrome", () => {
|
||||
.then((it) => it as AuthSuccess)
|
||||
.then((it) => navidrome.login(it.authToken))
|
||||
.then((it) => it.playlist(id))
|
||||
).rejects.toEqual("Navidrome error:data not found");
|
||||
).rejects.toEqual("Subsonic error:data not found");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3905,13 +4045,24 @@ describe("Navidrome", () => {
|
||||
it("should return the playlist with entries", async () => {
|
||||
const id = uuid();
|
||||
const name = "Great Playlist";
|
||||
const artist1 = anArtist();
|
||||
const album1 = anAlbum({ artistId: artist1.id, artistName: artist1.name, genre: POP });
|
||||
const track1 = aTrack({
|
||||
genre: { id: b64Encode("pop"), name: "pop" },
|
||||
genre: POP,
|
||||
number: 66,
|
||||
coverArt: album1.coverArt,
|
||||
artist: artistToArtistSummary(artist1),
|
||||
album: albumToAlbumSummary(album1)
|
||||
});
|
||||
|
||||
const artist2 = anArtist();
|
||||
const album2 = anAlbum({ artistId: artist2.id, artistName: artist2.name, genre: ROCK });
|
||||
const track2 = aTrack({
|
||||
genre: { id: b64Encode("rock"), name: "rock" },
|
||||
genre: ROCK,
|
||||
number: 77,
|
||||
coverArt: album2.coverArt,
|
||||
artist: artistToArtistSummary(artist2),
|
||||
album: albumToAlbumSummary(album2)
|
||||
});
|
||||
|
||||
mockGET
|
||||
@@ -4263,7 +4414,7 @@ describe("Navidrome", () => {
|
||||
.then((it) => it as AuthSuccess)
|
||||
.then((it) => navidrome.login(it.authToken))
|
||||
.then((it) => it.similarSongs(id))
|
||||
).rejects.toEqual("Navidrome error:data not found");
|
||||
).rejects.toEqual("Subsonic error:data not found");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -4323,10 +4474,9 @@ describe("Navidrome", () => {
|
||||
it("should return them", async () => {
|
||||
const artistId = "bobMarleyId";
|
||||
const artistName = "Bob Marley";
|
||||
const pop = asGenre("Pop");
|
||||
|
||||
const album1 = anAlbum({ name: "Burnin", genre: pop });
|
||||
const album2 = anAlbum({ name: "Churning", genre: pop });
|
||||
const album1 = anAlbum({ name: "Burnin", genre: POP });
|
||||
const album2 = anAlbum({ name: "Churning", genre: POP });
|
||||
|
||||
const artist = anArtist({
|
||||
id: artistId,
|
||||
@@ -4337,19 +4487,19 @@ describe("Navidrome", () => {
|
||||
const track1 = aTrack({
|
||||
artist: artistToArtistSummary(artist),
|
||||
album: albumToAlbumSummary(album1),
|
||||
genre: pop,
|
||||
genre: POP
|
||||
});
|
||||
|
||||
const track2 = aTrack({
|
||||
artist: artistToArtistSummary(artist),
|
||||
album: albumToAlbumSummary(album2),
|
||||
genre: pop,
|
||||
genre: POP,
|
||||
});
|
||||
|
||||
const track3 = aTrack({
|
||||
artist: artistToArtistSummary(artist),
|
||||
album: albumToAlbumSummary(album1),
|
||||
genre: pop,
|
||||
genre: POP,
|
||||
});
|
||||
|
||||
mockGET
|
||||
Reference in New Issue
Block a user