@@ -628,6 +628,79 @@ def renewal_callback():
628628 assert len (client .calls ) == 1
629629
630630
631+ def test_start_does_not_invoke_callback_without_due_watched_cert ():
632+ now = datetime (2026 , 1 , 1 , tzinfo = UTC )
633+ callback_called = threading .Event ()
634+
635+ mgr = RenewalManager (
636+ DummyClient (),
637+ renewal_callback = callback_called .set ,
638+ renew_threshold_days = 30 ,
639+ clock_fn = lambda : now ,
640+ )
641+
642+ mgr .start ()
643+ try :
644+ assert not callback_called .wait (timeout = 0.05 )
645+ finally :
646+ mgr .stop ()
647+
648+
649+ def test_update_watch_domains_does_not_invoke_callback_for_fresh_cert ():
650+ now = datetime (2026 , 1 , 1 , tzinfo = UTC )
651+ callback_called = threading .Event ()
652+ client = DummyClient ()
653+ client .key_store = DummyKeyStore ()
654+ key = Key .generate ("ecdsa" )
655+ csr = key .create_csr (domain = "fresh-callback.example.com" , alt_names = ["fresh-callback.example.com" ])
656+ cert_builder = (
657+ x509 .CertificateBuilder ()
658+ .subject_name (csr .subject )
659+ .issuer_name (x509 .Name ([x509 .NameAttribute (x509 .NameOID .COMMON_NAME , "pytest.certapi.local" )]))
660+ .public_key (csr .public_key ())
661+ .serial_number (x509 .random_serial_number ())
662+ .not_valid_before (now )
663+ .not_valid_after (now + timedelta (days = 90 ))
664+ )
665+ for ext in csr .extensions :
666+ cert_builder = cert_builder .add_extension (ext .value , ext .critical )
667+ cert = key .sign_csr (cert_builder )
668+ client .key_store .set_domain_cert ("fresh-callback.example.com" , [cert ])
669+
670+ mgr = RenewalManager (
671+ client ,
672+ renewal_callback = callback_called .set ,
673+ renew_threshold_days = 30 ,
674+ clock_fn = lambda : now ,
675+ )
676+ mgr .start ()
677+ try :
678+ mgr .update_watch_domains (["fresh-callback.example.com" ])
679+ assert not callback_called .wait (timeout = 0.05 )
680+ finally :
681+ mgr .stop ()
682+
683+
684+ def test_worker_invokes_callback_when_cached_cert_is_due ():
685+ now = datetime (2026 , 1 , 1 , tzinfo = UTC )
686+ callback_called = threading .Event ()
687+ mgr = RenewalManager (
688+ DummyClient (),
689+ renewal_callback = callback_called .set ,
690+ renew_threshold_days = 30 ,
691+ clock_fn = lambda : now ,
692+ )
693+ with mgr ._lock :
694+ mgr ._watch_domains = {"due-callback.example.com" }
695+ mgr ._cache ["due-callback.example.com" ] = now + timedelta (days = 1 )
696+
697+ mgr .start ()
698+ try :
699+ assert callback_called .wait (timeout = 2 )
700+ finally :
701+ mgr .stop ()
702+
703+
631704def test_running_trigger_now_blocks_until_callback_update_finishes ():
632705 now = datetime (2026 , 1 , 1 , tzinfo = UTC )
633706 obtain_started = threading .Event ()
@@ -768,6 +841,7 @@ def test_stop_does_not_wait_for_hung_remote_request_thread():
768841 mgr ._watch_domains = {"hung.example.com" }
769842
770843 mgr .start ()
844+ mgr .trigger_now ()
771845 assert started .wait (timeout = 2 )
772846 mgr .stop ()
773847
0 commit comments