Compare commits

..

3 Commits

Author SHA1 Message Date
Laurent le Beau-Martin
192f65a56b Improve ffmpeg command to transcode flac (#99)
* Improve ffmpeg command to transcode flac

The command previously suggested forced the output sample rate to 48 kHz, even if the input was lower, at 44.1 kHz. 
This new command lets `ffmpeg` select the appropriate output sample rate to minimize conversion. 
Documentation: https://www.ffmpeg.org/ffmpeg-filters.html#aformat-1

* Update transcoding command

- Support more sample rates and bit depths.
- Add note about S1
2022-03-10 15:06:56 +11:00
Simon J
9b3df4ce1a Support for using boolean values when using yaml docker-compose files rather than strings for booleans (#98) 2022-02-28 22:07:17 +11:00
Simon J
df9a6d4663 Improve date handling (#94) 2022-02-02 13:26:01 +11:00
8 changed files with 348 additions and 244 deletions

View File

@@ -209,10 +209,16 @@ In this case you could set;
BNB_SUBSONIC_CUSTOM_CLIENTS="audio/flac" BNB_SUBSONIC_CUSTOM_CLIENTS="audio/flac"
``` ```
This would result in 2 players in Navidrome, one called 'bonob', the other called 'bonob+audio/flac'. You could then configure a custom flac transcoder in Navidrome that re-samples the flacs to a sonos supported format, ie [Using something like this](https://stackoverflow.com/questions/41420391/ffmpeg-flac-24-bit-96khz-to-16-bit-48khz); This would result in 2 players in Navidrome, one called 'bonob', the other called 'bonob+audio/flac'. You could then configure a custom flac transcoder in Navidrome that re-samples the flacs to a sonos supported format, ie [Using something like this](https://stackoverflow.com/questions/41420391/ffmpeg-flac-24-bit-96khz-to-16-bit-48khz) or [this](https://stackoverflow.com/questions/52119489/ffmpeg-limit-audio-sample-rate):
```bash ```bash
ffmpeg -i %s -af aresample=resampler=soxr:out_sample_fmt=s16:out_sample_rate=48000 -f flac - ffmpeg -i %s -af aformat=sample_fmts=s16|s32:sample_rates=8000|11025|16000|22050|24000|32000|44100|48000 -f flac -
```
**Note for Sonos S1:** [24-bit depth is only supported by Sonos S2](https://support.sonos.com/s/article/79?language=en_US), so if your system is still on Sonos S1, transcoding should convert all FLACs to 16-bit:
```bash
ffmpeg -i %s -af aformat=sample_fmts=s16:sample_rates=8000|11025|16000|22050|24000|32000|44100|48000 -f flac -
``` ```
### Changing Icon colors ### Changing Icon colors

View File

@@ -27,8 +27,8 @@ services:
BNB_URL: http://192.168.1.111:4534 BNB_URL: http://192.168.1.111:4534
BNB_SECRET: changeme BNB_SECRET: changeme
BNB_SONOS_SERVICE_ID: 246 BNB_SONOS_SERVICE_ID: 246
BNB_SONOS_AUTO_REGISTER: "true" BNB_SONOS_AUTO_REGISTER: true
BNB_SONOS_DEVICE_DISCOVERY: "true" BNB_SONOS_DEVICE_DISCOVERY: true
# ip address of one of your sonos devices # ip address of one of your sonos devices
BNB_SONOS_SEED_HOST: 192.168.1.121 BNB_SONOS_SEED_HOST: 192.168.1.121
BNB_SUBSONIC_URL: http://navidrome:4533 BNB_SUBSONIC_URL: http://navidrome:4533

View File

@@ -1,13 +1,38 @@
import dayjs, { Dayjs } from "dayjs"; import dayjs, { Dayjs } from "dayjs";
export const isChristmas = (clock: Clock = SystemClock) => clock.now().month() == 11 && clock.now().date() == 25; function fixedDateMonthEvent(dateMonth: string) {
export const isMay4 = (clock: Clock = SystemClock) => clock.now().month() == 4 && clock.now().date() == 4; const date = Number.parseInt(dateMonth.split("/")[0]!);
export const isHalloween = (clock: Clock = SystemClock) => clock.now().month() == 9 && clock.now().date() == 31 const month = Number.parseInt(dateMonth.split("/")[1]!);
export const isHoli = (clock: Clock = SystemClock) => ["2022/03/18", "2023/03/07", "2024/03/25", "2025/03/14"].map(dayjs).find(it => it.isSame(clock.now())) != undefined return (clock: Clock = SystemClock) => {
export const isCNY = (clock: Clock = SystemClock) => ["2022/02/01", "2023/01/22", "2024/02/10", "2025/02/29"].map(dayjs).find(it => it.isSame(clock.now())) != undefined return clock.now().date() == date && clock.now().month() == month - 1;
export const isCNY_2022 = (clock: Clock = SystemClock) => clock.now().isSame(dayjs("2022/02/01")) };
export const isCNY_2023 = (clock: Clock = SystemClock) => clock.now().isSame(dayjs("2023/01/22")) }
export const isCNY_2024 = (clock: Clock = SystemClock) => clock.now().isSame(dayjs("2024/02/10"))
function fixedDateEvent(date: string) {
const dayjsDate = dayjs(date);
return (clock: Clock = SystemClock) => {
return clock.now().isSame(dayjsDate, "day");
};
}
function anyOf(rules: ((clock: Clock) => boolean)[]) {
return (clock: Clock = SystemClock) => {
return rules.find((rule) => rule(clock)) != undefined;
};
}
export const isChristmas = fixedDateMonthEvent("25/12");
export const isMay4 = fixedDateMonthEvent("04/05");
export const isHalloween = fixedDateMonthEvent("31/10");
export const isHoli = anyOf(
["2022/03/18", "2023/03/07", "2024/03/25", "2025/03/14"].map(fixedDateEvent)
)
export const isCNY_2022 = fixedDateEvent("2022/02/01");
export const isCNY_2023 = fixedDateEvent("2023/01/22");
export const isCNY_2024 = fixedDateEvent("2024/02/10");
export const isCNY_2025 = fixedDateEvent("2025/02/29");
export const isCNY = anyOf([isCNY_2022, isCNY_2023, isCNY_2024, isCNY_2025]);
export interface Clock { export interface Clock {
now(): Dayjs; now(): Dayjs;
@@ -22,7 +47,8 @@ export class FixedClock implements Clock {
this.time = time; this.time = time;
} }
add = (t: number, unit: dayjs.UnitTypeShort) => this.time = this.time.add(t, unit) add = (t: number, unit: dayjs.UnitTypeShort) =>
(this.time = this.time.add(t, unit));
now = () => this.time; now = () => this.time;
} }

View File

@@ -5,20 +5,22 @@ import url from "./url_builder";
export const WORD = /^\w+$/; export const WORD = /^\w+$/;
export const COLOR = /^#?\w+$/; export const COLOR = /^#?\w+$/;
type EnvVarOpts = { type EnvVarOpts<T> = {
default: string | undefined; default: T | undefined;
legacy: string[] | undefined; legacy: string[] | undefined;
validationPattern: RegExp | undefined; validationPattern: RegExp | undefined;
parser: ((value: string) => T) | undefined
}; };
export function envVar( export function envVar<T>(
name: string, name: string,
opts: Partial<EnvVarOpts> = { opts: Partial<EnvVarOpts<T>> = {
default: undefined, default: undefined,
legacy: undefined, legacy: undefined,
validationPattern: undefined, validationPattern: undefined,
parser: undefined
} }
) { ): T {
const result = [name, ...(opts.legacy || [])] const result = [name, ...(opts.legacy || [])]
.map((it) => ({ key: it, value: process.env[it] })) .map((it) => ({ key: it, value: process.env[it] }))
.find((it) => it.value); .find((it) => it.value);
@@ -36,17 +38,28 @@ export function envVar(
logger.warn(`Configuration key '${result.key}' is deprecated, replace with '${name}'`) logger.warn(`Configuration key '${result.key}' is deprecated, replace with '${name}'`)
} }
return result?.value || opts.default; let value: T | undefined = undefined;
if(result?.value && opts.parser) {
value = opts.parser(result?.value)
} else if(result?.value)
value = result?.value as any as T
return value == undefined ? opts.default as T : value;
} }
export const bnbEnvVar = (key: string, opts: Partial<EnvVarOpts> = {}) => export const bnbEnvVar = <T>(key: string, opts: Partial<EnvVarOpts<T>> = {}) =>
envVar(`BNB_${key}`, { envVar(`BNB_${key}`, {
...opts, ...opts,
legacy: [`BONOB_${key}`, ...(opts.legacy || [])], legacy: [`BONOB_${key}`, ...(opts.legacy || [])],
}); });
const asBoolean = (value: string) => value == "true";
const asInt = (value: string) => Number.parseInt(value);
export default function () { export default function () {
const port = +bnbEnvVar("PORT", { default: "4534" })!; const port = bnbEnvVar<number>("PORT", { default: 4534, parser: asInt })!;
const bonobUrl = bnbEnvVar("URL", { const bonobUrl = bnbEnvVar("URL", {
legacy: ["BONOB_WEB_ADDRESS"], legacy: ["BONOB_WEB_ADDRESS"],
default: `http://${hostname()}:${port}`, default: `http://${hostname()}:${port}`,
@@ -62,34 +75,34 @@ export default function () {
return { return {
port, port,
bonobUrl: url(bonobUrl), bonobUrl: url(bonobUrl),
secret: bnbEnvVar("SECRET", { default: "bonob" })!, secret: bnbEnvVar<string>("SECRET", { default: "bonob" })!,
authTimeout: bnbEnvVar("AUTH_TIMEOUT", { default: "1h" })!, authTimeout: bnbEnvVar<string>("AUTH_TIMEOUT", { default: "1h" })!,
icons: { icons: {
foregroundColor: bnbEnvVar("ICON_FOREGROUND_COLOR", { foregroundColor: bnbEnvVar<string>("ICON_FOREGROUND_COLOR", {
validationPattern: COLOR, validationPattern: COLOR,
}), }),
backgroundColor: bnbEnvVar("ICON_BACKGROUND_COLOR", { backgroundColor: bnbEnvVar<string>("ICON_BACKGROUND_COLOR", {
validationPattern: COLOR, validationPattern: COLOR,
}), }),
}, },
sonos: { sonos: {
serviceName: bnbEnvVar("SONOS_SERVICE_NAME", { default: "bonob" })!, serviceName: bnbEnvVar<string>("SONOS_SERVICE_NAME", { default: "bonob" })!,
discovery: { discovery: {
enabled: enabled:
bnbEnvVar("SONOS_DEVICE_DISCOVERY", { default: "true" }) == "true", bnbEnvVar<boolean>("SONOS_DEVICE_DISCOVERY", { default: true, parser: asBoolean }),
seedHost: bnbEnvVar("SONOS_SEED_HOST"), seedHost: bnbEnvVar<string>("SONOS_SEED_HOST"),
}, },
autoRegister: autoRegister:
bnbEnvVar("SONOS_AUTO_REGISTER", { default: "false" }) == "true", bnbEnvVar<boolean>("SONOS_AUTO_REGISTER", { default: false, parser: asBoolean }),
sid: Number(bnbEnvVar("SONOS_SERVICE_ID", { default: "246" })), sid: bnbEnvVar<number>("SONOS_SERVICE_ID", { default: 246, parser: asInt }),
}, },
subsonic: { subsonic: {
url: bnbEnvVar("SUBSONIC_URL", { legacy: ["BONOB_NAVIDROME_URL"], default: `http://${hostname()}:4533` })!, url: bnbEnvVar("SUBSONIC_URL", { legacy: ["BONOB_NAVIDROME_URL"], default: `http://${hostname()}:4533` })!,
customClientsFor: bnbEnvVar("SUBSONIC_CUSTOM_CLIENTS", { legacy: ["BONOB_NAVIDROME_CUSTOM_CLIENTS"] }), customClientsFor: bnbEnvVar<string>("SUBSONIC_CUSTOM_CLIENTS", { legacy: ["BONOB_NAVIDROME_CUSTOM_CLIENTS"] }),
artistImageCache: bnbEnvVar("SUBSONIC_ARTIST_IMAGE_CACHE"), artistImageCache: bnbEnvVar<string>("SUBSONIC_ARTIST_IMAGE_CACHE"),
}, },
scrobbleTracks: bnbEnvVar("SCROBBLE_TRACKS", { default: "true" }) == "true", scrobbleTracks: bnbEnvVar<boolean>("SCROBBLE_TRACKS", { default: true, parser: asBoolean }),
reportNowPlaying: reportNowPlaying:
bnbEnvVar("REPORT_NOW_PLAYING", { default: "true" }) == "true", bnbEnvVar<boolean>("REPORT_NOW_PLAYING", { default: true, parser: asBoolean }),
}; };
} }

View File

@@ -1,58 +1,85 @@
import dayjs from "dayjs"; import { randomInt } from "crypto";
import { isChristmas, isCNY, isHalloween, isHoli } from "../src/clock"; import dayjs, { Dayjs } from "dayjs";
import timezone from "dayjs/plugin/timezone";
dayjs.extend(timezone);
describe("isChristmas", () => { import { Clock, isChristmas, isCNY, isCNY_2022, isCNY_2023, isCNY_2024, isCNY_2025, isHalloween, isHoli, isMay4 } from "../src/clock";
["2000/12/25", "2022/12/25", "2030/12/25"].forEach((date) => {
it(`should return true for ${date} regardless of year`, () => {
expect(isChristmas({ now: () => dayjs(date) })).toEqual(true);
const randomDate = () => dayjs().subtract(randomInt(1, 1000), 'days');
const randomDates = (count: number, exclude: string[]) => {
const result: Dayjs[] = [];
while(result.length < count) {
const next = randomDate();
if(!exclude.find(it => dayjs(it).isSame(next, 'date'))) {
result.push(next)
}
}
return result
}
function describeFixedDateMonthEvent(
name: string,
dateMonth: string,
f: (clock: Clock) => boolean
) {
const randomYear = randomInt(2020, 3000);
const date = dateMonth.split("/")[0];
const month = dateMonth.split("/")[1];
describe(name, () => {
it(`should return true for ${randomYear}-${month}-${date}T00:00:00 ragardless of year`, () => {
expect(f({ now: () => dayjs(`${randomYear}-${month}-${date}T00:00:00Z`) })).toEqual(true);
}); });
it(`should return true for ${randomYear}-${month}-${date}T12:00:00 regardless of year`, () => {
expect(f({ now: () => dayjs(`${randomYear}-${month}-${date}T12:00:00Z`) })).toEqual(true);
});
it(`should return true for ${randomYear}-${month}-${date}T23:59:00 regardless of year`, () => {
expect(f({ now: () => dayjs(`${randomYear}-${month}-${date}T23:59:00`) })).toEqual(true);
}); });
["2000/12/24", "2000/12/26", "2021/01/01"].forEach((date) => { ["2000/12/24", "2000/12/26", "2021/01/01"].forEach((date) => {
it(`should return false for ${date} regardless of year`, () => { it(`should return false for ${date}`, () => {
expect(isChristmas({ now: () => dayjs(date) })).toEqual(false); expect(f({ now: () => dayjs(date) })).toEqual(false);
}); });
}); });
}); });
}
function describeFixedDateEvent(
name: string,
dates: string[],
f: (clock: Clock) => boolean
) {
describe(name, () => {
dates.forEach((date) => {
it(`should return true for ${date}T00:00:00`, () => {
expect(f({ now: () => dayjs(`${date}T00:00:00`) })).toEqual(true);
});
it(`should return true for ${date}T23:59:59`, () => {
expect(f({ now: () => dayjs(`${date}T23:59:59`) })).toEqual(true);
});
});
describe("isHalloween", () => { randomDates(10, dates).forEach((date) => {
["2000/10/31", "2022/10/31", "2030/10/31"].forEach((date) => { it(`should return false for ${date}`, () => {
it(`should return true for ${date} regardless of year`, () => { expect(f({ now: () => dayjs(date) })).toEqual(false);
expect(isHalloween({ now: () => dayjs(date) })).toEqual(true);
}); });
}); });
});
}
["2000/09/31", "2000/10/30", "2021/01/01"].forEach((date) => { describeFixedDateMonthEvent("christmas", "25/12", isChristmas);
it(`should return false for ${date} regardless of year`, () => { describeFixedDateMonthEvent("halloween", "31/10", isHalloween);
expect(isHalloween({ now: () => dayjs(date) })).toEqual(false); describeFixedDateMonthEvent("may4", "04/05", isMay4);
});
});
});
describe("isHoli", () => { describeFixedDateEvent("holi", ["2022-03-18", "2023-03-07", "2024-03-25", "2025-03-14"], isHoli);
["2022/03/18", "2023/03/07", "2024/03/25", "2025/03/14"].forEach((date) => { describeFixedDateEvent("cny", ["2022-02-01", "2023-01-22", "2024-02-10", "2025-02-29"], isCNY);
it(`should return true for ${date} regardless of year`, () => { describeFixedDateEvent("cny 2022", ["2022-02-01"], isCNY_2022);
expect(isHoli({ now: () => dayjs(date) })).toEqual(true); describeFixedDateEvent("cny 2023", ["2023/01/22"], isCNY_2023);
}); describeFixedDateEvent("cny 2024", ["2024/02/10"], isCNY_2024);
}); describeFixedDateEvent("cny 2025", ["2025/02/29"], isCNY_2025);
["2000/09/31", "2000/10/30", "2021/01/01"].forEach((date) => {
it(`should return false for ${date} regardless of year`, () => {
expect(isHoli({ now: () => dayjs(date) })).toEqual(false);
});
});
});
describe("isCNY", () => {
["2022/02/01", "2023/01/22", "2024/02/10", "2025/02/29"].forEach((date) => {
it(`should return true for ${date} regardless of year`, () => {
expect(isCNY({ now: () => dayjs(date) })).toEqual(true);
});
});
["2000/09/31", "2000/10/30", "2021/01/01"].forEach((date) => {
it(`should return false for ${date} regardless of year`, () => {
expect(isCNY({ now: () => dayjs(date) })).toEqual(false);
});
});
});

View File

@@ -96,43 +96,36 @@ describe("config", () => {
propertyGetter: (config: any) => any propertyGetter: (config: any) => any
) { ) {
describe(name, () => { describe(name, () => {
function expecting({ it.each([
value, [expectedDefault, ""],
expected, [expectedDefault, undefined],
}: { [true, "true"],
value: string; [false, "false"],
expected: boolean; [false, "foo"],
}) { ])("should be %s when env var is '%s'", (expected, value) => {
describe(`when value is '${value}'`, () => {
it(`should be ${expected}`, () => {
process.env[envVar] = value; process.env[envVar] = value;
expect(propertyGetter(config())).toEqual(expected); expect(propertyGetter(config())).toEqual(expected);
}); })
});
}
expecting({ value: "", expected: expectedDefault });
expecting({ value: "true", expected: true });
expecting({ value: "false", expected: false });
expecting({ value: "foo", expected: false });
}); });
} }
describe("bonobUrl", () => { describe("bonobUrl", () => {
["BNB_URL", "BONOB_URL", "BONOB_WEB_ADDRESS"].forEach((key) => { describe.each([
describe(`when ${key} is specified`, () => { "BNB_URL",
"BONOB_URL",
"BONOB_WEB_ADDRESS"
])("when %s is specified", (k) => {
it("should be used", () => { it("should be used", () => {
const url = "http://bonob1.example.com:8877/"; const url = "http://bonob1.example.com:8877/";
process.env["BNB_URL"] = ""; process.env["BNB_URL"] = "";
process.env["BONOB_URL"] = ""; process.env["BONOB_URL"] = "";
process.env["BONOB_WEB_ADDRESS"] = ""; process.env["BONOB_WEB_ADDRESS"] = "";
process.env[key] = url; process.env[k] = url;
expect(config().bonobUrl.href()).toEqual(url); expect(config().bonobUrl.href()).toEqual(url);
}); });
}); });
});
describe("when none of BNB_URL, BONOB_URL, 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", () => { describe("when BONOB_PORT is not specified", () => {
@@ -165,8 +158,10 @@ describe("config", () => {
describe("icons", () => { describe("icons", () => {
describe("foregroundColor", () => { describe("foregroundColor", () => {
["BNB_ICON_FOREGROUND_COLOR", "BONOB_ICON_FOREGROUND_COLOR"].forEach( describe.each([
(k) => { "BNB_ICON_FOREGROUND_COLOR",
"BONOB_ICON_FOREGROUND_COLOR",
])("%s", (k) => {
describe(`when ${k} is not specified`, () => { describe(`when ${k} is not specified`, () => {
it(`should default to undefined`, () => { it(`should default to undefined`, () => {
expect(config().icons.foregroundColor).toEqual(undefined); expect(config().icons.foregroundColor).toEqual(undefined);
@@ -202,13 +197,14 @@ describe("config", () => {
); );
}); });
}); });
} });
);
}); });
describe("backgroundColor", () => { describe("backgroundColor", () => {
["BNB_ICON_BACKGROUND_COLOR", "BONOB_ICON_BACKGROUND_COLOR"].forEach( describe.each([
(k) => { "BNB_ICON_BACKGROUND_COLOR",
"BONOB_ICON_BACKGROUND_COLOR",
])("%s", (k) => {
describe(`when ${k} is not specified`, () => { describe(`when ${k} is not specified`, () => {
it(`should default to undefined`, () => { it(`should default to undefined`, () => {
expect(config().icons.backgroundColor).toEqual(undefined); expect(config().icons.backgroundColor).toEqual(undefined);
@@ -244,8 +240,7 @@ describe("config", () => {
); );
}); });
}); });
} });
);
}); });
}); });
@@ -254,9 +249,12 @@ describe("config", () => {
expect(config().secret).toEqual("bonob"); expect(config().secret).toEqual("bonob");
}); });
["BNB_SECRET", "BONOB_SECRET"].forEach((key) => { describe.each([
it(`should be overridable using ${key}`, () => { "BNB_SECRET",
process.env[key] = "new secret"; "BONOB_SECRET"
])("%s", (k) => {
it(`should be overridable using ${k}`, () => {
process.env[k] = "new secret";
expect(config().secret).toEqual("new secret"); expect(config().secret).toEqual("new secret");
}); });
}); });
@@ -279,79 +277,103 @@ describe("config", () => {
expect(config().sonos.serviceName).toEqual("bonob"); expect(config().sonos.serviceName).toEqual("bonob");
}); });
["BNB_SONOS_SERVICE_NAME", "BONOB_SONOS_SERVICE_NAME"].forEach((k) => { describe.each([
"BNB_SONOS_SERVICE_NAME",
"BONOB_SONOS_SERVICE_NAME"
])(
"%s",
(k) => {
it("should be overridable", () => { it("should be overridable", () => {
process.env[k] = "foobar1000"; process.env[k] = "foobar1000";
expect(config().sonos.serviceName).toEqual("foobar1000"); expect(config().sonos.serviceName).toEqual("foobar1000");
}); });
}); }
);
}); });
["BNB_SONOS_DEVICE_DISCOVERY", "BONOB_SONOS_DEVICE_DISCOVERY"].forEach( describe.each([
(k) => { "BNB_SONOS_DEVICE_DISCOVERY",
"BONOB_SONOS_DEVICE_DISCOVERY",
])("%s", (k) => {
describeBooleanConfigValue( describeBooleanConfigValue(
"deviceDiscovery", "deviceDiscovery",
k, k,
true, true,
(config) => config.sonos.discovery.enabled (config) => config.sonos.discovery.enabled
); );
} });
);
describe("seedHost", () => { describe("seedHost", () => {
it("should default to undefined", () => { it("should default to undefined", () => {
expect(config().sonos.discovery.seedHost).toBeUndefined(); expect(config().sonos.discovery.seedHost).toBeUndefined();
}); });
["BNB_SONOS_SEED_HOST", "BONOB_SONOS_SEED_HOST"].forEach((k) => { describe.each([
"BNB_SONOS_SEED_HOST",
"BONOB_SONOS_SEED_HOST"
])(
"%s",
(k) => {
it("should be overridable", () => { it("should be overridable", () => {
process.env[k] = "123.456.789.0"; process.env[k] = "123.456.789.0";
expect(config().sonos.discovery.seedHost).toEqual("123.456.789.0"); expect(config().sonos.discovery.seedHost).toEqual("123.456.789.0");
}); });
}); }
);
}); });
["BNB_SONOS_AUTO_REGISTER", "BONOB_SONOS_AUTO_REGISTER"].forEach((k) => { describe.each([
"BNB_SONOS_AUTO_REGISTER",
"BONOB_SONOS_AUTO_REGISTER"
])(
"%s",
(k) => {
describeBooleanConfigValue( describeBooleanConfigValue(
"autoRegister", "autoRegister",
k, k,
false, false,
(config) => config.sonos.autoRegister (config) => config.sonos.autoRegister
); );
}); }
);
describe("sid", () => { describe("sid", () => {
it("should default to 246", () => { it("should default to 246", () => {
expect(config().sonos.sid).toEqual(246); expect(config().sonos.sid).toEqual(246);
}); });
["BNB_SONOS_SERVICE_ID", "BONOB_SONOS_SERVICE_ID"].forEach((k) => { describe.each([
"BNB_SONOS_SERVICE_ID",
"BONOB_SONOS_SERVICE_ID"
])(
"%s",
(k) => {
it("should be overridable", () => { it("should be overridable", () => {
process.env[k] = "786"; process.env[k] = "786";
expect(config().sonos.sid).toEqual(786); expect(config().sonos.sid).toEqual(786);
}); });
}); }
);
}); });
}); });
describe("subsonic", () => { describe("subsonic", () => {
describe("url", () => { describe("url", () => {
["BNB_SUBSONIC_URL", "BONOB_SUBSONIC_URL", "BONOB_NAVIDROME_URL"].forEach( describe.each([
(k) => { "BNB_SUBSONIC_URL",
"BONOB_SUBSONIC_URL",
"BONOB_NAVIDROME_URL",
])("%s", (k) => {
describe(`when ${k} is not specified`, () => { describe(`when ${k} is not specified`, () => {
it(`should default to http://${hostname()}:4533`, () => { it(`should default to http://${hostname()}:4533`, () => {
expect(config().subsonic.url).toEqual( expect(config().subsonic.url).toEqual(`http://${hostname()}:4533`);
`http://${hostname()}:4533`
);
}); });
}); });
describe(`when ${k} is ''`, () => { describe(`when ${k} is ''`, () => {
it(`should default to http://${hostname()}:4533`, () => { it(`should default to http://${hostname()}:4533`, () => {
process.env[k] = ""; process.env[k] = "";
expect(config().subsonic.url).toEqual( expect(config().subsonic.url).toEqual(`http://${hostname()}:4533`);
`http://${hostname()}:4533`
);
}); });
}); });
@@ -362,8 +384,7 @@ describe("config", () => {
expect(config().subsonic.url).toEqual(url); expect(config().subsonic.url).toEqual(url);
}); });
}); });
} });
);
}); });
describe("customClientsFor", () => { describe("customClientsFor", () => {
@@ -371,11 +392,11 @@ describe("config", () => {
expect(config().subsonic.customClientsFor).toBeUndefined(); expect(config().subsonic.customClientsFor).toBeUndefined();
}); });
[ describe.each([
"BNB_SUBSONIC_CUSTOM_CLIENTS", "BNB_SUBSONIC_CUSTOM_CLIENTS",
"BONOB_SUBSONIC_CUSTOM_CLIENTS", "BONOB_SUBSONIC_CUSTOM_CLIENTS",
"BONOB_NAVIDROME_CUSTOM_CLIENTS", "BONOB_NAVIDROME_CUSTOM_CLIENTS",
].forEach((k) => { ])("%s", (k) => {
it(`should be overridable for ${k}`, () => { it(`should be overridable for ${k}`, () => {
process.env[k] = "whoop/whoop"; process.env[k] = "whoop/whoop";
expect(config().subsonic.customClientsFor).toEqual("whoop/whoop"); expect(config().subsonic.customClientsFor).toEqual("whoop/whoop");
@@ -395,7 +416,10 @@ describe("config", () => {
}); });
}); });
["BNB_SCROBBLE_TRACKS", "BONOB_SCROBBLE_TRACKS"].forEach((k) => { describe.each([
"BNB_SCROBBLE_TRACKS",
"BONOB_SCROBBLE_TRACKS"
])("%s", (k) => {
describeBooleanConfigValue( describeBooleanConfigValue(
"scrobbleTracks", "scrobbleTracks",
k, k,
@@ -404,12 +428,18 @@ describe("config", () => {
); );
}); });
["BNB_REPORT_NOW_PLAYING", "BONOB_REPORT_NOW_PLAYING"].forEach((k) => { describe.each([
"BNB_REPORT_NOW_PLAYING",
"BONOB_REPORT_NOW_PLAYING"
])(
"%s",
(k) => {
describeBooleanConfigValue( describeBooleanConfigValue(
"reportNowPlaying", "reportNowPlaying",
k, k,
true, true,
(config) => config.reportNowPlaying (config) => config.reportNowPlaying
); );
}); }
);
}); });

View File

@@ -90,6 +90,8 @@ describe("rating to and from ints", () => {
}); });
describe("service config", () => { describe("service config", () => {
jest.setTimeout(Number.parseInt(process.env["JEST_TIMEOUT"] || "2000"));
const bonobWithNoContextPath = url("http://localhost:1234"); const bonobWithNoContextPath = url("http://localhost:1234");
const bonobWithContextPath = url("http://localhost:5678/some-context-path"); const bonobWithContextPath = url("http://localhost:5678/some-context-path");

View File

@@ -54,7 +54,7 @@
] ]
/* List of folders to include type definitions from. */, /* List of folders to include type definitions from. */,
// "types": ["src/customTypes/scale-that-svg.d.ts"], /* Type declaration files to be included in compilation. */ // "types": ["src/customTypes/scale-that-svg.d.ts"], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */