mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-27 09:21:35 +07:00
Doctor: warn on partial missing-default account coverage
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
import { runDoctorConfigWithInput } from "./doctor-config-flow.test-utils.js";
|
||||
|
||||
const { noteSpy } = vi.hoisted(() => ({
|
||||
noteSpy: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../terminal/note.js", () => ({
|
||||
note: noteSpy,
|
||||
}));
|
||||
|
||||
vi.mock("./doctor-legacy-config.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./doctor-legacy-config.js")>();
|
||||
return {
|
||||
...actual,
|
||||
normalizeLegacyConfigValues: (cfg: unknown) => ({
|
||||
config: cfg,
|
||||
changes: [],
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
import { loadAndMaybeMigrateDoctorConfig } from "./doctor-config-flow.js";
|
||||
|
||||
describe("doctor missing default account binding warning", () => {
|
||||
it("emits a doctor warning when named accounts have no valid account-scoped bindings", async () => {
|
||||
await withEnvAsync(
|
||||
{
|
||||
TELEGRAM_BOT_TOKEN: undefined,
|
||||
TELEGRAM_BOT_TOKEN_FILE: undefined,
|
||||
},
|
||||
async () => {
|
||||
await runDoctorConfigWithInput({
|
||||
config: {
|
||||
channels: {
|
||||
telegram: {
|
||||
accounts: {
|
||||
alerts: {},
|
||||
work: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
bindings: [{ agentId: "ops", match: { channel: "telegram" } }],
|
||||
},
|
||||
run: loadAndMaybeMigrateDoctorConfig,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
expect(noteSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("channels.telegram: accounts.default is missing"),
|
||||
"Doctor warnings",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -37,6 +37,25 @@ describe("collectMissingDefaultAccountBindingWarnings", () => {
|
||||
expect(collectMissingDefaultAccountBindingWarnings(cfg)).toEqual([]);
|
||||
});
|
||||
|
||||
it("warns when bindings cover only a subset of configured accounts", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: {
|
||||
telegram: {
|
||||
accounts: {
|
||||
alerts: { botToken: "a" },
|
||||
work: { botToken: "w" },
|
||||
},
|
||||
},
|
||||
},
|
||||
bindings: [{ agentId: "ops", match: { channel: "telegram", accountId: "alerts" } }],
|
||||
};
|
||||
|
||||
const warnings = collectMissingDefaultAccountBindingWarnings(cfg);
|
||||
expect(warnings).toHaveLength(1);
|
||||
expect(warnings[0]).toContain("subset");
|
||||
expect(warnings[0]).toContain("Uncovered accounts: work");
|
||||
});
|
||||
|
||||
it("does not warn when wildcard account binding exists", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: {
|
||||
|
||||
@@ -249,33 +249,52 @@ export function collectMissingDefaultAccountBindingWarnings(cfg: OpenClawConfig)
|
||||
const accountIdSet = new Set(normalizedAccountIds);
|
||||
const channelPattern = normalizeBindingChannelKey(channelKey);
|
||||
|
||||
const hasValidBinding = bindings.some((binding) => {
|
||||
let hasWildcardBinding = false;
|
||||
const coveredAccountIds = new Set<string>();
|
||||
for (const binding of bindings) {
|
||||
const bindingRecord = asObjectRecord(binding);
|
||||
if (!bindingRecord) {
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
const match = asObjectRecord(bindingRecord.match);
|
||||
if (!match) {
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
const matchChannel =
|
||||
typeof match.channel === "string" ? normalizeBindingChannelKey(match.channel) : "";
|
||||
if (!matchChannel || matchChannel !== channelPattern) {
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
const rawAccountId = typeof match.accountId === "string" ? match.accountId.trim() : "";
|
||||
if (!rawAccountId) {
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
if (rawAccountId === "*") {
|
||||
return true;
|
||||
hasWildcardBinding = true;
|
||||
continue;
|
||||
}
|
||||
return accountIdSet.has(normalizeAccountId(rawAccountId));
|
||||
});
|
||||
const normalizedBindingAccountId = normalizeAccountId(rawAccountId);
|
||||
if (accountIdSet.has(normalizedBindingAccountId)) {
|
||||
coveredAccountIds.add(normalizedBindingAccountId);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasValidBinding) {
|
||||
if (hasWildcardBinding) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uncoveredAccountIds = normalizedAccountIds.filter(
|
||||
(accountId) => !coveredAccountIds.has(accountId),
|
||||
);
|
||||
if (uncoveredAccountIds.length === 0) {
|
||||
continue;
|
||||
}
|
||||
if (coveredAccountIds.size > 0) {
|
||||
warnings.push(
|
||||
`- channels.${channelKey}: accounts.default is missing and account bindings only cover a subset of configured accounts. Uncovered accounts: ${uncoveredAccountIds.join(", ")}. Add bindings[].match.accountId for uncovered accounts (or "*"), or add channels.${channelKey}.accounts.default.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user