1. 重构项目文件结构,统一文件存放位置与命名规则 2. 配置tsconfig路径别名,替换原有相对路径导入 3. 迁移drama-status枚举到dramas.types统一管理 4. 新增全局配置文件与工具类,补充完整注释与文档 5. 修复所有导入路径与依赖引用问题
238 lines
7.2 KiB
TypeScript
238 lines
7.2 KiB
TypeScript
import { INestApplication } from '@nestjs/common';
|
|
import { Test } from '@nestjs/testing';
|
|
import * as request from 'supertest';
|
|
import { AppModule } from '../src/app/app.module';
|
|
import { bootstrapApp } from '../src/bootstrap';
|
|
import { loginAdmin } from './test-helpers';
|
|
|
|
describe('Core drama platform modules', () => {
|
|
let app: INestApplication;
|
|
let adminToken: string;
|
|
let userToken: string;
|
|
|
|
beforeAll(async () => {
|
|
const moduleRef = await Test.createTestingModule({
|
|
imports: [AppModule],
|
|
}).compile();
|
|
|
|
app = moduleRef.createNestApplication();
|
|
bootstrapApp(app);
|
|
await app.init();
|
|
adminToken = await loginAdmin(app);
|
|
|
|
const login = await request(app.getHttpServer())
|
|
.post('/api/app/v1/auth/tiktok-login')
|
|
.send({
|
|
code: 'core-modules-code',
|
|
mockOpenId: 'core-open-id',
|
|
nickname: 'Core User',
|
|
avatarUrl: 'https://cdn.example.com/avatar.png',
|
|
})
|
|
.expect(201);
|
|
|
|
userToken = login.body.data.accessToken;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
});
|
|
|
|
it('creates albums and episodes with TikTok, media, review, publish, and pricing fields', async () => {
|
|
const cover = await request(app.getHttpServer())
|
|
.post('/api/admin/v1/media/covers')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.send({
|
|
fileName: 'cover.jpg',
|
|
url: 'https://cdn.example.com/cover.jpg',
|
|
})
|
|
.expect(201);
|
|
|
|
const uploadJob = await request(app.getHttpServer())
|
|
.post('/api/admin/v1/media/upload-jobs')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.send({
|
|
jobId: 'job-001',
|
|
byteplusVid: 'v02-byteplus-001',
|
|
status: 'completed',
|
|
rawResponse: { provider: 'byteplus' },
|
|
})
|
|
.expect(201);
|
|
|
|
expect(uploadJob.body.data).toMatchObject({
|
|
id: expect.any(Number),
|
|
jobId: 'job-001',
|
|
byteplusVid: 'v02-byteplus-001',
|
|
status: 'completed',
|
|
rawResponse: { provider: 'byteplus' },
|
|
});
|
|
|
|
const album = await request(app.getHttpServer())
|
|
.post('/api/admin/v1/dramas')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.send({
|
|
tiktokAlbumId: 'tk-album-001',
|
|
title: 'Core Album',
|
|
description: 'Album for core schema.',
|
|
coverId: cover.body.data.id,
|
|
coverUrl: cover.body.data.url,
|
|
type: 'revenge',
|
|
status: 'draft',
|
|
onlineVersion: 1,
|
|
})
|
|
.expect(201);
|
|
|
|
expect(album.body.data).toMatchObject({
|
|
id: expect.any(Number),
|
|
tiktokAlbumId: 'tk-album-001',
|
|
title: 'Core Album',
|
|
coverId: cover.body.data.id,
|
|
coverUrl: 'https://cdn.example.com/cover.jpg',
|
|
status: 'draft',
|
|
onlineVersion: 1,
|
|
});
|
|
|
|
const episode = await request(app.getHttpServer())
|
|
.post('/api/admin/v1/episodes')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.send({
|
|
albumId: album.body.data.id,
|
|
tiktokEpisodeId: 'tk-episode-001',
|
|
seq: 1,
|
|
title: 'Core Episode 1',
|
|
byteplusVid: 'v02-byteplus-001',
|
|
coverId: cover.body.data.id,
|
|
coverUrl: cover.body.data.url,
|
|
videoUrl: 'https://cdn.example.com/episode.mp4',
|
|
freeType: 'paid',
|
|
price: 299,
|
|
reviewStatus: 'pending',
|
|
publishStatus: 'draft',
|
|
durationSeconds: 300,
|
|
})
|
|
.expect(201);
|
|
|
|
expect(episode.body.data).toMatchObject({
|
|
id: expect.any(Number),
|
|
albumId: album.body.data.id,
|
|
tiktokEpisodeId: 'tk-episode-001',
|
|
seq: 1,
|
|
byteplusVid: 'v02-byteplus-001',
|
|
coverId: cover.body.data.id,
|
|
freeType: 'paid',
|
|
price: 299,
|
|
reviewStatus: 'pending',
|
|
publishStatus: 'draft',
|
|
});
|
|
});
|
|
|
|
it('supports review submission, publish status changes, player auth, trade orders, webhook unlocks, and unlock records', async () => {
|
|
const album = await request(app.getHttpServer())
|
|
.post('/api/admin/v1/dramas')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.send({
|
|
title: 'Player Album',
|
|
coverUrl: 'https://cdn.example.com/player-cover.jpg',
|
|
type: 'player',
|
|
status: 'published',
|
|
onlineVersion: 1,
|
|
})
|
|
.expect(201);
|
|
|
|
const episode = await request(app.getHttpServer())
|
|
.post('/api/admin/v1/episodes')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.send({
|
|
albumId: album.body.data.id,
|
|
seq: 1,
|
|
title: 'Paid Player Episode',
|
|
byteplusVid: 'v02-player-001',
|
|
coverUrl: 'https://cdn.example.com/player-episode.jpg',
|
|
videoUrl: 'https://cdn.example.com/player-episode.mp4',
|
|
freeType: 'paid',
|
|
price: 399,
|
|
reviewStatus: 'approved',
|
|
publishStatus: 'published',
|
|
durationSeconds: 300,
|
|
})
|
|
.expect(201);
|
|
|
|
await request(app.getHttpServer())
|
|
.post(`/api/admin/v1/dramas/${album.body.data.id}/submit-review`)
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(201);
|
|
|
|
await request(app.getHttpServer())
|
|
.patch(`/api/admin/v1/episodes/${episode.body.data.id}/publish-status`)
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.send({ publishStatus: 'published' })
|
|
.expect(200);
|
|
|
|
const lockedPlayer = await request(app.getHttpServer())
|
|
.get(`/api/app/v1/player/episodes/${episode.body.data.id}`)
|
|
.set('Authorization', `Bearer ${userToken}`)
|
|
.expect(200);
|
|
|
|
expect(lockedPlayer.body.data).toMatchObject({
|
|
albumId: album.body.data.id,
|
|
episodeId: episode.body.data.id,
|
|
vid: 'v02-player-001',
|
|
hasPermission: false,
|
|
playAuthToken: null,
|
|
});
|
|
|
|
const order = await request(app.getHttpServer())
|
|
.post('/api/app/v1/orders')
|
|
.set('Authorization', `Bearer ${userToken}`)
|
|
.send({ episodeId: episode.body.data.id })
|
|
.expect(201);
|
|
|
|
expect(order.body.data).toMatchObject({
|
|
id: expect.any(Number),
|
|
albumId: album.body.data.id,
|
|
episodeId: episode.body.data.id,
|
|
tradeOrderId: expect.stringMatching(/^TRADE/),
|
|
amount: 399,
|
|
status: 'pending',
|
|
});
|
|
|
|
await request(app.getHttpServer())
|
|
.post('/api/webhooks/tiktok/payments')
|
|
.send({
|
|
tradeOrderId: order.body.data.tradeOrderId,
|
|
tiktokOrderId: 'tk-order-001',
|
|
providerTransactionId: 'txn-core-001',
|
|
status: 'paid',
|
|
signature: 'dev-signature',
|
|
})
|
|
.expect(201);
|
|
|
|
const unlockedPlayer = await request(app.getHttpServer())
|
|
.get(`/api/app/v1/player/episodes/${episode.body.data.id}`)
|
|
.set('Authorization', `Bearer ${userToken}`)
|
|
.expect(200);
|
|
|
|
expect(unlockedPlayer.body.data).toMatchObject({
|
|
albumId: album.body.data.id,
|
|
episodeId: episode.body.data.id,
|
|
vid: 'v02-player-001',
|
|
hasPermission: true,
|
|
playAuthToken: expect.stringMatching(/^play_auth_/),
|
|
});
|
|
|
|
const unlocks = await request(app.getHttpServer())
|
|
.get('/api/admin/v1/unlock-records')
|
|
.set('Authorization', `Bearer ${adminToken}`)
|
|
.expect(200);
|
|
|
|
expect(unlocks.body.data.list).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
userId: expect.any(Number),
|
|
episodeId: episode.body.data.id,
|
|
source: 'payment',
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
});
|