shell-parse.test.ts 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import { describe, expect, test } from "bun:test"
  2. import { Effect } from "effect"
  3. import os from "os"
  4. import path from "path"
  5. import { ShellParse } from "@opencode-ai/core/shell/parse"
  6. describe("ShellParse", () => {
  7. test("splits bash commands and derives reusable prefixes", async () => {
  8. const result = await Effect.runPromise(
  9. ShellParse.scan("git status && npm run test -- --watch", "/bin/bash", "/workspace"),
  10. )
  11. expect(result).toEqual({
  12. commands: [
  13. { resource: "git status", save: "git status *" },
  14. { resource: "npm run test -- --watch", save: "npm run test *" },
  15. ],
  16. directories: [],
  17. })
  18. })
  19. test("portable scanning never adds permission resources", async () => {
  20. const commands = [
  21. "git status && npm run test -- --watch",
  22. "echo $(curl evil | sed s/x/y/)",
  23. "cat <<'EOF'\nstatic body\nEOF",
  24. "cat <<EOF\n$(printf dynamic)\nEOF",
  25. "cd /tmp/$USER && git status",
  26. "$COMMAND status",
  27. "if true; then printf yes; else printf no; fi",
  28. ]
  29. for (const command of commands) {
  30. const legacy = await Effect.runPromise(ShellParse.scan(command, "/bin/bash", "/workspace"))
  31. const portable = await Effect.runPromise(ShellParse.scan(command, "/bin/bash", "/workspace", { portable: true }))
  32. expect(
  33. portable.commands.every((item) => legacy.commands.some((candidate) => candidate.resource === item.resource)),
  34. ).toBe(true)
  35. expect(portable.directories.every((item) => legacy.directories.includes(item))).toBe(true)
  36. }
  37. })
  38. test("portable scanning authorizes opaque heredocs without inferring directories", async () => {
  39. const command = "cat <<'EOF'\nstatic body\nEOF"
  40. const portable = await Effect.runPromise(ShellParse.scan(command, "/bin/bash", "/workspace", { portable: true }))
  41. expect(portable).toEqual({ commands: [{ resource: command, save: command }], directories: [] })
  42. })
  43. test.each(['c"\\d" relative', "'cd' /tmp", "c''d /tmp", "c\\\nd /tmp"])(
  44. "portable scanning keeps source-shaped command heads under shell authorization: %s",
  45. async (command) => {
  46. const portable = await Effect.runPromise(ShellParse.scan(command, "/bin/bash", "/workspace", { portable: true }))
  47. expect(portable.commands.map((item) => item.resource)).toEqual([command])
  48. expect(portable.directories).toEqual([])
  49. },
  50. )
  51. test("splits PowerShell commands case-insensitively", async () => {
  52. const result = await Effect.runPromise(
  53. ShellParse.scan(
  54. "Get-ChildItem; Write-Output 'done'",
  55. "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
  56. "C:\\workspace",
  57. ),
  58. )
  59. expect(result.commands).toEqual([
  60. { resource: "Get-ChildItem", save: "Get-ChildItem *" },
  61. { resource: "Write-Output 'done'", save: "Write-Output *" },
  62. ])
  63. })
  64. test("does not permission directory changes separately", async () => {
  65. const result = await Effect.runPromise(ShellParse.scan("cd 'src dir' && git status", "/bin/bash", "/workspace"))
  66. expect(result).toEqual({
  67. commands: [{ resource: "git status", save: "git status *" }],
  68. directories: ["src dir"],
  69. })
  70. })
  71. test("extracts PowerShell directory parameters", async () => {
  72. const result = await Effect.runPromise(
  73. ShellParse.scan("Set-Location -LiteralPath '..\\outside'; Get-ChildItem", "pwsh", "C:\\workspace"),
  74. )
  75. expect(result.directories).toEqual(["..\\outside"])
  76. })
  77. test("expands deterministic directory variables", async () => {
  78. const bash = await Effect.runPromise(ShellParse.scan("cd ~/src", "/bin/bash", "/workspace"))
  79. expect(bash.directories).toEqual([path.join(os.homedir(), "src")])
  80. const powershell = await Effect.runPromise(
  81. ShellParse.scan('Set-Location "$PWD/src"; Set-Location $PSHOME', "/usr/local/bin/pwsh", "/workspace"),
  82. )
  83. expect(powershell.directories).toEqual(["/workspace/src", "/usr/local/bin"])
  84. })
  85. })