1919import org .slf4j .LoggerFactory ;
2020import software .amazon .awssdk .utils .Pair ;
2121
22- import java .security .Permission ;
2322import java .time .Clock ;
2423import java .time .Duration ;
2524import java .time .Instant ;
@@ -35,13 +34,17 @@ public class OperatorShutdownHandlerTest {
3534 @ Mock private Clock clock ;
3635 @ Mock private ShutdownService shutdownService ;
3736 private OperatorShutdownHandler operatorShutdownHandler ;
37+
38+ // Fixed base time for predictable testing
39+ private Instant baseTime ;
3840
3941
4042
4143 @ BeforeEach
4244 void beforeEach () {
4345 mocks = MockitoAnnotations .openMocks (this );
44- when (clock .instant ()).thenAnswer (i -> Instant .now ());
46+ baseTime = Instant .now (); // Capture a fixed base time
47+ when (clock .instant ()).thenReturn (baseTime ); // Use thenReturn for fixed value
4548 doThrow (new RuntimeException ()).when (shutdownService ).Shutdown (1 );
4649 this .operatorShutdownHandler = new OperatorShutdownHandler (Duration .ofHours (12 ), Duration .ofHours (12 ), Duration .ofHours (12 ), clock , shutdownService );
4750 }
@@ -62,7 +65,6 @@ void shutdownOnAttestFailure(VertxTestContext testContext) {
6265 this .operatorShutdownHandler .handleAttestResponse (Pair .of (AttestationResponseCode .AttestationFailure , "Unauthorized" ));
6366 } catch (RuntimeException e ) {
6467 verify (shutdownService ).Shutdown (1 );
65- String message = logWatcher .list .get (0 ).getFormattedMessage ();
6668 Assertions .assertEquals ("core attestation failed with AttestationFailure, shutting down operator, core response: Unauthorized" , logWatcher .list .get (0 ).getFormattedMessage ());
6769 testContext .completeNow ();
6870 }
@@ -169,21 +171,50 @@ void saltsLogErrorAtInterval(VertxTestContext testContext) {
169171
170172 @ Test
171173 void storeRefreshRecordsSuccessTimestamp (VertxTestContext testContext ) {
174+ // Record successful refresh at baseTime
172175 this .operatorShutdownHandler .handleStoreRefresh ("test_store" , true );
173176
177+ // Advance time by 11 hours (still under threshold) - should NOT trigger shutdown
178+ when (clock .instant ()).thenReturn (baseTime .plus (11 , ChronoUnit .HOURS ));
179+ assertDoesNotThrow (() -> {
180+ this .operatorShutdownHandler .checkStoreRefreshStaleness ();
181+ });
174182 verify (shutdownService , never ()).Shutdown (anyInt ());
175- testContext .completeNow ();
183+
184+ // Now advance time to 13 hours from original success - SHOULD trigger shutdown
185+ when (clock .instant ()).thenReturn (baseTime .plus (13 , ChronoUnit .HOURS ));
186+ try {
187+ this .operatorShutdownHandler .checkStoreRefreshStaleness ();
188+ } catch (RuntimeException e ) {
189+ verify (shutdownService ).Shutdown (1 );
190+ testContext .completeNow ();
191+ }
176192 }
177193
178194 @ Test
179195 void storeRefreshFailureDoesNotResetTimestamp (VertxTestContext testContext ) {
196+ // Record successful refresh at baseTime
180197 this .operatorShutdownHandler .handleStoreRefresh ("test_store" , true );
181198
199+ // Advance time by 2 hours
200+ when (clock .instant ()).thenReturn (baseTime .plus (2 , ChronoUnit .HOURS ));
201+
202+ // Record multiple failures - these should NOT reset the timestamp
203+ this .operatorShutdownHandler .handleStoreRefresh ("test_store" , false );
182204 this .operatorShutdownHandler .handleStoreRefresh ("test_store" , false );
183205 this .operatorShutdownHandler .handleStoreRefresh ("test_store" , false );
184206
185- verify (shutdownService , never ()).Shutdown (anyInt ());
186- testContext .completeNow ();
207+ // Advance time to 13 hours from ORIGINAL success (not from failures)
208+ // This proves failures didn't reset the timestamp
209+ when (clock .instant ()).thenReturn (baseTime .plus (13 , ChronoUnit .HOURS ));
210+
211+ // Should trigger shutdown based on original success timestamp
212+ try {
213+ this .operatorShutdownHandler .checkStoreRefreshStaleness ();
214+ } catch (RuntimeException e ) {
215+ verify (shutdownService ).Shutdown (1 );
216+ testContext .completeNow ();
217+ }
187218 }
188219
189220 @ Test
@@ -192,11 +223,11 @@ void storeRefreshStaleShutdown(VertxTestContext testContext) {
192223 logWatcher .start ();
193224 ((Logger ) LoggerFactory .getLogger (OperatorShutdownHandler .class )).addAppender (logWatcher );
194225
195- // Initial successful refresh
226+ // Initial successful refresh at baseTime
196227 this .operatorShutdownHandler .handleStoreRefresh ("test_store" , true );
197228
198- // Move time forward by 12 hours + 1 second
199- when (clock .instant ()).thenAnswer ( i -> Instant . now () .plus (12 , ChronoUnit .HOURS ).plusSeconds (1 ));
229+ // Move time forward by 12 hours + 1 second from baseTime
230+ when (clock .instant ()).thenReturn ( baseTime .plus (12 , ChronoUnit .HOURS ).plusSeconds (1 ));
200231
201232 // Check staleness - should trigger shutdown
202233 try {
@@ -212,19 +243,19 @@ void storeRefreshStaleShutdown(VertxTestContext testContext) {
212243
213244 @ Test
214245 void storeRefreshRecoverBeforeStale (VertxTestContext testContext ) {
215- // Initial successful refresh
246+ // Initial successful refresh at baseTime
216247 this .operatorShutdownHandler .handleStoreRefresh ("test_store" , true );
217248
218- // Move time forward by 11 hours
219- when (clock .instant ()).thenAnswer ( i -> Instant . now () .plus (11 , ChronoUnit .HOURS ));
249+ // Move time forward by 11 hours from baseTime
250+ when (clock .instant ()).thenReturn ( baseTime .plus (11 , ChronoUnit .HOURS ));
220251
221- // Another successful refresh before timeout
252+ // Another successful refresh before timeout (at baseTime + 11 hours)
222253 this .operatorShutdownHandler .handleStoreRefresh ("test_store" , true );
223254
224- // Move time forward another 12 hours from original time (but only 1 hour from last refresh)
225- when (clock .instant ()).thenAnswer ( i -> Instant . now () .plus (12 , ChronoUnit .HOURS ));
255+ // Move time forward another 12 hours from original time (but only 1 hour from last refresh at baseTime + 11h )
256+ when (clock .instant ()).thenReturn ( baseTime .plus (12 , ChronoUnit .HOURS ));
226257
227- // Check staleness - should NOT trigger shutdown
258+ // Check staleness - should NOT trigger shutdown (only 1 hour since last refresh)
228259 assertDoesNotThrow (() -> {
229260 this .operatorShutdownHandler .checkStoreRefreshStaleness ();
230261 });
@@ -238,20 +269,20 @@ void multipleStoresOneStaleTriggers(VertxTestContext testContext) {
238269 logWatcher .start ();
239270 ((Logger ) LoggerFactory .getLogger (OperatorShutdownHandler .class )).addAppender (logWatcher );
240271
241- // Multiple stores succeed
272+ // Multiple stores succeed at baseTime
242273 this .operatorShutdownHandler .handleStoreRefresh ("store1" , true );
243274 this .operatorShutdownHandler .handleStoreRefresh ("store2" , true );
244275 this .operatorShutdownHandler .handleStoreRefresh ("store3" , true );
245276
246- // Move time forward
247- when (clock .instant ()).thenAnswer ( i -> Instant . now () .plus (6 , ChronoUnit .HOURS ));
277+ // Move time forward by 6 hours from baseTime
278+ when (clock .instant ()).thenReturn ( baseTime .plus (6 , ChronoUnit .HOURS ));
248279
249- // Store1 and Store2 refresh successfully, but Store3 doesn't
280+ // Store1 and Store2 refresh successfully at baseTime+6h , but Store3 doesn't
250281 this .operatorShutdownHandler .handleStoreRefresh ("store1" , true );
251282 this .operatorShutdownHandler .handleStoreRefresh ("store2" , true );
252283
253- // Move time forward 12 hours from start (store3 hasn't refreshed for 12 hours)
254- when (clock .instant ()).thenAnswer ( i -> Instant . now () .plus (12 , ChronoUnit .HOURS ).plusSeconds (1 ));
284+ // Move time forward 12 hours from baseTime (store3 hasn't refreshed for 12 hours)
285+ when (clock .instant ()).thenReturn ( baseTime .plus (12 , ChronoUnit .HOURS ).plusSeconds (1 ));
255286
256287 // Check staleness - should trigger shutdown for store3
257288 try {
0 commit comments