permission.shared.test.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import { describe, expect, test } from "bun:test"
  2. import type { PermissionRequest } from "@kirincode-ai/sdk/v2"
  3. import {
  4. createPermissionBodyState,
  5. permissionAlwaysLines,
  6. permissionCancel,
  7. permissionEscape,
  8. permissionInfo,
  9. permissionReject,
  10. permissionRun,
  11. } from "@/cli/cmd/run/permission.shared"
  12. function req(input: Partial<PermissionRequest> = {}): PermissionRequest {
  13. return {
  14. id: "perm-1",
  15. sessionID: "session-1",
  16. permission: "read",
  17. patterns: [],
  18. metadata: {},
  19. always: [],
  20. ...input,
  21. }
  22. }
  23. describe("run permission shared", () => {
  24. test("replies immediately for allow once", () => {
  25. const out = permissionRun(createPermissionBodyState("perm-1"), "perm-1", "once")
  26. expect(out.reply).toEqual({
  27. requestID: "perm-1",
  28. reply: "once",
  29. })
  30. })
  31. test("requires confirmation for allow always", () => {
  32. const next = permissionRun(createPermissionBodyState("perm-1"), "perm-1", "always")
  33. expect(next.state.stage).toBe("always")
  34. expect(next.state.selected).toBe("confirm")
  35. expect(next.reply).toBeUndefined()
  36. expect(permissionRun(next.state, "perm-1", "confirm").reply).toEqual({
  37. requestID: "perm-1",
  38. reply: "always",
  39. })
  40. expect(permissionRun(next.state, "perm-1", "cancel").state).toMatchObject({
  41. stage: "permission",
  42. selected: "always",
  43. })
  44. })
  45. test("builds trimmed reject replies and stage transitions", () => {
  46. const next = permissionRun(createPermissionBodyState("perm-1"), "perm-1", "reject")
  47. expect(next.state.stage).toBe("reject")
  48. const out = permissionReject({ ...next.state, message: " use rg " }, "perm-1")
  49. expect(out).toEqual({
  50. requestID: "perm-1",
  51. reply: "reject",
  52. message: "use rg",
  53. })
  54. expect(permissionCancel(next.state)).toMatchObject({
  55. stage: "permission",
  56. selected: "reject",
  57. })
  58. expect(permissionEscape(createPermissionBodyState("perm-1"))).toMatchObject({
  59. stage: "reject",
  60. selected: "reject",
  61. })
  62. expect(permissionEscape({ ...next.state, stage: "always", selected: "confirm" })).toMatchObject({
  63. stage: "permission",
  64. selected: "always",
  65. })
  66. })
  67. test("maps supported permission types into display info", () => {
  68. expect(
  69. permissionInfo(
  70. req({
  71. permission: "bash",
  72. metadata: {
  73. input: {
  74. command: "git status --short",
  75. },
  76. },
  77. }),
  78. ),
  79. ).toMatchObject({
  80. title: "Shell command",
  81. lines: ["$ git status --short"],
  82. })
  83. expect(
  84. permissionInfo(
  85. req({
  86. permission: "task",
  87. metadata: {
  88. description: "investigate stream",
  89. subagent_type: "general",
  90. },
  91. }),
  92. ),
  93. ).toMatchObject({
  94. title: "General Task",
  95. lines: ["◉ investigate stream"],
  96. })
  97. expect(
  98. permissionInfo(
  99. req({
  100. permission: "external_directory",
  101. patterns: ["/tmp/work/**/*.ts", "/tmp/work/**/*.tsx"],
  102. }),
  103. ),
  104. ).toMatchObject({
  105. title: "Access external directory /tmp/work",
  106. lines: ["- /tmp/work/**/*.ts", "- /tmp/work/**/*.tsx"],
  107. })
  108. expect(permissionInfo(req({ permission: "doom_loop" }))).toMatchObject({
  109. title: "Continue after repeated failures",
  110. })
  111. expect(permissionInfo(req({ permission: "custom_tool" }))).toMatchObject({
  112. title: "Call tool custom_tool",
  113. lines: ["Tool: custom_tool"],
  114. })
  115. })
  116. test("formats always-allow copy for wildcard and explicit patterns", () => {
  117. expect(permissionAlwaysLines(req({ permission: "bash", always: ["*"] }))).toEqual([
  118. "This will allow bash until KirinCode is restarted.",
  119. ])
  120. expect(permissionAlwaysLines(req({ always: ["src/**/*.ts", "src/**/*.tsx"] }))).toEqual([
  121. "This will allow the following patterns until KirinCode is restarted.",
  122. "- src/**/*.ts",
  123. "- src/**/*.tsx",
  124. ])
  125. })
  126. })