mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:00:42 +00:00
test: trim doctor command hotspots
This commit is contained in:
@@ -111,6 +111,133 @@ vi.mock("../channels/plugins/bootstrap-registry.js", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../channels/plugins/doctor-contract-api.js", () => ({
|
||||
loadBundledChannelDoctorContractApi: vi.fn(() => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("../channels/plugins/setup-promotion-helpers.js", () => {
|
||||
const commonSingleAccountKeys = new Set([
|
||||
"name",
|
||||
"token",
|
||||
"tokenFile",
|
||||
"botToken",
|
||||
"appToken",
|
||||
"account",
|
||||
"signalNumber",
|
||||
"authDir",
|
||||
"cliPath",
|
||||
"dbPath",
|
||||
"httpUrl",
|
||||
"httpHost",
|
||||
"httpPort",
|
||||
"webhookPath",
|
||||
"webhookUrl",
|
||||
"webhookSecret",
|
||||
"service",
|
||||
"region",
|
||||
"homeserver",
|
||||
"userId",
|
||||
"accessToken",
|
||||
"password",
|
||||
"deviceName",
|
||||
"url",
|
||||
"code",
|
||||
"dmPolicy",
|
||||
"allowFrom",
|
||||
"groupPolicy",
|
||||
"groupAllowFrom",
|
||||
"defaultTo",
|
||||
]);
|
||||
const fallbackSingleAccountKeys: Record<string, readonly string[]> = {
|
||||
telegram: ["streaming"],
|
||||
};
|
||||
const namedAccountPromotionKeys: Record<string, readonly string[]> = {
|
||||
telegram: ["botToken", "tokenFile"],
|
||||
};
|
||||
|
||||
return {
|
||||
resolveSingleAccountKeysToMove: ({
|
||||
channelKey,
|
||||
channel,
|
||||
}: {
|
||||
channelKey: string;
|
||||
channel: Record<string, unknown>;
|
||||
}) => {
|
||||
const accounts =
|
||||
channel.accounts && typeof channel.accounts === "object" && !Array.isArray(channel.accounts)
|
||||
? (channel.accounts as Record<string, unknown>)
|
||||
: {};
|
||||
const hasNamedAccounts = Object.keys(accounts).filter(Boolean).length > 0;
|
||||
const allowedNamedKeys = namedAccountPromotionKeys[channelKey];
|
||||
return Object.entries(channel)
|
||||
.filter(([key, value]) => {
|
||||
if (key === "accounts" || key === "enabled" || value === undefined) {
|
||||
return false;
|
||||
}
|
||||
const isKnownKey =
|
||||
commonSingleAccountKeys.has(key) ||
|
||||
(fallbackSingleAccountKeys[channelKey]?.includes(key) ?? false);
|
||||
if (!isKnownKey) {
|
||||
return false;
|
||||
}
|
||||
if (hasNamedAccounts && allowedNamedKeys && !allowedNamedKeys.includes(key)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map(([key]) => key);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./doctor/shared/channel-legacy-config-migrate.js", () => ({
|
||||
applyChannelDoctorCompatibilityMigrations: (cfg: Record<string, unknown>) => ({
|
||||
next: cfg,
|
||||
changes: [],
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("./doctor/channel-capabilities.js", () => {
|
||||
const byChannel = {
|
||||
googlechat: {
|
||||
dmAllowFromMode: "nestedOnly",
|
||||
groupModel: "route",
|
||||
groupAllowFromFallbackToAllowFrom: false,
|
||||
warnOnEmptyGroupSenderAllowlist: false,
|
||||
},
|
||||
matrix: {
|
||||
dmAllowFromMode: "nestedOnly",
|
||||
groupModel: "sender",
|
||||
groupAllowFromFallbackToAllowFrom: false,
|
||||
warnOnEmptyGroupSenderAllowlist: true,
|
||||
},
|
||||
msteams: {
|
||||
dmAllowFromMode: "topOnly",
|
||||
groupModel: "hybrid",
|
||||
groupAllowFromFallbackToAllowFrom: false,
|
||||
warnOnEmptyGroupSenderAllowlist: true,
|
||||
},
|
||||
zalouser: {
|
||||
dmAllowFromMode: "topOnly",
|
||||
groupModel: "hybrid",
|
||||
groupAllowFromFallbackToAllowFrom: false,
|
||||
warnOnEmptyGroupSenderAllowlist: false,
|
||||
},
|
||||
} as const;
|
||||
const fallback = {
|
||||
dmAllowFromMode: "topOnly",
|
||||
groupModel: "sender",
|
||||
groupAllowFromFallbackToAllowFrom: true,
|
||||
warnOnEmptyGroupSenderAllowlist: true,
|
||||
};
|
||||
return {
|
||||
getDoctorChannelCapabilities: (channelName?: string) =>
|
||||
channelName && channelName in byChannel
|
||||
? byChannel[channelName as keyof typeof byChannel]
|
||||
: fallback,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../plugins/doctor-contract-registry.js", () => {
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
|
||||
@@ -12,16 +12,6 @@ vi.mock("../plugins/setup-registry.js", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
function asLegacyConfig(value: unknown): OpenClawConfig {
|
||||
return value as OpenClawConfig;
|
||||
}
|
||||
|
||||
function getLegacyProperty(value: unknown, key: string): unknown {
|
||||
if (!value || typeof value !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
return (value as Record<string, unknown>)[key];
|
||||
}
|
||||
describe("normalizeCompatibilityConfigValues", () => {
|
||||
let previousOauthDir: string | undefined;
|
||||
let tempOauthDir = "";
|
||||
@@ -90,201 +80,6 @@ describe("normalizeCompatibilityConfigValues", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("migrates Slack dm.policy/dm.allowFrom to dmPolicy/allowFrom aliases", () => {
|
||||
const res = normalizeCompatibilityConfigValues({
|
||||
channels: {
|
||||
slack: {
|
||||
dm: { enabled: true, policy: "open", allowFrom: ["*"] },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.config.channels?.slack?.dmPolicy).toBe("open");
|
||||
expect(res.config.channels?.slack?.allowFrom).toEqual(["*"]);
|
||||
expect(res.config.channels?.slack?.dm).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(res.changes).toEqual([
|
||||
"Moved channels.slack.dm.policy → channels.slack.dmPolicy.",
|
||||
"Moved channels.slack.dm.allowFrom → channels.slack.allowFrom.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("migrates Discord account dm.policy/dm.allowFrom to dmPolicy/allowFrom aliases", () => {
|
||||
const res = normalizeCompatibilityConfigValues({
|
||||
channels: {
|
||||
discord: {
|
||||
accounts: {
|
||||
work: {
|
||||
dm: { policy: "allowlist", allowFrom: ["123"], groupEnabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.config.channels?.discord?.accounts?.work?.dmPolicy).toBe("allowlist");
|
||||
expect(res.config.channels?.discord?.accounts?.work?.allowFrom).toEqual(["123"]);
|
||||
expect(res.config.channels?.discord?.accounts?.work?.dm).toEqual({
|
||||
groupEnabled: true,
|
||||
});
|
||||
expect(res.changes).toEqual([
|
||||
"Moved channels.discord.accounts.work.dm.policy → channels.discord.accounts.work.dmPolicy.",
|
||||
"Moved channels.discord.accounts.work.dm.allowFrom → channels.discord.accounts.work.allowFrom.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("migrates Discord streaming boolean alias into nested streaming.mode", () => {
|
||||
const res = normalizeCompatibilityConfigValues(
|
||||
asLegacyConfig({
|
||||
channels: {
|
||||
discord: {
|
||||
streaming: true,
|
||||
accounts: {
|
||||
work: {
|
||||
streaming: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.config.channels?.discord?.streaming).toEqual({ mode: "partial" });
|
||||
expect(getLegacyProperty(res.config.channels?.discord, "streamMode")).toBeUndefined();
|
||||
expect(res.config.channels?.discord?.accounts?.work?.streaming).toEqual({ mode: "off" });
|
||||
expect(
|
||||
getLegacyProperty(res.config.channels?.discord?.accounts?.work, "streamMode"),
|
||||
).toBeUndefined();
|
||||
expect(res.changes).toEqual([
|
||||
"Moved channels.discord.streaming (boolean) → channels.discord.streaming.mode (partial).",
|
||||
"Moved channels.discord.accounts.work.streaming (boolean) → channels.discord.accounts.work.streaming.mode (off).",
|
||||
]);
|
||||
});
|
||||
|
||||
it("migrates Discord legacy streamMode into nested streaming.mode", () => {
|
||||
const res = normalizeCompatibilityConfigValues(
|
||||
asLegacyConfig({
|
||||
channels: {
|
||||
discord: {
|
||||
streaming: false,
|
||||
streamMode: "block",
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.config.channels?.discord?.streaming).toEqual({ mode: "block" });
|
||||
expect(getLegacyProperty(res.config.channels?.discord, "streamMode")).toBeUndefined();
|
||||
expect(res.changes).toEqual([
|
||||
"Moved channels.discord.streamMode → channels.discord.streaming.mode (block).",
|
||||
]);
|
||||
});
|
||||
|
||||
it("migrates Telegram streamMode into nested streaming.mode", () => {
|
||||
const res = normalizeCompatibilityConfigValues(
|
||||
asLegacyConfig({
|
||||
channels: {
|
||||
telegram: {
|
||||
streamMode: "block",
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.config.channels?.telegram?.streaming).toEqual({ mode: "block" });
|
||||
expect(getLegacyProperty(res.config.channels?.telegram, "streamMode")).toBeUndefined();
|
||||
expect(res.changes).toEqual([
|
||||
"Moved channels.telegram.streamMode → channels.telegram.streaming.mode (block).",
|
||||
]);
|
||||
});
|
||||
|
||||
it("migrates Slack legacy streaming keys into nested streaming config", () => {
|
||||
const res = normalizeCompatibilityConfigValues(
|
||||
asLegacyConfig({
|
||||
channels: {
|
||||
slack: {
|
||||
streaming: false,
|
||||
streamMode: "status_final",
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(res.config.channels?.slack?.streaming).toEqual({
|
||||
mode: "progress",
|
||||
nativeTransport: false,
|
||||
});
|
||||
expect(getLegacyProperty(res.config.channels?.slack, "streamMode")).toBeUndefined();
|
||||
expect(res.changes).toEqual([
|
||||
"Moved channels.slack.streamMode → channels.slack.streaming.mode (progress).",
|
||||
"Moved channels.slack.streaming (boolean) → channels.slack.streaming.nativeTransport.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("preserves top-level Telegram allowlist fallback for existing named accounts", () => {
|
||||
const res = normalizeCompatibilityConfigValues({
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["123"],
|
||||
groupPolicy: "allowlist",
|
||||
accounts: {
|
||||
bot1: {
|
||||
enabled: true,
|
||||
botToken: "bot-1-token",
|
||||
},
|
||||
bot2: {
|
||||
enabled: true,
|
||||
botToken: "bot-2-token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.config.channels?.telegram?.dmPolicy).toBe("allowlist");
|
||||
expect(res.config.channels?.telegram?.allowFrom).toEqual(["123"]);
|
||||
expect(res.config.channels?.telegram?.groupPolicy).toBe("allowlist");
|
||||
expect(res.config.channels?.telegram?.accounts?.bot1?.botToken).toBe("bot-1-token");
|
||||
expect(res.config.channels?.telegram?.accounts?.bot2?.botToken).toBe("bot-2-token");
|
||||
expect(res.changes).not.toContain(
|
||||
"Moved channels.telegram single-account top-level values into channels.telegram.accounts.default.",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps Telegram policy fallback top-level while still seeding default auth", () => {
|
||||
const res = normalizeCompatibilityConfigValues({
|
||||
channels: {
|
||||
telegram: {
|
||||
enabled: true,
|
||||
botToken: "legacy-token",
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: ["123"],
|
||||
groupPolicy: "allowlist",
|
||||
accounts: {
|
||||
bot1: {
|
||||
enabled: true,
|
||||
botToken: "bot-1-token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.config.channels?.telegram?.accounts?.default).toMatchObject({
|
||||
botToken: "legacy-token",
|
||||
});
|
||||
expect(res.config.channels?.telegram?.botToken).toBeUndefined();
|
||||
expect(res.config.channels?.telegram?.dmPolicy).toBe("allowlist");
|
||||
expect(res.config.channels?.telegram?.allowFrom).toEqual(["123"]);
|
||||
expect(res.config.channels?.telegram?.groupPolicy).toBe("allowlist");
|
||||
expect(res.changes).toContain(
|
||||
"Moved channels.telegram single-account top-level values into channels.telegram.accounts.default.",
|
||||
);
|
||||
});
|
||||
|
||||
it("migrates browser ssrfPolicy allowPrivateNetwork to dangerouslyAllowPrivateNetwork", () => {
|
||||
const res = normalizeCompatibilityConfigValues({
|
||||
browser: {
|
||||
|
||||
@@ -364,10 +364,22 @@ vi.mock("./doctor-memory-search.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/doctor-contract-registry.js", () => ({
|
||||
applyPluginDoctorCompatibilityMigrations: (config: unknown) => ({
|
||||
config,
|
||||
changes: [],
|
||||
}),
|
||||
collectRelevantDoctorPluginIds,
|
||||
listPluginDoctorLegacyConfigRules,
|
||||
}));
|
||||
|
||||
vi.mock("../channels/plugins/doctor-contract-api.js", () => ({
|
||||
loadBundledChannelDoctorContractApi: vi.fn(() => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("../channels/plugins/bootstrap-registry.js", () => ({
|
||||
getBootstrapChannelPlugin: vi.fn(() => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("./doctor-bundled-plugin-runtime-deps.js", () => ({
|
||||
maybeRepairBundledPluginRuntimeDeps: vi.fn(async () => {}),
|
||||
}));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
|
||||
import { runPluginSetupConfigMigrations } from "../../../plugins/setup-registry.js";
|
||||
import { collectChannelDoctorCompatibilityMutations } from "./channel-doctor.js";
|
||||
import { applyChannelDoctorCompatibilityMigrations } from "./channel-legacy-config-migrate.js";
|
||||
import {
|
||||
normalizeLegacyBrowserConfig,
|
||||
normalizeLegacyCrossContextMessageConfig,
|
||||
@@ -48,12 +48,10 @@ export function normalizeCompatibilityConfigValues(cfg: OpenClawConfig): {
|
||||
next = normalizeLegacyCrossContextMessageConfig(next, changes);
|
||||
next = normalizeLegacyMediaProviderOptions(next, changes);
|
||||
next = normalizeLegacyMistralModelMaxTokens(next, changes);
|
||||
for (const mutation of collectChannelDoctorCompatibilityMutations(next)) {
|
||||
if (mutation.changes.length === 0) {
|
||||
continue;
|
||||
}
|
||||
next = mutation.config;
|
||||
changes.push(...mutation.changes);
|
||||
const channelMigrations = applyChannelDoctorCompatibilityMigrations(next);
|
||||
if (channelMigrations.changes.length > 0) {
|
||||
next = channelMigrations.next;
|
||||
changes.push(...channelMigrations.changes);
|
||||
}
|
||||
|
||||
return { config: next, changes };
|
||||
|
||||
Reference in New Issue
Block a user