Skip to content

Commit e8c59b0

Browse files
committed
fix(review-feedback-1256): address latest review comments
1 parent 70aa49c commit e8c59b0

2 files changed

Lines changed: 88 additions & 14 deletions

File tree

apps/dsa-desktop/main.js

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -490,24 +490,35 @@ function restorePackagedRuntimeStateFromBackup() {
490490
const runtimeEntries = resolveRuntimeFileEntries(appDir);
491491
const relativeFiles = normalizeBackupFileList(manifest);
492492
const restored = [];
493+
const failed = [];
493494

494-
relativeFiles.forEach((relativePath) => {
495-
const entry = runtimeEntries.find((candidate) => candidate.relativePath === relativePath);
496-
const source = path.join(backupRoot, relativePath);
497-
const target = entry ? entry.absolutePath : path.join(appDir, relativePath);
498-
if (!fs.existsSync(source)) {
499-
return;
500-
}
501-
ensureDirectory(path.dirname(target));
502-
fs.copyFileSync(source, target);
503-
restored.push(relativePath);
504-
});
505-
506-
cleanupUpdateBackupRoot();
495+
try {
496+
relativeFiles.forEach((relativePath) => {
497+
try {
498+
const entry = runtimeEntries.find((candidate) => candidate.relativePath === relativePath);
499+
const source = path.join(backupRoot, relativePath);
500+
const target = entry ? entry.absolutePath : path.join(appDir, relativePath);
501+
if (!fs.existsSync(source)) {
502+
return;
503+
}
504+
ensureDirectory(path.dirname(target));
505+
fs.copyFileSync(source, target);
506+
restored.push(relativePath);
507+
} catch (error) {
508+
const message = error instanceof Error ? error.message : String(error);
509+
failed.push(`${relativePath} (${message})`);
510+
}
511+
});
512+
} finally {
513+
cleanupUpdateBackupRoot();
514+
}
507515

508516
if (restored.length) {
509517
console.log(`[update] restored runtime files from backup: ${restored.join(', ')}`);
510518
}
519+
if (failed.length) {
520+
logLine(`[update] skipped runtime restore files after copy failure: ${failed.join(', ')}`);
521+
}
511522
}
512523

513524
function resolveBackendPath() {
@@ -1529,6 +1540,7 @@ module.exports = {
15291540
fetchLatestReleaseJson,
15301541
normalizeVersionString,
15311542
parseSemver,
1543+
restorePackagedRuntimeStateFromBackup,
15321544
sanitizeReleaseUrl,
15331545
stopBackend,
15341546
__setBackendProcessForTest,

apps/dsa-desktop/tests/main.test.js

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@ const assert = require('node:assert/strict');
22
const test = require('node:test');
33
const Module = require('node:module');
44
const { EventEmitter } = require('node:events');
5+
const fs = require('node:fs');
6+
const os = require('node:os');
57
const path = require('node:path');
68

7-
function loadMainModule(t) {
9+
function loadMainModule(t, options = {}) {
810
const originalLoad = Module._load;
11+
const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
912
const fakeApp = {
1013
isPackaged: false,
1114
getVersion: () => '3.12.0',
1215
getPath: () => '/tmp/dsa-user-data',
1316
whenReady: () => ({ then: () => undefined }),
1417
on: () => undefined,
1518
quit: () => undefined,
19+
...(options.app || {}),
1620
};
1721
const fakeDialog = {
1822
showMessageBox: async () => ({ response: 0 }),
@@ -51,9 +55,16 @@ function loadMainModule(t) {
5155

5256
t.after(() => {
5357
Module._load = originalLoad;
58+
if (options.platform && originalPlatform) {
59+
Object.defineProperty(process, 'platform', originalPlatform);
60+
}
5461
delete require.cache[mainPath];
5562
});
5663

64+
if (options.platform) {
65+
Object.defineProperty(process, 'platform', { ...originalPlatform, value: options.platform });
66+
}
67+
5768
return require('../main.js');
5869
}
5970

@@ -220,6 +231,57 @@ test('desktop update backup list includes WAL and SHM artifacts', (t) => {
220231
assert.ok(files.includes(path.join('logs', 'desktop.log')));
221232
});
222233

234+
test('restorePackagedRuntimeStateFromBackup skips failed copies and clears backup', (t) => {
235+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'dsa-desktop-restore-'));
236+
const appDir = path.join(tempRoot, 'app');
237+
const userDataDir = path.join(tempRoot, 'userData');
238+
const backupRoot = path.join(userDataDir, '.dsa-desktop-update-backup');
239+
const backupDbPath = path.join(backupRoot, 'data', 'stock_analysis.db');
240+
fs.mkdirSync(path.dirname(backupDbPath), { recursive: true });
241+
fs.mkdirSync(appDir, { recursive: true });
242+
fs.writeFileSync(path.join(appDir, 'Uninstall Daily Stock Analysis.exe'), '');
243+
fs.writeFileSync(backupDbPath, 'backup-db');
244+
fs.writeFileSync(
245+
path.join(backupRoot, 'runtime-state.json'),
246+
JSON.stringify({ files: [path.join('data', 'stock_analysis.db')] }),
247+
'utf-8'
248+
);
249+
250+
const mainModule = loadMainModule(t, {
251+
platform: 'win32',
252+
app: {
253+
isPackaged: true,
254+
getPath: (name) => {
255+
if (name === 'exe') {
256+
return path.join(appDir, 'Daily Stock Analysis.exe');
257+
}
258+
return userDataDir;
259+
},
260+
},
261+
});
262+
const originalCopyFileSync = fs.copyFileSync;
263+
let failedCopyAttempted = false;
264+
265+
fs.copyFileSync = (source, target) => {
266+
if (source === backupDbPath) {
267+
failedCopyAttempted = true;
268+
throw new Error('target locked');
269+
}
270+
return originalCopyFileSync(source, target);
271+
};
272+
273+
t.after(() => {
274+
fs.copyFileSync = originalCopyFileSync;
275+
fs.rmSync(tempRoot, { recursive: true, force: true });
276+
});
277+
278+
assert.doesNotThrow(() => {
279+
mainModule.restorePackagedRuntimeStateFromBackup();
280+
});
281+
assert.equal(failedCopyAttempted, true);
282+
assert.equal(fs.existsSync(backupRoot), false);
283+
});
284+
223285
test('stopBackend waits for backend process exit', async (t) => {
224286
const mainModule = loadMainModule(t);
225287
const killSignals = [];

0 commit comments

Comments
 (0)