mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Similar songs (#18)
* Support for getting similarSongs from navidrome * Ability to load topSongs from navidrome * Load artists not in library from navidrome
This commit is contained in:
@@ -39,10 +39,12 @@ export const NO_IMAGES: Images = {
|
|||||||
large: undefined,
|
large: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SimilarArtist = ArtistSummary & { inLibrary: boolean };
|
||||||
|
|
||||||
export type Artist = ArtistSummary & {
|
export type Artist = ArtistSummary & {
|
||||||
image: Images
|
image: Images
|
||||||
albums: AlbumSummary[];
|
albums: AlbumSummary[];
|
||||||
similarArtists: ArtistSummary[]
|
similarArtists: SimilarArtist[]
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AlbumSummary = {
|
export type AlbumSummary = {
|
||||||
@@ -179,4 +181,6 @@ export interface MusicLibrary {
|
|||||||
deletePlaylist(id: string): Promise<boolean>
|
deletePlaylist(id: string): Promise<boolean>
|
||||||
addToPlaylist(playlistId: string, trackId: string): Promise<boolean>
|
addToPlaylist(playlistId: string, trackId: string): Promise<boolean>
|
||||||
removeFromPlaylist(playlistId: string, indicies: number[]): Promise<boolean>
|
removeFromPlaylist(playlistId: string, indicies: number[]): Promise<boolean>
|
||||||
|
similarSongs(id: string): Promise<Track[]>;
|
||||||
|
topSongs(artistId: string): Promise<Track[]>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export type artistInfo = {
|
|||||||
|
|
||||||
export type ArtistInfo = {
|
export type ArtistInfo = {
|
||||||
image: Images;
|
image: Images;
|
||||||
similarArtist: { id: string; name: string }[];
|
similarArtist: (ArtistSummary & { inLibrary: boolean })[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GetArtistInfoResponse = SubsonicResponse & {
|
export type GetArtistInfoResponse = SubsonicResponse & {
|
||||||
@@ -196,6 +196,14 @@ export type GetPlaylistsResponse = {
|
|||||||
playlists: { playlist: playlist[] };
|
playlists: { playlist: playlist[] };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetSimilarSongsResponse = {
|
||||||
|
similarSongs: { song: song[] }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetTopSongsResponse = {
|
||||||
|
topSongs: { song: song[] }
|
||||||
|
}
|
||||||
|
|
||||||
export type GetSongResponse = {
|
export type GetSongResponse = {
|
||||||
song: song;
|
song: song;
|
||||||
};
|
};
|
||||||
@@ -240,7 +248,7 @@ const asTrack = (album: Album, song: song) => ({
|
|||||||
album,
|
album,
|
||||||
artist: {
|
artist: {
|
||||||
id: song._artistId,
|
id: song._artistId,
|
||||||
name: song._artist,
|
name: song._artist
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -351,6 +359,8 @@ export class Navidrome implements MusicService {
|
|||||||
"subsonic-response.searchResult3.album",
|
"subsonic-response.searchResult3.album",
|
||||||
"subsonic-response.searchResult3.artist",
|
"subsonic-response.searchResult3.artist",
|
||||||
"subsonic-response.searchResult3.song",
|
"subsonic-response.searchResult3.song",
|
||||||
|
"subsonic-response.similarSongs.song",
|
||||||
|
"subsonic-response.topSongs.song",
|
||||||
],
|
],
|
||||||
}).xml2js(response.data) as SubconicEnvelope
|
}).xml2js(response.data) as SubconicEnvelope
|
||||||
)
|
)
|
||||||
@@ -392,6 +402,7 @@ export class Navidrome implements MusicService {
|
|||||||
this.getJSON<GetArtistInfoResponse>(credentials, "/rest/getArtistInfo", {
|
this.getJSON<GetArtistInfoResponse>(credentials, "/rest/getArtistInfo", {
|
||||||
id,
|
id,
|
||||||
count: 50,
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}).then((it) => ({
|
}).then((it) => ({
|
||||||
image: {
|
image: {
|
||||||
small: validate(it.artistInfo.smallImageUrl),
|
small: validate(it.artistInfo.smallImageUrl),
|
||||||
@@ -401,6 +412,7 @@ export class Navidrome implements MusicService {
|
|||||||
similarArtist: (it.artistInfo.similarArtist || []).map((artist) => ({
|
similarArtist: (it.artistInfo.similarArtist || []).map((artist) => ({
|
||||||
id: artist._id,
|
id: artist._id,
|
||||||
name: artist._name,
|
name: artist._name,
|
||||||
|
inLibrary: artist._id != "-1",
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -698,7 +710,7 @@ export class Navidrome implements MusicService {
|
|||||||
},
|
},
|
||||||
artist: {
|
artist: {
|
||||||
id: entry._artistId,
|
id: entry._artistId,
|
||||||
name: entry._artist,
|
name: entry._artist
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
@@ -730,6 +742,24 @@ export class Navidrome implements MusicService {
|
|||||||
songIndexToRemove: indicies,
|
songIndexToRemove: indicies,
|
||||||
})
|
})
|
||||||
.then((_) => true),
|
.then((_) => true),
|
||||||
|
similarSongs: async (id: string) => navidrome
|
||||||
|
.getJSON<GetSimilarSongsResponse>(credentials, "/rest/getSimilarSongs", { id, count: 50 })
|
||||||
|
.then((it) => (it.similarSongs.song || []))
|
||||||
|
.then(songs =>
|
||||||
|
Promise.all(
|
||||||
|
songs.map((song) => navidrome.getAlbum(credentials, song._albumId).then(album => asTrack(album, song)))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
topSongs: async (artistId: string) => navidrome
|
||||||
|
.getArtist(credentials, artistId)
|
||||||
|
.then(({ name }) => navidrome
|
||||||
|
.getJSON<GetTopSongsResponse>(credentials, "/rest/getTopSongs", { artist: name, count: 50 })
|
||||||
|
.then((it) => (it.topSongs.song || []))
|
||||||
|
.then(songs =>
|
||||||
|
Promise.all(
|
||||||
|
songs.map((song) => navidrome.getAlbum(credentials, song._albumId).then(album => asTrack(album, song)))
|
||||||
|
)
|
||||||
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
return Promise.resolve(musicLibrary);
|
return Promise.resolve(musicLibrary);
|
||||||
|
|||||||
@@ -458,7 +458,7 @@ function bindSmapiSoapServiceToExpress(
|
|||||||
album(urlWithToken(accessToken), it)
|
album(urlWithToken(accessToken), it)
|
||||||
),
|
),
|
||||||
relatedBrowse:
|
relatedBrowse:
|
||||||
artist.similarArtists.length > 0
|
artist.similarArtists.filter(it => it.inLibrary).length > 0
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
id: `relatedArtists:${artist.id}`,
|
id: `relatedArtists:${artist.id}`,
|
||||||
@@ -715,6 +715,7 @@ function bindSmapiSoapServiceToExpress(
|
|||||||
return musicLibrary
|
return musicLibrary
|
||||||
.artist(typeId!)
|
.artist(typeId!)
|
||||||
.then((artist) => artist.similarArtists)
|
.then((artist) => artist.similarArtists)
|
||||||
|
.then(similarArtists => similarArtists.filter(it => it.inLibrary))
|
||||||
.then(slice2(paging))
|
.then(slice2(paging))
|
||||||
.then(([page, total]) => {
|
.then(([page, total]) => {
|
||||||
return getMetadataResult({
|
return getMetadataResult({
|
||||||
|
|||||||
@@ -98,8 +98,9 @@ export function anArtist(fields: Partial<Artist> = {}): Artist {
|
|||||||
large: `/artist/art/${id}/large`,
|
large: `/artist/art/${id}/large`,
|
||||||
},
|
},
|
||||||
similarArtists: [
|
similarArtists: [
|
||||||
{ id: uuid(), name: "Similar artist1" },
|
{ id: uuid(), name: "Similar artist1", inLibrary: true },
|
||||||
{ id: uuid(), name: "Similar artist2" },
|
{ id: uuid(), name: "Similar artist2", inLibrary: true },
|
||||||
|
{ id: "-1", name: "Artist not in library", inLibrary: false },
|
||||||
],
|
],
|
||||||
...fields,
|
...fields,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -143,6 +143,8 @@ export class InMemoryMusicService implements MusicService {
|
|||||||
deletePlaylist: async (_: string) => Promise.reject("Unsupported operation"),
|
deletePlaylist: async (_: string) => Promise.reject("Unsupported operation"),
|
||||||
addToPlaylist: async (_: string) => Promise.reject("Unsupported operation"),
|
addToPlaylist: async (_: string) => Promise.reject("Unsupported operation"),
|
||||||
removeFromPlaylist: async (_: string, _2: number[]) => Promise.reject("Unsupported operation"),
|
removeFromPlaylist: async (_: string, _2: number[]) => Promise.reject("Unsupported operation"),
|
||||||
|
similarSongs: async (_: string) => Promise.resolve([]),
|
||||||
|
topSongs: async (_: string) => Promise.resolve([]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
AlbumQuery,
|
AlbumQuery,
|
||||||
PlaylistSummary,
|
PlaylistSummary,
|
||||||
Playlist,
|
Playlist,
|
||||||
ArtistSummary,
|
SimilarArtist,
|
||||||
} from "../src/music_service";
|
} from "../src/music_service";
|
||||||
import {
|
import {
|
||||||
anAlbum,
|
anAlbum,
|
||||||
@@ -151,7 +151,7 @@ describe("asURLSearchParams", () => {
|
|||||||
|
|
||||||
expect(asURLSearchParams(q)).toEqual(expected);
|
expect(asURLSearchParams(q)).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const ok = (data: string) => ({
|
const ok = (data: string) => ({
|
||||||
@@ -159,8 +159,13 @@ const ok = (data: string) => ({
|
|||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
|
||||||
const similarArtistXml = (artistSummary: ArtistSummary) =>
|
const similarArtistXml = (similarArtist: SimilarArtist) => {
|
||||||
`<similarArtist id="${artistSummary.id}" name="${artistSummary.name}" albumCount="3"></similarArtist>`;
|
if(similarArtist.inLibrary)
|
||||||
|
return `<similarArtist id="${similarArtist.id}" name="${similarArtist.name}" albumCount="3"></similarArtist>`
|
||||||
|
else
|
||||||
|
return `<similarArtist id="-1" name="${similarArtist.name}" albumCount="3"></similarArtist>`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const getArtistInfoXml = (
|
const getArtistInfoXml = (
|
||||||
artist: Artist
|
artist: Artist
|
||||||
@@ -222,19 +227,18 @@ const albumListXml = (
|
|||||||
) => `<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
|
${albums
|
||||||
.map(([artist, album]) => albumXml(artist, album))
|
.map(([artist, album]) => albumXml(artist, album))
|
||||||
.join("")}
|
.join("")}
|
||||||
</albumList>
|
</albumList>
|
||||||
</subsonic-response>`;
|
</subsonic-response>`;
|
||||||
|
|
||||||
const artistXml = (artist: Artist) => `<artist id="${artist.id}" name="${
|
const artistXml = (artist: Artist) => `<artist id="${artist.id}" name="${artist.name
|
||||||
artist.name
|
}" albumCount="${artist.albums.length}" artistImageUrl="....">
|
||||||
}" albumCount="${artist.albums.length}" artistImageUrl="....">
|
|
||||||
${artist.albums
|
${artist.albums
|
||||||
.map((album) =>
|
.map((album) =>
|
||||||
albumXml(artist, album)
|
albumXml(artist, album)
|
||||||
)
|
)
|
||||||
.join("")}
|
.join("")}
|
||||||
</artist>`;
|
</artist>`;
|
||||||
|
|
||||||
const getArtistXml = (
|
const getArtistXml = (
|
||||||
@@ -260,20 +264,32 @@ const getAlbumXml = (
|
|||||||
tracks: Track[]
|
tracks: Track[]
|
||||||
) => `<subsonic-response status="ok" version="1.8.0">
|
) => `<subsonic-response status="ok" version="1.8.0">
|
||||||
${albumXml(
|
${albumXml(
|
||||||
artist,
|
artist,
|
||||||
album,
|
album,
|
||||||
tracks
|
tracks
|
||||||
)}
|
)}
|
||||||
</subsonic-response>`;
|
</subsonic-response>`;
|
||||||
|
|
||||||
const getSongXml = (
|
const getSongXml = (
|
||||||
track: Track
|
track: Track
|
||||||
) => `<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)">
|
||||||
${songXml(
|
${songXml(
|
||||||
track
|
track
|
||||||
)}
|
)}
|
||||||
</subsonic-response>`;
|
</subsonic-response>`;
|
||||||
|
|
||||||
|
const similarSongsXml = (tracks: Track[]) => `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)">
|
||||||
|
<similarSongs>
|
||||||
|
${tracks.map(songXml).join("")}
|
||||||
|
</similarSongs>
|
||||||
|
</subsonic-response>`
|
||||||
|
|
||||||
|
const topSongsXml = (tracks: Track[]) => `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)">
|
||||||
|
<topSongs>
|
||||||
|
${tracks.map(songXml).join("")}
|
||||||
|
</topSongs>
|
||||||
|
</subsonic-response>`
|
||||||
|
|
||||||
export type ArtistWithAlbum = {
|
export type ArtistWithAlbum = {
|
||||||
artist: Artist;
|
artist: Artist;
|
||||||
album: Album;
|
album: Album;
|
||||||
@@ -291,7 +307,10 @@ const getPlayLists = (
|
|||||||
</subsonic-response>`;
|
</subsonic-response>`;
|
||||||
|
|
||||||
const error = (code: string, message: string) =>
|
const error = (code: string, message: string) =>
|
||||||
`<subsonic-response xmlns="http://subsonic.org/restapi" status="failed" version="1.16.1" type="navidrome" serverVersion="0.42.0 (f1bd736b)"><error code="${code}" message="${message}"></error></subsonic-response>`;
|
`<subsonic-response xmlns="http://subsonic.org/restapi" status="failed" version="1.16.1" type="navidrome" serverVersion="0.42.0 (f1bd736b)">
|
||||||
|
<error code="${code}" message="${message}">
|
||||||
|
</error>
|
||||||
|
</subsonic-response>`;
|
||||||
|
|
||||||
const createPlayList = (playlist: PlaylistSummary) => `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.42.0 (f1bd736b)">
|
const createPlayList = (playlist: PlaylistSummary) => `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.42.0 (f1bd736b)">
|
||||||
${playlistXml(playlist)}
|
${playlistXml(playlist)}
|
||||||
@@ -300,9 +319,8 @@ const createPlayList = (playlist: PlaylistSummary) => `<subsonic-response xmlns=
|
|||||||
const getPlayList = (
|
const getPlayList = (
|
||||||
playlist: Playlist
|
playlist: Playlist
|
||||||
) => `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.42.0 (f1bd736b)">
|
) => `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.42.0 (f1bd736b)">
|
||||||
<playlist id="${playlist.id}" name="${playlist.name}" songCount="${
|
<playlist id="${playlist.id}" name="${playlist.name}" songCount="${playlist.entries.length
|
||||||
playlist.entries.length
|
}" duration="627" public="true" owner="bob" created="2021-05-06T02:07:30.460465988Z" changed="2021-05-06T02:40:04Z">
|
||||||
}" duration="627" public="true" owner="bob" created="2021-05-06T02:07:30.460465988Z" changed="2021-05-06T02:40:04Z">
|
|
||||||
${playlist.entries
|
${playlist.entries
|
||||||
.map(
|
.map(
|
||||||
(it) => `<entry
|
(it) => `<entry
|
||||||
@@ -500,8 +518,10 @@ describe("Navidrome", () => {
|
|||||||
large: `http://localhost:80/${DODGY_IMAGE_NAME}`,
|
large: `http://localhost:80/${DODGY_IMAGE_NAME}`,
|
||||||
},
|
},
|
||||||
similarArtists: [
|
similarArtists: [
|
||||||
{ id: "similar1.id", name: "similar1" },
|
{ id: "similar1.id", name: "similar1", inLibrary: true },
|
||||||
{ id: "similar2.id", name: "similar2" },
|
{ id: "-1", name: "similar2", inLibrary: false },
|
||||||
|
{ id: "similar3.id", name: "similar3", inLibrary: true },
|
||||||
|
{ id: "-1", name: "similar4", inLibrary: false },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -536,7 +556,7 @@ describe("Navidrome", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtist`, {
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtist`, {
|
||||||
params:asURLSearchParams( {
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artist.id,
|
id: artist.id,
|
||||||
}),
|
}),
|
||||||
@@ -547,14 +567,15 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artist.id,
|
id: artist.id,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("and has one similar artists", () => {
|
describe("and has one similar artist", () => {
|
||||||
const album1: Album = anAlbum({ genre: asGenre("G1") });
|
const album1: Album = anAlbum({ genre: asGenre("G1") });
|
||||||
|
|
||||||
const album2: Album = anAlbum({ genre: asGenre("G2") });
|
const album2: Album = anAlbum({ genre: asGenre("G2") });
|
||||||
@@ -566,7 +587,7 @@ describe("Navidrome", () => {
|
|||||||
medium: `http://localhost:80/${DODGY_IMAGE_NAME}`,
|
medium: `http://localhost:80/${DODGY_IMAGE_NAME}`,
|
||||||
large: `http://localhost:80/${DODGY_IMAGE_NAME}`,
|
large: `http://localhost:80/${DODGY_IMAGE_NAME}`,
|
||||||
},
|
},
|
||||||
similarArtists: [{ id: "similar1.id", name: "similar1" }],
|
similarArtists: [{ id: "similar1.id", name: "similar1", inLibrary: true }],
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -611,7 +632,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artist.id,
|
id: artist.id,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
@@ -675,7 +697,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artist.id,
|
id: artist.id,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
@@ -739,7 +762,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artist.id,
|
id: artist.id,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
@@ -794,7 +818,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artist.id,
|
id: artist.id,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
@@ -847,7 +872,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artist.id,
|
id: artist.id,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
@@ -898,7 +924,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artist.id,
|
id: artist.id,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
@@ -2087,7 +2114,7 @@ describe("Navidrome", () => {
|
|||||||
|
|
||||||
expect(streamClientApplication).toHaveBeenCalledWith(track);
|
expect(streamClientApplication).toHaveBeenCalledWith(track);
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/stream`, {
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/stream`, {
|
||||||
params:asURLSearchParams( {
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: trackId,
|
id: trackId,
|
||||||
c: clientApplication,
|
c: clientApplication,
|
||||||
@@ -2231,7 +2258,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artistId,
|
id: artistId,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
}
|
}
|
||||||
@@ -2307,7 +2335,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artistId,
|
id: artistId,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
}
|
}
|
||||||
@@ -2383,7 +2412,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artistId,
|
id: artistId,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
}
|
}
|
||||||
@@ -2452,7 +2482,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artistId,
|
id: artistId,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
}
|
}
|
||||||
@@ -2531,7 +2562,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artistId,
|
id: artistId,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
}
|
}
|
||||||
@@ -2608,7 +2640,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artistId,
|
id: artistId,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
}
|
}
|
||||||
@@ -2680,7 +2713,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artistId,
|
id: artistId,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
}
|
}
|
||||||
@@ -2757,7 +2791,8 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id: artistId,
|
id: artistId,
|
||||||
count: 50
|
count: 50,
|
||||||
|
includeNotPresent: true
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
}
|
}
|
||||||
@@ -3343,7 +3378,7 @@ describe("Navidrome", () => {
|
|||||||
mockGET
|
mockGET
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
.mockImplementationOnce(() =>
|
.mockImplementationOnce(() =>
|
||||||
Promise.resolve(ok(error("70", "not there")))
|
Promise.resolve(ok(error("70", "data not found")))
|
||||||
);
|
);
|
||||||
|
|
||||||
return expect(
|
return expect(
|
||||||
@@ -3352,7 +3387,7 @@ describe("Navidrome", () => {
|
|||||||
.then((it) => it as AuthSuccess)
|
.then((it) => it as AuthSuccess)
|
||||||
.then((it) => navidrome.login(it.authToken))
|
.then((it) => navidrome.login(it.authToken))
|
||||||
.then((it) => it.playlist(id))
|
.then((it) => it.playlist(id))
|
||||||
).rejects.toEqual("not there");
|
).rejects.toEqual("data not found");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3447,26 +3482,26 @@ describe("Navidrome", () => {
|
|||||||
const id = uuid();
|
const id = uuid();
|
||||||
|
|
||||||
mockGET
|
mockGET
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
.mockImplementationOnce(() =>
|
.mockImplementationOnce(() =>
|
||||||
Promise.resolve(ok(createPlayList({id, name})))
|
Promise.resolve(ok(createPlayList({ id, name })))
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = await navidrome
|
const result = await navidrome
|
||||||
.generateToken({ username, password })
|
.generateToken({ username, password })
|
||||||
.then((it) => it as AuthSuccess)
|
.then((it) => it as AuthSuccess)
|
||||||
.then((it) => navidrome.login(it.authToken))
|
.then((it) => navidrome.login(it.authToken))
|
||||||
.then((it) => it.createPlaylist(name));
|
.then((it) => it.createPlaylist(name));
|
||||||
|
|
||||||
expect(result).toEqual({ id, name });
|
expect(result).toEqual({ id, name });
|
||||||
|
|
||||||
expect(mockGET).toHaveBeenCalledWith(`${url}/rest/createPlaylist`, {
|
expect(mockGET).toHaveBeenCalledWith(`${url}/rest/createPlaylist`, {
|
||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
name,
|
name,
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3475,26 +3510,26 @@ describe("Navidrome", () => {
|
|||||||
const id = "id-to-delete";
|
const id = "id-to-delete";
|
||||||
|
|
||||||
mockGET
|
mockGET
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
.mockImplementationOnce(() =>
|
.mockImplementationOnce(() =>
|
||||||
Promise.resolve(ok(EMPTY))
|
Promise.resolve(ok(EMPTY))
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = await navidrome
|
const result = await navidrome
|
||||||
.generateToken({ username, password })
|
.generateToken({ username, password })
|
||||||
.then((it) => it as AuthSuccess)
|
.then((it) => it as AuthSuccess)
|
||||||
.then((it) => navidrome.login(it.authToken))
|
.then((it) => navidrome.login(it.authToken))
|
||||||
.then((it) => it.deletePlaylist(id));
|
.then((it) => it.deletePlaylist(id));
|
||||||
|
|
||||||
expect(result).toEqual(true);
|
expect(result).toEqual(true);
|
||||||
|
|
||||||
expect(mockGET).toHaveBeenCalledWith(`${url}/rest/deletePlaylist`, {
|
expect(mockGET).toHaveBeenCalledWith(`${url}/rest/deletePlaylist`, {
|
||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
id,
|
id,
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3522,7 +3557,7 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
playlistId,
|
playlistId,
|
||||||
songIdToAdd: trackId,
|
songIdToAdd: trackId,
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
@@ -3532,7 +3567,7 @@ describe("Navidrome", () => {
|
|||||||
describe("removing a track from a playlist", () => {
|
describe("removing a track from a playlist", () => {
|
||||||
it("should remove it", async () => {
|
it("should remove it", async () => {
|
||||||
const playlistId = uuid();
|
const playlistId = uuid();
|
||||||
const indicies =[6, 100, 33];
|
const indicies = [6, 100, 33];
|
||||||
|
|
||||||
mockGET
|
mockGET
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
@@ -3552,12 +3587,334 @@ describe("Navidrome", () => {
|
|||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
playlistId,
|
playlistId,
|
||||||
songIndexToRemove: indicies,
|
songIndexToRemove: indicies,
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("similarSongs", () => {
|
||||||
|
describe("when there is one similar songs", () => {
|
||||||
|
it("should return it", async () => {
|
||||||
|
const id = "idWithTracks";
|
||||||
|
const pop = asGenre("Pop");
|
||||||
|
|
||||||
|
const album1 = anAlbum({ id: "album1", name: "Burnin", genre: pop });
|
||||||
|
const artist1 = anArtist({
|
||||||
|
id: "artist1",
|
||||||
|
name: "Bob Marley",
|
||||||
|
albums: [album1],
|
||||||
|
});
|
||||||
|
|
||||||
|
const track1 = aTrack({
|
||||||
|
id: "track1",
|
||||||
|
artist: artistToArtistSummary(artist1),
|
||||||
|
album: albumToAlbumSummary(album1),
|
||||||
|
genre: pop
|
||||||
|
});
|
||||||
|
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(similarSongsXml([track1])))
|
||||||
|
).mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getAlbumXml(artist1, album1, [])))
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.similarSongs(id));
|
||||||
|
|
||||||
|
expect(result).toEqual([track1]);
|
||||||
|
|
||||||
|
expect(mockGET).toHaveBeenCalledWith(`${url}/rest/getSimilarSongs`, {
|
||||||
|
params: asURLSearchParams({
|
||||||
|
...authParams,
|
||||||
|
id,
|
||||||
|
count: 50,
|
||||||
|
}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when there are similar songs", () => {
|
||||||
|
it("should return them", async () => {
|
||||||
|
const id = "idWithTracks";
|
||||||
|
const pop = asGenre("Pop");
|
||||||
|
|
||||||
|
const album1 = anAlbum({ id: "album1", name: "Burnin", genre: pop });
|
||||||
|
const artist1 = anArtist({
|
||||||
|
id: "artist1",
|
||||||
|
name: "Bob Marley",
|
||||||
|
albums: [album1],
|
||||||
|
});
|
||||||
|
|
||||||
|
const album2 = anAlbum({ id: "album2", name: "Walking", genre: pop });
|
||||||
|
const artist2 = anArtist({
|
||||||
|
id: "artist2",
|
||||||
|
name: "Bob Jane",
|
||||||
|
albums: [album2],
|
||||||
|
});
|
||||||
|
|
||||||
|
const track1 = aTrack({
|
||||||
|
id: "track1",
|
||||||
|
artist: artistToArtistSummary(artist1),
|
||||||
|
album: albumToAlbumSummary(album1),
|
||||||
|
genre: pop
|
||||||
|
});
|
||||||
|
const track2 = aTrack({
|
||||||
|
id: "track2",
|
||||||
|
artist: artistToArtistSummary(artist2),
|
||||||
|
album: albumToAlbumSummary(album2),
|
||||||
|
genre: pop
|
||||||
|
});
|
||||||
|
const track3 = aTrack({
|
||||||
|
id: "track3",
|
||||||
|
artist: artistToArtistSummary(artist1),
|
||||||
|
album: albumToAlbumSummary(album1),
|
||||||
|
genre: pop
|
||||||
|
});
|
||||||
|
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(similarSongsXml([track1, track2, track3])))
|
||||||
|
).mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getAlbumXml(artist1, album1, [])))
|
||||||
|
).mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getAlbumXml(artist2, album2, [])))
|
||||||
|
).mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getAlbumXml(artist1, album1, [])))
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.similarSongs(id));
|
||||||
|
|
||||||
|
expect(result).toEqual([track1, track2, track3]);
|
||||||
|
|
||||||
|
expect(mockGET).toHaveBeenCalledWith(`${url}/rest/getSimilarSongs`, {
|
||||||
|
params: asURLSearchParams({
|
||||||
|
...authParams,
|
||||||
|
id,
|
||||||
|
count: 50,
|
||||||
|
}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when there are no similar songs", () => {
|
||||||
|
it("should return []", async () => {
|
||||||
|
const id = "idWithNoTracks";
|
||||||
|
|
||||||
|
const xml = similarSongsXml([]);
|
||||||
|
console.log(`xml = ${xml}`)
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(xml))
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.similarSongs(id));
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
|
||||||
|
expect(mockGET).toHaveBeenCalledWith(`${url}/rest/getSimilarSongs`, {
|
||||||
|
params: asURLSearchParams({
|
||||||
|
...authParams,
|
||||||
|
id,
|
||||||
|
count: 50,
|
||||||
|
}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when there id doesnt exist", () => {
|
||||||
|
it("should fail", async () => {
|
||||||
|
const id = "idThatHasAnError";
|
||||||
|
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(error("70", "data not found")))
|
||||||
|
);
|
||||||
|
|
||||||
|
return expect(navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.similarSongs(id))).rejects.toEqual("data not found");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("topSongs", () => {
|
||||||
|
describe("when there is one top song", () => {
|
||||||
|
it("should return it", async () => {
|
||||||
|
const artistId = "bobMarleyId";
|
||||||
|
const artistName = "Bob Marley";
|
||||||
|
const pop = asGenre("Pop");
|
||||||
|
|
||||||
|
const album1 = anAlbum({ name: "Burnin", genre: pop });
|
||||||
|
const artist = anArtist({
|
||||||
|
id: artistId,
|
||||||
|
name: artistName,
|
||||||
|
albums: [album1],
|
||||||
|
});
|
||||||
|
|
||||||
|
const track1 = aTrack({
|
||||||
|
artist: artistToArtistSummary(artist),
|
||||||
|
album: albumToAlbumSummary(album1),
|
||||||
|
genre: pop
|
||||||
|
});
|
||||||
|
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistXml(artist)))
|
||||||
|
).mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(topSongsXml([track1])))
|
||||||
|
).mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getAlbumXml(artist, album1, [])))
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.topSongs(artistId));
|
||||||
|
|
||||||
|
expect(result).toEqual([track1]);
|
||||||
|
|
||||||
|
expect(mockGET).toHaveBeenCalledWith(`${url}/rest/getTopSongs`, {
|
||||||
|
params: asURLSearchParams({
|
||||||
|
...authParams,
|
||||||
|
artist: artistName,
|
||||||
|
count: 50,
|
||||||
|
}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when there are many top songs", () => {
|
||||||
|
it("should return them", async () => {
|
||||||
|
const artistId = "bobMarleyId";
|
||||||
|
const artistName = "Bob Marley";
|
||||||
|
const pop = asGenre("Pop");
|
||||||
|
|
||||||
|
const album1 = anAlbum({ name: "Burnin", genre: pop });
|
||||||
|
const album2 = anAlbum({ name: "Churning", genre: pop });
|
||||||
|
|
||||||
|
const artist = anArtist({
|
||||||
|
id: artistId,
|
||||||
|
name: artistName,
|
||||||
|
albums: [album1, album2],
|
||||||
|
});
|
||||||
|
|
||||||
|
const track1 = aTrack({
|
||||||
|
artist: artistToArtistSummary(artist),
|
||||||
|
album: albumToAlbumSummary(album1),
|
||||||
|
genre: pop
|
||||||
|
});
|
||||||
|
|
||||||
|
const track2 = aTrack({
|
||||||
|
artist: artistToArtistSummary(artist),
|
||||||
|
album: albumToAlbumSummary(album2),
|
||||||
|
genre: pop
|
||||||
|
});
|
||||||
|
|
||||||
|
const track3 = aTrack({
|
||||||
|
artist: artistToArtistSummary(artist),
|
||||||
|
album: albumToAlbumSummary(album1),
|
||||||
|
genre: pop
|
||||||
|
});
|
||||||
|
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistXml(artist)))
|
||||||
|
).mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(topSongsXml([track1, track2, track3])))
|
||||||
|
).mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getAlbumXml(artist, album1, [])))
|
||||||
|
).mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getAlbumXml(artist, album2, [])))
|
||||||
|
).mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getAlbumXml(artist, album1, [])))
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.topSongs(artistId));
|
||||||
|
|
||||||
|
expect(result).toEqual([track1, track2, track3]);
|
||||||
|
|
||||||
|
expect(mockGET).toHaveBeenCalledWith(`${url}/rest/getTopSongs`, {
|
||||||
|
params: asURLSearchParams({
|
||||||
|
...authParams,
|
||||||
|
artist: artistName,
|
||||||
|
count: 50,
|
||||||
|
}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when there are no similar songs", () => {
|
||||||
|
it("should return []", async () => {
|
||||||
|
const artistId = "bobMarleyId";
|
||||||
|
const artistName = "Bob Marley";
|
||||||
|
const pop = asGenre("Pop");
|
||||||
|
|
||||||
|
const album1 = anAlbum({ name: "Burnin", genre: pop });
|
||||||
|
const artist = anArtist({
|
||||||
|
id: artistId,
|
||||||
|
name: artistName,
|
||||||
|
albums: [album1],
|
||||||
|
});
|
||||||
|
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistXml(artist)))
|
||||||
|
).mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(topSongsXml([])))
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.topSongs(artistId));
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
|
||||||
|
expect(mockGET).toHaveBeenCalledWith(`${url}/rest/getTopSongs`, {
|
||||||
|
params: asURLSearchParams({
|
||||||
|
...authParams,
|
||||||
|
artist: artistName,
|
||||||
|
count: 50,
|
||||||
|
}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1132,18 +1132,22 @@ describe("api", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("asking for relatedArtists", () => {
|
describe("asking for relatedArtists", () => {
|
||||||
describe("when the artist has many", () => {
|
describe("when the artist has many, some in the library and some not", () => {
|
||||||
const relatedArtist1 = anArtist();
|
const relatedArtist1 = anArtist();
|
||||||
const relatedArtist2 = anArtist();
|
const relatedArtist2 = anArtist();
|
||||||
const relatedArtist3 = anArtist();
|
const relatedArtist3 = anArtist();
|
||||||
const relatedArtist4 = anArtist();
|
const relatedArtist4 = anArtist();
|
||||||
|
const relatedArtist5 = anArtist();
|
||||||
|
const relatedArtist6 = anArtist();
|
||||||
|
|
||||||
const artist = anArtist({
|
const artist = anArtist({
|
||||||
similarArtists: [
|
similarArtists: [
|
||||||
relatedArtist1,
|
{ ...relatedArtist1, inLibrary: true },
|
||||||
relatedArtist2,
|
{ ...relatedArtist2, inLibrary: true },
|
||||||
relatedArtist3,
|
{ ...relatedArtist3, inLibrary: false },
|
||||||
relatedArtist4,
|
{ ...relatedArtist4, inLibrary: true },
|
||||||
|
{ ...relatedArtist5, inLibrary: false },
|
||||||
|
{ ...relatedArtist6, inLibrary: true },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1163,8 +1167,8 @@ describe("api", () => {
|
|||||||
mediaCollection: [
|
mediaCollection: [
|
||||||
relatedArtist1,
|
relatedArtist1,
|
||||||
relatedArtist2,
|
relatedArtist2,
|
||||||
relatedArtist3,
|
|
||||||
relatedArtist4,
|
relatedArtist4,
|
||||||
|
relatedArtist6,
|
||||||
].map((it) => ({
|
].map((it) => ({
|
||||||
itemType: "artist",
|
itemType: "artist",
|
||||||
id: `artist:${it.id}`,
|
id: `artist:${it.id}`,
|
||||||
@@ -1193,7 +1197,7 @@ describe("api", () => {
|
|||||||
});
|
});
|
||||||
expect(result[0]).toEqual(
|
expect(result[0]).toEqual(
|
||||||
getMetadataResult({
|
getMetadataResult({
|
||||||
mediaCollection: [relatedArtist2, relatedArtist3].map(
|
mediaCollection: [relatedArtist2, relatedArtist4].map(
|
||||||
(it) => ({
|
(it) => ({
|
||||||
itemType: "artist",
|
itemType: "artist",
|
||||||
id: `artist:${it.id}`,
|
id: `artist:${it.id}`,
|
||||||
@@ -1238,6 +1242,44 @@ describe("api", () => {
|
|||||||
expect(accessTokens.mint).toHaveBeenCalledWith(authToken);
|
expect(accessTokens.mint).toHaveBeenCalledWith(authToken);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("when the artist some however none are in the library", () => {
|
||||||
|
const relatedArtist1 = anArtist();
|
||||||
|
const relatedArtist2 = anArtist();
|
||||||
|
|
||||||
|
const artist = anArtist({
|
||||||
|
similarArtists: [
|
||||||
|
{
|
||||||
|
...relatedArtist1,
|
||||||
|
inLibrary: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...relatedArtist2,
|
||||||
|
inLibrary: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
musicLibrary.artist.mockResolvedValue(artist);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return an empty list", async () => {
|
||||||
|
const result = await ws.getMetadataAsync({
|
||||||
|
id: `relatedArtists:${artist.id}`,
|
||||||
|
index: 0,
|
||||||
|
count: 100,
|
||||||
|
});
|
||||||
|
expect(result[0]).toEqual(
|
||||||
|
getMetadataResult({
|
||||||
|
index: 0,
|
||||||
|
total: 0,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(musicLibrary.artist).toHaveBeenCalledWith(artist.id);
|
||||||
|
expect(accessTokens.mint).toHaveBeenCalledWith(authToken);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("asking for albums", () => {
|
describe("asking for albums", () => {
|
||||||
@@ -1947,12 +1989,19 @@ describe("api", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when it has similar artists", () => {
|
describe("when it has similar artists, some in the library and some not", () => {
|
||||||
const similar1 = anArtist();
|
const similar1 = anArtist();
|
||||||
const similar2 = anArtist();
|
const similar2 = anArtist();
|
||||||
|
const similar3 = anArtist();
|
||||||
|
const similar4 = anArtist();
|
||||||
|
|
||||||
const artist = anArtist({
|
const artist = anArtist({
|
||||||
similarArtists: [similar1, similar2],
|
similarArtists: [
|
||||||
|
{ ...similar1, inLibrary: true },
|
||||||
|
{ ...similar2, inLibrary: false },
|
||||||
|
{ ...similar3, inLibrary: false },
|
||||||
|
{ ...similar4, inLibrary: true },
|
||||||
|
],
|
||||||
albums: [],
|
albums: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2014,6 +2063,38 @@ describe("api", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("when none of the similar artists are in the library", () => {
|
||||||
|
const relatedArtist1 = anArtist();
|
||||||
|
const relatedArtist2 = anArtist();
|
||||||
|
const artist = anArtist({
|
||||||
|
similarArtists: [
|
||||||
|
{ ...relatedArtist1, inLibrary: false },
|
||||||
|
{ ...relatedArtist2, inLibrary: false },
|
||||||
|
],
|
||||||
|
albums: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
musicLibrary.artist.mockResolvedValue(artist);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not return a RELATED_ARTISTS browse option", async () => {
|
||||||
|
const root = await ws.getExtendedMetadataAsync({
|
||||||
|
id: `artist:${artist.id}`,
|
||||||
|
index: 0,
|
||||||
|
count: 100,
|
||||||
|
});
|
||||||
|
expect(root[0]).toEqual({
|
||||||
|
getExtendedMetadataResult: {
|
||||||
|
// artist has no albums
|
||||||
|
count: "0",
|
||||||
|
index: "0",
|
||||||
|
total: "0",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("asking for a track", () => {
|
describe("asking for a track", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user