diff --git a/extensions/shared/status-issues.ts b/extensions/shared/status-issues.ts new file mode 100644 index 00000000000..1eb39e2b686 --- /dev/null +++ b/extensions/shared/status-issues.ts @@ -0,0 +1,18 @@ +export function readStatusIssueFields( + value: unknown, + fields: readonly TField[], +): Record | null { + if (!value || typeof value !== "object") { + return null; + } + const record = value as Record; + const result = {} as Record; + for (const field of fields) { + result[field] = record[field]; + } + return result; +} + +export function coerceStatusIssueAccountId(value: unknown): string | undefined { + return typeof value === "string" ? value : typeof value === "number" ? String(value) : undefined; +} diff --git a/extensions/zalo/src/status-issues.test.ts b/extensions/zalo/src/status-issues.test.ts new file mode 100644 index 00000000000..1326ba13604 --- /dev/null +++ b/extensions/zalo/src/status-issues.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { collectZaloStatusIssues } from "./status-issues.js"; + +describe("collectZaloStatusIssues", () => { + it("warns when dmPolicy is open", () => { + const issues = collectZaloStatusIssues([ + { + accountId: "default", + enabled: true, + configured: true, + dmPolicy: "open", + }, + ]); + expect(issues).toHaveLength(1); + expect(issues[0]?.kind).toBe("config"); + }); + + it("skips unconfigured accounts", () => { + const issues = collectZaloStatusIssues([ + { + accountId: "default", + enabled: true, + configured: false, + dmPolicy: "open", + }, + ]); + expect(issues).toHaveLength(0); + }); +}); diff --git a/extensions/zalo/src/status-issues.ts b/extensions/zalo/src/status-issues.ts index cf6b3a3a384..c19992a64ee 100644 --- a/extensions/zalo/src/status-issues.ts +++ b/extensions/zalo/src/status-issues.ts @@ -1,38 +1,16 @@ import type { ChannelAccountSnapshot, ChannelStatusIssue } from "openclaw/plugin-sdk/zalo"; +import { coerceStatusIssueAccountId, readStatusIssueFields } from "../../shared/status-issues.js"; -type ZaloAccountStatus = { - accountId?: unknown; - enabled?: unknown; - configured?: unknown; - dmPolicy?: unknown; -}; - -const isRecord = (value: unknown): value is Record => - Boolean(value && typeof value === "object"); - -const asString = (value: unknown): string | undefined => - typeof value === "string" ? value : typeof value === "number" ? String(value) : undefined; - -function readZaloAccountStatus(value: ChannelAccountSnapshot): ZaloAccountStatus | null { - if (!isRecord(value)) { - return null; - } - return { - accountId: value.accountId, - enabled: value.enabled, - configured: value.configured, - dmPolicy: value.dmPolicy, - }; -} +const ZALO_STATUS_FIELDS = ["accountId", "enabled", "configured", "dmPolicy"] as const; export function collectZaloStatusIssues(accounts: ChannelAccountSnapshot[]): ChannelStatusIssue[] { const issues: ChannelStatusIssue[] = []; for (const entry of accounts) { - const account = readZaloAccountStatus(entry); + const account = readStatusIssueFields(entry, ZALO_STATUS_FIELDS); if (!account) { continue; } - const accountId = asString(account.accountId) ?? "default"; + const accountId = coerceStatusIssueAccountId(account.accountId) ?? "default"; const enabled = account.enabled !== false; const configured = account.configured === true; if (!enabled || !configured) { diff --git a/extensions/zalouser/src/status-issues.ts b/extensions/zalouser/src/status-issues.ts index fca889a5115..b42c915e00a 100644 --- a/extensions/zalouser/src/status-issues.ts +++ b/extensions/zalouser/src/status-issues.ts @@ -1,42 +1,24 @@ import type { ChannelAccountSnapshot, ChannelStatusIssue } from "openclaw/plugin-sdk/zalouser"; +import { coerceStatusIssueAccountId, readStatusIssueFields } from "../../shared/status-issues.js"; -type ZalouserAccountStatus = { - accountId?: unknown; - enabled?: unknown; - configured?: unknown; - dmPolicy?: unknown; - lastError?: unknown; -}; - -const isRecord = (value: unknown): value is Record => - Boolean(value && typeof value === "object"); - -const asString = (value: unknown): string | undefined => - typeof value === "string" ? value : typeof value === "number" ? String(value) : undefined; - -function readZalouserAccountStatus(value: ChannelAccountSnapshot): ZalouserAccountStatus | null { - if (!isRecord(value)) { - return null; - } - return { - accountId: value.accountId, - enabled: value.enabled, - configured: value.configured, - dmPolicy: value.dmPolicy, - lastError: value.lastError, - }; -} +const ZALOUSER_STATUS_FIELDS = [ + "accountId", + "enabled", + "configured", + "dmPolicy", + "lastError", +] as const; export function collectZalouserStatusIssues( accounts: ChannelAccountSnapshot[], ): ChannelStatusIssue[] { const issues: ChannelStatusIssue[] = []; for (const entry of accounts) { - const account = readZalouserAccountStatus(entry); + const account = readStatusIssueFields(entry, ZALOUSER_STATUS_FIELDS); if (!account) { continue; } - const accountId = asString(account.accountId) ?? "default"; + const accountId = coerceStatusIssueAccountId(account.accountId) ?? "default"; const enabled = account.enabled !== false; if (!enabled) { continue;