grep.test.ts 3.1 KB

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