@@ -21,8 +21,10 @@ await withMockedModule(
2121 } ) ,
2222) ;
2323
24- const { finalizeRunState, loadState, saveState } = await import ( "./state.js" ) ;
25- const { getActiveRun, setActiveRun } = await import ( "./active-run.js" ) ;
24+ const { finalizeRunState, loadState, saveCrashState, saveState } =
25+ await import ( "./state.js" ) ;
26+ const { getActiveRun, markCrashed, resetCrashedForTests, setActiveRun } =
27+ await import ( "./active-run.js" ) ;
2628type RunState = import ( "./state.js" ) . RunState ;
2729
2830let cwd = "" ;
@@ -105,3 +107,34 @@ test("saveState calls for different sessions do not block each other", async ()
105107 expect ( a ) . toMatchObject ( { kind : "ok" , state : { task : "a" } } ) ;
106108 expect ( b ) . toMatchObject ( { kind : "ok" , state : { task : "b" } } ) ;
107109} ) ;
110+
111+ test ( "saveCrashState fails closed without the crash fence and lands nothing" , async ( ) => {
112+ const sessionId = "sess-gated" ;
113+ await expect (
114+ saveCrashState ( cwd , sessionId , state ( { status : "crashed" } ) , home ) ,
115+ ) . rejects . toThrow ( "not in crashed state" ) ;
116+ expect ( await loadState ( cwd , sessionId , home ) ) . toMatchObject ( {
117+ kind : "missing" ,
118+ } ) ;
119+ } ) ;
120+
121+ test ( "saveCrashState lands the terminal record once crashed" , async ( ) => {
122+ const sessionId = "sess-crash-lands" ;
123+ markCrashed ( ) ;
124+ try {
125+ await saveCrashState (
126+ cwd ,
127+ sessionId ,
128+ state ( { status : "crashed" , finishedAt : 7 , error : "boom" } ) ,
129+ home ,
130+ ) ;
131+ } finally {
132+ // The flag is process-global and sticky: restore it so later tests in
133+ // this process keep their saveState chains live.
134+ resetCrashedForTests ( ) ;
135+ }
136+ expect ( await loadState ( cwd , sessionId , home ) ) . toMatchObject ( {
137+ kind : "ok" ,
138+ state : { status : "crashed" , finishedAt : 7 , error : "boom" } ,
139+ } ) ;
140+ } ) ;
0 commit comments