tool-chain.test.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { describe, expect, test } from "bun:test";
  2. import { getAllBaseTools, parseToolPreset, getTools } from "../../src/tools.ts";
  3. import {
  4. findToolByName,
  5. getEmptyToolPermissionContext,
  6. buildTool,
  7. } from "../../src/Tool.ts";
  8. // ─── Tool Registration & Discovery ──────────────────────────────────────
  9. describe("Tool chain: registration and discovery", () => {
  10. test("getAllBaseTools returns a non-empty array of tools", () => {
  11. const tools = getAllBaseTools();
  12. expect(tools.length).toBeGreaterThan(0);
  13. });
  14. test("all base tools have required fields", () => {
  15. const tools = getAllBaseTools();
  16. for (const tool of tools) {
  17. expect(tool.name).toBeTruthy();
  18. expect(tool.description).toBeTruthy();
  19. expect(tool.inputSchema).toBeDefined();
  20. expect(typeof tool.call).toBe("function");
  21. }
  22. });
  23. test("findToolByName finds core tools from the full list", () => {
  24. const tools = getAllBaseTools();
  25. const bash = findToolByName(tools, "Bash");
  26. expect(bash).toBeDefined();
  27. expect(bash!.name).toBe("Bash");
  28. const read = findToolByName(tools, "Read");
  29. expect(read).toBeDefined();
  30. expect(read!.name).toBe("Read");
  31. const edit = findToolByName(tools, "Edit");
  32. expect(edit).toBeDefined();
  33. expect(edit!.name).toBe("Edit");
  34. });
  35. test("findToolByName returns undefined for non-existent tool", () => {
  36. const tools = getAllBaseTools();
  37. expect(findToolByName(tools, "NonExistentTool")).toBeUndefined();
  38. });
  39. test("findToolByName is case-sensitive (exact match only)", () => {
  40. const tools = getAllBaseTools();
  41. expect(findToolByName(tools, "Bash")).toBeDefined();
  42. expect(findToolByName(tools, "bash")).toBeUndefined();
  43. });
  44. test("findToolByName resolves via toolMatchesName", () => {
  45. const tools = getAllBaseTools();
  46. const agent = findToolByName(tools, "Agent");
  47. expect(agent).toBeDefined();
  48. // Verify it can also find by checking name directly
  49. expect(tools.some(t => t.name === "Agent")).toBe(true);
  50. });
  51. test("tool names are unique across the base tool list", () => {
  52. const tools = getAllBaseTools();
  53. const names = tools.map(t => t.name);
  54. expect(new Set(names).size).toBe(names.length);
  55. });
  56. });
  57. // ─── Tool Presets ──────────────────────────────────────────────────────
  58. describe("Tool chain: presets", () => {
  59. test('parseToolPreset("default") returns "default" string', () => {
  60. // parseToolPreset returns a preset name string, not a tool array
  61. expect(parseToolPreset("default")).toBe("default");
  62. });
  63. test("parseToolPreset returns null for unknown preset", () => {
  64. expect(parseToolPreset("nonexistent")).toBeNull();
  65. });
  66. test("parseToolPreset is case-insensitive", () => {
  67. expect(parseToolPreset("DEFAULT")).toBe("default");
  68. });
  69. });
  70. // ─── getTools (with permission context) ────────────────────────────────
  71. describe("Tool chain: getTools with context", () => {
  72. test("getTools returns tools (subset of base tools)", () => {
  73. const allTools = getAllBaseTools();
  74. const ctx = getEmptyToolPermissionContext();
  75. const tools = getTools(ctx);
  76. expect(tools.length).toBeGreaterThan(0);
  77. expect(tools.length).toBeLessThanOrEqual(allTools.length);
  78. });
  79. test("getTools results all have name and call function", () => {
  80. const ctx = getEmptyToolPermissionContext();
  81. const tools = getTools(ctx);
  82. for (const tool of tools) {
  83. expect(tool.name).toBeTruthy();
  84. expect(typeof tool.call).toBe("function");
  85. }
  86. });
  87. });
  88. // ─── buildTool + findToolByName end-to-end ─────────────────────────────
  89. describe("Tool chain: buildTool + findToolByName", () => {
  90. test("a built tool can be found in a custom list", () => {
  91. const customTool = buildTool({
  92. name: "TestTool",
  93. description: "A test tool",
  94. inputSchema: {
  95. type: "object" as const,
  96. properties: { input: { type: "string" } },
  97. required: ["input"],
  98. },
  99. call: async () => ({ output: "test" }),
  100. });
  101. const found = findToolByName([customTool], "TestTool");
  102. expect(found).toBe(customTool);
  103. });
  104. test("built tool defaults are correctly applied", () => {
  105. const tool = buildTool({
  106. name: "MinimalTool",
  107. description: "Minimal",
  108. inputSchema: {
  109. type: "object" as const,
  110. properties: {},
  111. },
  112. call: async () => ({}),
  113. });
  114. expect(tool.isEnabled()).toBe(true);
  115. expect(tool.isConcurrencySafe()).toBe(false);
  116. expect(tool.isReadOnly()).toBe(false);
  117. expect(tool.isDestructive()).toBe(false);
  118. });
  119. });