diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2fcdca..1e5fbd7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,6 +22,7 @@ jobs: yarn install --frozen-lockfile yarn lint yarn test + yarn test:e2e --forceExit buildECR: needs: test diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8b0a4eb..891acbc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,6 +16,7 @@ jobs: build: runs-on: ubuntu-latest + timeout-minutes: 15 strategy: matrix: @@ -38,3 +39,23 @@ jobs: env: CI: true NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + integration-tests: + + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v1 + - name: Use Node.js 10 + uses: actions/setup-node@v1 + with: + node-version: 10 + registry-url: https://npm.pkg.github.com + scope: '@kiltprotocol' + - name: yarn install, lint, test and build + run: | + yarn upgrade --scope @kiltprotocol --latest + yarn test:e2e --forceExit + env: + CI: true + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/environment/test.env b/environment/test.env new file mode 100644 index 0000000..42c5650 --- /dev/null +++ b/environment/test.env @@ -0,0 +1,5 @@ +BOOT_NODE_ADDRESS=ws://127.0.0.1:9944 +MONGODB_HOST=localhost +MONGODB_USER='' +MONGODB_PASS='' +SECRET=thisIsASecret diff --git a/package.json b/package.json index 76b9fd0..875498f 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@types/node": "^10.7.1", "@types/supertest": "^2.0.5", "jest": "^23.5.0", + "mongodb-memory-server": "^6.6.3", "nodemon": "^1.18.3", "prettier": "^1.14.2", "supertest": "^3.1.0", diff --git a/src/config/config.service.ts b/src/config/config.service.ts index 9b464c6..5ee976f 100644 --- a/src/config/config.service.ts +++ b/src/config/config.service.ts @@ -1,6 +1,5 @@ import * as dotenv from 'dotenv' import * as fs from 'fs' -import { Injectable } from '@nestjs/common' export class ConfigService { private readonly envConfig: { [key: string]: string } diff --git a/src/contacts/contacts.controller.ts b/src/contacts/contacts.controller.ts index 5aeaa59..ac7a257 100644 --- a/src/contacts/contacts.controller.ts +++ b/src/contacts/contacts.controller.ts @@ -42,7 +42,7 @@ export class ContactsController { throw new BadRequestException('bad signature for hash') } } - this.contactService.add(contact) + await this.contactService.add(contact) } @Get() diff --git a/src/faucet/interfaces/faucet.interfaces.ts b/src/faucet/interfaces/faucet.interfaces.ts index 7c2fc7d..a0c987a 100644 --- a/src/faucet/interfaces/faucet.interfaces.ts +++ b/src/faucet/interfaces/faucet.interfaces.ts @@ -21,4 +21,6 @@ export declare interface FaucetService { ): Promise updateOnTransactionFailure(drop: FaucetDrop): Promise + + reset(): Promise } diff --git a/src/faucet/mongodb-faucet.service.ts b/src/faucet/mongodb-faucet.service.ts index 905773b..3cd0598 100644 --- a/src/faucet/mongodb-faucet.service.ts +++ b/src/faucet/mongodb-faucet.service.ts @@ -80,4 +80,8 @@ export class MongoDbFaucetService implements FaucetService { const updatedFaucetDrop = new this.faucetDropDBModel(drop) await updatedFaucetDrop.save() } + + public async reset(): Promise { + await this.faucetDropDBModel.deleteMany({}).exec() + } } diff --git a/test/MockMongooseModule.ts b/test/MockMongooseModule.ts new file mode 100644 index 0000000..ef4c1d8 --- /dev/null +++ b/test/MockMongooseModule.ts @@ -0,0 +1,23 @@ +import { MongooseModule } from '@nestjs/mongoose' +import { ConfigModule } from '../src/config/config.module' +import { MongoMemoryServer } from 'mongodb-memory-server' +import { ConfigService } from '../src/config/config.service' + +export const mongodbInstance = new MongoMemoryServer() + +/** + * A MongooseModule that creates a new ephemeral in-memory db for testing purposes. + * This module should be used instead of MyMongooseModule to allow concurrent testing - + * multiple independent in-memory db's can run at the same time. + */ +export const MockMongooseModule = MongooseModule.forRootAsync({ + imports: [ConfigModule], + useFactory: async () => { + await mongodbInstance.ensureInstance() + const uri = await mongodbInstance.getUri() + return { + uri, + } + }, + inject: [ConfigService], +}) diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts index b3b0256..badfc05 100644 --- a/test/app.e2e-spec.ts +++ b/test/app.e2e-spec.ts @@ -1,24 +1,82 @@ -import { INestApplication } from '@nestjs/common'; -import { Test } from '@nestjs/testing'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; +import { INestApplication } from '@nestjs/common' +import { Test } from '@nestjs/testing' +import request from 'supertest' +import { Identity } from '@kiltprotocol/sdk-js' +import { MockMongooseModule, mongodbInstance } from './MockMongooseModule' +import { AppModule } from '../src/app.module' -describe('AppController (e2e)', () => { - let app: INestApplication; +jest.mock( + '@kiltprotocol/sdk-js/build/blockchainApiConnection/BlockchainApiConnection' +) +jest.mock('../src/mongoose/mongoose.module', () => ({ + MyMongooseModule: MockMongooseModule, +})) + +describe('AppController availability (e2e)', () => { + let app: INestApplication beforeAll(async () => { const moduleFixture = await Test.createTestingModule({ imports: [AppModule], - }).compile(); + }).compile() - app = moduleFixture.createNestApplication(); - await app.init(); - }); + app = moduleFixture.createNestApplication() + await app.init() + }, 30000) - it('/ (GET)', () => { + it('root responds with 404 (GET)', () => { return request(app.getHttpServer()) .get('/') - .expect(200) - .expect('Hello World!'); - }); -}); + .expect(404) + }) + + it('ctypes endpoint available (GET)', () => { + return request(app.getHttpServer()) + .get('/ctype') + .expect(200, []) + }) + + it('contacts endpoint available (GET)', () => { + return request(app.getHttpServer()) + .get('/contacts') + .expect(200, []) + }) + + it('outgoing messages enpoint available (POST)', async () => { + return request(app.getHttpServer()) + .post(`/messaging`) + .send({}) + .expect(400) + }) + + it('message inbox endpoint available (GET)', async () => { + const idAlice = await Identity.buildFromURI('//Alice') + return request(app.getHttpServer()) + .get(`/messaging/inbox/${idAlice.address}`) + .expect(200, []) + }) + + it('message outbox endpoint available (GET)', async () => { + const idAlice = await Identity.buildFromURI('//Alice') + return request(app.getHttpServer()) + .get(`/messaging/sent/${idAlice.address}`) + .expect(200, []) + }) + + it('health enpoint available (GET)', () => { + return request(app.getHttpServer()) + .get('/health') + .expect(200, { status: 'ok', info: {} }) + }) + + it('faucet endpoint available (POST)', async () => { + return request(app.getHttpServer()) + .post(`/faucet/drop`) + .send({}) + .expect(400) + }) + + afterAll(async () => { + await Promise.all([app.close(), mongodbInstance.stop()]) + }) +}) diff --git a/test/contacts.e2e-spec.ts b/test/contacts.e2e-spec.ts new file mode 100644 index 0000000..a89a5d1 --- /dev/null +++ b/test/contacts.e2e-spec.ts @@ -0,0 +1,352 @@ +import { INestApplication } from '@nestjs/common' +import { Test } from '@nestjs/testing' +import request from 'supertest' +import * as sdk from '@kiltprotocol/sdk-js' +import { MockMongooseModule, mongodbInstance } from './MockMongooseModule' +import { + ContactsService, + Contact, +} from '../src/contacts/interfaces/contacts.interfaces' +import { ContactsModule } from '../src/contacts/contacts.module' +import { IDidDocument } from '@kiltprotocol/sdk-js/build/did/Did' + +describe('contacts endpoint (e2e)', () => { + let app: INestApplication + let idAlice: sdk.Identity + let idBob: sdk.Identity + let contactsService: ContactsService + let contactA: Contact + let contactB: Contact + + beforeAll(async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [ContactsModule, MockMongooseModule], + }).compile() + + app = moduleFixture.createNestApplication() + await app.init() + + contactsService = app.get('ContactsService') + idAlice = await sdk.Identity.buildFromURI('//Alice') + idBob = await sdk.Identity.buildFromURI('//Bob') + + contactA = { + publicIdentity: idAlice.getPublicIdentity(), + metaData: { name: 'Alice' }, + } + contactB = { + publicIdentity: { + ...idBob.getPublicIdentity(), + serviceAddress: 'www.example.com', + }, + metaData: { name: 'Bob' }, + } + }, 30000) + + describe('get', () => { + beforeAll(async () => { + await contactsService.removeAll() + await contactsService.add(contactA) + await contactsService.add(contactB) + }) + + it('gets all contacts', async () => { + await request(app.getHttpServer()) + .get(`/contacts`) + .expect(200) + .expect(response => { + expect(response.body).toBeInstanceOf(Array) + expect(response.body).toHaveLength(2) + expect(response.body[0]).toEqual(expect.objectContaining(contactA)) + expect(response.body[1]).toEqual(expect.objectContaining(contactB)) + }) + }) + + it('gets contact by address', async () => { + await request(app.getHttpServer()) + .get(`/contacts/${contactA.publicIdentity.address}`) + .expect(200) + .expect(response => { + expect(response.body).toEqual(expect.objectContaining(contactA)) + }) + await request(app.getHttpServer()) + .get(`/contacts/${contactB.publicIdentity.address}`) + .expect(200) + .expect(response => { + expect(response.body).toEqual(expect.objectContaining(contactB)) + }) + }) + + it('rejects request for contact if address unknown', async () => { + await request(app.getHttpServer()) + .get(`/contacts/${'unknown-address'}`) + .expect(404) + }) + + describe('with did', () => { + it('rejects query for did if contact has none', async () => { + await request(app.getHttpServer()) + .get(`/contacts/did/${contactA.publicIdentity.address}`) + .expect(404) + }) + + it('gets did by contact address', async () => { + const didAlice = sdk.Did.fromIdentity( + idAlice + ).createDefaultDidDocument() + + await contactsService.add({ ...contactA, did: didAlice }) + await request(app.getHttpServer()) + .get(`/contacts/did/${contactA.publicIdentity.address}`) + .expect(200) + .expect(response => { + expect(response.body).toMatchObject(didAlice) + }) + }) + }) + }) + + describe('add', () => { + beforeEach(async () => { + await contactsService.removeAll() + }) + + it('adds new contact if valid', async () => { + await expect(contactsService.list()).resolves.toHaveLength(0) + await request(app.getHttpServer()) + .post(`/contacts`) + .send(contactA) + .expect(201) + + const storedContacts = await contactsService.list() + expect(storedContacts).toBeInstanceOf(Array) + expect(storedContacts).toHaveLength(1) + expect(storedContacts[0]).toMatchObject(contactA) + }) + + it('overwrites name but not public identity of a contact if already registered', async () => { + await request(app.getHttpServer()) + .post(`/contacts`) + .send(contactA) + .expect(201) + + await request(app.getHttpServer()) + .post(`/contacts`) + .send({ + ...contactA, + metaData: { name: 'my friend Alice' }, + publicIdentity: { + ...contactA.publicIdentity, + serviceAddress: 'www.example.com', + }, + }) + .expect(201) + + const contacts = await contactsService.list() + expect(contacts).toHaveLength(1) + expect(contacts[0]).toMatchObject({ + ...contactA, + metaData: { name: 'my friend Alice' }, + }) + }) + + it('rejects if address missing', async () => { + const corruptedContact: Contact = { + ...contactA, + publicIdentity: { ...contactA.publicIdentity, address: undefined }, + } + await request(app.getHttpServer()) + .post(`/contacts`) + .send(corruptedContact) + .expect(400) + + await expect(contactsService.list()).resolves.toEqual([]) + }) + + it('rejects if boxPublicKeyAsHex missing', async () => { + const corruptedContact: Contact = { + ...contactA, + publicIdentity: { + ...contactA.publicIdentity, + boxPublicKeyAsHex: undefined, + }, + } + await request(app.getHttpServer()) + .post(`/contacts`) + .send(corruptedContact) + .expect(400) + + await expect(contactsService.list()).resolves.toEqual([]) + }) + + it('rejects if name missing', async () => { + const corruptedContact: Contact = { + ...contactA, + metaData: { + name: undefined, + }, + } + await request(app.getHttpServer()) + .post(`/contacts`) + .send(corruptedContact) + .expect(400) + + await expect(contactsService.list()).resolves.toEqual([]) + }) + + describe('with did', () => { + let didAlice: IDidDocument + let signature: string + + beforeAll(() => { + didAlice = sdk.Did.fromIdentity(idAlice).createDefaultDidDocument() + // TODO (KILTprotocol/ticket#687): I would expect this to work, but it doesn't: + // signature = sdk.Did.signDidDocument(didAlice, idAlice).signature + signature = idAlice.signStr( + sdk.Crypto.hashStr(JSON.stringify(didAlice)) + ) + }) + + it('adds new contact with did', async () => { + await expect(contactsService.list()).resolves.toHaveLength(0) + await request(app.getHttpServer()) + .post(`/contacts`) + .send({ + publicIdentity: idAlice.getPublicIdentity(), + metaData: { name: 'Alice' }, + did: didAlice, + signature, + } as Contact) + .expect(201) + + const storedContacts = await contactsService.list() + expect(storedContacts).toBeInstanceOf(Array) + expect(storedContacts).toHaveLength(1) + expect(storedContacts[0]).toMatchObject({ + publicIdentity: idAlice.getPublicIdentity(), + metaData: { name: 'Alice' }, + did: didAlice, + }) + }) + + it('adds did to existing contact', async () => { + await request(app.getHttpServer()) + .post(`/contacts`) + .send({ + publicIdentity: idAlice.getPublicIdentity(), + metaData: { name: 'Alice' }, + } as Contact) + .expect(201) + + await expect(contactsService.list()).resolves.toHaveLength(1) + + await request(app.getHttpServer()) + .post(`/contacts`) + .send({ + publicIdentity: idAlice.getPublicIdentity(), + metaData: { name: 'Alice' }, + did: didAlice, + signature, + } as Contact) + .expect(201) + + const storedContacts = await contactsService.list() + expect(storedContacts).toBeInstanceOf(Array) + expect(storedContacts).toHaveLength(1) + expect(storedContacts[0]).toMatchObject({ + publicIdentity: idAlice.getPublicIdentity(), + metaData: { name: 'Alice' }, + did: didAlice, + }) + }) + + it('rejects if signature missing', async () => { + await request(app.getHttpServer()) + .post(`/contacts`) + .send({ + publicIdentity: idAlice.getPublicIdentity(), + metaData: { name: 'Alice' }, + did: didAlice, + } as Contact) + .expect(400) + + await expect(contactsService.list()).resolves.toEqual([]) + }) + + it('rejects if bad signature', async () => { + await request(app.getHttpServer()) + .post(`/contacts`) + .send({ + publicIdentity: idAlice.getPublicIdentity(), + metaData: { name: 'Alice' }, + did: didAlice, + signature: idAlice.signStr( + JSON.stringify({ ...didAlice, service: 'www.happy-phishing.com' }) + ), + } as Contact) + .expect(400) + + await expect(contactsService.list()).resolves.toEqual([]) + }) + }) + }) + + describe('delete', () => { + beforeEach(async () => { + await contactsService.removeAll() + await contactsService.add(contactA) + await contactsService.add(contactB) + }) + + it('rejects unauthorized delete requests', async () => { + await request(app.getHttpServer()) + .delete(`/contacts`) + .expect(403) + await expect(contactsService.list()).resolves.toHaveLength(2) + }) + + it('accepts authorized delete-all requests', async () => { + const TOKEN = 'authtoken' + // set token with which http delete request is authorized + process.env['SECRET'] = TOKEN + await request(app.getHttpServer()) + .delete(`/contacts`) + .set('Authorization', TOKEN) + .expect(200) + await expect(contactsService.list()).resolves.toEqual([]) + }) + }) + + it('register -> get -> reset', async () => { + await request(app.getHttpServer()) + .post(`/contacts`) + .send(contactA) + .expect(201) + + await request(app.getHttpServer()) + .get(`/contacts/${contactA.publicIdentity.address}`) + .expect(200) + .expect(response => { + expect(response.body).toEqual(expect.objectContaining(contactA)) + }) + + const TOKEN = 'authtoken' + // set token with which http delete request is authorized + process.env['SECRET'] = TOKEN + await request(app.getHttpServer()) + .delete(`/contacts`) + .set('Authorization', TOKEN) + .expect(200) + + await request(app.getHttpServer()) + .get(`/contacts`) + .expect(200) + .expect(response => { + expect(response.body).toEqual([]) + }) + }) + + afterAll(async () => { + await Promise.all([app.close(), mongodbInstance.stop()]) + }) +}) diff --git a/test/ctypes.e2e-spec.ts b/test/ctypes.e2e-spec.ts new file mode 100644 index 0000000..bf246ff --- /dev/null +++ b/test/ctypes.e2e-spec.ts @@ -0,0 +1,293 @@ +import { INestApplication } from '@nestjs/common' +import { Test } from '@nestjs/testing' +import request from 'supertest' +import * as sdk from '@kiltprotocol/sdk-js' +import { MockMongooseModule, mongodbInstance } from './MockMongooseModule' +import { CTypesModule } from '../src/ctypes/ctypes.module' +import { CType, CTypeService } from '../src/ctypes/interfaces/ctype.interfaces' +import { BlockchainModule } from '../src/blockchain/blockchain.module' + +jest.mock('@kiltprotocol/sdk-js/build/ctype/CType.chain', () => { + return { + getOwner: jest.fn(async () => null), + } +}) +jest.mock( + '@kiltprotocol/sdk-js/build/blockchainApiConnection/BlockchainApiConnection' +) + +describe('ctypes endpoint (e2e)', () => { + let app: INestApplication + let idAlice: sdk.Identity + let ctypeService: CTypeService + + const kiltCTypeA = sdk.CType.fromSchema({ + $schema: 'http://kilt-protocol.org/draft-01/ctype#', + properties: { + name: { + type: 'string', + }, + age: { + type: 'integer', + }, + }, + type: 'object', + title: 'test_ctype', + }) + const cTypeRecordA: CType = { + cType: kiltCTypeA, + metaData: { + ctypeHash: kiltCTypeA.hash, + metadata: { + title: { default: 'Test Ctype' }, + properties: { + name: { title: { default: 'name' } }, + age: { title: { default: 'age' } }, + }, + }, + }, + } + + const kiltCTypeB = sdk.CType.fromSchema({ + $schema: 'http://kilt-protocol.org/draft-01/ctype#', + properties: { + name: { + type: 'string', + }, + age: { + type: 'integer', + }, + }, + type: 'object', + title: 'another_ctype', + }) + const cTypeRecordB: CType = { + cType: kiltCTypeB, + metaData: { + ctypeHash: kiltCTypeB.hash, + metadata: { + title: { default: 'Test Ctype' }, + properties: { + name: { title: { default: 'name' } }, + age: { title: { default: 'age' } }, + }, + }, + }, + } + + beforeAll(async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [CTypesModule, BlockchainModule, MockMongooseModule], + }).compile() + + app = moduleFixture.createNestApplication() + await app.init() + + ctypeService = app.get('CTypeService') + idAlice = await sdk.Identity.buildFromURI('//Alice') + }, 30000) + + beforeEach(async () => { + await ctypeService.removeAll() + }) + + describe('get', () => { + beforeEach(async () => { + await ctypeService.register(cTypeRecordA) + await ctypeService.register(cTypeRecordB) + }) + + it('gets all ctypes', async () => { + await request(app.getHttpServer()) + .get(`/ctype`) + .expect(200) + .expect(response => { + expect(response.body).toBeInstanceOf(Array) + expect(response.body).toHaveLength(2) + expect(response.body).toMatchObject([cTypeRecordA, cTypeRecordB]) + }) + }) + + it('gets ctype by hash', async () => { + await request(app.getHttpServer()) + .get(`/ctype/${cTypeRecordA.cType.hash}`) + .expect(200) + .expect(response => { + expect(response.body).toMatchObject(cTypeRecordA) + }) + await request(app.getHttpServer()) + .get(`/ctype/${cTypeRecordB.cType.hash}`) + .expect(200) + .expect(response => { + expect(response.body).toMatchObject(cTypeRecordB) + }) + }) + + it('rejects request for ctype if hash unknown', async () => { + await request(app.getHttpServer()) + .get(`/ctype/${'unknown-hash'}`) + .expect(404) + }) + }) + + describe('add', () => { + const mockedGetOwner = require('@kiltprotocol/sdk-js/build/ctype/CType.chain') + .getOwner + + beforeEach(() => { + mockedGetOwner.mockResolvedValue(null) + }) + + it('adds new ctype if hash on chain', async () => { + mockedGetOwner.mockResolvedValue(idAlice.address) + + await request(app.getHttpServer()) + .post(`/ctype`) + .send(cTypeRecordA) + .expect(201) + + expect(mockedGetOwner).toHaveBeenCalledWith(kiltCTypeA.hash) + const storedCtypes = await ctypeService.findAll() + expect(storedCtypes).toBeInstanceOf(Array) + expect(storedCtypes).toHaveLength(1) + // it overwrites ctype owner with actual owner + expect(storedCtypes[0]).toMatchObject({ + ...cTypeRecordA, + cType: { ...kiltCTypeA, owner: idAlice.address }, + }) + }) + + it('overwrites owner with chain owner', async () => { + mockedGetOwner.mockResolvedValue('new-owner') + const cTypeRecordWithOwner = { + ...cTypeRecordA, + cType: sdk.CType.fromSchema(kiltCTypeA.schema, idAlice.address), + } + await request(app.getHttpServer()) + .post(`/ctype`) + .send(cTypeRecordWithOwner) + .expect(201) + + expect(mockedGetOwner).toHaveBeenCalledWith(kiltCTypeA.hash) + const storedCtypes = await ctypeService.findAll() + expect(storedCtypes).toBeInstanceOf(Array) + expect(storedCtypes).toHaveLength(1) + // it overwrites ctype owner with actual owner + expect(storedCtypes[0]).toMatchObject({ + ...cTypeRecordWithOwner, + cType: { ...kiltCTypeA, owner: 'new-owner' }, + }) + }) + + it('rejects new ctype if hash not on chain', async () => { + await request(app.getHttpServer()) + .post(`/ctype`) + .send(cTypeRecordA) + .expect(400) + + expect(mockedGetOwner).toHaveBeenCalledWith(kiltCTypeA.hash) + await expect(ctypeService.findAll()).resolves.toEqual([]) + }) + + it('rejects if data corrupted or ctype invalid', async () => { + mockedGetOwner.mockResolvedValue(idAlice.address) + const corruptedCtype: CType = { + ...cTypeRecordA, + cType: { + ...kiltCTypeA, + schema: { ...kiltCTypeA.schema, title: 'different-title' }, + }, + } + await request(app.getHttpServer()) + .post(`/ctype`) + .send(corruptedCtype) + .expect(400) + + await expect(ctypeService.findAll()).resolves.toEqual([]) + }) + + it('rejects if ctype already registered', async () => { + mockedGetOwner.mockResolvedValue(idAlice.address) + await request(app.getHttpServer()) + .post(`/ctype`) + .send(cTypeRecordA) + .expect(201) + + await request(app.getHttpServer()) + .post(`/ctype`) + .send(cTypeRecordA) + .expect(400) + + await expect(ctypeService.findAll()).resolves.toHaveLength(1) + }) + }) + + describe('delete', () => { + beforeEach(async () => { + await ctypeService.register(cTypeRecordA) + await ctypeService.register(cTypeRecordB) + }) + + it('rejects unauthorized delete requests', async () => { + await request(app.getHttpServer()) + .delete(`/ctype`) + .expect(403) + await expect(ctypeService.findAll()).resolves.toMatchObject([ + cTypeRecordA, + cTypeRecordB, + ]) + }) + + it('accepts authorized delete-all requests', async () => { + const TOKEN = 'authtoken' + // set token with which http delete request is authorized + process.env['SECRET'] = TOKEN + await request(app.getHttpServer()) + .delete(`/ctype`) + .set('Authorization', TOKEN) + .expect(200) + await expect(ctypeService.findAll()).resolves.toEqual([]) + }) + }) + + it('register -> get -> reset', async () => { + require('@kiltprotocol/sdk-js/build/ctype/CType.chain').getOwner.mockResolvedValue( + idAlice.address + ) + + await request(app.getHttpServer()) + .post(`/ctype`) + .send(cTypeRecordA) + .expect(201) + + await request(app.getHttpServer()) + .get(`/ctype/${cTypeRecordA.cType.hash}`) + .expect(200) + .expect(response => { + expect(response.body).toMatchObject({ + ...cTypeRecordA, + // it overwrites ctype owner with actual owner + cType: { ...kiltCTypeA, owner: idAlice.address }, + }) + }) + + const TOKEN = 'authtoken' + // set token with which http delete request is authorized + process.env['SECRET'] = TOKEN + await request(app.getHttpServer()) + .delete(`/ctype`) + .set('Authorization', TOKEN) + .expect(200) + + await request(app.getHttpServer()) + .get(`/ctype`) + .expect(200) + .expect(response => { + expect(response.body).toEqual([]) + }) + }) + + afterAll(async () => { + await Promise.all([app.close(), mongodbInstance.stop()]) + }) +}) diff --git a/test/faucet.e2e-spec.ts b/test/faucet.e2e-spec.ts new file mode 100644 index 0000000..89b5938 --- /dev/null +++ b/test/faucet.e2e-spec.ts @@ -0,0 +1,89 @@ +import { INestApplication } from '@nestjs/common' +import { Test } from '@nestjs/testing' +import request from 'supertest' +import { Identity, Balance } from '@kiltprotocol/sdk-js' +import BN from 'bn.js' +import { FaucetService } from '../src/faucet/interfaces/faucet.interfaces' +import { MockMongooseModule, mongodbInstance } from './MockMongooseModule' +import { FaucetModule } from '../src/faucet/faucet.module' + +jest.mock('@kiltprotocol/sdk-js/build/balance/Balance.chain', () => { + return { + makeTransfer: jest.fn(() => Promise.resolve({ isFinalized: true })), + } +}) + +const FAUCET_SEED = + '0xcdfd6024d2b0eba27d54cc92a44cd9a627c69b2dbda15ed7e58085425119ae03' + +describe('faucet endpoint (e2e)', () => { + let app: INestApplication + let idAlice: Identity + let faucetService: FaucetService + + beforeAll(async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [FaucetModule, MockMongooseModule], + }).compile() + + app = moduleFixture.createNestApplication() + await app.init() + + faucetService = app.get('FaucetService') + idAlice = await Identity.buildFromURI('//Alice') + + process.env['FAUCET_ACCOUNT'] = FAUCET_SEED + }, 30000) + + beforeEach(async () => { + await faucetService.reset() + }) + + it('rejects malformed requests', async () => { + return request(app.getHttpServer()) + .post(`/faucet/drop`) + .send(idAlice.getBoxPublicKey()) + .expect(400) + }) + + // TODO, see KILTprotocol/ticket#686 + xit('handles invalid destination address / public key', async () => { + require('@kiltprotocol/sdk-js/build/balance/Balance.chain').makeTransfer.mockRejectedValueOnce( + 'transfer destination invalid' + ) + await request(app.getHttpServer()) + .post(`/faucet/drop`) + .send('pubkey=0x1234') + .expect(400) + }) + + it('accepts first valid request', async () => { + const spy = jest.spyOn(Balance, 'makeTransfer') + await request(app.getHttpServer()) + .post(`/faucet/drop`) + .send(`pubkey=${idAlice.getBoxPublicKey()}`) + .expect(201) + + expect(spy).toHaveBeenCalledWith( + expect.objectContaining({ seedAsHex: FAUCET_SEED }), + idAlice.getBoxPublicKey(), + expect.any(BN) + ) + }) + + it('rejects second valid request', async () => { + await request(app.getHttpServer()) + .post(`/faucet/drop`) + .send(`pubkey=${idAlice.getBoxPublicKey()}`) + .expect(201) + + await request(app.getHttpServer()) + .post(`/faucet/drop`) + .send(`pubkey=${idAlice.getBoxPublicKey()}`) + .expect(400) + }) + + afterAll(async () => { + await Promise.all([app.close(), mongodbInstance.stop()]) + }) +}) diff --git a/test/messaging.e2e-spec.ts b/test/messaging.e2e-spec.ts new file mode 100644 index 0000000..99a7579 --- /dev/null +++ b/test/messaging.e2e-spec.ts @@ -0,0 +1,381 @@ +import { INestApplication } from '@nestjs/common' +import { Test } from '@nestjs/testing' +import { + Identity, + Message, + MessageBodyType, + IEncryptedMessage, +} from '@kiltprotocol/sdk-js' +import supertest from 'supertest' +import { MessagingService } from '../src/messaging/interfaces/messaging.interfaces' +import { MessagingModule } from '../src/messaging/messaging.module' +import { MockMongooseModule, mongodbInstance } from './MockMongooseModule' + +function assertErrorMessageIs( + message: string, + response: supertest.Response +): void { + if (response.body['message'] !== message) + throw new Error( + `Expected error message '${message}', got '${response.body['message']}'` + ) +} + +let app: INestApplication +let request: supertest.SuperTest +let messagingService: MessagingService + +beforeAll(async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [MessagingModule, MockMongooseModule], + }).compile() + + app = moduleFixture.createNestApplication() + await app.init() + + messagingService = app.get('MessagingService') + request = supertest(app.getHttpServer()) +}, 30000) + +describe('messaging (e2e)', () => { + let sender: Identity + let recipient: Identity + let message: Message + + beforeAll(async () => { + sender = await Identity.buildFromURI('//Alice') + recipient = await Identity.buildFromURI('//Bob') + message = new Message( + { + type: MessageBodyType.REQUEST_TERMS, + content: { cTypeHash: 'CTYPEHASH' }, + }, + sender, + recipient.getPublicIdentity() + ) + }) + + it('/messaging (GET) returns 404', () => { + return request.get('/messaging').expect(404) + }) + + describe('sending messages', () => { + describe('positive tests', () => { + beforeEach(async () => { + await messagingService.removeAll() + }) + + it('accepts valid send request', async () => { + const encrypted = message.encrypt() + await request + .post(`/messaging`) + .send(encrypted) + .expect(201) + + const inbox = await app + .get('MessagingService') + .findByReceiverAddress(recipient.address) + + expect(inbox).toBeInstanceOf(Array) + expect(inbox).toHaveLength(1) + expect(inbox[0]).toMatchObject({ + ...encrypted, + messageId: expect.any(String), + receivedAt: expect.any(Number), + }) + }) + + it('returns message id & timestamp', async () => { + const encrypted = message.encrypt() + await request + .post(`/messaging`) + .send(encrypted) + .expect(201) + .expect(response => { + expect(response.body).toHaveProperty( + 'messageId', + expect.any(String) + ) + expect(response.body).toHaveProperty( + 'receivedAt', + expect.any(Number) + ) + // expect timestamp to be within one second of Date.now() + expect(Date.now() - response.body['receivedAt']).toBeLessThan(1000) + }) + }) + + it('shows message in sent & inbox after sending', async () => { + const encrypted = message.encrypt() + function responseContainsSentMessage( + response: supertest.Response + ): void { + expect(response.body).toBeInstanceOf(Array) + expect(response.body).toHaveLength(1) + expect(response.body[0]).toMatchObject({ + ...encrypted, + messageId: expect.any(String), + receivedAt: expect.any(Number), + }) + } + // wait for message to be delivered + await request + .post(`/messaging`) + .send(encrypted) + .expect(201) + // request recipient's inbox & sender's outbox + await request + .get(`/messaging/inbox/${recipient.address}`) + .expect(200) + .expect(responseContainsSentMessage) + await request + .get(`/messaging/sent/${sender.address}`) + .expect(200) + .expect(responseContainsSentMessage) + }) + }) + + describe('negative tests', () => { + it('rejects request with empty body', async () => { + return request + .post(`/messaging`) + .send({}) + .expect(400) + }) + + it('rejects request with missing sender address', async () => { + const encrypted = message.encrypt() + encrypted.senderAddress = undefined + return request + .post(`/messaging`) + .send(encrypted) + .expect(400) + .expect(response => + assertErrorMessageIs('no sender address', response) + ) + }) + + it('rejects request with missing recipient address', async () => { + const encrypted = message.encrypt() + encrypted.receiverAddress = undefined + return request + .post(`/messaging`) + .send(encrypted) + .expect(400) + .expect(response => + assertErrorMessageIs('no receiver address', response) + ) + }) + + it('rejects request with missing nonce', async () => { + const encrypted = message.encrypt() + encrypted.nonce = undefined + return request + .post(`/messaging`) + .send(encrypted) + .expect(400) + .expect(response => assertErrorMessageIs('no nonce', response)) + }) + + it('rejects request with missing message body', async () => { + const encrypted = message.encrypt() + encrypted.message = undefined + return request + .post(`/messaging`) + .send(encrypted) + .expect(400) + .expect(response => assertErrorMessageIs('no message', response)) + }) + + it('rejects request with missing hash', async () => { + const encrypted = message.encrypt() + encrypted.hash = undefined + return request + .post(`/messaging`) + .send(encrypted) + .expect(400) + .expect(response => assertErrorMessageIs('no hash', response)) + }) + + it('rejects request with missing signature', async () => { + const encrypted = message.encrypt() + encrypted.signature = undefined + return request + .post(`/messaging`) + .send(encrypted) + .expect(400) + .expect(response => assertErrorMessageIs('no signature', response)) + }) + }) + }) + + describe('deleting messages', () => { + let message1: IEncryptedMessage + let message2: IEncryptedMessage + + beforeEach(async () => { + message1 = message.encrypt() + message1.messageId = 'id1' + message2 = message.encrypt() + message2.messageId = 'id2' + await messagingService.removeAll() + await messagingService.add(message1) + await messagingService.add(message2) + }) + + it('deletes a message by id', async () => { + let inbox: IEncryptedMessage[] + await expect( + messagingService.findByReceiverAddress(recipient.address) + ).resolves.toHaveLength(2) + await request.delete('/messaging/id1').expect(200) + inbox = await messagingService.findByReceiverAddress(recipient.address) + expect(inbox).toHaveLength(1) + expect(inbox[0]).toMatchObject(message2) + await request.delete('/messaging/id2').expect(200) + inbox = await messagingService.findByReceiverAddress(recipient.address) + expect(inbox).toHaveLength(0) + }) + + // TODO, see KILTprotocol/ticket#685 + xit('rejects delete requests for unknown id', async () => { + await request.delete('/messaging/idx').expect(400) + }) + + it('rejects unauthorized delete-all requests', async () => { + return request.delete(`/messaging`).expect(403) + }) + + it('accepts authorized delete-all requests', async () => { + const TOKEN = 'authtoken' + // set token with which http delete request is authorized + process.env['SECRET'] = TOKEN + await expect( + messagingService.findByReceiverAddress(recipient.address) + ).resolves.toHaveLength(2) + await request + .delete(`/messaging`) + .set('Authorization', TOKEN) + .expect(200) + await expect( + messagingService.findByReceiverAddress(recipient.address) + ).resolves.toHaveLength(0) + }) + }) + + describe('inbox', () => { + beforeEach(async () => { + await messagingService.removeAll() + }) + + it('lists empty inbox (GET)', async () => { + return request + .get(`/messaging/inbox/${recipient.address}`) + .expect(200, []) + }) + + it('lists incoming messages', async () => { + await request.get(`/messaging/inbox/${recipient.address}`).expect(200, []) + const message1: IEncryptedMessage = { + ...message.encrypt(), + messageId: 'id1', + receivedAt: Date.now(), + } + const message2: IEncryptedMessage = { + ...message.encrypt(), + messageId: 'id2', + receivedAt: Date.now(), + } + + await messagingService.add(message1) + await request + .get(`/messaging/inbox/${recipient.address}`) + .expect(200) + .expect(response => { + expect(response.body).toHaveLength(1) + expect(response.body[0]).toMatchObject(message1) + }) + await messagingService.add(message2) + await request + .get(`/messaging/inbox/${recipient.address}`) + .expect(200) + .expect(response => { + expect(response.body).toHaveLength(2) + expect(response.body[0]).toMatchObject(message1) + expect(response.body[1]).toMatchObject(message2) + }) + }) + }) + + describe('outbox', () => { + beforeEach(async () => { + await messagingService.removeAll() + }) + + it('lists empty outbox (GET)', async () => { + return request.get(`/messaging/sent/${sender.address}`).expect(200, []) + }) + + it('lists outgoing messages', async () => { + const message1: IEncryptedMessage = { + ...message.encrypt(), + messageId: 'id1', + receivedAt: Date.now(), + } + const message2: IEncryptedMessage = { + ...message.encrypt(), + messageId: 'id2', + receivedAt: Date.now(), + } + + await request.get(`/messaging/sent/${sender.address}`).expect(200, []) + await messagingService.add(message1) + await request + .get(`/messaging/sent/${sender.address}`) + .expect(200) + .expect(response => { + expect(response.body).toHaveLength(1) + expect(response.body[0]).toMatchObject(message1) + }) + await messagingService.add(message2) + await request + .get(`/messaging/sent/${sender.address}`) + .expect(200) + .expect(response => { + expect(response.body).toHaveLength(2) + expect(response.body[0]).toMatchObject(message1) + expect(response.body[1]).toMatchObject(message2) + }) + }) + }) + + it('send -> receive -> decrypt -> delete', async () => { + await messagingService.removeAll() + const encrypted = message.encrypt() + const messageId: string = await request + .post(`/messaging`) + .send(encrypted) + .expect(201) + .then(response => response.body['messageId']) + + const receivedMessages: IEncryptedMessage[] = await request + .get(`/messaging/inbox/${recipient.address}`) + .expect(200) + .then(response => response.body) + expect(receivedMessages).toBeInstanceOf(Array) + expect(receivedMessages).toHaveLength(1) + expect(receivedMessages[0]).toMatchObject({ + ...encrypted, + messageId, + receivedAt: expect.any(Number), + }) + const decrypted = Message.decrypt(encrypted, recipient) + expect(decrypted).toMatchObject(message) + await request.delete(`/messaging/${messageId}`).expect(200) + await request.get(`/messaging/inbox/${recipient.address}`).expect(200, []) + }) +}) + +afterAll(async () => { + await Promise.all([app.close(), mongodbInstance.stop()]) +}) diff --git a/test/mongoose.e2e-spec.ts b/test/mongoose.e2e-spec.ts new file mode 100644 index 0000000..0c4c9db --- /dev/null +++ b/test/mongoose.e2e-spec.ts @@ -0,0 +1,28 @@ +import { INestApplication } from '@nestjs/common' +import { Test } from '@nestjs/testing' +import { MessagingService } from '../src/messaging/interfaces/messaging.interfaces' +import { MessagingModule } from '../src/messaging/messaging.module' +import { MockMongooseModule, mongodbInstance } from './MockMongooseModule' + +let app: INestApplication +let messagingService: MessagingService + +it('connects to mongodb', async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [MockMongooseModule, MessagingModule], + }).compile() + + app = moduleFixture.createNestApplication() + await app.init() + + messagingService = app.get('MessagingService') + + await expect(messagingService.findBySenderAddress('0xaaa')).resolves.toEqual( + [] + ) +}, 30000) + +afterAll(async () => { + await mongodbInstance.stop() + await app.close() +}) diff --git a/yarn.lock b/yarn.lock index 4071568..0f5a24c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -390,6 +390,23 @@ resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80" integrity sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw== +"@types/cross-spawn@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/cross-spawn/-/cross-spawn-6.0.2.tgz#168309de311cd30a2b8ae720de6475c2fbf33ac7" + integrity sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw== + dependencies: + "@types/node" "*" + +"@types/debug@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" + integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== + +"@types/dedent@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@types/dedent/-/dedent-0.7.0.tgz#155f339ca404e6dd90b9ce46a3f78fd69ca9b050" + integrity sha512-EGlKlgMhnLt/cM4DbUSafFdrkeJoC9Mvnj0PUCU7tFmTjMjNRT957kXCx0wYm3JuEq4o4ZsS5vG+NlkM2DMd2A== + "@types/express-serve-static-core@*": version "4.16.3" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.3.tgz#49d9cea50e801f8bf757702060752fe65f169ba7" @@ -407,6 +424,18 @@ "@types/express-serve-static-core" "*" "@types/serve-static" "*" +"@types/find-cache-dir@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@types/find-cache-dir/-/find-cache-dir-3.2.0.tgz#eaaf331699dccf52c47926e4d4f8f3ed8db33f3c" + integrity sha512-+JeT9qb2Jwzw72WdjU+TSvD5O1QRPWCeRpDJV+guiIq+2hwR0DFGw+nZNbTFjMIVe6Bf4GgAKeB/6Ytx6+MbeQ== + +"@types/find-package-json@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/find-package-json/-/find-package-json-1.1.1.tgz#c0d296ac74fe3309ed0fe75a9c3edb42a776d30c" + integrity sha512-XMCocYkg6VUpkbOQMKa3M5cgc3MvU/LJKQwd3VUJrWZbLr2ARUggupsCAF8DxjEEIuSO6HlnH+vl+XV4bgVeEQ== + dependencies: + "@types/node" "*" + "@types/jest@^23.3.1": version "23.3.14" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.14.tgz#37daaf78069e7948520474c87b80092ea912520a" @@ -417,16 +446,33 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lockfile@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/lockfile/-/lockfile-1.0.1.tgz#434a3455e89843312f01976e010c60f1bcbd56f7" + integrity sha512-65WZedEm4AnOsBDdsapJJG42MhROu3n4aSSiu87JXF/pSdlubxZxp3S1yz3kTfkJ2KBPud4CpjoHVAptOm9Zmw== + "@types/long@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef" integrity sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q== +"@types/md5-file@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/md5-file/-/md5-file-4.0.2.tgz#c7241e88f4aa17218c774befb0fc34f33f21fe36" + integrity sha512-8gacRfEqLrmZ6KofpFfxyjsm/LYepeWUWUJGaf5A9W9J5B2/dRZMdkDqFDL6YDa9IweH12IO76jO7mpsK2B3wg== + "@types/mime@*": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== +"@types/mkdirp@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-1.0.1.tgz#0930b948914a78587de35458b86c907b6e98bbf6" + integrity sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q== + dependencies: + "@types/node" "*" + "@types/mongodb@*": version "3.1.25" resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.1.25.tgz#1da0219b7d8c08859f811774673c803f906e5e05" @@ -491,6 +537,16 @@ dependencies: "@types/superagent" "*" +"@types/tmp@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.0.tgz#e3f52b4d7397eaa9193592ef3fdd44dc0af4298c" + integrity sha512-flgpHJjntpBAdJD43ShRosQvNC0ME97DCfGvZEDlAThQmnerRXrLbX6YgzRBQCZTthET9eAWFAMaYP0m0Y4HzQ== + +"@types/uuid@^8.0.0": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.1.tgz#42958a1a880640b139eea97a1640e1a3f61016d2" + integrity sha512-2kE8rEFgJpbBAPw5JghccEevQb0XVU0tewF/8h7wPQTeCtoJ6h8qmBIwuzUVm2MutmzC/cpCkwxudixoNYDp1A== + abab@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" @@ -532,6 +588,13 @@ acorn@^6.0.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== +agent-base@6: + version "6.0.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" + integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg== + dependencies: + debug "4" + ajv@^6.12.2: version "6.12.3" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706" @@ -1025,6 +1088,23 @@ bl@^1.2.1: readable-stream "^2.3.5" safe-buffer "^5.1.1" +bl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.0.tgz#e1a574cdf528e4053019bb800b041c0ac88da493" + integrity sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA== + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" + +bl@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.2.tgz#52b71e9088515d0606d9dd9cc7aa48dc1f98e73a" + integrity sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + blakejs@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.1.0.tgz#69df92ef953aa88ca51a32df6ab1c54a155fc7a5" @@ -1164,6 +1244,16 @@ bson@^1.1.1, bson@~1.1.1: resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.1.tgz#4330f5e99104c4e751e7351859e2d408279f2f13" integrity sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg== +bson@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.4.tgz#f76870d799f15b854dffb7ee32f0a874797f7e89" + integrity sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q== + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + buffer-from@1.x, buffer-from@^1.0.0, buffer-from@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -1182,6 +1272,14 @@ buffer@^5.1.0: base64-js "^1.0.2" ieee754 "^1.1.4" +buffer@^5.5.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -1250,6 +1348,11 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== +camelcase@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e" + integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w== + capture-exit@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-1.2.0.tgz#1c5fcc489fd0ab00d4f1ac7ae1072e3173fbab6f" @@ -1460,6 +1563,11 @@ commist@^1.0.0: leven "^2.1.0" minimist "^1.1.0" +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -1612,6 +1720,15 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" @@ -1672,6 +1789,13 @@ debug@3.1.0: dependencies: ms "2.0.0" +debug@4, debug@^4.1.0, debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -1679,13 +1803,6 @@ debug@^3.1.0, debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - decamelize@^1.1.1, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1696,6 +1813,11 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -1757,6 +1879,11 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= +denque@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf" + integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ== + depd@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" @@ -2314,6 +2441,13 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + figlet@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.2.1.tgz#48d35df9d9b10b1b3888302e6e57904a0b00509c" @@ -2373,6 +2507,20 @@ finalhandler@1.1.1: statuses "~1.4.0" unpipe "~1.0.0" +find-cache-dir@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" + integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-package-json@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/find-package-json/-/find-package-json-1.2.0.tgz#4057d1b943f82d8445fe52dc9cf456f6b8b58083" + integrity sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw== + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -2395,6 +2543,14 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + follow-redirects@^1.3.0: version "1.7.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76" @@ -2450,6 +2606,11 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-minipass@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" @@ -2499,6 +2660,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-port@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" + integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== + get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -2808,6 +2974,14 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" @@ -2875,7 +3049,7 @@ inherits@2, inherits@2.0.3, inherits@~2.0.1, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -inherits@^2.0.1, inherits@^2.0.3: +inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3883,6 +4057,20 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lockfile@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609" + integrity sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA== + dependencies: + signal-exit "^3.0.2" + lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -3952,6 +4140,13 @@ make-dir@^1.0.0: dependencies: pify "^3.0.0" +make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + make-error@1.x, make-error@^1.1.1: version "1.3.5" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" @@ -3988,6 +4183,11 @@ math-random@^1.0.1: resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== +md5-file@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-5.0.0.tgz#e519f631feca9c39e7f9ea1780b63c4745012e20" + integrity sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw== + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -4040,6 +4240,11 @@ memory-fs@^0.4.0: errno "^0.1.3" readable-stream "^2.0.1" +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -4189,6 +4394,51 @@ mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1: dependencies: minimist "0.0.8" +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mongodb-memory-server-core@6.6.3: + version "6.6.3" + resolved "https://registry.yarnpkg.com/mongodb-memory-server-core/-/mongodb-memory-server-core-6.6.3.tgz#b4f0304c48ce96a52b699511e042254756fc3d4c" + integrity sha512-MTs2qCb+5JG4qPCenqo+L0cxQiO09EqTZNk4I5+dz8nRUiVPFLL64tO/oJ1WDuSJ6vcyk8dC+QsNAD1wjZxx8g== + dependencies: + "@types/cross-spawn" "^6.0.2" + "@types/debug" "^4.1.5" + "@types/dedent" "^0.7.0" + "@types/find-cache-dir" "^3.2.0" + "@types/find-package-json" "^1.1.1" + "@types/lockfile" "^1.0.1" + "@types/md5-file" "^4.0.2" + "@types/mkdirp" "^1.0.1" + "@types/tmp" "^0.2.0" + "@types/uuid" "^8.0.0" + camelcase "^6.0.0" + cross-spawn "^7.0.3" + debug "^4.1.1" + dedent "^0.7.0" + find-cache-dir "^3.3.1" + find-package-json "^1.2.0" + get-port "^5.1.1" + https-proxy-agent "^5.0.0" + lockfile "^1.0.4" + md5-file "^5.0.0" + mkdirp "^1.0.4" + tar-stream "^2.1.3" + tmp "^0.2.1" + uuid "^8.2.0" + yauzl "^2.10.0" + optionalDependencies: + mongodb "^3.5.9" + +mongodb-memory-server@^6.6.3: + version "6.6.3" + resolved "https://registry.yarnpkg.com/mongodb-memory-server/-/mongodb-memory-server-6.6.3.tgz#e1e18c805fa233b7efe49cbe2866d052e533a1ed" + integrity sha512-zx91SQQUBafVfBX8IJjfZa0lIMzdDYs/UB1vnr33e5bSPBwSai+mVV6gW3osF4paLFxOkcvOwx758G9F9HytgA== + dependencies: + mongodb-memory-server-core "6.6.3" + mongodb@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.3.2.tgz#ff086b5f552cf07e24ce098694210f3d42d668b2" @@ -4198,6 +4448,19 @@ mongodb@3.3.2: require_optional "^1.0.1" safe-buffer "^5.1.2" +mongodb@^3.5.9: + version "3.6.0" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.6.0.tgz#babd7172ec717e2ed3f85e079b3f1aa29dce4724" + integrity sha512-/XWWub1mHZVoqEsUppE0GV7u9kanLvHxho6EvBxQbShXTKYF9trhZC2NzbulRGeG7xMJHD8IOWRcdKx5LPjAjQ== + dependencies: + bl "^2.2.0" + bson "^1.1.4" + denque "^1.4.1" + require_optional "^1.0.1" + safe-buffer "^5.1.2" + optionalDependencies: + saslprep "^1.0.0" + mongoose-legacy-pluralize@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4" @@ -4706,6 +4969,13 @@ p-limit@^2.0.0: dependencies: p-try "^2.0.0" +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -4720,6 +4990,13 @@ p-locate@^3.0.0: dependencies: p-limit "^2.0.0" +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -4794,6 +5071,11 @@ path-exists@^3.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -4809,6 +5091,11 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + path-parse@^1.0.5, path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -4855,6 +5142,11 @@ pbkdf2@^3.1.1: safe-buffer "^5.0.1" sha.js "^2.4.8" +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -4889,6 +5181,13 @@ pkg-dir@^2.0.0: dependencies: find-up "^2.1.0" +pkg-dir@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + pn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" @@ -5153,6 +5452,15 @@ readable-stream@1.1.x, "readable-stream@1.x >=1.1.9": string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdirp@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" @@ -5381,6 +5689,13 @@ rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: dependencies: glob "^7.1.3" +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -5423,6 +5738,11 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -5451,6 +5771,13 @@ sane@^2.0.0: optionalDependencies: fsevents "^1.2.3" +saslprep@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" + integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== + dependencies: + sparse-bitfield "^3.0.3" + sax@>=0.6.0, sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -5473,6 +5800,11 @@ semver-diff@^2.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== +semver@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + send@0.16.2: version "0.16.2" resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" @@ -5552,11 +5884,23 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -5663,6 +6007,13 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE= + dependencies: + memory-pager "^1.0.2" + spdx-correct@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" @@ -5841,6 +6192,13 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -5956,6 +6314,17 @@ tapable@^1.0.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== +tar-stream@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.3.tgz#1e2022559221b7866161660f118255e20fa79e41" + integrity sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA== + dependencies: + bl "^4.0.1" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar@^4: version "4.4.8" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" @@ -6035,6 +6404,13 @@ timers-ext@0.1, timers-ext@^0.1.5: es5-ext "~0.10.46" next-tick "1" +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" @@ -6437,7 +6813,7 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -6465,7 +6841,7 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== -uuid@^8.1.0: +uuid@^8.1.0, uuid@^8.2.0: version "8.3.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ== @@ -6589,6 +6965,13 @@ which@^1.2.12, which@^1.2.9, which@^1.3.0: dependencies: isexe "^2.0.0" +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -6790,6 +7173,14 @@ yargs@^3.10.0: window-size "^0.1.4" y18n "^3.2.0" +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yn@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a"