import { describe, it, expect, vi } from "vitest";
import {
initializeHandler,
getHostConfigOrThrow,
getHostConfigOptional,
resolveTargetHosts,
assertValidAction,
formatResponse,
paginateItems,
createExhaustiveError,
resolveNonEmptyString,
getOptionalString,
getHostNameFromInput
} from "./base-handler.js";
import { ResponseFormat, type HostConfig } from "../../types.js";
import type { ServiceContainer } from "../../services/container.js";
// Mock host configs for testing
const mockHosts: HostConfig[] = [
{ name: "host1", host: "192.168.1.1", protocol: "http" },
{ name: "host2", host: "192.168.1.2", protocol: "http" },
{ name: "host3", host: "192.168.1.3", protocol: "ssh" }
];
describe("initializeHandler", () => {
it("extracts service from container using getter", () => {
const mockService = { name: "dockerService" };
const mockContainer = {
getDockerService: vi.fn().mockReturnValue(mockService)
} as unknown as ServiceContainer;
const result = initializeHandler(
{ response_format: ResponseFormat.MARKDOWN },
mockContainer,
(c) => c.getDockerService(),
mockHosts
);
expect(result.service).toBe(mockService);
expect(result.format).toBe(ResponseFormat.MARKDOWN);
expect(result.hosts).toBe(mockHosts);
expect(mockContainer.getDockerService).toHaveBeenCalledOnce();
});
it("defaults to MARKDOWN format when not specified", () => {
const mockContainer = {
getDockerService: vi.fn().mockReturnValue({})
} as unknown as ServiceContainer;
const result = initializeHandler(
{},
mockContainer,
(c) => c.getDockerService(),
mockHosts
);
expect(result.format).toBe(ResponseFormat.MARKDOWN);
});
it("uses JSON format when specified", () => {
const mockContainer = {
getDockerService: vi.fn().mockReturnValue({})
} as unknown as ServiceContainer;
const result = initializeHandler(
{ response_format: ResponseFormat.JSON },
mockContainer,
(c) => c.getDockerService(),
mockHosts
);
expect(result.format).toBe(ResponseFormat.JSON);
});
});
describe("getHostConfigOrThrow", () => {
it("returns host when found", () => {
const result = getHostConfigOrThrow(mockHosts, "host1");
expect(result.name).toBe("host1");
expect(result.host).toBe("192.168.1.1");
});
it("throws when host not found", () => {
expect(() => getHostConfigOrThrow(mockHosts, "unknown")).toThrow(
"Host not found: unknown"
);
});
it("throws with empty host list", () => {
expect(() => getHostConfigOrThrow([], "host1")).toThrow(
"Host not found: host1"
);
});
});
describe("getHostConfigOptional", () => {
it("returns host when found", () => {
const result = getHostConfigOptional(mockHosts, "host2");
expect(result?.name).toBe("host2");
});
it("returns undefined when hostName is undefined", () => {
const result = getHostConfigOptional(mockHosts, undefined);
expect(result).toBeUndefined();
});
it("returns undefined when host not found", () => {
const result = getHostConfigOptional(mockHosts, "unknown");
expect(result).toBeUndefined();
});
});
describe("resolveTargetHosts", () => {
it("returns all hosts when hostName is undefined", () => {
const result = resolveTargetHosts(mockHosts, undefined);
expect(result).toHaveLength(3);
expect(result).toEqual(mockHosts);
});
it("returns single host array when hostName provided", () => {
const result = resolveTargetHosts(mockHosts, "host1");
expect(result).toHaveLength(1);
expect(result[0].name).toBe("host1");
});
it("throws when specified host not found", () => {
expect(() => resolveTargetHosts(mockHosts, "unknown")).toThrow(
"Host not found: unknown"
);
});
});
describe("assertValidAction", () => {
it("passes when action matches expected", () => {
expect(() =>
assertValidAction({ action: "container" }, "container", "container")
).not.toThrow();
});
it("throws when action does not match", () => {
expect(() =>
assertValidAction({ action: "docker" }, "container", "container")
).toThrow("Invalid action for container handler: docker");
});
it("includes handler name in error message", () => {
expect(() =>
assertValidAction({ action: "wrong" }, "expected", "myHandler")
).toThrow("Invalid action for myHandler handler: wrong");
});
});
describe("formatResponse", () => {
const testData = { key: "value", count: 42 };
it("returns JSON string when format is JSON", () => {
const result = formatResponse(testData, ResponseFormat.JSON, () => "markdown");
expect(result).toBe('{\n "key": "value",\n "count": 42\n}');
});
it("calls markdown formatter when format is MARKDOWN", () => {
const formatter = vi.fn().mockReturnValue("# Markdown Output");
const result = formatResponse(testData, ResponseFormat.MARKDOWN, formatter);
expect(result).toBe("# Markdown Output");
expect(formatter).toHaveBeenCalledWith(testData);
expect(formatter).toHaveBeenCalledOnce();
});
it("handles complex nested objects in JSON mode", () => {
const complexData = { nested: { deep: [1, 2, 3] } };
const result = formatResponse(complexData, ResponseFormat.JSON, () => "");
const parsed = JSON.parse(result);
expect(parsed.nested.deep).toEqual([1, 2, 3]);
});
});
describe("paginateItems", () => {
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
it("returns all items with default pagination", () => {
const result = paginateItems(items, {});
expect(result.items).toEqual(items);
expect(result.total).toBe(10);
expect(result.offset).toBe(0);
expect(result.limit).toBe(50);
expect(result.hasMore).toBe(false);
});
it("respects offset parameter", () => {
const result = paginateItems(items, { offset: 3 });
expect(result.items).toEqual([4, 5, 6, 7, 8, 9, 10]);
expect(result.offset).toBe(3);
expect(result.hasMore).toBe(false);
});
it("respects limit parameter", () => {
const result = paginateItems(items, { limit: 3 });
expect(result.items).toEqual([1, 2, 3]);
expect(result.limit).toBe(3);
expect(result.hasMore).toBe(true);
});
it("handles offset and limit together", () => {
const result = paginateItems(items, { offset: 2, limit: 3 });
expect(result.items).toEqual([3, 4, 5]);
expect(result.total).toBe(10);
expect(result.offset).toBe(2);
expect(result.limit).toBe(3);
expect(result.hasMore).toBe(true);
});
it("returns empty array when offset exceeds length", () => {
const result = paginateItems(items, { offset: 100 });
expect(result.items).toEqual([]);
expect(result.total).toBe(10);
expect(result.hasMore).toBe(false);
});
it("handles empty array", () => {
const result = paginateItems([], {});
expect(result.items).toEqual([]);
expect(result.total).toBe(0);
expect(result.hasMore).toBe(false);
});
it("correctly identifies when there are no more items", () => {
const result = paginateItems(items, { offset: 7, limit: 3 });
expect(result.items).toEqual([8, 9, 10]);
expect(result.hasMore).toBe(false);
});
});
describe("createExhaustiveError", () => {
it("creates error with context and subaction", () => {
const value = { subaction: "unknown_action" } as never;
const error = createExhaustiveError(value, "container");
expect(error.message).toBe("Unknown container: unknown_action");
});
it("handles value without subaction property", () => {
const value = "raw_value" as never;
const error = createExhaustiveError(value, "action");
expect(error.message).toBe("Unknown action: raw_value");
});
});
describe("resolveNonEmptyString", () => {
it("returns trimmed string for valid input", () => {
expect(resolveNonEmptyString(" hello ")).toBe("hello");
});
it("returns undefined for empty string", () => {
expect(resolveNonEmptyString("")).toBeUndefined();
});
it("returns undefined for whitespace-only string", () => {
expect(resolveNonEmptyString(" ")).toBeUndefined();
});
it("returns undefined for non-string values", () => {
expect(resolveNonEmptyString(123)).toBeUndefined();
expect(resolveNonEmptyString(null)).toBeUndefined();
expect(resolveNonEmptyString(undefined)).toBeUndefined();
expect(resolveNonEmptyString({})).toBeUndefined();
expect(resolveNonEmptyString([])).toBeUndefined();
});
it("preserves internal whitespace", () => {
expect(resolveNonEmptyString(" hello world ")).toBe("hello world");
});
});
describe("getOptionalString", () => {
it("returns string value when valid", () => {
expect(getOptionalString("test", "field")).toBe("test");
});
it("returns undefined when value is undefined", () => {
expect(getOptionalString(undefined, "field")).toBeUndefined();
});
it("throws when value is not a string", () => {
expect(() => getOptionalString(123, "myField")).toThrow(
"Invalid myField: expected string"
);
});
it("throws with field name in error for object", () => {
expect(() => getOptionalString({}, "configValue")).toThrow(
"Invalid configValue: expected string"
);
});
it("returns empty string when value is empty string", () => {
expect(getOptionalString("", "field")).toBe("");
});
});
describe("getHostNameFromInput", () => {
it("returns host name when present", () => {
const input = { action: "test", host: "myhost" };
expect(getHostNameFromInput(input)).toBe("myhost");
});
it("returns undefined when host key not present", () => {
const input = { action: "test" };
expect(getHostNameFromInput(input)).toBeUndefined();
});
it("returns undefined when host value is undefined", () => {
const input = { action: "test", host: undefined };
expect(getHostNameFromInput(input)).toBeUndefined();
});
it("throws when host is not a string", () => {
const input = { action: "test", host: 123 };
expect(() => getHostNameFromInput(input)).toThrow(
"Invalid host: expected string"
);
});
it("returns empty string when host is empty string", () => {
const input = { action: "test", host: "" };
expect(getHostNameFromInput(input)).toBe("");
});
});