import { AlbumWithRefs, AlbumWithRefsWithId, ArtistWithRefs, ArtistWithRefsWithId, TagWithRefs, TagWithRefsWithId, TrackWithRefs, TrackWithRefsWithId } from "../../../client/src/api/api"; import { userEndpoints } from "../../endpoints/User"; import { createAlbum, createArtist, createTag, createTrack, 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", } 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: RandomDeleteTrackDistribution, } 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 RandomDeleteTrackDistribution { validTrack: 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: { response = createTrack(action.userId, action.payload, db); status = 200; break; } case DBActionType.CreateAlbum: { response = createAlbum(action.userId, action.payload, db); status = 200; break; } case DBActionType.CreateArtist: { response = createArtist(action.userId, action.payload, db); status = 200; break; } case DBActionType.CreateTag: { response = createTag(action.userId, action.payload, db); status = 200; break; } case DBActionType.DeleteTrack: { deleteTrack(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: { let res = await helpers.createTrack(req, action.payload); status = res.status; response = res.body; break; } case DBActionType.CreateAlbum: { let res = await helpers.createAlbum(req, action.payload); status = res.status; response = res.body; break; } case DBActionType.CreateArtist: { let res = await helpers.createArtist(req, action.payload); status = res.status; response = res.body; break; } case DBActionType.CreateTag: { let res = await helpers.createTag(req, action.payload); status = res.status; response = res.body; break; } case DBActionType.DeleteTrack: { let res = await helpers.deleteTrack(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: { return { type: type, payload: createRandomTrack( db, userId, distribution.createTrackParams, randomNumGen ), userId: userId, }; } case DBActionType.CreateArtist: { return { type: type, payload: createRandomArtist( db, userId, distribution.createArtistParams, randomNumGen ), userId: userId, }; } case DBActionType.CreateAlbum: { return { type: type, payload: createRandomAlbum( db, userId, distribution.createAlbumParams, randomNumGen ), userId: userId, }; } case DBActionType.CreateTag: { return { type: type, payload: createRandomTag( db, userId, distribution.createTagParams, randomNumGen ), userId: userId, }; } case DBActionType.DeleteTrack: { return { type: type, payload: applyDistribution(distribution.deleteTrackParams.validTrack, randomNumGen) ? Math.floor(randomNumGen() * db[userId].tracks.length) + 1 : Math.floor(randomNumGen() * db[userId].tracks.length) + 1 + db[userId].tracks.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, } }