@@ -27,12 +27,14 @@ vi.mock("fs/promises", async () => {
2727 rename : vi . fn ( actual . rename ) ,
2828 unlink : vi . fn ( actual . unlink ) ,
2929 access : vi . fn ( actual . access ) ,
30+ stat : vi . fn ( actual . stat ) ,
31+ open : vi . fn ( actual . open ) ,
3032 mkdtemp : vi . fn ( actual . mkdtemp ) ,
3133 rm : vi . fn ( actual . rm ) ,
3234 readdir : vi . fn ( actual . readdir ) ,
3335 mkdir : vi . fn ( actual . mkdir ) ,
3436 }
35- // fs.stat and fs. lstat are available via { ...actual }
37+ // fs.lstat is available via { ...actual }
3638
3739 return mockedFs
3840} )
@@ -587,6 +589,60 @@ describe("safeWriteJson", () => {
587589 expect ( leftovers ) . toEqual ( [ ] )
588590 } )
589591
592+ it . each ( [ "EACCES" , "EIO" , "EPERM" , undefined ] ) (
593+ "should preserve committed content when canonical access fails with %s" ,
594+ async ( code ) => {
595+ const backupPath = path . join ( tempDir , `.${ path . basename ( currentTestFilePath ) } .bak_1_stale.tmp` )
596+ await fs . writeFile ( backupPath , JSON . stringify ( { stale : true } ) )
597+ // Directory access succeeds; only the canonical-path check fails.
598+ vi . mocked ( fs . access )
599+ . mockResolvedValueOnce ( undefined )
600+ . mockRejectedValueOnce ( Object . assign ( new Error ( "Canonical access failed" ) , { code } ) )
601+ const merge = vi . fn ( ( existing : unknown ) => existing )
602+
603+ await safeWriteJson ( currentTestFilePath , { } , { merge } )
604+
605+ expect ( merge ) . toHaveBeenCalledWith ( { initial : "content" } , { } )
606+ expect ( await readFileContent ( currentTestFilePath ) ) . toEqual ( { initial : "content" } )
607+ expect ( await fs . readdir ( tempDir ) ) . toEqual ( [ path . basename ( currentTestFilePath ) ] )
608+ } ,
609+ )
610+
611+ it . each ( [ "ENOENT" , "EACCES" , "EIO" ] ) (
612+ "should recover from a readable backup when another backup stat fails with %s" ,
613+ async ( code ) => {
614+ await fs . unlink ( currentTestFilePath )
615+ const unreadableBackup = path . join ( tempDir , `.${ path . basename ( currentTestFilePath ) } .bak_1_bad.tmp` )
616+ const readableBackup = path . join ( tempDir , `.${ path . basename ( currentTestFilePath ) } .bak_2_good.tmp` )
617+ await fs . writeFile ( unreadableBackup , JSON . stringify ( { stale : true } ) )
618+ await fs . writeFile ( readableBackup , JSON . stringify ( { committed : true } ) )
619+ const actual = await vi . importActual < typeof import ( "fs/promises" ) > ( "fs/promises" )
620+ vi . mocked ( fs . stat ) . mockImplementation ( async ( backupPath ) => {
621+ if ( backupPath === unreadableBackup ) {
622+ throw Object . assign ( new Error ( "Backup stat failed" ) , { code } )
623+ }
624+ return actual . stat ( backupPath )
625+ } )
626+ const merge = vi . fn ( ( existing : unknown ) => existing )
627+
628+ await safeWriteJson ( currentTestFilePath , { } , { merge } )
629+
630+ expect ( merge ) . toHaveBeenCalledWith ( { committed : true } , { } )
631+ expect ( await readFileContent ( currentTestFilePath ) ) . toEqual ( { committed : true } )
632+ expect ( await fs . readdir ( tempDir ) ) . toEqual ( [ path . basename ( currentTestFilePath ) ] )
633+ } ,
634+ )
635+
636+ test ( "should open the replacement with write access for flushing" , async ( ) => {
637+ const open = vi . mocked ( fs . open )
638+ open . mockClear ( )
639+
640+ await safeWriteJson ( currentTestFilePath , { flushed : true } )
641+
642+ expect ( open ) . toHaveBeenCalledExactlyOnceWith ( expect . stringContaining ( ".new_" ) , "r+" )
643+ expect ( await readFileContent ( currentTestFilePath ) ) . toEqual ( { flushed : true } )
644+ } )
645+
590646 test ( "should garbage collect orphaned replacement files when the canonical path exists" , async ( ) => {
591647 const initialData = { message : "Initial content" }
592648
0 commit comments