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('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', }), ]), ); }); });