Files
pnokerandClaude Code b588204110 feat(cli): adopt the remaining guardian tests and auth store fix
adopts the remaining uncommitted work from the parallel session:
five new guardian test files (client-errors, device-import-template,
entity-id-guard, jwt-guard, option-hygiene) that lock in the error
taxonomy, id encoding, and cli option hygiene; an attachment file
cleaner in the agentic service; attachment controller and service
tests; a salt-type fix in the auth store; ci workflow typecheck step;
and the cli test report. 818 tests total across 37 files.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-06 21:26:15 +08:00

126 lines
4.0 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { formatOutput } from "../src/utils/format";
describe("format utils", () => {
it("formatOutput json should stringify", () => {
const result = formatOutput({ ok: true, data: "test" }, "json");
const parsed = JSON.parse(result);
expect(parsed.ok).toBe(true);
expect(parsed.data).toBe("test");
});
it("formatOutput json should handle arrays", () => {
const result = formatOutput([{ id: 1, name: "a" }], "json");
const parsed = JSON.parse(result);
expect(parsed).toHaveLength(1);
expect(parsed[0].id).toBe(1);
});
it("formatOutput table should handle arrays", () => {
const result = formatOutput(
[
{ id: 1, name: "Device A" },
{ id: 2, name: "Device B" },
],
"table",
);
expect(result).toContain("Device A");
expect(result).toContain("Device B");
expect(result).toContain("id");
expect(result).toContain("name");
});
it("formatOutput table should handle empty array", () => {
const result = formatOutput([], "table");
expect(result).toBe("(empty)");
});
it("formatOutput table should handle flat object", () => {
const result = formatOutput(
{ gateway: "http://localhost:8000", tenant: "default" },
"table",
);
expect(result).toContain("gateway");
expect(result).toContain("default");
});
it("formatOutput should keep 204 results silent", () => {
expect(formatOutput(undefined, "json")).toBe("");
expect(formatOutput(undefined, "table")).toBe("");
expect(formatOutput(undefined, "yaml")).toBe("");
});
});
describe("table rendering polish (no trailing whitespace, ext blobs, clamping)", () => {
/** Rows whose last column is empty on some records — the historical trailing-space source. */
const heterogeneousRows = [
{ id: 1, name: "alpha" },
{ id: 2 },
{ id: 3, name: "gamma" },
];
/** Offset-page envelope whose footer line must also stay clean. */
const pagedRows = {
total: 3,
items: [
{ id: 1, name: "alpha" },
{ id: 2, name: "beta" },
],
hasNext: true,
};
it("never pads the last column: no rendered line carries trailing whitespace", () => {
for (const payload of [heterogeneousRows, pagedRows, [{ only: "column" }], [1, 2]]) {
const table = formatOutput(payload, "table");
for (const line of table.split("\n")) {
expect(line, JSON.stringify(payload)).toMatch(/\S$/u);
}
}
});
it("flattens driverExt blobs one level: type/version inline, nulls dropped", () => {
const table = formatOutput(
[
{
id: 1,
name: "virtual-driver",
driverExt: { type: "VIRTUAL", version: null, content: null },
},
{
id: 2,
name: "mqtt-driver",
driverExt: { type: "MQTT", version: "1.2.0", content: null },
},
],
"table",
);
// Readable inline rendering instead of the wire JSON form.
expect(table).toContain("type=VIRTUAL");
expect(table).toContain("type=MQTT version=1.2.0");
// No raw JSON braces and no null-member artifacts leak into the cell.
expect(table).not.toContain("{");
expect(table).not.toContain('"content":null');
expect(table).not.toContain('"version":null');
});
it("keeps compact JSON for non-ext object cells (scoping guard)", () => {
const table = formatOutput([{ meta: { a: 1 } }], "table");
expect(table).toContain('"a":1');
});
it("clamps over-long cells past the column budget with an ellipsis", () => {
const long = "x".repeat(200);
const table = formatOutput([{ id: 1, description: long }], "table");
const lines = table.split("\n");
expect(table).not.toContain(long);
expect(table).toContain("…");
// The clamped cell is the last column: the row ends with the ellipsis.
expect(lines[2]).toMatch(/…$/u);
// The separator proves the column stopped at the 80-char budget.
expect(lines[1]).toHaveLength("id".length + "-+-".length + 80);
});
});