From 702a9bd356fe9d59a0e24eaa9b1c4eddc0f32900 Mon Sep 17 00:00:00 2001 From: Fred blum Date: Thu, 23 Apr 2026 09:42:05 +0300 Subject: [PATCH] fix(config): mirror sync restore failure test and tidy warning format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review follow-ups on #70515: add the parallel failure-injection test for maybeRecoverSuspiciousConfigReadSync so the sync path has the same coverage, and move the captured error message inside the suspicious- reason parentheses in the failure warning so the line no longer reads 'failed: (...): ' (double colon) — it now reads 'failed: (..., )' with a single trailing colon after 'failed'. --- src/config/io.observe-recovery.test.ts | 38 ++++++++++++++++++++++++++ src/config/io.observe-recovery.ts | 12 ++++---- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/config/io.observe-recovery.test.ts b/src/config/io.observe-recovery.test.ts index e7180e86a03..4352788e387 100644 --- a/src/config/io.observe-recovery.test.ts +++ b/src/config/io.observe-recovery.test.ts @@ -316,6 +316,44 @@ describe("config observe recovery", () => { }); }); + it("sync recovery records copyFileSync failure instead of falsely claiming restore succeeded", async () => { + await withSuiteHome(async (home) => { + const { deps, configPath, auditPath, warn } = makeDeps(home); + await seedConfigBackup(configPath, recoverableTelegramConfig); + const clobbered = await writeClobberedUpdateChannel(configPath); + + const copyError = Object.assign(new Error("EACCES: permission denied"), { code: "EACCES" }); + const failingFs: ObserveRecoveryDeps["fs"] = { + ...deps.fs, + copyFileSync: () => { + throw copyError; + }, + }; + const recovered = maybeRecoverSuspiciousConfigReadSync({ + deps: { ...deps, fs: failingFs }, + configPath, + raw: clobbered.raw, + parsed: clobbered.parsed, + }); + + expect((recovered.parsed as { gateway?: { mode?: string } }).gateway?.mode).toBe("local"); + await expect(fsp.readFile(configPath, "utf-8")).resolves.toBe(clobbered.raw); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining("Config auto-restore from backup failed:"), + ); + expect(warn).toHaveBeenCalledWith(expect.stringContaining("EACCES: permission denied")); + expect(warn).not.toHaveBeenCalledWith( + expect.stringContaining("Config auto-restored from backup:"), + ); + + const observe = await readLastObserveEvent(auditPath); + expect(observe?.restoredFromBackup).toBe(false); + expect(observe?.valid).toBe(false); + expect(observe?.restoreErrorCode).toBe("EACCES"); + expect(observe?.restoreErrorMessage).toBe("EACCES: permission denied"); + }); + }); + it("dedupes repeated suspicious hashes", async () => { await withSuiteHome(async (home) => { const { deps, configPath, auditPath } = makeDeps(home); diff --git a/src/config/io.observe-recovery.ts b/src/config/io.observe-recovery.ts index 82c3c87f358..dddc35c99b4 100644 --- a/src/config/io.observe-recovery.ts +++ b/src/config/io.observe-recovery.ts @@ -688,9 +688,9 @@ export async function maybeRecoverSuspiciousConfigRead(params: { ); } else { params.deps.logger.warn( - `Config auto-restore from backup failed: ${params.configPath} (${suspicious.join(", ")})${ - restoreErrorDetails.message ? `: ${restoreErrorDetails.message}` : "" - }`, + `Config auto-restore from backup failed: ${params.configPath} (${suspicious.join(", ")}${ + restoreErrorDetails.message ? `; ${restoreErrorDetails.message}` : "" + })`, ); } await appendConfigAuditRecord( @@ -795,9 +795,9 @@ export function maybeRecoverSuspiciousConfigReadSync(params: { ); } else { params.deps.logger.warn( - `Config auto-restore from backup failed: ${params.configPath} (${suspicious.join(", ")})${ - restoreErrorDetails.message ? `: ${restoreErrorDetails.message}` : "" - }`, + `Config auto-restore from backup failed: ${params.configPath} (${suspicious.join(", ")}${ + restoreErrorDetails.message ? `; ${restoreErrorDetails.message}` : "" + })`, ); } appendConfigAuditRecordSync(