import { AdminService } from '../src/modules/admin/admin.service'; import { DataService } from '../src/modules/data/data.service'; import { TikTokDataProvider } from '../src/modules/data/providers/tiktok-data.provider'; import { PublishStatus } from '../src/modules/dramas/drama-status.enum'; import { DramasService } from '../src/modules/dramas/dramas.service'; import { LogsService } from '../src/modules/logs/logs.service'; import { UsersService } from '../src/modules/users/users.service'; function createRepository() { const records: T[] = []; return { records, create: jest.fn((input: Partial) => input as T), save: jest.fn(async (input: T) => { if (!input.id) { input.id = records.length + 1; } const index = records.findIndex((item) => item.id === input.id); if (index >= 0) { records[index] = input; } else { records.push(input); } return input; }), findOne: jest.fn(async ({ where }: { where: Partial }) => records.find((item) => Object.entries(where).every( ([key, value]) => item[key as keyof T] === value, ), ) ?? null, ), find: jest.fn(async () => [...records]), count: jest.fn(async () => records.length), }; } function createDataSource(repositories: Record) { return { isInitialized: true, getRepository: jest.fn((entity: { name: string }) => { const repository = repositories[entity.name]; if (!repository) { throw new Error(`Missing repository for ${entity.name}`); } return repository; }), }; } describe('database-backed persistence', () => { it('persists permissions and admin profile changes through TypeORM repositories', async () => { const permissionRepository = createRepository(); const roleRepository = createRepository(); const adminUserRepository = createRepository(); const dataSource = createDataSource({ Permission: permissionRepository, Role: roleRepository, AdminUser: adminUserRepository, }); const service = new AdminService( { signAsync: jest.fn() } as never, dataSource as never, ); await service.onModuleInit(); const permissions = await service.listPermissions({ page: 1, pageSize: 50 }); expect(permissions.list).toEqual( expect.arrayContaining([ expect.objectContaining({ code: 'permission:read', name: '查看权限' }), expect.objectContaining({ code: 'data:read', name: '查看数据' }), ]), ); const updated = await service.updateProfile(1, { nickname: '数据库管理员', email: 'db-admin@example.com', }); expect(updated).toMatchObject({ id: 1, nickname: '数据库管理员', email: 'db-admin@example.com', }); expect(adminUserRepository.save).toHaveBeenCalledWith( expect.objectContaining({ nickname: '数据库管理员', email: 'db-admin@example.com', }), ); }); it('persists app users through TypeORM repositories when a DataSource is configured', async () => { const userRepository = createRepository(); const dataSource = createDataSource({ User: userRepository }); const service = new UsersService(dataSource as never); const result = await service.upsertTikTokUser({ tiktokOpenId: 'open-db-1', nickname: 'DB User', }); expect(result.isNewUser).toBe(true); expect(userRepository.save).toHaveBeenCalledWith( expect.objectContaining({ tiktokOpenId: 'open-db-1', nickname: 'DB User', status: 'active', }), ); }); it('persists request logs and playback error logs through TypeORM repositories', async () => { const requestLogRepository = createRepository(); const playbackErrorLogRepository = createRepository(); const dataSource = createDataSource({ RequestLog: requestLogRepository, PlaybackErrorLog: playbackErrorLogRepository, }); const service = new LogsService(dataSource as never); await service.recordRequest({ requestId: '00000000-0000-4000-8000-000000000001', method: 'GET', path: '/api/admin/v1/logs', statusCode: 200, responseCode: 0, message: 'success', costMs: 3, }); await service.createPlaybackError({ requestId: '00000000-0000-4000-8000-000000000002', dramaId: 1, episodeId: 2, playUrl: 'https://cdn.example.com/video.mp4', playerErrorCode: 'NETWORK', message: 'network error', occurredAt: '2026-07-01T00:00:00.000Z', }); expect(requestLogRepository.save).toHaveBeenCalled(); expect(playbackErrorLogRepository.save).toHaveBeenCalled(); }); it('persists synced drama and episode metrics through TypeORM repositories', async () => { const dramaMetricRepository = createRepository(); const episodeMetricRepository = createRepository(); const syncJobRepository = createRepository(); const dataSource = createDataSource({ DramaMetric: dramaMetricRepository, EpisodeMetric: episodeMetricRepository, DataSyncJob: syncJobRepository, }); const dramasService = new DramasService(); const tiktokDataProvider = new TikTokDataProvider(); const service = new DataService( dramasService, tiktokDataProvider, dataSource as never, ); const drama = await dramasService.createDrama({ title: 'DB Metrics', coverUrl: 'https://cdn.example.com/cover.jpg', type: 'action', status: PublishStatus.Published, }); await dramasService.createEpisode({ dramaId: drama.id, episodeNo: 1, title: 'DB Metrics Episode', coverUrl: drama.coverUrl, videoUrl: 'https://cdn.example.com/video.mp4', durationSeconds: 180, status: PublishStatus.Published, }); await service.sync({ dramaId: drama.id }); expect(dramaMetricRepository.save).toHaveBeenCalled(); expect(episodeMetricRepository.save).toHaveBeenCalled(); expect(syncJobRepository.save).toHaveBeenCalled(); }); });