chore: 初始化TikTok短剧后端项目基础架构
创建了完整的NestJS项目结构,包含配置文件、核心模块、工具类、拦截器、过滤器等基础代码,实现了健康检查、用户认证、管理员RBAC、剧集管理、订单支付、媒体管理等核心功能的基础框架,配置了ESLint、TypeScript、Jest等开发工具链。
This commit is contained in:
200
test/dramas.e2e-spec.ts
Normal file
200
test/dramas.e2e-spec.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import * as request from 'supertest';
|
||||
import { AppModule } from '../src/app.module';
|
||||
import { bootstrapApp } from '../src/bootstrap';
|
||||
import { loginAdmin } from './test-helpers';
|
||||
|
||||
describe('Dramas and episodes', () => {
|
||||
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('allows admin APIs to create a drama and episodes with release metadata', async () => {
|
||||
const dramaResponse = await request(app.getHttpServer())
|
||||
.post('/api/admin/v1/dramas')
|
||||
.set('Authorization', `Bearer ${adminToken}`)
|
||||
.send({
|
||||
title: 'City Revenge',
|
||||
description: 'A short drama series.',
|
||||
coverUrl: 'https://cdn.example.com/cover.jpg',
|
||||
portraitCoverUrl: 'https://cdn.example.com/portrait.jpg',
|
||||
landscapeCoverUrl: 'https://cdn.example.com/landscape.jpg',
|
||||
type: 'revenge',
|
||||
tags: ['urban', 'revenge'],
|
||||
region: 'US',
|
||||
language: 'en',
|
||||
year: 2026,
|
||||
status: 'published',
|
||||
sortOrder: 10,
|
||||
isRecommended: true,
|
||||
})
|
||||
.expect(201);
|
||||
|
||||
expect(dramaResponse.body.data).toMatchObject({
|
||||
id: expect.any(Number),
|
||||
title: 'City Revenge',
|
||||
status: 'published',
|
||||
type: 'revenge',
|
||||
});
|
||||
|
||||
const dramaId = dramaResponse.body.data.id;
|
||||
const episodeResponse = await request(app.getHttpServer())
|
||||
.post('/api/admin/v1/episodes')
|
||||
.set('Authorization', `Bearer ${adminToken}`)
|
||||
.send({
|
||||
dramaId,
|
||||
episodeNo: 1,
|
||||
title: 'Episode 1',
|
||||
description: 'Opening episode.',
|
||||
coverUrl: 'https://cdn.example.com/ep1.jpg',
|
||||
videoUrl: 'https://cdn.example.com/ep1.mp4',
|
||||
trialDurationSeconds: 60,
|
||||
durationSeconds: 300,
|
||||
isPaid: true,
|
||||
unlockPrice: 199,
|
||||
publishAt: '2026-07-01T00:00:00.000Z',
|
||||
nextReleaseAt: '2026-07-02T00:00:00.000Z',
|
||||
status: 'published',
|
||||
})
|
||||
.expect(201);
|
||||
|
||||
expect(episodeResponse.body.data).toMatchObject({
|
||||
id: expect.any(Number),
|
||||
dramaId,
|
||||
episodeNo: 1,
|
||||
nextReleaseAt: '2026-07-02T00:00:00.000Z',
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects duplicate episode numbers under the same drama', async () => {
|
||||
const drama = await request(app.getHttpServer())
|
||||
.post('/api/admin/v1/dramas')
|
||||
.set('Authorization', `Bearer ${adminToken}`)
|
||||
.send({
|
||||
title: 'Duplicate Episode Test',
|
||||
coverUrl: 'https://cdn.example.com/cover.jpg',
|
||||
type: 'romance',
|
||||
status: 'draft',
|
||||
})
|
||||
.expect(201);
|
||||
|
||||
const payload = {
|
||||
dramaId: drama.body.data.id,
|
||||
episodeNo: 1,
|
||||
title: 'Episode 1',
|
||||
coverUrl: 'https://cdn.example.com/ep1.jpg',
|
||||
videoUrl: 'https://cdn.example.com/ep1.mp4',
|
||||
durationSeconds: 300,
|
||||
status: 'draft',
|
||||
};
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post('/api/admin/v1/episodes')
|
||||
.set('Authorization', `Bearer ${adminToken}`)
|
||||
.send(payload)
|
||||
.expect(201);
|
||||
|
||||
const duplicate = await request(app.getHttpServer())
|
||||
.post('/api/admin/v1/episodes')
|
||||
.set('Authorization', `Bearer ${adminToken}`)
|
||||
.send(payload)
|
||||
.expect(409);
|
||||
|
||||
expect(duplicate.body).toMatchObject({
|
||||
code: 409,
|
||||
data: {},
|
||||
message: 'Episode number already exists in this drama',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns only published dramas and published episodes to app APIs', async () => {
|
||||
const published = await request(app.getHttpServer())
|
||||
.post('/api/admin/v1/dramas')
|
||||
.set('Authorization', `Bearer ${adminToken}`)
|
||||
.send({
|
||||
title: 'Published Drama',
|
||||
coverUrl: 'https://cdn.example.com/pub.jpg',
|
||||
type: 'action',
|
||||
status: 'published',
|
||||
})
|
||||
.expect(201);
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post('/api/admin/v1/dramas')
|
||||
.set('Authorization', `Bearer ${adminToken}`)
|
||||
.send({
|
||||
title: 'Draft Drama',
|
||||
coverUrl: 'https://cdn.example.com/draft.jpg',
|
||||
type: 'action',
|
||||
status: 'draft',
|
||||
})
|
||||
.expect(201);
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post('/api/admin/v1/episodes')
|
||||
.set('Authorization', `Bearer ${adminToken}`)
|
||||
.send({
|
||||
dramaId: published.body.data.id,
|
||||
episodeNo: 1,
|
||||
title: 'Published Episode',
|
||||
coverUrl: 'https://cdn.example.com/ep1.jpg',
|
||||
videoUrl: 'https://cdn.example.com/ep1.mp4',
|
||||
durationSeconds: 300,
|
||||
status: 'published',
|
||||
})
|
||||
.expect(201);
|
||||
|
||||
await request(app.getHttpServer())
|
||||
.post('/api/admin/v1/episodes')
|
||||
.set('Authorization', `Bearer ${adminToken}`)
|
||||
.send({
|
||||
dramaId: published.body.data.id,
|
||||
episodeNo: 2,
|
||||
title: 'Draft Episode',
|
||||
coverUrl: 'https://cdn.example.com/ep2.jpg',
|
||||
videoUrl: 'https://cdn.example.com/ep2.mp4',
|
||||
durationSeconds: 300,
|
||||
status: 'draft',
|
||||
})
|
||||
.expect(201);
|
||||
|
||||
const dramas = await request(app.getHttpServer())
|
||||
.get('/api/app/v1/dramas')
|
||||
.expect(200);
|
||||
|
||||
expect(dramas.body.data.list).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ title: 'Published Drama' }),
|
||||
]),
|
||||
);
|
||||
expect(dramas.body.data.list).not.toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ title: 'Draft Drama' })]),
|
||||
);
|
||||
|
||||
const episodes = await request(app.getHttpServer())
|
||||
.get(`/api/app/v1/dramas/${published.body.data.id}/episodes`)
|
||||
.expect(200);
|
||||
|
||||
expect(episodes.body.data.list).toEqual([
|
||||
expect.objectContaining({
|
||||
episodeNo: 1,
|
||||
title: 'Published Episode',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user