/** * test/http/diff.test.ts (N-diff-be, B1) — read-only git diff parsing + getDiff. * * Two layers: * 1. Pure parsers (parseNumstat / parseUnifiedDiff) fed canned git output — the * deterministic core (AC-B1.5: rename/new/delete/binary/empty/untracked, +/- * consistent with `git diff --numstat`; never throws; `cmd` red' const patch = [ 'diff --git a/x.txt b/x.txt', '--- a/x.txt', '+++ b/x.txt', '@@ -0,0 +1 @@', '+' + payload, '', ].join('\n') const [file] = parseUnifiedDiff(patch) expect(file?.hunks[0]?.lines[0]).toEqual({ kind: 'added', text: payload }) }) it('parses several files in one patch', () => { const patch = MODIFIED + [ 'diff --git a/bar.txt b/bar.txt', '--- a/bar.txt', '+++ b/bar.txt', '@@ -1 +1 @@', '-one', '+two', '', ].join('\n') const files = parseUnifiedDiff(patch) expect(files.map((f) => f.newPath)).toEqual(['foo.txt', 'bar.txt']) }) }) // ── getDiff (integration against a real git repo) ──────────────────────────── async function git(cwd: string, ...args: string[]): Promise { await execFileAsync('git', args, { cwd }) } describe('getDiff (real git repo)', () => { let repo: string beforeAll(async () => { repo = await fs.mkdtemp(path.join(os.tmpdir(), 'webterm-diff-')) await git(repo, 'init', '-q', '-b', 'main') await git(repo, 'config', 'user.email', 'test@example.com') await git(repo, 'config', 'user.name', 'Test') await git(repo, 'config', 'commit.gpgsign', 'false') await fs.writeFile(path.join(repo, 'tracked.txt'), 'one\ntwo\nthree\n') await git(repo, 'add', '.') await git(repo, 'commit', '-q', '-m', 'init') }) afterAll(async () => { await fs.rm(repo, { recursive: true, force: true }) }) it('returns structured working-tree diff with numstat-consistent counts', async () => { await fs.writeFile(path.join(repo, 'tracked.txt'), 'one\nTWO\nthree\nfour\n') const result = await getDiff(repo, { staged: false, cfg: LIMITS }) expect(result.staged).toBe(false) expect(result.truncated).toBe(false) const file = result.files.find((f) => f.newPath === 'tracked.txt') expect(file).toBeDefined() expect(file?.status).toBe('modified') // changed line two + added line four = 2 added, 1 removed expect(file?.added).toBe(2) expect(file?.removed).toBe(1) // revert for later tests await fs.writeFile(path.join(repo, 'tracked.txt'), 'one\ntwo\nthree\n') }) it('lists untracked files via porcelain (L3, working tree only)', async () => { await fs.writeFile(path.join(repo, 'fresh.txt'), 'brand new\n') const result = await getDiff(repo, { staged: false, cfg: LIMITS }) const untracked = result.files.find((f) => f.newPath === 'fresh.txt') expect(untracked?.status).toBe('untracked') await fs.rm(path.join(repo, 'fresh.txt')) }) it('does not include untracked files in the staged diff', async () => { await fs.writeFile(path.join(repo, 'staged-only.txt'), 'queued\n') await git(repo, 'add', 'staged-only.txt') const result = await getDiff(repo, { staged: true, cfg: LIMITS }) expect(result.staged).toBe(true) const file = result.files.find((f) => f.newPath === 'staged-only.txt') expect(file?.status).toBe('added') expect(result.files.every((f) => f.status !== 'untracked')).toBe(true) await git(repo, 'reset', '-q') await fs.rm(path.join(repo, 'staged-only.txt')) }) it('marks truncated when the file count exceeds diffMaxFiles', async () => { for (let i = 0; i < 5; i++) { await fs.writeFile(path.join(repo, `gen-${i}.txt`), `content ${i}\n`) } const result = await getDiff(repo, { staged: false, cfg: { ...LIMITS, diffMaxFiles: 2 }, }) expect(result.truncated).toBe(true) expect(result.files.length).toBeLessThanOrEqual(2) for (let i = 0; i < 5; i++) await fs.rm(path.join(repo, `gen-${i}.txt`)) }) it('never throws for a non-existent / non-git path (returns empty)', async () => { const result = await getDiff(path.join(os.tmpdir(), 'definitely-not-here-xyz'), { staged: false, cfg: LIMITS, }) expect(result.files).toEqual([]) expect(result.truncated).toBe(false) }) })