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.
 
 
 
 

677 lines
25 KiB

import { AlbumBaseWithRefs, AlbumWithRefs, AlbumWithRefsWithId, ArtistBaseWithRefs, ArtistWithRefs, ArtistWithRefsWithId, TagBaseWithRefs, TagWithRefs, TagWithRefsWithId, TrackBaseWithRefs, TrackWithRefs, TrackWithRefsWithId } from "../../../client/src/api/api";
import { userEndpoints } from "../../endpoints/User";
import { createAlbum, createArtist, createTag, createTrack, deleteAlbum, deleteArtist, deleteTag, deleteTrack, modifyAlbum, modifyArtist, modifyTag, modifyTrack, ReferenceDatabase } from "./DBReferenceModel";
import * as helpers from '../integration/helpers';
import { DBErrorKind, isDBError } from "../../endpoints/types";
let _ = require('lodash');
export enum DBActionType {
CreateTrack = "CreateTrack",
CreateAlbum = "CreateAlbum",
CreateTag = "CreateTag",
CreateArtist = "CreateArtist",
DeleteTrack = "DeleteTrack",
DeleteAlbum = "DeleteAlbum",
DeleteArtist = "DeleteArtist",
DeleteTag = "DeleteTag",
PutTrack = "PutTrack",
PutAlbum = "PutAlbum",
PutArtist = "PutArtist",
PutTag = "PutTag",
PatchTrack = "PatchTrack",
PatchAlbum = "PatchAlbum",
PatchArtist = "PatchArtist",
PatchTag = "PatchTag",
}
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,
patchTrackParams: RandomPatchTrackDistribution,
patchAlbumParams: RandomPatchAlbumDistribution,
patchArtistParams: RandomPatchArtistDistribution,
patchTagParams: RandomPatchTagDistribution,
putTrackParams: RandomPutTrackDistribution,
putAlbumParams: RandomPutAlbumDistribution,
putArtistParams: RandomPutArtistDistribution,
putTagParams: RandomPutTagDistribution,
}
export interface RandomCreateTrackDistribution {
linkArtists: {
numValid: Distribution<number>,
numInvalid: Distribution<number>,
}
linkTags: {
numValid: Distribution<number>,
numInvalid: Distribution<number>,
}
linkAlbum: Distribution<boolean | 'nonexistent'>,
}
export interface RandomPutTrackDistribution extends RandomCreateTrackDistribution {
validId: Distribution<boolean>,
}
export interface RandomPatchTrackDistribution extends RandomPutTrackDistribution {
replaceName: Distribution<boolean>,
replaceArtists: Distribution<boolean>,
replaceTags: Distribution<boolean>,
replaceAlbum: Distribution<boolean>,
}
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 RandomPutAlbumDistribution extends RandomCreateAlbumDistribution {
validId: Distribution<boolean>,
}
export interface RandomPatchAlbumDistribution extends RandomPutAlbumDistribution {
replaceName: Distribution<boolean>,
replaceArtists: Distribution<boolean>,
replaceTags: Distribution<boolean>,
replaceTracks: Distribution<boolean>,
}
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 RandomPutArtistDistribution extends RandomCreateArtistDistribution {
validId: Distribution<boolean>,
}
export interface RandomPatchArtistDistribution extends RandomPutArtistDistribution {
replaceName: Distribution<boolean>,
replaceAlbums: Distribution<boolean>,
replaceTags: Distribution<boolean>,
replaceTracks: Distribution<boolean>,
}
export interface RandomCreateTagDistribution {
linkParent: Distribution<boolean | 'nonexistent'>,
}
export interface RandomPutTagDistribution extends RandomCreateTagDistribution {
validId: Distribution<boolean>,
}
export interface RandomPatchTagDistribution extends RandomPutTagDistribution {
replaceName: Distribution<boolean>,
replaceParent: Distribution<boolean>,
validId: Distribution<boolean>,
}
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;
}
case DBActionType.PutTrack:
case DBActionType.PutAlbum:
case DBActionType.PutArtist:
case DBActionType.PutTag:
case DBActionType.PatchTrack:
case DBActionType.PatchAlbum:
case DBActionType.PatchArtist:
case DBActionType.PatchTag:
{
let funcs = {
[DBActionType.PutTrack]: modifyTrack,
[DBActionType.PutAlbum]: modifyAlbum,
[DBActionType.PutArtist]: modifyArtist,
[DBActionType.PutTag]: modifyTag,
[DBActionType.PatchTrack]: modifyTrack,
[DBActionType.PatchAlbum]: modifyAlbum,
[DBActionType.PatchArtist]: modifyArtist,
[DBActionType.PatchTag]: modifyTag,
}
funcs[action.type](action.userId, action.payload.id, _.omit(action.payload, ['id']), db);
response = {};
status = 200;
break;
}
}
} catch (e) {
if (isDBError(e)) {
if (e.kind === DBErrorKind.ResourceNotFound) {
status = 404;
response = {};
} else if(e.kind === DBErrorKind.ResourceConflict) {
status = 409;
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;
}
case DBActionType.PutTrack:
case DBActionType.PutAlbum:
case DBActionType.PutArtist:
case DBActionType.PutTag:
case DBActionType.PatchTrack:
case DBActionType.PatchAlbum:
case DBActionType.PatchArtist:
case DBActionType.PatchTag:
{
let funcs = {
[DBActionType.PutTrack]: helpers.putTrack,
[DBActionType.PutAlbum]: helpers.putAlbum,
[DBActionType.PutArtist]: helpers.putArtist,
[DBActionType.PutTag]: helpers.putTag,
[DBActionType.PatchTrack]: helpers.patchTrack,
[DBActionType.PatchAlbum]: helpers.patchAlbum,
[DBActionType.PatchArtist]: helpers.patchArtist,
[DBActionType.PatchTag]: helpers.patchTag,
}
let res = await funcs[action.type](req, action.payload.id, _.omit(action.payload, ['id']));
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:
case DBActionType.PutTrack:
case DBActionType.PutAlbum:
case DBActionType.PutArtist:
case DBActionType.PutTag:
case DBActionType.PatchTrack:
case DBActionType.PatchAlbum:
case DBActionType.PatchArtist:
case DBActionType.PatchTag:
{
let funcs = {
[DBActionType.CreateTrack]: createRandomTrack,
[DBActionType.CreateArtist]: createRandomArtist,
[DBActionType.CreateAlbum]: createRandomAlbum,
[DBActionType.CreateTag]: createRandomTag,
[DBActionType.PutTrack]: createRandomTrack,
[DBActionType.PutArtist]: createRandomArtist,
[DBActionType.PutAlbum]: createRandomAlbum,
[DBActionType.PutTag]: createRandomTag,
[DBActionType.PatchTrack]: createPartialRandomTrack,
[DBActionType.PatchArtist]: createPartialRandomArtist,
[DBActionType.PatchAlbum]: createPartialRandomAlbum,
[DBActionType.PatchTag]: createPartialRandomTag,
}
let params = {
[DBActionType.CreateTrack]: distribution.createTrackParams,
[DBActionType.CreateArtist]: distribution.createArtistParams,
[DBActionType.CreateAlbum]: distribution.createAlbumParams,
[DBActionType.CreateTag]: distribution.createTagParams,
[DBActionType.PutTrack]: distribution.putTrackParams,
[DBActionType.PutArtist]: distribution.putArtistParams,
[DBActionType.PutAlbum]: distribution.putAlbumParams,
[DBActionType.PutTag]: distribution.putTagParams,
[DBActionType.PatchTrack]: distribution.patchTrackParams,
[DBActionType.PatchArtist]: distribution.patchArtistParams,
[DBActionType.PatchAlbum]: distribution.patchAlbumParams,
[DBActionType.PatchTag]: distribution.patchTagParams,
}
let objectArrays = {
[DBActionType.PatchTrack]: db[userId].tracks,
[DBActionType.PatchArtist]: db[userId].artists,
[DBActionType.PatchAlbum]: db[userId].albums,
[DBActionType.PatchTag]: db[userId].tags,
[DBActionType.PutTrack]: db[userId].tracks,
[DBActionType.PutArtist]: db[userId].artists,
[DBActionType.PutAlbum]: db[userId].albums,
[DBActionType.PutTag]: db[userId].tags,
}
let object: any = {
type: type,
payload: funcs[type](
db,
userId,
params[type] as any,
randomNumGen
),
userId: userId,
};
// For patch and put operations, we have to include an ID.
if ([DBActionType.PatchTrack,
DBActionType.PatchTag,
DBActionType.PatchAlbum,
DBActionType.PatchArtist,
DBActionType.PutTrack,
DBActionType.PutTag,
DBActionType.PutAlbum,
DBActionType.PutArtist].includes(type)) {
let _type = type as
DBActionType.PatchTrack | DBActionType.PatchTag | DBActionType.PatchAlbum | DBActionType.PatchArtist
| DBActionType.PutTrack | DBActionType.PutTag | DBActionType.PutAlbum | DBActionType.PutArtist
let validIdx = Math.floor(randomNumGen() * objectArrays[_type].length);
let validId = objectArrays[_type][validIdx] ? objectArrays[_type][validIdx].id : 1;
object.payload.id = applyDistribution(params[_type].validId, randomNumGen) ? validId : validId + 99999;
}
return object;
}
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,
}
let validIdx = Math.floor(randomNumGen() * objectArrays[type].length);
let validId = objectArrays[type][validIdx] ? objectArrays[type][validIdx].id : 1;
return {
type: type,
payload: applyDistribution(params[type].validId, randomNumGen) ? validId : validId + 99999,
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 createPartialRandomTrack(
db: ReferenceDatabase,
userId: number,
dist: RandomPatchTrackDistribution,
randomNumGen: any,
): TrackBaseWithRefs {
let track: TrackBaseWithRefs = createRandomTrack(db, userId, dist, randomNumGen)
if (!applyDistribution(dist.replaceName, randomNumGen)) { delete track.name }
if (!applyDistribution(dist.replaceTags, randomNumGen)) { delete track.tagIds }
if (!applyDistribution(dist.replaceAlbum, randomNumGen)) { delete track.albumId }
if (!applyDistribution(dist.replaceArtists, randomNumGen)) { delete track.artistIds }
return track;
}
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 createPartialRandomArtist(
db: ReferenceDatabase,
userId: number,
dist: RandomPatchArtistDistribution,
randomNumGen: any,
): ArtistBaseWithRefs {
let artist: ArtistBaseWithRefs = createRandomArtist(db, userId, dist, randomNumGen)
if (!applyDistribution(dist.replaceName, randomNumGen)) { delete artist.name }
if (!applyDistribution(dist.replaceTags, randomNumGen)) { delete artist.tagIds }
if (!applyDistribution(dist.replaceAlbums, randomNumGen)) { delete artist.albumIds }
if (!applyDistribution(dist.replaceTracks, randomNumGen)) { delete artist.trackIds }
return artist;
}
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 createPartialRandomAlbum(
db: ReferenceDatabase,
userId: number,
dist: RandomPatchAlbumDistribution,
randomNumGen: any,
): AlbumBaseWithRefs {
let album: AlbumBaseWithRefs = createRandomAlbum(db, userId, dist, randomNumGen)
if (!applyDistribution(dist.replaceName, randomNumGen)) { delete album.name }
if (!applyDistribution(dist.replaceTags, randomNumGen)) { delete album.tagIds }
if (!applyDistribution(dist.replaceArtists, randomNumGen)) { delete album.artistIds }
if (!applyDistribution(dist.replaceTracks, randomNumGen)) { delete album.trackIds }
return album;
}
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,
}
}
export function createPartialRandomTag(
db: ReferenceDatabase,
userId: number,
dist: RandomPatchTagDistribution,
randomNumGen: any,
): TagBaseWithRefs {
let tag: TagBaseWithRefs = createRandomTag(db, userId, dist, randomNumGen)
if (!applyDistribution(dist.replaceName, randomNumGen)) { delete tag.name }
if (!applyDistribution(dist.replaceParent, randomNumGen)) { delete tag.parentId }
return tag;
}