From 12a2da7d3cd51b8c60661ab28c34e17ac6e2846e Mon Sep 17 00:00:00 2001 From: Innei Date: Fri, 14 Jan 2022 15:36:17 +0800 Subject: [PATCH] test: add mock db test --- jest.config.js | 14 +- package.json | 1 + pnpm-lock.yaml | 130 +++++++++++++++++- src/app.module.ts | 4 +- src/modules/snippet/snippet.controller.ts | 3 +- src/modules/snippet/snippet.service.ts | 16 ++- src/processors/database/database.module.ts | 2 +- test-setup.js | 6 + test/helper/db-mock.helper.ts | 45 ++++++ .../snippet/snippet.controller.e2e-spec.ts | 23 ---- .../modules/snippet/snippet.service.spec.ts | 81 +++++++++++ 11 files changed, 280 insertions(+), 45 deletions(-) create mode 100644 test-setup.js create mode 100644 test/helper/db-mock.helper.ts create mode 100644 test/src/modules/snippet/snippet.service.spec.ts diff --git a/jest.config.js b/jest.config.js index ed30e871..a6c6b427 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,30 +1,26 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -const { pathsToModuleNameMapper } = require('ts-jest/utils') +const { pathsToModuleNameMapper } = require('ts-jest') // In the following statement, replace `./tsconfig` with the path to your `tsconfig` file // which contains the path mapping (ie the `compilerOptions.paths` option): const { compilerOptions } = require('./tsconfig.json') -const { cd, $, chalk } = require('zx') + module.exports = { moduleFileExtensions: ['js', 'json', 'ts'], rootDir: '.', testRegex: '.*\\.spec\\.ts$', - transform: { - '^.+\\.(t|j)s$': 'ts-jest', - }, + collectCoverageFrom: ['**/*.(t|j)s'], coverageDirectory: '../coverage', extensionsToTreatAsEsm: ['.ts'], - preset: 'ts-jest/presets/default-esm', + preset: 'ts-jest', testEnvironment: 'node', globals: { 'ts-jest': { useESM: true, }, isDev: process.env.NODE_ENV === 'development', - $, - chalk, - cd, }, + setupFiles: ['./test-setup.js'], moduleNameMapper: { ...pathsToModuleNameMapper(compilerOptions.paths, { prefix: '/' }), diff --git a/package.json b/package.json index b334ab18..d39794c7 100644 --- a/package.json +++ b/package.json @@ -150,6 +150,7 @@ "jest": "27.4.7", "lint-staged": "12.1.7", "mockingoose": "2.15.2", + "mongodb-memory-server": "8.1.0", "prettier": "2.5.1", "rimraf": "3.0.2", "run-script-webpack-plugin": "0.0.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5cdffb08..c831c7af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,6 +73,7 @@ specifiers: marked: 4.0.9 mkdirp: '*' mockingoose: 2.15.2 + mongodb-memory-server: 8.1.0 mongoose: '*' mongoose-lean-id: 0.3.0 mongoose-lean-virtuals: 0.9.0 @@ -193,6 +194,7 @@ devDependencies: jest: 27.4.7_ts-node@10.4.0 lint-staged: 12.1.7 mockingoose: 2.15.2_mongoose@6.1.6 + mongodb-memory-server: 8.1.0 prettier: 2.5.1 rimraf: 3.0.2 run-script-webpack-plugin: 0.0.11 @@ -2116,6 +2118,10 @@ packages: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true + /@types/tmp/0.2.3: + resolution: {integrity: sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA==} + dev: true + /@types/ua-parser-js/0.7.36: resolution: {integrity: sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==} dev: true @@ -2877,6 +2883,12 @@ packages: engines: {node: '>=8'} dev: true + /async-mutex/0.3.2: + resolution: {integrity: sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==} + dependencies: + tslib: 2.3.1 + dev: true + /async-retry/1.3.3: resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} dependencies: @@ -3130,6 +3142,10 @@ packages: dependencies: buffer: 5.7.1 + /buffer-crc32/0.2.13: + resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=} + dev: true + /buffer-equal-constant-time/1.0.1: resolution: {integrity: sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=} dev: false @@ -3210,6 +3226,11 @@ packages: resolution: {integrity: sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==} engines: {node: '>=10'} + /camelcase/6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true + /caniuse-lite/1.0.30001279: resolution: {integrity: sha512-VfEHpzHEXj6/CxggTwSFoZBBYGQfQv9Cf42KPlO79sWXCD1QNKWKsKzFeWL7QpZHJQYAvocqV6Rty1yJMkqWLQ==} dev: true @@ -3424,6 +3445,10 @@ packages: engines: {node: '>= 12'} dev: true + /commondir/1.0.1: + resolution: {integrity: sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=} + dev: true + /component-emitter/1.3.0: resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} @@ -4457,6 +4482,12 @@ packages: bser: 2.1.1 dev: true + /fd-slicer/1.1.0: + resolution: {integrity: sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=} + dependencies: + pend: 1.2.0 + dev: true + /figures/3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -4487,6 +4518,15 @@ packages: dependencies: to-regex-range: 5.0.1 + /find-cache-dir/3.3.2: + resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + engines: {node: '>=8'} + dependencies: + commondir: 1.0.1 + make-dir: 3.1.0 + pkg-dir: 4.2.0 + dev: true + /find-my-way/4.5.1: resolution: {integrity: sha512-kE0u7sGoUFbMXcOG/xpkmz4sRLCklERnBcg7Ftuu1iAxsfEt2S46RLJ3Sq7vshsEy2wJT2hZxE58XZK27qa8kg==} engines: {node: '>=10'} @@ -4612,6 +4652,10 @@ packages: resolution: {integrity: sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=} dev: false + /fs-constants/1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + dev: true + /fs-extra/10.0.0: resolution: {integrity: sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==} engines: {node: '>=12'} @@ -4693,6 +4737,11 @@ packages: engines: {node: '>=8.0.0'} dev: true + /get-port/5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + dev: true + /get-stream/5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -6302,6 +6351,12 @@ packages: hasBin: true dev: false + /md5-file/5.0.0: + resolution: {integrity: sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + /memfs/3.3.0: resolution: {integrity: sha512-BEE62uMfKOavX3iG7GYX43QJ+hAeeWnwIAuJ/R6q96jaMtiLzhsxHJC8B1L7fK7Pt/vXDRwb3SG/yBpNGDPqzg==} engines: {node: '>= 4.0.0'} @@ -6429,6 +6484,41 @@ packages: '@types/whatwg-url': 8.2.1 whatwg-url: 11.0.0 + /mongodb-memory-server-core/8.1.0: + resolution: {integrity: sha512-U9NZc07OqiOOymrYn6g9yrAaB2VLp5RFUJ+vhjxcgrvZ/7tHQVX2OmhQfOlevzW1Pt1KvFhqe7ysioysPxQXYg==} + engines: {node: '>=12.22.0'} + dependencies: + '@types/tmp': 0.2.3 + async-mutex: 0.3.2 + camelcase: 6.3.0 + debug: 4.3.3 + find-cache-dir: 3.3.2 + get-port: 5.1.1 + https-proxy-agent: 5.0.0 + md5-file: 5.0.0 + mongodb: 4.2.2 + new-find-package-json: 1.1.0 + semver: 7.3.5 + tar-stream: 2.2.0 + tmp: 0.2.1 + tslib: 2.3.1 + uuid: 8.3.2 + yauzl: 2.10.0 + transitivePeerDependencies: + - supports-color + dev: true + + /mongodb-memory-server/8.1.0: + resolution: {integrity: sha512-nV3bS2XxguZC/KUAgNyX/bYruxlW/xdwYKIcbq4VTWiSFdwXJAcn4gh1WVrlMcfRFOYF4Pyk/NrZeeuqpbM+Bw==} + engines: {node: '>=12.22.0'} + requiresBuild: true + dependencies: + mongodb-memory-server-core: 8.1.0 + tslib: 2.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /mongodb/4.2.2: resolution: {integrity: sha512-zt8rCTnTKyMQppyt63qMnrLM5dbADgUk18ORPF1XbtHLIYCyc9hattaYHi0pqMvNxDpgGgUofSVzS+UQErgTug==} engines: {node: '>=12.9.0'} @@ -6544,6 +6634,16 @@ packages: reflect-metadata: 0.1.13 dev: false + /new-find-package-json/1.1.0: + resolution: {integrity: sha512-KOH3BNZcTKPzEkaJgG2iSUaurxKmefqRKmCOYH+8xqJytNIgjqU4J88BHfK+gy/UlEzlhccLyuJDJAcCgexSwA==} + engines: {node: '>=12.22.0'} + dependencies: + debug: 4.3.3 + tslib: 2.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /no-case/2.3.2: resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} dependencies: @@ -6945,6 +7045,10 @@ packages: resolution: {integrity: sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=} dev: false + /pend/1.2.0: + resolution: {integrity: sha1-elfrVQpng/kRUzH89GY9XI4AelA=} + dev: true + /performance-now/2.1.0: resolution: {integrity: sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=} dev: false @@ -7945,6 +8049,17 @@ packages: engines: {node: '>=6'} dev: true + /tar-stream/2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.0 + dev: true + /tar/6.1.11: resolution: {integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==} engines: {node: '>= 10'} @@ -8084,6 +8199,13 @@ packages: dependencies: os-tmpdir: 1.0.2 + /tmp/0.2.1: + resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} + engines: {node: '>=8.17.0'} + dependencies: + rimraf: 3.0.2 + dev: true + /tmpl/1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} dev: true @@ -8385,7 +8507,6 @@ packages: /uuid/8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true - dev: false /v8-compile-cache/2.3.0: resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} @@ -8800,6 +8921,13 @@ packages: yargs-parser: 20.2.9 dev: true + /yauzl/2.10.0: + resolution: {integrity: sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=} + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + dev: true + /yn/3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} diff --git a/src/app.module.ts b/src/app.module.ts index 369cbd4f..727cb2a5 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -46,7 +46,7 @@ import { SnippetModule } from './modules/snippet/snippet.module' import { ToolModule } from './modules/tool/tool.module' import { UserModule } from './modules/user/user.module' import { CacheModule } from './processors/cache/cache.module' -import { DbModule } from './processors/database/database.module' +import { DatabaseModule } from './processors/database/database.module' import { GatewayModule } from './processors/gateway/gateway.module' import { HelperModule } from './processors/helper/helper.module' import { LoggerModule } from './processors/logger/logger.module' @@ -65,7 +65,7 @@ mkdirs() @Module({ imports: [ - DbModule, + DatabaseModule, CacheModule, GraphQLModule.forRoot({ diff --git a/src/modules/snippet/snippet.controller.ts b/src/modules/snippet/snippet.controller.ts index a84379ba..7e789321 100644 --- a/src/modules/snippet/snippet.controller.ts +++ b/src/modules/snippet/snippet.controller.ts @@ -82,8 +82,7 @@ export class SnippetController { async update(@Param() param: MongoIdDto, @Body() body: SnippetModel) { const { id } = param - await this.snippetService.update(id, body) - return await this.snippetService.getSnippetById(id) + return await this.snippetService.update(id, body) } @Delete('/:id') diff --git a/src/modules/snippet/snippet.service.ts b/src/modules/snippet/snippet.service.ts index 7fa168dc..d985ce37 100644 --- a/src/modules/snippet/snippet.service.ts +++ b/src/modules/snippet/snippet.service.ts @@ -1,4 +1,8 @@ -import { BadRequestException, Injectable } from '@nestjs/common' +import { + BadRequestException, + Injectable, + NotFoundException, +} from '@nestjs/common' import { load } from 'js-yaml' import { InjectModel } from 'nestjs-typegoose' import { SnippetModel, SnippetType } from './snippet.model' @@ -31,12 +35,7 @@ export class SnippetService { await this.validateType(model) delete model.created - await this.model.updateOne( - { - _id: id, - }, - { ...model }, - ) + return await this.model.findByIdAndUpdate(id, { ...model }, { new: true }) } async delete(id: string) { @@ -91,6 +90,9 @@ export class SnippetService { } async attachSnippet(model: SnippetModel) { + if (!model) { + throw new NotFoundException() + } switch (model.type) { case SnippetType.JSON: { Reflect.set(model, 'data', JSON.parse(model.raw)) diff --git a/src/processors/database/database.module.ts b/src/processors/database/database.module.ts index e16422ca..53546170 100644 --- a/src/processors/database/database.module.ts +++ b/src/processors/database/database.module.ts @@ -45,4 +45,4 @@ const models = TypegooseModule.forFeature([ exports: [models, DatabaseService], }) @Global() -export class DbModule {} +export class DatabaseModule {} diff --git a/test-setup.js b/test-setup.js new file mode 100644 index 00000000..71386c78 --- /dev/null +++ b/test-setup.js @@ -0,0 +1,6 @@ +const { cd, $, chalk } = require('zx') +const globals = { $, chalk, cd, consola: console } + +for (const key in globals) { + global[key] = globals[key] +} diff --git a/test/helper/db-mock.helper.ts b/test/helper/db-mock.helper.ts new file mode 100644 index 00000000..7e9daa47 --- /dev/null +++ b/test/helper/db-mock.helper.ts @@ -0,0 +1,45 @@ +import { MongoMemoryServer } from 'mongodb-memory-server' +import mongoose from 'mongoose' + +let mongod: MongoMemoryServer + +/** + + * Connect to mock memory db. + */ +const connect = async () => { + mongod = await MongoMemoryServer.create() + const uri = mongod.getUri() + + await mongoose.connect(uri, { + autoIndex: true, + maxPoolSize: 10, + }) +} + +/** + * Close db connection + */ +const closeDatabase = async () => { + await mongoose.connection.dropDatabase() + await mongoose.connection.close() + await mongod.stop() +} + +/** + * Delete db collections + */ +const clearDatabase = async () => { + const collections = mongoose.connection.collections + + for (const key in collections) { + const collection = collections[key] + await collection.deleteMany({}) + } +} + +export const dbHelper = { + connect, + close: closeDatabase, + clear: clearDatabase, +} diff --git a/test/src/modules/snippet/snippet.controller.e2e-spec.ts b/test/src/modules/snippet/snippet.controller.e2e-spec.ts index 986d4aaf..03ff0dde 100644 --- a/test/src/modules/snippet/snippet.controller.e2e-spec.ts +++ b/test/src/modules/snippet/snippet.controller.e2e-spec.ts @@ -77,29 +77,6 @@ describe.only('test /snippets', () => { }) }) - // test('POST /snippets, should return 201', async () => { - // mockingoose(model).toReturn( - // { - // ...mockPayload1, - // }, - // 'create', - // ) - // await app - // .inject({ - // method: 'POST', - // url: '/snippets', - // payload: mockPayload1, - // }) - // .then(async (res) => { - // const json = res.json() - // expect(res.statusCode).toBe(201) - // expect(json).toBeDefined() - // expect(json.name).toBe('Snippet_1') - // // set mockingoose - - // }) - // }) - test('POST /snippets, re-create same of name should return 400', async () => { await app .inject({ diff --git a/test/src/modules/snippet/snippet.service.spec.ts b/test/src/modules/snippet/snippet.service.spec.ts new file mode 100644 index 00000000..a69fee34 --- /dev/null +++ b/test/src/modules/snippet/snippet.service.spec.ts @@ -0,0 +1,81 @@ +import { BadRequestException, NotFoundException } from '@nestjs/common' +import { Test } from '@nestjs/testing' +import { getModelForClass } from '@typegoose/typegoose' +import { getModelToken } from 'nestjs-typegoose' +import { dbHelper } from 'test/helper/db-mock.helper' +import { SnippetModel, SnippetType } from '~/modules/snippet/snippet.model' +import { SnippetService } from '~/modules/snippet/snippet.service' + +describe.only('test Snippet Service', () => { + let service: SnippetService + + beforeAll(async () => { + await dbHelper.connect() + const moduleRef = Test.createTestingModule({ + providers: [ + SnippetService, + { + provide: getModelToken('SnippetModel'), + useValue: getModelForClass(SnippetModel), + }, + ], + }) + + const app = await moduleRef.compile() + await app.init() + service = app.get(SnippetService) + }) + + afterAll(async () => { + await dbHelper.close() + }) + + const snippet = { + name: 'test', + raw: '{"foo": "bar"}', + type: SnippetType.JSON, + private: false, + reference: 'root', + } + let id = '' + it('should create one', async () => { + const res = await service.create(snippet) + + expect(res).toMatchObject(snippet) + expect(res.id).toBeDefined() + + id = res.id + }) + + it('should not allow duplicate create', async () => { + await expect(service.create(snippet)).rejects.toThrow(BadRequestException) + }) + + test('get only data snippet', async () => { + const res = await service.getSnippetByName(snippet.name, snippet.reference) + expect(res.name).toBe(snippet.name) + expect(res.data).toBeDefined() + }) + + test('get full snippet', async () => { + const res = await service.getSnippetById(id) + expect(res.name).toBe(snippet.name) + expect(res.data).toBeDefined() + }) + + test('modify', async () => { + const newSnippet = { + name: 'test', + raw: '{"foo": "b"}', + type: SnippetType.JSON, + private: true, + reference: 'root', + } + const res = await service.update(id, newSnippet) + expect(res.raw).toBe(newSnippet.raw) + }) + test('delete', async () => { + await service.delete(id) + await expect(service.getSnippetById(id)).rejects.toThrow(NotFoundException) + }) +})