diff --git a/src/smapi.ts b/src/smapi.ts index fcc565a..4b41cb9 100644 --- a/src/smapi.ts +++ b/src/smapi.ts @@ -6,12 +6,7 @@ import path from "path"; import logger from "./logger"; import { LinkCodes } from "./link_codes"; -import { - Album, - ArtistSummary, - MusicLibrary, - MusicService, -} from "./music_service"; +import { Album, MusicLibrary, MusicService, slice2 } from "./music_service"; export const LOGIN_ROUTE = "/login"; export const SOAP_PATH = "/ws/sonos"; @@ -152,7 +147,7 @@ export type Container = { title: string; }; -export const container = ({ +const container = ({ id, title, }: { @@ -164,6 +159,12 @@ export const container = ({ title, }); +const album = (album: Album) => ({ + itemType: "album", + id: `album:${album.id}`, + title: album.name, +}); + type SoapyHeaders = { credentials?: Credentials; }; @@ -223,51 +224,46 @@ function bindSmapiSoapServiceToExpress( case "root": return getMetadataResult({ mediaCollection: [ - { itemType: "container", id: "artists", title: "Artists" }, - { itemType: "container", id: "albums", title: "Albums" }, + container({ id: "artists", title: "Artists" }), + container({ id: "albums", title: "Albums" }), ], index: 0, total: 2, }); case "artists": + return await musicLibrary.artists(paging).then((result) => + getMetadataResult({ + mediaCollection: result.results.map((it) => ({ + itemType: "artist", + id: `artist:${it.id}`, + artistId: it.id, + title: it.name, + albumArtURI: it.image.small, + })), + index: paging._index, + total: result.total, + }) + ); + case "artist": return await musicLibrary - .artists(paging) - .then( - ({ - results, + .artist(typeId!) + .then((artist) => artist.albums) + .then(slice2(paging)) + .then(([page, total]) => + getMetadataResult({ + mediaCollection: page.map(album), + index: 0, total, - }: { - results: ArtistSummary[]; - total: number; - }) => - getMetadataResult({ - mediaCollection: results.map((it) => ({ - itemType: "artist", - id: `artist:${it.id}`, - artistId: it.id, - title: it.name, - albumArtURI: it.image.small, - })), - index: paging._index, - total, - }) + }) ); case "albums": - return await musicLibrary - .albums(paging) - .then( - ({ results, total }: { results: Album[]; total: number }) => - getMetadataResult({ - mediaCollection: results.map((it) => - container({ - id: `album:${it.id}`, - title: it.name, - }) - ), - index: paging._index, - total, - }) - ); + return await musicLibrary.albums(paging).then((result) => + getMetadataResult({ + mediaCollection: result.results.map(album), + index: paging._index, + total: result.total, + }) + ); default: throw `Unsupported id:${id}`; } diff --git a/tests/builders.ts b/tests/builders.ts index f506ce3..3288bc7 100644 --- a/tests/builders.ts +++ b/tests/builders.ts @@ -3,7 +3,7 @@ import { v4 as uuid } from "uuid"; import { Credentials } from "../src/smapi"; import { Service, Device } from "../src/sonos"; -import { Artist } from "../src/music_service"; +import { Album, Artist } from "../src/music_service"; const randomInt = (max: number) => Math.floor(Math.random() * max); const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`; @@ -68,20 +68,31 @@ export function someCredentials(token: string): Credentials { }; } -export const BOB_MARLEY: Artist = { - id: uuid(), - name: "Bob Marley", - albums: [ - { id: uuid(), name: "Burin'", year: "1973", genre: "Reggae" }, - { id: uuid(), name: "Exodus", year: "1977", genre: "Reggae" }, - { id: uuid(), name: "Kaya", year: "1978", genre: "Ska" }, - ], - image: { - small: "http://localhost/BOB_MARLEY/sml", - medium: "http://localhost/BOB_MARLEY/med", - large: "http://localhost/BOB_MARLEY/lge", - }, -}; +export function anArtist(fields: Partial = {}): Artist { + const id = uuid(); + return { + id, + name: `Artist ${id}`, + albums: [], + image: { + small: undefined, + medium: undefined, + large: undefined + }, + ...fields + } +} + +export function anAlbum(fields: Partial = {}): Album { + const id = uuid(); + return { + id, + name: `Album ${id}`, + genre: "Metal", + year: "1900", + ...fields + } +} export const BLONDIE: Artist = { id: uuid(), @@ -107,6 +118,21 @@ export const BLONDIE: Artist = { }, }; +export const BOB_MARLEY: Artist = { + id: uuid(), + name: "Bob Marley", + albums: [ + { id: uuid(), name: "Burin'", year: "1973", genre: "Reggae" }, + { id: uuid(), name: "Exodus", year: "1977", genre: "Reggae" }, + { id: uuid(), name: "Kaya", year: "1978", genre: "Ska" }, + ], + image: { + small: "http://localhost/BOB_MARLEY/sml", + medium: "http://localhost/BOB_MARLEY/med", + large: "http://localhost/BOB_MARLEY/lge", + }, +}; + export const MADONNA: Artist = { id: uuid(), name: "Madonna", @@ -142,9 +168,6 @@ export const METALLICA: Artist = { }, }; -export const ALL_ALBUMS = [ - ...BOB_MARLEY.albums, - ...BLONDIE.albums, - ...MADONNA.albums, - ...METALLICA.albums, -]; +export const ALL_ARTISTS = [BOB_MARLEY, BLONDIE, MADONNA, METALLICA] + +export const ALL_ALBUMS = ALL_ARTISTS.flatMap(it => it.albums || []); diff --git a/tests/smapi.test.ts b/tests/smapi.test.ts index fee22c2..552d297 100644 --- a/tests/smapi.test.ts +++ b/tests/smapi.test.ts @@ -6,19 +6,14 @@ import X2JS from "x2js"; import { InMemoryLinkCodes, LinkCodes } from "../src/link_codes"; import makeServer from "../src/server"; import { bonobService, SONOS_DISABLED } from "../src/sonos"; -import { - STRINGS_ROUTE, - LOGIN_ROUTE, - getMetadataResult, - container, -} from "../src/smapi"; +import { STRINGS_ROUTE, LOGIN_ROUTE, getMetadataResult } from "../src/smapi"; import { aService, - BLONDIE, - BOB_MARLEY, getAppLinkMessage, someCredentials, + anArtist, + anAlbum, } from "./builders"; import { InMemoryMusicService } from "./in_memory_music_service"; import supersoap from "./supersoap"; @@ -356,8 +351,8 @@ describe("api", () => { expect(root[0]).toEqual( getMetadataResult({ mediaCollection: [ - container({ id: "artists", title: "Artists" }), - container({ id: "albums", title: "Albums" }), + { itemType: "container", id: "artists", title: "Artists" }, + { itemType: "container", id: "albums", title: "Albums" }, ], index: 0, total: 2, @@ -366,83 +361,218 @@ describe("api", () => { }); }); + describe("asking for a single artist", () => { + const artistWithManyAlbums = anArtist({ + albums: [ + anAlbum(), + anAlbum(), + anAlbum(), + anAlbum(), + anAlbum(), + ], + }); + + beforeEach(() => { + musicService.hasArtists(artistWithManyAlbums); + }); + + describe("asking for all albums", () => { + it("should return a collection of albums", async () => { + const result = await ws.getMetadataAsync({ + id: `artist:${artistWithManyAlbums.id}`, + index: 0, + count: 100, + }); + expect(result[0]).toEqual( + getMetadataResult({ + mediaCollection: artistWithManyAlbums.albums.map((it) => ({ + itemType: "album", + id: `album:${it.id}`, + title: it.name, + })), + index: 0, + total: artistWithManyAlbums.albums.length, + }) + ); + }); + }); + + describe("asking for a page of albums", () => { + it("should return just that page", async () => { + const result = await ws.getMetadataAsync({ + id: `artist:${artistWithManyAlbums.id}`, + index: 2, + count: 2, + }); + expect(result[0]).toEqual( + getMetadataResult({ + mediaCollection: [ + artistWithManyAlbums.albums[2]!, + artistWithManyAlbums.albums[3]!, + ].map((it) => ({ + itemType: "album", + id: `album:${it.id}`, + title: it.name, + })), + index: 0, + total: artistWithManyAlbums.albums.length, + }) + ); + }); + }); + }); + describe("asking for artists", () => { - it("should return it", async () => { - musicService.hasArtists(BLONDIE, BOB_MARLEY); + const artists = [ + anArtist(), + anArtist(), + anArtist(), + anArtist(), + anArtist(), + ]; - const artists = await ws.getMetadataAsync({ - id: "artists", - index: 0, - count: 100, - }); - expect(artists[0]).toEqual( - getMetadataResult({ - mediaCollection: [BLONDIE, BOB_MARLEY].map((it) => ({ - itemType: "artist", - id: `artist:${it.id}`, - artistId: it.id, - title: it.name, - albumArtURI: it.image.small, - })), + beforeEach(() => { + musicService.hasArtists(...artists); + }); + + describe("asking for all artists", () => { + it("should return them all", async () => { + const result = await ws.getMetadataAsync({ + id: "artists", index: 0, - total: 2, - }) - ); + count: 100, + }); + expect(result[0]).toEqual( + getMetadataResult({ + mediaCollection: artists.map((it) => ({ + itemType: "artist", + id: `artist:${it.id}`, + artistId: it.id, + title: it.name, + albumArtURI: it.image.small, + })), + index: 0, + total: artists.length, + }) + ); + }); + }); + + describe("asking for a page of artists", () => { + it("should return it", async () => { + const result = await ws.getMetadataAsync({ + id: "artists", + index: 1, + count: 3, + }); + expect(result[0]).toEqual( + getMetadataResult({ + mediaCollection: [artists[1]!, artists[2]!, artists[3]!].map( + (it) => ({ + itemType: "artist", + id: `artist:${it.id}`, + artistId: it.id, + title: it.name, + albumArtURI: it.image.small, + }) + ), + index: 1, + total: artists.length, + }) + ); + }); }); }); - describe("asking for all albums", () => { - it("should return it", async () => { - musicService.hasArtists(BLONDIE, BOB_MARLEY); + // describe("asking for an album by id", () => { + // it("should return it", async () => { + // musicService.hasArtists(BLONDIE, BOB_MARLEY); + // const album = BOB_MARLEY.albums[0]!; - const albums = await ws.getMetadataAsync({ - id: "albums", - index: 0, - count: 100, - }); - expect(albums[0]).toEqual( - getMetadataResult({ - mediaCollection: [ - ...BLONDIE.albums, - ...BOB_MARLEY.albums, - ].map((it) => - container({ id: `album:${it.id}`, title: it.name }) - ), - index: 0, - total: BLONDIE.albums.length + BOB_MARLEY.albums.length, - }) - ); + // const result = await ws.getMetadataAsync({ + // id: `album:${album.id}`, + // index: 0, + // count: 100, + // }); + // expect(result).toEqual( + // getMetadataResult({ + // mediaCollection: [ + // ...BLONDIE.albums, + // ...BOB_MARLEY.albums, + // ].map((it) => + // ({ itemType: "album", id: `album:${it.id}`, title: it.name }) + // ), + // index: 0, + // total: BLONDIE.albums.length + BOB_MARLEY.albums.length, + // }) + // ); + // }); + // }); + + describe("asking for albums", () => { + const artist1 = anArtist({ + albums: [anAlbum(), anAlbum(), anAlbum()], + }); + const artist2 = anArtist({ + albums: [anAlbum(), anAlbum()], + }); + const artist3 = anArtist({ + albums: [], + }); + const artist4 = anArtist({ + albums: [anAlbum()], }); - }); - describe("asking for albums with paging", () => { - it("should return it", async () => { - musicService.hasArtists(BLONDIE, BOB_MARLEY); + beforeEach(() => { + musicService.hasArtists(artist1, artist2, artist3, artist4); + }); - expect(BLONDIE.albums.length).toEqual(2); - expect(BOB_MARLEY.albums.length).toEqual(3); - - const albums = await ws.getMetadataAsync({ - id: "albums", - index: 2, - count: 2, + describe("asking for all albums", () => { + it("should return them all", async () => { + const result = await ws.getMetadataAsync({ + id: "albums", + index: 0, + count: 100, + }); + expect(result[0]).toEqual( + getMetadataResult({ + mediaCollection: [artist1, artist2, artist3, artist4] + .flatMap((it) => it.albums) + .map((it) => ({ + itemType: "album", + id: `album:${it.id}`, + title: it.name, + })), + index: 0, + total: 6, + }) + ); }); - expect(albums[0]).toEqual( - getMetadataResult({ - mediaCollection: [ - container({ - id: `album:${BOB_MARLEY.albums[0]!.id}`, - title: BOB_MARLEY.albums[0]!.name, - }), - container({ - id: `album:${BOB_MARLEY.albums[1]!.id}`, - title: BOB_MARLEY.albums[1]!.name, - }), - ], + }); + + describe("asking for a page of albums", () => { + it("should return only that page", async () => { + const result = await ws.getMetadataAsync({ + id: "albums", index: 2, - total: BLONDIE.albums.length + BOB_MARLEY.albums.length, - }) - ); + count: 3, + }); + expect(result[0]).toEqual( + getMetadataResult({ + mediaCollection: [ + artist1.albums[2]!, + artist2.albums[0]!, + artist2.albums[1]!, + ].map((it) => ({ + itemType: "album", + id: `album:${it.id}`, + title: it.name, + })), + index: 2, + total: 6, + }) + ); + }); }); }); });