AlbumQuery and ArtistQuery types on MusicService

This commit is contained in:
simojenki
2021-03-02 10:24:55 +11:00
parent bdee01d2c7
commit 06024e8e36
5 changed files with 39 additions and 76 deletions

View File

@@ -38,31 +38,35 @@ export type Album = {
}; };
export type Paging = { export type Paging = {
_index?: number; _index: number;
_count?: number; _count: number;
}; };
export type Result<T> = { export type Result<T> = {
results: T[], results: T[];
total: number total: number;
} };
export function slice2<T>({ _index, _count }: Paging) { export function slice2<T>({ _index, _count }: Paging) {
const i0 = _index || 0; return (things: T[]): [T[], number] => [
const i1 = _count ? i0 + _count : undefined; things.slice(_index, _index + _count),
return (things: T[]): [T[], number] => [things.slice(i0, i1), things.length] things.length,
];
} }
export const asResult = <T>([results, total]: [T[], number]) => ({ results, total }) export const asResult = <T>([results, total]: [T[], number]) => ({
results,
total,
});
export type ArtistQuery = Paging
export type AlbumQuery = Paging & {
artistId?: string
}
export interface MusicLibrary { export interface MusicLibrary {
artists({ _index, _count }: Paging): Promise<Result<Artist>>; artists(q: ArtistQuery): Promise<Result<Artist>>;
artist(id: string): Artist; artist(id: string): Artist;
albums({ albums(q: AlbumQuery): Promise<Result<Album>>;
artistId,
_index,
_count,
}: {
artistId?: string;
} & Paging): Promise<Result<Album>>;
} }

View File

@@ -1,5 +1,5 @@
import { Md5 } from "ts-md5/dist/md5"; import { Md5 } from "ts-md5/dist/md5";
import { Credentials, MusicService, Paging, Album, Artist, Result, slice2, asResult } from "./music_service"; import { Credentials, MusicService, Album, Artist, Result, slice2, asResult, AlbumQuery, ArtistQuery } from "./music_service";
import X2JS from "x2js"; import X2JS from "x2js";
import axios from "axios"; import axios from "axios";
@@ -98,25 +98,20 @@ export class Navidrome implements MusicService {
const navidrome = this; const navidrome = this;
const credentials: Credentials = this.parseToken(token); const credentials: Credentials = this.parseToken(token);
return Promise.resolve({ return Promise.resolve({
artists: (paging: Paging): Promise<Result<Artist>> => artists: (q: ArtistQuery): Promise<Result<Artist>> =>
navidrome navidrome
.get<GetArtistsResponse>(credentials, "/rest/getArtists") .get<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((it) => ({ id: it._id, name: it._name })) artists.map((it) => ({ id: it._id, name: it._name }))
) )
.then(slice2(paging)) .then(slice2(q))
.then(asResult), .then(asResult),
artist: (id: string) => ({ artist: (id: string) => ({
id, id,
name: id, name: id,
}), }),
albums: ({ albums: (_: AlbumQuery): Promise<Result<Album>> => {
artistId,
}: {
artistId?: string;
} & Paging): Promise<Result<Album>> => {
console.log(artistId);
return Promise.resolve({ results: [], total: 0}); return Promise.resolve({ results: [], total: 0});
}, },
}); });

View File

@@ -66,7 +66,7 @@ describe("InMemoryMusicService", () => {
{ id: BLONDIE.id, name: BLONDIE.name }, { id: BLONDIE.id, name: BLONDIE.name },
{ id: METALLICA.id, name: METALLICA.name }, { id: METALLICA.id, name: METALLICA.name },
]; ];
expect(await musicLibrary.artists({})).toEqual({ expect(await musicLibrary.artists({ _index: 0, _count: 100 })).toEqual({
results: artists, results: artists,
total: 4, total: 4,
}); });
@@ -126,7 +126,7 @@ describe("InMemoryMusicService", () => {
describe("albums", () => { describe("albums", () => {
describe("fetching with no filtering", () => { describe("fetching with no filtering", () => {
it("should return all the albums for all the artists", async () => { it("should return all the albums for all the artists", async () => {
expect(await musicLibrary.albums({})).toEqual({ expect(await musicLibrary.albums({ _index: 0, _count: 100 })).toEqual({
results: ALL_ALBUMS, results: ALL_ALBUMS,
total: ALL_ALBUMS.length, total: ALL_ALBUMS.length,
}); });
@@ -135,56 +135,27 @@ describe("InMemoryMusicService", () => {
describe("fetching for a single artist", () => { describe("fetching for a single artist", () => {
it("should return them all if the artist has some", async () => { it("should return them all if the artist has some", async () => {
expect(await musicLibrary.albums({ artistId: BLONDIE.id })).toEqual({ expect(await musicLibrary.albums({ artistId: BLONDIE.id, _index: 0, _count: 100 })).toEqual({
results: BLONDIE.albums, results: BLONDIE.albums,
total: BLONDIE.albums.length, total: BLONDIE.albums.length,
}); });
}); });
it("should return empty list of the artists does not have any", async () => { it("should return empty list of the artists does not have any", async () => {
expect(await musicLibrary.albums({ artistId: MADONNA.id })).toEqual({ expect(await musicLibrary.albums({ artistId: MADONNA.id, _index: 0, _count: 100 })).toEqual({
results: [], results: [],
total: 0, total: 0,
}); });
}); });
it("should return empty list if the artist id is not valid", async () => { it("should return empty list if the artist id is not valid", async () => {
expect(await musicLibrary.albums({ artistId: uuid() })).toEqual({ expect(await musicLibrary.albums({ artistId: uuid(), _index: 0, _count: 100 })).toEqual({
results: [], results: [],
total: 0, total: 0,
}); });
}); });
}); });
describe("fetching with just index", () => {
it("should return everything after", async () => {
const albums = [
BOB_MARLEY.albums[2],
...BLONDIE.albums,
...MADONNA.albums,
...METALLICA.albums,
];
expect(await musicLibrary.albums({ _index: 2 })).toEqual({
results: albums,
total: ALL_ALBUMS.length,
});
});
});
describe("fetching with just count", () => {
it("should return first n items", async () => {
const albums = [
BOB_MARLEY.albums[0],
BOB_MARLEY.albums[1],
BOB_MARLEY.albums[2],
];
expect(await musicLibrary.albums({ _count: 3 })).toEqual({
results: albums,
total: ALL_ALBUMS.length,
});
});
});
describe("fetching with index and count", () => { describe("fetching with index and count", () => {
it("should be able to return the first page", async () => { it("should be able to return the first page", async () => {
const albums = [BOB_MARLEY.albums[0], BOB_MARLEY.albums[1]]; const albums = [BOB_MARLEY.albums[0], BOB_MARLEY.albums[1]];

View File

@@ -9,7 +9,8 @@ import {
AuthFailure, AuthFailure,
Artist, Artist,
MusicLibrary, MusicLibrary,
Paging, ArtistQuery,
AlbumQuery,
slice2, slice2,
asResult, asResult,
} from "../src/music_service"; } from "../src/music_service";
@@ -57,9 +58,9 @@ export class InMemoryMusicService implements MusicService {
if (this.users[credentials.username] != credentials.password) if (this.users[credentials.username] != credentials.password)
return Promise.reject("Invalid auth token"); return Promise.reject("Invalid auth token");
return Promise.resolve({ return Promise.resolve({
artists: (paging: Paging) => artists: (q: ArtistQuery) =>
Promise.resolve(this.artists.map(artistWithAlbumsToArtist)) Promise.resolve(this.artists.map(artistWithAlbumsToArtist))
.then(slice2(paging)) .then(slice2(q))
.then(asResult), .then(asResult),
artist: (id: string) => artist: (id: string) =>
pipe( pipe(
@@ -68,26 +69,18 @@ export class InMemoryMusicService implements MusicService {
O.map(artistWithAlbumsToArtist), O.map(artistWithAlbumsToArtist),
getOrThrow(`No artist with id '${id}'`) getOrThrow(`No artist with id '${id}'`)
), ),
albums: ({ albums: (q: AlbumQuery) =>
artistId,
_index,
_count,
}: {
artistId?: string;
_index?: number;
_count?: number;
}) =>
Promise.resolve( Promise.resolve(
this.artists.filter( this.artists.filter(
pipe( pipe(
O.fromNullable(artistId), O.fromNullable(q.artistId),
O.map(artistWithId), O.map(artistWithId),
O.getOrElse(() => all) O.getOrElse(() => all)
) )
) )
) )
.then((artists) => artists.flatMap((it) => it.albums)) .then((artists) => artists.flatMap((it) => it.albums))
.then(slice2({ _index, _count })) .then(slice2(q))
.then(asResult), .then(asResult),
}); });
} }

View File

@@ -101,12 +101,12 @@ describe("navidrome", () => {
}); });
}); });
describe("when no paging specified", () => { describe("when no paging is ineffect", () => {
it("should return all the artists", async () => { it("should return all the artists", async () => {
const artists = await navidrome const artists = await navidrome
.generateToken({ username, password }) .generateToken({ username, password })
.then((it) => navidrome.login(it.authToken)) .then((it) => navidrome.login(it.authToken))
.then((it) => it.artists({})); .then((it) => it.artists({ _index: 0, _count: 100 }));
const expectedArtists = [ const expectedArtists = [
{ id: "2911b2d67a6b11eb804dd360a6225680", name: "10 Planets" }, { id: "2911b2d67a6b11eb804dd360a6225680", name: "10 Planets" },