@@ -455,6 +455,86 @@ func TestRunDegradesWhenStartupResyncFails(t *testing.T) {
455455 require .Equal (t , ReasonObserverError , status .Reason )
456456}
457457
458+ func TestHandleStandbyTimerCallsStandbyAndClearsState (t * testing.T ) {
459+ t .Parallel ()
460+
461+ idleSince := time .Date (2026 , 4 , 6 , 10 , 55 , 0 , 0 , time .UTC )
462+ store := newFakeInstanceStore ([]Instance {{
463+ ID : "inst-standby" ,
464+ Name : "inst-standby" ,
465+ State : StateRunning ,
466+ NetworkEnabled : true ,
467+ IP : "192.168.100.61" ,
468+ AutoStandby : & Policy {Enabled : true , IdleTimeout : "1m" },
469+ Runtime : & Runtime {
470+ IdleSince : & idleSince ,
471+ },
472+ }})
473+ controller := NewController (store , & fakeConnectionSource {}, ControllerOptions {
474+ Now : func () time.Time { return idleSince .Add (time .Minute ) },
475+ })
476+
477+ require .NoError (t , controller .startupResync (context .Background ()))
478+
479+ controller .handleStandbyTimer (context .Background (), "inst-standby" )
480+
481+ require .Equal (t , []string {"inst-standby" }, store .standbyIDs )
482+ require .Nil (t , store .persistedRuntime ["inst-standby" ])
483+
484+ controller .mu .RLock ()
485+ state := controller .states ["inst-standby" ]
486+ require .NotNil (t , state )
487+ assert .Nil (t , state .compiledPolicy )
488+ assert .Nil (t , state .activeInbound )
489+ assert .Nil (t , state .idleSince )
490+ assert .Nil (t , state .lastInboundAt )
491+ assert .Nil (t , state .nextStandbyAt )
492+ assert .False (t , state .standbyRequested )
493+ controller .mu .RUnlock ()
494+ }
495+
496+ func TestHandleStandbyTimerFailureRearmsIdleCountdown (t * testing.T ) {
497+ t .Parallel ()
498+
499+ idleSince := time .Date (2026 , 4 , 6 , 10 , 55 , 0 , 0 , time .UTC )
500+ now := idleSince .Add (time .Minute )
501+ store := newFakeInstanceStore ([]Instance {{
502+ ID : "inst-standby-fail" ,
503+ Name : "inst-standby-fail" ,
504+ State : StateRunning ,
505+ NetworkEnabled : true ,
506+ IP : "192.168.100.62" ,
507+ AutoStandby : & Policy {Enabled : true , IdleTimeout : "1m" },
508+ Runtime : & Runtime {
509+ IdleSince : & idleSince ,
510+ },
511+ }})
512+ store .standbyErr = errors .New ("standby failed" )
513+ controller := NewController (store , & fakeConnectionSource {}, ControllerOptions {
514+ Now : func () time.Time { return now },
515+ })
516+
517+ require .NoError (t , controller .startupResync (context .Background ()))
518+
519+ controller .handleStandbyTimer (context .Background (), "inst-standby-fail" )
520+
521+ require .Equal (t , []string {"inst-standby-fail" }, store .standbyIDs )
522+ require .NotNil (t , store .persistedRuntime ["inst-standby-fail" ])
523+ require .NotNil (t , store .persistedRuntime ["inst-standby-fail" ].IdleSince )
524+ assert .Equal (t , now , * store .persistedRuntime ["inst-standby-fail" ].IdleSince )
525+
526+ controller .mu .RLock ()
527+ state := controller .states ["inst-standby-fail" ]
528+ require .NotNil (t , state )
529+ assert .NotNil (t , state .compiledPolicy )
530+ assert .False (t , state .standbyRequested )
531+ assert .NotNil (t , state .idleSince )
532+ assert .Equal (t , now , * state .idleSince )
533+ require .NotNil (t , state .nextStandbyAt )
534+ assert .Equal (t , now .Add (time .Minute ), * state .nextStandbyAt )
535+ controller .mu .RUnlock ()
536+ }
537+
458538func mustAddr (raw string ) netip.Addr {
459539 return netip .MustParseAddr (raw )
460540}
0 commit comments