diff --git a/src/music_service.ts b/src/music_service.ts index ded443d..6bb8cbc 100644 --- a/src/music_service.ts +++ b/src/music_service.ts @@ -35,7 +35,7 @@ export type Images = { }; export type Artist = ArtistSummary & { - albums: Album[]; + albums: AlbumSummary[]; }; export type AlbumSummary = { @@ -45,7 +45,16 @@ export type AlbumSummary = { genre: string | undefined; }; -export type Album = AlbumSummary & {}; +export type Album = AlbumSummary & { + tracks: Track[] +}; + +export type Track = { + id: string; + name: string; + mimeType: string; + duration: string; +}; export type Paging = { _index: number; diff --git a/src/navidrome.ts b/src/navidrome.ts index d461b82..ef5e924 100644 --- a/src/navidrome.ts +++ b/src/navidrome.ts @@ -53,18 +53,17 @@ export type album = { _coverArt: string; }; -export type artist = { +export type artistSummary = { _id: string; _name: string; _albumCount: string; _artistImageUrl: string | undefined; - album: album[]; -}; +} export type GetArtistsResponse = SubsonicResponse & { artists: { index: { - artist: artist[]; + artist: artistSummary[]; _name: string; }[]; }; @@ -108,14 +107,43 @@ export type ArtistInfo = { image: Images; }; -export type GetArtistInfoResponse = { +export type GetArtistInfoResponse = SubsonicResponse & { artistInfo: artistInfo; }; -export type GetArtistResponse = { - artist: artist; +export type GetArtistResponse = SubsonicResponse & { + artist: artistSummary & { + album: album[]; + }; }; +export type song = { + "_id": string, + "_parent": string, + "_title": string, + "_album": string, + "_artist": string, + "_coverArt": string, + "_created": "2004-11-08T23:36:11", + "_duration": string, + "_bitRate": "128", + "_suffix": "mp3", + "_contentType": string, + "_albumId": string, + "_artistId": string, + "_type": "music" +} + +export type GetAlbumResponse = { + album: { + _id: string, + _name: string, + _genre: string, + _year: string, + song: song[] + } +} + export function isError( subsonicResponse: SubsonicResponse ): subsonicResponse is SubsonicError { @@ -211,7 +239,7 @@ export class Navidrome implements MusicService { getArtist = ( credentials: Credentials, id: string - ): Promise => + ): Promise => this.get(credentials, "/rest/getArtist", { id, }) @@ -301,9 +329,21 @@ export class Navidrome implements MusicService { total: Math.min(MAX_ALBUM_LIST, total), })); }, - album: (_: string): Promise => { - return Promise.reject("not implemented"); - }, + album: (id: string): Promise => navidrome + .get(credentials, "/rest/getAlbum", { id }) + .then(it => it.album) + .then(album => ({ + id: album._id, + name: album._name, + year: album._year, + genre: album._genre, + tracks: album.song.map(track => ({ + id: track._id, + name: track._title, + mimeType: track._contentType, + duration: track._duration, + })) + })), genres: () => navidrome .get(credentials, "/rest/getGenres") diff --git a/src/smapi.ts b/src/smapi.ts index 67bd2af..d0826b4 100644 --- a/src/smapi.ts +++ b/src/smapi.ts @@ -280,8 +280,8 @@ function bindSmapiSoapServiceToExpress( index: paging._index, total, }) - ); - case "artist": + ); + case "artist": return await musicLibrary .artist(typeId!) .then((artist) => artist.albums) diff --git a/tests/builders.ts b/tests/builders.ts index 3816c22..138a587 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 { Album, Artist } from "../src/music_service"; +import { Album, Artist, Track } from "../src/music_service"; const randomInt = (max: number) => Math.floor(Math.random() * Math.floor(max)); const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`; @@ -83,6 +83,17 @@ export function anArtist(fields: Partial = {}): Artist { }; } +export function aTrack(fields: Partial = {}): Track { + const id = uuid(); + return { + id, + name: `Track ${id}`, + mimeType: `audio/mp3-${id}`, + duration: `${randomInt(500)}`, + ...fields + } +} + export function anAlbum(fields: Partial = {}): Album { const genres = ["Metal", "Pop", "Rock", "Hip-Hop"]; const id = uuid(); @@ -91,6 +102,7 @@ export function anAlbum(fields: Partial = {}): Album { name: `Album ${id}`, genre: genres[randomInt(genres.length)], year: `19${randomInt(99)}`, + tracks: [aTrack(), aTrack(), aTrack()], ...fields, }; } @@ -123,9 +135,9 @@ 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" }, + { 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", diff --git a/tests/navidrome.test.ts b/tests/navidrome.test.ts index 996c31e..839aac0 100644 --- a/tests/navidrome.test.ts +++ b/tests/navidrome.test.ts @@ -15,8 +15,10 @@ import { albumToAlbumSummary, range, asArtistAlbumPairs, + Track, + AlbumSummary } from "../src/music_service"; -import { anAlbum, anArtist } from "./builders"; +import { anAlbum, anArtist, aTrack } from "./builders"; jest.mock("../src/random_string"); @@ -66,7 +68,7 @@ const artistInfoXml = ( `; -const albumXml = (artist: Artist, album: Album) => ` ` ``; + isVideo="false">${tracks.map(track => songXml(artist, album, track))}`; + +const songXml = (artist: Artist, album: AlbumSummary, track: Track) => ``; const albumListXml = ( albums: [Artist, Album][] ) => ` ${albums.map(([artist, album]) => - albumXml(artist, album) - )} + albumXml(artist, album) +)} `; const artistXml = ( artist: Artist ) => ` - + ${artist.albums.map((album) => albumXml(artist, album))} `; @@ -106,12 +127,16 @@ const genresXml = ( ) => ` ${genres.map( - (it) => - `${it}` - )} + (it) => + `${it}` +)} `; +const getAlbumXml = (artist: Artist, album: Album) => ` + ${albumXml(artist, album, album.tracks)} + ` + const PING_OK = ``; describe("Navidrome", () => { @@ -184,7 +209,7 @@ describe("Navidrome", () => { .mockImplementationOnce(() => Promise.resolve(ok(genresXml(genres)))); }); - it.only("should return them alphabetically sorted", async () => { + it("should return them alphabetically sorted", async () => { const result = await navidrome .generateToken({ username, password }) .then((it) => it as AuthSuccess) @@ -504,7 +529,7 @@ describe("Navidrome", () => { .then((it) => it.albums(paging)); expect(result).toEqual({ - results: albums.map(albumToAlbumSummary), + results: albums, total: 6, }); @@ -596,4 +621,46 @@ describe("Navidrome", () => { }); }); }); + + describe("getting an album", () => { + describe("when it exists", () => { + const album = anAlbum({ tracks: [ + aTrack(), + aTrack(), + aTrack(), + aTrack(), + ] }); + + const artist = anArtist({ albums: [album] }) + + beforeEach(() => { + mockGET + .mockImplementationOnce(() => Promise.resolve(ok(PING_OK))) + .mockImplementationOnce(() => + Promise.resolve( + ok( + getAlbumXml(artist, album) + ) + ) + ); + }); + + it("should return the album", async () => { + const result = await navidrome + .generateToken({ username, password }) + .then((it) => it as AuthSuccess) + .then((it) => navidrome.login(it.authToken)) + .then((it) => it.album(album.id)); + + expect(result).toEqual(album); + + expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbum`, { + params: { + id: album.id, + ...authParams, + }, + }); + }); + }); + }); });