chore: 初始化TikTok短剧后端项目基础架构
创建了完整的NestJS项目结构,包含配置文件、核心模块、工具类、拦截器、过滤器等基础代码,实现了健康检查、用户认证、管理员RBAC、剧集管理、订单支付、媒体管理等核心功能的基础框架,配置了ESLint、TypeScript、Jest等开发工具链。
This commit is contained in:
111
test/api-standard.e2e-spec.ts
Normal file
111
test/api-standard.e2e-spec.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import * as request from 'supertest';
|
||||
import { validate as validateUuid, version as uuidVersion } from 'uuid';
|
||||
import { AppModule } from '../src/app.module';
|
||||
import { bootstrapApp } from '../src/bootstrap';
|
||||
import { loginAdmin } from './test-helpers';
|
||||
|
||||
describe('API standard', () => {
|
||||
let app: INestApplication;
|
||||
let adminToken: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrapApp(app);
|
||||
await app.init();
|
||||
adminToken = await loginAdmin(app);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('wraps successful responses with code, data, message, timestamp, requestId and cost', async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get('/api/app/v1/health')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).toEqual({
|
||||
code: 0,
|
||||
data: { status: 'ok' },
|
||||
message: 'success',
|
||||
timestamp: expect.any(Number),
|
||||
requestId: expect.any(String),
|
||||
cost: expect.stringMatching(/^\d+ms$/),
|
||||
});
|
||||
expect(validateUuid(response.body.requestId)).toBe(true);
|
||||
expect(uuidVersion(response.body.requestId)).toBe(4);
|
||||
});
|
||||
|
||||
it('reuses a valid X-Request-Id header', async () => {
|
||||
const requestId = '0f5c2e16-1e44-4d55-93a4-ef7c66d9d1f2';
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.get('/api/app/v1/health')
|
||||
.set('X-Request-Id', requestId)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.requestId).toBe(requestId);
|
||||
});
|
||||
|
||||
it('wraps errors in the standard response shape', async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get('/api/app/v1/missing')
|
||||
.expect(404);
|
||||
|
||||
expect(response.body).toEqual({
|
||||
code: 404,
|
||||
data: {},
|
||||
message: expect.any(String),
|
||||
timestamp: expect.any(Number),
|
||||
requestId: expect.any(String),
|
||||
cost: expect.stringMatching(/^\d+ms$/),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns paginated lists using data.list and data.pagination', async () => {
|
||||
const response = await request(app.getHttpServer())
|
||||
.get('/api/admin/v1/logs/requests')
|
||||
.set('Authorization', `Bearer ${adminToken}`)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.code).toBe(0);
|
||||
expect(response.body.data).toEqual({
|
||||
list: expect.any(Array),
|
||||
pagination: {
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: expect.any(Number),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('can query a request log by requestId', async () => {
|
||||
const requestId = '180facd4-68f9-48c9-b6b1-3d8eab56a681';
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.get('/api/app/v1/health')
|
||||
.set('X-Request-Id', requestId)
|
||||
.expect(200);
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/admin/v1/logs/requests/${requestId}`)
|
||||
.set('Authorization', `Bearer ${adminToken}`)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data).toMatchObject({
|
||||
requestId,
|
||||
method: 'GET',
|
||||
path: '/api/app/v1/health',
|
||||
statusCode: 200,
|
||||
responseCode: 0,
|
||||
message: 'success',
|
||||
});
|
||||
expect(response.body.data.costMs).toEqual(expect.any(Number));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user