import { describe, expect, test } from 'bun:test'; import { stageGitFile, stageGitFiles, unstageGitFile, unstageGitFiles } from './gitApiHttp'; type FetchCall = { input: RequestInfo | URL; init?: RequestInit; }; const previousFetch = globalThis.fetch; const previousWindowDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'window'); const installFetchMock = () => { const calls: FetchCall[] = []; globalThis.fetch = (async (input, init) => { calls.push({ input, init }); return new Response(JSON.stringify({ success: true }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); }) as typeof fetch; return calls; }; const installWindowMock = () => { Object.defineProperty(globalThis, 'window', { configurable: true, value: { location: { origin: 'http://localhost:3000' }, }, }); }; const restoreMocks = () => { globalThis.fetch = previousFetch; if (previousWindowDescriptor) { Object.defineProperty(globalThis, 'window', previousWindowDescriptor); } else { delete (globalThis as { window?: Window }).window; } }; const captureError = async (callback: () => Promise): Promise => { try { await callback(); return null; } catch (error) { return error; } }; describe('gitApiHttp index mutations', () => { test('sends bulk stage payloads as paths', async () => { installWindowMock(); const calls = installFetchMock(); try { await stageGitFiles('/repo', ['a.ts', 'b.ts']); expect(calls).toHaveLength(1); expect(String(calls[0].input)).toBe('http://localhost:3000/api/git/stage?directory=%2Frepo'); expect(calls[0].init?.method).toBe('POST'); expect(JSON.parse(String(calls[0].init?.body))).toEqual({ paths: ['a.ts', 'b.ts'] }); } finally { restoreMocks(); } }); test('sends bulk unstage payloads as paths', async () => { installWindowMock(); const calls = installFetchMock(); try { await unstageGitFiles('/repo', ['a.ts', 'b.ts']); expect(calls).toHaveLength(1); expect(String(calls[0].input)).toBe('http://localhost:3000/api/git/unstage?directory=%2Frepo'); expect(calls[0].init?.method).toBe('POST'); expect(JSON.parse(String(calls[0].init?.body))).toEqual({ paths: ['a.ts', 'b.ts'] }); } finally { restoreMocks(); } }); test('single-file helpers use the bulk paths payload shape', async () => { installWindowMock(); const calls = installFetchMock(); try { await stageGitFile('/repo', 'a.ts'); await unstageGitFile('/repo', 'b.ts'); expect(JSON.parse(String(calls[0].init?.body))).toEqual({ paths: ['a.ts'] }); expect(JSON.parse(String(calls[1].init?.body))).toEqual({ paths: ['b.ts'] }); } finally { restoreMocks(); } }); test('rejects empty bulk path lists before fetching', async () => { installWindowMock(); const calls = installFetchMock(); try { const stageError = await captureError(() => stageGitFiles('/repo', [' ', ''])); const unstageError = await captureError(() => unstageGitFiles('/repo', [])); expect(stageError).toBeInstanceOf(Error); expect((stageError as Error).message).toBe('path is required to stage git changes'); expect(unstageError).toBeInstanceOf(Error); expect((unstageError as Error).message).toBe('path is required to unstage git changes'); expect(calls).toHaveLength(0); } finally { restoreMocks(); } }); });