mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-25 03:34:22 +01:00
big format
This commit is contained in:
@@ -6,23 +6,23 @@ import { tmpdir } from "os"
|
||||
|
||||
describe("Patch namespace", () => {
|
||||
let tempDir: string
|
||||
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = await fs.mkdtemp(path.join(tmpdir(), "patch-test-"))
|
||||
})
|
||||
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up temp directory
|
||||
await fs.rm(tempDir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
|
||||
describe("parsePatch", () => {
|
||||
test("should parse simple add file patch", () => {
|
||||
const patchText = `*** Begin Patch
|
||||
*** Add File: test.txt
|
||||
+Hello World
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = Patch.parsePatch(patchText)
|
||||
expect(result.hunks).toHaveLength(1)
|
||||
expect(result.hunks[0]).toEqual({
|
||||
@@ -31,19 +31,19 @@ describe("Patch namespace", () => {
|
||||
contents: "Hello World",
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
test("should parse delete file patch", () => {
|
||||
const patchText = `*** Begin Patch
|
||||
*** Delete File: old.txt
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = Patch.parsePatch(patchText)
|
||||
expect(result.hunks).toHaveLength(1)
|
||||
const hunk = result.hunks[0]
|
||||
expect(hunk.type).toBe("delete")
|
||||
expect(hunk.path).toBe("old.txt")
|
||||
})
|
||||
|
||||
|
||||
test("should parse patch with multiple hunks", () => {
|
||||
const patchText = `*** Begin Patch
|
||||
*** Add File: new.txt
|
||||
@@ -54,13 +54,13 @@ describe("Patch namespace", () => {
|
||||
-new line
|
||||
+updated line
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = Patch.parsePatch(patchText)
|
||||
expect(result.hunks).toHaveLength(2)
|
||||
expect(result.hunks[0].type).toBe("add")
|
||||
expect(result.hunks[1].type).toBe("update")
|
||||
})
|
||||
|
||||
|
||||
test("should parse file move operation", () => {
|
||||
const patchText = `*** Begin Patch
|
||||
*** Update File: old-name.txt
|
||||
@@ -69,7 +69,7 @@ describe("Patch namespace", () => {
|
||||
-Old content
|
||||
+New content
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = Patch.parsePatch(patchText)
|
||||
expect(result.hunks).toHaveLength(1)
|
||||
const hunk = result.hunks[0]
|
||||
@@ -79,21 +79,21 @@ describe("Patch namespace", () => {
|
||||
expect(hunk.move_path).toBe("new-name.txt")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
test("should throw error for invalid patch format", () => {
|
||||
const invalidPatch = `This is not a valid patch`
|
||||
|
||||
|
||||
expect(() => Patch.parsePatch(invalidPatch)).toThrow("Invalid patch format")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe("maybeParseApplyPatch", () => {
|
||||
test("should parse direct apply_patch command", () => {
|
||||
const patchText = `*** Begin Patch
|
||||
*** Add File: test.txt
|
||||
+Content
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = Patch.maybeParseApplyPatch(["apply_patch", patchText])
|
||||
expect(result.type).toBe(Patch.MaybeApplyPatch.Body)
|
||||
if (result.type === Patch.MaybeApplyPatch.Body) {
|
||||
@@ -101,17 +101,17 @@ describe("Patch namespace", () => {
|
||||
expect(result.args.hunks).toHaveLength(1)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
test("should parse applypatch command", () => {
|
||||
const patchText = `*** Begin Patch
|
||||
*** Add File: test.txt
|
||||
+Content
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = Patch.maybeParseApplyPatch(["applypatch", patchText])
|
||||
expect(result.type).toBe(Patch.MaybeApplyPatch.Body)
|
||||
})
|
||||
|
||||
|
||||
test("should handle bash heredoc format", () => {
|
||||
const script = `apply_patch <<'PATCH'
|
||||
*** Begin Patch
|
||||
@@ -119,20 +119,20 @@ describe("Patch namespace", () => {
|
||||
+Content
|
||||
*** End Patch
|
||||
PATCH`
|
||||
|
||||
|
||||
const result = Patch.maybeParseApplyPatch(["bash", "-lc", script])
|
||||
expect(result.type).toBe(Patch.MaybeApplyPatch.Body)
|
||||
if (result.type === Patch.MaybeApplyPatch.Body) {
|
||||
expect(result.args.hunks).toHaveLength(1)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
test("should return NotApplyPatch for non-patch commands", () => {
|
||||
const result = Patch.maybeParseApplyPatch(["echo", "hello"])
|
||||
expect(result.type).toBe(Patch.MaybeApplyPatch.NotApplyPatch)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe("applyPatch", () => {
|
||||
test("should add a new file", async () => {
|
||||
const patchText = `*** Begin Patch
|
||||
@@ -140,36 +140,39 @@ PATCH`
|
||||
+Hello World
|
||||
+This is a new file
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = await Patch.applyPatch(patchText)
|
||||
expect(result.added).toHaveLength(1)
|
||||
expect(result.modified).toHaveLength(0)
|
||||
expect(result.deleted).toHaveLength(0)
|
||||
|
||||
|
||||
const content = await fs.readFile(result.added[0], "utf-8")
|
||||
expect(content).toBe("Hello World\nThis is a new file")
|
||||
})
|
||||
|
||||
|
||||
test("should delete an existing file", async () => {
|
||||
const filePath = path.join(tempDir, "to-delete.txt")
|
||||
await fs.writeFile(filePath, "This file will be deleted")
|
||||
|
||||
|
||||
const patchText = `*** Begin Patch
|
||||
*** Delete File: ${filePath}
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = await Patch.applyPatch(patchText)
|
||||
expect(result.deleted).toHaveLength(1)
|
||||
expect(result.deleted[0]).toBe(filePath)
|
||||
|
||||
const exists = await fs.access(filePath).then(() => true).catch(() => false)
|
||||
|
||||
const exists = await fs
|
||||
.access(filePath)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
expect(exists).toBe(false)
|
||||
})
|
||||
|
||||
|
||||
test("should update an existing file", async () => {
|
||||
const filePath = path.join(tempDir, "to-update.txt")
|
||||
await fs.writeFile(filePath, "line 1\nline 2\nline 3\n")
|
||||
|
||||
|
||||
const patchText = `*** Begin Patch
|
||||
*** Update File: ${filePath}
|
||||
@@
|
||||
@@ -178,20 +181,20 @@ PATCH`
|
||||
+line 2 updated
|
||||
line 3
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = await Patch.applyPatch(patchText)
|
||||
expect(result.modified).toHaveLength(1)
|
||||
expect(result.modified[0]).toBe(filePath)
|
||||
|
||||
|
||||
const content = await fs.readFile(filePath, "utf-8")
|
||||
expect(content).toBe("line 1\nline 2 updated\nline 3\n")
|
||||
})
|
||||
|
||||
|
||||
test("should move and update a file", async () => {
|
||||
const oldPath = path.join(tempDir, "old-name.txt")
|
||||
const newPath = path.join(tempDir, "new-name.txt")
|
||||
await fs.writeFile(oldPath, "old content\n")
|
||||
|
||||
|
||||
const patchText = `*** Begin Patch
|
||||
*** Update File: ${oldPath}
|
||||
*** Move to: ${newPath}
|
||||
@@ -199,26 +202,29 @@ PATCH`
|
||||
-old content
|
||||
+new content
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = await Patch.applyPatch(patchText)
|
||||
expect(result.modified).toHaveLength(1)
|
||||
expect(result.modified[0]).toBe(newPath)
|
||||
|
||||
const oldExists = await fs.access(oldPath).then(() => true).catch(() => false)
|
||||
|
||||
const oldExists = await fs
|
||||
.access(oldPath)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
expect(oldExists).toBe(false)
|
||||
|
||||
|
||||
const newContent = await fs.readFile(newPath, "utf-8")
|
||||
expect(newContent).toBe("new content\n")
|
||||
})
|
||||
|
||||
|
||||
test("should handle multiple operations in one patch", async () => {
|
||||
const file1 = path.join(tempDir, "file1.txt")
|
||||
const file2 = path.join(tempDir, "file2.txt")
|
||||
const file3 = path.join(tempDir, "file3.txt")
|
||||
|
||||
|
||||
await fs.writeFile(file1, "content 1")
|
||||
await fs.writeFile(file2, "content 2")
|
||||
|
||||
|
||||
const patchText = `*** Begin Patch
|
||||
*** Add File: ${file3}
|
||||
+new file content
|
||||
@@ -228,95 +234,98 @@ PATCH`
|
||||
+updated content 1
|
||||
*** Delete File: ${file2}
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = await Patch.applyPatch(patchText)
|
||||
expect(result.added).toHaveLength(1)
|
||||
expect(result.modified).toHaveLength(1)
|
||||
expect(result.deleted).toHaveLength(1)
|
||||
})
|
||||
|
||||
|
||||
test("should create parent directories when adding files", async () => {
|
||||
const nestedPath = path.join(tempDir, "deep", "nested", "file.txt")
|
||||
|
||||
|
||||
const patchText = `*** Begin Patch
|
||||
*** Add File: ${nestedPath}
|
||||
+Deep nested content
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = await Patch.applyPatch(patchText)
|
||||
expect(result.added).toHaveLength(1)
|
||||
expect(result.added[0]).toBe(nestedPath)
|
||||
|
||||
const exists = await fs.access(nestedPath).then(() => true).catch(() => false)
|
||||
|
||||
const exists = await fs
|
||||
.access(nestedPath)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
expect(exists).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe("error handling", () => {
|
||||
test("should throw error when updating non-existent file", async () => {
|
||||
const nonExistent = path.join(tempDir, "does-not-exist.txt")
|
||||
|
||||
|
||||
const patchText = `*** Begin Patch
|
||||
*** Update File: ${nonExistent}
|
||||
@@
|
||||
-old line
|
||||
+new line
|
||||
*** End Patch`
|
||||
|
||||
|
||||
await expect(Patch.applyPatch(patchText)).rejects.toThrow()
|
||||
})
|
||||
|
||||
|
||||
test("should throw error when deleting non-existent file", async () => {
|
||||
const nonExistent = path.join(tempDir, "does-not-exist.txt")
|
||||
|
||||
|
||||
const patchText = `*** Begin Patch
|
||||
*** Delete File: ${nonExistent}
|
||||
*** End Patch`
|
||||
|
||||
|
||||
await expect(Patch.applyPatch(patchText)).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe("edge cases", () => {
|
||||
test("should handle empty files", async () => {
|
||||
const emptyFile = path.join(tempDir, "empty.txt")
|
||||
await fs.writeFile(emptyFile, "")
|
||||
|
||||
|
||||
const patchText = `*** Begin Patch
|
||||
*** Update File: ${emptyFile}
|
||||
@@
|
||||
+First line
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = await Patch.applyPatch(patchText)
|
||||
expect(result.modified).toHaveLength(1)
|
||||
|
||||
|
||||
const content = await fs.readFile(emptyFile, "utf-8")
|
||||
expect(content).toBe("First line\n")
|
||||
})
|
||||
|
||||
|
||||
test("should handle files with no trailing newline", async () => {
|
||||
const filePath = path.join(tempDir, "no-newline.txt")
|
||||
await fs.writeFile(filePath, "no newline")
|
||||
|
||||
|
||||
const patchText = `*** Begin Patch
|
||||
*** Update File: ${filePath}
|
||||
@@
|
||||
-no newline
|
||||
+has newline now
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = await Patch.applyPatch(patchText)
|
||||
expect(result.modified).toHaveLength(1)
|
||||
|
||||
|
||||
const content = await fs.readFile(filePath, "utf-8")
|
||||
expect(content).toBe("has newline now\n")
|
||||
})
|
||||
|
||||
|
||||
test("should handle multiple update chunks in single file", async () => {
|
||||
const filePath = path.join(tempDir, "multi-chunk.txt")
|
||||
await fs.writeFile(filePath, "line 1\nline 2\nline 3\nline 4\n")
|
||||
|
||||
|
||||
const patchText = `*** Begin Patch
|
||||
*** Update File: ${filePath}
|
||||
@@
|
||||
@@ -328,12 +337,12 @@ PATCH`
|
||||
-line 4
|
||||
+LINE 4
|
||||
*** End Patch`
|
||||
|
||||
|
||||
const result = await Patch.applyPatch(patchText)
|
||||
expect(result.modified).toHaveLength(1)
|
||||
|
||||
|
||||
const content = await fs.readFile(filePath, "utf-8")
|
||||
expect(content).toBe("line 1\nLINE 2\nline 3\nLINE 4\n")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,7 +13,9 @@ function apiError(headers?: Record<string, string>): MessageV2.APIError {
|
||||
describe("session.retry.getRetryDelayInMs", () => {
|
||||
test("doubles delay on each attempt when headers missing", () => {
|
||||
const error = apiError()
|
||||
const delays = Array.from({ length: 7 }, (_, index) => SessionRetry.getRetryDelayInMs(error, index + 1))
|
||||
const delays = Array.from({ length: 7 }, (_, index) =>
|
||||
SessionRetry.getRetryDelayInMs(error, index + 1),
|
||||
)
|
||||
expect(delays).toStrictEqual([2000, 4000, 8000, 16000, 32000, 64000, 128000])
|
||||
})
|
||||
|
||||
|
||||
@@ -24,9 +24,12 @@ test("allStructured matches command sequences", () => {
|
||||
"git status*": "allow",
|
||||
}
|
||||
expect(Wildcard.allStructured({ head: "git", tail: ["status", "--short"] }, rules)).toBe("allow")
|
||||
expect(Wildcard.allStructured({ head: "npm", tail: ["run", "build", "--watch"] }, { "npm run *": "allow" })).toBe(
|
||||
"allow",
|
||||
)
|
||||
expect(
|
||||
Wildcard.allStructured(
|
||||
{ head: "npm", tail: ["run", "build", "--watch"] },
|
||||
{ "npm run *": "allow" },
|
||||
),
|
||||
).toBe("allow")
|
||||
expect(Wildcard.allStructured({ head: "ls", tail: ["-la"] }, rules)).toBeUndefined()
|
||||
})
|
||||
|
||||
@@ -51,5 +54,7 @@ test("allStructured handles sed flags", () => {
|
||||
expect(Wildcard.allStructured({ head: "sed", tail: ["-i", "file"] }, rules)).toBe("ask")
|
||||
expect(Wildcard.allStructured({ head: "sed", tail: ["-i.bak", "file"] }, rules)).toBe("ask")
|
||||
expect(Wildcard.allStructured({ head: "sed", tail: ["-n", "1p", "file"] }, rules)).toBe("allow")
|
||||
expect(Wildcard.allStructured({ head: "sed", tail: ["-i", "-n", "/./p", "myfile.txt"] }, rules)).toBe("ask")
|
||||
expect(
|
||||
Wildcard.allStructured({ head: "sed", tail: ["-i", "-n", "/./p", "myfile.txt"] }, rules),
|
||||
).toBe("ask")
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user