macros.test.ts•8.25 kB
import { test, expect, describe } from "./test-fixtures.js";
describe("Macro Functionality Tests", () => {
test("should successfully load and execute a valid macro", async ({ client }) => {
const result = await client.callTool({
name: "invoke",
arguments: {
calls: [
{
server: "macros",
tool: "addNumbers",
parameters: { a: 5, b: 3 },
},
],
},
});
const content = result.content as Array<{ type: string; text: string }>;
const text = content[0]?.text || "";
expect(text).toContain("result");
expect(text).toContain("8");
});
test("should derive macro name from filename (kebab-case to camelCase)", async ({ client }) => {
const result = await client.callTool({
name: "list_macros",
arguments: {},
});
const content = result.content as Array<{ type: string; text: string }>;
const data = JSON.parse(content[0]?.text || "{}");
const macroNames = data.macros.map((m: { name: string }) => m.name);
// add-numbers.ts -> addNumbers
expect(macroNames).toContain("addNumbers");
});
test("should validate macro arguments with Zod schema", async ({ client }) => {
// Try to call addNumbers with wrong type (string instead of number)
const result = await client.callTool({
name: "invoke",
arguments: {
calls: [
{
server: "macros",
tool: "addNumbers",
parameters: { a: "not a number", b: 3 },
},
],
},
});
const content = result.content as Array<{ type: string; text: string }>;
const text = content[0]?.text || "";
// Should contain validation error
expect(text).toContain("Error");
});
test("should handle optional parameters correctly", async ({ client }) => {
// greet macro has optional 'greeting' parameter
const result = await client.callTool({
name: "invoke",
arguments: {
calls: [
{
server: "macros",
tool: "greet",
parameters: { name: "World" },
},
],
},
});
const content = result.content as Array<{ type: string; text: string }>;
const text = content[0]?.text || "";
// Should use default greeting "Hello"
expect(text).toContain("Hello");
expect(text).toContain("World");
});
test("should use custom optional parameter when provided", async ({ client }) => {
const result = await client.callTool({
name: "invoke",
arguments: {
calls: [
{
server: "macros",
tool: "greet",
parameters: { name: "World", greeting: "Hi" },
},
],
},
});
const content = result.content as Array<{ type: string; text: string }>;
const text = content[0]?.text || "";
// Should use custom greeting
expect(text).toContain("Hi");
expect(text).toContain("World");
});
test("should include macros with syntax errors in error list", async ({ client }) => {
const result = await client.callTool({
name: "list_macros",
arguments: {},
});
const content = result.content as Array<{ type: string; text: string }>;
const data = JSON.parse(content[0]?.text || "{}");
// Should have an errors array
expect(data.errors).toBeDefined();
expect(Array.isArray(data.errors)).toBe(true);
// Should have at least one error for broken-syntax.ts
const brokenSyntaxError = data.errors.find((e: any) => e.name === "brokenSyntax");
expect(brokenSyntaxError).toBeDefined();
expect(brokenSyntaxError.error).toBeDefined();
});
test("should include macros that don't call defineMacro in error list", async ({ client }) => {
const result = await client.callTool({
name: "list_macros",
arguments: {},
});
const content = result.content as Array<{ type: string; text: string }>;
const data = JSON.parse(content[0]?.text || "{}");
// Should have error for no-define.ts
const noDefineError = data.errors.find((e: any) => e.name === "noDefine");
expect(noDefineError).toBeDefined();
expect(noDefineError.error).toContain("did not call defineMacro");
});
test("should throw error when executing macro that has runtime error", async ({ client }) => {
const result = await client.callTool({
name: "invoke",
arguments: {
calls: [
{
server: "macros",
tool: "runtimeError",
parameters: { value: "test" },
},
],
},
});
const content = result.content as Array<{ type: string; text: string }>;
const text = content[0]?.text || "";
// Should contain error message
expect(text).toContain("Error");
expect(text).toContain("Intentional runtime error");
});
test("should list only successfully loaded macros in macros array", async ({ client }) => {
const result = await client.callTool({
name: "list_macros",
arguments: {},
});
const content = result.content as Array<{ type: string; text: string }>;
const data = JSON.parse(content[0]?.text || "{}");
// Only successfully loaded macros should be in the macros array
const macroNames = data.macros.map((m: { name: string }) => m.name);
// These should be present (valid macros)
expect(macroNames).toContain("addNumbers");
expect(macroNames).toContain("greet");
expect(macroNames).toContain("runtimeError"); // Loads successfully, fails at runtime
// These should NOT be in macros array (they failed to load)
expect(macroNames).not.toContain("brokenSyntax");
expect(macroNames).not.toContain("noDefine");
});
test("should provide informative error messages", async ({ client }) => {
const result = await client.callTool({
name: "list_macros",
arguments: {},
});
const content = result.content as Array<{ type: string; text: string }>;
const data = JSON.parse(content[0]?.text || "{}");
// Check that errors have necessary fields
for (const error of data.errors) {
expect(error.name).toBeDefined();
expect(error.path).toBeDefined();
expect(error.error).toBeDefined();
expect(typeof error.name).toBe("string");
expect(typeof error.path).toBe("string");
expect(typeof error.error).toBe("string");
}
});
test("should load macros from multiple roots", async ({ client }) => {
const result = await client.callTool({
name: "list_macros",
arguments: {},
});
const content = result.content as Array<{ type: string; text: string }>;
const data = JSON.parse(content[0]?.text || "{}");
const macroNames = data.macros.map((m: { name: string }) => m.name);
// addNumbers is from root1, greet is from root2
expect(macroNames).toContain("addNumbers");
expect(macroNames).toContain("greet");
// Verify they have different paths
const addNumbers = data.macros.find((m: any) => m.name === "addNumbers");
const greet = data.macros.find((m: any) => m.name === "greet");
expect(addNumbers.modulePath).toContain("root1");
expect(greet.modulePath).toContain("root2");
});
test("should include description in macro definition", async ({ client }) => {
const result = await client.callTool({
name: "list_macros",
arguments: {},
});
const content = result.content as Array<{ type: string; text: string }>;
const data = JSON.parse(content[0]?.text || "{}");
const addNumbers = data.macros.find((m: any) => m.name === "addNumbers");
expect(addNumbers.description).toBe("Add two numbers together");
});
test("should throw error when invoking non-existent macro", async ({ client }) => {
const result = await client.callTool({
name: "invoke",
arguments: {
calls: [
{
server: "macros",
tool: "nonExistentMacro",
parameters: {},
},
],
},
});
const content = result.content as Array<{ type: string; text: string }>;
const text = content[0]?.text || "";
expect(text).toContain("Error");
expect(text).toContain("not found");
});
});