import { describe, expect, it, vi } from 'vitest'; import { createWatchSession, getWatchSession, parseYouTubeUrl, updateWatchSessionState, } from './watchTogether'; describe('parseYouTubeUrl', () => { it.each([ ['https://www.youtube.com/watch?v=dQw4w9WgXcQ', 'dQw4w9WgXcQ'], ['https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=42', 'dQw4w9WgXcQ'], ['https://youtu.be/dQw4w9WgXcQ', 'dQw4w9WgXcQ'], ['https://youtu.be/dQw4w9WgXcQ?t=1', 'dQw4w9WgXcQ'], ['https://www.youtube.com/embed/dQw4w9WgXcQ', 'dQw4w9WgXcQ'], ['https://www.youtube.com/shorts/dQw4w9WgXcQ', 'dQw4w9WgXcQ'], ['https://m.youtube.com/watch?v=dQw4w9WgXcQ', 'dQw4w9WgXcQ'], ['dQw4w9WgXcQ', 'dQw4w9WgXcQ'], ])('extracts the id from %s', (url, expected) => { expect(parseYouTubeUrl(url)).toBe(expected); }); it.each([ '', ' ', 'https://vimeo.com/123', 'not a url', 'short_id', 'https://www.youtube.com/playlist?list=PL123', ])('returns null for %s', (input) => { expect(parseYouTubeUrl(input)).toBeNull(); }); }); function makeClient(opts: { user?: { id: string } | null; insertReturn?: { data: unknown; error: unknown }; selectReturn?: { data: unknown; error: unknown }; updateReturn?: { error: unknown }; }): any { const single = vi.fn().mockResolvedValue(opts.insertReturn ?? { data: {}, error: null }); const insertSelect = vi.fn().mockReturnValue({ single }); const insertChain = vi.fn().mockReturnValue({ select: insertSelect }); const maybeSingle = vi.fn().mockResolvedValue(opts.selectReturn ?? { data: null, error: null }); const eqSelect = vi.fn().mockReturnValue({ maybeSingle }); const selectChain = vi.fn().mockReturnValue({ eq: eqSelect }); const eqUpdate = vi.fn().mockResolvedValue(opts.updateReturn ?? { error: null }); const updateChain = vi.fn().mockReturnValue({ eq: eqUpdate }); const from = vi.fn().mockReturnValue({ insert: insertChain, select: selectChain, update: updateChain, }); return { auth: { getUser: vi.fn().mockResolvedValue({ data: { user: opts.user ?? { id: 'u-1' } } }) }, from, }; } describe('createWatchSession', () => { it('inserts row + maps response to camelCase', async () => { const client = makeClient({ insertReturn: { data: { id: 'w-1', conversation_id: 'c-1', owner_user_id: 'u-1', video_id: 'dQw4w9WgXcQ', started_at: '2026-05-16T00:00:00Z', ended_at: null, current_state: { playing: false, position_seconds: 0, updated_at_ms: 0 }, }, error: null, }, }); const out = await createWatchSession(client, { conversationId: 'c-1', videoId: 'dQw4w9WgXcQ' }); expect(out.id).toBe('w-1'); expect(out.ownerUserId).toBe('u-1'); expect(out.videoId).toBe('dQw4w9WgXcQ'); expect(out.endedAt).toBeNull(); expect(out.currentState.playing).toBe(false); }); }); describe('getWatchSession', () => { it('returns null when row not found', async () => { const client = makeClient({ selectReturn: { data: null, error: null } }); const out = await getWatchSession(client, 'w-missing'); expect(out).toBeNull(); }); it('coerces missing current_state fields to safe defaults', async () => { const client = makeClient({ selectReturn: { data: { id: 'w-1', conversation_id: 'c-1', owner_user_id: 'u-1', video_id: 'dQw4w9WgXcQ', started_at: '2026-05-16T00:00:00Z', ended_at: null, current_state: {}, }, error: null, }, }); const out = await getWatchSession(client, 'w-1'); expect(out?.currentState).toEqual({ playing: false, positionSeconds: 0, updatedAtMs: 0 }); }); }); describe('updateWatchSessionState', () => { it('does not throw on success', async () => { const client = makeClient({ updateReturn: { error: null } }); await expect( updateWatchSessionState(client, 'w-1', { playing: true, positionSeconds: 42.5, updatedAtMs: Date.now(), }), ).resolves.toBeUndefined(); }); });