





















@@ -2330,6 +2330,193 @@ describe("update-cli", () => {
23302330);
23312331});
233223322333+it("repairs legacy config before persisting a requested update channel", async () => {
2334+const tempDir = createCaseDir("openclaw-update");
2335+mockPackageInstallStatus(tempDir);
2336+const legacyConfig = {
2337+channels: {
2338+slack: {
2339+streaming: "partial",
2340+nativeStreaming: false,
2341+},
2342+telegram: {
2343+streaming: "block",
2344+},
2345+},
2346+} as OpenClawConfig;
2347+const migratedConfig = {
2348+channels: {
2349+slack: {
2350+streaming: {
2351+mode: "partial",
2352+nativeTransport: false,
2353+},
2354+},
2355+telegram: {
2356+streaming: {
2357+mode: "block",
2358+},
2359+},
2360+},
2361+} as OpenClawConfig;
2362+vi.mocked(readConfigFileSnapshot)
2363+.mockResolvedValueOnce({
2364+ ...baseSnapshot,
2365+parsed: legacyConfig,
2366+resolved: legacyConfig,
2367+sourceConfig: legacyConfig,
2368+config: legacyConfig,
2369+runtimeConfig: legacyConfig,
2370+valid: false,
2371+hash: "legacy-hash",
2372+issues: [
2373+{
2374+path: "channels.slack.streaming",
2375+message: "Invalid input: expected object, received string",
2376+},
2377+],
2378+legacyIssues: [
2379+{
2380+path: "channels.slack",
2381+message: "legacy slack streaming keys",
2382+},
2383+{
2384+path: "channels.telegram",
2385+message: "legacy telegram streaming keys",
2386+},
2387+],
2388+})
2389+.mockResolvedValueOnce({
2390+ ...baseSnapshot,
2391+parsed: migratedConfig,
2392+resolved: migratedConfig,
2393+sourceConfig: migratedConfig,
2394+config: migratedConfig,
2395+runtimeConfig: migratedConfig,
2396+valid: true,
2397+hash: "migrated-hash",
2398+});
2399+2400+await updateCommand({ channel: "beta", yes: true });
2401+2402+expect(replaceConfigFile).toHaveBeenCalledTimes(2);
2403+expect(replaceConfigFile).toHaveBeenNthCalledWith(1, {
2404+nextConfig: expect.objectContaining({
2405+channels: expect.objectContaining({
2406+slack: expect.objectContaining({
2407+streaming: expect.objectContaining({
2408+mode: "partial",
2409+nativeTransport: false,
2410+}),
2411+}),
2412+telegram: expect.objectContaining({
2413+streaming: expect.objectContaining({
2414+mode: "block",
2415+}),
2416+}),
2417+}),
2418+}),
2419+baseHash: "legacy-hash",
2420+writeOptions: {
2421+allowConfigSizeDrop: true,
2422+skipOutputLogs: false,
2423+},
2424+});
2425+expect(replaceConfigFile).toHaveBeenNthCalledWith(2, {
2426+nextConfig: {
2427+ ...migratedConfig,
2428+update: {
2429+channel: "beta",
2430+},
2431+},
2432+baseHash: "migrated-hash",
2433+});
2434+expect(defaultRuntime.exit).not.toHaveBeenCalledWith(1);
2435+});
2436+2437+it("does not auto-repair legacy config when authored includes are present", async () => {
2438+const tempDir = createCaseDir("openclaw-update");
2439+mockPackageInstallStatus(tempDir);
2440+const legacyConfigWithInclude = {
2441+$include: "./channels.json5",
2442+channels: {
2443+slack: {
2444+streaming: "partial",
2445+nativeStreaming: false,
2446+},
2447+},
2448+} as unknown as OpenClawConfig;
2449+vi.mocked(readConfigFileSnapshot).mockResolvedValueOnce({
2450+ ...baseSnapshot,
2451+parsed: legacyConfigWithInclude,
2452+resolved: legacyConfigWithInclude,
2453+sourceConfig: legacyConfigWithInclude,
2454+config: legacyConfigWithInclude,
2455+runtimeConfig: legacyConfigWithInclude,
2456+valid: false,
2457+hash: "legacy-include-hash",
2458+issues: [
2459+{
2460+path: "channels.slack.streaming",
2461+message: "Invalid input: expected object, received string",
2462+},
2463+],
2464+legacyIssues: [
2465+{
2466+path: "channels.slack",
2467+message: "legacy slack streaming keys",
2468+},
2469+],
2470+});
2471+2472+await updateCommand({ channel: "beta", yes: true });
2473+2474+expect(replaceConfigFile).not.toHaveBeenCalled();
2475+expect(runCommandWithTimeout).not.toHaveBeenCalled();
2476+expect(defaultRuntime.exit).toHaveBeenCalledWith(1);
2477+});
2478+2479+it("does not repair legacy config during a dry run", async () => {
2480+const tempDir = createCaseDir("openclaw-update");
2481+mockPackageInstallStatus(tempDir);
2482+const legacyConfig = {
2483+channels: {
2484+slack: {
2485+streaming: "partial",
2486+nativeStreaming: false,
2487+},
2488+},
2489+} as OpenClawConfig;
2490+vi.mocked(readConfigFileSnapshot).mockResolvedValueOnce({
2491+ ...baseSnapshot,
2492+parsed: legacyConfig,
2493+resolved: legacyConfig,
2494+sourceConfig: legacyConfig,
2495+config: legacyConfig,
2496+runtimeConfig: legacyConfig,
2497+valid: false,
2498+hash: "legacy-hash",
2499+issues: [
2500+{
2501+path: "channels.slack.streaming",
2502+message: "Invalid input: expected object, received string",
2503+},
2504+],
2505+legacyIssues: [
2506+{
2507+path: "channels.slack",
2508+message: "legacy slack streaming keys",
2509+},
2510+],
2511+});
2512+2513+await updateCommand({ dryRun: true, channel: "beta", yes: true });
2514+2515+expect(replaceConfigFile).not.toHaveBeenCalled();
2516+expect(runCommandWithTimeout).not.toHaveBeenCalled();
2517+expect(defaultRuntime.exit).toHaveBeenCalledWith(1);
2518+});
2519+23332520it("does not persist the requested channel when the package update fails", async () => {
23342521const tempDir = createCaseDir("openclaw-update");
23352522mockPackageInstallStatus(tempDir);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。