mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-22 06:32:00 +00:00
test: dedupe helper-heavy test suites
This commit is contained in:
@@ -23,6 +23,36 @@ import {
|
||||
|
||||
const resolveDefaultAccountId = () => DEFAULT_ACCOUNT_ID;
|
||||
|
||||
function createMergedReaderCfg(
|
||||
channelId: "whatsapp" | "imessage",
|
||||
accountConfig: { allowFrom: string[]; defaultTo: string },
|
||||
) {
|
||||
return {
|
||||
channels: {
|
||||
[channelId]: {
|
||||
allowFrom: ["root"],
|
||||
defaultTo: " root:chat ",
|
||||
accounts: {
|
||||
alt: accountConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createConfigWritesCfg() {
|
||||
return {
|
||||
channels: {
|
||||
telegram: {
|
||||
configWrites: true,
|
||||
accounts: {
|
||||
Work: { configWrites: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function expectAdapterAllowFromAndDefaultTo(adapter: unknown) {
|
||||
const channelAdapter = adapter as {
|
||||
resolveAllowFrom?: (params: { cfg: object; accountId: string }) => unknown;
|
||||
@@ -46,108 +76,92 @@ function expectAdapterAllowFromAndDefaultTo(adapter: unknown) {
|
||||
}
|
||||
|
||||
describe("mapAllowFromEntries", () => {
|
||||
it("coerces allowFrom entries to strings", () => {
|
||||
expect(mapAllowFromEntries(["user", 42])).toEqual(["user", "42"]);
|
||||
});
|
||||
|
||||
it("returns empty list for missing input", () => {
|
||||
expect(mapAllowFromEntries(undefined)).toEqual([]);
|
||||
it.each([
|
||||
{
|
||||
name: "coerces allowFrom entries to strings",
|
||||
input: ["user", 42],
|
||||
expected: ["user", "42"],
|
||||
},
|
||||
{
|
||||
name: "returns empty list for missing input",
|
||||
input: undefined,
|
||||
expected: [],
|
||||
},
|
||||
])("$name", ({ input, expected }) => {
|
||||
expect(mapAllowFromEntries(input)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveOptionalConfigString", () => {
|
||||
it("trims and returns string values", () => {
|
||||
expect(resolveOptionalConfigString(" room:123 ")).toBe("room:123");
|
||||
});
|
||||
|
||||
it("coerces numeric values", () => {
|
||||
expect(resolveOptionalConfigString(123)).toBe("123");
|
||||
});
|
||||
|
||||
it("returns undefined for empty values", () => {
|
||||
expect(resolveOptionalConfigString(" ")).toBeUndefined();
|
||||
expect(resolveOptionalConfigString(undefined)).toBeUndefined();
|
||||
it.each([
|
||||
{
|
||||
name: "trims and returns string values",
|
||||
input: " room:123 ",
|
||||
expected: "room:123",
|
||||
},
|
||||
{
|
||||
name: "coerces numeric values",
|
||||
input: 123,
|
||||
expected: "123",
|
||||
},
|
||||
{
|
||||
name: "returns undefined for empty string values",
|
||||
input: " ",
|
||||
expected: undefined,
|
||||
},
|
||||
{
|
||||
name: "returns undefined for missing values",
|
||||
input: undefined,
|
||||
expected: undefined,
|
||||
},
|
||||
])("$name", ({ input, expected }) => {
|
||||
expect(resolveOptionalConfigString(input)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("provider config readers", () => {
|
||||
it("reads merged WhatsApp allowFrom/defaultTo without the channel registry", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["root"],
|
||||
defaultTo: " root:chat ",
|
||||
accounts: {
|
||||
alt: {
|
||||
allowFrom: ["49123", "42"],
|
||||
defaultTo: " alt:chat ",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(resolveWhatsAppConfigAllowFrom({ cfg, accountId: "alt" })).toEqual(["49123", "42"]);
|
||||
expect(resolveWhatsAppConfigDefaultTo({ cfg, accountId: "alt" })).toBe("alt:chat");
|
||||
});
|
||||
|
||||
it("reads merged iMessage allowFrom/defaultTo without the channel registry", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
imessage: {
|
||||
allowFrom: ["root"],
|
||||
defaultTo: " root:chat ",
|
||||
accounts: {
|
||||
alt: {
|
||||
allowFrom: ["chat_id:9", "user@example.com"],
|
||||
defaultTo: " alt:chat ",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(resolveIMessageConfigAllowFrom({ cfg, accountId: "alt" })).toEqual([
|
||||
"chat_id:9",
|
||||
"user@example.com",
|
||||
]);
|
||||
expect(resolveIMessageConfigDefaultTo({ cfg, accountId: "alt" })).toBe("alt:chat");
|
||||
it.each([
|
||||
{
|
||||
name: "reads merged WhatsApp allowFrom/defaultTo without the channel registry",
|
||||
cfg: createMergedReaderCfg("whatsapp", {
|
||||
allowFrom: ["49123", "42"],
|
||||
defaultTo: " alt:chat ",
|
||||
}),
|
||||
resolveAllowFrom: resolveWhatsAppConfigAllowFrom,
|
||||
resolveDefaultTo: resolveWhatsAppConfigDefaultTo,
|
||||
expectedAllowFrom: ["49123", "42"],
|
||||
},
|
||||
{
|
||||
name: "reads merged iMessage allowFrom/defaultTo without the channel registry",
|
||||
cfg: createMergedReaderCfg("imessage", {
|
||||
allowFrom: ["chat_id:9", "user@example.com"],
|
||||
defaultTo: " alt:chat ",
|
||||
}),
|
||||
resolveAllowFrom: resolveIMessageConfigAllowFrom,
|
||||
resolveDefaultTo: resolveIMessageConfigDefaultTo,
|
||||
expectedAllowFrom: ["chat_id:9", "user@example.com"],
|
||||
},
|
||||
])("$name", ({ cfg, resolveAllowFrom, resolveDefaultTo, expectedAllowFrom }) => {
|
||||
expect(resolveAllowFrom({ cfg, accountId: "alt" })).toEqual(expectedAllowFrom);
|
||||
expect(resolveDefaultTo({ cfg, accountId: "alt" })).toBe("alt:chat");
|
||||
});
|
||||
});
|
||||
|
||||
describe("config write helpers", () => {
|
||||
it("matches account ids case-insensitively", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: {
|
||||
configWrites: true,
|
||||
accounts: {
|
||||
Work: { configWrites: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(resolveChannelConfigWrites({ cfg, channelId: "telegram", accountId: "work" })).toBe(
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
resolveChannelConfigWrites({
|
||||
cfg: createConfigWritesCfg(),
|
||||
channelId: "telegram",
|
||||
accountId: "work",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("blocks account-scoped writes when the configured account key differs only by case", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: {
|
||||
configWrites: true,
|
||||
accounts: {
|
||||
Work: { configWrites: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
authorizeConfigWrite({
|
||||
cfg,
|
||||
cfg: createConfigWritesCfg(),
|
||||
target: {
|
||||
kind: "account",
|
||||
scope: { channelId: "telegram", accountId: "work" },
|
||||
|
||||
@@ -71,39 +71,45 @@ describe("sendPayloadWithChunkedTextAndMedia", () => {
|
||||
});
|
||||
|
||||
describe("resolveOutboundMediaUrls", () => {
|
||||
it("prefers mediaUrls over the legacy single-media field", () => {
|
||||
expect(
|
||||
resolveOutboundMediaUrls({
|
||||
it.each([
|
||||
{
|
||||
name: "prefers mediaUrls over the legacy single-media field",
|
||||
payload: {
|
||||
mediaUrls: ["https://example.com/a.png", "https://example.com/b.png"],
|
||||
mediaUrl: "https://example.com/legacy.png",
|
||||
}),
|
||||
).toEqual(["https://example.com/a.png", "https://example.com/b.png"]);
|
||||
});
|
||||
|
||||
it("falls back to the legacy single-media field", () => {
|
||||
expect(
|
||||
resolveOutboundMediaUrls({
|
||||
},
|
||||
expected: ["https://example.com/a.png", "https://example.com/b.png"],
|
||||
},
|
||||
{
|
||||
name: "falls back to the legacy single-media field",
|
||||
payload: {
|
||||
mediaUrl: "https://example.com/legacy.png",
|
||||
}),
|
||||
).toEqual(["https://example.com/legacy.png"]);
|
||||
},
|
||||
expected: ["https://example.com/legacy.png"],
|
||||
},
|
||||
])("$name", ({ payload, expected }) => {
|
||||
expect(resolveOutboundMediaUrls(payload)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("countOutboundMedia", () => {
|
||||
it("counts normalized media entries", () => {
|
||||
expect(
|
||||
countOutboundMedia({
|
||||
it.each([
|
||||
{
|
||||
name: "counts normalized media entries",
|
||||
payload: {
|
||||
mediaUrls: ["https://example.com/a.png", "https://example.com/b.png"],
|
||||
}),
|
||||
).toBe(2);
|
||||
});
|
||||
|
||||
it("counts legacy single-media payloads", () => {
|
||||
expect(
|
||||
countOutboundMedia({
|
||||
},
|
||||
expected: 2,
|
||||
},
|
||||
{
|
||||
name: "counts legacy single-media payloads",
|
||||
payload: {
|
||||
mediaUrl: "https://example.com/legacy.png",
|
||||
}),
|
||||
).toBe(1);
|
||||
},
|
||||
expected: 1,
|
||||
},
|
||||
])("$name", ({ payload, expected }) => {
|
||||
expect(countOutboundMedia(payload)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,33 +122,76 @@ describe("hasOutboundMedia", () => {
|
||||
});
|
||||
|
||||
describe("hasOutboundText", () => {
|
||||
it("checks raw text presence by default", () => {
|
||||
expect(hasOutboundText({ text: "hello" })).toBe(true);
|
||||
expect(hasOutboundText({ text: " " })).toBe(true);
|
||||
expect(hasOutboundText({})).toBe(false);
|
||||
});
|
||||
|
||||
it("can trim whitespace-only text", () => {
|
||||
expect(hasOutboundText({ text: " " }, { trim: true })).toBe(false);
|
||||
expect(hasOutboundText({ text: " hi " }, { trim: true })).toBe(true);
|
||||
it.each([
|
||||
{
|
||||
name: "checks raw text presence by default",
|
||||
payload: { text: "hello" },
|
||||
options: undefined,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "treats whitespace-only text as present by default",
|
||||
payload: { text: " " },
|
||||
options: undefined,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "returns false when text is missing",
|
||||
payload: {},
|
||||
options: undefined,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "can trim whitespace-only text",
|
||||
payload: { text: " " },
|
||||
options: { trim: true },
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "keeps non-empty trimmed text",
|
||||
payload: { text: " hi " },
|
||||
options: { trim: true },
|
||||
expected: true,
|
||||
},
|
||||
])("$name", ({ payload, options, expected }) => {
|
||||
expect(hasOutboundText(payload, options)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasOutboundReplyContent", () => {
|
||||
it("detects text or media content", () => {
|
||||
expect(hasOutboundReplyContent({ text: "hello" })).toBe(true);
|
||||
expect(hasOutboundReplyContent({ mediaUrl: "https://example.com/a.png" })).toBe(true);
|
||||
expect(hasOutboundReplyContent({})).toBe(false);
|
||||
});
|
||||
|
||||
it("can ignore whitespace-only text unless media exists", () => {
|
||||
expect(hasOutboundReplyContent({ text: " " }, { trimText: true })).toBe(false);
|
||||
expect(
|
||||
hasOutboundReplyContent(
|
||||
{ text: " ", mediaUrls: ["https://example.com/a.png"] },
|
||||
{ trimText: true },
|
||||
),
|
||||
).toBe(true);
|
||||
it.each([
|
||||
{
|
||||
name: "detects text content",
|
||||
payload: { text: "hello" },
|
||||
options: undefined,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "detects media content",
|
||||
payload: { mediaUrl: "https://example.com/a.png" },
|
||||
options: undefined,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "returns false when text and media are both missing",
|
||||
payload: {},
|
||||
options: undefined,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "can ignore whitespace-only text",
|
||||
payload: { text: " " },
|
||||
options: { trimText: true },
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "still reports content when trimmed text is blank but media exists",
|
||||
payload: { text: " ", mediaUrls: ["https://example.com/a.png"] },
|
||||
options: { trimText: true },
|
||||
expected: true,
|
||||
},
|
||||
])("$name", ({ payload, options, expected }) => {
|
||||
expect(hasOutboundReplyContent(payload, options)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -186,16 +235,27 @@ describe("resolveSendableOutboundReplyParts", () => {
|
||||
});
|
||||
|
||||
describe("resolveTextChunksWithFallback", () => {
|
||||
it("returns existing chunks unchanged", () => {
|
||||
expect(resolveTextChunksWithFallback("hello", ["a", "b"])).toEqual(["a", "b"]);
|
||||
});
|
||||
|
||||
it("falls back to the full text when chunkers return nothing", () => {
|
||||
expect(resolveTextChunksWithFallback("hello", [])).toEqual(["hello"]);
|
||||
});
|
||||
|
||||
it("returns empty for empty text with no chunks", () => {
|
||||
expect(resolveTextChunksWithFallback("", [])).toEqual([]);
|
||||
it.each([
|
||||
{
|
||||
name: "returns existing chunks unchanged",
|
||||
text: "hello",
|
||||
chunks: ["a", "b"],
|
||||
expected: ["a", "b"],
|
||||
},
|
||||
{
|
||||
name: "falls back to the full text when chunkers return nothing",
|
||||
text: "hello",
|
||||
chunks: [],
|
||||
expected: ["hello"],
|
||||
},
|
||||
{
|
||||
name: "returns empty for empty text with no chunks",
|
||||
text: "",
|
||||
chunks: [],
|
||||
expected: [],
|
||||
},
|
||||
])("$name", ({ text, chunks, expected }) => {
|
||||
expect(resolveTextChunksWithFallback(text, chunks)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -11,44 +11,157 @@ import {
|
||||
createDefaultChannelRuntimeState,
|
||||
} from "./status-helpers.js";
|
||||
|
||||
describe("createDefaultChannelRuntimeState", () => {
|
||||
it("builds default runtime state without extra fields", () => {
|
||||
expect(createDefaultChannelRuntimeState("default")).toEqual({
|
||||
accountId: "default",
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
});
|
||||
});
|
||||
const defaultRuntimeState = {
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
};
|
||||
|
||||
it("merges extra fields into the default runtime state", () => {
|
||||
expect(
|
||||
createDefaultChannelRuntimeState("alerts", {
|
||||
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,
|
||||
}),
|
||||
).toEqual({
|
||||
accountId: "alerts",
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
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({
|
||||
configured: false,
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
});
|
||||
expect(buildBaseChannelStatusSummary({})).toEqual(defaultChannelSummary);
|
||||
});
|
||||
|
||||
it("keeps explicit values", () => {
|
||||
@@ -61,6 +174,7 @@ describe("buildBaseChannelStatusSummary", () => {
|
||||
lastError: "boom",
|
||||
}),
|
||||
).toEqual({
|
||||
...defaultChannelSummary,
|
||||
configured: true,
|
||||
running: true,
|
||||
lastStartAt: 1,
|
||||
@@ -81,13 +195,10 @@ describe("buildBaseChannelStatusSummary", () => {
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
...defaultChannelSummary,
|
||||
configured: true,
|
||||
mode: "webhook",
|
||||
secretSource: "env",
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -98,19 +209,7 @@ describe("buildBaseAccountStatusSnapshot", () => {
|
||||
buildBaseAccountStatusSnapshot({
|
||||
account: { accountId: "default", enabled: true, configured: true },
|
||||
}),
|
||||
).toEqual({
|
||||
accountId: "default",
|
||||
name: undefined,
|
||||
enabled: true,
|
||||
configured: true,
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
probe: undefined,
|
||||
lastInboundAt: null,
|
||||
lastOutboundAt: null,
|
||||
});
|
||||
).toEqual(expectedAccountSnapshot({ enabled: true, configured: true }));
|
||||
});
|
||||
|
||||
it("merges extra snapshot fields after the shared account shape", () => {
|
||||
@@ -125,17 +224,7 @@ describe("buildBaseAccountStatusSnapshot", () => {
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
accountId: "default",
|
||||
name: undefined,
|
||||
enabled: undefined,
|
||||
configured: true,
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
probe: undefined,
|
||||
lastInboundAt: null,
|
||||
lastOutboundAt: null,
|
||||
...expectedAccountSnapshot({ configured: true }),
|
||||
connected: true,
|
||||
mode: "polling",
|
||||
});
|
||||
@@ -150,19 +239,7 @@ describe("buildComputedAccountStatusSnapshot", () => {
|
||||
enabled: true,
|
||||
configured: false,
|
||||
}),
|
||||
).toEqual({
|
||||
accountId: "default",
|
||||
name: undefined,
|
||||
enabled: true,
|
||||
configured: false,
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
probe: undefined,
|
||||
lastInboundAt: null,
|
||||
lastOutboundAt: null,
|
||||
});
|
||||
).toEqual(expectedAccountSnapshot({ enabled: true }));
|
||||
});
|
||||
|
||||
it("merges computed extras after the shared fields", () => {
|
||||
@@ -177,173 +254,100 @@ describe("buildComputedAccountStatusSnapshot", () => {
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
accountId: "default",
|
||||
name: undefined,
|
||||
enabled: undefined,
|
||||
configured: true,
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
probe: undefined,
|
||||
lastInboundAt: null,
|
||||
lastOutboundAt: null,
|
||||
...expectedAccountSnapshot({ configured: true }),
|
||||
connected: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("createComputedAccountStatusAdapter", () => {
|
||||
it("builds account snapshots from computed account metadata and extras", () => {
|
||||
const status = 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,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(
|
||||
status.buildAccountSnapshot?.({
|
||||
account: { accountId: "default", enabled: true, profileUrl: "https://example.test" },
|
||||
cfg: {} as never,
|
||||
runtime: { accountId: "default", running: true },
|
||||
probe: { ok: true },
|
||||
}),
|
||||
).toEqual({
|
||||
accountId: "default",
|
||||
name: undefined,
|
||||
enabled: true,
|
||||
configured: true,
|
||||
running: true,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
probe: { ok: true },
|
||||
lastInboundAt: null,
|
||||
lastOutboundAt: null,
|
||||
profileUrl: "https://example.test",
|
||||
connected: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("createAsyncComputedAccountStatusAdapter", () => {
|
||||
it("builds account snapshots from async computed account metadata and extras", async () => {
|
||||
const status = 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,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await expect(
|
||||
status.buildAccountSnapshot?.({
|
||||
account: { accountId: "default", enabled: true, profileUrl: "https://example.test" },
|
||||
cfg: {} as never,
|
||||
runtime: { accountId: "default", running: true },
|
||||
probe: { ok: true },
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
accountId: "default",
|
||||
name: undefined,
|
||||
enabled: true,
|
||||
configured: true,
|
||||
running: true,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
probe: { ok: true },
|
||||
lastInboundAt: null,
|
||||
lastOutboundAt: null,
|
||||
profileUrl: "https://example.test",
|
||||
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("builds runtime lifecycle fields with defaults", () => {
|
||||
expect(buildRuntimeAccountStatusSnapshot({})).toEqual({
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
probe: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("merges extra fields into runtime snapshots", () => {
|
||||
expect(buildRuntimeAccountStatusSnapshot({}, { port: 3978 })).toEqual({
|
||||
running: false,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
probe: undefined,
|
||||
port: 3978,
|
||||
});
|
||||
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("includes token/probe fields with mode by default", () => {
|
||||
expect(buildTokenChannelStatusSummary({})).toEqual({
|
||||
configured: false,
|
||||
tokenSource: "none",
|
||||
running: false,
|
||||
mode: null,
|
||||
lastStartAt: null,
|
||||
lastStopAt: null,
|
||||
lastError: null,
|
||||
probe: undefined,
|
||||
lastProbeAt: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("can omit mode for channels without a mode state", () => {
|
||||
expect(
|
||||
buildTokenChannelStatusSummary(
|
||||
{
|
||||
configured: true,
|
||||
tokenSource: "env",
|
||||
running: true,
|
||||
lastStartAt: 1,
|
||||
lastStopAt: 2,
|
||||
lastError: "boom",
|
||||
probe: { ok: true },
|
||||
lastProbeAt: 3,
|
||||
},
|
||||
{ includeMode: false },
|
||||
),
|
||||
).toEqual({
|
||||
configured: true,
|
||||
tokenSource: "env",
|
||||
running: true,
|
||||
lastStartAt: 1,
|
||||
lastStopAt: 2,
|
||||
lastError: "boom",
|
||||
probe: { ok: true },
|
||||
lastProbeAt: 3,
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user