chore: init
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import { ModuleMetadata } from '@nestjs/common'
|
||||
import { ConfigModule } from '@nestjs/config'
|
||||
import { APP_FILTER, APP_GUARD, APP_PIPE } from '@nestjs/core'
|
||||
import { NestFastifyApplication } from '@nestjs/platform-fastify'
|
||||
|
||||
import { Test } from '@nestjs/testing'
|
||||
|
||||
import { fastifyApp } from '@server/common/adapters/fastify.adapter'
|
||||
import { AllExceptionsFilter } from '@server/common/filters/any-exception.filter'
|
||||
import { ZodValidationPipe } from '@server/common/pipes/zod-validation.pipe'
|
||||
import * as config from '@server/config'
|
||||
import { AuthModule } from '@server/modules/auth/auth.module'
|
||||
import { JwtAuthGuard } from '@server/modules/auth/guards/jwt-auth.guard'
|
||||
import { CaslModule } from '@server/modules/casl/casl.module'
|
||||
import { CacheModule } from '@server/shared/cache/cache.module'
|
||||
import { DatabaseModule } from '@server/shared/database/database.module'
|
||||
|
||||
import { HelperModule } from '@server/shared/helper/helper.module'
|
||||
|
||||
import { LoggerModule } from '@server/shared/logger/logger.module'
|
||||
import { RedisModule } from '@server/shared/redis/redis.module'
|
||||
import { TRPCModule } from '@server/shared/trpc/trpc.module'
|
||||
|
||||
export function createE2EApp(module: ModuleMetadata) {
|
||||
const proxy: {
|
||||
app: NestFastifyApplication
|
||||
} = {} as any
|
||||
|
||||
beforeAll(async () => {
|
||||
const { ...nestModule } = module
|
||||
nestModule.imports ||= []
|
||||
nestModule.imports.push(
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: [`.env.${process.env.NODE_ENV}`, '.env'],
|
||||
load: [...Object.values(config)],
|
||||
}),
|
||||
LoggerModule,
|
||||
|
||||
CacheModule,
|
||||
DatabaseModule,
|
||||
RedisModule,
|
||||
HelperModule,
|
||||
|
||||
AuthModule,
|
||||
CaslModule,
|
||||
TRPCModule,
|
||||
)
|
||||
nestModule.providers ||= []
|
||||
|
||||
nestModule.providers.push(
|
||||
{
|
||||
provide: APP_PIPE,
|
||||
useClass: ZodValidationPipe,
|
||||
},
|
||||
// {
|
||||
// provide: APP_INTERCEPTOR,
|
||||
// useClass: TransformInterceptor,
|
||||
// },
|
||||
{
|
||||
provide: APP_FILTER,
|
||||
useClass: AllExceptionsFilter,
|
||||
},
|
||||
{ provide: APP_GUARD, useClass: JwtAuthGuard },
|
||||
)
|
||||
|
||||
const testingModule = await Test.createTestingModule(nestModule).compile()
|
||||
|
||||
const app = testingModule.createNestApplication<NestFastifyApplication>(
|
||||
fastifyApp,
|
||||
{ logger: ['log', 'warn', 'error', 'debug'] },
|
||||
)
|
||||
|
||||
await app.init()
|
||||
|
||||
await app.getHttpAdapter().getInstance().ready()
|
||||
|
||||
proxy.app = app
|
||||
})
|
||||
|
||||
return proxy
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { ModuleMetadata } from '@nestjs/common'
|
||||
import { ConfigModule } from '@nestjs/config'
|
||||
import { Test, TestingModule } from '@nestjs/testing'
|
||||
|
||||
import * as config from '@server/config'
|
||||
|
||||
type ClassType<T> = new (...args: any[]) => T
|
||||
export function createServiceUnitTestApp<T>(Service: ClassType<T>, module?: ModuleMetadata) {
|
||||
const proxy = {} as {
|
||||
service: T
|
||||
app: TestingModule
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const { imports, providers } = module || {}
|
||||
const app = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: [`.env.${process.env.NODE_ENV}`, '.env'],
|
||||
load: [...Object.values(config)],
|
||||
}),
|
||||
...(imports || []),
|
||||
],
|
||||
providers: [Service, ...(providers || [])],
|
||||
}).compile()
|
||||
|
||||
await app.init()
|
||||
|
||||
proxy.service = app.get<T>(Service)
|
||||
proxy.app = app
|
||||
})
|
||||
return proxy
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { getExtendedPrismaClient } from '@server/shared/database/prisma.extension'
|
||||
|
||||
export const prisma = getExtendedPrismaClient({
|
||||
url: process.env.DATABASE_URL,
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Prisma } from 'database'
|
||||
|
||||
import { prisma } from './prisma'
|
||||
|
||||
export default async () => {
|
||||
await prisma.$transaction(async (c) => {
|
||||
const tasks = [] as Promise<any>[]
|
||||
|
||||
const allNames = [] as string[]
|
||||
Prisma.dmmf.datamodel.models.forEach((model) => {
|
||||
allNames.push(model.name[0].toLowerCase() + model.name.slice(1))
|
||||
})
|
||||
for (const key of allNames) {
|
||||
if (key.startsWith('$'))
|
||||
continue
|
||||
tasks.push(c[key].deleteMany())
|
||||
}
|
||||
await Promise.all(tasks)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Role } from '@server/modules/auth/auth.constant'
|
||||
import { snowflake } from '@server/shared/database/snowflake.util'
|
||||
import { UserOptionalDefaults } from 'database/zod'
|
||||
|
||||
export function generateMockUser(): UserOptionalDefaults {
|
||||
const id = snowflake.nextId()
|
||||
return {
|
||||
username: `mockUser_${id}`,
|
||||
avatar: `https://picsum.photos/200/200`,
|
||||
password: 'mockPassword123',
|
||||
email: `mockuser_${id}@example.com`,
|
||||
role: Role.User,
|
||||
}
|
||||
}
|
||||
|
||||
const mockUserData1 = generateMockUser()
|
||||
|
||||
export { mockUserData1 }
|
||||
@@ -0,0 +1,9 @@
|
||||
import resetDb from './lib/reset-db'
|
||||
|
||||
beforeAll(async () => {
|
||||
await resetDb()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
// await resetDb()
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
import { AuthModule } from '@server/modules/auth/auth.module'
|
||||
import { createE2EApp } from '@test/helper/create-e2e-app'
|
||||
import { prisma } from '@test/lib/prisma'
|
||||
import { generateMockUser } from '@test/mock/data/user.data'
|
||||
import { hashSync } from 'bcrypt'
|
||||
|
||||
describe('Account', () => {
|
||||
const proxy = createE2EApp({
|
||||
imports: [AuthModule],
|
||||
})
|
||||
|
||||
it('GET /account/profile', async () => {
|
||||
const password = 'a123456'
|
||||
|
||||
const { id, username } = await prisma.user.create({
|
||||
data: {
|
||||
...generateMockUser(),
|
||||
password: hashSync(password, 8),
|
||||
},
|
||||
})
|
||||
|
||||
const response = await proxy.app.inject({
|
||||
method: 'POST',
|
||||
url: '/auth/login',
|
||||
body: {
|
||||
username,
|
||||
password,
|
||||
type: 'account',
|
||||
},
|
||||
})
|
||||
|
||||
const result = response.json()
|
||||
const token = result.authToken
|
||||
|
||||
const data = await proxy.app.inject({
|
||||
method: 'GET',
|
||||
url: '/account/profile',
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
|
||||
const res = data.json()
|
||||
|
||||
expect(res.id).toBe(id)
|
||||
expect(res.username).toBe(username)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,60 @@
|
||||
import { AuthModule } from '@server/modules/auth/auth.module'
|
||||
import { createE2EApp } from '@test/helper/create-e2e-app'
|
||||
import { prisma } from '@test/lib/prisma'
|
||||
import { generateMockUser } from '@test/mock/data/user.data'
|
||||
import { hashSync } from 'bcrypt'
|
||||
import { pick } from 'lodash'
|
||||
|
||||
describe('Auth', () => {
|
||||
const proxy = createE2EApp({
|
||||
imports: [AuthModule],
|
||||
})
|
||||
|
||||
it('GET /auth/captcha/image', async () => {
|
||||
const response = await proxy.app.inject({
|
||||
method: 'GET',
|
||||
url: '/auth/captcha/image',
|
||||
})
|
||||
|
||||
const result = response.json()
|
||||
expect(result.image).toContain('base64')
|
||||
expect(typeof result.id).toBe('string')
|
||||
})
|
||||
|
||||
it('POST /auth/login', async () => {
|
||||
const password = 'password123'
|
||||
const { username } = await prisma.user.create({
|
||||
data: {
|
||||
...generateMockUser(),
|
||||
password: hashSync(password, 8),
|
||||
},
|
||||
})
|
||||
|
||||
const response = await proxy.app.inject({
|
||||
method: 'POST',
|
||||
url: '/auth/login',
|
||||
body: {
|
||||
username,
|
||||
password,
|
||||
type: 'account',
|
||||
},
|
||||
})
|
||||
|
||||
const result = response.json()
|
||||
expect(result.authToken).toBeDefined()
|
||||
})
|
||||
|
||||
it('POST /auth/register', async () => {
|
||||
const mockUser = pick(generateMockUser(), ['username', 'password'])
|
||||
const response = await proxy.app.inject({
|
||||
method: 'POST',
|
||||
url: '/auth/register',
|
||||
body: {
|
||||
...mockUser,
|
||||
type: 'account',
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.statusCode).toBe(201)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,73 @@
|
||||
import { AuthModule } from '@server/modules/auth/auth.module'
|
||||
import { AuthService } from '@server/modules/auth/auth.service'
|
||||
import { TodoModule } from '@server/modules/todo/todo.module'
|
||||
import { createContext } from '@server/shared/trpc/trpc.context'
|
||||
import { Caller } from '@server/shared/trpc/trpc.instance'
|
||||
import { TRPCService } from '@server/shared/trpc/trpc.service'
|
||||
import { createE2EApp } from '@test/helper/create-e2e-app'
|
||||
import { prisma } from '@test/lib/prisma'
|
||||
import { mockUserData1 } from '@test/mock/data/user.data'
|
||||
import { Todo, User } from 'database'
|
||||
|
||||
describe('Todo', () => {
|
||||
const proxy = createE2EApp({
|
||||
imports: [TodoModule, AuthModule],
|
||||
})
|
||||
|
||||
let user: User
|
||||
let todo: Todo
|
||||
let token: string
|
||||
let caller: Caller
|
||||
|
||||
beforeAll(async () => {
|
||||
user = await prisma.user.create({
|
||||
data: mockUserData1,
|
||||
})
|
||||
|
||||
todo = await prisma.todo.create({
|
||||
data: {
|
||||
value: 'code',
|
||||
status: false,
|
||||
userId: user.id,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
const authService = proxy.app.get(AuthService)
|
||||
|
||||
token = await authService.sign(user.id, user.role)
|
||||
|
||||
const ctx = await createContext({ req: { headers: { authorization: token } } })
|
||||
caller = proxy.app.get(TRPCService).createCaller(ctx)
|
||||
})
|
||||
|
||||
it('GET /todos/:id successful', async () => {
|
||||
const response = await proxy.app.inject({
|
||||
method: 'GET',
|
||||
url: `/todos/${todo.id}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.statusCode).toEqual(200)
|
||||
})
|
||||
|
||||
it('TRPC todo.byId', async () => {
|
||||
const result = await caller.todo.byId({ id: todo.id })
|
||||
|
||||
expect(result.id).toEqual(todo.id)
|
||||
})
|
||||
|
||||
it('GET /todos/:id cannot find by other', async () => {
|
||||
const response = await proxy.app.inject({
|
||||
method: 'GET',
|
||||
url: `/todos/${todo.id}`,
|
||||
headers: {
|
||||
authorization: `Bearer ${'error_token'}`,
|
||||
},
|
||||
})
|
||||
expect(response.statusCode).toEqual(401)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,68 @@
|
||||
import { TodoService } from '@server/modules/todo/todo.service'
|
||||
import { DatabaseModule } from '@server/shared/database/database.module'
|
||||
import { createServiceUnitTestApp } from '@test/helper/create-service-unit'
|
||||
import { prisma } from '@test/lib/prisma'
|
||||
import resetDb from '@test/lib/reset-db'
|
||||
import { mockUserData1 } from '@test/mock/data/user.data'
|
||||
import { Todo, User } from 'database'
|
||||
|
||||
describe('todoService', () => {
|
||||
const proxy = createServiceUnitTestApp(TodoService, {
|
||||
imports: [DatabaseModule],
|
||||
})
|
||||
|
||||
let user: User
|
||||
let todo: Todo
|
||||
|
||||
beforeAll(async () => {
|
||||
await resetDb()
|
||||
user = await prisma.user.create({
|
||||
data: mockUserData1,
|
||||
})
|
||||
})
|
||||
|
||||
it('query todo', async () => {
|
||||
const result = await proxy.service.list({ page: 1, limit: 10 }, user.id)
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
"items": [],
|
||||
"meta": {
|
||||
"currentPage": 1,
|
||||
"isFirstPage": true,
|
||||
"isLastPage": true,
|
||||
"nextPage": null,
|
||||
"pageCount": 0,
|
||||
"previousPage": null,
|
||||
"totalCount": 0,
|
||||
},
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
it('create todo', async () => {
|
||||
todo = await proxy.service.create({ value: 'code' }, user.id)
|
||||
expect(todo.value).toEqual('code')
|
||||
expect(todo.userId).toEqual(user.id)
|
||||
})
|
||||
|
||||
it('get todo by id successful', async () => {
|
||||
const result = await proxy.service.findOne(todo.id)
|
||||
expect(result).toBeDefined()
|
||||
})
|
||||
|
||||
it('get todo throw when 404', async () => {
|
||||
expect(proxy.service.findOne('not-found')).rejects.toThrowError()
|
||||
})
|
||||
|
||||
it('update todo', async () => {
|
||||
const result = await proxy.service.update(todo.id, { status: true })
|
||||
|
||||
expect(result.status).toEqual(true)
|
||||
})
|
||||
|
||||
it('delete todo', async () => {
|
||||
await proxy.service.delete(todo.id)
|
||||
|
||||
expect(proxy.service.findOne(todo.id)).rejects.toThrowErrorMatchingInlineSnapshot(`[NotFoundError: No Todo found]`)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,33 @@
|
||||
import { UserService } from '@server/modules/user/user.service'
|
||||
import { DatabaseModule } from '@server/shared/database/database.module'
|
||||
import { RedisModule } from '@server/shared/redis/redis.module'
|
||||
import { createServiceUnitTestApp } from '@test/helper/create-service-unit'
|
||||
import { prisma } from '@test/lib/prisma'
|
||||
import { generateMockUser } from '@test/mock/data/user.data'
|
||||
|
||||
describe('userService', () => {
|
||||
const proxy = createServiceUnitTestApp(UserService, {
|
||||
imports: [DatabaseModule, RedisModule],
|
||||
})
|
||||
|
||||
it('should register user successfully', async () => {
|
||||
const userModel = generateMockUser()
|
||||
await proxy.service.register({ ...userModel })
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
username: userModel.username,
|
||||
},
|
||||
})
|
||||
|
||||
expect(user).toBeDefined()
|
||||
expect(user?.username).toBe(userModel.username)
|
||||
})
|
||||
|
||||
it('should throw if existed', async () => {
|
||||
const userModel = generateMockUser()
|
||||
await proxy.service.register({ ...userModel })
|
||||
|
||||
await expect(proxy.service.register({ ...userModel })).rejects.toThrowError()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user