albums broken

This commit is contained in:
simojenki
2021-03-08 08:13:02 +11:00
parent 86a2411f21
commit ba566ef1d2
5 changed files with 160 additions and 32 deletions

View File

@@ -35,7 +35,7 @@ export type Images = {
}; };
export type Artist = ArtistSummary & { export type Artist = ArtistSummary & {
albums: Album[]; albums: AlbumSummary[];
}; };
export type AlbumSummary = { export type AlbumSummary = {
@@ -45,7 +45,16 @@ export type AlbumSummary = {
genre: string | undefined; 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 = { export type Paging = {
_index: number; _index: number;

View File

@@ -53,18 +53,17 @@ export type album = {
_coverArt: string; _coverArt: string;
}; };
export type artist = { export type artistSummary = {
_id: string; _id: string;
_name: string; _name: string;
_albumCount: string; _albumCount: string;
_artistImageUrl: string | undefined; _artistImageUrl: string | undefined;
album: album[]; }
};
export type GetArtistsResponse = SubsonicResponse & { export type GetArtistsResponse = SubsonicResponse & {
artists: { artists: {
index: { index: {
artist: artist[]; artist: artistSummary[];
_name: string; _name: string;
}[]; }[];
}; };
@@ -108,14 +107,43 @@ export type ArtistInfo = {
image: Images; image: Images;
}; };
export type GetArtistInfoResponse = { export type GetArtistInfoResponse = SubsonicResponse & {
artistInfo: artistInfo; artistInfo: artistInfo;
}; };
export type GetArtistResponse = { export type GetArtistResponse = SubsonicResponse & {
artist: artist; 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( export function isError(
subsonicResponse: SubsonicResponse subsonicResponse: SubsonicResponse
): subsonicResponse is SubsonicError { ): subsonicResponse is SubsonicError {
@@ -211,7 +239,7 @@ export class Navidrome implements MusicService {
getArtist = ( getArtist = (
credentials: Credentials, credentials: Credentials,
id: string id: string
): Promise<IdName & { albums: Album[] }> => ): Promise<IdName & { albums: AlbumSummary[] }> =>
this.get<GetArtistResponse>(credentials, "/rest/getArtist", { this.get<GetArtistResponse>(credentials, "/rest/getArtist", {
id, id,
}) })
@@ -301,9 +329,21 @@ export class Navidrome implements MusicService {
total: Math.min(MAX_ALBUM_LIST, total), total: Math.min(MAX_ALBUM_LIST, total),
})); }));
}, },
album: (_: string): Promise<Album> => { album: (id: string): Promise<Album> => navidrome
return Promise.reject("not implemented"); .get<GetAlbumResponse>(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: () => genres: () =>
navidrome navidrome
.get<GenGenresResponse>(credentials, "/rest/getGenres") .get<GenGenresResponse>(credentials, "/rest/getGenres")

View File

@@ -280,8 +280,8 @@ function bindSmapiSoapServiceToExpress(
index: paging._index, index: paging._index,
total, total,
}) })
); );
case "artist": case "artist":
return await musicLibrary return await musicLibrary
.artist(typeId!) .artist(typeId!)
.then((artist) => artist.albums) .then((artist) => artist.albums)

View File

@@ -3,7 +3,7 @@ import { v4 as uuid } from "uuid";
import { Credentials } from "../src/smapi"; import { Credentials } from "../src/smapi";
import { Service, Device } from "../src/sonos"; 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 randomInt = (max: number) => Math.floor(Math.random() * Math.floor(max));
const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`; const randomIpAddress = () => `127.0.${randomInt(255)}.${randomInt(255)}`;
@@ -83,6 +83,17 @@ export function anArtist(fields: Partial<Artist> = {}): Artist {
}; };
} }
export function aTrack(fields: Partial<Track> = {}): Track {
const id = uuid();
return {
id,
name: `Track ${id}`,
mimeType: `audio/mp3-${id}`,
duration: `${randomInt(500)}`,
...fields
}
}
export function anAlbum(fields: Partial<Album> = {}): Album { export function anAlbum(fields: Partial<Album> = {}): Album {
const genres = ["Metal", "Pop", "Rock", "Hip-Hop"]; const genres = ["Metal", "Pop", "Rock", "Hip-Hop"];
const id = uuid(); const id = uuid();
@@ -91,6 +102,7 @@ export function anAlbum(fields: Partial<Album> = {}): Album {
name: `Album ${id}`, name: `Album ${id}`,
genre: genres[randomInt(genres.length)], genre: genres[randomInt(genres.length)],
year: `19${randomInt(99)}`, year: `19${randomInt(99)}`,
tracks: [aTrack(), aTrack(), aTrack()],
...fields, ...fields,
}; };
} }
@@ -123,9 +135,9 @@ export const BOB_MARLEY: Artist = {
id: uuid(), id: uuid(),
name: "Bob Marley", name: "Bob Marley",
albums: [ albums: [
{ id: uuid(), name: "Burin'", year: "1973", genre: "Reggae" }, { id: uuid(), name: "Burin'", year: "1973", genre: "Reggae", },
{ id: uuid(), name: "Exodus", year: "1977", genre: "Reggae" }, { id: uuid(), name: "Exodus", year: "1977", genre: "Reggae", },
{ id: uuid(), name: "Kaya", year: "1978", genre: "Ska" }, { id: uuid(), name: "Kaya", year: "1978", genre: "Ska", },
], ],
image: { image: {
small: "http://localhost/BOB_MARLEY/sml", small: "http://localhost/BOB_MARLEY/sml",

View File

@@ -15,8 +15,10 @@ import {
albumToAlbumSummary, albumToAlbumSummary,
range, range,
asArtistAlbumPairs, asArtistAlbumPairs,
Track,
AlbumSummary
} from "../src/music_service"; } from "../src/music_service";
import { anAlbum, anArtist } from "./builders"; import { anAlbum, anArtist, aTrack } from "./builders";
jest.mock("../src/random_string"); jest.mock("../src/random_string");
@@ -66,7 +68,7 @@ const artistInfoXml = (
</artistInfo> </artistInfo>
</subsonic-response>`; </subsonic-response>`;
const albumXml = (artist: Artist, album: Album) => `<album id="${album.id}" const albumXml = (artist: Artist, album: AlbumSummary, tracks: Track[] = []) => `<album id="${album.id}"
parent="${artist.id}" parent="${artist.id}"
isDir="true" isDir="true"
title="${album.name}" name="${album.name}" album="${album.name}" title="${album.name}" name="${album.name}" album="${album.name}"
@@ -79,24 +81,43 @@ const albumXml = (artist: Artist, album: Album) => `<album id="${album.id}"
created="2021-01-07T08:19:55.834207205Z" created="2021-01-07T08:19:55.834207205Z"
artistId="${artist.id}" artistId="${artist.id}"
songCount="19" songCount="19"
isVideo="false"></album>`; isVideo="false">${tracks.map(track => songXml(artist, album, track))}</album>`;
const songXml = (artist: Artist, album: AlbumSummary, track: Track) => `<song
id="${track.id}"
parent="${album.id}"
title="${track.name}"
album="${album.name}"
artist="${artist.name}"
isDir="false"
coverArt="71381"
created="2004-11-08T23:36:11"
duration="${track.duration}"
bitRate="128"
size="5624132"
suffix="mp3"
contentType="${track.mimeType}"
isVideo="false"
path="ACDC/High voltage/ACDC - The Jack.mp3"
albumId="${album.id}"
artistId="${artist.name}"
type="music"/>`;
const albumListXml = ( const albumListXml = (
albums: [Artist, Album][] albums: [Artist, Album][]
) => `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)"> ) => `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)">
<albumList> <albumList>
${albums.map(([artist, album]) => ${albums.map(([artist, album]) =>
albumXml(artist, album) albumXml(artist, album)
)} )}
</albumList> </albumList>
</subsonic-response>`; </subsonic-response>`;
const artistXml = ( const artistXml = (
artist: Artist artist: Artist
) => `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)"> ) => `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)">
<artist id="${artist.id}" name="${artist.name}" albumCount="${ <artist id="${artist.id}" name="${artist.name}" albumCount="${artist.albums.length
artist.albums.length }" artistImageUrl="....">
}" artistImageUrl="....">
${artist.albums.map((album) => albumXml(artist, album))} ${artist.albums.map((album) => albumXml(artist, album))}
</artist> </artist>
</subsonic-response>`; </subsonic-response>`;
@@ -106,12 +127,16 @@ const genresXml = (
) => `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)"> ) => `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)">
<genres> <genres>
${genres.map( ${genres.map(
(it) => (it) =>
`<genre songCount="1475" albumCount="86">${it}</genre>` `<genre songCount="1475" albumCount="86">${it}</genre>`
)} )}
</genres> </genres>
</subsonic-response>`; </subsonic-response>`;
const getAlbumXml = (artist: Artist, album: Album) => `<subsonic-response status="ok" version="1.8.0">
${albumXml(artist, album, album.tracks)}
</subsonic-response>`
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>`; 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("Navidrome", () => {
@@ -184,7 +209,7 @@ describe("Navidrome", () => {
.mockImplementationOnce(() => Promise.resolve(ok(genresXml(genres)))); .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 const result = await navidrome
.generateToken({ username, password }) .generateToken({ username, password })
.then((it) => it as AuthSuccess) .then((it) => it as AuthSuccess)
@@ -504,7 +529,7 @@ describe("Navidrome", () => {
.then((it) => it.albums(paging)); .then((it) => it.albums(paging));
expect(result).toEqual({ expect(result).toEqual({
results: albums.map(albumToAlbumSummary), results: albums,
total: 6, 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,
},
});
});
});
});
}); });