@@ -2,17 +2,21 @@ const assert = require('node:assert/strict');
22const test = require ( 'node:test' ) ;
33const Module = require ( 'node:module' ) ;
44const { EventEmitter } = require ( 'node:events' ) ;
5+ const fs = require ( 'node:fs' ) ;
6+ const os = require ( 'node:os' ) ;
57const 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+
223285test ( 'stopBackend waits for backend process exit' , async ( t ) => {
224286 const mainModule = loadMainModule ( t ) ;
225287 const killSignals = [ ] ;
0 commit comments