mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Listing devices and services on bonob page sourced from sonos devices
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
.nyc_output
|
.nyc_output
|
||||||
.vscode
|
.vscode
|
||||||
build
|
build
|
||||||
|
ignore
|
||||||
node_modules
|
node_modules
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ RUN yarn install && \
|
|||||||
|
|
||||||
FROM node:14.15-alpine
|
FROM node:14.15-alpine
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 4534
|
||||||
|
|
||||||
WORKDIR /bonob
|
WORKDIR /bonob
|
||||||
|
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -11,17 +11,31 @@ bonob is ditributed via docker and can be run in a number of ways
|
|||||||
### Full sonos device auto-discovery by using docker --network host
|
### Full sonos device auto-discovery by using docker --network host
|
||||||
```
|
```
|
||||||
docker run \
|
docker run \
|
||||||
-e PORT=3000 \
|
-p 4534 \
|
||||||
-p 3000 \
|
|
||||||
--network host \
|
--network host \
|
||||||
simojenki/bonob
|
simojenki/bonob
|
||||||
```
|
```
|
||||||
|
|
||||||
### Full sonos device auto-discovery by using a sonos seed device, without requiring docker host networking
|
### Full sonos device auto-discovery on custom port by using a sonos seed device, without requiring docker host networking
|
||||||
```
|
```
|
||||||
docker run \
|
docker run \
|
||||||
-e PORT=3000 \
|
|
||||||
-e BONOB_SONOS_SEED_HOST=192.168.1.123 \
|
-e BONOB_SONOS_SEED_HOST=192.168.1.123 \
|
||||||
|
-e PORT=3000 \
|
||||||
-p 3000 \
|
-p 3000 \
|
||||||
simojenki/bonob
|
simojenki/bonob
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Disabling sonos device discovery entirely
|
||||||
|
```
|
||||||
|
docker run \
|
||||||
|
-e BONOB_SONOS_SEED_HOST=disabled \
|
||||||
|
-p 4534 \
|
||||||
|
simojenki/bonob
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
item | default value | description
|
||||||
|
---- | ------------- | -----------
|
||||||
|
PORT | 4534 | Default http port for bonob to listen on
|
||||||
|
BONOB_SONOS_SEED_HOST | undefined | sonos device seed host for auto-discovery, or 'disabled' to turn off device discovery entirely
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
preset: 'ts-jest',
|
preset: 'ts-jest',
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
|
setupFilesAfterEnv: ["<rootDir>/tests/setup.js"],
|
||||||
};
|
};
|
||||||
@@ -9,11 +9,13 @@
|
|||||||
"@svrooij/sonos": "^2.3.0",
|
"@svrooij/sonos": "^2.3.0",
|
||||||
"@types/express": "^4.17.11",
|
"@types/express": "^4.17.11",
|
||||||
"@types/node": "^14.14.22",
|
"@types/node": "^14.14.22",
|
||||||
|
"@types/underscore": "1.10.24",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"eta": "^1.12.1",
|
"eta": "^1.12.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"ts-md5": "^1.2.7",
|
"ts-md5": "^1.2.7",
|
||||||
"typescript": "^4.1.3",
|
"typescript": "^4.1.3",
|
||||||
|
"underscore":"^1.12.0",
|
||||||
"winston": "^3.3.3"
|
"winston": "^3.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import sonos from "./sonos";
|
import sonos from "./sonos";
|
||||||
import server from "./server";
|
import server from "./server";
|
||||||
|
|
||||||
const PORT = process.env["PORT"] || 3000;
|
const PORT = process.env["PORT"] || 4534;
|
||||||
|
|
||||||
const app = server(sonos(process.env["BONOB_SONOS_SEED_HOST"]));
|
const app = server(sonos(process.env["BONOB_SONOS_SEED_HOST"]));
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import express, { Express } from "express";
|
import express, { Express } from "express";
|
||||||
import * as Eta from "eta";
|
import * as Eta from "eta";
|
||||||
import { Sonos } from "./sonos";
|
import { Sonos, servicesFrom } from "./sonos";
|
||||||
|
|
||||||
function server(sonos: Sonos): Express {
|
function server(sonos: Sonos): Express {
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -11,9 +11,12 @@ function server(sonos: Sonos): Express {
|
|||||||
app.set("views", "./web/views");
|
app.set("views", "./web/views");
|
||||||
|
|
||||||
app.get("/", (_, res) => {
|
app.get("/", (_, res) => {
|
||||||
res.render("index", {
|
sonos.devices().then(devices => {
|
||||||
devices: sonos.devices(),
|
res.render("index", {
|
||||||
});
|
devices,
|
||||||
|
services: servicesFrom(devices),
|
||||||
|
})
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
|
|||||||
75
src/sonos.ts
75
src/sonos.ts
@@ -1,27 +1,49 @@
|
|||||||
import { SonosManager, SonosDevice } from "@svrooij/sonos";
|
import { SonosManager, SonosDevice } from "@svrooij/sonos";
|
||||||
|
// import { MusicService } from "@svrooij/sonos/lib/services";
|
||||||
|
import { uniq } from "underscore";
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
|
|
||||||
type Device = {
|
export type Device = {
|
||||||
name: string;
|
name: string;
|
||||||
group: string;
|
group: string;
|
||||||
ip: string;
|
ip: string;
|
||||||
port: number;
|
port: number;
|
||||||
|
services: Service[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Service = {
|
||||||
|
name: string;
|
||||||
|
id: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface Sonos {
|
export interface Sonos {
|
||||||
devices: () => Device[];
|
devices: () => Promise<Device[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SONOS_DISABLED: Sonos = {
|
export const SONOS_DISABLED: Sonos = {
|
||||||
devices: () => [],
|
devices: () => Promise.resolve([]),
|
||||||
};
|
};
|
||||||
|
|
||||||
const asDevice = (sonosDevice: SonosDevice) => ({
|
export const servicesFrom = (devices: Device[]) =>
|
||||||
name: sonosDevice.Name,
|
uniq(
|
||||||
group: sonosDevice.GroupName || "",
|
devices.flatMap((d) => d.services),
|
||||||
ip: sonosDevice.Host,
|
false,
|
||||||
port: sonosDevice.Port,
|
(s) => s.id
|
||||||
});
|
);
|
||||||
|
|
||||||
|
export const asDevice = (sonosDevice: SonosDevice): Promise<Device> =>
|
||||||
|
sonosDevice.MusicServicesService.ListAndParseAvailableServices().then(
|
||||||
|
(services) => ({
|
||||||
|
name: sonosDevice.Name,
|
||||||
|
group: sonosDevice.GroupName || "",
|
||||||
|
ip: sonosDevice.Host,
|
||||||
|
port: sonosDevice.Port,
|
||||||
|
services: services.map((s) => ({
|
||||||
|
name: s.Name,
|
||||||
|
id: s.Id,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const setupDiscovery = (
|
const setupDiscovery = (
|
||||||
manager: SonosManager,
|
manager: SonosManager,
|
||||||
@@ -37,24 +59,24 @@ const setupDiscovery = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function autoDiscoverySonos(sonosSeedHost?: string): Sonos {
|
export function autoDiscoverySonos(sonosSeedHost?: string): Sonos {
|
||||||
const manager = new SonosManager();
|
|
||||||
|
|
||||||
setupDiscovery(manager, sonosSeedHost)
|
|
||||||
.then((r) => {
|
|
||||||
if (r) logger.info({ devices: manager.Devices.map(asDevice) });
|
|
||||||
else logger.warn("Failed to auto discover hosts!");
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
logger.warn(`Failed to find sonos devices ${e}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
devices: () => {
|
devices: async () => {
|
||||||
try {
|
const manager = new SonosManager();
|
||||||
return manager.Devices.map(asDevice)
|
return setupDiscovery(manager, sonosSeedHost)
|
||||||
}catch(e) {
|
.then((success) => {
|
||||||
return []
|
if (success) {
|
||||||
}
|
const devices = Promise.all(manager.Devices.map(asDevice));
|
||||||
|
logger.info({ devices });
|
||||||
|
return devices;
|
||||||
|
} else {
|
||||||
|
logger.warn("Didn't find any sonos devices!");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
logger.error(`Failed looking for sonos devices ${e}`);
|
||||||
|
return [];
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -62,6 +84,7 @@ export function autoDiscoverySonos(sonosSeedHost?: string): Sonos {
|
|||||||
export default function sonos(sonosSeedHost?: string): Sonos {
|
export default function sonos(sonosSeedHost?: string): Sonos {
|
||||||
switch (sonosSeedHost) {
|
switch (sonosSeedHost) {
|
||||||
case "disabled":
|
case "disabled":
|
||||||
|
logger.info("Sonos device discovery disabled");
|
||||||
return SONOS_DISABLED;
|
return SONOS_DISABLED;
|
||||||
default:
|
default:
|
||||||
return autoDiscoverySonos(sonosSeedHost);
|
return autoDiscoverySonos(sonosSeedHost);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import request from "supertest";
|
import request from "supertest";
|
||||||
import makeServer from "../src/server";
|
import makeServer from "../src/server";
|
||||||
import { SONOS_DISABLED, Sonos } from "../src/sonos";
|
import { SONOS_DISABLED, Sonos, Device } from "../src/sonos";
|
||||||
|
|
||||||
describe("index", () => {
|
describe("index", () => {
|
||||||
describe("when sonos integration is disabled", () => {
|
describe("when sonos integration is disabled", () => {
|
||||||
@@ -11,25 +11,50 @@ describe("index", () => {
|
|||||||
const res = await request(server).get("/").send();
|
const res = await request(server).get("/").send();
|
||||||
|
|
||||||
expect(res.status).toEqual(200);
|
expect(res.status).toEqual(200);
|
||||||
expect(res.text).not.toMatch(/class=device/)
|
expect(res.text).not.toMatch(/class=device/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const device1 : Device = {
|
||||||
|
name: "device1",
|
||||||
|
group: "group1",
|
||||||
|
ip: "172.0.0.1",
|
||||||
|
port: 4301,
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
name: "s1",
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "s2",
|
||||||
|
id: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const device2: Device = {
|
||||||
|
name: "device2",
|
||||||
|
group: "group2",
|
||||||
|
ip: "172.0.0.2",
|
||||||
|
port: 4302,
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
name: "s3",
|
||||||
|
id: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "s4",
|
||||||
|
id: 4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
describe("when sonos integration is enabled", () => {
|
describe("when sonos integration is enabled", () => {
|
||||||
const fakeSonos: Sonos = {
|
const fakeSonos: Sonos = {
|
||||||
devices: () => [{
|
devices: () =>Promise.resolve([device1, device2]),
|
||||||
name: "device1",
|
};
|
||||||
group: "group1",
|
|
||||||
ip: "172.0.0.1",
|
|
||||||
port: 4301
|
|
||||||
},{
|
|
||||||
name: "device2",
|
|
||||||
group: "group2",
|
|
||||||
ip: "172.0.0.2",
|
|
||||||
port: 4302
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
const server = makeServer(fakeSonos);
|
const server = makeServer(fakeSonos);
|
||||||
|
|
||||||
@@ -38,8 +63,23 @@ describe("index", () => {
|
|||||||
const res = await request(server).get("/").send();
|
const res = await request(server).get("/").send();
|
||||||
|
|
||||||
expect(res.status).toEqual(200);
|
expect(res.status).toEqual(200);
|
||||||
expect(res.text).toMatch(/device1\s+\(172.0.0.1:4301\)/)
|
expect(res.text).toMatch(
|
||||||
expect(res.text).toMatch(/device2\s+\(172.0.0.2:4302\)/)
|
/device1\s+\(172.0.0.1:4301\)/
|
||||||
|
);
|
||||||
|
expect(res.text).toMatch(
|
||||||
|
/device2\s+\(172.0.0.2:4302\)/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should contain a list of services returned from sonos", async () => {
|
||||||
|
const res = await request(server).get("/").send();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200);
|
||||||
|
expect(res.text).toMatch(/Services\s+4/);
|
||||||
|
expect(res.text).toMatch(/s1\s+\(1\)/);
|
||||||
|
expect(res.text).toMatch(/s2\s+\(2\)/);
|
||||||
|
expect(res.text).toMatch(/s3\s+\(3\)/);
|
||||||
|
expect(res.text).toMatch(/s4\s+\(4\)/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
76
tests/music_services.ts
Normal file
76
tests/music_services.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { MusicService } from "@svrooij/sonos/lib/services";
|
||||||
|
|
||||||
|
export const AMAZON_MUSIC: MusicService = {
|
||||||
|
Name: "Amazon Music",
|
||||||
|
Version: "1.1",
|
||||||
|
Uri: "https://sonos.amazonmusic.com/",
|
||||||
|
SecureUri: "https://sonos.amazonmusic.com/",
|
||||||
|
ContainerType: "MService",
|
||||||
|
Capabilities: "2208321",
|
||||||
|
Presentation: {
|
||||||
|
Strings: {
|
||||||
|
Version: "23",
|
||||||
|
Uri: "https://sonos.amazonmusic.com/strings.xml",
|
||||||
|
},
|
||||||
|
PresentationMap: {
|
||||||
|
Version: "17",
|
||||||
|
Uri: "https://sonos.amazonmusic.com/PresentationMap.xml",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Id: 201,
|
||||||
|
Policy: { Auth: "DeviceLink", PollInterval: 60 },
|
||||||
|
Manifest: {
|
||||||
|
Uri: "",
|
||||||
|
Version: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const APPLE_MUSIC: MusicService = {
|
||||||
|
Name: "Apple Music",
|
||||||
|
Version: "1.1",
|
||||||
|
Uri: "https://sonos-music.apple.com/ws/SonosSoap",
|
||||||
|
SecureUri: "https://sonos-music.apple.com/ws/SonosSoap",
|
||||||
|
ContainerType: "MService",
|
||||||
|
Capabilities: "3117633",
|
||||||
|
Presentation: {
|
||||||
|
Strings: {
|
||||||
|
Version: "24",
|
||||||
|
Uri: "https://sonos-music.apple.com/xml/strings.xml",
|
||||||
|
},
|
||||||
|
PresentationMap: {
|
||||||
|
Version: "22",
|
||||||
|
Uri: "http://sonos-pmap.ws.sonos.com/applemusicbrand_pmap3.xml",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Id: 204,
|
||||||
|
Policy: { Auth: "AppLink", PollInterval: 60 },
|
||||||
|
Manifest: {
|
||||||
|
Uri: "",
|
||||||
|
Version: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AUDIBLE: MusicService = {
|
||||||
|
Name: "Audible",
|
||||||
|
Version: "1.1",
|
||||||
|
Uri: "https://sonos.audible.com/smapi",
|
||||||
|
SecureUri: "https://sonos.audible.com/smapi",
|
||||||
|
ContainerType: "MService",
|
||||||
|
Capabilities: "1095249",
|
||||||
|
Presentation: {
|
||||||
|
Strings: {
|
||||||
|
Version: "5",
|
||||||
|
Uri: "https://sonos.audible.com/smapi/strings.xml",
|
||||||
|
},
|
||||||
|
PresentationMap: {
|
||||||
|
Version: "5",
|
||||||
|
Uri: "https://sonos.audible.com/smapi/PresentationMap.xml",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Id: 239,
|
||||||
|
Policy: { Auth: "AppLink", PollInterval: 30 },
|
||||||
|
Manifest: {
|
||||||
|
Uri: "",
|
||||||
|
Version: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
9
tests/setup.js
Normal file
9
tests/setup.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
global.console = {
|
||||||
|
log: jest.fn(), // console.log are ignored in tests
|
||||||
|
|
||||||
|
// Keep native behaviour for other methods, use those to print out things in your own tests, not `console.log`
|
||||||
|
error: console.error,
|
||||||
|
warn: console.warn,
|
||||||
|
info: console.info,
|
||||||
|
debug: console.debug,
|
||||||
|
};
|
||||||
293
tests/sonos.test.ts
Normal file
293
tests/sonos.test.ts
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
import { SonosManager, SonosDevice } from "@svrooij/sonos";
|
||||||
|
import { MusicServicesService } from "@svrooij/sonos/lib/services";
|
||||||
|
jest.mock("@svrooij/sonos");
|
||||||
|
|
||||||
|
import { AMAZON_MUSIC, APPLE_MUSIC, AUDIBLE } from "./music_services";
|
||||||
|
|
||||||
|
import sonos, { SONOS_DISABLED, asDevice, Device, servicesFrom } from "../src/sonos";
|
||||||
|
|
||||||
|
const mockSonosManagerConstructor = <jest.Mock<SonosManager>>SonosManager;
|
||||||
|
|
||||||
|
describe("sonos", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockSonosManagerConstructor.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("asDevice", () => {
|
||||||
|
it("should convert", async () => {
|
||||||
|
const musicServicesService = {
|
||||||
|
ListAndParseAvailableServices: jest.fn(),
|
||||||
|
};
|
||||||
|
const device = {
|
||||||
|
Name: "d1",
|
||||||
|
GroupName: "g1",
|
||||||
|
Host: "127.0.0.222",
|
||||||
|
Port: 123,
|
||||||
|
MusicServicesService: (musicServicesService as unknown) as MusicServicesService,
|
||||||
|
} as SonosDevice;
|
||||||
|
|
||||||
|
musicServicesService.ListAndParseAvailableServices.mockResolvedValue([
|
||||||
|
AMAZON_MUSIC,
|
||||||
|
APPLE_MUSIC,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(await asDevice(device)).toEqual({
|
||||||
|
name: "d1",
|
||||||
|
group: "g1",
|
||||||
|
ip: "127.0.0.222",
|
||||||
|
port: 123,
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
name: AMAZON_MUSIC.Name,
|
||||||
|
id: AMAZON_MUSIC.Id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: APPLE_MUSIC.Name,
|
||||||
|
id: APPLE_MUSIC.Id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function someDevice(params: Partial<Device> = {}): Device {
|
||||||
|
const device = {
|
||||||
|
name: "device123",
|
||||||
|
group: "",
|
||||||
|
ip: "127.0.0.11",
|
||||||
|
port: 123,
|
||||||
|
services: [],
|
||||||
|
};
|
||||||
|
return { ...device, ...params };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("servicesFrom", () => {
|
||||||
|
it("should only return uniq services", () => {
|
||||||
|
const service1 = { id: 1, name: "service1" };
|
||||||
|
const service2 = { id: 2, name: "service2" };
|
||||||
|
const service3 = { id: 3, name: "service3" };
|
||||||
|
const service4 = { id: 4, name: "service4" };
|
||||||
|
|
||||||
|
const d1 = someDevice({ services: [service1, service2] });
|
||||||
|
const d2 = someDevice({ services: [service1, service2, service3] });
|
||||||
|
const d3 = someDevice({ services: [service4] });
|
||||||
|
|
||||||
|
const devices: Device[] = [d1, d2, d3];
|
||||||
|
|
||||||
|
expect(servicesFrom(devices)).toEqual([service1, service2, service3, service4])
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when is disabled", () => {
|
||||||
|
it("should return a disabled client", async () => {
|
||||||
|
const disabled = sonos("disabled");
|
||||||
|
|
||||||
|
expect(disabled).toEqual(SONOS_DISABLED);
|
||||||
|
expect(await disabled.devices()).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("sonos device discovery", () => {
|
||||||
|
const device1_MusicServicesService = {
|
||||||
|
ListAndParseAvailableServices: jest.fn(),
|
||||||
|
};
|
||||||
|
const device1 = {
|
||||||
|
Name: "device1",
|
||||||
|
GroupName: "group1",
|
||||||
|
Host: "127.0.0.11",
|
||||||
|
Port: 111,
|
||||||
|
MusicServicesService: (device1_MusicServicesService as unknown) as MusicServicesService,
|
||||||
|
} as SonosDevice;
|
||||||
|
|
||||||
|
const device2_MusicServicesService = {
|
||||||
|
ListAndParseAvailableServices: jest.fn(),
|
||||||
|
};
|
||||||
|
const device2 = {
|
||||||
|
Name: "device2",
|
||||||
|
GroupName: "group2",
|
||||||
|
Host: "127.0.0.22",
|
||||||
|
Port: 222,
|
||||||
|
MusicServicesService: (device2_MusicServicesService as unknown) as MusicServicesService,
|
||||||
|
} as SonosDevice;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
device1_MusicServicesService.ListAndParseAvailableServices.mockClear();
|
||||||
|
device2_MusicServicesService.ListAndParseAvailableServices.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when no sonos seed host is provided", () => {
|
||||||
|
it("should perform auto-discovery", async () => {
|
||||||
|
const sonosManager = {
|
||||||
|
InitializeWithDiscovery: jest.fn(),
|
||||||
|
Devices: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
mockSonosManagerConstructor.mockReturnValue(
|
||||||
|
(sonosManager as unknown) as SonosManager
|
||||||
|
);
|
||||||
|
sonosManager.InitializeWithDiscovery.mockResolvedValue(true);
|
||||||
|
|
||||||
|
const actualDevices = await sonos(undefined).devices();
|
||||||
|
|
||||||
|
expect(SonosManager).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sonosManager.InitializeWithDiscovery).toHaveBeenCalledWith(10);
|
||||||
|
|
||||||
|
expect(actualDevices).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when sonos seed host is empty string", () => {
|
||||||
|
it("should perform auto-discovery", async () => {
|
||||||
|
const sonosManager = {
|
||||||
|
InitializeWithDiscovery: jest.fn(),
|
||||||
|
Devices: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
mockSonosManagerConstructor.mockReturnValue(
|
||||||
|
(sonosManager as unknown) as SonosManager
|
||||||
|
);
|
||||||
|
sonosManager.InitializeWithDiscovery.mockResolvedValue(true);
|
||||||
|
|
||||||
|
const actualDevices = await sonos("").devices();
|
||||||
|
|
||||||
|
expect(SonosManager).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sonosManager.InitializeWithDiscovery).toHaveBeenCalledWith(10);
|
||||||
|
|
||||||
|
expect(actualDevices).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when a sonos seed host is provided", () => {
|
||||||
|
it("should perform auto-discovery", async () => {
|
||||||
|
const seedHost = "theSeedsOfLife";
|
||||||
|
|
||||||
|
const sonosManager = {
|
||||||
|
InitializeFromDevice: jest.fn(),
|
||||||
|
Devices: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
mockSonosManagerConstructor.mockReturnValue(
|
||||||
|
(sonosManager as unknown) as SonosManager
|
||||||
|
);
|
||||||
|
sonosManager.InitializeFromDevice.mockResolvedValue(true);
|
||||||
|
|
||||||
|
const actualDevices = await sonos(seedHost).devices();
|
||||||
|
|
||||||
|
expect(SonosManager).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sonosManager.InitializeFromDevice).toHaveBeenCalledWith(
|
||||||
|
seedHost
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(actualDevices).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when some devices are found", () => {
|
||||||
|
it("should be able to return them", async () => {
|
||||||
|
const sonosManager = {
|
||||||
|
InitializeWithDiscovery: jest.fn(),
|
||||||
|
Devices: [device1, device2],
|
||||||
|
};
|
||||||
|
|
||||||
|
mockSonosManagerConstructor.mockReturnValue(
|
||||||
|
(sonosManager as unknown) as SonosManager
|
||||||
|
);
|
||||||
|
sonosManager.InitializeWithDiscovery.mockResolvedValue(true);
|
||||||
|
|
||||||
|
device1_MusicServicesService.ListAndParseAvailableServices.mockResolvedValue(
|
||||||
|
[AMAZON_MUSIC, APPLE_MUSIC]
|
||||||
|
);
|
||||||
|
device2_MusicServicesService.ListAndParseAvailableServices.mockResolvedValue(
|
||||||
|
[AUDIBLE]
|
||||||
|
);
|
||||||
|
|
||||||
|
const actualDevices = await sonos(undefined).devices();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
device1_MusicServicesService.ListAndParseAvailableServices
|
||||||
|
).toHaveBeenCalled();
|
||||||
|
expect(
|
||||||
|
device2_MusicServicesService.ListAndParseAvailableServices
|
||||||
|
).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(actualDevices).toEqual([
|
||||||
|
{
|
||||||
|
name: device1.Name,
|
||||||
|
group: device1.GroupName,
|
||||||
|
ip: device1.Host,
|
||||||
|
port: device1.Port,
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
name: AMAZON_MUSIC.Name,
|
||||||
|
id: AMAZON_MUSIC.Id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: APPLE_MUSIC.Name,
|
||||||
|
id: APPLE_MUSIC.Id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: device2.Name,
|
||||||
|
group: device2.GroupName,
|
||||||
|
ip: device2.Host,
|
||||||
|
port: device2.Port,
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
name: AUDIBLE.Name,
|
||||||
|
id: AUDIBLE.Id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when initialisation returns false", () => {
|
||||||
|
it("should return empty []", async () => {
|
||||||
|
const initialize = jest.fn();
|
||||||
|
const sonosManager = {
|
||||||
|
InitializeWithDiscovery: initialize as (
|
||||||
|
x: number
|
||||||
|
) => Promise<boolean>,
|
||||||
|
Devices: [device1, device2],
|
||||||
|
} as SonosManager;
|
||||||
|
|
||||||
|
mockSonosManagerConstructor.mockReturnValue(sonosManager);
|
||||||
|
initialize.mockResolvedValue(false);
|
||||||
|
|
||||||
|
const actualDevices = await sonos("").devices();
|
||||||
|
|
||||||
|
expect(SonosManager).toHaveBeenCalledTimes(1);
|
||||||
|
expect(initialize).toHaveBeenCalledWith(10);
|
||||||
|
|
||||||
|
expect(actualDevices).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when getting devices fails", () => {
|
||||||
|
it("should return empty []", async () => {
|
||||||
|
const initialize = jest.fn();
|
||||||
|
|
||||||
|
const sonosManager = ({
|
||||||
|
InitializeWithDiscovery: initialize as (
|
||||||
|
x: number
|
||||||
|
) => Promise<boolean>,
|
||||||
|
Devices: () => {
|
||||||
|
throw Error("Boom");
|
||||||
|
},
|
||||||
|
} as unknown) as SonosManager;
|
||||||
|
|
||||||
|
mockSonosManagerConstructor.mockReturnValue(sonosManager);
|
||||||
|
initialize.mockResolvedValue(true);
|
||||||
|
|
||||||
|
const actualDevices = await sonos("").devices();
|
||||||
|
|
||||||
|
expect(SonosManager).toHaveBeenCalledTimes(1);
|
||||||
|
expect(initialize).toHaveBeenCalledWith(10);
|
||||||
|
|
||||||
|
expect(actualDevices).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<ul>
|
|
||||||
<% it.devices.forEach(function(d){ %>
|
|
||||||
<li class="device"><%= d.name %> (<%= d.ip %>:<%= d.port %>)</li>
|
|
||||||
<% }) %>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
@@ -5,7 +5,13 @@
|
|||||||
<h2>Devices</h2>
|
<h2>Devices</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<% it.devices.forEach(function(d){ %>
|
<% it.devices.forEach(function(d){ %>
|
||||||
<li><%= d.name %> (<%= d.ip %>:<%= d.port %>)</li>
|
<li><%= d.name %> (<%= d.ip %>:<%= d.port %>)</li>
|
||||||
|
<% }) %>
|
||||||
|
</ul>
|
||||||
|
<h2>Services <%= it.services.length %></h2>
|
||||||
|
<ul>
|
||||||
|
<% it.services.forEach(function(s){ %>
|
||||||
|
<li><%= s.name %> (<%= s.id %>)</li>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
10
yarn.lock
10
yarn.lock
@@ -686,6 +686,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/superagent" "*"
|
"@types/superagent" "*"
|
||||||
|
|
||||||
|
"@types/underscore@1.10.24":
|
||||||
|
version "1.10.24"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.10.24.tgz#dede004deed3b3f99c4db0bdb9ee21cae25befdd"
|
||||||
|
integrity sha512-T3NQD8hXNW2sRsSbLNjF/aBo18MyJlbw0lSpQHB/eZZtScPdexN4HSa8cByYwTw9Wy7KuOFr81mlDQcQQaZ79w==
|
||||||
|
|
||||||
"@types/yargs-parser@*":
|
"@types/yargs-parser@*":
|
||||||
version "20.2.0"
|
version "20.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9"
|
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9"
|
||||||
@@ -4614,6 +4619,11 @@ undefsafe@^2.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
debug "^2.2.0"
|
debug "^2.2.0"
|
||||||
|
|
||||||
|
underscore@^1.12.0:
|
||||||
|
version "1.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.0.tgz#4814940551fc80587cef7840d1ebb0f16453be97"
|
||||||
|
integrity sha512-21rQzss/XPMjolTiIezSu3JAjgagXKROtNrYFEOWK109qY1Uv2tVjPTZ1ci2HgvQDA16gHYSthQIJfB+XId/rQ==
|
||||||
|
|
||||||
union-value@^1.0.0:
|
union-value@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
|
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
|
||||||
|
|||||||
Reference in New Issue
Block a user