From 02a08ad9f397cea8050087fca61fb4b01b8a4873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AF=BB?= Date: Tue, 17 Jan 2023 19:10:53 +0800 Subject: [PATCH] test: add note controller case (#939) --- .eslintignore | 2 + .github/workflows/build.yml | 1 + .github/workflows/docker.yml | 2 + test/helper/create-e2e-app.ts | 103 ++++++ test/helper/db-mock.helper copy.ts | 74 +++++ test/helper/db-mock.helper.ts | 6 +- test/helper/defineProvider.ts | 12 + test/helper/register-app.helper.ts | 23 -- test/helper/setup-e2e.ts | 31 ++ test/mock/modules/auth.mock.ts | 8 + test/mock/modules/comment.mock.ts | 12 + test/mock/modules/config.mock.ts | 20 ++ test/mock/modules/gateway.mock.ts | 20 ++ test/mock/processors/counting.mock.ts | 18 ++ test/mock/processors/text-macro.mock.ts | 12 + test/setup.ts | 26 +- test/setupFiles/add-something-to-global.ts | 20 -- test/setupFiles/lifecycle.ts | 37 +++ test/src/app.controller.e2e-spec.ts | 1 - .../modules/configs/configs.service.spec.ts | 7 - .../note.controller.e2e-spec.ts.snap | 302 ++++++++++++++++++ .../modules/note/note.controller.e2e-spec.ts | 202 ++++++++++++ test/src/modules/note/note.e2e-mock.db.ts | 15 + .../options/options.controller.e2e-spec.ts | 31 +- .../serverless/serverless.service.spec.ts | 17 +- .../snippet/snippet.controller.e2e-spec.ts | 64 ++-- .../modules/snippet/snippet.service.spec.ts | 11 +- .../modules/user/user.controller.e2e-spec.ts | 6 - .../helper/helper.jwt.service.spec.ts | 4 - test/tsconfig.json | 9 +- vitest.config.ts | 2 +- 31 files changed, 937 insertions(+), 161 deletions(-) create mode 100644 test/helper/create-e2e-app.ts create mode 100644 test/helper/db-mock.helper copy.ts create mode 100644 test/helper/defineProvider.ts delete mode 100644 test/helper/register-app.helper.ts create mode 100644 test/helper/setup-e2e.ts create mode 100644 test/mock/modules/auth.mock.ts create mode 100644 test/mock/modules/comment.mock.ts create mode 100644 test/mock/modules/config.mock.ts create mode 100644 test/mock/modules/gateway.mock.ts create mode 100644 test/mock/processors/counting.mock.ts create mode 100644 test/mock/processors/text-macro.mock.ts delete mode 100644 test/setupFiles/add-something-to-global.ts create mode 100644 test/setupFiles/lifecycle.ts create mode 100644 test/src/modules/note/__snapshots__/note.controller.e2e-spec.ts.snap create mode 100644 test/src/modules/note/note.controller.e2e-spec.ts create mode 100644 test/src/modules/note/note.e2e-mock.db.ts diff --git a/.eslintignore b/.eslintignore index 1c2dda68..f723e582 100644 --- a/.eslintignore +++ b/.eslintignore @@ -13,3 +13,5 @@ packages/*/test packages/*/tests packages/*/esm packages/*/types + +test/**/*.db.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a65835db..5d12412b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,6 +7,7 @@ on: push: branches: - '**' + pull_request: branches: [master] diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 23425c79..643b1986 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,6 +6,8 @@ on: - '**' tags: - 'v*' + paths-ignore: + - test/** pull_request: branches: [master] diff --git a/test/helper/create-e2e-app.ts b/test/helper/create-e2e-app.ts new file mode 100644 index 00000000..caf16826 --- /dev/null +++ b/test/helper/create-e2e-app.ts @@ -0,0 +1,103 @@ +import { ModuleMetadata } from '@nestjs/common' +import { APP_INTERCEPTOR } from '@nestjs/core' +import { NestFastifyApplication } from '@nestjs/platform-fastify' +import { BeAnObject, ReturnModelType } from '@typegoose/typegoose/lib/types' + +import { HttpCacheInterceptor } from '~/common/interceptors/cache.interceptor' +import { DbQueryInterceptor } from '~/common/interceptors/db-query.interceptor' +import { JSONTransformInterceptor } from '~/common/interceptors/json-transform.interceptor' +import { ResponseFilterInterceptor } from '~/common/interceptors/response-filter.interceptor' +import { ResponseInterceptor } from '~/common/interceptors/response.interceptor' +import { getModelToken } from '~/transformers/model.transformer' + +import { dbHelper } from './db-mock.helper' +import { redisHelper } from './redis-mock.helper' +import { setupE2EApp } from './setup-e2e' + +type ClassType = new (...args: any[]) => any + +type ModelMap = Map< + ClassType, + { + name: string + token: string + model: ReturnModelType + } +> +interface E2EAppMetaData { + models?: ClassType[] + pourData?: (modelMap: ModelMap) => Promise Promise)> +} + +export const createE2EApp = (module: ModuleMetadata & E2EAppMetaData) => { + const proxy: { + app: NestFastifyApplication + } = {} as any + + let pourDataCleanup: (() => Promise) | undefined + + beforeAll(async () => { + const { CacheService, token } = await redisHelper + const { models, pourData, ...nestModule } = module + nestModule.providers ||= [] + + nestModule.providers.push( + { + provide: APP_INTERCEPTOR, + useClass: DbQueryInterceptor, + }, + + { + provide: APP_INTERCEPTOR, + useClass: HttpCacheInterceptor, // 5 + }, + + { + provide: APP_INTERCEPTOR, + useClass: JSONTransformInterceptor, // 3 + }, + { + provide: APP_INTERCEPTOR, + useClass: ResponseFilterInterceptor, // 2 + }, + { + provide: APP_INTERCEPTOR, + useClass: ResponseInterceptor, // 1 + }, + ) + + nestModule.providers.push({ provide: token, useValue: CacheService }) + const modelMap = new Map() as ModelMap + if (models) { + models.forEach((model) => { + const token = getModelToken(model.name) + const modelInstance = dbHelper.getModel(model) + nestModule.providers.push({ + provide: token, + useValue: modelInstance, + }) + modelMap.set(model, { + name: model.name, + token, + model: modelInstance, + }) + }) + } + if (pourData) { + const cleanup = await pourData(modelMap) + // @ts-ignore + pourDataCleanup = cleanup + } + const app = await setupE2EApp(nestModule) + + proxy.app = app + }) + + afterAll(async () => { + if (pourDataCleanup) { + return await pourDataCleanup() + } + }) + + return proxy +} diff --git a/test/helper/db-mock.helper copy.ts b/test/helper/db-mock.helper copy.ts new file mode 100644 index 00000000..70f8fa9e --- /dev/null +++ b/test/helper/db-mock.helper copy.ts @@ -0,0 +1,74 @@ +import { MongoMemoryServer } from 'mongodb-memory-server' +import mongoose from 'mongoose' +import { nanoid } from 'nanoid/async' + +import { getModelForClass } from '@typegoose/typegoose' +import { + AnyParamConstructor, + BeAnObject, + IModelOptions, + ReturnModelType, +} from '@typegoose/typegoose/lib/types' + +let mongod: MongoMemoryServer + +const dbMap = new Map() +/** + + * Connect to mock memory db. + */ +const connect = async () => { + mongod = await MongoMemoryServer.create() + const uri = mongod.getUri() + + const mongooseInstance = await mongoose.connect(uri, { + autoIndex: true, + maxPoolSize: 10, + }) + const id = await nanoid() + dbMap.set(id, mongooseInstance) + return id +} + +/** + * Close db connection + */ +const closeDatabase = async (id: string) => { + const mongoose = dbMap.get(id) + if (!mongoose) { + return + } + await mongoose.connection.dropDatabase() + await mongoose.connection.close() + dbMap.delete(id) + if (dbMap.size === 0) await mongod.stop() +} + +/** + * Delete db collections + */ +const clearDatabase = async (id: string) => { + const mongoose = dbMap.get(id) + if (!mongoose) { + return + } + 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(), + + getModel, QueryHelpers = BeAnObject>( + cl: U, + options?: IModelOptions, + ): ReturnModelType { + return getModelForClass(cl, options) + }, +} diff --git a/test/helper/db-mock.helper.ts b/test/helper/db-mock.helper.ts index b5c09132..fa2f59fe 100644 --- a/test/helper/db-mock.helper.ts +++ b/test/helper/db-mock.helper.ts @@ -55,6 +55,10 @@ export const dbHelper = { cl: U, options?: IModelOptions, ): ReturnModelType { - return getModelForClass(cl, options) + return getModelForClass(cl, { + existingMongoose: mongoose, + existingConnection: mongoose.connection, + ...options, + }) }, } diff --git a/test/helper/defineProvider.ts b/test/helper/defineProvider.ts new file mode 100644 index 00000000..99958c15 --- /dev/null +++ b/test/helper/defineProvider.ts @@ -0,0 +1,12 @@ +export interface Provider { + provide: new (...args: any[]) => T + useValue: Partial +} + +export const defineProvider = (provider: Provider) => { + return provider +} + +export const defineProviders = (providers: Provider[]) => { + return providers +} diff --git a/test/helper/register-app.helper.ts b/test/helper/register-app.helper.ts deleted file mode 100644 index 6544c663..00000000 --- a/test/helper/register-app.helper.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ValidationPipe } from '@nestjs/common' -import { NestFastifyApplication } from '@nestjs/platform-fastify' -import { TestingModule } from '@nestjs/testing' - -import { fastifyApp } from '~/common/adapters/fastify.adapter' - -export const setupE2EApp = async (module: TestingModule) => { - const app = module.createNestApplication(fastifyApp) - app.useGlobalPipes( - new ValidationPipe({ - transform: true, - whitelist: true, - errorHttpStatusCode: 422, - forbidUnknownValues: true, - enableDebugMessages: isDev, - stopAtFirstError: true, - }), - ) - - await app.init() - await app.getHttpAdapter().getInstance().ready() - return app -} diff --git a/test/helper/setup-e2e.ts b/test/helper/setup-e2e.ts new file mode 100644 index 00000000..7e17dfb9 --- /dev/null +++ b/test/helper/setup-e2e.ts @@ -0,0 +1,31 @@ +import { ModuleMetadata, ValidationPipe } from '@nestjs/common' +import { NestFastifyApplication } from '@nestjs/platform-fastify' +import { Test, TestingModule } from '@nestjs/testing' + +import { fastifyApp } from '~/common/adapters/fastify.adapter' + +export const setupE2EApp = async (module: TestingModule | ModuleMetadata) => { + let nextModule: TestingModule + if (module instanceof TestingModule) { + nextModule = module + } else { + nextModule = await Test.createTestingModule(module).compile() + } + + const app = + nextModule.createNestApplication(fastifyApp) + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + whitelist: true, + errorHttpStatusCode: 422, + forbidUnknownValues: true, + enableDebugMessages: isDev, + stopAtFirstError: true, + }), + ) + + await app.init() + await app.getHttpAdapter().getInstance().ready() + return app +} diff --git a/test/mock/modules/auth.mock.ts b/test/mock/modules/auth.mock.ts new file mode 100644 index 00000000..785b124d --- /dev/null +++ b/test/mock/modules/auth.mock.ts @@ -0,0 +1,8 @@ +import { defineProvider } from 'test/helper/defineProvider' + +import { AuthService } from '~/modules/auth/auth.service' + +export const authProvider = defineProvider({ + useValue: {}, + provide: AuthService, +}) diff --git a/test/mock/modules/comment.mock.ts b/test/mock/modules/comment.mock.ts new file mode 100644 index 00000000..9ae9aa54 --- /dev/null +++ b/test/mock/modules/comment.mock.ts @@ -0,0 +1,12 @@ +import { dbHelper } from 'test/helper/db-mock.helper' +import { defineProvider } from 'test/helper/defineProvider' + +import { CommentModel } from '~/modules/comment/comment.model' +import { CommentService } from '~/modules/comment/comment.service' + +export const commentProvider = defineProvider({ + provide: CommentService, + useValue: { + model: dbHelper.getModel(CommentModel) as any, + }, +}) diff --git a/test/mock/modules/config.mock.ts b/test/mock/modules/config.mock.ts new file mode 100644 index 00000000..e250890d --- /dev/null +++ b/test/mock/modules/config.mock.ts @@ -0,0 +1,20 @@ +import { defineProvider } from 'test/helper/defineProvider' + +import { generateDefaultConfig } from '~/modules/configs/configs.default' +import { ConfigsService } from '~/modules/configs/configs.service' + +export const configProvider = defineProvider({ + provide: ConfigsService, + useValue: { + defaultConfig: generateDefaultConfig(), + async get(key) { + return this.defaultConfig[key] + }, + async getConfig() { + return this.defaultConfig + }, + async waitForConfigReady() { + return this.defaultConfig + }, + }, +}) diff --git a/test/mock/modules/gateway.mock.ts b/test/mock/modules/gateway.mock.ts new file mode 100644 index 00000000..42fabf54 --- /dev/null +++ b/test/mock/modules/gateway.mock.ts @@ -0,0 +1,20 @@ +import { defineProviders } from 'test/helper/defineProvider' + +import { AdminEventsGateway } from '~/processors/gateway/admin/events.gateway' +import { SystemEventsGateway } from '~/processors/gateway/system/events.gateway' +import { WebEventsGateway } from '~/processors/gateway/web/events.gateway' + +export const gatewayProviders = defineProviders([ + { + provide: WebEventsGateway, + useValue: {}, + }, + { + provide: AdminEventsGateway, + useValue: {}, + }, + { + provide: SystemEventsGateway, + useValue: {}, + }, +]) diff --git a/test/mock/processors/counting.mock.ts b/test/mock/processors/counting.mock.ts new file mode 100644 index 00000000..4d878e7d --- /dev/null +++ b/test/mock/processors/counting.mock.ts @@ -0,0 +1,18 @@ +import { defineProvider } from 'test/helper/defineProvider' + +import { CountingService } from '~/processors/helper/helper.counting.service' + +export const countingServiceProvider = defineProvider({ + useValue: { + async updateLikeCount() { + return true + }, + async getThisRecordIsLiked() { + return true + }, + async updateReadCount() { + return + }, + }, + provide: CountingService, +}) diff --git a/test/mock/processors/text-macro.mock.ts b/test/mock/processors/text-macro.mock.ts new file mode 100644 index 00000000..17aa8b6b --- /dev/null +++ b/test/mock/processors/text-macro.mock.ts @@ -0,0 +1,12 @@ +import { defineProvider } from 'test/helper/defineProvider' + +import { TextMacroService } from '~/processors/helper/helper.macro.service' + +export const textMacroProvider = defineProvider({ + provide: TextMacroService, + useValue: { + async replaceTextMacro(text) { + return text + }, + }, +}) diff --git a/test/setup.ts b/test/setup.ts index abf959f1..a2a5099c 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -1,7 +1,29 @@ -import { register } from '~/global/index.global' +import { mkdirSync } from 'fs-extra' +import { chalk } from 'zx' + +import { Logger } from '@nestjs/common' + +import { + DATA_DIR, + LOG_DIR, + STATIC_FILE_DIR, + TEMP_DIR, + THEME_DIR, + USER_ASSET_DIR, +} from '~/constants/path.constant' export async function setup() { - await register() + mkdirSync(DATA_DIR, { recursive: true }) + Logger.log(chalk.blue(`数据目录已经建好:${DATA_DIR}`)) + mkdirSync(TEMP_DIR, { recursive: true }) + Logger.log(chalk.blue(`临时目录已经建好:${TEMP_DIR}`)) + mkdirSync(LOG_DIR, { recursive: true }) + Logger.log(chalk.blue(`日志目录已经建好:${LOG_DIR}`)) + mkdirSync(USER_ASSET_DIR, { recursive: true }) + Logger.log(chalk.blue(`资源目录已经建好:${USER_ASSET_DIR}`)) + mkdirSync(STATIC_FILE_DIR, { recursive: true }) + Logger.log(chalk.blue(`文件存放目录已经建好:${STATIC_FILE_DIR}`)) + mkdirSync(THEME_DIR, { recursive: true }) } export async function teardown() {} diff --git a/test/setupFiles/add-something-to-global.ts b/test/setupFiles/add-something-to-global.ts deleted file mode 100644 index bb1220f1..00000000 --- a/test/setupFiles/add-something-to-global.ts +++ /dev/null @@ -1,20 +0,0 @@ -// @ts-nocheck -import { beforeAll } from 'vitest' - -import 'zx/globals' - -import consola from 'consola' - -beforeAll(async () => { - await import('zx/globals') - - global.isDev = true - global.cwd = process.cwd() - global.consola = consola -}) - -beforeEach(() => { - global.isDev = true - global.cwd = process.cwd() - global.consola = consola -}) diff --git a/test/setupFiles/lifecycle.ts b/test/setupFiles/lifecycle.ts new file mode 100644 index 00000000..dcee4833 --- /dev/null +++ b/test/setupFiles/lifecycle.ts @@ -0,0 +1,37 @@ +// @ts-nocheck +import { beforeAll } from 'vitest' + +import 'zx/globals' + +import consola from 'consola' +import { dbHelper } from 'test/helper/db-mock.helper' +import { redisHelper } from 'test/helper/redis-mock.helper' + +import { registerJSONGlobal } from '~/global/json.global' + +beforeAll(async () => { + await import('zx/globals') + + global.isDev = true + global.cwd = process.cwd() + global.consola = consola + + registerJSONGlobal() +}) + +afterAll(async () => { + await dbHelper.clear() + await dbHelper.close() + await (await redisHelper).close() +}) + +beforeAll(async () => { + await dbHelper.connect() + await redisHelper +}) + +beforeEach(() => { + global.isDev = true + global.cwd = process.cwd() + global.consola = consola +}) diff --git a/test/src/app.controller.e2e-spec.ts b/test/src/app.controller.e2e-spec.ts index 1b206bc4..58711e8d 100644 --- a/test/src/app.controller.e2e-spec.ts +++ b/test/src/app.controller.e2e-spec.ts @@ -24,7 +24,6 @@ describe('AppController (e2e)', () => { }) .overrideProvider(CacheService) .useValue({}) - .compile() app = moduleRef.createNestApplication(fastifyApp) diff --git a/test/src/modules/configs/configs.service.spec.ts b/test/src/modules/configs/configs.service.spec.ts index 6eede699..1b35bca8 100644 --- a/test/src/modules/configs/configs.service.spec.ts +++ b/test/src/modules/configs/configs.service.spec.ts @@ -1,4 +1,3 @@ -import { dbHelper } from 'test/helper/db-mock.helper' import { MockCacheService, redisHelper } from 'test/helper/redis-mock.helper' import { vi } from 'vitest' @@ -20,11 +19,6 @@ describe('Test ConfigsService', () => { let service: ConfigsService let redisService: MockCacheService - afterAll(async () => { - await dbHelper.clear() - await dbHelper.close() - await (await redisHelper).close() - }) const optionModel = getModelForClass(OptionModel) const mockEmitFn = vi.fn() @@ -32,7 +26,6 @@ describe('Test ConfigsService', () => { const { CacheService: redisService$ } = await redisHelper redisService = redisService$ - await dbHelper.connect() const moduleRef = await Test.createTestingModule({ imports: [], diff --git a/test/src/modules/note/__snapshots__/note.controller.e2e-spec.ts.snap b/test/src/modules/note/__snapshots__/note.controller.e2e-spec.ts.snap new file mode 100644 index 00000000..9bc6a9ac --- /dev/null +++ b/test/src/modules/note/__snapshots__/note.controller.e2e-spec.ts.snap @@ -0,0 +1,302 @@ +// Vitest Snapshot v1 + +exports[`NoteController (e2e) > GET /latest 1`] = ` +{ + "data": { + "allow_comment": true, + "comments_index": 0, + "count": { + "like": 0, + "read": 0, + }, + "created": "2021-03-20T00:00:00.000Z", + "hide": false, + "images": [], + "modified": null, + "music": [], + "nid": 20, + "text": "Content 20", + "title": "Note 20", + "topic": null, + }, + "next": { + "nid": 19, + "topic": null, + }, +} +`; + +exports[`NoteController (e2e) > GET /list/:id 1`] = ` +{ + "data": [ + { + "created": "2023-01-17T11:01:57.851Z", + "nid": 21, + "title": "Note 2 (updated)", + "topic": null, + }, + { + "created": "2021-03-20T00:00:00.000Z", + "nid": 20, + "title": "Note 20", + "topic": null, + }, + { + "created": "2021-03-19T00:00:00.000Z", + "nid": 19, + "title": "Note 19", + "topic": null, + }, + { + "created": "2021-03-18T00:00:00.000Z", + "nid": 18, + "title": "Note 18", + "topic": null, + }, + { + "created": "2021-03-17T00:00:00.000Z", + "nid": 17, + "title": "Note 17", + "topic": null, + }, + ], + "size": 5, +} +`; + +exports[`NoteController (e2e) > GET /notes 1`] = ` +{ + "data": [ + { + "allow_comment": true, + "comments_index": 0, + "count": { + "like": 0, + "read": 0, + }, + "created": "2021-03-20T00:00:00.000Z", + "hide": false, + "images": [], + "meta": null, + "modified": null, + "music": [], + "nid": 20, + "text": "Content 20", + "title": "Note 20", + "topic": null, + }, + { + "allow_comment": true, + "comments_index": 0, + "count": { + "like": 0, + "read": 0, + }, + "created": "2021-03-19T00:00:00.000Z", + "hide": false, + "images": [], + "meta": null, + "modified": null, + "music": [], + "nid": 19, + "text": "Content 19", + "title": "Note 19", + "topic": null, + }, + { + "allow_comment": true, + "comments_index": 0, + "count": { + "like": 0, + "read": 0, + }, + "created": "2021-03-18T00:00:00.000Z", + "hide": false, + "images": [], + "meta": null, + "modified": null, + "music": [], + "nid": 18, + "text": "Content 18", + "title": "Note 18", + "topic": null, + }, + { + "allow_comment": true, + "comments_index": 0, + "count": { + "like": 0, + "read": 0, + }, + "created": "2021-03-17T00:00:00.000Z", + "hide": false, + "images": [], + "meta": null, + "modified": null, + "music": [], + "nid": 17, + "text": "Content 17", + "title": "Note 17", + "topic": null, + }, + { + "allow_comment": true, + "comments_index": 0, + "count": { + "like": 0, + "read": 0, + }, + "created": "2021-03-16T00:00:00.000Z", + "hide": false, + "images": [], + "meta": null, + "modified": null, + "music": [], + "nid": 16, + "text": "Content 16", + "title": "Note 16", + "topic": null, + }, + { + "allow_comment": true, + "comments_index": 0, + "count": { + "like": 0, + "read": 0, + }, + "created": "2021-03-15T00:00:00.000Z", + "hide": false, + "images": [], + "meta": null, + "modified": null, + "music": [], + "nid": 15, + "text": "Content 15", + "title": "Note 15", + "topic": null, + }, + { + "allow_comment": true, + "comments_index": 0, + "count": { + "like": 0, + "read": 0, + }, + "created": "2021-03-14T00:00:00.000Z", + "hide": false, + "images": [], + "meta": null, + "modified": null, + "music": [], + "nid": 14, + "text": "Content 14", + "title": "Note 14", + "topic": null, + }, + { + "allow_comment": true, + "comments_index": 0, + "count": { + "like": 0, + "read": 0, + }, + "created": "2021-03-13T00:00:00.000Z", + "hide": false, + "images": [], + "meta": null, + "modified": null, + "music": [], + "nid": 13, + "text": "Content 13", + "title": "Note 13", + "topic": null, + }, + { + "allow_comment": true, + "comments_index": 0, + "count": { + "like": 0, + "read": 0, + }, + "created": "2021-03-12T00:00:00.000Z", + "hide": false, + "images": [], + "meta": null, + "modified": null, + "music": [], + "nid": 12, + "text": "Content 12", + "title": "Note 12", + "topic": null, + }, + { + "allow_comment": true, + "comments_index": 0, + "count": { + "like": 0, + "read": 0, + }, + "created": "2021-03-11T00:00:00.000Z", + "hide": false, + "images": [], + "meta": null, + "modified": null, + "music": [], + "nid": 11, + "text": "Content 11", + "title": "Note 11", + "topic": null, + }, + ], + "pagination": { + "current_page": 1, + "has_next_page": true, + "has_prev_page": false, + "size": 10, + "total": 20, + "total_page": 2, + }, +} +`; + +exports[`NoteController (e2e) > Get patched note 1`] = ` +{ + "allow_comment": true, + "comments_index": 0, + "count": { + "like": 0, + "read": 0, + }, + "created": "2023-01-17T11:01:57.851Z", + "hide": false, + "images": [], + "modified": null, + "mood": "happy", + "music": [], + "nid": 21, + "text": "Content 2 (updated)", + "title": "Note 2 (updated)", + "topic": null, + "weather": "sunny", +} +`; + +exports[`NoteController (e2e) > POST /notes 1`] = ` +{ + "allow_comment": true, + "comments_index": 0, + "count": { + "like": 0, + "read": 0, + }, + "created": "2023-01-17T11:01:57.851Z", + "hide": false, + "images": [], + "meta": null, + "modified": null, + "music": [], + "nid": 21, + "text": "Content 2", + "title": "Note 2", +} +`; diff --git a/test/src/modules/note/note.controller.e2e-spec.ts b/test/src/modules/note/note.controller.e2e-spec.ts new file mode 100644 index 00000000..c0f4431f --- /dev/null +++ b/test/src/modules/note/note.controller.e2e-spec.ts @@ -0,0 +1,202 @@ +import { createE2EApp } from 'test/helper/create-e2e-app' +import { authProvider } from 'test/mock/modules/auth.mock' +import { commentProvider } from 'test/mock/modules/comment.mock' +import { configProvider } from 'test/mock/modules/config.mock' +import { gatewayProviders } from 'test/mock/modules/gateway.mock' +import { countingServiceProvider } from 'test/mock/processors/counting.mock' + +import { EventEmitter2 } from '@nestjs/event-emitter' +import { ReturnModelType } from '@typegoose/typegoose' + +import { OptionModel } from '~/modules/configs/configs.model' +import { NoteController } from '~/modules/note/note.controller' +import { NoteModel } from '~/modules/note/note.model' +import { NoteService } from '~/modules/note/note.service' +import { UserModel } from '~/modules/user/user.model' +import { UserService } from '~/modules/user/user.service' +import { CountingService } from '~/processors/helper/helper.counting.service' +import { EventManagerService } from '~/processors/helper/helper.event.service' +import { HttpService } from '~/processors/helper/helper.http.service' +import { ImageService } from '~/processors/helper/helper.image.service' +import { TextMacroService } from '~/processors/helper/helper.macro.service' +import { SubPubBridgeService } from '~/processors/redis/subpub.service' + +import MockDbData from './note.e2e-mock.db' + +describe('NoteController (e2e)', () => { + let model: ReturnModelType + const proxy = createE2EApp({ + controllers: [NoteController], + providers: [ + NoteService, + ImageService, + EventManagerService, + commentProvider, + + { + provide: TextMacroService, + useValue: { + async replaceTextMacro(text) { + return text + }, + }, + }, + HttpService, + configProvider, + EventEmitter2, + UserService, + SubPubBridgeService, + ...gatewayProviders, + authProvider, + CountingService, + countingServiceProvider, + ], + imports: [], + models: [NoteModel, OptionModel, UserModel], + async pourData(modelMap) { + // @ts-ignore + const { model: _model } = modelMap.get(NoteModel) as { + model: ReturnModelType + } + model = _model + for await (const data of MockDbData) { + await _model.create(data) + } + }, + }) + + afterAll(async () => { + await model.deleteMany({}) + }) + + test('GET /notes', async () => { + const { app } = proxy + const res = await app.inject({ + method: 'GET', + url: '/notes', + }) + const data = res.json() + expect(res.statusCode).toBe(200) + + data.data.forEach((d) => { + delete d.id + delete d._id + }) + expect(data).toMatchSnapshot() + }) + + const createdNoteData: Partial = { + title: 'Note 2', + text: 'Content 2', + + allowComment: true, + // use cutsom date + created: new Date('2023-01-17T11:01:57.851Z'), + } + + test('POST /notes', async () => { + const { app } = proxy + const res = await app.inject({ + method: 'POST', + url: '/notes', + payload: createdNoteData, + }) + + const data = res.json() + expect(res.statusCode).toBe(201) + createdNoteData.id = data.id + createdNoteData.nid = data.nid + delete data.id + expect(data).toMatchSnapshot() + }) + + test('PATCH /notes/:id', async () => { + const { app } = proxy + const res = await app.inject({ + method: 'PATCH', + url: `/notes/${createdNoteData.id}`, + payload: { + title: 'Note 2 (updated)', + text: `Content 2 (updated)`, + mood: 'happy', + weather: 'sunny', + }, + }) + + expect(res.statusCode).toBe(204) + }) + + test('Get patched note', async () => { + const { app } = proxy + const res = await app.inject({ + method: 'GET', + url: `/notes/${createdNoteData.id}`, + }) + + expect(res.statusCode).toBe(200) + const data = res.json() + delete data.id + expect(data).toMatchSnapshot() + }) + + test('GET /list/:id', async () => { + const { app } = proxy + const res = await app.inject({ + method: 'GET', + url: `/notes/list/${createdNoteData.id}`, + }) + + expect(res.statusCode).toBe(200) + const data = res.json() + + data.data.forEach((note) => { + delete note.id + }) + + expect(data).toMatchSnapshot() + }) + + test('DEL /notes/:id', async () => { + const { app } = proxy + const res = await app.inject({ + method: 'DELETE', + url: `/notes/${createdNoteData.id}`, + }) + + expect(res.statusCode).toBe(204) + }) + + it('should got 404 when get deleted note', async () => { + const { app } = proxy + { + const res = await app.inject({ + method: 'GET', + url: `/notes/${createdNoteData.id}`, + }) + + expect(res.statusCode).toBe(404) + } + { + const res = await app.inject({ + method: 'GET', + url: `/notes/nid/${createdNoteData.nid}`, + }) + + expect(res.statusCode).toBe(404) + } + }) + + test('GET /latest', async () => { + const { app } = proxy + const res = await app.inject({ + method: 'GET', + url: '/notes/latest', + }) + + expect(res.statusCode).toBe(200) + const data = res.json() + delete data.data.id + delete data.next.id + expect(data).toMatchSnapshot() + }) +}) diff --git a/test/src/modules/note/note.e2e-mock.db.ts b/test/src/modules/note/note.e2e-mock.db.ts new file mode 100644 index 00000000..a1cf3665 --- /dev/null +++ b/test/src/modules/note/note.e2e-mock.db.ts @@ -0,0 +1,15 @@ +import { NoteModel } from '~/modules/note/note.model' + +export default Array.from({ length: 20 }).map((_, _i) => { + const i = _i + 1 + return { + title: 'Note ' + i, + text: 'Content ' + i, + created: new Date(`2021-03-${i.toFixed().padStart(2, '0')}T00:00:00.000Z`), + modified: null, + allowComment: true, + + hide: false, + commentsIndex: 0, + } +}) as NoteModel[] diff --git a/test/src/modules/options/options.controller.e2e-spec.ts b/test/src/modules/options/options.controller.e2e-spec.ts index 864cbb0a..489108ba 100644 --- a/test/src/modules/options/options.controller.e2e-spec.ts +++ b/test/src/modules/options/options.controller.e2e-spec.ts @@ -1,34 +1,15 @@ -import { NestFastifyApplication } from '@nestjs/platform-fastify' -import { Test } from '@nestjs/testing' +import { createE2EApp } from 'test/helper/create-e2e-app' +import { configProvider } from 'test/mock/modules/config.mock' -import { fastifyApp } from '~/common/adapters/fastify.adapter' -import { generateDefaultConfig } from '~/modules/configs/configs.default' -import { ConfigsService } from '~/modules/configs/configs.service' import { BaseOptionController } from '~/modules/option/controllers/base.option.controller' describe('OptionController (e2e)', () => { - let app: NestFastifyApplication - - beforeAll(async () => { - const moduleRef = await Test.createTestingModule({ - controllers: [BaseOptionController], - providers: [ - { - provide: ConfigsService, - useValue: { - defaultConfig: generateDefaultConfig(), - }, - }, - ], - }).compile() - - app = moduleRef.createNestApplication(fastifyApp) - await app.init() - await app.getHttpAdapter().getInstance().ready() + const proxy = createE2EApp({ + controllers: [BaseOptionController], + providers: [configProvider], }) - test('GET /config/jsonschema', () => { - return app + return proxy.app .inject({ method: 'GET', url: '/config/jsonschema', diff --git a/test/src/modules/serverless/serverless.service.spec.ts b/test/src/modules/serverless/serverless.service.spec.ts index 446fd366..45901f7f 100644 --- a/test/src/modules/serverless/serverless.service.spec.ts +++ b/test/src/modules/serverless/serverless.service.spec.ts @@ -1,4 +1,4 @@ -import { dbHelper } from 'test/helper/db-mock.helper' +import mongoose from 'mongoose' import { redisHelper } from 'test/helper/redis-mock.helper' import { Test } from '@nestjs/testing' @@ -17,9 +17,6 @@ describe('test serverless function service', () => { let service: ServerlessService beforeAll(async () => { - const connection = await dbHelper.connect() - - await (await redisHelper).connect() const moduleRef = Test.createTestingModule({ providers: [ ServerlessService, @@ -32,15 +29,13 @@ describe('test serverless function service', () => { { provide: DatabaseService, useValue: { - db: connection.connection.db, + db: mongoose.connection.db, }, }, { provide: getModelToken('SnippetModel'), - useValue: getModelForClass(SnippetModel, { - existingConnection: connection.connection, - }), + useValue: getModelForClass(SnippetModel), }, ], }) @@ -50,12 +45,6 @@ describe('test serverless function service', () => { service = app.get(ServerlessService) }) - afterAll(async () => { - await dbHelper.clear() - await dbHelper.close() - ;(await redisHelper).close() - }) - describe('run serverless function', () => { test('case-1', async () => { const model = new SnippetModel() diff --git a/test/src/modules/snippet/snippet.controller.e2e-spec.ts b/test/src/modules/snippet/snippet.controller.e2e-spec.ts index 0b41f440..7b145e09 100644 --- a/test/src/modules/snippet/snippet.controller.e2e-spec.ts +++ b/test/src/modules/snippet/snippet.controller.e2e-spec.ts @@ -1,70 +1,42 @@ -import { dbHelper } from 'test/helper/db-mock.helper' -import { MockCacheService, redisHelper } from 'test/helper/redis-mock.helper' -import { setupE2EApp } from 'test/helper/register-app.helper' +import { createE2EApp } from 'test/helper/create-e2e-app' import { NestFastifyApplication } from '@nestjs/platform-fastify' -import { Test } from '@nestjs/testing' -import { getModelForClass } from '@typegoose/typegoose' import { ServerlessService } from '~/modules/serverless/serverless.service' import { SnippetController } from '~/modules/snippet/snippet.controller' import { SnippetModel, SnippetType } from '~/modules/snippet/snippet.model' import { SnippetService } from '~/modules/snippet/snippet.service' import { DatabaseService } from '~/processors/database/database.service' -import { CacheService } from '~/processors/redis/cache.service' -import { getModelToken } from '~/transformers/model.transformer' describe('test /snippets', () => { let app: NestFastifyApplication + const proxy = createE2EApp({ + controllers: [SnippetController], + providers: [ + SnippetService, + { provide: DatabaseService, useValue: {} }, - beforeAll(async () => { - await dbHelper.connect() + { + provide: ServerlessService, + useValue: { + isValidServerlessFunction() { + return true + }, + }, + }, + ], + models: [SnippetModel], }) - afterAll(async () => { - await dbHelper.clear() - await dbHelper.close() - }) - const model = getModelForClass(SnippetModel) - const mockPayload1: Partial = Object.freeze({ name: 'Snippet_1', private: false, raw: JSON.stringify({ foo: 'bar' }), type: SnippetType.JSON, }) - let redisService: MockCacheService - afterAll(async () => { - await (await redisHelper).close() - }) - beforeAll(async () => { - const { CacheService: redisService$ } = await redisHelper - - redisService = redisService$ - - const ref = await Test.createTestingModule({ - controllers: [SnippetController], - providers: [ - SnippetService, - { provide: DatabaseService, useValue: {} }, - { provide: CacheService, useValue: redisService }, - { - provide: ServerlessService, - useValue: { - isValidServerlessFunction() { - return true - }, - }, - }, - { - provide: getModelToken(SnippetModel.name), - useValue: model, - }, - ], - }).compile() - - app = await setupE2EApp(ref) + beforeEach(() => { + app = proxy.app }) test('POST /snippets, should 422 with wrong name', async () => { diff --git a/test/src/modules/snippet/snippet.service.spec.ts b/test/src/modules/snippet/snippet.service.spec.ts index 62a46d79..779549b0 100644 --- a/test/src/modules/snippet/snippet.service.spec.ts +++ b/test/src/modules/snippet/snippet.service.spec.ts @@ -1,4 +1,3 @@ -import { dbHelper } from 'test/helper/db-mock.helper' import { redisHelper } from 'test/helper/redis-mock.helper' import { BadRequestException, NotFoundException } from '@nestjs/common' @@ -14,12 +13,8 @@ import { getModelToken } from '~/transformers/model.transformer' describe('test Snippet Service', () => { let service: SnippetService - afterAll(async () => { - await (await redisHelper).close() - }) - beforeAll(async () => { - await dbHelper.connect() + beforeAll(async () => { const redis = await redisHelper const moduleRef = Test.createTestingModule({ providers: [ @@ -40,10 +35,6 @@ describe('test Snippet Service', () => { service = app.get(SnippetService) }) - afterAll(async () => { - await dbHelper.close() - }) - const snippet = { name: 'test', raw: '{"foo": "bar"}', diff --git a/test/src/modules/user/user.controller.e2e-spec.ts b/test/src/modules/user/user.controller.e2e-spec.ts index c32640cf..0f789004 100644 --- a/test/src/modules/user/user.controller.e2e-spec.ts +++ b/test/src/modules/user/user.controller.e2e-spec.ts @@ -14,13 +14,7 @@ import { getModelToken } from '~/transformers/model.transformer' describe('AppController (e2e)', () => { let app: NestFastifyApplication - afterAll(async () => { - await dbHelper.close() - await (await redisHelper).close() - }) - beforeAll(async () => { - await dbHelper.connect() const { CacheService, token } = await redisHelper const moduleRef = await Test.createTestingModule({ controllers: [UserController], diff --git a/test/src/processors/helper/helper.jwt.service.spec.ts b/test/src/processors/helper/helper.jwt.service.spec.ts index 825f7f9f..ecebd161 100644 --- a/test/src/processors/helper/helper.jwt.service.spec.ts +++ b/test/src/processors/helper/helper.jwt.service.spec.ts @@ -8,10 +8,6 @@ import { CacheService } from '~/processors/redis/cache.service' describe('test jwt service', () => { let service: JWTService - afterAll(async () => { - await (await redisHelper).close() - }) - beforeAll(async () => { const { CacheService: MCacheService } = await redisHelper const moduleRef = Test.createTestingModule({ diff --git a/test/tsconfig.json b/test/tsconfig.json index d5e910dc..53f33c54 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -30,5 +30,12 @@ "./src/*" ] } - } + }, + "include": [ + "./src/**/*.ts", + "./src/**/*.tsx", + "./src/**/*.js", + "./src/**/*.jsx", + "./**/*.ts", + ], } \ No newline at end of file diff --git a/vitest.config.ts b/vitest.config.ts index 0d427326..435d0720 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -66,7 +66,7 @@ export default defineConfig({ name: 'a-vitest-plugin-that-changes-config', config: () => ({ test: { - setupFiles: ['./setupFiles/add-something-to-global.ts'], + setupFiles: ['./setupFiles/lifecycle.ts'], }, }), },