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.
 
 
 
 

469 lines
16 KiB

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<T> = Map<T, number>;
export interface RandomDBActionDistribution {
type: Distribution<DBActionType>,
userId: Distribution<number>,
createTrackParams: RandomCreateTrackDistribution,
createAlbumParams: RandomCreateAlbumDistribution,
createArtistParams: RandomCreateArtistDistribution,
createTagParams: RandomCreateTagDistribution,
deleteTrackParams: RandomDeleteObjectDistribution,
deleteArtistParams: RandomDeleteObjectDistribution,
deleteAlbumParams: RandomDeleteObjectDistribution,
deleteTagParams: RandomDeleteObjectDistribution,
}
export interface RandomCreateTrackDistribution {
linkArtists: {
numValid: Distribution<number>,
numInvalid: Distribution<number>,
}
linkTags: {
numValid: Distribution<number>,
numInvalid: Distribution<number>,
}
linkAlbum: Distribution<boolean | 'nonexistent'>,
}
export interface RandomCreateAlbumDistribution {
linkArtists: {
numValid: Distribution<number>,
numInvalid: Distribution<number>,
}
linkTags: {
numValid: Distribution<number>,
numInvalid: Distribution<number>,
}
linkTracks: {
numValid: Distribution<number>,
numInvalid: Distribution<number>,
}
}
export interface RandomCreateArtistDistribution {
linkAlbums: {
numValid: Distribution<number>,
numInvalid: Distribution<number>,
}
linkTags: {
numValid: Distribution<number>,
numInvalid: Distribution<number>,
}
linkTracks: {
numValid: Distribution<number>,
numInvalid: Distribution<number>,
}
}
export interface RandomCreateTagDistribution {
linkParent: Distribution<boolean | 'nonexistent'>,
}
export interface RandomDeleteObjectDistribution {
validId: 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:
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,
}
}