mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Fix album scolling so goes past 100 (#44)
This commit is contained in:
107
src/navidrome.ts
107
src/navidrome.ts
@@ -197,12 +197,12 @@ export type GetPlaylistsResponse = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type GetSimilarSongsResponse = {
|
export type GetSimilarSongsResponse = {
|
||||||
similarSongs: { song: song[] }
|
similarSongs: { song: song[] };
|
||||||
}
|
};
|
||||||
|
|
||||||
export type GetTopSongsResponse = {
|
export type GetTopSongsResponse = {
|
||||||
topSongs: { song: song[] }
|
topSongs: { song: song[] };
|
||||||
}
|
};
|
||||||
|
|
||||||
export type GetSongResponse = {
|
export type GetSongResponse = {
|
||||||
song: song;
|
song: song;
|
||||||
@@ -236,7 +236,7 @@ export type getAlbumListParams = {
|
|||||||
genre?: string;
|
genre?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_ALBUM_LIST = 500;
|
export const MAX_ALBUM_LIST = 500;
|
||||||
|
|
||||||
const asTrack = (album: Album, song: song) => ({
|
const asTrack = (album: Album, song: song) => ({
|
||||||
id: song._id,
|
id: song._id,
|
||||||
@@ -248,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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -389,13 +389,16 @@ export class Navidrome implements MusicService {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
getArtists = (credentials: Credentials): Promise<IdName[]> =>
|
getArtists = (
|
||||||
|
credentials: Credentials
|
||||||
|
): Promise<(IdName & { albumCount: number })[]> =>
|
||||||
this.getJSON<GetArtistsResponse>(credentials, "/rest/getArtists")
|
this.getJSON<GetArtistsResponse>(credentials, "/rest/getArtists")
|
||||||
.then((it) => (it.artists.index || []).flatMap((it) => it.artist || []))
|
.then((it) => (it.artists.index || []).flatMap((it) => it.artist || []))
|
||||||
.then((artists) =>
|
.then((artists) =>
|
||||||
artists.map((artist) => ({
|
artists.map((artist) => ({
|
||||||
id: artist._id,
|
id: artist._id,
|
||||||
name: artist._name,
|
name: artist._name,
|
||||||
|
albumCount: Number.parseInt(artist._albumCount),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -403,7 +406,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
|
includeNotPresent: true,
|
||||||
}).then((it) => ({
|
}).then((it) => ({
|
||||||
image: {
|
image: {
|
||||||
small: validate(it.artistInfo.smallImageUrl),
|
small: validate(it.artistInfo.smallImageUrl),
|
||||||
@@ -516,20 +519,29 @@ export class Navidrome implements MusicService {
|
|||||||
})),
|
})),
|
||||||
artist: async (id: string): Promise<Artist> =>
|
artist: async (id: string): Promise<Artist> =>
|
||||||
navidrome.getArtistWithInfo(credentials, id),
|
navidrome.getArtistWithInfo(credentials, id),
|
||||||
albums: (q: AlbumQuery): Promise<Result<AlbumSummary>> =>
|
albums: (q: AlbumQuery): Promise<Result<AlbumSummary>> => {
|
||||||
navidrome
|
return Promise.all([
|
||||||
.getJSON<GetAlbumListResponse>(credentials, "/rest/getAlbumList", {
|
navidrome
|
||||||
...pick(q, "type", "genre"),
|
.getArtists(credentials)
|
||||||
size: Math.min(MAX_ALBUM_LIST, q._count),
|
.then((it) =>
|
||||||
offset: q._index,
|
_.inject(it, (total, artist) => total + artist.albumCount, 0)
|
||||||
})
|
),
|
||||||
.then((response) => response.albumList.album || [])
|
navidrome
|
||||||
.then(navidrome.toAlbumSummary)
|
.getJSON<GetAlbumListResponse>(credentials, "/rest/getAlbumList", {
|
||||||
.then(slice2(q))
|
...pick(q, "type", "genre"),
|
||||||
.then(([page, total]) => ({
|
size: 500,
|
||||||
results: page,
|
offset: q._index,
|
||||||
total: Math.min(MAX_ALBUM_LIST, total),
|
})
|
||||||
})),
|
.then((response) => response.albumList.album || [])
|
||||||
|
.then(navidrome.toAlbumSummary),
|
||||||
|
]).then(([total, albums]) => ({
|
||||||
|
results: albums.slice(0, q._count),
|
||||||
|
total:
|
||||||
|
albums.length == 500
|
||||||
|
? total
|
||||||
|
: q._index + albums.length,
|
||||||
|
}));
|
||||||
|
},
|
||||||
album: (id: string): Promise<Album> =>
|
album: (id: string): Promise<Album> =>
|
||||||
navidrome.getAlbum(credentials, id),
|
navidrome.getAlbum(credentials, id),
|
||||||
genres: () =>
|
genres: () =>
|
||||||
@@ -538,7 +550,7 @@ export class Navidrome implements MusicService {
|
|||||||
.then((it) =>
|
.then((it) =>
|
||||||
pipe(
|
pipe(
|
||||||
it.genres.genre || [],
|
it.genres.genre || [],
|
||||||
A.filter(it => Number.parseInt(it._albumCount) > 0),
|
A.filter((it) => Number.parseInt(it._albumCount) > 0),
|
||||||
A.map((it) => it.__text),
|
A.map((it) => it.__text),
|
||||||
A.sort(ordString),
|
A.sort(ordString),
|
||||||
A.map((it) => ({ id: it, name: it }))
|
A.map((it) => ({ id: it, name: it }))
|
||||||
@@ -712,7 +724,7 @@ export class Navidrome implements MusicService {
|
|||||||
},
|
},
|
||||||
artist: {
|
artist: {
|
||||||
id: entry._artistId,
|
id: entry._artistId,
|
||||||
name: entry._artist
|
name: entry._artist,
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
@@ -744,24 +756,41 @@ export class Navidrome implements MusicService {
|
|||||||
songIndexToRemove: indicies,
|
songIndexToRemove: indicies,
|
||||||
})
|
})
|
||||||
.then((_) => true),
|
.then((_) => true),
|
||||||
similarSongs: async (id: string) => navidrome
|
similarSongs: async (id: string) =>
|
||||||
.getJSON<GetSimilarSongsResponse>(credentials, "/rest/getSimilarSongs", { id, count: 50 })
|
navidrome
|
||||||
.then((it) => (it.similarSongs.song || []))
|
.getJSON<GetSimilarSongsResponse>(
|
||||||
.then(songs =>
|
credentials,
|
||||||
Promise.all(
|
"/rest/getSimilarSongs",
|
||||||
songs.map((song) => navidrome.getAlbum(credentials, song._albumId).then(album => asTrack(album, song)))
|
{ id, count: 50 }
|
||||||
)
|
)
|
||||||
),
|
.then((it) => it.similarSongs.song || [])
|
||||||
topSongs: async (artistId: string) => navidrome
|
.then((songs) =>
|
||||||
.getArtist(credentials, artistId)
|
|
||||||
.then(({ name }) => navidrome
|
|
||||||
.getJSON<GetTopSongsResponse>(credentials, "/rest/getTopSongs", { artist: name, count: 50 })
|
|
||||||
.then((it) => (it.topSongs.song || []))
|
|
||||||
.then(songs =>
|
|
||||||
Promise.all(
|
Promise.all(
|
||||||
songs.map((song) => navidrome.getAlbum(credentials, song._albumId).then(album => asTrack(album, song)))
|
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);
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import {
|
|||||||
AuthSuccess,
|
AuthSuccess,
|
||||||
Images,
|
Images,
|
||||||
albumToAlbumSummary,
|
albumToAlbumSummary,
|
||||||
range,
|
|
||||||
asArtistAlbumPairs,
|
asArtistAlbumPairs,
|
||||||
Track,
|
Track,
|
||||||
AlbumSummary,
|
AlbumSummary,
|
||||||
@@ -381,6 +380,50 @@ const searchResult3 = ({
|
|||||||
</searchResult3>
|
</searchResult3>
|
||||||
</subsonic-response>`;
|
</subsonic-response>`;
|
||||||
|
|
||||||
|
const getArtistsXml = (artists: Artist[]) => {
|
||||||
|
const as: Artist[] = [];
|
||||||
|
const bs: Artist[] = [];
|
||||||
|
const cs: Artist[] = [];
|
||||||
|
const rest: Artist[] = [];
|
||||||
|
artists.forEach((it) => {
|
||||||
|
const firstChar = it.name.toLowerCase()[0];
|
||||||
|
switch (firstChar) {
|
||||||
|
case "a":
|
||||||
|
as.push(it);
|
||||||
|
break;
|
||||||
|
case "b":
|
||||||
|
bs.push(it);
|
||||||
|
break;
|
||||||
|
case "c":
|
||||||
|
cs.push(it);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
rest.push(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const artistSummaryXml = (artist: Artist) =>
|
||||||
|
`<artist id="${artist.id}" name="${artist.name}" albumCount="${artist.albums.length}"></artist>`;
|
||||||
|
|
||||||
|
return `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)">
|
||||||
|
<artists lastModified="1614586749000" ignoredArticles="The El La Los Las Le Les Os As O A">
|
||||||
|
<index name="A">
|
||||||
|
${as.map(artistSummaryXml).join("")}
|
||||||
|
</index>
|
||||||
|
<index name="B">
|
||||||
|
${bs.map(artistSummaryXml).join("")}
|
||||||
|
</index>
|
||||||
|
<index name="C">
|
||||||
|
${cs.map(artistSummaryXml).join("")}
|
||||||
|
</index>
|
||||||
|
<index name="D-Z">
|
||||||
|
${rest.map(artistSummaryXml).join("")}
|
||||||
|
</index>
|
||||||
|
</artists>
|
||||||
|
</subsonic-response>`;
|
||||||
|
};
|
||||||
|
|
||||||
const EMPTY = `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)"></subsonic-response>`;
|
const EMPTY = `<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>`;
|
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>`;
|
||||||
@@ -1090,34 +1133,19 @@ describe("Navidrome", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("when there are artists", () => {
|
describe("when there are artists", () => {
|
||||||
const artist1 = anArtist();
|
const artist1 = anArtist({ name: "A Artist" });
|
||||||
const artist2 = anArtist();
|
const artist2 = anArtist({ name: "B Artist" });
|
||||||
const artist3 = anArtist();
|
const artist3 = anArtist({ name: "C Artist" });
|
||||||
const artist4 = anArtist();
|
const artist4 = anArtist({ name: "D Artist" });
|
||||||
|
const artists = [artist1, artist2, artist3, artist4];
|
||||||
const getArtistsXml = `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)">
|
|
||||||
<artists lastModified="1614586749000" ignoredArticles="The El La Los Las Le Les Os As O A">
|
|
||||||
<index name="#">
|
|
||||||
<artist id="${artist1.id}" name="${artist1.name}" albumCount="22"></artist>
|
|
||||||
<artist id="${artist2.id}" name="${artist2.name}" albumCount="9"></artist>
|
|
||||||
</index>
|
|
||||||
<index name="A">
|
|
||||||
<artist id="${artist3.id}" name="${artist3.name}" albumCount="2"></artist>
|
|
||||||
</index>
|
|
||||||
<index name="B">
|
|
||||||
<artist id="${artist4.id}" name="${artist4.name}" albumCount="2"></artist>
|
|
||||||
</index>
|
|
||||||
<index name="C">
|
|
||||||
<!-- intentionally no artists -->
|
|
||||||
</index>
|
|
||||||
</artists>
|
|
||||||
</subsonic-response>`;
|
|
||||||
|
|
||||||
describe("when no paging is in effect", () => {
|
describe("when no paging is in effect", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockGET
|
mockGET
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(getArtistsXml)));
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml(artists)))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return all the artists", async () => {
|
it("should return all the artists", async () => {
|
||||||
@@ -1150,7 +1178,9 @@ describe("Navidrome", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockGET
|
mockGET
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(getArtistsXml)));
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml(artists)))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return only the correct page of artists", async () => {
|
it("should return only the correct page of artists", async () => {
|
||||||
@@ -1188,11 +1218,15 @@ describe("Navidrome", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockGET
|
mockGET
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml([artist])))
|
||||||
|
)
|
||||||
.mockImplementationOnce(() =>
|
.mockImplementationOnce(() =>
|
||||||
Promise.resolve(
|
Promise.resolve(
|
||||||
ok(
|
ok(
|
||||||
albumListXml([
|
albumListXml([
|
||||||
[artist, album1],
|
[artist, album1],
|
||||||
|
// album2 is not Pop
|
||||||
[artist, album3],
|
[artist, album3],
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
@@ -1203,7 +1237,7 @@ describe("Navidrome", () => {
|
|||||||
it("should pass the filter to navidrome", async () => {
|
it("should pass the filter to navidrome", async () => {
|
||||||
const q: AlbumQuery = {
|
const q: AlbumQuery = {
|
||||||
_index: 0,
|
_index: 0,
|
||||||
_count: 500,
|
_count: 100,
|
||||||
genre: "Pop",
|
genre: "Pop",
|
||||||
type: "byGenre",
|
type: "byGenre",
|
||||||
};
|
};
|
||||||
@@ -1218,6 +1252,11 @@ describe("Navidrome", () => {
|
|||||||
total: 2,
|
total: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
@@ -1235,12 +1274,16 @@ describe("Navidrome", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockGET
|
mockGET
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml([artist])))
|
||||||
|
)
|
||||||
.mockImplementationOnce(() =>
|
.mockImplementationOnce(() =>
|
||||||
Promise.resolve(
|
Promise.resolve(
|
||||||
ok(
|
ok(
|
||||||
albumListXml([
|
albumListXml([
|
||||||
[artist, album3],
|
[artist, album3],
|
||||||
[artist, album2],
|
[artist, album2],
|
||||||
|
[artist, album1],
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1248,7 +1291,7 @@ describe("Navidrome", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should pass the filter to navidrome", async () => {
|
it("should pass the filter to navidrome", async () => {
|
||||||
const q: AlbumQuery = { _index: 0, _count: 500, type: "newest" };
|
const q: AlbumQuery = { _index: 0, _count: 100, type: "newest" };
|
||||||
const result = await navidrome
|
const result = await navidrome
|
||||||
.generateToken({ username, password })
|
.generateToken({ username, password })
|
||||||
.then((it) => it as AuthSuccess)
|
.then((it) => it as AuthSuccess)
|
||||||
@@ -1256,8 +1299,13 @@ describe("Navidrome", () => {
|
|||||||
.then((it) => it.albums(q));
|
.then((it) => it.albums(q));
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
results: [album3, album2].map(albumToAlbumSummary),
|
results: [album3, album2, album1].map(albumToAlbumSummary),
|
||||||
total: 2,
|
total: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
@@ -1276,12 +1324,16 @@ describe("Navidrome", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockGET
|
mockGET
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml([artist])))
|
||||||
|
)
|
||||||
.mockImplementationOnce(() =>
|
.mockImplementationOnce(() =>
|
||||||
Promise.resolve(
|
Promise.resolve(
|
||||||
ok(
|
ok(
|
||||||
albumListXml([
|
albumListXml([
|
||||||
[artist, album3],
|
[artist, album3],
|
||||||
[artist, album2],
|
[artist, album2],
|
||||||
|
// album1 never played
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -1289,7 +1341,7 @@ describe("Navidrome", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should pass the filter to navidrome", async () => {
|
it("should pass the filter to navidrome", async () => {
|
||||||
const q: AlbumQuery = { _index: 0, _count: 500, type: "recent" };
|
const q: AlbumQuery = { _index: 0, _count: 100, type: "recent" };
|
||||||
const result = await navidrome
|
const result = await navidrome
|
||||||
.generateToken({ username, password })
|
.generateToken({ username, password })
|
||||||
.then((it) => it as AuthSuccess)
|
.then((it) => it as AuthSuccess)
|
||||||
@@ -1301,6 +1353,11 @@ describe("Navidrome", () => {
|
|||||||
total: 2,
|
total: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
@@ -1318,12 +1375,18 @@ describe("Navidrome", () => {
|
|||||||
mockGET
|
mockGET
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
.mockImplementationOnce(() =>
|
.mockImplementationOnce(() =>
|
||||||
Promise.resolve(ok(albumListXml([[artist, album2]])))
|
Promise.resolve(ok(getArtistsXml([artist])))
|
||||||
|
)
|
||||||
|
.mockImplementationOnce(
|
||||||
|
() =>
|
||||||
|
// album1 never played
|
||||||
|
Promise.resolve(ok(albumListXml([[artist, album2]])))
|
||||||
|
// album3 never played
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should pass the filter to navidrome", async () => {
|
it("should pass the filter to navidrome", async () => {
|
||||||
const q: AlbumQuery = { _index: 0, _count: 500, type: "frequent" };
|
const q: AlbumQuery = { _index: 0, _count: 100, type: "frequent" };
|
||||||
const result = await navidrome
|
const result = await navidrome
|
||||||
.generateToken({ username, password })
|
.generateToken({ username, password })
|
||||||
.then((it) => it as AuthSuccess)
|
.then((it) => it as AuthSuccess)
|
||||||
@@ -1335,6 +1398,11 @@ describe("Navidrome", () => {
|
|||||||
total: 1,
|
total: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
@@ -1349,16 +1417,19 @@ describe("Navidrome", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("when the artist has only 1 album", () => {
|
describe("when the artist has only 1 album", () => {
|
||||||
const artist1 = anArtist({
|
const artist = anArtist({
|
||||||
name: "one hit wonder",
|
name: "one hit wonder",
|
||||||
albums: [anAlbum({ genre: asGenre("Pop") })],
|
albums: [anAlbum({ genre: asGenre("Pop") })],
|
||||||
});
|
});
|
||||||
const artists = [artist1];
|
const artists = [artist];
|
||||||
const albums = artists.flatMap((artist) => artist.albums);
|
const albums = artists.flatMap((artist) => artist.albums);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockGET
|
mockGET
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml(artists)))
|
||||||
|
)
|
||||||
.mockImplementationOnce(() =>
|
.mockImplementationOnce(() =>
|
||||||
Promise.resolve(ok(albumListXml(asArtistAlbumPairs(artists))))
|
Promise.resolve(ok(albumListXml(asArtistAlbumPairs(artists))))
|
||||||
);
|
);
|
||||||
@@ -1367,7 +1438,7 @@ describe("Navidrome", () => {
|
|||||||
it("should return the album", async () => {
|
it("should return the album", async () => {
|
||||||
const q: AlbumQuery = {
|
const q: AlbumQuery = {
|
||||||
_index: 0,
|
_index: 0,
|
||||||
_count: 500,
|
_count: 100,
|
||||||
type: "alphabeticalByArtist",
|
type: "alphabeticalByArtist",
|
||||||
};
|
};
|
||||||
const result = await navidrome
|
const result = await navidrome
|
||||||
@@ -1381,6 +1452,11 @@ describe("Navidrome", () => {
|
|||||||
total: 1,
|
total: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
@@ -1393,17 +1469,20 @@ describe("Navidrome", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when the artist has only no albums", () => {
|
describe("when the only artist has no albums", () => {
|
||||||
const artist1 = anArtist({
|
const artist = anArtist({
|
||||||
name: "one hit wonder",
|
name: "no hit wonder",
|
||||||
albums: [],
|
albums: [],
|
||||||
});
|
});
|
||||||
const artists = [artist1];
|
const artists = [artist];
|
||||||
const albums = artists.flatMap((artist) => artist.albums);
|
const albums = artists.flatMap((artist) => artist.albums);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockGET
|
mockGET
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml(artists)))
|
||||||
|
)
|
||||||
.mockImplementationOnce(() =>
|
.mockImplementationOnce(() =>
|
||||||
Promise.resolve(ok(albumListXml(asArtistAlbumPairs(artists))))
|
Promise.resolve(ok(albumListXml(asArtistAlbumPairs(artists))))
|
||||||
);
|
);
|
||||||
@@ -1412,7 +1491,7 @@ describe("Navidrome", () => {
|
|||||||
it("should return the album", async () => {
|
it("should return the album", async () => {
|
||||||
const q: AlbumQuery = {
|
const q: AlbumQuery = {
|
||||||
_index: 0,
|
_index: 0,
|
||||||
_count: 500,
|
_count: 100,
|
||||||
type: "alphabeticalByArtist",
|
type: "alphabeticalByArtist",
|
||||||
};
|
};
|
||||||
const result = await navidrome
|
const result = await navidrome
|
||||||
@@ -1426,6 +1505,11 @@ describe("Navidrome", () => {
|
|||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
@@ -1438,7 +1522,7 @@ describe("Navidrome", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when there are less than 500 albums", () => {
|
describe("when there are 6 albums in total", () => {
|
||||||
const genre1 = asGenre("genre1");
|
const genre1 = asGenre("genre1");
|
||||||
const genre2 = asGenre("genre2");
|
const genre2 = asGenre("genre2");
|
||||||
const genre3 = asGenre("genre3");
|
const genre3 = asGenre("genre3");
|
||||||
@@ -1446,35 +1530,36 @@ describe("Navidrome", () => {
|
|||||||
const artist1 = anArtist({
|
const artist1 = anArtist({
|
||||||
name: "abba",
|
name: "abba",
|
||||||
albums: [
|
albums: [
|
||||||
anAlbum({ genre: genre1 }),
|
anAlbum({ name: "album1", genre: genre1 }),
|
||||||
anAlbum({ genre: genre2 }),
|
anAlbum({ name: "album2", genre: genre2 }),
|
||||||
anAlbum({ genre: genre3 }),
|
anAlbum({ name: "album3", genre: genre3 }),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const artist2 = anArtist({
|
const artist2 = anArtist({
|
||||||
name: "babba",
|
name: "babba",
|
||||||
albums: [
|
albums: [
|
||||||
anAlbum({ genre: genre1 }),
|
anAlbum({ name: "album4", genre: genre1 }),
|
||||||
anAlbum({ genre: genre2 }),
|
anAlbum({ name: "album5", genre: genre2 }),
|
||||||
anAlbum({ genre: genre3 }),
|
anAlbum({ name: "album6", genre: genre3 }),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const artists = [artist1, artist2];
|
const artists = [artist1, artist2];
|
||||||
const albums = artists.flatMap((artist) => artist.albums);
|
const albums = artists.flatMap((artist) => artist.albums);
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockGET
|
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
|
||||||
.mockImplementationOnce(() =>
|
|
||||||
Promise.resolve(ok(albumListXml(asArtistAlbumPairs(artists))))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("querying for all of them", () => {
|
describe("querying for all of them", () => {
|
||||||
it("should return all of them with corrent paging information", async () => {
|
it("should return all of them with corrent paging information", async () => {
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml(artists)))
|
||||||
|
)
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(albumListXml(asArtistAlbumPairs(artists))))
|
||||||
|
);
|
||||||
|
|
||||||
const q: AlbumQuery = {
|
const q: AlbumQuery = {
|
||||||
_index: 0,
|
_index: 0,
|
||||||
_count: 500,
|
_count: 100,
|
||||||
type: "alphabeticalByArtist",
|
type: "alphabeticalByArtist",
|
||||||
};
|
};
|
||||||
const result = await navidrome
|
const result = await navidrome
|
||||||
@@ -1488,6 +1573,11 @@ describe("Navidrome", () => {
|
|||||||
total: 6,
|
total: 6,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
@@ -1502,6 +1592,25 @@ describe("Navidrome", () => {
|
|||||||
|
|
||||||
describe("querying for a page of them", () => {
|
describe("querying for a page of them", () => {
|
||||||
it("should return the page with the corrent paging information", async () => {
|
it("should return the page with the corrent paging information", async () => {
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml(artists)))
|
||||||
|
)
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(
|
||||||
|
ok(
|
||||||
|
albumListXml([
|
||||||
|
[artist1, artist1.albums[2]!],
|
||||||
|
[artist2, artist2.albums[0]!],
|
||||||
|
// due to pre-fetch will get next 2 albums also
|
||||||
|
[artist2, artist2.albums[1]!],
|
||||||
|
[artist2, artist2.albums[2]!],
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const q: AlbumQuery = {
|
const q: AlbumQuery = {
|
||||||
_index: 2,
|
_index: 2,
|
||||||
_count: 2,
|
_count: 2,
|
||||||
@@ -1514,15 +1623,20 @@ describe("Navidrome", () => {
|
|||||||
.then((it) => it.albums(q));
|
.then((it) => it.albums(q));
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
results: [albums[2], albums[3]],
|
results: [artist1.albums[2], artist2.albums[0]],
|
||||||
total: 6,
|
total: 6,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
params: asURLSearchParams({
|
params: asURLSearchParams({
|
||||||
...authParams,
|
...authParams,
|
||||||
type: "alphabeticalByArtist",
|
type: "alphabeticalByArtist",
|
||||||
size: 2,
|
size: 500,
|
||||||
offset: 2,
|
offset: 2,
|
||||||
}),
|
}),
|
||||||
headers,
|
headers,
|
||||||
@@ -1531,60 +1645,406 @@ describe("Navidrome", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when there are more than 500 albums", () => {
|
describe("when the number of albums reported by getArtists does not match that of getAlbums", () => {
|
||||||
const first500Albums = range(500).map((i) =>
|
const genre = asGenre("lofi");
|
||||||
anAlbum({ name: `album ${i}`, genre: asGenre(`genre ${i}`) })
|
|
||||||
);
|
|
||||||
const artist = anArtist({
|
|
||||||
name: "> 500 albums",
|
|
||||||
albums: [...first500Albums, anAlbum(), anAlbum(), anAlbum()],
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
const album1 = anAlbum({ name: "album1", genre });
|
||||||
mockGET
|
const album2 = anAlbum({ name: "album2", genre });
|
||||||
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
const album3 = anAlbum({ name: "album3", genre });
|
||||||
.mockImplementationOnce(() =>
|
const album4 = anAlbum({ name: "album4", genre });
|
||||||
Promise.resolve(
|
const album5 = anAlbum({ name: "album5", genre });
|
||||||
ok(
|
|
||||||
albumListXml(
|
// the artists have 5 albums in the getArtists endpoint
|
||||||
first500Albums.map(
|
const artist1 = anArtist({
|
||||||
(album) => [artist, album] as [Artist, Album]
|
albums: [
|
||||||
|
album1,
|
||||||
|
album2,
|
||||||
|
album3,
|
||||||
|
album4,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const artist2 = anArtist({
|
||||||
|
albums: [
|
||||||
|
album5,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const artists = [artist1, artist2];
|
||||||
|
|
||||||
|
describe("when the number of albums returned from getAlbums is less the number of albums in the getArtists endpoint", () => {
|
||||||
|
describe("when the query comes back on 1 page", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml(artists)))
|
||||||
|
)
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(
|
||||||
|
ok(
|
||||||
|
albumListXml([
|
||||||
|
[artist1, album1],
|
||||||
|
[artist1, album2],
|
||||||
|
[artist1, album3],
|
||||||
|
// album4 is missing from the albums end point for some reason
|
||||||
|
[artist2, album5],
|
||||||
|
])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("querying for all of them", () => {
|
|
||||||
it("will return only the first 500 with the correct paging information", async () => {
|
|
||||||
const q: AlbumQuery = {
|
|
||||||
_index: 0,
|
|
||||||
_count: 1000,
|
|
||||||
type: "alphabeticalByArtist",
|
|
||||||
};
|
|
||||||
const result = await navidrome
|
|
||||||
.generateToken({ username, password })
|
|
||||||
.then((it) => it as AuthSuccess)
|
|
||||||
.then((it) => navidrome.login(it.authToken))
|
|
||||||
.then((it) => it.albums(q));
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
|
||||||
results: first500Albums.map(albumToAlbumSummary),
|
|
||||||
total: 500,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
it("should return the page of albums, updating the total to be accurate", async () => {
|
||||||
params: asURLSearchParams({
|
const q: AlbumQuery = {
|
||||||
...authParams,
|
_index: 0,
|
||||||
|
_count: 100,
|
||||||
type: "alphabeticalByArtist",
|
type: "alphabeticalByArtist",
|
||||||
size: 500,
|
};
|
||||||
offset: 0,
|
const result = await navidrome
|
||||||
}),
|
.generateToken({ username, password })
|
||||||
headers,
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.albums(q));
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
results: [
|
||||||
|
album1,
|
||||||
|
album2,
|
||||||
|
album3,
|
||||||
|
album5,
|
||||||
|
],
|
||||||
|
total: 4,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
|
params: asURLSearchParams({
|
||||||
|
...authParams,
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
size: 500,
|
||||||
|
offset: q._index,
|
||||||
|
}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("when the query is for the first page", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml(artists)))
|
||||||
|
)
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(
|
||||||
|
ok(
|
||||||
|
albumListXml([
|
||||||
|
[artist1, album1],
|
||||||
|
[artist1, album2],
|
||||||
|
// album3 & album5 is returned due to the prefetch
|
||||||
|
[artist1, album3],
|
||||||
|
// album4 is missing from the albums end point for some reason
|
||||||
|
[artist2, album5],
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter out the pre-fetched albums", async () => {
|
||||||
|
const q: AlbumQuery = {
|
||||||
|
_index: 0,
|
||||||
|
_count: 2,
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
};
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.albums(q));
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
results: [
|
||||||
|
album1,
|
||||||
|
album2,
|
||||||
|
],
|
||||||
|
total: 4,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
|
params: asURLSearchParams({
|
||||||
|
...authParams,
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
size: 500,
|
||||||
|
offset: q._index,
|
||||||
|
}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the query is for the last page only", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml(artists)))
|
||||||
|
)
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(
|
||||||
|
ok(
|
||||||
|
albumListXml([
|
||||||
|
// album1 is on the first page
|
||||||
|
// album2 is on the first page
|
||||||
|
[artist1, album3],
|
||||||
|
// album4 is missing from the albums end point for some reason
|
||||||
|
[artist2, album5],
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the last page of albums, updating the total to be accurate", async () => {
|
||||||
|
const q: AlbumQuery = {
|
||||||
|
_index: 2,
|
||||||
|
_count: 100,
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
};
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.albums(q));
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
results: [
|
||||||
|
album3,
|
||||||
|
album5,
|
||||||
|
],
|
||||||
|
total: 4,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
|
params: asURLSearchParams({
|
||||||
|
...authParams,
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
size: 500,
|
||||||
|
offset: q._index,
|
||||||
|
}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("when the number of albums returned from getAlbums is more than the number of albums in the getArtists endpoint", () => {
|
||||||
|
describe("when the query comes back on 1 page", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml([
|
||||||
|
// artist1 has lost 2 albums on the getArtists end point
|
||||||
|
{ ...artist1, albums: [album1, album2] },
|
||||||
|
artist2,
|
||||||
|
])))
|
||||||
|
)
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(
|
||||||
|
ok(
|
||||||
|
albumListXml([
|
||||||
|
[artist1, album1],
|
||||||
|
[artist1, album2],
|
||||||
|
[artist1, album3],
|
||||||
|
[artist1, album4],
|
||||||
|
[artist2, album5],
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the page of albums, updating the total to be accurate", async () => {
|
||||||
|
const q: AlbumQuery = {
|
||||||
|
_index: 0,
|
||||||
|
_count: 100,
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
};
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.albums(q));
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
results: [
|
||||||
|
album1,
|
||||||
|
album2,
|
||||||
|
album3,
|
||||||
|
album4,
|
||||||
|
album5,
|
||||||
|
],
|
||||||
|
total: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
|
params: asURLSearchParams({
|
||||||
|
...authParams,
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
size: 500,
|
||||||
|
offset: q._index,
|
||||||
|
}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the query is for the first page", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml([
|
||||||
|
// artist1 has lost 2 albums on the getArtists end point
|
||||||
|
{ ...artist1, albums: [album1, album2] },
|
||||||
|
artist2,
|
||||||
|
])))
|
||||||
|
)
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(
|
||||||
|
ok(
|
||||||
|
albumListXml([
|
||||||
|
[artist1, album1],
|
||||||
|
[artist1, album2],
|
||||||
|
[artist1, album3],
|
||||||
|
[artist1, album4],
|
||||||
|
[artist2, album5],
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter out the pre-fetched albums", async () => {
|
||||||
|
const q: AlbumQuery = {
|
||||||
|
_index: 0,
|
||||||
|
_count: 2,
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
};
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.albums(q));
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
results: [
|
||||||
|
album1,
|
||||||
|
album2,
|
||||||
|
],
|
||||||
|
total: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
|
params: asURLSearchParams({
|
||||||
|
...authParams,
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
size: 500,
|
||||||
|
offset: q._index,
|
||||||
|
}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the query is for the last page only", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(getArtistsXml([
|
||||||
|
// artist1 has lost 2 albums on the getArtists end point
|
||||||
|
{ ...artist1, albums: [album1, album2] },
|
||||||
|
artist2,
|
||||||
|
])))
|
||||||
|
)
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(
|
||||||
|
ok(
|
||||||
|
albumListXml([
|
||||||
|
[artist1, album3],
|
||||||
|
[artist1, album4],
|
||||||
|
[artist2, album5],
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the last page of albums, updating the total to be accurate", async () => {
|
||||||
|
const q: AlbumQuery = {
|
||||||
|
_index: 2,
|
||||||
|
_count: 100,
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
};
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.albums(q));
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
results: [
|
||||||
|
album3,
|
||||||
|
album4,
|
||||||
|
album5,
|
||||||
|
],
|
||||||
|
total: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtists`, {
|
||||||
|
params: asURLSearchParams(authParams),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
|
params: asURLSearchParams({
|
||||||
|
...authParams,
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
size: 500,
|
||||||
|
offset: q._index,
|
||||||
|
}),
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user