import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { HttpAPIClient } from '@renderer/api/httpClient'; class FakeEventSource { onopen: (() => void) | null = null; onerror: (() => void) | null = null; addEventListener = vi.fn(); close = vi.fn(); } describe('HttpAPIClient memberWorkSync', () => { let fetchMock: ReturnType; let eventSourceMock: ReturnType; beforeEach(() => { fetchMock = vi.fn(async () => jsonResponse({ ok: true })); eventSourceMock = vi.fn(() => new FakeEventSource()); vi.stubGlobal('fetch', fetchMock); vi.stubGlobal('EventSource', eventSourceMock); }); afterEach(() => { vi.unstubAllGlobals(); vi.restoreAllMocks(); }); it('maps browser-mode member work sync calls to the HTTP routes', async () => { const client = new HttpAPIClient('http://127.0.0.1:53123'); fetchMock .mockResolvedValueOnce(jsonResponse({ state: 'needs_sync' })) .mockResolvedValueOnce(jsonResponse({ state: 'still_working' })) .mockResolvedValueOnce(jsonResponse({ memberCount: 1 })) .mockResolvedValueOnce(jsonResponse({ accepted: true })); await expect( client.memberWorkSync.getStatus({ teamName: 'demo team', memberName: 'bob/qa' }) ).resolves.toEqual({ state: 'needs_sync' }); await expect( client.memberWorkSync.refreshStatus({ teamName: 'demo team', memberName: 'bob/qa' }) ).resolves.toEqual({ state: 'still_working' }); await expect(client.memberWorkSync.getMetrics({ teamName: 'demo team' })).resolves.toEqual({ memberCount: 1, }); await expect( client.memberWorkSync.report({ teamName: 'demo team', memberName: 'bob/qa', state: 'still_working', agendaFingerprint: 'agenda:v1:test', taskIds: ['task-a'], source: 'app', }) ).resolves.toEqual({ accepted: true }); expect(eventSourceMock).toHaveBeenCalledWith('http://127.0.0.1:53123/api/events'); expect(fetchMock).toHaveBeenNthCalledWith( 1, 'http://127.0.0.1:53123/api/teams/demo%20team/member-work-sync/bob%2Fqa', expect.objectContaining({ signal: expect.any(AbortSignal) }) ); expect(fetchMock).toHaveBeenNthCalledWith( 2, 'http://127.0.0.1:53123/api/teams/demo%20team/member-work-sync/bob%2Fqa/refresh', expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}), signal: expect.any(AbortSignal), }) ); expect(fetchMock).toHaveBeenNthCalledWith( 3, 'http://127.0.0.1:53123/api/teams/demo%20team/member-work-sync/metrics', expect.objectContaining({ signal: expect.any(AbortSignal) }) ); expect(fetchMock).toHaveBeenNthCalledWith( 4, 'http://127.0.0.1:53123/api/teams/demo%20team/member-work-sync/report', expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ teamName: 'demo team', memberName: 'bob/qa', state: 'still_working', agendaFingerprint: 'agenda:v1:test', taskIds: ['task-a'], source: 'app', }), signal: expect.any(AbortSignal), }) ); }); }); function jsonResponse(body: unknown): Response { return new Response(JSON.stringify(body), { status: 200, headers: { 'Content-Type': 'application/json' }, }); }