Files
openclaw/src/plugin-sdk/status-helpers.test.ts
2026-03-27 22:35:27 +00:00

372 lines
8.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
createAsyncComputedAccountStatusAdapter,
buildBaseAccountStatusSnapshot,
buildBaseChannelStatusSummary,
buildComputedAccountStatusSnapshot,
buildRuntimeAccountStatusSnapshot,
createComputedAccountStatusAdapter,
buildTokenChannelStatusSummary,
collectStatusIssuesFromLastError,
createDefaultChannelRuntimeState,
} from "./status-helpers.js";
const defaultRuntimeState = {
running: false,
lastStartAt: null,
lastStopAt: null,
lastError: null,
};
type ExpectedAccountSnapshot = {
accountId: string;
name?: string;
enabled?: boolean;
configured?: boolean;
running: boolean;
lastStartAt: number | null;
lastStopAt: number | null;
lastError: string | null;
probe?: unknown;
lastInboundAt: number | null;
lastOutboundAt: number | null;
} & Record<string, unknown>;
const defaultChannelSummary = {
configured: false,
...defaultRuntimeState,
};
const defaultTokenChannelSummary = {
...defaultChannelSummary,
tokenSource: "none",
mode: null,
probe: undefined,
lastProbeAt: null,
};
const defaultAccountSnapshot: ExpectedAccountSnapshot = {
accountId: "default",
name: undefined,
enabled: undefined,
configured: false,
...defaultRuntimeState,
probe: undefined,
lastInboundAt: null,
lastOutboundAt: null,
};
function expectedAccountSnapshot(
overrides: Partial<ExpectedAccountSnapshot> = {},
): ExpectedAccountSnapshot {
return {
...defaultAccountSnapshot,
...overrides,
};
}
const adapterAccount = {
accountId: "default",
enabled: true,
profileUrl: "https://example.test",
};
const adapterRuntime = {
accountId: "default",
running: true,
};
const adapterProbe = { ok: true };
function expectedAdapterAccountSnapshot() {
return {
...expectedAccountSnapshot({
enabled: true,
configured: true,
running: true,
probe: adapterProbe,
}),
profileUrl: adapterAccount.profileUrl,
connected: true,
};
}
function createComputedStatusAdapter() {
return createComputedAccountStatusAdapter<
{ accountId: string; enabled: boolean; profileUrl: string },
{ ok: boolean }
>({
defaultRuntime: createDefaultChannelRuntimeState("default"),
resolveAccountSnapshot: ({ account, runtime, probe }) => ({
accountId: account.accountId,
enabled: account.enabled,
configured: true,
extra: {
profileUrl: account.profileUrl,
connected: runtime?.running ?? false,
probe,
},
}),
});
}
function createAsyncStatusAdapter() {
return createAsyncComputedAccountStatusAdapter<
{ accountId: string; enabled: boolean; profileUrl: string },
{ ok: boolean }
>({
defaultRuntime: createDefaultChannelRuntimeState("default"),
resolveAccountSnapshot: async ({ account, runtime, probe }) => ({
accountId: account.accountId,
enabled: account.enabled,
configured: true,
extra: {
profileUrl: account.profileUrl,
connected: runtime?.running ?? false,
probe,
},
}),
});
}
describe("createDefaultChannelRuntimeState", () => {
it.each([
{
name: "builds default runtime state without extra fields",
accountId: "default",
extra: undefined,
expected: {
accountId: "default",
...defaultRuntimeState,
},
},
{
name: "merges extra fields into the default runtime state",
accountId: "alerts",
extra: {
probeAt: 123,
healthy: true,
},
expected: {
accountId: "alerts",
...defaultRuntimeState,
probeAt: 123,
healthy: true,
},
},
])("$name", ({ accountId, extra, expected }) => {
expect(createDefaultChannelRuntimeState(accountId, extra)).toEqual(expected);
});
});
describe("buildBaseChannelStatusSummary", () => {
it("defaults missing values", () => {
expect(buildBaseChannelStatusSummary({})).toEqual(defaultChannelSummary);
});
it("keeps explicit values", () => {
expect(
buildBaseChannelStatusSummary({
configured: true,
running: true,
lastStartAt: 1,
lastStopAt: 2,
lastError: "boom",
}),
).toEqual({
...defaultChannelSummary,
configured: true,
running: true,
lastStartAt: 1,
lastStopAt: 2,
lastError: "boom",
});
});
it("merges extra fields into the normalized channel summary", () => {
expect(
buildBaseChannelStatusSummary(
{
configured: true,
},
{
mode: "webhook",
secretSource: "env",
},
),
).toEqual({
...defaultChannelSummary,
configured: true,
mode: "webhook",
secretSource: "env",
});
});
});
describe("buildBaseAccountStatusSnapshot", () => {
it("builds account status with runtime defaults", () => {
expect(
buildBaseAccountStatusSnapshot({
account: { accountId: "default", enabled: true, configured: true },
}),
).toEqual(expectedAccountSnapshot({ enabled: true, configured: true }));
});
it("merges extra snapshot fields after the shared account shape", () => {
expect(
buildBaseAccountStatusSnapshot(
{
account: { accountId: "default", configured: true },
},
{
connected: true,
mode: "polling",
},
),
).toEqual({
...expectedAccountSnapshot({ configured: true }),
connected: true,
mode: "polling",
});
});
});
describe("buildComputedAccountStatusSnapshot", () => {
it("builds account status when configured is computed outside resolver", () => {
expect(
buildComputedAccountStatusSnapshot({
accountId: "default",
enabled: true,
configured: false,
}),
).toEqual(expectedAccountSnapshot({ enabled: true }));
});
it("merges computed extras after the shared fields", () => {
expect(
buildComputedAccountStatusSnapshot(
{
accountId: "default",
configured: true,
},
{
connected: true,
},
),
).toEqual({
...expectedAccountSnapshot({ configured: true }),
connected: true,
});
});
});
describe("computed account status adapters", () => {
it.each([
{
name: "sync",
createStatus: createComputedStatusAdapter,
},
{
name: "async",
createStatus: createAsyncStatusAdapter,
},
])(
"builds account snapshots from $name computed account metadata and extras",
async ({ createStatus }) => {
const status = createStatus();
await expect(
Promise.resolve(
status.buildAccountSnapshot?.({
account: adapterAccount,
cfg: {} as never,
runtime: adapterRuntime,
probe: adapterProbe,
}),
),
).resolves.toEqual(expectedAdapterAccountSnapshot());
},
);
});
describe("buildRuntimeAccountStatusSnapshot", () => {
it.each([
{
name: "builds runtime lifecycle fields with defaults",
input: {},
extra: undefined,
expected: {
...defaultRuntimeState,
probe: undefined,
},
},
{
name: "merges extra fields into runtime snapshots",
input: {},
extra: { port: 3978 },
expected: {
...defaultRuntimeState,
probe: undefined,
port: 3978,
},
},
])("$name", ({ input, extra, expected }) => {
expect(buildRuntimeAccountStatusSnapshot(input, extra)).toEqual(expected);
});
});
describe("buildTokenChannelStatusSummary", () => {
it.each([
{
name: "includes token/probe fields with mode by default",
input: {},
options: undefined,
expected: defaultTokenChannelSummary,
},
{
name: "can omit mode for channels without a mode state",
input: {
configured: true,
tokenSource: "env",
running: true,
lastStartAt: 1,
lastStopAt: 2,
lastError: "boom",
probe: { ok: true },
lastProbeAt: 3,
},
options: { includeMode: false },
expected: {
configured: true,
tokenSource: "env",
running: true,
lastStartAt: 1,
lastStopAt: 2,
lastError: "boom",
probe: { ok: true },
lastProbeAt: 3,
},
},
])("$name", ({ input, options, expected }) => {
expect(buildTokenChannelStatusSummary(input, options)).toEqual(expected);
});
});
describe("collectStatusIssuesFromLastError", () => {
it("returns runtime issues only for non-empty string lastError values", () => {
expect(
collectStatusIssuesFromLastError("demo-channel", [
{ accountId: "default", lastError: " timeout " },
{ accountId: "silent", lastError: " " },
{ accountId: "typed", lastError: { message: "boom" } },
]),
).toEqual([
{
channel: "demo-channel",
accountId: "default",
kind: "runtime",
message: "Channel error: timeout",
},
]);
});
});