From e159cfec3745296734cdd12c0125958144a17219 Mon Sep 17 00:00:00 2001 From: Sander Vocke Date: Wed, 15 Jul 2020 11:56:44 +0200 Subject: [PATCH] Testing framework up. Endpoint handlers refactored a bit. --- server/app.ts | 33 +++ .../endpoints/CreateArtistEndpointHandler.ts | 15 +- server/endpoints/CreateSongEndpointHandler.ts | 27 ++- .../endpoints/ListArtistsEndpointHandler.ts | 13 +- server/endpoints/ListSongsEndpointHandler.ts | 33 +-- server/endpoints/types.ts | 6 + server/lib/jasmine_examples/Player.js | 24 +++ server/lib/jasmine_examples/Song.js | 9 + server/package.json | 6 +- server/server.ts | 23 +- .../test/integration/flows/CreateSongFlow.js | 137 ++++++++++++ server/test/jasmine.json | 11 + server/yarn.lock | 198 ++++++++++++++---- 13 files changed, 438 insertions(+), 97 deletions(-) create mode 100644 server/app.ts create mode 100644 server/endpoints/types.ts create mode 100644 server/lib/jasmine_examples/Player.js create mode 100644 server/lib/jasmine_examples/Song.js create mode 100644 server/test/integration/flows/CreateSongFlow.js create mode 100644 server/test/jasmine.json diff --git a/server/app.ts b/server/app.ts new file mode 100644 index 0000000..d034174 --- /dev/null +++ b/server/app.ts @@ -0,0 +1,33 @@ +const bodyParser = require('body-parser'); +const models = require('./models'); +import * as api from '../client/src/api'; + +import { CreateSongEndpointHandler } from './endpoints/CreateSongEndpointHandler'; +import { CreateArtistEndpointHandler } from './endpoints/CreateArtistEndpointHandler'; +import { ListSongsEndpointHandler } from './endpoints/ListSongsEndpointHandler'; +import { ListArtistsEndpointHandler } from './endpoints/ListArtistsEndpointHandler'; +import * as endpointTypes from './endpoints/types'; + +const invokeHandler = (handler:endpointTypes.EndpointHandler) => { + return async (req: any, res: any) => { + await handler(req, res) + .catch((_e:endpointTypes.EndpointError) => { + let e:endpointTypes.EndpointError = _e; + console.log("Error handling request: ", e.internalMessage); + res.sendStatus(e.httpStatus); + }) + }; +} + +const SetupApp = (app: any) => { + app.use(bodyParser.json()); + app.use(bodyParser.urlencoded({ extended: true })); + + // Set up REST API endpoints + app.post(api.CreateSongEndpoint, invokeHandler(CreateSongEndpointHandler)); + app.get(api.ListSongsEndpoint, invokeHandler(ListSongsEndpointHandler)); + app.post(api.CreateArtistEndpoint, invokeHandler(CreateArtistEndpointHandler)); + app.get(api.ListArtistsEndpoint, invokeHandler(ListArtistsEndpointHandler)); +} + +export { SetupApp } \ No newline at end of file diff --git a/server/endpoints/CreateArtistEndpointHandler.ts b/server/endpoints/CreateArtistEndpointHandler.ts index 9351203..c8f6163 100644 --- a/server/endpoints/CreateArtistEndpointHandler.ts +++ b/server/endpoints/CreateArtistEndpointHandler.ts @@ -1,18 +1,21 @@ const models = require('../models'); import * as api from '../../client/src/api'; +import { EndpointError, EndpointHandler } from './types'; -export const CreateArtistEndpointHandler = (req: any, res: any) => { +export const CreateArtistEndpointHandler:EndpointHandler = async (req: any, res: any) => { if (!api.checkCreateArtistRequest(req)) { - console.log('Invalid CreateArtist request: ' + JSON.stringify(req.body)); - res.sendStatus(400); - return; + const e:EndpointError = { + internalMessage: 'Invalid CreateArtist request: ' + JSON.stringify(req.body), + httpStatus: 400 + }; + throw e; } const reqObject: api.CreateArtistRequest = req.body; - models.Artist.create(reqObject) + await models.Artist.create(reqObject) .then((artist: any) => { const responseObject: api.CreateArtistResponse = { id: artist.id }; res.status(200).send(responseObject); - }) + }); } \ No newline at end of file diff --git a/server/endpoints/CreateSongEndpointHandler.ts b/server/endpoints/CreateSongEndpointHandler.ts index 25a976a..40ca556 100644 --- a/server/endpoints/CreateSongEndpointHandler.ts +++ b/server/endpoints/CreateSongEndpointHandler.ts @@ -1,11 +1,14 @@ const models = require('../models'); import * as api from '../../client/src/api'; +import { EndpointError, EndpointHandler } from './types'; -export const CreateSongEndpointHandler = (req: any, res: any) => { +export const CreateSongEndpointHandler:EndpointHandler = async (req: any, res: any) => { if (!api.checkCreateSongRequest(req)) { - console.log('Invalid CreateSong request: ' + JSON.stringify(req.body)); - res.sendStatus(400); - return; + const e:EndpointError = { + internalMessage: 'Invalid CreateSong request: ' + JSON.stringify(req.body), + httpStatus: 400 + }; + throw e; } const reqObject: api.CreateSongRequest = req.body; @@ -18,7 +21,11 @@ export const CreateSongEndpointHandler = (req: any, res: any) => { }) .then((artist: any[]) => { if (artist.length != 1) { - throw 'There is no artist with id ' + artistId + '.'; + const e:EndpointError = { + internalMessage: 'There is no artist with id ' + artistId + '.', + httpStatus: 400 + }; + throw e; } return artist[0]; }) @@ -35,7 +42,11 @@ export const CreateSongEndpointHandler = (req: any, res: any) => { }) .then((album: any[]) => { if (album.length != 1) { - throw 'There is no album with id ' + albumId + '.'; + const e:EndpointError = { + internalMessage: 'There is no album with id ' + albumId + '.', + httpStatus: 400 + }; + throw e; } return album[0]; }) @@ -44,7 +55,7 @@ export const CreateSongEndpointHandler = (req: any, res: any) => { var albumInstancesPromise = Promise.all(albumInstancePromises); // Upon finish retrieving artists and albums, create the song and associate it. - Promise.all([artistInstancesPromise, albumInstancesPromise]) + await Promise.all([artistInstancesPromise, albumInstancesPromise]) .then((values: any) => { var [artists, albums] = values; models.Song.create({ @@ -64,5 +75,5 @@ export const CreateSongEndpointHandler = (req: any, res: any) => { }; res.status(200).send(responseObject); }) - }); + }); } \ No newline at end of file diff --git a/server/endpoints/ListArtistsEndpointHandler.ts b/server/endpoints/ListArtistsEndpointHandler.ts index b354242..f92616b 100644 --- a/server/endpoints/ListArtistsEndpointHandler.ts +++ b/server/endpoints/ListArtistsEndpointHandler.ts @@ -1,13 +1,16 @@ const models = require('../models'); import * as api from '../../client/src/api'; +import { EndpointError, EndpointHandler } from './types'; -export const ListArtistsEndpointHandler = (req: any, res: any) => { +export const ListArtistsEndpointHandler:EndpointHandler = async (req: any, res: any) => { if (!api.checkListArtistsRequest(req)) { - console.log('Invalid ListArtists request: ' + JSON.stringify(req.body)); - res.sendStatus(400); - return; + const e:EndpointError = { + internalMessage: 'Invalid ListArtists request: ' + JSON.stringify(req.body), + httpStatus: 400 + }; + throw e; } - models.Artist.findAll() + await models.Artist.findAll() .then((artists: any[]) => { const response: api.ListArtistsResponse = artists.map((artist: any) => { return { diff --git a/server/endpoints/ListSongsEndpointHandler.ts b/server/endpoints/ListSongsEndpointHandler.ts index 30d20ac..fcb9be0 100644 --- a/server/endpoints/ListSongsEndpointHandler.ts +++ b/server/endpoints/ListSongsEndpointHandler.ts @@ -1,13 +1,16 @@ const models = require('../models'); import * as api from '../../client/src/api'; +import { EndpointError, EndpointHandler } from './types'; -export const ListSongsEndpointHandler = (req: any, res: any) => { +export const ListSongsEndpointHandler:EndpointHandler = async (req: any, res: any) => { if (!api.checkListSongsRequest(req)) { - console.log('Invalid ListSongs request: ' + JSON.stringify(req.body)); - res.sendStatus(400); - return; + const e:EndpointError = { + internalMessage: 'Invalid ListSongs request: ' + JSON.stringify(req.body), + httpStatus: 400 + }; + throw e; } - models.Song.findAll({ + await models.Song.findAll({ include: [models.Artist, models.Album] }) .then((songs: any[]) => { @@ -16,14 +19,18 @@ export const ListSongsEndpointHandler = (req: any, res: any) => { return { title: song.title, id: song.id, - artists: song.Artists.map((artist: any) => { return { - name: artist.name, - id: artist.id, - }; }), - albums: song.Albums.map((album: any) => { return { - name: album.name, - id: album.id, - }; }), + artists: song.Artists.map((artist: any) => { + return { + name: artist.name, + id: artist.id, + }; + }), + albums: song.Albums.map((album: any) => { + return { + name: album.name, + id: album.id, + }; + }), }; }); res.send(response); diff --git a/server/endpoints/types.ts b/server/endpoints/types.ts new file mode 100644 index 0000000..b676aeb --- /dev/null +++ b/server/endpoints/types.ts @@ -0,0 +1,6 @@ +export type EndpointHandler = (req: any, res: any) => Promise; + +export interface EndpointError { + internalMessage:String; + httpStatus:Number; +} \ No newline at end of file diff --git a/server/lib/jasmine_examples/Player.js b/server/lib/jasmine_examples/Player.js new file mode 100644 index 0000000..fe95f89 --- /dev/null +++ b/server/lib/jasmine_examples/Player.js @@ -0,0 +1,24 @@ +function Player() { +} +Player.prototype.play = function(song) { + this.currentlyPlayingSong = song; + this.isPlaying = true; +}; + +Player.prototype.pause = function() { + this.isPlaying = false; +}; + +Player.prototype.resume = function() { + if (this.isPlaying) { + throw new Error("song is already playing"); + } + + this.isPlaying = true; +}; + +Player.prototype.makeFavorite = function() { + this.currentlyPlayingSong.persistFavoriteStatus(true); +}; + +module.exports = Player; diff --git a/server/lib/jasmine_examples/Song.js b/server/lib/jasmine_examples/Song.js new file mode 100644 index 0000000..3415bb8 --- /dev/null +++ b/server/lib/jasmine_examples/Song.js @@ -0,0 +1,9 @@ +function Song() { +} + +Song.prototype.persistFavoriteStatus = function(value) { + // something complicated + throw new Error("not yet implemented"); +}; + +module.exports = Song; diff --git a/server/package.json b/server/package.json index 63c3495..f0638f9 100644 --- a/server/package.json +++ b/server/package.json @@ -3,11 +3,15 @@ "version": "1.0.0", "scripts": { "start": "nodemon server.ts", - "build": "tsc" + "build": "tsc", + "test": "ts-node node_modules/jasmine/bin/jasmine --config=test/jasmine.json" }, "dependencies": { "body-parser": "^1.18.3", + "chai": "^4.2.0", + "chai-http": "^4.3.0", "express": "^4.16.4", + "jasmine": "^3.5.0", "sequelize": "^6.3.0", "sequelize-cli": "^6.2.0", "sqlite3": "^5.0.0", diff --git a/server/server.ts b/server/server.ts index 989d48b..45d2447 100644 --- a/server/server.ts +++ b/server/server.ts @@ -1,29 +1,16 @@ const express = require('express'); const bodyParser = require('body-parser'); - -import { CreateSongEndpointHandler } from './endpoints/CreateSongEndpointHandler'; -import { CreateArtistEndpointHandler } from './endpoints/CreateArtistEndpointHandler'; -import { ListSongsEndpointHandler } from './endpoints/ListSongsEndpointHandler'; -import { ListArtistsEndpointHandler } from './endpoints/ListArtistsEndpointHandler'; - const models = require('./models'); -import * as api from '../client/src/api'; -const app = express(); +import { SetupApp } from './app'; -// TODO: configurable port -const port = process.env.PORT || 5000; - -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: true })); +const app = express(); -// Set up REST API endpoints -app.post(api.CreateSongEndpoint, CreateSongEndpointHandler); -app.get(api.ListSongsEndpoint, ListSongsEndpointHandler); -app.post(api.CreateArtistEndpoint, CreateArtistEndpointHandler); -app.get(api.ListArtistsEndpoint, ListArtistsEndpointHandler); +SetupApp(app); models.sequelize.sync().then(() => { + // TODO: configurable port + const port = process.env.PORT || 5000; app.listen(port, () => console.log(`Listening on port ${port}`)); }) diff --git a/server/test/integration/flows/CreateSongFlow.js b/server/test/integration/flows/CreateSongFlow.js new file mode 100644 index 0000000..7d6e4ed --- /dev/null +++ b/server/test/integration/flows/CreateSongFlow.js @@ -0,0 +1,137 @@ +const chai = require('chai'); +const chaiHttp = require('chai-http'); +const express = require('express'); +const models = require('../../../models'); +import { SetupApp } from '../../../app'; +import { expect } from 'chai'; + +async function init() { + chai.use(chaiHttp); + const app = express(); + SetupApp(app); + await models.sequelize.sync({ force: true }); + return app; +} + +describe('POST /song/create with only a title', () => { + it('should return the first available id', done => { + init().then((app) => { + chai + .request(app) + .post('/song/create') + .send({ + title: "MySong" + }) + .end((err, res) => { + expect(err).to.be.null; + expect(res).to.have.status(200); + expect(res.body).to.deep.equal({ + id: 1 + }); + done(); + }); + }) + }); +}); + +describe('POST /song/create with a nonexistent artist Id', () => { + it('should fail', done => { + init().then((app) => { + chai + .request(app) + .post('/song/create') + .send({ + title: "MySong", + artistIds: [1] + }) + .end((err, res) => { + expect(err).to.be.null; + expect(res).to.have.status(400); + done(); + }); + }) + }); +}); + +describe('POST /song/create with an existing artist Id', () => { + it('should succeed', done => { + init().then((app) => { + async function createArtist() { + await chai.request(app) + .post('/artist/create') + .send({ + name: "MyArtist" + }) + .then((res) => { + expect(res).to.have.status(200); + expect(res.body).to.deep.equal({ + id: 1 + }); + }); + } + + async function createSong() { + chai.request(app) + .post('/song/create') + .send({ + title: "MySong", + artistIds: [1] + }) + .then((res) => { + expect(res).to.have.status(200); + expect(res.body).to.deep.equal({ + id: 1 + }); + }); + } + + init() + .then(createArtist) + .then(createSong) + .then(done); + }); + }); +}); + +describe('POST /song/create with two existing artist Ids', () => { + it('should succeed', done => { + init().then((app) => { + async function createArtist(name, expectId) { + await chai.request(app) + .post('/artist/create') + .send({ + name: name + }) + .then((res) => { + expect(res).to.have.status(200); + expect(res.body).to.deep.equal({ + id: expectId + }); + }); + } + + async function createSong() { + chai.request(app) + .post('/song/create') + .send({ + title: "MySong", + artistIds: [1, 2] + }) + .then((res) => { + expect(res).to.have.status(200); + expect(res.body).to.deep.equal({ + id: 1 + }); + }); + } + + init() + .then(() => { createArtist('Artist1', 1); }) + .then(() => { createArtist('Artist2', 2); }) + .then(createSong) + .then(done); + }); + }); +}); + +export { }; \ No newline at end of file diff --git a/server/test/jasmine.json b/server/test/jasmine.json new file mode 100644 index 0000000..2365457 --- /dev/null +++ b/server/test/jasmine.json @@ -0,0 +1,11 @@ +{ + "spec_dir": "test", + "spec_files": [ + "**/*[fF]low.js" + ], + "helpers": [ + "helpers/**/*.js" + ], + "stopSpecOnExpectationFailure": false, + "random": true +} diff --git a/server/yarn.lock b/server/yarn.lock index 1b1f19b..3a5962f 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -2,11 +2,29 @@ # yarn lockfile v1 +"@types/chai@4": + version "4.2.11" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.11.tgz#d3614d6c5f500142358e6ed24e1bf16657536c50" + integrity sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw== + +"@types/cookiejar@*": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80" + integrity sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw== + "@types/node@*": version "14.0.19" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.19.tgz#994d99708822bca643a2364f8aeed04a16e0f5a1" integrity sha512-yf3BP/NIXF37BjrK5klu//asUWitOEoUP5xE1mhSUjazotwJ/eJDgEmMQNlOeWOVv72j24QQ+3bqXHE++CFGag== +"@types/superagent@^3.8.3": + version "3.8.7" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-3.8.7.tgz#1f1ed44634d5459b3a672eb7235a8e7cfd97704c" + integrity sha512-9KhCkyXv268A2nZ1Wvu7rQWM+BmdYUVkycFeNnYrUL5Zwu7o8wPQ3wBfW59dDP+wuoxw0ww8YKgTNv8j/cgscA== + dependencies: + "@types/cookiejar" "*" + "@types/node" "*" + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -92,6 +110,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -175,6 +198,36 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +chai-http@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/chai-http/-/chai-http-4.3.0.tgz#3c37c675c1f4fe685185a307e345de7599337c1a" + integrity sha512-zFTxlN7HLMv+7+SPXZdkd5wUlK+KxH6Q7bIEMiEx0FK3zuuMqL7cwICAQ0V1+yYRozBburYuxN1qZstgHpFZQg== + dependencies: + "@types/chai" "4" + "@types/superagent" "^3.8.3" + cookiejar "^2.1.1" + is-ip "^2.0.0" + methods "^1.1.2" + qs "^6.5.1" + superagent "^3.7.0" + +chai@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" + integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^3.0.1" + get-func-name "^2.0.0" + pathval "^1.1.0" + type-detect "^4.0.5" + +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= + chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -230,6 +283,11 @@ commander@^2.19.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +component-emitter@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -270,6 +328,11 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookiejar@^2.1.0, cookiejar@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" + integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -297,7 +360,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@^3.2.6: +debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -316,6 +379,13 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +deep-eql@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + dependencies: + type-detect "^4.0.0" + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -486,7 +556,7 @@ ext@^1.1.2: dependencies: type "^2.0.0" -extend@~3.0.2: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -536,6 +606,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^2.3.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -545,6 +624,11 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +formidable@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" + integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q== + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -605,6 +689,11 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -612,7 +701,7 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob@^7.0.3, glob@^7.1.3: +glob@^7.0.3, glob@^7.1.3, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -720,6 +809,11 @@ ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== +ip-regex@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -737,6 +831,13 @@ is-fullwidth-code-point@^2.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= +is-ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ip/-/is-ip-2.0.0.tgz#68eea07e8a0a0a94c2d080dd674c731ab2a461ab" + integrity sha1-aO6gfooKCpTC0IDdZ0xzGrKkYas= + dependencies: + ip-regex "^2.0.0" + is-promise@^2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" @@ -762,6 +863,19 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= +jasmine-core@~3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4" + integrity sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA== + +jasmine@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.5.0.tgz#7101eabfd043a1fc82ac24e0ab6ec56081357f9e" + integrity sha512-DYypSryORqzsGoMazemIHUfMkXM7I7easFaxAvNM3Mr6Xz3Fy36TupTrAOxZWN8MVKEU5xECv22J4tUQf3uBzQ== + dependencies: + glob "^7.1.4" + jasmine-core "~3.5.0" + js-beautify@^1.8.8: version "1.11.0" resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.11.0.tgz#afb873dc47d58986360093dcb69951e8bcd5ded2" @@ -867,7 +981,7 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= -methods@~1.1.2: +methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -884,7 +998,7 @@ mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: dependencies: mime-db "1.44.0" -mime@1.6.0: +mime@1.6.0, mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -969,11 +1083,6 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -nested-error-stacks@^2: - version "2.1.0" - resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" - integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== - next-tick@1: version "1.1.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" @@ -1160,6 +1269,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +pathval@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" + integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -1203,6 +1317,11 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@^6.5.1: + version "6.9.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" + integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -1233,7 +1352,7 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -readable-stream@^2.0.6: +readable-stream@^2.0.6, readable-stream@^2.3.5: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -1246,11 +1365,6 @@ readable-stream@^2.0.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -reflect-metadata@>=0.1.12: - version "0.1.13" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" - integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== - request@^2.87.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" @@ -1287,7 +1401,7 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -resolve@^1.5.0, resolve@^1.9.0: +resolve@^1.5.0: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -1531,6 +1645,22 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +superagent@^3.7.0: + version "3.8.3" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" + integrity sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA== + dependencies: + component-emitter "^1.2.0" + cookiejar "^2.1.0" + debug "^3.1.0" + extend "^3.0.0" + form-data "^2.3.1" + formidable "^1.2.0" + methods "^1.1.1" + mime "^1.4.1" + qs "^6.5.1" + readable-stream "^2.3.5" + tar@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" @@ -1590,25 +1720,6 @@ ts-node@^8.10.2: source-map-support "^0.5.17" yn "3.1.1" -tslib@^1.8.1: - version "1.13.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" - integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== - -tsutils@^3.17.1: - version "3.17.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" - integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== - dependencies: - tslib "^1.8.1" - -ttypescript@^1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/ttypescript/-/ttypescript-1.5.10.tgz#5045083a91cf09a735ecc95d4711c1f3b83f2059" - integrity sha512-Hk7TRej1hM+p+Fo+Pyb/XK9pe9CAt3Sh5n5YRutxFS8hUgkh2u1Vd2K40kMcNP3WYhiVFBMqXwM/2E8O95Ep6g== - dependencies: - resolve "^1.9.0" - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -1621,6 +1732,11 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +type-detect@^4.0.0, type-detect@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -1639,16 +1755,6 @@ type@^2.0.0: resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== -typescript-is@^0.16.3: - version "0.16.3" - resolved "https://registry.yarnpkg.com/typescript-is/-/typescript-is-0.16.3.tgz#85eefd5f13ee03ecb0d4aed34c0ac0c773d271c6" - integrity sha512-Vlpo9YFnjWEBUyfD5Yx3MLYupxMdK15ZBHkDYOWpRZWcjXS2XNScKMWhY5JSouu9+q4wduGz/v1lfZdW9Hk+qQ== - dependencies: - nested-error-stacks "^2" - tsutils "^3.17.1" - optionalDependencies: - reflect-metadata ">=0.1.12" - typescript@~3.7.2: version "3.7.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"