import { beforeEach, describe, expect, it, vi } from 'vitest'; const { fetchDouyinUserVideosMock, fetchDouyinCommentsMock } = vi.hoisted(() => ({ fetchDouyinUserVideosMock: vi.fn(), fetchDouyinCommentsMock: vi.fn(), })); vi.mock('./_shared/public-api.js', () => ({ fetchDouyinUserVideos: fetchDouyinUserVideosMock, fetchDouyinComments: fetchDouyinCommentsMock, })); import { getRegistry } from '@jackwener/opencli/registry'; import { CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors'; import { DEFAULT_COMMENT_LIMIT, MAX_USER_VIDEOS_LIMIT, normalizeCommentLimit, normalizeUserVideosLimit } from './user-videos.js'; describe('douyin user-videos', () => { beforeEach(() => { fetchDouyinUserVideosMock.mockReset(); fetchDouyinCommentsMock.mockReset(); }); it('registers the command', () => { const registry = getRegistry(); const values = [...registry.values()]; const command = values.find((cmd) => cmd.site === 'douyin' && cmd.name === 'user-videos'); expect(command).toBeDefined(); }); it('clamps limit to a safe maximum', () => { expect(normalizeUserVideosLimit(100)).toBe(MAX_USER_VIDEOS_LIMIT); expect(normalizeUserVideosLimit(0)).toBe(1); expect(normalizeCommentLimit(99)).toBe(DEFAULT_COMMENT_LIMIT); }); it('uses shared public-api helpers and applies clamped limits', async () => { const registry = getRegistry(); const command = [...registry.values()].find((cmd) => cmd.site === 'douyin' && cmd.name === 'user-videos'); expect(command?.func).toBeDefined(); if (!command?.func) throw new Error('douyin user-videos command not registered'); fetchDouyinUserVideosMock.mockResolvedValueOnce([ { aweme_id: '1', desc: 'test video', video: { duration: 1234, play_addr: { url_list: ['https://example.com/video.mp4'] } }, statistics: { digg_count: 9 }, }, ]); fetchDouyinCommentsMock.mockResolvedValueOnce([ { text: 'nice', digg_count: 3, nickname: 'alice' }, ]); const page = { goto: vi.fn().mockResolvedValue(undefined), wait: vi.fn().mockResolvedValue(undefined), }; const rows = await command.func(page, { sec_uid: 'MS4w-test', limit: 100, comment_limit: 99, with_comments: true, }); expect(fetchDouyinUserVideosMock).toHaveBeenCalledWith(page, 'MS4w-test', MAX_USER_VIDEOS_LIMIT); expect(fetchDouyinCommentsMock).toHaveBeenCalledWith(page, '1', DEFAULT_COMMENT_LIMIT); expect(rows).toEqual([ { index: 1, aweme_id: '1', title: 'test video', duration: 1, digg_count: 9, play_url: 'https://example.com/video.mp4', top_comments: [ { text: 'nice', digg_count: 3, nickname: 'alice' }, ], }, ]); }); it('skips comment enrichment when with_comments is false', async () => { const registry = getRegistry(); const command = [...registry.values()].find((cmd) => cmd.site === 'douyin' && cmd.name === 'user-videos'); expect(command?.func).toBeDefined(); if (!command?.func) throw new Error('douyin user-videos command not registered'); fetchDouyinUserVideosMock.mockResolvedValueOnce([ { aweme_id: '2', desc: 'plain video', video: { duration: 2000, play_addr: { url_list: ['https://example.com/plain.mp4'] } }, statistics: { digg_count: 1 }, }, ]); const page = { goto: vi.fn().mockResolvedValue(undefined), wait: vi.fn().mockResolvedValue(undefined), }; const rows = await command.func(page, { sec_uid: 'MS4w-test', limit: 3, with_comments: false, comment_limit: 5, }); expect(fetchDouyinCommentsMock).not.toHaveBeenCalled(); expect(rows).toEqual([ { index: 1, aweme_id: '2', title: 'plain video', duration: 2, digg_count: 1, play_url: 'https://example.com/plain.mp4', top_comments: [], }, ]); }); it('throws EmptyResultError when the user videos API returns no rows', async () => { const command = [...getRegistry().values()].find((cmd) => cmd.site === 'douyin' && cmd.name === 'user-videos'); expect(command?.func).toBeDefined(); if (!command?.func) throw new Error('douyin user-videos command not registered'); fetchDouyinUserVideosMock.mockResolvedValueOnce([]); const page = { goto: vi.fn().mockResolvedValue(undefined), wait: vi.fn().mockResolvedValue(undefined), }; await expect(command.func(page, { sec_uid: 'MS4w-empty', limit: 3, with_comments: true, comment_limit: 5, })).rejects.toBeInstanceOf(EmptyResultError); }); it('surfaces comment enrichment failures instead of returning empty comments', async () => { const command = [...getRegistry().values()].find((cmd) => cmd.site === 'douyin' && cmd.name === 'user-videos'); expect(command?.func).toBeDefined(); if (!command?.func) throw new Error('douyin user-videos command not registered'); fetchDouyinUserVideosMock.mockResolvedValueOnce([ { aweme_id: '3', desc: 'comment failure', video: { duration: 2000, play_addr: { url_list: ['https://example.com/fail.mp4'] } }, statistics: { digg_count: 1 }, }, ]); fetchDouyinCommentsMock.mockRejectedValueOnce(new Error('comment API down')); const page = { goto: vi.fn().mockResolvedValue(undefined), wait: vi.fn().mockResolvedValue(undefined), }; await expect(command.func(page, { sec_uid: 'MS4w-test', limit: 3, with_comments: true, comment_limit: 5, })).rejects.toBeInstanceOf(CommandExecutionError); }); });