4343import static org .mockito .Mockito .mock ;
4444import static org .mockito .Mockito .spy ;
4545import static org .mockito .Mockito .verify ;
46+ import static org .mockito .Mockito .verifyNoInteractions ;
4647import static org .mockito .Mockito .when ;
4748
4849/** Contract tests for {@link DurableExecutionManager}. */
@@ -69,6 +70,30 @@ void noStoreModeMakesAllMaybeOperationsNoOp() throws Exception {
6970 dem .close ();
7071 }
7172
73+ @ Test
74+ @ SuppressWarnings ("unchecked" )
75+ void noStoreModeSnapshotAndNotifyKeepCheckpointMapEmpty () throws Exception {
76+ DurableExecutionManager dem = new DurableExecutionManager (null );
77+ KeyedStateBackend <Object > backend = mock (KeyedStateBackend .class );
78+
79+ // Cycle 1: snapshot + notify with null store. The snapshot-side guard must short-circuit
80+ // before any backend access, and the cleanup-side guard must leave the map untouched.
81+ dem .snapshotLastCompletedSequenceNumbers (backend , 1L );
82+ assertThat (dem .getCheckpointIdToSeqNums ()).isEmpty ();
83+ verifyNoInteractions (backend );
84+ dem .notifyCheckpointComplete (1L );
85+ assertThat (dem .getCheckpointIdToSeqNums ()).isEmpty ();
86+
87+ // Cycle 2: confirm the invariant holds across multiple checkpoints.
88+ dem .snapshotLastCompletedSequenceNumbers (backend , 2L );
89+ assertThat (dem .getCheckpointIdToSeqNums ()).isEmpty ();
90+ verifyNoInteractions (backend );
91+ dem .notifyCheckpointComplete (2L );
92+ assertThat (dem .getCheckpointIdToSeqNums ()).isEmpty ();
93+
94+ dem .close ();
95+ }
96+
7297 @ Test
7398 void withInjectedStorePersistsTaskResult () throws Exception {
7499 InMemoryActionStateStore store = new InMemoryActionStateStore (false );
0 commit comments