parent
ddb8d16d13
commit
9af9b55d39
26 changed files with 1584 additions and 1304 deletions
@ -0,0 +1,23 @@ |
||||
{ |
||||
// Use IntelliSense to learn about possible attributes. |
||||
// Hover to view descriptions of existing attributes. |
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 |
||||
"version": "0.2.0", |
||||
"configurations": [ |
||||
{ |
||||
"type": "node", |
||||
"request": "launch", |
||||
"name": "Jasmine Tests with SQLite", |
||||
"env": { |
||||
"MUDBASE_DB_CONFIG": "{\"client\": \"sqlite3\", \"connection\": \":memory:\"}" |
||||
}, |
||||
"program": "${workspaceFolder}/server/node_modules/jasmine-ts/lib/index", |
||||
"args": [ |
||||
"--config=test/jasmine.json", |
||||
], |
||||
"console": "integratedTerminal", |
||||
"cwd": "${workspaceFolder}/server", |
||||
"internalConsoleOptions": "neverOpen" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,10 @@ |
||||
import { DBError, DBErrorKind } from "../endpoints/types"; |
||||
|
||||
export function makeNotFoundError() { |
||||
const e: DBError = { |
||||
name: "DBError", |
||||
kind: DBErrorKind.ResourceNotFound, |
||||
message: 'Not all to-be-linked resources were found.', |
||||
}; |
||||
return e; |
||||
} |
@ -0,0 +1,12 @@ |
||||
export default function filterInPlace<T>(a: T[], condition: (value: T, index: number, array: T[]) => boolean): T[] { |
||||
let i = 0, j = 0; |
||||
|
||||
while (i < a.length) { |
||||
const val = a[i]; |
||||
if (condition(val, i, a)) a[j++] = val; |
||||
i++; |
||||
} |
||||
|
||||
a.length = j; |
||||
return a; |
||||
} |
@ -1,103 +0,0 @@ |
||||
const chai = require('chai'); |
||||
const chaiHttp = require('chai-http'); |
||||
const express = require('express'); |
||||
import { SetupApp } from '../../../app'; |
||||
import { expect } from 'chai'; |
||||
import * as helpers from './helpers'; |
||||
import { sha512 } from 'js-sha512'; |
||||
|
||||
async function init() { |
||||
chai.use(chaiHttp); |
||||
const app = express(); |
||||
const knex = await helpers.initTestDB(); |
||||
|
||||
// Add test users.
|
||||
await knex.insert({ email: "test1@test.com", passwordHash: sha512('pass1') }).into('users'); |
||||
await knex.insert({ email: "test2@test.com", passwordHash: sha512('pass2') }).into('users'); |
||||
|
||||
SetupApp(app, knex, ''); |
||||
|
||||
// Login as a test user.
|
||||
var agent = chai.request.agent(app); |
||||
await agent |
||||
.post('/login?username=' + encodeURIComponent("test1@test.com") + '&password=' + encodeURIComponent('pass1')) |
||||
.send({}); |
||||
return agent; |
||||
} |
||||
|
||||
describe('POST /album with no name', () => { |
||||
it('should fail', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createAlbum(req, {}, 400); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /album with a correct request', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createAlbum(req, { name: "MyAlbum" }, 200, { id: 1 }); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
|
||||
describe('PUT /album on nonexistent album', () => { |
||||
it('should fail', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.modifyAlbum(req, 1, { id: 1, name: "NewAlbumName" }, 400); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('PUT /album with an existing album', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createAlbum(req, { name: "MyAlbum" }, 200, { id: 1 }); |
||||
await helpers.modifyAlbum(req, 1, { name: "MyNewAlbum" }, 200); |
||||
await helpers.checkAlbum(req, 1, 200, { name: "MyNewAlbum", storeLinks: [], tagIds: [], songIds: [], artistIds: [] }); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /album with tags', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createTag(req, { name: "Root" }, 200, { id: 1 }) |
||||
await helpers.createTag(req, { name: "Leaf", parentId: 1 }, 200, { id: 2 }) |
||||
await helpers.createAlbum(req, { name: "MyAlbum", tagIds: [1, 2] }, 200, { id: 1 }) |
||||
await helpers.checkAlbum(req, 1, 200, { name: "MyAlbum", storeLinks: [], tagIds: [1, 2], songIds: [], artistIds: [] }) |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
@ -1,102 +0,0 @@ |
||||
const chai = require('chai'); |
||||
const chaiHttp = require('chai-http'); |
||||
const express = require('express'); |
||||
import { SetupApp } from '../../../app'; |
||||
import * as helpers from './helpers'; |
||||
import { sha512 } from 'js-sha512'; |
||||
|
||||
async function init() { |
||||
chai.use(chaiHttp); |
||||
const app = express(); |
||||
const knex = await helpers.initTestDB(); |
||||
|
||||
// Add test users.
|
||||
await knex.insert({ email: "test1@test.com", passwordHash: sha512('pass1') }).into('users'); |
||||
await knex.insert({ email: "test2@test.com", passwordHash: sha512('pass2') }).into('users'); |
||||
|
||||
SetupApp(app, knex, ''); |
||||
|
||||
// Login as a test user.
|
||||
var agent = chai.request.agent(app); |
||||
await agent |
||||
.post('/login?username=' + encodeURIComponent("test1@test.com") + '&password=' + encodeURIComponent('pass1')) |
||||
.send({}); |
||||
return agent; |
||||
} |
||||
|
||||
describe('POST /artist with no name', () => { |
||||
it('should fail', async done => { |
||||
let agent = await init(); |
||||
var req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createArtist(req, {}, 400); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /artist with a correct request', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
var req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createArtist(req, { name: "MyArtist" }, 200, { id: 1 }); |
||||
await helpers.checkArtist(req, 1, 200, { name: "MyArtist", storeLinks: [], tagIds: [] }); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('PUT /artist on nonexistent artist', () => { |
||||
it('should fail', async done => { |
||||
let agent = await init(); |
||||
var req = agent.keepOpen(); |
||||
try { |
||||
await helpers.modifyArtist(req, 0, { id: 0, name: "NewArtistName" }, 400) |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('PUT /artist with an existing artist', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
var req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createArtist(req, { name: "MyArtist" }, 200, { id: 1 }); |
||||
await helpers.modifyArtist(req, 1, { name: "MyNewArtist" }, 200); |
||||
await helpers.checkArtist(req, 1, 200, { name: "MyNewArtist", storeLinks: [], tagIds: [] }); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /artist with tags', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
var req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createTag(req, { name: "Root" }, 200, { id: 1 }); |
||||
await helpers.createTag(req, { name: "Leaf", parentId: 1 }, 200, { id: 2 }); |
||||
await helpers.createArtist(req, { name: "MyArtist", tagIds: [1, 2] }, 200, { id: 1 }); |
||||
await helpers.checkArtist(req, 1, 200, { name: "MyArtist", storeLinks: [], tagIds: [1, 2] }); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
@ -1,145 +0,0 @@ |
||||
const chai = require('chai'); |
||||
const chaiHttp = require('chai-http'); |
||||
const express = require('express'); |
||||
import { SetupApp } from '../../../app'; |
||||
import * as helpers from './helpers'; |
||||
|
||||
async function init() { |
||||
chai.use(chaiHttp); |
||||
const app = express(); |
||||
const knex = await helpers.initTestDB(); |
||||
|
||||
SetupApp(app, knex, ''); |
||||
|
||||
// Login as a test user.
|
||||
var agent = chai.request.agent(app); |
||||
return agent; |
||||
} |
||||
|
||||
describe('Auth registration password and email constraints', () => { |
||||
it('are enforced', async done => { |
||||
let req = await init(); |
||||
try { |
||||
await helpers.createUser(req, "someone", "password1A!", 400); //no valid email
|
||||
await helpers.createUser(req, "someone@email.com", "password1A", 400); //no special char
|
||||
await helpers.createUser(req, "someone@email.com", "password1!", 400); //no capital letter
|
||||
await helpers.createUser(req, "someone@email.com", "passwordA!", 400); //no number
|
||||
await helpers.createUser(req, "someone@email.com", "Ϭassword1A!", 400); //non-ASCII in password
|
||||
await helpers.createUser(req, "Ϭomeone@email.com", "password1A!", 400); //non-ASCII in email
|
||||
await helpers.createUser(req, "someone@email.com", "pass1A!", 400); //password too short
|
||||
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||
} finally { |
||||
req.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('Attempting to register an already registered user', () => { |
||||
it('should fail', async done => { |
||||
let req = await init(); |
||||
try { |
||||
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||
await helpers.createUser(req, "someone@email.com", "password1A!", 400); |
||||
} finally { |
||||
req.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('Auth login access for users', () => { |
||||
it('is correctly enforced', async done => { |
||||
let req = await init(); |
||||
try { |
||||
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||
await helpers.createUser(req, "someoneelse@other.com", "password2B!", 200); |
||||
await helpers.login(req, "someone@email.com", "password2B!", 401); |
||||
await helpers.login(req, "someoneelse@other.com", "password1A!", 401); |
||||
await helpers.login(req, "someone@email.com", "password1A!", 200); |
||||
await helpers.login(req, "someoneelse@other.com", "password2B!", 200); |
||||
} finally { |
||||
req.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('Auth access to objects', () => { |
||||
it('is only possible when logged in', async done => { |
||||
let req = await init(); |
||||
try { |
||||
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||
await helpers.login(req, "someone@email.com", "password1A!", 200); |
||||
|
||||
await helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 }); |
||||
await helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1} ); |
||||
await helpers.createAlbum(req, { name: "Album1" }, 200, { id: 1 }); |
||||
await helpers.createSong(req, { title: "Song1" }, 200, { id: 1 }); |
||||
|
||||
await helpers.checkTag(req, 1, 200); |
||||
await helpers.checkAlbum(req, 1, 200); |
||||
await helpers.checkArtist(req, 1, 200); |
||||
await helpers.checkSong(req, 1, 200); |
||||
|
||||
await helpers.logout(req, 200); |
||||
|
||||
await helpers.checkTag(req, 1, 401); |
||||
await helpers.checkAlbum(req, 1, 401); |
||||
await helpers.checkArtist(req, 1, 401); |
||||
await helpers.checkSong(req, 1, 401); |
||||
} finally { |
||||
req.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('Auth access to user objects', () => { |
||||
it('is restricted to each user', async done => { |
||||
let req = await init(); |
||||
try { |
||||
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||
await helpers.createUser(req, "someoneelse@other.com", "password2B!", 200); |
||||
|
||||
await helpers.login(req, "someone@email.com", "password1A!", 200); |
||||
await helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 }); |
||||
await helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1} ); |
||||
await helpers.createAlbum(req, { name: "Album1" }, 200, { id: 1 }); |
||||
await helpers.createSong(req, { title: "Song1" }, 200, { id: 1 }); |
||||
await helpers.logout(req, 200); |
||||
|
||||
await helpers.login(req, "someoneelse@other.com", "password2B!", 200); |
||||
await helpers.createTag(req, { name: "Tag2" }, 200, { id: 2 }); |
||||
await helpers.createArtist(req, { name: "Artist2" }, 200, { id: 2 } ); |
||||
await helpers.createAlbum(req, { name: "Album2" }, 200, { id: 2 }); |
||||
await helpers.createSong(req, { title: "Song2" }, 200, { id: 2 }); |
||||
await helpers.logout(req, 200); |
||||
|
||||
await helpers.login(req, "someone@email.com", "password1A!", 200); |
||||
await helpers.checkTag(req, 2, 404); |
||||
await helpers.checkAlbum(req, 2, 404); |
||||
await helpers.checkArtist(req, 2, 404); |
||||
await helpers.checkSong(req, 2, 404); |
||||
await helpers.checkTag(req, 1, 200); |
||||
await helpers.checkAlbum(req, 1, 200); |
||||
await helpers.checkArtist(req, 1, 200); |
||||
await helpers.checkSong(req, 1, 200); |
||||
await helpers.logout(req, 200); |
||||
|
||||
await helpers.login(req, "someoneelse@other.com", "password2B!", 200); |
||||
await helpers.checkTag(req, 1, 404); |
||||
await helpers.checkAlbum(req, 1, 404); |
||||
await helpers.checkArtist(req, 1, 404); |
||||
await helpers.checkSong(req, 1, 404); |
||||
await helpers.checkTag(req, 2, 200); |
||||
await helpers.checkAlbum(req, 2, 200); |
||||
await helpers.checkArtist(req, 2, 200); |
||||
await helpers.checkSong(req, 2, 200); |
||||
await helpers.logout(req, 200); |
||||
} finally { |
||||
req.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
@ -0,0 +1,145 @@ |
||||
const chai = require('chai'); |
||||
const chaiHttp = require('chai-http'); |
||||
const express = require('express'); |
||||
import { SetupApp } from '../../../app'; |
||||
import * as helpers from '../helpers'; |
||||
|
||||
async function init() { |
||||
chai.use(chaiHttp); |
||||
const app = express(); |
||||
const knex = await helpers.initTestDB(); |
||||
|
||||
SetupApp(app, knex, ''); |
||||
|
||||
// Login as a test user.
|
||||
var agent = chai.request.agent(app); |
||||
return agent; |
||||
} |
||||
|
||||
describe('Auth registration password and email constraints', () => { |
||||
it('are enforced', async done => { |
||||
let req = await init(); |
||||
try { |
||||
await helpers.createUser(req, "someone", "password1A!", 400); //no valid email
|
||||
await helpers.createUser(req, "someone@email.com", "password1A", 400); //no special char
|
||||
await helpers.createUser(req, "someone@email.com", "password1!", 400); //no capital letter
|
||||
await helpers.createUser(req, "someone@email.com", "passwordA!", 400); //no number
|
||||
await helpers.createUser(req, "someone@email.com", "Ϭassword1A!", 400); //non-ASCII in password
|
||||
await helpers.createUser(req, "Ϭomeone@email.com", "password1A!", 400); //non-ASCII in email
|
||||
await helpers.createUser(req, "someone@email.com", "pass1A!", 400); //password too short
|
||||
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||
} finally { |
||||
req.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('Attempting to register an already registered user', () => { |
||||
it('should fail', async done => { |
||||
let req = await init(); |
||||
try { |
||||
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||
await helpers.createUser(req, "someone@email.com", "password1A!", 409); |
||||
} finally { |
||||
req.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('Auth login access for users', () => { |
||||
it('is correctly enforced', async done => { |
||||
let req = await init(); |
||||
try { |
||||
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||
await helpers.createUser(req, "someoneelse@other.com", "password2B!", 200); |
||||
await helpers.login(req, "someone@email.com", "password2B!", 401); |
||||
await helpers.login(req, "someoneelse@other.com", "password1A!", 401); |
||||
await helpers.login(req, "someone@email.com", "password1A!", 200); |
||||
await helpers.login(req, "someoneelse@other.com", "password2B!", 200); |
||||
} finally { |
||||
req.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
// describe('Auth access to objects', () => {
|
||||
// it('is only possible when logged in', async done => {
|
||||
// let req = await init();
|
||||
// try {
|
||||
// await helpers.createUser(req, "someone@email.com", "password1A!", 200);
|
||||
// await helpers.login(req, "someone@email.com", "password1A!", 200);
|
||||
|
||||
// await helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 });
|
||||
// await helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1} );
|
||||
// await helpers.createAlbum(req, { name: "Album1" }, 200, { id: 1 });
|
||||
// await helpers.createSong(req, { title: "Song1" }, 200, { id: 1 });
|
||||
|
||||
// await helpers.checkTag(req, 1, 200);
|
||||
// await helpers.checkAlbum(req, 1, 200);
|
||||
// await helpers.checkArtist(req, 1, 200);
|
||||
// await helpers.checkSong(req, 1, 200);
|
||||
|
||||
// await helpers.logout(req, 200);
|
||||
|
||||
// await helpers.checkTag(req, 1, 401);
|
||||
// await helpers.checkAlbum(req, 1, 401);
|
||||
// await helpers.checkArtist(req, 1, 401);
|
||||
// await helpers.checkSong(req, 1, 401);
|
||||
// } finally {
|
||||
// req.close();
|
||||
// done();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe('Auth access to user objects', () => {
|
||||
// it('is restricted to each user', async done => {
|
||||
// let req = await init();
|
||||
// try {
|
||||
// await helpers.createUser(req, "someone@email.com", "password1A!", 200);
|
||||
// await helpers.createUser(req, "someoneelse@other.com", "password2B!", 200);
|
||||
|
||||
// await helpers.login(req, "someone@email.com", "password1A!", 200);
|
||||
// await helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 });
|
||||
// await helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1} );
|
||||
// await helpers.createAlbum(req, { name: "Album1" }, 200, { id: 1 });
|
||||
// await helpers.createSong(req, { title: "Song1" }, 200, { id: 1 });
|
||||
// await helpers.logout(req, 200);
|
||||
|
||||
// await helpers.login(req, "someoneelse@other.com", "password2B!", 200);
|
||||
// await helpers.createTag(req, { name: "Tag2" }, 200, { id: 2 });
|
||||
// await helpers.createArtist(req, { name: "Artist2" }, 200, { id: 2 } );
|
||||
// await helpers.createAlbum(req, { name: "Album2" }, 200, { id: 2 });
|
||||
// await helpers.createSong(req, { title: "Song2" }, 200, { id: 2 });
|
||||
// await helpers.logout(req, 200);
|
||||
|
||||
// await helpers.login(req, "someone@email.com", "password1A!", 200);
|
||||
// await helpers.checkTag(req, 2, 404);
|
||||
// await helpers.checkAlbum(req, 2, 404);
|
||||
// await helpers.checkArtist(req, 2, 404);
|
||||
// await helpers.checkSong(req, 2, 404);
|
||||
// await helpers.checkTag(req, 1, 200);
|
||||
// await helpers.checkAlbum(req, 1, 200);
|
||||
// await helpers.checkArtist(req, 1, 200);
|
||||
// await helpers.checkSong(req, 1, 200);
|
||||
// await helpers.logout(req, 200);
|
||||
|
||||
// await helpers.login(req, "someoneelse@other.com", "password2B!", 200);
|
||||
// await helpers.checkTag(req, 1, 404);
|
||||
// await helpers.checkAlbum(req, 1, 404);
|
||||
// await helpers.checkArtist(req, 1, 404);
|
||||
// await helpers.checkSong(req, 1, 404);
|
||||
// await helpers.checkTag(req, 2, 200);
|
||||
// await helpers.checkAlbum(req, 2, 200);
|
||||
// await helpers.checkArtist(req, 2, 200);
|
||||
// await helpers.checkSong(req, 2, 200);
|
||||
// await helpers.logout(req, 200);
|
||||
// } finally {
|
||||
// req.close();
|
||||
// done();
|
||||
// }
|
||||
// });
|
||||
// });
|
@ -1,127 +0,0 @@ |
||||
const chai = require('chai'); |
||||
const chaiHttp = require('chai-http'); |
||||
const express = require('express'); |
||||
import { SetupApp } from '../../../app'; |
||||
import * as helpers from './helpers'; |
||||
import { sha512 } from 'js-sha512'; |
||||
import { IntegrationImpl } from '../../../../client/src/api'; |
||||
|
||||
async function init() { |
||||
chai.use(chaiHttp); |
||||
const app = express(); |
||||
const knex = await helpers.initTestDB(); |
||||
|
||||
// Add test users.
|
||||
await knex.insert({ email: "test1@test.com", passwordHash: sha512('pass1') }).into('users'); |
||||
await knex.insert({ email: "test2@test.com", passwordHash: sha512('pass2') }).into('users'); |
||||
|
||||
SetupApp(app, knex, ''); |
||||
|
||||
// Login as a test user.
|
||||
var agent = chai.request.agent(app); |
||||
await agent |
||||
.post('/login?username=' + encodeURIComponent("test1@test.com") + '&password=' + encodeURIComponent('pass1')) |
||||
.send({}); |
||||
return agent; |
||||
} |
||||
|
||||
describe('POST /integration with missing or wrong data', () => { |
||||
it('should fail', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createIntegration(req, { type: IntegrationImpl.SpotifyClientCredentials, details: {}, secretDetails: {} }, 400); |
||||
await helpers.createIntegration(req, { name: "A", details: {}, secretDetails: {} }, 400); |
||||
await helpers.createIntegration(req, { name: "A", type: IntegrationImpl.SpotifyClientCredentials, secretDetails: {} }, 400); |
||||
await helpers.createIntegration(req, { name: "A", type: IntegrationImpl.SpotifyClientCredentials, details: {}, }, 400); |
||||
await helpers.createIntegration(req, { name: "A", type: "NonexistentType", details: {}, secretDetails: {} }, 400); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /integration with a correct request', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createIntegration(req, { name: "A", type: IntegrationImpl.SpotifyClientCredentials, details: {}, secretDetails: {} }, 200, { id: 1 }); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('PUT /integration with a correct request', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createIntegration(req, { name: "A", type: IntegrationImpl.SpotifyClientCredentials, details: {}, secretDetails: {} }, 200, { id: 1 }); |
||||
await helpers.modifyIntegration(req, 1, { name: "B", type: IntegrationImpl.SpotifyClientCredentials, details: { secret: 'cat' }, secretDetails: {} }, 200); |
||||
await helpers.checkIntegration(req, 1, 200, { name: "B", type: IntegrationImpl.SpotifyClientCredentials, details: { secret: 'cat' } }) |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('PUT /integration with wrong data', () => { |
||||
it('should fail', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createIntegration(req, { name: "A", type: IntegrationImpl.SpotifyClientCredentials, details: {}, secretDetails: {} }, 200, { id: 1 }); |
||||
await helpers.modifyIntegration(req, 1, { name: "B", type: "UnknownType", details: {}, secretDetails: {} }, 400); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('DELETE /integration with a correct request', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createIntegration(req, { name: "A", type: IntegrationImpl.SpotifyClientCredentials, details: {}, secretDetails: {} }, 200, { id: 1 }); |
||||
await helpers.checkIntegration(req, 1, 200, { name: "A", type: IntegrationImpl.SpotifyClientCredentials, details: {} }) |
||||
await helpers.deleteIntegration(req, 1, 200); |
||||
await helpers.checkIntegration(req, 1, 404); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('GET /integration list with a correct request', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createIntegration(req, { name: "A", type: IntegrationImpl.SpotifyClientCredentials, details: {}, secretDetails: {} }, 200, { id: 1 }); |
||||
await helpers.createIntegration(req, { name: "B", type: IntegrationImpl.SpotifyClientCredentials, details: {}, secretDetails: {} }, 200, { id: 2 }); |
||||
await helpers.createIntegration(req, { name: "C", type: IntegrationImpl.SpotifyClientCredentials, details: {}, secretDetails: {} }, 200, { id: 3 }); |
||||
await helpers.listIntegrations(req, 200, [ |
||||
{ id: 1, name: "A", type: IntegrationImpl.SpotifyClientCredentials, details: {} }, |
||||
{ id: 2, name: "B", type: IntegrationImpl.SpotifyClientCredentials, details: {} }, |
||||
{ id: 3, name: "C", type: IntegrationImpl.SpotifyClientCredentials, details: {} }, |
||||
]); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
@ -1,384 +0,0 @@ |
||||
const chai = require('chai'); |
||||
const chaiHttp = require('chai-http'); |
||||
const express = require('express'); |
||||
import { SetupApp } from '../../../app'; |
||||
import { expect } from 'chai'; |
||||
import * as helpers from './helpers'; |
||||
import { sha512 } from 'js-sha512'; |
||||
|
||||
async function init() { |
||||
chai.use(chaiHttp); |
||||
const app = express(); |
||||
const knex = await helpers.initTestDB(); |
||||
|
||||
// Add test users.
|
||||
await knex.insert({ email: "test1@test.com", passwordHash: sha512('pass1') }).into('users'); |
||||
await knex.insert({ email: "test2@test.com", passwordHash: sha512('pass2') }).into('users'); |
||||
|
||||
SetupApp(app, knex, ''); |
||||
|
||||
// Login as a test user.
|
||||
var agent = chai.request.agent(app); |
||||
await agent |
||||
.post('/login?username=' + encodeURIComponent("test1@test.com") + '&password=' + encodeURIComponent('pass1')) |
||||
.send({}); |
||||
return agent; |
||||
} |
||||
|
||||
describe('POST /query with no songs', () => { |
||||
it('should give empty list', async done => { |
||||
let agent = await init(); |
||||
try { |
||||
let res = await agent |
||||
.post('/query') |
||||
.send({ |
||||
'query': {}, |
||||
'offsetsLimits': { |
||||
'songOffset': 0, |
||||
'songLimit': 10, |
||||
}, |
||||
'ordering': { |
||||
'orderBy': { |
||||
'type': 'name', |
||||
}, |
||||
'ascending': true |
||||
}, |
||||
'responseType': 'details', |
||||
}) |
||||
expect(res).to.have.status(200); |
||||
expect(res.body).to.deep.equal({ |
||||
songs: [], |
||||
tags: [], |
||||
artists: [], |
||||
albums: [], |
||||
}); |
||||
} finally { |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /query with several songs and filters', () => { |
||||
it('should give all correct results', async done => { |
||||
const song1 = { |
||||
songId: 1, |
||||
title: 'Song1', |
||||
storeLinks: [ 'hello my', 'darling' ], |
||||
artists: [ |
||||
{ |
||||
artistId: 1, |
||||
name: 'Artist1', |
||||
storeLinks: [], |
||||
} |
||||
], |
||||
tags: [], |
||||
albums: [] |
||||
}; |
||||
const song2 = { |
||||
songId: 2, |
||||
title: 'Song2', |
||||
storeLinks: [], |
||||
artists: [ |
||||
{ |
||||
artistId: 1, |
||||
name: 'Artist1', |
||||
storeLinks: [], |
||||
} |
||||
], |
||||
tags: [], |
||||
albums: [] |
||||
}; |
||||
const song3 = { |
||||
songId: 3, |
||||
title: 'Song3', |
||||
storeLinks: [], |
||||
artists: [ |
||||
{ |
||||
artistId: 2, |
||||
name: 'Artist2', |
||||
storeLinks: [], |
||||
} |
||||
], |
||||
tags: [], |
||||
albums: [] |
||||
}; |
||||
|
||||
async function checkAllSongs(req) { |
||||
await req |
||||
.post('/query') |
||||
.send({ |
||||
"query": {}, |
||||
'offsetsLimits': { |
||||
'songOffset': 0, |
||||
'songLimit': 10, |
||||
}, |
||||
'ordering': { |
||||
'orderBy': { |
||||
'type': 'name', |
||||
}, |
||||
'ascending': true |
||||
}, |
||||
'responseType': 'details', |
||||
}) |
||||
.then((res) => { |
||||
expect(res).to.have.status(200); |
||||
expect(res.body).to.deep.equal({ |
||||
songs: [song1, song2, song3], |
||||
artists: [], |
||||
tags: [], |
||||
albums: [], |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
async function checkIdIn(req) { |
||||
await req |
||||
.post('/query') |
||||
.send({ |
||||
"query": { |
||||
"prop": "songId", |
||||
"propOperator": "IN", |
||||
"propOperand": [1, 3, 5] |
||||
}, |
||||
'offsetsLimits': { |
||||
'songOffset': 0, |
||||
'songLimit': 10, |
||||
}, |
||||
'ordering': { |
||||
'orderBy': { |
||||
'type': 'name', |
||||
}, |
||||
'ascending': true |
||||
}, |
||||
'responseType': 'details', |
||||
}) |
||||
.then((res) => { |
||||
expect(res).to.have.status(200); |
||||
expect(res.body).to.deep.equal({ |
||||
songs: [song1, song3], |
||||
artists: [], |
||||
tags: [], |
||||
albums: [], |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
async function checkIdNotIn(req) { |
||||
await req |
||||
.post('/query') |
||||
.send({ |
||||
"query": { |
||||
"prop": "songId", |
||||
"propOperator": "NOTIN", |
||||
"propOperand": [1, 3, 5] |
||||
}, |
||||
'offsetsLimits': { |
||||
'songOffset': 0, |
||||
'songLimit': 10, |
||||
}, |
||||
'ordering': { |
||||
'orderBy': { |
||||
'type': 'name', |
||||
}, |
||||
'ascending': true |
||||
}, |
||||
'responseType': 'details', |
||||
}) |
||||
.then((res) => { |
||||
expect(res).to.have.status(200); |
||||
expect(res.body).to.deep.equal({ |
||||
songs: [song2], |
||||
artists: [], |
||||
tags: [], |
||||
albums: [], |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
async function checkArtistIdIn(req) { |
||||
console.log("HERE!") |
||||
await req |
||||
.post('/query') |
||||
.send({ |
||||
"query": { |
||||
"prop": "artistId", |
||||
"propOperator": "IN", |
||||
"propOperand": [1] |
||||
}, |
||||
'offsetsLimits': { |
||||
'songOffset': 0, |
||||
'songLimit': 10, |
||||
}, |
||||
'ordering': { |
||||
'orderBy': { |
||||
'type': 'name', |
||||
}, |
||||
'ascending': true |
||||
}, |
||||
'responseType': 'details', |
||||
}) |
||||
.then((res) => { |
||||
expect(res).to.have.status(200); |
||||
expect(res.body).to.deep.equal({ |
||||
songs: [song1, song2], |
||||
artists: [], |
||||
tags: [], |
||||
albums: [], |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
async function checkOrRelation(req) { |
||||
await req |
||||
.post('/query') |
||||
.send({ |
||||
"query": { |
||||
"childrenOperator": "OR", |
||||
"children": [ |
||||
{ |
||||
"prop": "artistId", |
||||
"propOperator": "IN", |
||||
"propOperand": [2] |
||||
}, |
||||
{ |
||||
"prop": "songId", |
||||
"propOperator": "EQ", |
||||
"propOperand": 1 |
||||
} |
||||
] |
||||
}, |
||||
'offsetsLimits': { |
||||
'songOffset': 0, |
||||
'songLimit': 10, |
||||
}, |
||||
'ordering': { |
||||
'orderBy': { |
||||
'type': 'name', |
||||
}, |
||||
'ascending': true |
||||
}, |
||||
'responseType': 'details', |
||||
}) |
||||
.then((res) => { |
||||
expect(res).to.have.status(200); |
||||
expect(res.body).to.deep.equal({ |
||||
songs: [song1, song3], |
||||
artists: [], |
||||
tags: [], |
||||
albums: [], |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
async function checkStoreLinksLike(req) { |
||||
await req |
||||
.post('/query') |
||||
.send({ |
||||
"query": { |
||||
"prop": "songStoreLinks", |
||||
"propOperator": "LIKE", |
||||
"propOperand": 'llo m' |
||||
}, |
||||
'offsetsLimits': { |
||||
'songOffset': 0, |
||||
'songLimit': 10, |
||||
}, |
||||
'ordering': { |
||||
'orderBy': { |
||||
'type': 'name', |
||||
}, |
||||
'ascending': true |
||||
}, |
||||
'responseType': 'details', |
||||
}) |
||||
.then((res) => { |
||||
expect(res).to.have.status(200); |
||||
expect(res.body).to.deep.equal({ |
||||
songs: [song1], |
||||
artists: [], |
||||
tags: [], |
||||
albums: [], |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
async function checkResponseTypeIds(req) { |
||||
await req |
||||
.post('/query') |
||||
.send({ |
||||
"query": {}, |
||||
'offsetsLimits': { |
||||
'songOffset': 0, |
||||
'songLimit': 10, |
||||
}, |
||||
'ordering': { |
||||
'orderBy': { |
||||
'type': 'name', |
||||
}, |
||||
'ascending': true |
||||
}, |
||||
'responseType': 'ids', |
||||
}) |
||||
.then((res) => { |
||||
expect(res).to.have.status(200); |
||||
expect(res.body).to.deep.equal({ |
||||
songs: [song1.songId, song2.songId, song3.songId], |
||||
artists: [], |
||||
tags: [], |
||||
albums: [], |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
async function checkResponseTypeCount(req) { |
||||
await req |
||||
.post('/query') |
||||
.send({ |
||||
"query": {}, |
||||
'offsetsLimits': { |
||||
'songOffset': 0, |
||||
'songLimit': 10, |
||||
}, |
||||
'ordering': { |
||||
'orderBy': { |
||||
'type': 'name', |
||||
}, |
||||
'ascending': true |
||||
}, |
||||
'responseType': 'count', |
||||
}) |
||||
.then((res) => { |
||||
expect(res).to.have.status(200); |
||||
expect(res.body).to.deep.equal({ |
||||
songs: 3, |
||||
artists: 0, |
||||
tags: 0, |
||||
albums: 0, |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createArtist(req, { name: "Artist1" }, 200); |
||||
await helpers.createArtist(req, { name: "Artist2" }, 200); |
||||
await helpers.createSong(req, { title: "Song1", artistIds: [1], storeLinks: [ 'hello my', 'darling' ] }, 200); |
||||
await helpers.createSong(req, { title: "Song2", artistIds: [1] }, 200); |
||||
await helpers.createSong(req, { title: "Song3", artistIds: [2] }, 200); |
||||
await checkAllSongs(req); |
||||
await checkIdIn(req); |
||||
await checkIdNotIn(req); |
||||
await checkArtistIdIn(req); |
||||
await checkOrRelation(req); |
||||
await checkStoreLinksLike(req); |
||||
await checkResponseTypeCount(req); |
||||
await checkResponseTypeIds(req); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
@ -0,0 +1,203 @@ |
||||
const chai = require('chai'); |
||||
const chaiHttp = require('chai-http'); |
||||
const express = require('express'); |
||||
import { expect } from 'chai'; |
||||
import { SetupApp } from '../../../app'; |
||||
import { ReferenceDatabase } from '../../reference_model/DBReferenceModel'; |
||||
import { randomDBAction, RandomDBActionDistribution, DBActionType, applyReferenceDBAction, applyRealDBAction, DBAction } from '../../reference_model/randomGen'; |
||||
import * as helpers from '../helpers'; |
||||
import seedrandom from 'seedrandom'; |
||||
import { AlbumWithRefsWithId, Artist, ArtistWithRefsWithId, TagWithRefsWithId, TrackWithRefs, TrackWithRefsWithId } from '../../../../client/src/api/api'; |
||||
import sampleDB from '../sampleDB'; |
||||
let stringify = require('json-stringify-deterministic'); |
||||
|
||||
let _ = require('lodash'); |
||||
let tmp = require('tmp'); |
||||
let fs = require('fs'); |
||||
|
||||
async function init() { |
||||
chai.use(chaiHttp); |
||||
const app = express(); |
||||
const knex = await helpers.initTestDB(); |
||||
|
||||
SetupApp(app, knex, ''); |
||||
|
||||
var agent = chai.request.agent(app); |
||||
return agent; |
||||
} |
||||
|
||||
// Alters a response from a real or mock DB so that they can be deep-compared
|
||||
// and only non-trivial differences trigger an error.
|
||||
function normalizeResponse(response: any) { |
||||
let r: any = _.cloneDeep(response); |
||||
if (r && 'id' in r) { |
||||
r.id = '<redacted>'; |
||||
} |
||||
return r; |
||||
} |
||||
|
||||
// Alters a database export / reference database model so that it can be compared
|
||||
// to another so that only non-trivial differences trigger an error.
|
||||
function normalizeDB(oldDb: ReferenceDatabase) { |
||||
let db: ReferenceDatabase = _.cloneDeep(oldDb); |
||||
|
||||
|
||||
// Apply a deterministic sorting.
|
||||
// TODO: sorting by name is not deterministic.
|
||||
for (const userId in db) { |
||||
db[userId].tracks.sort((a: any, b: any) => a.name.localeCompare(b.name)) |
||||
db[userId].albums.sort((a: any, b: any) => a.name.localeCompare(b.name)) |
||||
db[userId].artists.sort((a: any, b: any) => a.name.localeCompare(b.name)) |
||||
db[userId].tags.sort((a: any, b: any) => a.name.localeCompare(b.name)) |
||||
} |
||||
|
||||
// Re-map IDs.
|
||||
interface IDMap { |
||||
map: Map<number, number>, |
||||
highestId: number, |
||||
}; |
||||
let trackMap: IDMap = { map: new Map<number, number>(), highestId: 0 }; |
||||
let albumMap: IDMap = { map: new Map<number, number>(), highestId: 0 }; |
||||
let artistMap: IDMap = { map: new Map<number, number>(), highestId: 0 }; |
||||
let tagMap: IDMap = { map: new Map<number, number>(), highestId: 0 }; |
||||
let remapId = (id: number, map: IDMap) => { |
||||
if (map.map.has(id)) { return map.map.get(id) as number; } |
||||
let newId: number = map.highestId + 1; |
||||
map.map.set(id, newId); |
||||
map.highestId = newId; |
||||
return newId; |
||||
} |
||||
for (const userId in db) { |
||||
// First remap the IDs only, ignoring references
|
||||
db[userId].tracks.forEach((x: TrackWithRefsWithId) => { console.log("X:", x); x.id = remapId(x.id, trackMap); }); |
||||
db[userId].albums.forEach((x: AlbumWithRefsWithId) => { x.id = remapId(x.id, albumMap); }) |
||||
db[userId].artists.forEach((x: ArtistWithRefsWithId) => { x.id = remapId(x.id, artistMap); }) |
||||
db[userId].tags.forEach((x: TagWithRefsWithId) => { x.id = remapId(x.id, tagMap); }) |
||||
} |
||||
for (const userId in db) { |
||||
// Now remap the references.
|
||||
db[userId].tracks.forEach((x: TrackWithRefsWithId) => { |
||||
x.tagIds = x.tagIds.map((id: number) => remapId(id, tagMap)); |
||||
x.artistIds = x.artistIds.map((id: number) => remapId(id, artistMap)); |
||||
x.albumId = x.albumId ? remapId(x.albumId, albumMap) : null; |
||||
}); |
||||
db[userId].albums.forEach((x: AlbumWithRefsWithId) => { |
||||
x.tagIds = x.tagIds.map((id: number) => remapId(id, tagMap)); |
||||
x.artistIds = x.artistIds.map((id: number) => remapId(id, artistMap)); |
||||
x.trackIds = x.trackIds.map((id: number) => remapId(id, trackMap)); |
||||
}); |
||||
db[userId].artists.forEach((x: ArtistWithRefsWithId) => { |
||||
x.tagIds = x.tagIds.map((id: number) => remapId(id, tagMap)); |
||||
x.albumIds = x.albumIds.map((id: number) => remapId(id, albumMap)); |
||||
x.trackIds = x.trackIds.map((id: number) => remapId(id, trackMap)); |
||||
}); |
||||
db[userId].tags.forEach((x: TagWithRefsWithId) => { |
||||
x.parentId = x.parentId ? remapId(x.parentId, tagMap) : null; |
||||
}); |
||||
} |
||||
|
||||
return db; |
||||
} |
||||
|
||||
describe('Randomized model-based DB back-end tests', () => { |
||||
it('all succeed', async done => { |
||||
let req = await init(); |
||||
let actionTrace: DBAction[] = []; |
||||
|
||||
let seed: string = process.env.TEST_RANDOM_SEED || Math.random().toFixed(5).toString(); |
||||
console.log(`Test random seed: '${seed}'`) |
||||
|
||||
try { |
||||
// Create a reference DB
|
||||
let refDB: ReferenceDatabase = _.cloneDeep(sampleDB); |
||||
|
||||
// Prime the real DB
|
||||
// First, create a user and log in.
|
||||
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||
await helpers.login(req, "someone@email.com", "password1A!", 200); |
||||
// Import the starting DB.
|
||||
await helpers.importDB(req, refDB[1]); |
||||
|
||||
// Check that we are starting from an equal situation
|
||||
let refState = normalizeDB(refDB); |
||||
let realState = normalizeDB({ |
||||
[1]: (await helpers.getExport(req)).body, |
||||
}); |
||||
expect(realState).to.deep.equal(refState); |
||||
|
||||
// Start doing some random changes, checking the state after each step.
|
||||
let rng = seedrandom(seed); |
||||
let dist: RandomDBActionDistribution = { |
||||
type: new Map([ |
||||
[DBActionType.CreateTrack, 0.7], |
||||
[DBActionType.DeleteTrack, 0.3] |
||||
]), |
||||
userId: new Map([[1, 1.0]]), |
||||
createTrackParams: { |
||||
linkAlbum: new Map<boolean | 'nonexistent', number>([[false, 0.45], [true, 0.45], ['nonexistent', 0.1]]), |
||||
linkTags: { |
||||
numValid: new Map([[0, 1.0]]), |
||||
numInvalid: new Map([[0, 0.9], [1, 0.05], [2, 0.05]]), |
||||
}, |
||||
linkArtists: { |
||||
numValid: new Map([[0, 1.0]]), |
||||
numInvalid: new Map([[0, 0.9], [1, 0.05], [2, 0.05]]), |
||||
}, |
||||
}, |
||||
deleteTrackParams: { |
||||
validTrack: new Map([[false, 0.2], [true, 0.8]]) |
||||
} |
||||
} |
||||
|
||||
for (let i = 0; i < 30; i++) { |
||||
let action = randomDBAction( |
||||
refDB, |
||||
rng, |
||||
dist |
||||
); |
||||
actionTrace.push(action); |
||||
console.log("Testing action: ", action); |
||||
let { response: refResponse, status: refStatus } = applyReferenceDBAction(action, refDB); |
||||
let { response: realResponse, status: realStatus } = await applyRealDBAction(action, req); |
||||
|
||||
// Compare the response and status.
|
||||
expect(normalizeResponse(realResponse)).to.deep.equal(normalizeResponse(refResponse)); |
||||
expect(realStatus).to.equal(refStatus); |
||||
|
||||
// Compare the database state after the action.
|
||||
let refState = normalizeDB(refDB); |
||||
let realState = normalizeDB({ |
||||
[1]: (await helpers.getExport(req)).body, |
||||
}); |
||||
expect(realState).to.deep.equal(refState); |
||||
} |
||||
} catch (e) { |
||||
// When catching a comparison error, add and dump various states to files for debugging.
|
||||
e.actionTrace = actionTrace; |
||||
e.startingDB = normalizeDB(sampleDB); |
||||
e.testSeed = seed; |
||||
if (e.actual && e.expected) { |
||||
e.actualDump = tmp.tmpNameSync(); |
||||
e.expectedDump = tmp.tmpNameSync(); |
||||
e.actionTraceDump = tmp.tmpNameSync(); |
||||
e.startingDBDump = tmp.tmpNameSync(); |
||||
fs.writeFileSync(e.actualDump, stringify(e.actual, { space: ' ' })); |
||||
fs.writeFileSync(e.expectedDump, stringify(e.expected, { space: ' ' })); |
||||
fs.writeFileSync(e.actionTraceDump, stringify(e.actionTrace, { space: ' ' })); |
||||
fs.writeFileSync(e.startingDBDump, stringify(e.startingDB, { space: ' ' })); |
||||
|
||||
console.log( |
||||
"A comparison error occurred. Wrote compared values to temporary files for debugging:\n" |
||||
+ ` actual: ${e.actualDump}\n` |
||||
+ ` expected: ${e.expectedDump}\n` |
||||
+ ` DB action trace: ${e.actionTraceDump}\n` |
||||
+ ` Starting DB: ${e.startingDBDump}` |
||||
); |
||||
} |
||||
throw e; |
||||
} finally { |
||||
req.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
@ -1,131 +0,0 @@ |
||||
const chai = require('chai'); |
||||
const chaiHttp = require('chai-http'); |
||||
const express = require('express'); |
||||
import { SetupApp } from '../../../app'; |
||||
import { expect } from 'chai'; |
||||
import * as helpers from './helpers'; |
||||
import { sha512 } from 'js-sha512'; |
||||
|
||||
async function init() { |
||||
chai.use(chaiHttp); |
||||
const app = express(); |
||||
const knex = await helpers.initTestDB(); |
||||
|
||||
// Add test users.
|
||||
await knex.insert({ email: "test1@test.com", passwordHash: sha512('pass1') }).into('users'); |
||||
await knex.insert({ email: "test2@test.com", passwordHash: sha512('pass2') }).into('users'); |
||||
|
||||
SetupApp(app, knex, ''); |
||||
|
||||
// Login as a test user.
|
||||
var agent = chai.request.agent(app); |
||||
await agent |
||||
.post('/login?username=' + encodeURIComponent("test1@test.com") + '&password=' + encodeURIComponent('pass1')) |
||||
.send({}); |
||||
return agent; |
||||
} |
||||
|
||||
describe('POST /song with no title', () => { |
||||
it('should fail', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createSong(req, {}, 400); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /song with only a title', () => { |
||||
it('should return the first available id', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createSong(req, { title: "MySong" }, 200, { id: 1 }); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /song with a nonexistent artist Id', () => { |
||||
it('should fail', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createSong(req, { title: "MySong", artistIds: [1] }, 400); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /song with an existing artist Id', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createArtist(req, { name: "MyArtist" }, 200, { id: 1 }); |
||||
await helpers.createSong(req, { title: "MySong", artistIds: [1] }, 200, { id: 1 }); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /song with two existing artist Ids', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1 }) |
||||
await helpers.createArtist(req, { name: "Artist2" }, 200, { id: 2 }) |
||||
await helpers.createSong(req, { title: "MySong", artistIds: [1, 2] }, 200, { id: 1 }) |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /song with an existent and a nonexistent artist Id', () => { |
||||
it('should fail', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1 }) |
||||
await helpers.createSong(req, { title: "MySong", artistIds: [1, 2] }, 400) |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /song with tags', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createTag(req, { name: "Root" }, 200, { id: 1 }) |
||||
await helpers.createTag(req, { name: "Leaf", parentId: 1 }, 200, { id: 2 }) |
||||
await helpers.createSong(req, { title: "Song", tagIds: [1, 2] }, 200, { id: 1 }) |
||||
await helpers.checkSong(req, 1, 200, { title: "Song", storeLinks: [], tagIds: [1, 2], albumIds: [], artistIds: [] }) |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
@ -1,87 +0,0 @@ |
||||
const chai = require('chai'); |
||||
const chaiHttp = require('chai-http'); |
||||
const express = require('express'); |
||||
import { SetupApp } from '../../../app'; |
||||
import { expect } from 'chai'; |
||||
import * as helpers from './helpers'; |
||||
import { sha512 } from 'js-sha512'; |
||||
|
||||
async function init() { |
||||
chai.use(chaiHttp); |
||||
const app = express(); |
||||
const knex = await helpers.initTestDB(); |
||||
|
||||
// Add test users.
|
||||
await knex.insert({ email: "test1@test.com", passwordHash: sha512('pass1') }).into('users'); |
||||
await knex.insert({ email: "test2@test.com", passwordHash: sha512('pass2') }).into('users'); |
||||
|
||||
SetupApp(app, knex, ''); |
||||
|
||||
// Login as a test user.
|
||||
var agent = chai.request.agent(app); |
||||
await agent |
||||
.post('/login?username=' + encodeURIComponent("test1@test.com") + '&password=' + encodeURIComponent('pass1')) |
||||
.send({}); |
||||
return agent; |
||||
} |
||||
|
||||
describe('POST /tag with no name', () => { |
||||
it('should fail', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createTag(req, {}, 400); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /tag with a correct request', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createTag(req, { name: "MyTag" }, 200, { id: 1 }); |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('POST /tag with a parent', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 }) |
||||
await helpers.createTag(req, { name: "Tag2", parentId: 1 }, 200, { id: 2 }) |
||||
await helpers.checkTag(req, 2, 200, { name: "Tag2", parentId: 1 }) |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
describe('PUT /tag with a new parent', () => { |
||||
it('should succeed', async done => { |
||||
let agent = await init(); |
||||
let req = agent.keepOpen(); |
||||
try { |
||||
await helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 }) |
||||
await helpers.createTag(req, { name: "Tag2" }, 200, { id: 2 }) |
||||
await helpers.modifyTag(req, 2, { parentId: 1 }, 200) |
||||
await helpers.checkTag(req, 2, 200, { name: "Tag2", parentId: 1 }) |
||||
} finally { |
||||
req.close(); |
||||
agent.close(); |
||||
done(); |
||||
} |
||||
}); |
||||
}); |
@ -0,0 +1,127 @@ |
||||
import { ReferenceDatabase } from "../reference_model/DBReferenceModel"; |
||||
|
||||
export const sampleDB: ReferenceDatabase = { |
||||
[1]: { |
||||
tracks: [ |
||||
{ |
||||
mbApi_typename: "track", |
||||
id: 1, |
||||
name: "No One Knows", |
||||
artistIds: [1], |
||||
tagIds: [2], |
||||
albumId: 2, |
||||
storeLinks: [], |
||||
}, |
||||
{ |
||||
mbApi_typename: "track", |
||||
id: 2, |
||||
name: "See Jam", |
||||
artistIds: [3], |
||||
tagIds: [3, 5], |
||||
albumId: 1, |
||||
storeLinks: [], |
||||
}, |
||||
{ |
||||
mbApi_typename: "track", |
||||
id: 3, |
||||
name: "Apocalypshit", |
||||
artistIds: [2], |
||||
tagIds: [4], |
||||
albumId: 3, |
||||
storeLinks: [], |
||||
}, |
||||
], |
||||
albums: [ |
||||
{ |
||||
mbApi_typename: "album", |
||||
id: 1, |
||||
name: "Lithuanian Artillery", |
||||
artistIds: [3], |
||||
tagIds: [3, 5], |
||||
trackIds: [2], |
||||
storeLinks: [], |
||||
}, |
||||
{ |
||||
mbApi_typename: "album", |
||||
id: 2, |
||||
name: "Songs For The Deaf", |
||||
artistIds: [1], |
||||
tagIds: [2], |
||||
trackIds: [1], |
||||
storeLinks: [], |
||||
}, |
||||
{ |
||||
mbApi_typename: "album", |
||||
id: 3, |
||||
name: "Apocalypshit", |
||||
artistIds: [2], |
||||
tagIds: [4], |
||||
trackIds: [3], |
||||
storeLinks: [], |
||||
}, |
||||
], |
||||
artists: [ |
||||
{ |
||||
mbApi_typename: "artist", |
||||
id: 1, |
||||
name: "Queens Of The Stone Age", |
||||
tagIds: [2], |
||||
trackIds: [1], |
||||
albumIds: [2], |
||||
storeLinks: [], |
||||
}, |
||||
{ |
||||
mbApi_typename: "artist", |
||||
id: 2, |
||||
name: "Molotov", |
||||
tagIds: [4], |
||||
trackIds: [3], |
||||
albumIds: [3], |
||||
storeLinks: [], |
||||
}, |
||||
{ |
||||
mbApi_typename: "artist", |
||||
id: 3, |
||||
name: "The Schwings Band", |
||||
tagIds: [3, 5], |
||||
trackIds: [2], |
||||
albumIds: [1], |
||||
storeLinks: [], |
||||
}, |
||||
], |
||||
tags: [ |
||||
{ |
||||
mbApi_typename: "tag", |
||||
id: 1, |
||||
name: "Genre", |
||||
parentId: null, |
||||
}, |
||||
{ |
||||
mbApi_typename: "tag", |
||||
id: 2, |
||||
name: "Desert Rock", |
||||
parentId: 1, |
||||
}, |
||||
{ |
||||
mbApi_typename: "tag", |
||||
id: 3, |
||||
name: "Swing", |
||||
parentId: 1, |
||||
}, |
||||
{ |
||||
mbApi_typename: "tag", |
||||
id: 4, |
||||
name: "Crazy", |
||||
parentId: 1, |
||||
}, |
||||
{ |
||||
mbApi_typename: "tag", |
||||
id: 5, |
||||
name: "Lindy Hop", |
||||
parentId: null, |
||||
}, |
||||
], |
||||
} |
||||
} |
||||
|
||||
export default sampleDB; |
@ -0,0 +1,116 @@ |
||||
import { AlbumWithRefsWithId, ArtistWithRefsWithId, DBDataFormat, PostTrackRequest, TrackWithDetails, TrackWithRefsWithId } from "../../../client/src/api/api"; |
||||
import { makeNotFoundError } from "../../db/common"; |
||||
import filterInPlace from "../../lib/filterInPlace"; |
||||
|
||||
// The mock reference database is in the same format as
|
||||
// the JSON import/export format, for multiple users.
|
||||
export type ReferenceDatabase = Record<number, DBDataFormat> |
||||
|
||||
type ObjectsType = "tracks" | "artists" | "tags" | "albums"; |
||||
|
||||
// Get a fresh ID for a new object.
|
||||
function getNewId(db: ReferenceDatabase, objectsType: ObjectsType): number { |
||||
let highest: number = 1; |
||||
for (const data of Object.values(db)) { |
||||
data[objectsType].forEach((obj: any) => highest = Math.max(highest, obj.id)); |
||||
} |
||||
return highest + 1; |
||||
} |
||||
|
||||
// Check a (set of) IDs for presence in the objects array.
|
||||
// All have to exist for it to return true.
|
||||
function checkExists(objects: any[], ids: number[]) { |
||||
return ids.reduce((prev: boolean, id: number) => { |
||||
return prev && objects.find((object: any) => object.id === id); |
||||
}, true); |
||||
} |
||||
|
||||
// If not in the array, put the number in the array.
|
||||
function ensureInSet(n: number, s: number[]) { |
||||
if (!(n in s)) { s.push(n); } |
||||
} |
||||
|
||||
// For a set of objects, ensure they point to another object.
|
||||
function ensureLinked(fromObjects: number[], fromObjectsType: ObjectsType, |
||||
toId: number, toObjectsType: ObjectsType, data: DBDataFormat) { |
||||
if (toObjectsType === 'tracks') { |
||||
fromObjects.forEach((fromId: number) => ensureInSet(toId,
|
||||
(data[fromObjectsType][fromId] as AlbumWithRefsWithId | ArtistWithRefsWithId).trackIds)) |
||||
} |
||||
} |
||||
|
||||
// Create a new object.
|
||||
export interface LinkField { field: string, otherObjectType: ObjectsType }; |
||||
export function createObject( |
||||
userId: number, |
||||
object: any, |
||||
objectType: ObjectsType, |
||||
singularLinkFields: LinkField[], |
||||
pluralLinkFields: LinkField[], |
||||
db: ReferenceDatabase |
||||
): { id: number } { |
||||
// Existence checks
|
||||
if (!(userId in db)) { throw makeNotFoundError() } |
||||
singularLinkFields.forEach((f: LinkField) => { |
||||
if (!checkExists(db[userId][f.otherObjectType], object[f.field] ? [object[f.field]] : [])) { |
||||
throw makeNotFoundError(); |
||||
} |
||||
}); |
||||
pluralLinkFields.forEach((f: LinkField) => { |
||||
if (!checkExists(db[userId][f.otherObjectType], object[f.field] || [])) { |
||||
throw makeNotFoundError(); |
||||
} |
||||
}); |
||||
|
||||
// Create an ID and the object
|
||||
let id = getNewId(db, objectType); |
||||
db[userId][objectType].push({ |
||||
...object, |
||||
id: id, |
||||
}) |
||||
|
||||
// reverse links
|
||||
singularLinkFields.forEach((f: LinkField) => { |
||||
ensureLinked(object[f.field] ? [object[f.field]] : [], f.otherObjectType, id, objectType, db[userId]); |
||||
}); |
||||
pluralLinkFields.forEach((f: LinkField) => { |
||||
ensureLinked(object[f.field] || [], f.otherObjectType, id, objectType, db[userId]); |
||||
}); |
||||
|
||||
return { id: id }; |
||||
} |
||||
|
||||
// Create a new track.
|
||||
export function createTrack(userId: number, track: PostTrackRequest, db: ReferenceDatabase): { id: number } { |
||||
return createObject( |
||||
userId, |
||||
track, |
||||
'tracks', |
||||
[{ field: 'albumId', otherObjectType: 'albums' }], |
||||
[ |
||||
{ field: 'artistIds', otherObjectType: 'artists' }, |
||||
{ field: 'tagIds', otherObjectType: 'tags' }, |
||||
], |
||||
db |
||||
); |
||||
} |
||||
|
||||
// Delete a track.
|
||||
export function deleteTrack(userId: number, id: number, db: ReferenceDatabase): void { |
||||
// Existence checks
|
||||
if (!(userId in db)) { throw makeNotFoundError() } |
||||
|
||||
// Find the object to delete.
|
||||
let idx = db[userId].tracks.findIndex((track: TrackWithRefsWithId) => track.id === id); |
||||
if (idx < 0) { |
||||
// Not found
|
||||
throw makeNotFoundError(); |
||||
} |
||||
|
||||
// Remove references
|
||||
db[userId].albums.forEach((x: AlbumWithRefsWithId) => { filterInPlace(x.trackIds, (tid: number) => tid !== id); }) |
||||
db[userId].artists.forEach((x: ArtistWithRefsWithId) => { filterInPlace(x.trackIds, (tid: number) => tid !== id); }) |
||||
|
||||
// Delete the object
|
||||
db[userId].tracks.splice(idx, 1); |
||||
} |
@ -0,0 +1,238 @@ |
||||
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
|
||||
} |
||||
} |
Loading…
Reference in new issue