patch.test.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import { describe, expect, test } from "bun:test"
  2. import path from "path"
  3. import { PatchTool } from "../../src/tool/patch"
  4. import { Instance } from "../../src/project/instance"
  5. import { tmpdir } from "../fixture/fixture"
  6. import * as fs from "fs/promises"
  7. const ctx = {
  8. sessionID: "test",
  9. messageID: "",
  10. toolCallID: "",
  11. agent: "build",
  12. abort: AbortSignal.any([]),
  13. metadata: () => {},
  14. }
  15. const patchTool = await PatchTool.init()
  16. describe("tool.patch", () => {
  17. test("should validate required parameters", async () => {
  18. await Instance.provide({
  19. directory: "/tmp",
  20. fn: async () => {
  21. await expect(patchTool.execute({ patchText: "" }, ctx)).rejects.toThrow(
  22. "patchText is required",
  23. )
  24. },
  25. })
  26. })
  27. test("should validate patch format", async () => {
  28. await Instance.provide({
  29. directory: "/tmp",
  30. fn: async () => {
  31. await expect(patchTool.execute({ patchText: "invalid patch" }, ctx)).rejects.toThrow(
  32. "Failed to parse patch",
  33. )
  34. },
  35. })
  36. })
  37. test("should handle empty patch", async () => {
  38. await Instance.provide({
  39. directory: "/tmp",
  40. fn: async () => {
  41. const emptyPatch = `*** Begin Patch
  42. *** End Patch`
  43. await expect(patchTool.execute({ patchText: emptyPatch }, ctx)).rejects.toThrow(
  44. "No file changes found in patch",
  45. )
  46. },
  47. })
  48. })
  49. test("should handle simple add file operation", async () => {
  50. await using fixture = await tmpdir()
  51. await Instance.provide({
  52. directory: fixture.path,
  53. fn: async () => {
  54. const patchText = `*** Begin Patch
  55. *** Add File: test-file.txt
  56. +Hello World
  57. +This is a test file
  58. *** End Patch`
  59. const result = await patchTool.execute({ patchText }, ctx)
  60. expect(result.title).toContain("files changed")
  61. expect(result.metadata.diff).toBeDefined()
  62. expect(result.output).toContain("Patch applied successfully")
  63. // Verify file was created
  64. const filePath = path.join(fixture.path, "test-file.txt")
  65. const content = await fs.readFile(filePath, "utf-8")
  66. expect(content).toBe("Hello World\nThis is a test file")
  67. },
  68. })
  69. })
  70. test("should handle file with context update", async () => {
  71. await using fixture = await tmpdir()
  72. await Instance.provide({
  73. directory: fixture.path,
  74. fn: async () => {
  75. const patchText = `*** Begin Patch
  76. *** Add File: config.js
  77. +const API_KEY = "test-key"
  78. +const DEBUG = false
  79. +const VERSION = "1.0"
  80. *** End Patch`
  81. const result = await patchTool.execute({ patchText }, ctx)
  82. expect(result.title).toContain("files changed")
  83. expect(result.metadata.diff).toBeDefined()
  84. expect(result.output).toContain("Patch applied successfully")
  85. // Verify file was created with correct content
  86. const filePath = path.join(fixture.path, "config.js")
  87. const content = await fs.readFile(filePath, "utf-8")
  88. expect(content).toBe(
  89. 'const API_KEY = "test-key"\nconst DEBUG = false\nconst VERSION = "1.0"',
  90. )
  91. },
  92. })
  93. })
  94. test("should handle multiple file operations", async () => {
  95. await using fixture = await tmpdir()
  96. await Instance.provide({
  97. directory: fixture.path,
  98. fn: async () => {
  99. const patchText = `*** Begin Patch
  100. *** Add File: file1.txt
  101. +Content of file 1
  102. *** Add File: file2.txt
  103. +Content of file 2
  104. *** Add File: file3.txt
  105. +Content of file 3
  106. *** End Patch`
  107. const result = await patchTool.execute({ patchText }, ctx)
  108. expect(result.title).toContain("3 files changed")
  109. expect(result.metadata.diff).toBeDefined()
  110. expect(result.output).toContain("Patch applied successfully")
  111. // Verify all files were created
  112. for (let i = 1; i <= 3; i++) {
  113. const filePath = path.join(fixture.path, `file${i}.txt`)
  114. const content = await fs.readFile(filePath, "utf-8")
  115. expect(content).toBe(`Content of file ${i}`)
  116. }
  117. },
  118. })
  119. })
  120. test("should create parent directories when adding nested files", async () => {
  121. await using fixture = await tmpdir()
  122. await Instance.provide({
  123. directory: fixture.path,
  124. fn: async () => {
  125. const patchText = `*** Begin Patch
  126. *** Add File: deep/nested/file.txt
  127. +Deep nested content
  128. *** End Patch`
  129. const result = await patchTool.execute({ patchText }, ctx)
  130. expect(result.title).toContain("files changed")
  131. expect(result.output).toContain("Patch applied successfully")
  132. // Verify nested file was created
  133. const nestedPath = path.join(fixture.path, "deep", "nested", "file.txt")
  134. const exists = await fs
  135. .access(nestedPath)
  136. .then(() => true)
  137. .catch(() => false)
  138. expect(exists).toBe(true)
  139. const content = await fs.readFile(nestedPath, "utf-8")
  140. expect(content).toBe("Deep nested content")
  141. },
  142. })
  143. })
  144. test("should generate proper unified diff in metadata", async () => {
  145. await using fixture = await tmpdir()
  146. await Instance.provide({
  147. directory: fixture.path,
  148. fn: async () => {
  149. // First create a file with simple content
  150. const patchText1 = `*** Begin Patch
  151. *** Add File: test.txt
  152. +line 1
  153. +line 2
  154. +line 3
  155. *** End Patch`
  156. await patchTool.execute({ patchText: patchText1 }, ctx)
  157. // Now create an update patch
  158. const patchText2 = `*** Begin Patch
  159. *** Update File: test.txt
  160. @@
  161. line 1
  162. -line 2
  163. +line 2 updated
  164. line 3
  165. *** End Patch`
  166. const result = await patchTool.execute({ patchText: patchText2 }, ctx)
  167. expect(result.metadata.diff).toBeDefined()
  168. expect(result.metadata.diff).toContain("@@")
  169. expect(result.metadata.diff).toContain("-line 2")
  170. expect(result.metadata.diff).toContain("+line 2 updated")
  171. },
  172. })
  173. })
  174. test("should handle complex patch with multiple operations", async () => {
  175. await using fixture = await tmpdir()
  176. await Instance.provide({
  177. directory: fixture.path,
  178. fn: async () => {
  179. const patchText = `*** Begin Patch
  180. *** Add File: new.txt
  181. +This is a new file
  182. +with multiple lines
  183. *** Add File: existing.txt
  184. +old content
  185. +new line
  186. +more content
  187. *** Add File: config.json
  188. +{
  189. + "version": "1.0",
  190. + "debug": true
  191. +}
  192. *** End Patch`
  193. const result = await patchTool.execute({ patchText }, ctx)
  194. expect(result.title).toContain("3 files changed")
  195. expect(result.metadata.diff).toBeDefined()
  196. expect(result.output).toContain("Patch applied successfully")
  197. // Verify all files were created
  198. const newPath = path.join(fixture.path, "new.txt")
  199. const newContent = await fs.readFile(newPath, "utf-8")
  200. expect(newContent).toBe("This is a new file\nwith multiple lines")
  201. const existingPath = path.join(fixture.path, "existing.txt")
  202. const existingContent = await fs.readFile(existingPath, "utf-8")
  203. expect(existingContent).toBe("old content\nnew line\nmore content")
  204. const configPath = path.join(fixture.path, "config.json")
  205. const configContent = await fs.readFile(configPath, "utf-8")
  206. expect(configContent).toBe('{\n "version": "1.0",\n "debug": true\n}')
  207. },
  208. })
  209. })
  210. })