grep.test.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import { describe, expect, test } from "bun:test"
  2. import path from "path"
  3. import { GrepTool } from "../../src/tool/grep"
  4. import { Instance } from "../../src/project/instance"
  5. import { tmpdir } from "../fixture/fixture"
  6. const ctx = {
  7. sessionID: "test",
  8. messageID: "",
  9. callID: "",
  10. agent: "build",
  11. abort: AbortSignal.any([]),
  12. messages: [],
  13. metadata: () => {},
  14. ask: async () => {},
  15. }
  16. const projectRoot = path.join(__dirname, "../..")
  17. describe("tool.grep", () => {
  18. test("basic search", async () => {
  19. await Instance.provide({
  20. directory: projectRoot,
  21. fn: async () => {
  22. const grep = await GrepTool.init()
  23. const result = await grep.execute(
  24. {
  25. pattern: "export",
  26. path: path.join(projectRoot, "src/tool"),
  27. include: "*.ts",
  28. },
  29. ctx,
  30. )
  31. expect(result.metadata.matches).toBeGreaterThan(0)
  32. expect(result.output).toContain("Found")
  33. },
  34. })
  35. })
  36. test("no matches returns correct output", async () => {
  37. await using tmp = await tmpdir({
  38. init: async (dir) => {
  39. await Bun.write(path.join(dir, "test.txt"), "hello world")
  40. },
  41. })
  42. await Instance.provide({
  43. directory: tmp.path,
  44. fn: async () => {
  45. const grep = await GrepTool.init()
  46. const result = await grep.execute(
  47. {
  48. pattern: "xyznonexistentpatternxyz123",
  49. path: tmp.path,
  50. },
  51. ctx,
  52. )
  53. expect(result.metadata.matches).toBe(0)
  54. expect(result.output).toBe("No files found")
  55. },
  56. })
  57. })
  58. test("handles CRLF line endings in output", async () => {
  59. // This test verifies the regex split handles both \n and \r\n
  60. await using tmp = await tmpdir({
  61. init: async (dir) => {
  62. // Create a test file with content
  63. await Bun.write(path.join(dir, "test.txt"), "line1\nline2\nline3")
  64. },
  65. })
  66. await Instance.provide({
  67. directory: tmp.path,
  68. fn: async () => {
  69. const grep = await GrepTool.init()
  70. const result = await grep.execute(
  71. {
  72. pattern: "line",
  73. path: tmp.path,
  74. },
  75. ctx,
  76. )
  77. expect(result.metadata.matches).toBeGreaterThan(0)
  78. },
  79. })
  80. })
  81. })
  82. describe("CRLF regex handling", () => {
  83. test("regex correctly splits Unix line endings", () => {
  84. const unixOutput = "file1.txt|1|content1\nfile2.txt|2|content2\nfile3.txt|3|content3"
  85. const lines = unixOutput.trim().split(/\r?\n/)
  86. expect(lines.length).toBe(3)
  87. expect(lines[0]).toBe("file1.txt|1|content1")
  88. expect(lines[2]).toBe("file3.txt|3|content3")
  89. })
  90. test("regex correctly splits Windows CRLF line endings", () => {
  91. const windowsOutput = "file1.txt|1|content1\r\nfile2.txt|2|content2\r\nfile3.txt|3|content3"
  92. const lines = windowsOutput.trim().split(/\r?\n/)
  93. expect(lines.length).toBe(3)
  94. expect(lines[0]).toBe("file1.txt|1|content1")
  95. expect(lines[2]).toBe("file3.txt|3|content3")
  96. })
  97. test("regex handles mixed line endings", () => {
  98. const mixedOutput = "file1.txt|1|content1\nfile2.txt|2|content2\r\nfile3.txt|3|content3"
  99. const lines = mixedOutput.trim().split(/\r?\n/)
  100. expect(lines.length).toBe(3)
  101. })
  102. })