125 lines
4.0 KiB
TypeScript
125 lines
4.0 KiB
TypeScript
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();
|
|
});
|
|
});
|