refactor: share doctor missing-default account scan

This commit is contained in:
Gustavo Madeira Santana 2026-03-03 03:18:23 -05:00
parent 6a8b5d5e50
commit bf984f37fb
3 changed files with 51 additions and 38 deletions

View File

@ -66,6 +66,22 @@ describe("collectMissingExplicitDefaultAccountWarnings", () => {
expect(collectMissingExplicitDefaultAccountWarnings(cfg)).toEqual([]);
});
it("normalizes defaultAccount before validating configured account ids", () => {
const cfg: OpenClawConfig = {
channels: {
telegram: {
defaultAccount: "Router D",
accounts: {
"router-d": { botToken: "r" },
work: { botToken: "w" },
},
},
},
};
expect(collectMissingExplicitDefaultAccountWarnings(cfg)).toEqual([]);
});
it("warns when defaultAccount is invalid for configured accounts", () => {
const cfg: OpenClawConfig = {
channels: {

View File

@ -219,15 +219,21 @@ function normalizeBindingChannelKey(raw?: string | null): string {
return (raw ?? "").trim().toLowerCase();
}
export function collectMissingDefaultAccountBindingWarnings(cfg: OpenClawConfig): string[] {
type ChannelMissingDefaultAccountContext = {
channelKey: string;
channel: Record<string, unknown>;
normalizedAccountIds: string[];
};
function collectChannelsMissingDefaultAccount(
cfg: OpenClawConfig,
): ChannelMissingDefaultAccountContext[] {
const channels = asObjectRecord(cfg.channels);
if (!channels) {
return [];
}
const bindings = Array.isArray(cfg.bindings) ? cfg.bindings : [];
const warnings: string[] = [];
const contexts: ChannelMissingDefaultAccountContext[] = [];
for (const [channelKey, rawChannel] of Object.entries(channels)) {
const channel = asObjectRecord(rawChannel);
if (!channel) {
@ -244,10 +250,20 @@ export function collectMissingDefaultAccountBindingWarnings(cfg: OpenClawConfig)
.map((accountId) => normalizeAccountId(accountId))
.filter(Boolean),
),
);
).toSorted((a, b) => a.localeCompare(b));
if (normalizedAccountIds.length === 0 || normalizedAccountIds.includes(DEFAULT_ACCOUNT_ID)) {
continue;
}
contexts.push({ channelKey, channel, normalizedAccountIds });
}
return contexts;
}
export function collectMissingDefaultAccountBindingWarnings(cfg: OpenClawConfig): string[] {
const bindings = Array.isArray(cfg.bindings) ? cfg.bindings : [];
const warnings: string[] = [];
for (const { channelKey, normalizedAccountIds } of collectChannelsMissingDefaultAccount(cfg)) {
const accountIdSet = new Set(normalizedAccountIds);
const channelPattern = normalizeBindingChannelKey(channelKey);
@ -309,31 +325,11 @@ export function collectMissingDefaultAccountBindingWarnings(cfg: OpenClawConfig)
}
export function collectMissingExplicitDefaultAccountWarnings(cfg: OpenClawConfig): string[] {
const channels = asObjectRecord(cfg.channels);
if (!channels) {
return [];
}
const warnings: string[] = [];
for (const [channelKey, rawChannel] of Object.entries(channels)) {
const channel = asObjectRecord(rawChannel);
if (!channel) {
continue;
}
const accounts = asObjectRecord(channel.accounts);
if (!accounts) {
continue;
}
const normalizedAccountIds = Array.from(
new Set(
Object.keys(accounts)
.map((accountId) => normalizeAccountId(accountId))
.filter(Boolean),
),
).toSorted((a, b) => a.localeCompare(b));
if (normalizedAccountIds.length < 2 || normalizedAccountIds.includes(DEFAULT_ACCOUNT_ID)) {
for (const { channelKey, channel, normalizedAccountIds } of collectChannelsMissingDefaultAccount(
cfg,
)) {
if (normalizedAccountIds.length < 2) {
continue;
}

View File

@ -12,6 +12,10 @@ const { warnMock } = vi.hoisted(() => ({
warnMock: vi.fn(),
}));
function warningLines(): string[] {
return warnMock.mock.calls.map(([line]) => String(line));
}
vi.mock("../logging/subsystem.js", () => ({
createSubsystemLogger: () => {
const logger = {
@ -140,8 +144,7 @@ describe("resolveDefaultTelegramAccountId", () => {
};
resolveDefaultTelegramAccountId(cfg);
const warnLines = warnMock.mock.calls.map(([line]) => String(line));
expect(warnLines.every((line: string) => !line.includes("accounts.default is missing"))).toBe(
expect(warningLines().every((line) => !line.includes("accounts.default is missing"))).toBe(
true,
);
});
@ -157,8 +160,7 @@ describe("resolveDefaultTelegramAccountId", () => {
};
resolveDefaultTelegramAccountId(cfg);
const warnLines = warnMock.mock.calls.map(([line]) => String(line));
expect(warnLines.every((line: string) => !line.includes("accounts.default is missing"))).toBe(
expect(warningLines().every((line) => !line.includes("accounts.default is missing"))).toBe(
true,
);
});
@ -173,8 +175,7 @@ describe("resolveDefaultTelegramAccountId", () => {
};
resolveDefaultTelegramAccountId(cfg);
const warnLines = warnMock.mock.calls.map(([line]) => String(line));
expect(warnLines.every((line: string) => !line.includes("accounts.default is missing"))).toBe(
expect(warningLines().every((line) => !line.includes("accounts.default is missing"))).toBe(
true,
);
});
@ -192,9 +193,9 @@ describe("resolveDefaultTelegramAccountId", () => {
resolveDefaultTelegramAccountId(cfg);
resolveDefaultTelegramAccountId(cfg);
const missingDefaultWarns = warnMock.mock.calls
.map(([line]) => String(line))
.filter((line) => line.includes("accounts.default is missing"));
const missingDefaultWarns = warningLines().filter((line) =>
line.includes("accounts.default is missing"),
);
expect(missingDefaultWarns).toHaveLength(1);
});