Files
Kilo-Org_kilocode/packages/opencode/test/kilocode/session-diff-restore.test.ts
T
Igor Šćekić 6ee090b5a4 Restore cloud session diffs on import (#10948)
* feat(cli): restore cloud session diffs on import

* fix(cli): hide git diff restore subprocesses

* fix(cli): harden session diff path guard

* test(cli): keep diff restore test synchronous

* test(cli): normalize diff restore line endings

* fix(cli): preserve imported diffs across session forks

* chore: remove session ingest findings doc

* fix(cli): avoid duplicate cumulative session diffs

* test(cli): isolate session export test failures

* fix(cli): preserve cumulative cloud fork diffs

* fix(cli): avoid cumulative diff prefix duplication
2026-06-08 15:44:03 +00:00

142 lines
5.2 KiB
TypeScript

import { afterEach, describe, expect, test } from "bun:test"
import fs from "node:fs"
import os from "node:os"
import path from "node:path"
import { appendSessionDiffs, mergeSessionDiffs } from "../../src/kilocode/session-portability/cumulative-diff"
import { extractSessionDiffs, restoreSessionDiffs } from "../../src/kilocode/session-portability/session-diff-restore"
const dirs: string[] = []
function tmp() {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "kilo-session-diff-"))
dirs.push(dir)
return dir
}
function git(dir: string, args: string[]) {
const proc = Bun.spawnSync(["git", ...args], {
cwd: dir,
stdout: "pipe",
stderr: "pipe",
windowsHide: true,
})
if (proc.exitCode !== 0) {
const text = new TextDecoder().decode(proc.stderr)
throw new Error(text)
}
return new TextDecoder().decode(proc.stdout)
}
function repo() {
const dir = tmp()
fs.mkdirSync(path.join(dir, "src"), { recursive: true })
git(dir, ["init"])
git(dir, ["config", "user.email", "test@example.com"])
git(dir, ["config", "user.name", "Test User"])
fs.writeFileSync(path.join(dir, "src/index.ts"), "before\n")
git(dir, ["add", "."])
git(dir, ["commit", "-m", "initial"])
return dir
}
function patch(dir: string) {
fs.writeFileSync(path.join(dir, "src/index.ts"), "after\n")
return git(dir, ["diff", "--src-prefix=a/", "--dst-prefix=b/"])
}
afterEach(() => {
for (const dir of dirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true })
}
})
describe("session diff restore", () => {
test("merges imported base diffs before local diffs without duplicating unchanged imports", () => {
const base = [{ file: "a.txt", patch: "base", additions: 1, deletions: 0, status: "added" as const }]
const local = [{ file: "b.txt", patch: "local", additions: 1, deletions: 0, status: "added" as const }]
expect(mergeSessionDiffs({ base, local })).toEqual([...base, ...local])
expect(mergeSessionDiffs({ base, local: base })).toEqual(base)
expect(mergeSessionDiffs({ base, local: [...base, ...local] })).toEqual([...base, ...local])
})
test("appends turn diffs to imported cumulative diffs without repeating existing tails", () => {
const base = [{ file: "a.txt", patch: "base", additions: 1, deletions: 0, status: "added" as const }]
const local = [{ file: "b.txt", patch: "local", additions: 1, deletions: 0, status: "added" as const }]
expect(appendSessionDiffs({ existing: base, next: local })).toEqual([...base, ...local])
expect(appendSessionDiffs({ existing: [...base, ...local], next: local })).toEqual([...base, ...local])
expect(appendSessionDiffs({ existing: base, next: [...base, ...local] })).toEqual([...base, ...local])
expect(appendSessionDiffs({ existing: [...base, ...local], next: base })).toEqual([...base, ...local])
})
test("extracts top-level sessionDiff before legacy message summaries", () => {
const diff = {
file: "src/index.ts",
patch: "diff --git a/src/index.ts b/src/index.ts\n",
additions: 1,
deletions: 0,
status: "modified",
}
const data = {
sessionDiff: [diff],
messages: [{ info: { summary: { diffs: [{ file: "legacy.txt", additions: 1, deletions: 0 }] } } }],
}
expect(extractSessionDiffs(data)).toEqual([diff])
})
test("falls back to legacy message summary diffs", () => {
const data = {
messages: [
{ info: { summary: { diffs: [{ file: "a.txt", after: "first", additions: 1, deletions: 0 }] } } },
{ info: { summary: { diffs: [{ file: "a.txt", after: "second", additions: 1, deletions: 0 }] } } },
],
}
expect(extractSessionDiffs(data)).toEqual([{ file: "a.txt", after: "second", additions: 1, deletions: 0 }])
})
test("applies patch diffs in a git workspace", () => {
const dir = repo()
const text = patch(dir)
git(dir, ["checkout", "--", "."])
const result = restoreSessionDiffs({
directory: dir,
diffs: [{ file: "src/index.ts", patch: text, additions: 1, deletions: 1, status: "modified" }],
})
expect(result).toEqual({ applied: 1, skipped: 0, total: 1 })
expect(fs.readFileSync(path.join(dir, "src/index.ts"), "utf8").replace(/\r\n/g, "\n")).toBe("after\n")
})
test("applies patch diffs missing a final patch newline", () => {
const dir = repo()
const text = patch(dir).trimEnd()
git(dir, ["checkout", "--", "."])
const result = restoreSessionDiffs({
directory: dir,
diffs: [{ file: "src/index.ts", patch: text, additions: 1, deletions: 1, status: "modified" }],
})
expect(result).toEqual({ applied: 1, skipped: 0, total: 1 })
expect(fs.readFileSync(path.join(dir, "src/index.ts"), "utf8").replace(/\r\n/g, "\n")).toBe("after\n")
})
test("skips snapshot diffs outside the workspace", () => {
const dir = tmp()
const out = path.join(path.dirname(dir), `${path.basename(dir)}-outside.txt`)
fs.rmSync(out, { force: true })
const result = restoreSessionDiffs({
directory: dir,
diffs: [{ file: `../${path.basename(out)}`, after: "outside", additions: 1, deletions: 0, status: "modified" }],
})
expect(result).toEqual({ applied: 0, skipped: 1, total: 1 })
expect(fs.existsSync(out)).toBe(false)
})
})