/** * YouTubeClient.getCommentReplies Unit Tests * * Tests the comment reply retrieval method that fetches replies * for a given parent comment thread from the YouTube Data API. */ import { describe, it, expect, vi, beforeEach } from 'vitest' const mockCommentsList = vi.fn() vi.mock('googleapis', () => { class MockOAuth2 { setCredentials(): void { // no-op } } return { google: { auth: { OAuth2: MockOAuth2, }, youtube: () => ({ videos: { list: vi.fn() }, commentThreads: { list: vi.fn() }, comments: { list: mockCommentsList, insert: vi.fn(), setModerationStatus: vi.fn(), delete: vi.fn(), }, channels: { list: vi.fn() }, }), }, } }) const mockPayload = {} as import('payload').Payload describe('YouTubeClient.getCommentReplies', () => { beforeEach(() => { vi.clearAllMocks() }) it('should return parsed replies for a parent comment', async () => { mockCommentsList.mockResolvedValueOnce({ data: { items: [ { id: 'reply-1', snippet: { parentId: 'comment-abc', textOriginal: 'Great point!', textDisplay: 'Great point!', authorDisplayName: 'User A', authorProfileImageUrl: 'https://example.com/a.jpg', authorChannelUrl: 'https://youtube.com/channel/UC_a', authorChannelId: { value: 'UC_a' }, likeCount: 5, publishedAt: '2026-01-15T10:00:00Z', updatedAt: '2026-01-15T10:00:00Z', }, }, { id: 'reply-2', snippet: { parentId: 'comment-abc', textOriginal: 'Thanks for sharing', textDisplay: 'Thanks for sharing', authorDisplayName: 'User B', authorProfileImageUrl: 'https://example.com/b.jpg', authorChannelUrl: 'https://youtube.com/channel/UC_b', authorChannelId: { value: 'UC_b' }, likeCount: 2, publishedAt: '2026-01-15T11:00:00Z', updatedAt: '2026-01-15T11:00:00Z', }, }, ], nextPageToken: undefined, }, }) const { YouTubeClient } = await import( '@/lib/integrations/youtube/YouTubeClient' ) const client = new YouTubeClient( { clientId: 'test-id', clientSecret: 'test-secret', accessToken: 'test-token', refreshToken: 'test-refresh', }, mockPayload, ) const result = await client.getCommentReplies('comment-abc') expect(result.replies).toHaveLength(2) expect(result.replies[0].id).toBe('reply-1') expect(result.replies[0].snippet.parentId).toBe('comment-abc') expect(result.replies[0].snippet.textOriginal).toBe('Great point!') expect(result.replies[0].snippet.likeCount).toBe(5) expect(result.replies[1].id).toBe('reply-2') expect(result.nextPageToken).toBeUndefined() expect(mockCommentsList).toHaveBeenCalledWith({ part: ['snippet'], parentId: 'comment-abc', maxResults: 100, textFormat: 'plainText', }) }) it('should pass custom maxResults parameter', async () => { mockCommentsList.mockResolvedValueOnce({ data: { items: [], nextPageToken: undefined, }, }) const { YouTubeClient } = await import( '@/lib/integrations/youtube/YouTubeClient' ) const client = new YouTubeClient( { clientId: 'test-id', clientSecret: 'test-secret', accessToken: 'test-token', refreshToken: 'test-refresh', }, mockPayload, ) await client.getCommentReplies('comment-xyz', 20) expect(mockCommentsList).toHaveBeenCalledWith({ part: ['snippet'], parentId: 'comment-xyz', maxResults: 20, textFormat: 'plainText', }) }) it('should return empty replies array when no items exist', async () => { mockCommentsList.mockResolvedValueOnce({ data: { items: null, nextPageToken: undefined, }, }) const { YouTubeClient } = await import( '@/lib/integrations/youtube/YouTubeClient' ) const client = new YouTubeClient( { clientId: 'test-id', clientSecret: 'test-secret', accessToken: 'test-token', refreshToken: 'test-refresh', }, mockPayload, ) const result = await client.getCommentReplies('comment-empty') expect(result.replies).toEqual([]) expect(result.nextPageToken).toBeUndefined() }) it('should return nextPageToken when more replies are available', async () => { mockCommentsList.mockResolvedValueOnce({ data: { items: [ { id: 'reply-page1', snippet: { parentId: 'comment-many', textOriginal: 'First page reply', textDisplay: 'First page reply', authorDisplayName: 'User C', authorProfileImageUrl: 'https://example.com/c.jpg', authorChannelUrl: 'https://youtube.com/channel/UC_c', authorChannelId: { value: 'UC_c' }, likeCount: 0, publishedAt: '2026-02-01T12:00:00Z', updatedAt: '2026-02-01T12:00:00Z', }, }, ], nextPageToken: 'page2token', }, }) const { YouTubeClient } = await import( '@/lib/integrations/youtube/YouTubeClient' ) const client = new YouTubeClient( { clientId: 'test-id', clientSecret: 'test-secret', accessToken: 'test-token', refreshToken: 'test-refresh', }, mockPayload, ) const result = await client.getCommentReplies('comment-many') expect(result.replies).toHaveLength(1) expect(result.nextPageToken).toBe('page2token') }) it('should propagate API errors', async () => { mockCommentsList.mockRejectedValueOnce(new Error('commentListForbidden')) const { YouTubeClient } = await import( '@/lib/integrations/youtube/YouTubeClient' ) const client = new YouTubeClient( { clientId: 'test-id', clientSecret: 'test-secret', accessToken: 'test-token', refreshToken: 'test-refresh', }, mockPayload, ) await expect( client.getCommentReplies('comment-forbidden'), ).rejects.toThrow('commentListForbidden') }) })