import { describe, it, expect } from "vitest";
import {
formatContainersMarkdown,
formatLogsMarkdown,
formatHostStatusMarkdown,
truncateIfNeeded,
formatScoutReadMarkdown,
formatScoutListMarkdown,
formatScoutTreeMarkdown,
formatScoutExecMarkdown,
formatScoutFindMarkdown,
formatScoutTransferMarkdown,
formatScoutDiffMarkdown,
formatNetworksMarkdown,
formatVolumesMarkdown
} from "./index.js";
import type { DockerNetworkInfo } from "../types.js";
describe("truncateIfNeeded", () => {
it("should return text unchanged if under limit", () => {
const text = "short text";
expect(truncateIfNeeded(text)).toBe(text);
});
it("should truncate text exceeding CHARACTER_LIMIT", () => {
const longText = "x".repeat(50001);
const result = truncateIfNeeded(longText);
expect(result.length).toBeLessThan(longText.length);
expect(result).toContain("truncated");
});
});
describe("formatContainersMarkdown", () => {
it("should return 'No containers found' for empty array", () => {
const result = formatContainersMarkdown([], 0, 0, false);
expect(result).toContain("No containers found");
});
it("should format container list with state emojis", () => {
const containers = [
{
id: "abc123",
name: "test-container",
image: "nginx:latest",
state: "running" as const,
status: "Up 2 hours",
hostName: "tootie",
ports: [],
labels: {},
created: "2024-01-01T00:00:00Z"
}
];
const result = formatContainersMarkdown(containers, 1, 0, false);
expect(result).toContain("š¢");
expect(result).toContain("test-container");
expect(result).toContain("nginx:latest");
});
it("should show pagination info when hasMore is true", () => {
const containers = [
{
id: "abc123",
name: "test",
image: "nginx",
state: "running" as const,
status: "Up",
hostName: "tootie",
ports: [],
labels: {},
created: "2024-01-01T00:00:00Z"
}
];
const result = formatContainersMarkdown(containers, 10, 0, true);
expect(result).toContain("More results available");
});
});
describe("formatLogsMarkdown", () => {
it("should return 'No logs found' for empty array", () => {
const result = formatLogsMarkdown([], "test", "host");
expect(result).toContain("No logs found");
});
it("should format log entries with timestamps", () => {
const logs = [{ timestamp: "2024-01-01T12:00:00Z", message: "Test log message" }];
const result = formatLogsMarkdown(logs, "container", "host");
expect(result).toContain("12:00:00");
expect(result).toContain("Test log message");
});
});
describe("formatHostStatusMarkdown", () => {
it("should show online status with green emoji", () => {
const status = [
{
name: "tootie",
connected: true,
containerCount: 10,
runningCount: 8
}
];
const result = formatHostStatusMarkdown(status);
expect(result).toContain("š¢");
expect(result).toContain("Online");
expect(result).toContain("tootie");
});
it("should show offline status with red emoji", () => {
const status = [
{
name: "offline-host",
connected: false,
containerCount: 0,
runningCount: 0,
error: "Connection refused"
}
];
const result = formatHostStatusMarkdown(status);
expect(result).toContain("š“");
expect(result).toContain("Offline");
});
});
describe("scout formatters", () => {
describe("formatScoutReadMarkdown", () => {
it("formats file content with path header", () => {
const result = formatScoutReadMarkdown(
"tootie",
"/etc/hosts",
"127.0.0.1 localhost",
100,
false
);
expect(result).toContain("tootie:/etc/hosts");
expect(result).toContain("127.0.0.1 localhost");
});
it("shows truncation notice when truncated", () => {
const result = formatScoutReadMarkdown(
"tootie",
"/var/log/big.log",
"partial content...",
1000000,
true
);
expect(result).toContain("truncated");
});
});
describe("formatScoutListMarkdown", () => {
it("formats directory listing", () => {
const listing = "total 4\ndrwxr-xr-x 2 root root 4096 Jan 1 00:00 test";
const result = formatScoutListMarkdown("tootie", "/var/log", listing);
expect(result).toContain("tootie:/var/log");
expect(result).toContain("total 4");
});
});
describe("formatScoutTreeMarkdown", () => {
it("formats tree output", () => {
const tree = ".\nāāā dir1\nāāā file.txt";
const result = formatScoutTreeMarkdown("tootie", "/home", tree, 3);
expect(result).toContain("tootie:/home");
expect(result).toContain("āāā dir1");
});
});
describe("formatScoutExecMarkdown", () => {
it("formats command result", () => {
const result = formatScoutExecMarkdown("tootie", "/tmp", "ls -la", "file1\nfile2", 0);
expect(result).toContain("ls -la");
expect(result).toContain("file1");
expect(result).toContain("**Exit:** 0");
});
});
describe("formatScoutFindMarkdown", () => {
it("formats find results", () => {
const files = "/var/log/syslog\n/var/log/auth.log";
const result = formatScoutFindMarkdown("tootie", "/var", "*.log", files);
expect(result).toContain("*.log");
expect(result).toContain("/var/log/syslog");
});
});
describe("formatScoutTransferMarkdown", () => {
it("formats transfer result", () => {
const result = formatScoutTransferMarkdown(
"tootie",
"/tmp/file.txt",
"shart",
"/backup/file.txt",
1024
);
expect(result).toContain("tootie:/tmp/file.txt");
expect(result).toContain("shart:/backup/file.txt");
expect(result).toContain("1.0 KB");
});
it("includes warning if present", () => {
const result = formatScoutTransferMarkdown(
"tootie",
"/tmp/file.txt",
"shart",
"/etc/config",
512,
"Warning: system path"
);
expect(result).toContain("Warning");
});
});
describe("formatScoutDiffMarkdown", () => {
it("formats diff output", () => {
const diff = "--- a/hosts\n+++ b/hosts\n@@ -1 +1 @@\n-old\n+new";
const result = formatScoutDiffMarkdown("tootie", "/etc/hosts", "shart", "/etc/hosts", diff);
expect(result).toContain("tootie:/etc/hosts");
expect(result).toContain("shart:/etc/hosts");
expect(result).toContain("---");
});
});
});
describe("docker formatters", () => {
describe("formatNetworksMarkdown", () => {
it("should return 'No networks found' for empty array", () => {
const result = formatNetworksMarkdown([], 0, 0);
expect(result).toContain("No networks found");
});
it("should format networks list grouped by host", () => {
const networks = [
{
id: "net1",
name: "bridge",
driver: "bridge",
scope: "local",
hostName: "tootie"
},
{
id: "net2",
name: "host",
driver: "host",
scope: "local",
hostName: "tootie"
},
{
id: "net3",
name: "overlay",
driver: "overlay",
scope: "swarm",
hostName: "shart"
}
];
const result = formatNetworksMarkdown(networks, 3, 0);
expect(result).toContain("## Docker Networks");
expect(result).toContain("Showing 3 of 3 networks");
expect(result).toContain("### tootie");
expect(result).toContain("### shart");
expect(result).toContain("bridge (bridge, local)");
expect(result).toContain("overlay (overlay, swarm)");
});
it("should respect pagination", () => {
const allNetworks = [
{
id: "net1",
name: "net1",
driver: "bridge",
scope: "local",
hostName: "host1"
},
{
id: "net2",
name: "net2",
driver: "bridge",
scope: "local",
hostName: "host1"
},
{
id: "net3",
name: "net3",
driver: "bridge",
scope: "local",
hostName: "host1"
}
];
// Simulate pagination at call site (offset=1, limit=1)
const paginatedNetworks = allNetworks.slice(1, 2);
const result = formatNetworksMarkdown(paginatedNetworks, 3, 1);
expect(result).toContain("Showing 1 of 3 networks");
expect(result).toContain("net2");
expect(result).not.toContain("net1");
expect(result).not.toContain("net3");
});
it("should handle out-of-bounds offset gracefully", () => {
// When pagination happens at call site with offset >= total count,
// an empty array is passed to the formatter (e.g., allNetworks.slice(5, 15) with only 2 items)
const paginatedNetworks: DockerNetworkInfo[] = [];
const result = formatNetworksMarkdown(paginatedNetworks, 2, 5);
expect(result).toContain("No networks found");
});
it("should preserve input order and NOT reorder pre-paginated data", () => {
// Handler now sorts BEFORE pagination, so formatter receives sorted data
// Formatter must preserve this order, even if it spans multiple hosts
const preSortedPaginatedNetworks = [
// Handler sorted by hostName, then paginated - this slice has zulu before alpha
{ id: "net6", name: "net6", driver: "bridge", scope: "local", hostName: "zulu" },
{ id: "net1", name: "net1", driver: "bridge", scope: "local", hostName: "alpha" }
];
const result = formatNetworksMarkdown(preSortedPaginatedNetworks, 10, 5);
// Formatter must preserve input order (zulu before alpha)
const zuluIndex = result.indexOf("### zulu");
const alphaIndex = result.indexOf("### alpha");
expect(zuluIndex).toBeGreaterThan(-1);
expect(alphaIndex).toBeGreaterThan(-1);
expect(zuluIndex).toBeLessThan(alphaIndex); // zulu should appear BEFORE alpha in output
});
});
describe("formatVolumesMarkdown", () => {
it("should return 'No volumes found' for empty array", () => {
const result = formatVolumesMarkdown([], 0, 0);
expect(result).toContain("No volumes found");
});
it("should format volumes list grouped by host", () => {
const volumes = [
{
name: "vol1",
driver: "local",
scope: "local",
hostName: "tootie"
},
{
name: "vol2",
driver: "local",
scope: "local",
hostName: "tootie"
},
{
name: "vol3",
driver: "nfs",
scope: "global",
hostName: "shart"
}
];
const result = formatVolumesMarkdown(volumes, 3, 0);
expect(result).toContain("## Docker Volumes");
expect(result).toContain("Showing 3 of 3 volumes");
expect(result).toContain("### tootie");
expect(result).toContain("### shart");
expect(result).toContain("vol1 (local, local)");
expect(result).toContain("vol3 (nfs, global)");
});
it("should respect pagination", () => {
const allVolumes = [
{
name: "vol1",
driver: "local",
scope: "local",
hostName: "host1"
},
{
name: "vol2",
driver: "local",
scope: "local",
hostName: "host1"
},
{
name: "vol3",
driver: "local",
scope: "local",
hostName: "host1"
}
];
// Simulate pagination at call site (offset=1, limit=1)
const paginatedVolumes = allVolumes.slice(1, 2);
const result = formatVolumesMarkdown(paginatedVolumes, 3, 1);
expect(result).toContain("Showing 1 of 3 volumes");
expect(result).toContain("vol2");
expect(result).not.toContain("vol1");
expect(result).not.toContain("vol3");
});
it("should handle out-of-bounds offset with empty volumes array", () => {
// totalCount=2 but offset=5 (beyond available items)
const result = formatVolumesMarkdown([], 2, 5);
expect(result).toContain("No volumes found");
});
it("should preserve host header order for pre-sorted paginated volumes", () => {
// Pre-sorted volumes spanning multiple hosts
const volumes = [
{
name: "vol1",
driver: "local",
scope: "local",
hostName: "alpha"
},
{
name: "vol2",
driver: "local",
scope: "local",
hostName: "alpha"
},
{
name: "vol3",
driver: "nfs",
scope: "global",
hostName: "bravo"
},
{
name: "vol4",
driver: "nfs",
scope: "global",
hostName: "bravo"
},
{
name: "vol5",
driver: "local",
scope: "local",
hostName: "charlie"
}
];
const result = formatVolumesMarkdown(volumes, 5, 0);
const alphaIndex = result.indexOf("### alpha");
const bravoIndex = result.indexOf("### bravo");
const charlieIndex = result.indexOf("### charlie");
expect(alphaIndex).toBeGreaterThan(-1);
expect(bravoIndex).toBeGreaterThan(-1);
expect(charlieIndex).toBeGreaterThan(-1);
expect(alphaIndex).toBeLessThan(bravoIndex);
expect(bravoIndex).toBeLessThan(charlieIndex);
});
});
});