























@@ -159,6 +159,47 @@ describe("config observe recovery", () => {
159159};
160160}
161161162+function withAsyncHealthWriteFailure(
163+deps: ObserveRecoveryDeps,
164+healthPath: string,
165+): ObserveRecoveryDeps {
166+const writeFile = deps.fs.promises.writeFile.bind(deps.fs.promises);
167+return {
168+ ...deps,
169+fs: {
170+ ...deps.fs,
171+promises: {
172+ ...deps.fs.promises,
173+writeFile: async (target, data, options) => {
174+if (target === healthPath) {
175+throw new Error("health write failed");
176+}
177+return await writeFile(target, data, options);
178+},
179+},
180+},
181+};
182+}
183+184+function withSyncHealthWriteFailure(
185+deps: ObserveRecoveryDeps,
186+healthPath: string,
187+): ObserveRecoveryDeps {
188+const writeFileSync = deps.fs.writeFileSync.bind(deps.fs);
189+return {
190+ ...deps,
191+fs: {
192+ ...deps.fs,
193+writeFileSync: (target, data, options) => {
194+if (target === healthPath) {
195+throw new Error("health write failed");
196+}
197+return writeFileSync(target, data, options);
198+},
199+},
200+};
201+}
202+162203it("auto-restores suspicious update-channel-only roots from backup", async () => {
163204await withSuiteHome(async (home) => {
164205const { deps, configPath, auditPath, warn } = makeDeps(home);
@@ -383,6 +424,48 @@ describe("config observe recovery", () => {
383424});
384425});
385426427+it("logs async health-state write failures", async () => {
428+await withSuiteHome(async (home) => {
429+const { deps, configPath, warn } = makeDeps(home);
430+const snapshot = await makeSnapshot(configPath, recoverableTelegramConfig);
431+const healthPath = path.join(home, ".openclaw", "logs", "config-health.json");
432+433+await expect(
434+promoteConfigSnapshotToLastKnownGood({
435+deps: withAsyncHealthWriteFailure(deps, healthPath),
436+ snapshot,
437+logger: deps.logger,
438+}),
439+).resolves.toBe(true);
440+441+expect(warn).toHaveBeenCalledWith(
442+expect.stringContaining(
443+`Config health-state write failed: ${healthPath}: health write failed`,
444+),
445+);
446+});
447+});
448+449+it("logs sync health-state write failures", async () => {
450+await withSuiteHome(async (home) => {
451+const { deps, configPath, warn } = makeDeps(home);
452+const healthPath = path.join(home, ".openclaw", "logs", "config-health.json");
453+await seedConfigBackup(configPath, recoverableTelegramConfig);
454+await writeClobberedUpdateChannel(configPath);
455+456+recoverClobberedUpdateChannelSync({
457+deps: withSyncHealthWriteFailure(deps, healthPath),
458+ configPath,
459+});
460+461+expect(warn).toHaveBeenCalledWith(
462+expect.stringContaining(
463+`Config health-state write failed: ${healthPath}: health write failed`,
464+),
465+);
466+});
467+});
468+386469it("promotes a valid startup config and restores it after an invalid direct edit", async () => {
387470await withSuiteHome(async (home) => {
388471const { deps, configPath, auditPath, warn } = makeDeps(home);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。