import { AlbumWithRefsWithId, ArtistWithRefsWithId, TagWithRefsWithId, TrackWithRefs } from "../../../client/src/api/api"; import { userEndpoints } from "../../endpoints/User"; import { createTrack, deleteTrack, ReferenceDatabase } from "./DBReferenceModel"; import * as helpers from '../integration/helpers'; import { DBErrorKind, isDBError } from "../../endpoints/types"; export enum DBActionType { CreateTrack = 0, DeleteTrack, } export interface DBAction { type: DBActionType, userId: number, payload: any, } export type Distribution = Map; export interface RandomDBActionDistribution { type: Distribution, userId: Distribution, createTrackParams: RandomCreateTrackDistribution, deleteTrackParams: RandomDeleteTrackDistribution, } export interface RandomCreateTrackDistribution { linkArtists: { numValid: Distribution, numInvalid: Distribution, } linkTags: { numValid: Distribution, numInvalid: Distribution, } linkAlbum: 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.DeleteTrack: { deleteTrack(action.userId, action.payload, db); response = {}; status = 200; break; } } } catch(e) { if(isDBError(e)) { if(e.kind === DBErrorKind.ResourceNotFound) { status = 404; response = {}; } } } 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.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.DeleteTrack: { return { type: type, payload: applyDistribution(distribution.deleteTrackParams.validTrack, randomNumGen) ? Math.floor(Math.random() * db[userId].tracks.length) + 1 : Math.floor(Math.random() * db[userId].tracks.length) + 1 + db[userId].tracks.length, userId: userId, } } } } export function createRandomTrack( db: ReferenceDatabase, userId: number, trackDist: RandomCreateTrackDistribution, randomNumGen: any, ): TrackWithRefs { let allValidArtistIds: number[] = db[userId] && db[userId].artists ? db[userId].artists.map((a: ArtistWithRefsWithId) => a.id) : []; let allValidTagIds: number[] = db[userId] && db[userId].tags ? db[userId].tags.map((a: TagWithRefsWithId) => a.id) : []; let allValidAlbumIds: number[] = db[userId] && db[userId].albums ? db[userId].albums.map((a: AlbumWithRefsWithId) => a.id) : []; let artists: number[] = (() => { let validArtists: number[] = pickNFromArray(allValidArtistIds, randomNumGen, applyDistribution(trackDist.linkArtists.numValid, randomNumGen)); let invalidArtists: number[] = []; for (let i = 0; i < applyDistribution(trackDist.linkArtists.numInvalid, randomNumGen); i++) { invalidArtists.push(Math.round(Math.random() * 100) + allValidArtistIds.length); } return [...validArtists, ...invalidArtists]; })(); let tags: number[] = (() => { let validTags: number[] = pickNFromArray(allValidTagIds, randomNumGen, applyDistribution(trackDist.linkTags.numValid, randomNumGen)); let invalidTags: number[] = []; for (let i = 0; i < applyDistribution(trackDist.linkTags.numInvalid, randomNumGen); i++) { invalidTags.push(Math.round(Math.random() * 100) + allValidTagIds.length); } return [...validTags, ...invalidTags]; })(); let maybeAlbum: number | null = (() => { let r: boolean | null | 'nonexistent' = applyDistribution(trackDist.linkAlbum, randomNumGen); let maybeValidAlbum: number | null = r === true && allValidAlbumIds.length ? pickNFromArray(allValidAlbumIds, randomNumGen, 1)[0] : null; let maybeInvalidAlbum: number | null = r === 'nonexistent' ? allValidAlbumIds.length + 1 : null; return maybeValidAlbum || maybeInvalidAlbum; })(); return { mbApi_typename: 'track', albumId: maybeAlbum, artistIds: artists, tagIds: tags, name: randomString(randomNumGen, 20), storeLinks: [], // TODO } }