import { describe, expect, it, vi } from "vitest" ;
import type { OpenClawConfig } from "../../config/config.js" ;
import { runDoctorRepairSequence } from "./repair-sequencing.js" ;
vi.mock("./shared/channel-doctor.js" , () => ({
collectChannelDoctorRepairMutations: ({ cfg }: { cfg: OpenClawConfig }) => {
const allowFrom = cfg.channels?.discord?.allowFrom as unknown[] | undefined;
if (allowFrom?.[0 ] === 123 ) {
return [
{
config: {
...cfg,
channels: {
...cfg.channels,
discord: {
...cfg.channels?.discord,
allowFrom: ["123" ],
},
},
},
changes: ["channels.discord.allowFrom: converted 1 numeric ID to strings" ],
},
];
}
if (allowFrom?.[0 ] === 106232522769186816 ) {
return [
{
config: cfg,
changes: [],
warnings: [
"channels.discord.allowFrom[0] cannot be auto-repaired because it is not a safe integer" ,
],
},
];
}
return [];
},
createChannelDoctorEmptyAllowlistPolicyHooks: () => ({
extraWarningsForAccount: () => [],
shouldSkipDefaultEmptyGroupAllowlistWarning: () => false ,
}),
}));
vi.mock("./shared/empty-allowlist-scan.js" , () => ({
scanEmptyAllowlistPolicyWarnings: (cfg: OpenClawConfig) =>
cfg.channels?.signal
? ["channels.signal.accounts.ops\u001B[31m-team\u001B[0m\r\nnext.dmPolicy warning" ]
: [],
}));
vi.mock("./shared/allowlist-policy-repair.js" , () => ({
maybeRepairAllowlistPolicyAllowFrom: async (cfg: OpenClawConfig) => ({
config: cfg,
changes: [],
}),
}));
vi.mock("./shared/bundled-plugin-load-paths.js" , () => ({
maybeRepairBundledPluginLoadPaths: (cfg: OpenClawConfig) => ({
config: cfg,
changes: [],
}),
}));
vi.mock("./shared/open-policy-allowfrom.js" , () => ({
maybeRepairOpenPolicyAllowFrom: (cfg: OpenClawConfig) => ({
config: cfg,
changes: [],
}),
}));
vi.mock("./shared/stale-plugin-config.js" , () => ({
maybeRepairStalePluginConfig: (cfg: OpenClawConfig) => ({
config: cfg,
changes: [],
}),
}));
vi.mock("./shared/legacy-tools-by-sender.js" , () => ({
maybeRepairLegacyToolsBySenderKeys: (cfg: OpenClawConfig) => {
const channels = cfg.channels as Record<string, unknown> | undefined;
const tools = channels?.tools as
| { exec?: { toolsBySender?: Record<string, unknown> } }
| undefined;
const bySender = tools?.exec?.toolsBySender;
const rawKey = bySender
? Object.keys(bySender).find((key) => !key.startsWith("id:" ))
: undefined;
if (!bySender || !rawKey) {
return { config: cfg, changes: [] };
}
const targetKey = `id:${rawKey.trim()}`;
return {
config: {
...cfg,
channels: {
...cfg.channels,
tools: {
...(channels?.tools as Record<string, unknown> | undefined),
exec: {
...tools?.exec,
toolsBySender: {
[targetKey]: bySender[rawKey],
},
},
},
},
},
changes: [
`channels.tools.exec.toolsBySender: migrated 1 legacy key to typed id: entries (${rawKey} -> ${targetKey})`,
],
};
},
}));
vi.mock("./shared/exec-safe-bins.js" , () => ({
maybeRepairExecSafeBinProfiles: (cfg: OpenClawConfig) => ({
config: cfg,
changes: [],
}),
}));
describe("doctor repair sequencing" , () => {
it("applies ordered repairs and sanitizes empty-allowlist warnings" , async () => {
const result = await runDoctorRepairSequence({
state: {
cfg: {
channels: {
discord: {
allowFrom: [123 ],
},
tools: {
exec: {
toolsBySender: {
"bad\u001B[31m-key\u001B[0m\r\nnext" : { enabled: true },
},
},
},
signal: {
accounts: {
"ops\u001B[31m-team\u001B[0m\r\nnext" : {
dmPolicy: "allowlist" ,
},
},
},
},
} as unknown as OpenClawConfig,
candidate: {
channels: {
discord: {
allowFrom: [123 ],
},
tools: {
exec: {
toolsBySender: {
"bad\u001B[31m-key\u001B[0m\r\nnext" : { enabled: true },
},
},
},
signal: {
accounts: {
"ops\u001B[31m-team\u001B[0m\r\nnext" : {
dmPolicy: "allowlist" ,
},
},
},
},
} as unknown as OpenClawConfig,
pendingChanges: false ,
fixHints: [],
},
doctorFixCommand: "openclaw doctor --fix" ,
});
expect(result.state.pendingChanges).toBe(true );
expect(result.state.candidate.channels?.discord?.allowFrom).toEqual(["123" ]);
expect(result.changeNotes).toEqual(
expect.arrayContaining([
expect.stringContaining("channels.discord.allowFrom: converted 1 numeric ID to strings" ),
expect.stringContaining(
"channels.tools.exec.toolsBySender: migrated 1 legacy key to typed id: entries" ,
),
]),
);
expect(result.changeNotes.join("\n" )).toContain("bad-keynext -> id:bad-keynext" );
expect(result.changeNotes.join("\n" )).not.toContain("\u001B" );
expect(result.changeNotes.join("\n" )).not.toContain("\r" );
expect(result.warningNotes).toEqual(
expect.arrayContaining([
expect.stringContaining("channels.signal.accounts.ops-teamnext.dmPolicy" ),
]),
);
expect(result.warningNotes.join("\n" )).not.toContain("\u001B" );
expect(result.warningNotes.join("\n" )).not.toContain("\r" );
});
it("emits Discord warnings when unsafe numeric ids block repair" , async () => {
const result = await runDoctorRepairSequence({
state: {
cfg: {
channels: {
discord: {
allowFrom: [106232522769186816 ],
},
},
} as unknown as OpenClawConfig,
candidate: {
channels: {
discord: {
allowFrom: [106232522769186816 ],
},
},
} as unknown as OpenClawConfig,
pendingChanges: false ,
fixHints: [],
},
doctorFixCommand: "openclaw doctor --fix" ,
});
expect(result.changeNotes).toEqual([]);
expect(result.warningNotes).toHaveLength(1 );
expect(result.warningNotes[0 ]).toContain("cannot be auto-repaired" );
expect(result.warningNotes[0 ]).toContain("channels.discord.allowFrom[0]" );
expect(result.state.pendingChanges).toBe(false );
expect(result.state.candidate.channels?.discord?.allowFrom).toEqual([106232522769186816 ]);
});
});
Messung V0.5 in Prozent C=95 H=95 G=94
¤ Dauer der Verarbeitung: 0.9 Sekunden
(vorverarbeitet am 2026-06-10)
¤
*© Formatika GbR, Deutschland