mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Query albums by genre
This commit is contained in:
@@ -74,7 +74,26 @@ export type ArtistQuery = Paging
|
|||||||
|
|
||||||
export type AlbumQuery = Paging & {
|
export type AlbumQuery = Paging & {
|
||||||
artistId?: string
|
artistId?: string
|
||||||
|
genre?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const artistToArtistSummary = (
|
||||||
|
it: Artist
|
||||||
|
): ArtistSummary => ({
|
||||||
|
id: it.id,
|
||||||
|
name: it.name,
|
||||||
|
image: it.image,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const albumToAlbumSummary = (
|
||||||
|
it: Album
|
||||||
|
): AlbumSummary => ({
|
||||||
|
id: it.id,
|
||||||
|
name: it.name,
|
||||||
|
year: it.year,
|
||||||
|
genre: it.genre,
|
||||||
|
});
|
||||||
|
|
||||||
export interface MusicService {
|
export interface MusicService {
|
||||||
generateToken(credentials: Credentials): Promise<AuthSuccess | AuthFailure>;
|
generateToken(credentials: Credentials): Promise<AuthSuccess | AuthFailure>;
|
||||||
login(authToken: string): Promise<MusicLibrary>;
|
login(authToken: string): Promise<MusicLibrary>;
|
||||||
@@ -84,5 +103,5 @@ export interface MusicLibrary {
|
|||||||
artists(q: ArtistQuery): Promise<Result<ArtistSummary>>;
|
artists(q: ArtistQuery): Promise<Result<ArtistSummary>>;
|
||||||
artist(id: string): Promise<Artist>;
|
artist(id: string): Promise<Artist>;
|
||||||
albums(q: AlbumQuery): Promise<Result<AlbumSummary>>;
|
albums(q: AlbumQuery): Promise<Result<AlbumSummary>>;
|
||||||
// album(id: string): Promise<Album>;
|
album(id: string): Promise<Album>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { option as O } from "fp-ts";
|
||||||
|
import { pipe } from "fp-ts/lib/function";
|
||||||
import { Md5 } from "ts-md5/dist/md5";
|
import { Md5 } from "ts-md5/dist/md5";
|
||||||
import {
|
import {
|
||||||
Credentials,
|
Credentials,
|
||||||
@@ -19,6 +21,7 @@ import axios from "axios";
|
|||||||
import { Encryption } from "./encryption";
|
import { Encryption } from "./encryption";
|
||||||
import randomString from "./random_string";
|
import randomString from "./random_string";
|
||||||
|
|
||||||
|
|
||||||
export const t = (password: string, s: string) =>
|
export const t = (password: string, s: string) =>
|
||||||
Md5.hashStr(`${password}${s}`);
|
Md5.hashStr(`${password}${s}`);
|
||||||
|
|
||||||
@@ -66,6 +69,12 @@ export type GetArtistsResponse = SubsonicResponse & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetAlbumListResponse = SubsonicResponse & {
|
||||||
|
albumList: {
|
||||||
|
album: album[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type SubsonicError = SubsonicResponse & {
|
export type SubsonicError = SubsonicResponse & {
|
||||||
error: {
|
error: {
|
||||||
_code: string;
|
_code: string;
|
||||||
@@ -105,6 +114,17 @@ export type IdName = {
|
|||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type getAlbumListParams = {
|
||||||
|
type: string,
|
||||||
|
size?: number;
|
||||||
|
offet?: number;
|
||||||
|
fromYear?: string,
|
||||||
|
toYear?: string,
|
||||||
|
genre?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_ALBUM_LIST = 500;
|
||||||
|
|
||||||
export class Navidrome implements MusicService {
|
export class Navidrome implements MusicService {
|
||||||
url: string;
|
url: string;
|
||||||
encryption: Encryption;
|
encryption: Encryption;
|
||||||
@@ -235,8 +255,36 @@ export class Navidrome implements MusicService {
|
|||||||
image: artistInfo.image,
|
image: artistInfo.image,
|
||||||
albums: artist.albums,
|
albums: artist.albums,
|
||||||
})),
|
})),
|
||||||
albums: (_: AlbumQuery): Promise<Result<AlbumSummary>> => {
|
albums: (q: AlbumQuery): Promise<Result<AlbumSummary>> => {
|
||||||
return Promise.resolve({ results: [], total: 0 });
|
const p = pipe(
|
||||||
|
O.fromNullable(q.genre),
|
||||||
|
O.map<string, getAlbumListParams>(genre => ({ type: "byGenre", genre })),
|
||||||
|
O.getOrElse<getAlbumListParams>(() => ({ type: "alphabeticalByArtist" })),
|
||||||
|
)
|
||||||
|
|
||||||
|
return navidrome
|
||||||
|
.get<GetAlbumListResponse>(credentials, "/rest/getAlbumList", {
|
||||||
|
...p,
|
||||||
|
size: MAX_ALBUM_LIST,
|
||||||
|
offset: 0,
|
||||||
|
})
|
||||||
|
.then((response) => response.albumList.album)
|
||||||
|
.then((albumList) =>
|
||||||
|
albumList.map((album) => ({
|
||||||
|
id: album._id,
|
||||||
|
name: album._name,
|
||||||
|
year: album._year,
|
||||||
|
genre: album._genre,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.then(slice2(q))
|
||||||
|
.then(([page, total]) => ({
|
||||||
|
results: page,
|
||||||
|
total: Math.min(MAX_ALBUM_LIST, total),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
album: (_: string): Promise<Album> => {
|
||||||
|
return Promise.reject("not implemented");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
23
src/smapi.ts
23
src/smapi.ts
@@ -6,7 +6,12 @@ import path from "path";
|
|||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
|
|
||||||
import { LinkCodes } from "./link_codes";
|
import { LinkCodes } from "./link_codes";
|
||||||
import { AlbumSummary, MusicLibrary, MusicService, slice2 } from "./music_service";
|
import {
|
||||||
|
AlbumSummary,
|
||||||
|
MusicLibrary,
|
||||||
|
MusicService,
|
||||||
|
slice2,
|
||||||
|
} from "./music_service";
|
||||||
|
|
||||||
export const LOGIN_ROUTE = "/login";
|
export const LOGIN_ROUTE = "/login";
|
||||||
export const SOAP_PATH = "/ws/sonos";
|
export const SOAP_PATH = "/ws/sonos";
|
||||||
@@ -250,14 +255,14 @@ function bindSmapiSoapServiceToExpress(
|
|||||||
total: result.total,
|
total: result.total,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
case "albums":
|
case "albums":
|
||||||
return await musicLibrary.albums(paging).then((result) =>
|
return await musicLibrary.albums(paging).then((result) =>
|
||||||
getMetadataResult({
|
getMetadataResult({
|
||||||
mediaCollection: result.results.map(album),
|
mediaCollection: result.results.map(album),
|
||||||
index: paging._index,
|
index: paging._index,
|
||||||
total: result.total,
|
total: result.total,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
case "artist":
|
case "artist":
|
||||||
return await musicLibrary
|
return await musicLibrary
|
||||||
.artist(typeId!)
|
.artist(typeId!)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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 } from "../src/music_service";
|
||||||
|
|
||||||
const randomInt = (max: number) => Math.floor(Math.random() * 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)}`;
|
||||||
|
|
||||||
export const aService = (fields: Partial<Service> = {}): Service => ({
|
export const aService = (fields: Partial<Service> = {}): Service => ({
|
||||||
@@ -73,25 +73,26 @@ export function anArtist(fields: Partial<Artist> = {}): Artist {
|
|||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: `Artist ${id}`,
|
name: `Artist ${id}`,
|
||||||
albums: [],
|
albums: [anAlbum(), anAlbum(), anAlbum()],
|
||||||
image: {
|
image: {
|
||||||
small: undefined,
|
small: `/artist/art/${id}/small`,
|
||||||
medium: undefined,
|
medium: `/artist/art/${id}/small`,
|
||||||
large: undefined
|
large: `/artist/art/${id}/large`,
|
||||||
},
|
},
|
||||||
...fields
|
...fields,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function anAlbum(fields: Partial<Album> = {}): Album {
|
export function anAlbum(fields: Partial<Album> = {}): Album {
|
||||||
|
const genres = ["Metal", "Pop", "Rock", "Hip-Hop"];
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: `Album ${id}`,
|
name: `Album ${id}`,
|
||||||
genre: "Metal",
|
genre: genres[randomInt(genres.length)],
|
||||||
year: "1900",
|
year: `19${randomInt(99)}`,
|
||||||
...fields
|
...fields,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BLONDIE: Artist = {
|
export const BLONDIE: Artist = {
|
||||||
@@ -168,6 +169,6 @@ export const METALLICA: Artist = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ALL_ARTISTS = [BOB_MARLEY, BLONDIE, MADONNA, METALLICA]
|
export const ALL_ARTISTS = [BOB_MARLEY, BLONDIE, MADONNA, METALLICA];
|
||||||
|
|
||||||
export const ALL_ALBUMS = ALL_ARTISTS.flatMap(it => it.albums || []);
|
export const ALL_ALBUMS = ALL_ARTISTS.flatMap((it) => it.albums || []);
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
|
import { InMemoryMusicService } from "./in_memory_music_service";
|
||||||
import {
|
import {
|
||||||
InMemoryMusicService,
|
AuthSuccess,
|
||||||
|
MusicLibrary,
|
||||||
artistToArtistSummary,
|
artistToArtistSummary,
|
||||||
} from "./in_memory_music_service";
|
albumToAlbumSummary,
|
||||||
import { AuthSuccess, MusicLibrary } from "../src/music_service";
|
} from "../src/music_service";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
import {
|
import { anArtist, anAlbum } from "./builders";
|
||||||
BOB_MARLEY,
|
|
||||||
MADONNA,
|
|
||||||
BLONDIE,
|
|
||||||
METALLICA,
|
|
||||||
ALL_ALBUMS,
|
|
||||||
} from "./builders";
|
|
||||||
|
|
||||||
describe("InMemoryMusicService", () => {
|
describe("InMemoryMusicService", () => {
|
||||||
const service = new InMemoryMusicService();
|
const service = new InMemoryMusicService();
|
||||||
@@ -48,10 +44,19 @@ describe("InMemoryMusicService", () => {
|
|||||||
|
|
||||||
describe("artistToArtistSummary", () => {
|
describe("artistToArtistSummary", () => {
|
||||||
it("should map fields correctly", () => {
|
it("should map fields correctly", () => {
|
||||||
expect(artistToArtistSummary(BOB_MARLEY)).toEqual({
|
const artist = anArtist({
|
||||||
id: BOB_MARLEY.id,
|
id: uuid(),
|
||||||
name: BOB_MARLEY.name,
|
name: "The Artist",
|
||||||
image: BOB_MARLEY.image,
|
image: {
|
||||||
|
small: "/path/to/small/jpg",
|
||||||
|
medium: "/path/to/medium/jpg",
|
||||||
|
large: "/path/to/large/jpg",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(artistToArtistSummary(artist)).toEqual({
|
||||||
|
id: artist.id,
|
||||||
|
name: artist.name,
|
||||||
|
image: artist.image,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -63,7 +68,6 @@ describe("InMemoryMusicService", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
service.clear();
|
service.clear();
|
||||||
|
|
||||||
service.hasArtists(BOB_MARLEY, MADONNA, BLONDIE, METALLICA);
|
|
||||||
service.hasUser(user);
|
service.hasUser(user);
|
||||||
|
|
||||||
const token = (await service.generateToken(user)) as AuthSuccess;
|
const token = (await service.generateToken(user)) as AuthSuccess;
|
||||||
@@ -71,59 +75,67 @@ describe("InMemoryMusicService", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("artists", () => {
|
describe("artists", () => {
|
||||||
describe("fetching all", () => {
|
const artist1 = anArtist();
|
||||||
|
const artist2 = anArtist();
|
||||||
|
const artist3 = anArtist();
|
||||||
|
const artist4 = anArtist();
|
||||||
|
const artist5 = anArtist();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service.hasArtists(artist1, artist2, artist3, artist4, artist5);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetching all in one page", () => {
|
||||||
it("should provide an array of artists", async () => {
|
it("should provide an array of artists", async () => {
|
||||||
const artists = [
|
|
||||||
artistToArtistSummary(BOB_MARLEY),
|
|
||||||
artistToArtistSummary(MADONNA),
|
|
||||||
artistToArtistSummary(BLONDIE),
|
|
||||||
artistToArtistSummary(METALLICA),
|
|
||||||
];
|
|
||||||
expect(
|
expect(
|
||||||
await musicLibrary.artists({ _index: 0, _count: 100 })
|
await musicLibrary.artists({ _index: 0, _count: 100 })
|
||||||
).toEqual({
|
).toEqual({
|
||||||
results: artists,
|
results: [
|
||||||
total: 4,
|
artistToArtistSummary(artist1),
|
||||||
|
artistToArtistSummary(artist2),
|
||||||
|
artistToArtistSummary(artist3),
|
||||||
|
artistToArtistSummary(artist4),
|
||||||
|
artistToArtistSummary(artist5),
|
||||||
|
],
|
||||||
|
total: 5,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("fetching the second page", () => {
|
describe("fetching the second page", () => {
|
||||||
it("should provide an array of artists", async () => {
|
it("should provide an array of artists", async () => {
|
||||||
const artists = [
|
|
||||||
artistToArtistSummary(BLONDIE),
|
|
||||||
artistToArtistSummary(METALLICA),
|
|
||||||
];
|
|
||||||
expect(await musicLibrary.artists({ _index: 2, _count: 2 })).toEqual({
|
expect(await musicLibrary.artists({ _index: 2, _count: 2 })).toEqual({
|
||||||
results: artists,
|
results: [
|
||||||
total: 4,
|
artistToArtistSummary(artist3),
|
||||||
|
artistToArtistSummary(artist4),
|
||||||
|
],
|
||||||
|
total: 5,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("fetching the more items than fit on the second page", () => {
|
describe("fetching the last page", () => {
|
||||||
it("should provide an array of artists", async () => {
|
it("should provide an array of artists", async () => {
|
||||||
const artists = [
|
expect(await musicLibrary.artists({ _index: 4, _count: 2 })).toEqual({
|
||||||
artistToArtistSummary(MADONNA),
|
results: [artistToArtistSummary(artist5)],
|
||||||
artistToArtistSummary(BLONDIE),
|
total: 5,
|
||||||
artistToArtistSummary(METALLICA),
|
});
|
||||||
];
|
|
||||||
expect(
|
|
||||||
await musicLibrary.artists({ _index: 1, _count: 50 })
|
|
||||||
).toEqual({ results: artists, total: 4 });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("artist", () => {
|
describe("artist", () => {
|
||||||
|
const artist1 = anArtist({ id: uuid(), name: "Artist 1" });
|
||||||
|
const artist2 = anArtist({ id: uuid(), name: "Artist 2" });
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service.hasArtists(artist1, artist2);
|
||||||
|
});
|
||||||
|
|
||||||
describe("when it exists", () => {
|
describe("when it exists", () => {
|
||||||
it("should provide an artist", async () => {
|
it("should provide an artist", async () => {
|
||||||
expect(await musicLibrary.artist(MADONNA.id)).toEqual(
|
expect(await musicLibrary.artist(artist1.id)).toEqual(artist1);
|
||||||
MADONNA
|
expect(await musicLibrary.artist(artist2.id)).toEqual(artist2);
|
||||||
);
|
|
||||||
expect(await musicLibrary.artist(BLONDIE.id)).toEqual(
|
|
||||||
BLONDIE
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -136,36 +148,177 @@ describe("InMemoryMusicService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("albums", () => {
|
describe("album", () => {
|
||||||
describe("fetching with no filtering", () => {
|
describe("when it exists", () => {
|
||||||
it("should return all the albums for all the artists", async () => {
|
const albumToLookFor = anAlbum({ id: "albumToLookFor" });
|
||||||
expect(await musicLibrary.albums({ _index: 0, _count: 100 })).toEqual(
|
const artist1 = anArtist({ albums: [anAlbum(), anAlbum(), anAlbum()] });
|
||||||
{
|
const artist2 = anArtist({
|
||||||
results: ALL_ALBUMS,
|
albums: [anAlbum(), albumToLookFor, anAlbum()],
|
||||||
total: ALL_ALBUMS.length,
|
});
|
||||||
}
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service.hasArtists(artist1, artist2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should provide an artist", async () => {
|
||||||
|
expect(await musicLibrary.album(albumToLookFor.id)).toEqual(
|
||||||
|
albumToLookFor
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("fetching for a single artist", () => {
|
describe("when it doesnt exist", () => {
|
||||||
it("should return them all if the artist has some", async () => {
|
it("should blow up", async () => {
|
||||||
expect(
|
return expect(musicLibrary.album("-1")).rejects.toEqual(
|
||||||
await musicLibrary.albums({
|
"No album with id '-1'"
|
||||||
artistId: BLONDIE.id,
|
);
|
||||||
_index: 0,
|
});
|
||||||
_count: 100,
|
});
|
||||||
})
|
});
|
||||||
).toEqual({
|
|
||||||
results: BLONDIE.albums,
|
describe("albums", () => {
|
||||||
total: BLONDIE.albums.length,
|
const artist1_album1 = anAlbum({ genre: "Pop" });
|
||||||
|
const artist1_album2 = anAlbum({ genre: "Rock" });
|
||||||
|
const artist1_album3 = anAlbum({ genre: "Metal" });
|
||||||
|
const artist1_album4 = anAlbum({ genre: "Pop" });
|
||||||
|
const artist1_album5 = anAlbum({ genre: "Pop" });
|
||||||
|
|
||||||
|
const artist2_album1 = anAlbum({ genre: "Metal" });
|
||||||
|
|
||||||
|
const artist3_album1 = anAlbum({ genre: "Hip-Hop" });
|
||||||
|
const artist3_album2 = anAlbum({ genre: "Pop" });
|
||||||
|
|
||||||
|
const totalAlbumCount = 8;
|
||||||
|
|
||||||
|
const artist1 = anArtist({
|
||||||
|
albums: [
|
||||||
|
artist1_album1,
|
||||||
|
artist1_album2,
|
||||||
|
artist1_album3,
|
||||||
|
artist1_album4,
|
||||||
|
artist1_album5,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const artist2 = anArtist({ albums: [artist2_album1] });
|
||||||
|
const artist3 = anArtist({ albums: [artist3_album1, artist3_album2] });
|
||||||
|
const artistWithNoAlbums = anArtist({ albums: [] });
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service.hasArtists(artist1, artist2, artist3, artistWithNoAlbums);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with no filtering", () => {
|
||||||
|
describe("fetching all on one page", () => {
|
||||||
|
it("should return all the albums for all the artists", async () => {
|
||||||
|
expect(
|
||||||
|
await musicLibrary.albums({ _index: 0, _count: 100 })
|
||||||
|
).toEqual({
|
||||||
|
results: [
|
||||||
|
albumToAlbumSummary(artist1_album1),
|
||||||
|
albumToAlbumSummary(artist1_album2),
|
||||||
|
albumToAlbumSummary(artist1_album3),
|
||||||
|
albumToAlbumSummary(artist1_album4),
|
||||||
|
albumToAlbumSummary(artist1_album5),
|
||||||
|
|
||||||
|
albumToAlbumSummary(artist2_album1),
|
||||||
|
|
||||||
|
albumToAlbumSummary(artist3_album1),
|
||||||
|
albumToAlbumSummary(artist3_album2),
|
||||||
|
],
|
||||||
|
total: totalAlbumCount,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return empty list of the artists does not have any", async () => {
|
describe("fetching a page", () => {
|
||||||
|
it("should return only that page", async () => {
|
||||||
|
expect(await musicLibrary.albums({ _index: 4, _count: 3 })).toEqual(
|
||||||
|
{
|
||||||
|
results: [
|
||||||
|
albumToAlbumSummary(artist1_album5),
|
||||||
|
albumToAlbumSummary(artist2_album1),
|
||||||
|
albumToAlbumSummary(artist3_album1),
|
||||||
|
],
|
||||||
|
total: totalAlbumCount,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetching the last page", () => {
|
||||||
|
it("should return only that page", async () => {
|
||||||
|
expect(
|
||||||
|
await musicLibrary.albums({ _index: 6, _count: 100 })
|
||||||
|
).toEqual({
|
||||||
|
results: [
|
||||||
|
albumToAlbumSummary(artist3_album1),
|
||||||
|
albumToAlbumSummary(artist3_album2),
|
||||||
|
],
|
||||||
|
total: totalAlbumCount,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("filtering by artist", () => {
|
||||||
|
describe("fetching all", () => {
|
||||||
|
it("should return all artist albums", async () => {
|
||||||
|
expect(
|
||||||
|
await musicLibrary.albums({
|
||||||
|
artistId: artist3.id,
|
||||||
|
_index: 0,
|
||||||
|
_count: 100,
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
results: [
|
||||||
|
albumToAlbumSummary(artist3_album1),
|
||||||
|
albumToAlbumSummary(artist3_album2),
|
||||||
|
],
|
||||||
|
total: artist3.albums.length,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the artist has more albums than a single page", () => {
|
||||||
|
describe("can fetch a single page", () => {
|
||||||
|
it("should return only the albums for that page", async () => {
|
||||||
|
expect(
|
||||||
|
await musicLibrary.albums({
|
||||||
|
artistId: artist1.id,
|
||||||
|
_index: 1,
|
||||||
|
_count: 3,
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
results: [
|
||||||
|
albumToAlbumSummary(artist1_album2),
|
||||||
|
albumToAlbumSummary(artist1_album3),
|
||||||
|
albumToAlbumSummary(artist1_album4),
|
||||||
|
],
|
||||||
|
total: artist1.albums.length,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("can fetch the last page", () => {
|
||||||
|
it("should return only the albums for the last page", async () => {
|
||||||
|
expect(
|
||||||
|
await musicLibrary.albums({
|
||||||
|
artistId: artist1.id,
|
||||||
|
_index: 4,
|
||||||
|
_count: 100,
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
results: [albumToAlbumSummary(artist1_album5)],
|
||||||
|
total: artist1.albums.length,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return empty list if the artists does not have any", async () => {
|
||||||
expect(
|
expect(
|
||||||
await musicLibrary.albums({
|
await musicLibrary.albums({
|
||||||
artistId: MADONNA.id,
|
artistId: artistWithNoAlbums.id,
|
||||||
_index: 0,
|
_index: 0,
|
||||||
_count: 100,
|
_count: 100,
|
||||||
})
|
})
|
||||||
@@ -189,25 +342,71 @@ describe("InMemoryMusicService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("fetching with index and count", () => {
|
describe("filtering by genre", () => {
|
||||||
it("should be able to return the first page", async () => {
|
describe("fetching all on one page", () => {
|
||||||
const albums = [BOB_MARLEY.albums[0], BOB_MARLEY.albums[1]];
|
it.only("should return all the albums of that genre for all the artists", async () => {
|
||||||
expect(await musicLibrary.albums({ _index: 0, _count: 2 })).toEqual({
|
expect(
|
||||||
results: albums,
|
await musicLibrary.albums({ _index: 0, _count: 100, genre: "Pop" })
|
||||||
total: ALL_ALBUMS.length,
|
).toEqual({
|
||||||
|
results: [
|
||||||
|
albumToAlbumSummary(artist1_album1),
|
||||||
|
albumToAlbumSummary(artist1_album4),
|
||||||
|
albumToAlbumSummary(artist1_album5),
|
||||||
|
albumToAlbumSummary(artist3_album2),
|
||||||
|
],
|
||||||
|
total: 4,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should be able to return the second page", async () => {
|
|
||||||
const albums = [BOB_MARLEY.albums[2], BLONDIE.albums[0]];
|
describe("when the genre has more albums than a single page", () => {
|
||||||
expect(await musicLibrary.albums({ _index: 2, _count: 2 })).toEqual({
|
describe("can fetch a single page", () => {
|
||||||
results: albums,
|
it("should return only the albums for that page", async () => {
|
||||||
total: ALL_ALBUMS.length,
|
expect(
|
||||||
|
await musicLibrary.albums({
|
||||||
|
genre: "Pop",
|
||||||
|
_index: 1,
|
||||||
|
_count: 3,
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
results: [
|
||||||
|
albumToAlbumSummary(artist1_album1),
|
||||||
|
albumToAlbumSummary(artist1_album4),
|
||||||
|
albumToAlbumSummary(artist1_album5),
|
||||||
|
albumToAlbumSummary(artist3_album2),
|
||||||
|
],
|
||||||
|
total: 4,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("can fetch the last page", () => {
|
||||||
|
it("should return only the albums for the last page", async () => {
|
||||||
|
expect(
|
||||||
|
await musicLibrary.albums({
|
||||||
|
artistId: artist1.id,
|
||||||
|
_index: 4,
|
||||||
|
_count: 100,
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
results: [albumToAlbumSummary(artist1_album5)],
|
||||||
|
total: artist1.albums.length,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
it("should be able to return the last page", async () => {
|
|
||||||
expect(await musicLibrary.albums({ _index: 5, _count: 2 })).toEqual({
|
it("should return empty list if there are no albums for the genre", async () => {
|
||||||
results: METALLICA.albums,
|
expect(
|
||||||
total: ALL_ALBUMS.length,
|
await musicLibrary.albums({
|
||||||
|
genre: "genre with no albums",
|
||||||
|
_index: 0,
|
||||||
|
_count: 100,
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
results: [],
|
||||||
|
total: 0,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { option as O } from "fp-ts";
|
import { option as O } from "fp-ts";
|
||||||
import { pipe } from "fp-ts/lib/function";
|
import { pipe } from "fp-ts/lib/function";
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MusicService,
|
MusicService,
|
||||||
Credentials,
|
Credentials,
|
||||||
@@ -13,33 +12,20 @@ import {
|
|||||||
AlbumQuery,
|
AlbumQuery,
|
||||||
slice2,
|
slice2,
|
||||||
asResult,
|
asResult,
|
||||||
ArtistSummary,
|
artistToArtistSummary,
|
||||||
|
albumToAlbumSummary,
|
||||||
Album,
|
Album,
|
||||||
AlbumSummary
|
|
||||||
} from "../src/music_service";
|
} from "../src/music_service";
|
||||||
|
|
||||||
export const artistToArtistSummary = (
|
|
||||||
it: Artist
|
|
||||||
): ArtistSummary => ({
|
|
||||||
id: it.id,
|
|
||||||
name: it.name,
|
|
||||||
image: it.image,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const albumToAlbumSummary = (
|
|
||||||
it: Album
|
|
||||||
): AlbumSummary => ({
|
|
||||||
id: it.id,
|
|
||||||
name: it.name,
|
|
||||||
year: it.year,
|
|
||||||
genre: it.genre,
|
|
||||||
});
|
|
||||||
|
|
||||||
type P<T> = (t: T) => boolean;
|
type P<T> = (t: T) => boolean;
|
||||||
const all: P<any> = (_: any) => true;
|
const all: P<any> = (_: any) => true;
|
||||||
const artistWithId = (id: string): P<Artist> => (artist: Artist) =>
|
|
||||||
|
const albumByArtist = (id: string): P<[Artist, Album]> => ([artist, _]) =>
|
||||||
artist.id === id;
|
artist.id === id;
|
||||||
|
|
||||||
|
const albumWithGenre = (genre: string): P<[Artist, Album]> => ([_, album]) =>
|
||||||
|
album.genre === genre;
|
||||||
|
|
||||||
export class InMemoryMusicService implements MusicService {
|
export class InMemoryMusicService implements MusicService {
|
||||||
users: Record<string, string> = {};
|
users: Record<string, string> = {};
|
||||||
artists: Artist[] = [];
|
artists: Artist[] = [];
|
||||||
@@ -76,24 +62,40 @@ export class InMemoryMusicService implements MusicService {
|
|||||||
pipe(
|
pipe(
|
||||||
this.artists.find((it) => it.id === id),
|
this.artists.find((it) => it.id === id),
|
||||||
O.fromNullable,
|
O.fromNullable,
|
||||||
O.map(it => Promise.resolve(it)),
|
O.map((it) => Promise.resolve(it)),
|
||||||
O.getOrElse(() => Promise.reject(`No artist with id '${id}'`))
|
O.getOrElse(() => Promise.reject(`No artist with id '${id}'`))
|
||||||
),
|
),
|
||||||
albums: (q: AlbumQuery) =>
|
albums: (q: AlbumQuery) =>
|
||||||
Promise.resolve(
|
Promise.resolve(
|
||||||
this.artists.filter(
|
this.artists
|
||||||
pipe(
|
.flatMap((artist) => artist.albums.map((album) => [artist, album]))
|
||||||
O.fromNullable(q.artistId),
|
.filter(
|
||||||
O.map(artistWithId),
|
pipe(
|
||||||
O.getOrElse(() => all)
|
pipe(
|
||||||
|
O.fromNullable(q.artistId),
|
||||||
|
O.map(albumByArtist)
|
||||||
|
),
|
||||||
|
O.alt(() =>
|
||||||
|
pipe(
|
||||||
|
O.fromNullable(q.genre),
|
||||||
|
O.map(albumWithGenre)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
O.getOrElse(() => all)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.then((artists) => artists.flatMap((it) => it.albums))
|
.then((matches) => matches.map(([_, album]) => album as Album))
|
||||||
.then(it => it.map(albumToAlbumSummary))
|
.then((it) => it.map(albumToAlbumSummary))
|
||||||
.then(slice2(q))
|
.then(slice2(q))
|
||||||
.then(asResult),
|
.then(asResult),
|
||||||
// album: (id: albumId) => Promise.resolve(this.artists.flatMap(it => it.albums).find(it => it.id === id))
|
album: (id: string) =>
|
||||||
|
pipe(
|
||||||
|
this.artists.flatMap((it) => it.albums).find((it) => it.id === id),
|
||||||
|
O.fromNullable,
|
||||||
|
O.map((it) => Promise.resolve(it)),
|
||||||
|
O.getOrElse(() => Promise.reject(`No album with id '${id}'`))
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,15 @@ import axios from "axios";
|
|||||||
jest.mock("axios");
|
jest.mock("axios");
|
||||||
|
|
||||||
import randomString from "../src/random_string";
|
import randomString from "../src/random_string";
|
||||||
import { Album, Artist, AuthSuccess, Images } from "../src/music_service";
|
import {
|
||||||
|
Album,
|
||||||
|
Artist,
|
||||||
|
AuthSuccess,
|
||||||
|
Images,
|
||||||
|
albumToAlbumSummary,
|
||||||
|
} from "../src/music_service";
|
||||||
|
import { anAlbum, anArtist } from "./builders";
|
||||||
|
|
||||||
jest.mock("../src/random_string");
|
jest.mock("../src/random_string");
|
||||||
|
|
||||||
describe("t", () => {
|
describe("t", () => {
|
||||||
@@ -71,6 +79,16 @@ const albumXml = (artist: Artist, album: Album) => `<album id="${album.id}"
|
|||||||
songCount="19"
|
songCount="19"
|
||||||
isVideo="false"></album>`;
|
isVideo="false"></album>`;
|
||||||
|
|
||||||
|
const albumListXml = (
|
||||||
|
albums: [Artist, Album][]
|
||||||
|
) => `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)">
|
||||||
|
<albumList>
|
||||||
|
${albums.map(([artist, album]) =>
|
||||||
|
albumXml(artist, album)
|
||||||
|
)}
|
||||||
|
</albumList>
|
||||||
|
</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)">
|
||||||
@@ -145,31 +163,14 @@ describe("Navidrome", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getArtist", () => {
|
describe("getting an artist", () => {
|
||||||
const album1: Album = {
|
const album1: Album = anAlbum();
|
||||||
id: "album1",
|
|
||||||
name: "super album",
|
|
||||||
year: "2001",
|
|
||||||
genre: "Pop",
|
|
||||||
};
|
|
||||||
|
|
||||||
const album2: Album = {
|
const album2: Album = anAlbum();
|
||||||
id: "album2",
|
|
||||||
name: "bad album",
|
|
||||||
year: "2002",
|
|
||||||
genre: "Rock",
|
|
||||||
};
|
|
||||||
|
|
||||||
const artist: Artist = {
|
const artist: Artist = anArtist({
|
||||||
id: "someUUID_123",
|
|
||||||
name: "BananaMan",
|
|
||||||
image: {
|
|
||||||
small: "sml1",
|
|
||||||
medium: "med1",
|
|
||||||
large: "lge1",
|
|
||||||
},
|
|
||||||
albums: [album1, album2],
|
albums: [album1, album2],
|
||||||
};
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockGET
|
mockGET
|
||||||
@@ -180,37 +181,39 @@ describe("Navidrome", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.only("should do it", async () => {
|
describe("when the artist exists", () => {
|
||||||
const result: Artist = await navidrome
|
it("should return it", async () => {
|
||||||
.generateToken({ username, password })
|
const result: Artist = await navidrome
|
||||||
.then((it) => it as AuthSuccess)
|
.generateToken({ username, password })
|
||||||
.then((it) => navidrome.login(it.authToken))
|
.then((it) => it as AuthSuccess)
|
||||||
.then((it) => it.artist(artist.id));
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.artist(artist.id));
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
id: artist.id,
|
|
||||||
name: artist.name,
|
|
||||||
image: { small: "sml1", medium: "med1", large: "lge1" },
|
|
||||||
albums: [album1, album2]
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtist`, {
|
|
||||||
params: {
|
|
||||||
id: artist.id,
|
id: artist.id,
|
||||||
...authParams,
|
name: artist.name,
|
||||||
},
|
image: artist.image,
|
||||||
});
|
albums: artist.albums,
|
||||||
|
});
|
||||||
|
|
||||||
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtistInfo`, {
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtist`, {
|
||||||
params: {
|
params: {
|
||||||
id: artist.id,
|
id: artist.id,
|
||||||
...authParams,
|
...authParams,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getArtistInfo`, {
|
||||||
|
params: {
|
||||||
|
id: artist.id,
|
||||||
|
...authParams,
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getArtists", () => {
|
describe("getting artists", () => {
|
||||||
describe("when there are no results", () => {
|
describe("when there are no results", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockGET
|
mockGET
|
||||||
@@ -246,30 +249,10 @@ describe("Navidrome", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("when there are artists", () => {
|
describe("when there are artists", () => {
|
||||||
const artist1: Artist = {
|
const artist1 = anArtist();
|
||||||
id: "artist1.id",
|
const artist2 = anArtist();
|
||||||
name: "artist1.name",
|
const artist3 = anArtist();
|
||||||
image: { small: "s1", medium: "m1", large: "l1" },
|
const artist4 = anArtist();
|
||||||
albums: [],
|
|
||||||
};
|
|
||||||
const artist2: Artist = {
|
|
||||||
id: "artist2.id",
|
|
||||||
name: "artist2.name",
|
|
||||||
image: { small: "s2", medium: "m2", large: "l2" },
|
|
||||||
albums: [],
|
|
||||||
};
|
|
||||||
const artist3: Artist = {
|
|
||||||
id: "artist3.id",
|
|
||||||
name: "artist3.name",
|
|
||||||
image: { small: "s3", medium: "m3", large: "l3" },
|
|
||||||
albums: [],
|
|
||||||
};
|
|
||||||
const artist4: Artist = {
|
|
||||||
id: "artist4.id",
|
|
||||||
name: "artist4.name",
|
|
||||||
image: { small: "s4", medium: "m4", large: "l4" },
|
|
||||||
albums: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const getArtistsXml = `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="0.40.0 (8799358a)">
|
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">
|
<artists lastModified="1614586749000" ignoredArticles="The El La Los Las Le Les Os As O A">
|
||||||
@@ -402,4 +385,178 @@ describe("Navidrome", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const range = (size: number) => [...Array(size).keys()];
|
||||||
|
|
||||||
|
const asArtistAlbumPairs = (artists: Artist[]): [Artist, Album][] =>
|
||||||
|
artists.flatMap((artist) =>
|
||||||
|
artist.albums.map((album) => [artist, album] as [Artist, Album])
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("getting albums", () => {
|
||||||
|
describe("filtering", () => {
|
||||||
|
const album1 = anAlbum({ genre: "Pop" });
|
||||||
|
const album2 = anAlbum({ genre: "Rock" });
|
||||||
|
const album3 = anAlbum({ genre: "Pop" });
|
||||||
|
|
||||||
|
const artist = anArtist({ albums: [album1, album2, album3]});
|
||||||
|
|
||||||
|
describe("by genre", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(ok(albumListXml([[artist, album1], [artist, album3]])))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should pass the filter to navidrome", async () => {
|
||||||
|
const q = { _index: 0, _count: 500, genre: "Pop" };
|
||||||
|
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, album3].map(albumToAlbumSummary),
|
||||||
|
total: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
|
params: {
|
||||||
|
type: "byGenre",
|
||||||
|
genre: "Pop",
|
||||||
|
size: 500,
|
||||||
|
offset: 0,
|
||||||
|
...authParams,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when there are less than 500 albums", () => {
|
||||||
|
const artist1 = anArtist({
|
||||||
|
name: "abba",
|
||||||
|
albums: [anAlbum(), anAlbum(), anAlbum()],
|
||||||
|
});
|
||||||
|
const artist2 = anArtist({
|
||||||
|
name: "babba",
|
||||||
|
albums: [anAlbum(), anAlbum(), anAlbum()],
|
||||||
|
});
|
||||||
|
const artists = [artist1, artist2];
|
||||||
|
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", () => {
|
||||||
|
it("should return all of them with corrent paging information", async () => {
|
||||||
|
const paging = { _index: 0, _count: 500 };
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.albums(paging));
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
results: albums.map(albumToAlbumSummary),
|
||||||
|
total: 6,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
|
params: {
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
size: 500,
|
||||||
|
offset: 0,
|
||||||
|
...authParams,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("querying for a page of them", () => {
|
||||||
|
it("should return the page with the corrent paging information", async () => {
|
||||||
|
const paging = { _index: 2, _count: 2 };
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.albums(paging));
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
results: [albums[2], albums[3]],
|
||||||
|
total: 6,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
|
params: {
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
size: 500,
|
||||||
|
offset: 0,
|
||||||
|
...authParams,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when there are more than 500 albums", () => {
|
||||||
|
const first500Albums = range(500).map((i) =>
|
||||||
|
anAlbum({ name: `album ${i}` })
|
||||||
|
);
|
||||||
|
const artist = anArtist({
|
||||||
|
name: "> 500 albums",
|
||||||
|
albums: [...first500Albums, anAlbum(), anAlbum(), anAlbum()],
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockGET
|
||||||
|
.mockImplementationOnce(() => Promise.resolve(ok(PING_OK)))
|
||||||
|
.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve(
|
||||||
|
ok(
|
||||||
|
albumListXml(
|
||||||
|
first500Albums.map(
|
||||||
|
(album) => [artist, album] as [Artist, Album]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("querying for all of them", () => {
|
||||||
|
it("will return only the first 500 with the correct paging information", async () => {
|
||||||
|
const paging = { _index: 0, _count: 1000 };
|
||||||
|
const result = await navidrome
|
||||||
|
.generateToken({ username, password })
|
||||||
|
.then((it) => it as AuthSuccess)
|
||||||
|
.then((it) => navidrome.login(it.authToken))
|
||||||
|
.then((it) => it.albums(paging));
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
results: first500Albums.map(albumToAlbumSummary),
|
||||||
|
total: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(axios.get).toHaveBeenCalledWith(`${url}/rest/getAlbumList`, {
|
||||||
|
params: {
|
||||||
|
type: "alphabeticalByArtist",
|
||||||
|
size: 500,
|
||||||
|
offset: 0,
|
||||||
|
...authParams,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user