import { AlbumWithRefs, AlbumWithRefsWithId, ArtistWithRefs, ArtistWithRefsWithId, TagWithRefs, TagWithRefsWithId, TrackWithRefs, TrackWithRefsWithId } from "../../../client/src/api/api"; import { userEndpoints } from "../../endpoints/User"; import { createAlbum, createArtist, createTag, createTrack, deleteAlbum, deleteArtist, deleteTag, deleteTrack, ReferenceDatabase } from "./DBReferenceModel"; import * as helpers from '../integration/helpers'; import { DBErrorKind, isDBError } from "../../endpoints/types"; export enum DBActionType { CreateTrack = "CreateTrack", CreateAlbum = "CreateAlbum", CreateTag = "CreateTag", CreateArtist = "CreateArtist", DeleteTrack = "DeleteTrack", DeleteAlbum = "DeleteAlbum", DeleteArtist = "DeleteArtist", DeleteTag = "DeleteTag", } export interface DBAction { type: DBActionType, userId: number, payload: any, } export type Distribution = Map; export interface RandomDBActionDistribution { type: Distribution, userId: Distribution, createTrackParams: RandomCreateTrackDistribution, createAlbumParams: RandomCreateAlbumDistribution, createArtistParams: RandomCreateArtistDistribution, createTagParams: RandomCreateTagDistribution, deleteTrackParams: RandomDeleteObjectDistribution, deleteArtistParams: RandomDeleteObjectDistribution, deleteAlbumParams: RandomDeleteObjectDistribution, deleteTagParams: RandomDeleteObjectDistribution, } export interface RandomCreateTrackDistribution { linkArtists: { numValid: Distribution, numInvalid: Distribution, } linkTags: { numValid: Distribution, numInvalid: Distribution, } linkAlbum: Distribution, } export interface RandomCreateAlbumDistribution { linkArtists: { numValid: Distribution, numInvalid: Distribution, } linkTags: { numValid: Distribution, numInvalid: Distribution, } linkTracks: { numValid: Distribution, numInvalid: Distribution, } } export interface RandomCreateArtistDistribution { linkAlbums: { numValid: Distribution, numInvalid: Distribution, } linkTags: { numValid: Distribution, numInvalid: Distribution, } linkTracks: { numValid: Distribution, numInvalid: Distribution, } } export interface RandomCreateTagDistribution { linkParent: Distribution, } export interface RandomDeleteObjectDistribution { validId: Distribution, } export function applyDistribution( dist: Map, randomNumGen: any, ): T { let n = randomNumGen(); let r: T | undefined = undefined; dist.forEach((value: number, key: T) => { if (r) { return; } if (n <= value) { r = key; } else { n -= value; } }) if (r === undefined) { throw new Error(`Invalid distribution: n=${n}, dist ${JSON.stringify(dist.entries())}`); } return r; } export function randomString(randomNumGen: any, length: number) { let chars = 'abcdefghijklmnopqrstuvwxyz'; let retval = ''; for (let i = 0; i < length; i++) { retval += chars[Math.floor(randomNumGen() * 26)]; } return retval; } export function pickNFromArray( array: T[], randomNumGen: any, N: number) : T[] { let r: T[] = []; for (let i = 0; i < N; i++) { let idx = Math.floor(randomNumGen() * array.length); r.push(array[idx]); array.splice(idx); } return r; } export function applyReferenceDBAction( action: DBAction, db: ReferenceDatabase ): { response: any, status: number, } { let response: any = undefined; let status: number = 0; try { switch (action.type) { case DBActionType.CreateTrack: case DBActionType.CreateAlbum: case DBActionType.CreateArtist: case DBActionType.CreateTag: { let funcs = { [DBActionType.CreateTrack]: createTrack, [DBActionType.CreateAlbum]: createAlbum, [DBActionType.CreateArtist]: createArtist, [DBActionType.CreateTag]: createTag, } response = funcs[action.type](action.userId, action.payload, db); status = 200; break; } case DBActionType.DeleteTrack: case DBActionType.DeleteAlbum: case DBActionType.DeleteArtist: case DBActionType.DeleteTag: { let funcs = { [DBActionType.DeleteTrack]: deleteTrack, [DBActionType.DeleteAlbum]: deleteAlbum, [DBActionType.DeleteArtist]: deleteArtist, [DBActionType.DeleteTag]: deleteTag, } funcs[action.type](action.userId, action.payload, db); response = {}; status = 200; break; } } } catch (e) { if (isDBError(e)) { if (e.kind === DBErrorKind.ResourceNotFound) { status = 404; response = {}; } } else { throw e; } } return { response: response, status: status }; } export async function applyRealDBAction( action: DBAction, req: any, ): Promise<{ response: any, status: number, }> { let response: any = undefined; let status: number = 0; switch (action.type) { case DBActionType.CreateTrack: case DBActionType.CreateAlbum: case DBActionType.CreateArtist: case DBActionType.CreateTag: { let funcs = { [DBActionType.CreateTrack]: helpers.createTrack, [DBActionType.CreateAlbum]: helpers.createAlbum, [DBActionType.CreateArtist]: helpers.createArtist, [DBActionType.CreateTag]: helpers.createTag, } let res = await funcs[action.type](req, action.payload); status = res.status; response = res.body; break; } case DBActionType.DeleteTrack: case DBActionType.DeleteAlbum: case DBActionType.DeleteArtist: case DBActionType.DeleteTag: { let funcs = { [DBActionType.DeleteTrack]: helpers.deleteTrack, [DBActionType.DeleteAlbum]: helpers.deleteAlbum, [DBActionType.DeleteArtist]: helpers.deleteArtist, [DBActionType.DeleteTag]: helpers.deleteTag, } let res = await funcs[action.type](req, action.payload); status = res.status; response = res.body; break; } } return { response: response, status: status }; } export function randomDBAction( db: ReferenceDatabase, randomNumGen: any, distribution: RandomDBActionDistribution, ): DBAction { let type = applyDistribution( distribution.type, randomNumGen ); let userId = applyDistribution( distribution.userId, randomNumGen ); switch (type) { case DBActionType.CreateTrack: case DBActionType.CreateArtist: case DBActionType.CreateAlbum: case DBActionType.CreateTag: { let funcs = { [DBActionType.CreateTrack]: createRandomTrack, [DBActionType.CreateArtist]: createRandomArtist, [DBActionType.CreateAlbum]: createRandomAlbum, [DBActionType.CreateTag]: createRandomTag, } let params = { [DBActionType.CreateTrack]: distribution.createTrackParams, [DBActionType.CreateArtist]: distribution.createArtistParams, [DBActionType.CreateAlbum]: distribution.createAlbumParams, [DBActionType.CreateTag]: distribution.createTagParams, } return { type: type, payload: funcs[type]( db, userId, params[type] as any, randomNumGen ), userId: userId, }; } case DBActionType.DeleteTrack: case DBActionType.DeleteArtist: case DBActionType.DeleteAlbum: case DBActionType.DeleteTag: { let params = { [DBActionType.DeleteTrack]: distribution.deleteTrackParams, [DBActionType.DeleteArtist]: distribution.deleteArtistParams, [DBActionType.DeleteAlbum]: distribution.deleteAlbumParams, [DBActionType.DeleteTag]: distribution.deleteTagParams, } let objectArrays = { [DBActionType.DeleteTrack]: db[userId].tracks, [DBActionType.DeleteArtist]: db[userId].artists, [DBActionType.DeleteAlbum]: db[userId].albums, [DBActionType.DeleteTag]: db[userId].tags, } return { type: type, payload: applyDistribution(params[type].validId, randomNumGen) ? Math.floor(randomNumGen() * objectArrays[type].length) + 1 : Math.floor(randomNumGen() * objectArrays[type].length) + 1 + objectArrays[type].length, userId: userId, } } } } function pickArtists(userId: number, nValid: number, nInvalid: number, rng: any, db: ReferenceDatabase) { let allValidArtistIds: number[] = db[userId] && db[userId].artists ? db[userId].artists.map((a: ArtistWithRefsWithId) => a.id) : []; let validArtists: number[] = pickNFromArray(allValidArtistIds, rng, nValid); let invalidArtists: number[] = []; for (let i = 0; i < nInvalid; i++) { invalidArtists.push(Math.round(rng() * 100) + allValidArtistIds.length); } return [...validArtists, ...invalidArtists]; } function pickAlbums(userId: number, nValid: number, nInvalid: number, rng: any, db: ReferenceDatabase) { let allValidAlbumIds: number[] = db[userId] && db[userId].albums ? db[userId].albums.map((a: AlbumWithRefsWithId) => a.id) : []; let validAlbums: number[] = pickNFromArray(allValidAlbumIds, rng, nValid); let invalidAlbums: number[] = []; for (let i = 0; i < nInvalid; i++) { invalidAlbums.push(Math.round(rng() * 100) + allValidAlbumIds.length); } return [...validAlbums, ...invalidAlbums]; } function pickTracks(userId: number, nValid: number, nInvalid: number, rng: any, db: ReferenceDatabase) { let allValidTrackIds: number[] = db[userId] && db[userId].tracks ? db[userId].tracks.map((a: TrackWithRefsWithId) => a.id) : []; let validTracks: number[] = pickNFromArray(allValidTrackIds, rng, nValid); let invalidTracks: number[] = []; for (let i = 0; i < nInvalid; i++) { invalidTracks.push(Math.round(rng() * 100) + allValidTrackIds.length); } return [...validTracks, ...invalidTracks]; } function pickTags(userId: number, nValid: number, nInvalid: number, rng: any, db: ReferenceDatabase) { let allValidTagIds: number[] = db[userId] && db[userId].tags ? db[userId].tags.map((a: TagWithRefsWithId) => a.id) : []; let validTags: number[] = pickNFromArray(allValidTagIds, rng, nValid); let invalidTags: number[] = []; for (let i = 0; i < nInvalid; i++) { invalidTags.push(Math.round(rng() * 100) + allValidTagIds.length); } return [...validTags, ...invalidTags]; } export function createRandomTrack( db: ReferenceDatabase, userId: number, dist: RandomCreateTrackDistribution, randomNumGen: any, ): TrackWithRefs { let artists: number[] = pickArtists(userId, applyDistribution(dist.linkArtists.numValid, randomNumGen), applyDistribution(dist.linkArtists.numInvalid, randomNumGen), randomNumGen, db); let tags: number[] = pickTags(userId, applyDistribution(dist.linkTags.numValid, randomNumGen), applyDistribution(dist.linkTags.numInvalid, randomNumGen), randomNumGen, db); let maybeAlbum: number | null = (() => { let r: boolean | null | 'nonexistent' = applyDistribution(dist.linkAlbum, randomNumGen); let albums = pickAlbums(userId, r === true ? 1 : 0, r === 'nonexistent' ? 1 : 0, randomNumGen, db); return albums.length ? albums[0] : null; })(); return { mbApi_typename: 'track', albumId: maybeAlbum, artistIds: artists, tagIds: tags, name: randomString(randomNumGen, 20), storeLinks: [], // TODO } } export function createRandomArtist( db: ReferenceDatabase, userId: number, dist: RandomCreateArtistDistribution, randomNumGen: any, ): ArtistWithRefs { let tracks: number[] = pickTracks(userId, applyDistribution(dist.linkTracks.numValid, randomNumGen), applyDistribution(dist.linkTracks.numInvalid, randomNumGen), randomNumGen, db); let tags: number[] = pickTags(userId, applyDistribution(dist.linkTags.numValid, randomNumGen), applyDistribution(dist.linkTags.numInvalid, randomNumGen), randomNumGen, db); let albums: number[] = pickAlbums(userId, applyDistribution(dist.linkAlbums.numValid, randomNumGen), applyDistribution(dist.linkAlbums.numInvalid, randomNumGen), randomNumGen, db); return { mbApi_typename: 'artist', albumIds: albums, trackIds: tracks, tagIds: tags, name: randomString(randomNumGen, 20), storeLinks: [], // TODO } } export function createRandomAlbum( db: ReferenceDatabase, userId: number, dist: RandomCreateAlbumDistribution, randomNumGen: any, ): AlbumWithRefs { let tracks: number[] = pickTracks(userId, applyDistribution(dist.linkTracks.numValid, randomNumGen), applyDistribution(dist.linkTracks.numInvalid, randomNumGen), randomNumGen, db); let tags: number[] = pickTags(userId, applyDistribution(dist.linkTags.numValid, randomNumGen), applyDistribution(dist.linkTags.numInvalid, randomNumGen), randomNumGen, db); let artists: number[] = pickArtists(userId, applyDistribution(dist.linkArtists.numValid, randomNumGen), applyDistribution(dist.linkArtists.numInvalid, randomNumGen), randomNumGen, db); return { mbApi_typename: 'album', artistIds: artists, trackIds: tracks, tagIds: tags, name: randomString(randomNumGen, 20), storeLinks: [], // TODO } } export function createRandomTag( db: ReferenceDatabase, userId: number, dist: RandomCreateTagDistribution, randomNumGen: any, ): TagWithRefs { let maybeParent: number | null = (() => { let r: boolean | null | 'nonexistent' = applyDistribution(dist.linkParent, randomNumGen); let tags = pickTags(userId, r === true ? 1 : 0, r === 'nonexistent' ? 1 : 0, randomNumGen, db); return tags.length ? tags[0] : null; })(); return { mbApi_typename: 'tag', name: randomString(randomNumGen, 20), parentId: maybeParent, } }