mirror of https://github.com/openclaw/openclaw.git
test: add shared helper coverage
This commit is contained in:
parent
35cf3d0ce5
commit
341d3e3493
|
|
@ -0,0 +1,16 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeDeviceAuthRole, normalizeDeviceAuthScopes } from "./device-auth.js";
|
||||
|
||||
describe("shared/device-auth", () => {
|
||||
it("trims device auth roles without further rewriting", () => {
|
||||
expect(normalizeDeviceAuthRole(" operator ")).toBe("operator");
|
||||
expect(normalizeDeviceAuthRole("")).toBe("");
|
||||
});
|
||||
|
||||
it("dedupes, trims, sorts, and filters auth scopes", () => {
|
||||
expect(
|
||||
normalizeDeviceAuthScopes([" node.invoke ", "operator.read", "", "node.invoke", "a.scope"]),
|
||||
).toEqual(["a.scope", "node.invoke", "operator.read"]);
|
||||
expect(normalizeDeviceAuthScopes(undefined)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import { resolveGatewayBindUrl } from "./gateway-bind-url.js";
|
||||
|
||||
describe("shared/gateway-bind-url", () => {
|
||||
it("returns null for loopback/default binds", () => {
|
||||
expect(
|
||||
resolveGatewayBindUrl({
|
||||
scheme: "ws",
|
||||
port: 18789,
|
||||
pickTailnetHost: () => "100.64.0.1",
|
||||
pickLanHost: () => "192.168.1.2",
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("resolves custom binds only when custom host is present after trimming", () => {
|
||||
expect(
|
||||
resolveGatewayBindUrl({
|
||||
bind: "custom",
|
||||
customBindHost: " gateway.local ",
|
||||
scheme: "wss",
|
||||
port: 443,
|
||||
pickTailnetHost: vi.fn(),
|
||||
pickLanHost: vi.fn(),
|
||||
}),
|
||||
).toEqual({
|
||||
url: "wss://gateway.local:443",
|
||||
source: "gateway.bind=custom",
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveGatewayBindUrl({
|
||||
bind: "custom",
|
||||
customBindHost: " ",
|
||||
scheme: "ws",
|
||||
port: 18789,
|
||||
pickTailnetHost: vi.fn(),
|
||||
pickLanHost: vi.fn(),
|
||||
}),
|
||||
).toEqual({
|
||||
error: "gateway.bind=custom requires gateway.customBindHost.",
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves tailnet and lan binds or returns clear errors", () => {
|
||||
expect(
|
||||
resolveGatewayBindUrl({
|
||||
bind: "tailnet",
|
||||
scheme: "ws",
|
||||
port: 18789,
|
||||
pickTailnetHost: () => "100.64.0.1",
|
||||
pickLanHost: vi.fn(),
|
||||
}),
|
||||
).toEqual({
|
||||
url: "ws://100.64.0.1:18789",
|
||||
source: "gateway.bind=tailnet",
|
||||
});
|
||||
expect(
|
||||
resolveGatewayBindUrl({
|
||||
bind: "tailnet",
|
||||
scheme: "ws",
|
||||
port: 18789,
|
||||
pickTailnetHost: () => null,
|
||||
pickLanHost: vi.fn(),
|
||||
}),
|
||||
).toEqual({
|
||||
error: "gateway.bind=tailnet set, but no tailnet IP was found.",
|
||||
});
|
||||
|
||||
expect(
|
||||
resolveGatewayBindUrl({
|
||||
bind: "lan",
|
||||
scheme: "wss",
|
||||
port: 8443,
|
||||
pickTailnetHost: vi.fn(),
|
||||
pickLanHost: () => "192.168.1.2",
|
||||
}),
|
||||
).toEqual({
|
||||
url: "wss://192.168.1.2:8443",
|
||||
source: "gateway.bind=lan",
|
||||
});
|
||||
expect(
|
||||
resolveGatewayBindUrl({
|
||||
bind: "lan",
|
||||
scheme: "ws",
|
||||
port: 18789,
|
||||
pickTailnetHost: vi.fn(),
|
||||
pickLanHost: () => null,
|
||||
}),
|
||||
).toEqual({
|
||||
error: "gateway.bind=lan set, but no private LAN IP was found.",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { resolveProcessScopedMap } from "./process-scoped-map.js";
|
||||
|
||||
const MAP_KEY = Symbol("process-scoped-map:test");
|
||||
const OTHER_MAP_KEY = Symbol("process-scoped-map:other");
|
||||
|
||||
afterEach(() => {
|
||||
delete (process as Record<PropertyKey, unknown>)[MAP_KEY];
|
||||
delete (process as Record<PropertyKey, unknown>)[OTHER_MAP_KEY];
|
||||
});
|
||||
|
||||
describe("shared/process-scoped-map", () => {
|
||||
it("reuses the same map for the same symbol", () => {
|
||||
const first = resolveProcessScopedMap<number>(MAP_KEY);
|
||||
first.set("a", 1);
|
||||
|
||||
const second = resolveProcessScopedMap<number>(MAP_KEY);
|
||||
|
||||
expect(second).toBe(first);
|
||||
expect(second.get("a")).toBe(1);
|
||||
});
|
||||
|
||||
it("keeps distinct maps for distinct symbols", () => {
|
||||
const first = resolveProcessScopedMap<number>(MAP_KEY);
|
||||
const second = resolveProcessScopedMap<number>(OTHER_MAP_KEY);
|
||||
|
||||
expect(second).not.toBe(first);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
formatDurationCompact,
|
||||
formatTokenShort,
|
||||
formatTokenUsageDisplay,
|
||||
resolveIoTokens,
|
||||
resolveTotalTokens,
|
||||
truncateLine,
|
||||
} from "./subagents-format.js";
|
||||
|
||||
describe("shared/subagents-format", () => {
|
||||
it("formats compact durations across minute, hour, and day buckets", () => {
|
||||
expect(formatDurationCompact()).toBe("n/a");
|
||||
expect(formatDurationCompact(30_000)).toBe("1m");
|
||||
expect(formatDurationCompact(61 * 60_000)).toBe("1h1m");
|
||||
expect(formatDurationCompact(25 * 60 * 60_000)).toBe("1d1h");
|
||||
});
|
||||
|
||||
it("formats token counts with integer, kilo, and million branches", () => {
|
||||
expect(formatTokenShort()).toBeUndefined();
|
||||
expect(formatTokenShort(999.9)).toBe("999");
|
||||
expect(formatTokenShort(1_500)).toBe("1.5k");
|
||||
expect(formatTokenShort(15_400)).toBe("15k");
|
||||
expect(formatTokenShort(1_250_000)).toBe("1.3m");
|
||||
});
|
||||
|
||||
it("truncates lines only when needed", () => {
|
||||
expect(truncateLine("short", 10)).toBe("short");
|
||||
expect(truncateLine("trim me ", 7)).toBe("trim me...");
|
||||
});
|
||||
|
||||
it("resolves token totals and io breakdowns from valid numeric fields only", () => {
|
||||
expect(resolveTotalTokens()).toBeUndefined();
|
||||
expect(resolveTotalTokens({ totalTokens: 42 })).toBe(42);
|
||||
expect(resolveTotalTokens({ inputTokens: 10, outputTokens: 5 })).toBe(15);
|
||||
expect(resolveTotalTokens({ inputTokens: Number.NaN, outputTokens: 5 })).toBeUndefined();
|
||||
|
||||
expect(resolveIoTokens({ inputTokens: 10, outputTokens: 5 })).toEqual({
|
||||
input: 10,
|
||||
output: 5,
|
||||
total: 15,
|
||||
});
|
||||
expect(resolveIoTokens({ inputTokens: Number.NaN, outputTokens: 0 })).toBeUndefined();
|
||||
});
|
||||
|
||||
it("formats io and prompt-cache usage displays with fallback branches", () => {
|
||||
expect(
|
||||
formatTokenUsageDisplay({
|
||||
inputTokens: 1_200,
|
||||
outputTokens: 300,
|
||||
totalTokens: 2_100,
|
||||
}),
|
||||
).toBe("tokens 1.5k (in 1.2k / out 300), prompt/cache 2.1k");
|
||||
|
||||
expect(formatTokenUsageDisplay({ totalTokens: 500 })).toBe("tokens 500 prompt/cache");
|
||||
expect(formatTokenUsageDisplay({ inputTokens: 0, outputTokens: 0, totalTokens: 0 })).toBe("");
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue