You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

238 lines
7.1 KiB

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<T> = Map<T, number>;
export interface RandomDBActionDistribution {
type: Distribution<DBActionType>,
userId: Distribution<number>,
createTrackParams: RandomCreateTrackDistribution,
deleteTrackParams: RandomDeleteTrackDistribution,
}
export interface RandomCreateTrackDistribution {
linkArtists: {
numValid: Distribution<number>,
numInvalid: Distribution<number>,
}
linkTags: {
numValid: Distribution<number>,
numInvalid: Distribution<number>,
}
linkAlbum: Distribution<boolean | 'nonexistent'>,
}
export interface RandomDeleteTrackDistribution {
validTrack: Distribution<boolean>,
}
export function applyDistribution<T>(
dist: Map<T, number>,
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<T>(
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
}
}