import { ExecutionStep, HttpMethod, PaginationType, RunStatus, Tool } from "@superglue/shared";
import { describe, expect, it } from "vitest";
import { ApiConfig } from "../../shared/types.js";
import {
buildRunResponse,
mapFailureBehavior,
mapPaginationType,
mapStepToOpenAPI,
mapToolToOpenAPI,
} from "./tools.js";
describe("tools API helpers", () => {
describe("mapPaginationType", () => {
it("should map all known pagination types", () => {
expect(mapPaginationType("OFFSET_BASED")).toBe("offsetBased");
expect(mapPaginationType("PAGE_BASED")).toBe("pageBased");
expect(mapPaginationType("CURSOR_BASED")).toBe("cursorBased");
expect(mapPaginationType("DISABLED")).toBe("disabled");
});
it("should return disabled for undefined input", () => {
expect(mapPaginationType(undefined)).toBe("disabled");
});
it("should lowercase unknown types", () => {
expect(mapPaginationType("CUSTOM_TYPE")).toBe("custom_type");
});
});
describe("mapFailureBehavior", () => {
it("should map fail behavior", () => {
expect(mapFailureBehavior("FAIL")).toBe("fail");
expect(mapFailureBehavior("fail")).toBe("fail");
});
it("should map continue behavior", () => {
expect(mapFailureBehavior("CONTINUE")).toBe("continue");
expect(mapFailureBehavior("continue")).toBe("continue");
});
it("should return undefined for undefined input", () => {
expect(mapFailureBehavior(undefined)).toBeUndefined();
});
});
describe("mapStepToOpenAPI", () => {
const baseStep: ExecutionStep = {
id: "step-1",
apiConfig: {
urlHost: "https://api.example.com",
urlPath: "/users",
method: HttpMethod.GET,
instruction: "Fetch users",
id: "step-1",
},
};
it("should map basic step fields", () => {
const result = mapStepToOpenAPI(baseStep);
expect(result.id).toBe("step-1");
expect(result.url).toBe("https://api.example.com/users");
expect(result.method).toBe("GET");
});
it("should default method to GET", () => {
const stepWithoutMethod: ExecutionStep = {
id: "step-1",
apiConfig: {
id: "step-1",
instruction: "Fetch users",
urlHost: "https://api.example.com",
urlPath: "/users",
},
};
const result = mapStepToOpenAPI(stepWithoutMethod);
expect(result.method).toBe("GET");
});
it("should include optional fields when present", () => {
const stepWithOptionals: ExecutionStep = {
...baseStep,
apiConfig: {
...baseStep.apiConfig,
queryParams: { search: "test" },
headers: { "X-Api-Key": "secret" },
body: '{"name": "test"}',
instruction: "Fetch users",
},
integrationId: "integration-123",
modify: true,
loopSelector: "$.data[*]",
failureBehavior: "CONTINUE",
};
const result = mapStepToOpenAPI(stepWithOptionals);
expect(result.queryParams).toEqual({ search: "test" });
expect(result.headers).toEqual({ "X-Api-Key": "secret" });
expect(result.body).toBe('{"name": "test"}');
expect(result.instruction).toBe("Fetch users");
expect(result.systemId).toBe("integration-123");
expect(result.modify).toBe(true);
expect(result.dataSelector).toBe("$.data[*]");
expect(result.failureBehavior).toBe("continue");
});
it("should map pagination config", () => {
const stepWithPagination: ExecutionStep = {
...baseStep,
apiConfig: {
...baseStep.apiConfig,
pagination: {
type: PaginationType.CURSOR_BASED,
pageSize: "50",
cursorPath: "$.nextCursor",
stopCondition: "$.hasMore === false",
},
},
};
const result = mapStepToOpenAPI(stepWithPagination);
expect(result.pagination).toEqual({
type: "cursorBased",
pageSize: "50",
cursorPath: "$.nextCursor",
stopCondition: "$.hasMore === false",
});
});
it("should handle empty urlHost and urlPath", () => {
const stepWithEmptyUrl: ExecutionStep = {
id: "step-1",
apiConfig: {} as ApiConfig,
};
const result = mapStepToOpenAPI(stepWithEmptyUrl);
expect(result.url).toBe("");
});
});
describe("mapToolToOpenAPI", () => {
const baseTool: Tool = {
id: "tool-123",
instruction: "Test tool instruction",
inputSchema: { type: "object", properties: { name: { type: "string" } } },
responseSchema: { type: "object", properties: { result: { type: "string" } } },
steps: [
{
id: "step-1",
apiConfig: {
urlHost: "https://api.example.com",
urlPath: "/test",
method: HttpMethod.POST,
instruction: "Test step instruction",
id: "step-1",
},
},
],
createdAt: new Date("2024-01-01T00:00:00Z"),
updatedAt: new Date("2024-01-02T00:00:00Z"),
};
it("should map basic tool fields", () => {
const result = mapToolToOpenAPI(baseTool);
expect(result.id).toBe("tool-123");
expect(result.name).toBe("tool-123");
expect(result.instruction).toBe("Test tool instruction");
expect(result.inputSchema).toEqual({
type: "object",
properties: { name: { type: "string" } },
});
expect(result.outputSchema).toEqual({
type: "object",
properties: { result: { type: "string" } },
});
});
it("should default version to 1.0.0", () => {
const result = mapToolToOpenAPI(baseTool);
expect(result.version).toBe("1.0.0");
});
it("should use provided version", () => {
const toolWithVersion = { ...baseTool, version: "2.0.0" };
const result = mapToolToOpenAPI(toolWithVersion);
expect(result.version).toBe("2.0.0");
});
it("should map steps", () => {
const result = mapToolToOpenAPI(baseTool);
expect(result.steps).toHaveLength(1);
expect(result.steps[0].id).toBe("step-1");
expect(result.steps[0].url).toBe("https://api.example.com/test");
});
it("should include finalTransform as outputTransform", () => {
const toolWithTransform = { ...baseTool, finalTransform: "$.data" };
const result = mapToolToOpenAPI(toolWithTransform);
expect(result.outputTransform).toBe("$.data");
});
it("should format dates as ISO strings", () => {
const result = mapToolToOpenAPI(baseTool);
expect(result.createdAt).toBe("2024-01-01T00:00:00.000Z");
expect(result.updatedAt).toBe("2024-01-02T00:00:00.000Z");
});
it("should handle string dates", () => {
const toolWithStringDates = {
...baseTool,
createdAt: "2024-01-01T00:00:00Z" as unknown as Date,
updatedAt: "2024-01-02T00:00:00Z" as unknown as Date,
};
const result = mapToolToOpenAPI(toolWithStringDates);
expect(result.createdAt).toBe("2024-01-01T00:00:00Z");
expect(result.updatedAt).toBe("2024-01-02T00:00:00Z");
});
});
describe("buildRunResponse", () => {
const baseTool: Tool = {
id: "tool-123",
instruction: "Test tool",
steps: [],
};
const baseParams = {
runId: "run-456",
tool: baseTool,
status: RunStatus.SUCCESS,
requestSource: "api",
startedAt: new Date("2024-01-01T10:00:00Z"),
};
it("should build basic run response", () => {
const result = buildRunResponse(baseParams);
expect(result.runId).toBe("run-456");
expect(result.toolId).toBe("tool-123");
expect(result.tool).toEqual({ id: "tool-123", version: "1.0.0" });
expect(result.status).toBe("success");
expect(result.requestSource).toBe("api");
});
it("should include metadata with startedAt", () => {
const result = buildRunResponse(baseParams);
expect(result.metadata.startedAt).toBe("2024-01-01T10:00:00.000Z");
expect(result.metadata.completedAt).toBeUndefined();
expect(result.metadata.durationMs).toBeUndefined();
});
it("should calculate duration when completedAt is provided", () => {
const result = buildRunResponse({
...baseParams,
completedAt: new Date("2024-01-01T10:01:00Z"),
});
expect(result.metadata.completedAt).toBe("2024-01-01T10:01:00.000Z");
expect(result.metadata.durationMs).toBe(60000);
});
it("should include optional fields", () => {
const result = buildRunResponse({
...baseParams,
toolPayload: { input: "test" },
data: { output: "result" },
error: "Some error",
traceId: "trace-789",
options: { timeout: 30000 },
});
expect(result.toolPayload).toEqual({ input: "test" });
expect(result.data).toEqual({ output: "result" });
expect(result.error).toBe("Some error");
expect(result.traceId).toBe("trace-789");
expect(result.options).toEqual({ timeout: 30000 });
});
it("should map stepResults", () => {
const result = buildRunResponse({
...baseParams,
stepResults: [
{ stepId: "step-1", success: true, data: { foo: "bar" } },
{ stepId: "step-2", success: false, error: "Failed" },
],
});
expect(result.stepResults).toEqual([
{ stepId: "step-1", success: true, data: { foo: "bar" }, error: undefined },
{ stepId: "step-2", success: false, data: undefined, error: "Failed" },
]);
});
it("should map all run statuses correctly", () => {
expect(buildRunResponse({ ...baseParams, status: RunStatus.RUNNING }).status).toBe("running");
expect(buildRunResponse({ ...baseParams, status: RunStatus.SUCCESS }).status).toBe("success");
expect(buildRunResponse({ ...baseParams, status: RunStatus.FAILED }).status).toBe("failed");
expect(buildRunResponse({ ...baseParams, status: RunStatus.ABORTED }).status).toBe("aborted");
});
it("should use tool version if provided", () => {
const toolWithVersion = { ...baseTool, version: "2.5.0" };
const result = buildRunResponse({ ...baseParams, tool: toolWithVersion });
expect(result.tool).toEqual({ id: "tool-123", version: "2.5.0" });
});
});
});