@ -31,10 +31,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit ;
import java.util.concurrent.atomic.AtomicInteger ;
import static java.util.concurrent.TimeUnit.MILLISECONDS ;
import static java.util.concurrent.TimeUnit.SECONDS ;
import static org.assertj.core.api.Assertions.assertThat ;
import static org.awaitility.Awaitility.await ;
public class CertificateReloadManagerTest {
@ -59,11 +56,10 @@ public class CertificateReloadManagerTest {
}
}
private void writeFileAndAwaitMtimeChang e ( Path path , String content , long baselineMtime ) throws IOException {
private void writeFileAndBumpMtim e ( Path path , String content , long baselineMtime ) throws IOException {
Files . writeString ( path , content ) ;
await ( ) . atMost ( 2 , SECONDS )
. pollInterval ( 10 , MILLISECONDS )
. until ( ( ) - > Files . getLastModifiedTime ( path ) . toMillis ( ) ! = baselineMtime ) ;
// Force a strictly newer mtime: back-to-back writes can share a millisecond, hiding the change from the watcher.
Files . setLastModifiedTime ( path , FileTime . fromMillis ( baselineMtime + 1000 ) ) ;
}
private long mtime ( Path path ) throws IOException {
@ -77,7 +73,7 @@ public class CertificateReloadManagerTest {
certificateReloadManager . registerWatcher ( "test-cert" , certFile , reloadCount : : incrementAndGet ) ;
long baseline = mtime ( certFile ) ;
writeFileAndAwaitMtimeChang e ( certFile , "-----BEGIN CERTIFICATE-----\nTEST_CERT_V2_MODIFIED\n-----END CERTIFICATE-----\n" , baseline ) ;
writeFileAndBumpMtim e ( certFile , "-----BEGIN CERTIFICATE-----\nTEST_CERT_V2_MODIFIED\n-----END CERTIFICATE-----\n" , baseline ) ;
ReflectionTestUtils . invokeMethod ( certificateReloadManager , "checkCertificates" ) ;
@ -85,7 +81,7 @@ public class CertificateReloadManagerTest {
}
@Test
public void givenCertificateFileUnchanged_whenCheckForChanges_thenShouldNotTriggerReload ( ) throws Exception {
public void givenCertificateFileUnchanged_whenCheckForChanges_thenShouldNotTriggerReload ( ) {
AtomicInteger reloadCount = new AtomicInteger ( 0 ) ;
certificateReloadManager . registerWatcher ( "test-cert" , certFile , reloadCount : : incrementAndGet ) ;
@ -147,7 +143,7 @@ public class CertificateReloadManagerTest {
certificateReloadManager . registerWatcher ( "test-key" , keyFile , keyReloadCount : : incrementAndGet ) ;
long baseline = mtime ( keyFile ) ;
writeFileAndAwaitMtimeChang e ( keyFile , "-----BEGIN PRIVATE KEY-----\nTEST_KEY_V2_MODIFIED\n-----END PRIVATE KEY-----\n" , baseline ) ;
writeFileAndBumpMtim e ( keyFile , "-----BEGIN PRIVATE KEY-----\nTEST_KEY_V2_MODIFIED\n-----END PRIVATE KEY-----\n" , baseline ) ;
ReflectionTestUtils . invokeMethod ( certificateReloadManager , "checkCertificates" ) ;
@ -168,8 +164,8 @@ public class CertificateReloadManagerTest {
long baseline1 = mtime ( certFile ) ;
long baseline2 = mtime ( cert2File ) ;
writeFileAndAwaitMtimeChang e ( certFile , "-----BEGIN CERTIFICATE-----\nMODIFIED1\n-----END CERTIFICATE-----\n" , baseline1 ) ;
writeFileAndAwaitMtimeChang e ( cert2File , "-----BEGIN CERTIFICATE-----\nMODIFIED2\n-----END CERTIFICATE-----\n" , baseline2 ) ;
writeFileAndBumpMtim e ( certFile , "-----BEGIN CERTIFICATE-----\nMODIFIED1\n-----END CERTIFICATE-----\n" , baseline1 ) ;
writeFileAndBumpMtim e ( cert2File , "-----BEGIN CERTIFICATE-----\nMODIFIED2\n-----END CERTIFICATE-----\n" , baseline2 ) ;
ReflectionTestUtils . invokeMethod ( certificateReloadManager , "checkCertificates" ) ;
@ -191,8 +187,8 @@ public class CertificateReloadManagerTest {
long baseline1 = mtime ( certFile ) ;
long baseline2 = mtime ( cert2File ) ;
writeFileAndAwaitMtimeChang e ( certFile , "-----BEGIN CERTIFICATE-----\nMODIFIED1\n-----END CERTIFICATE-----\n" , baseline1 ) ;
writeFileAndAwaitMtimeChang e ( cert2File , "-----BEGIN CERTIFICATE-----\nMODIFIED2\n-----END CERTIFICATE-----\n" , baseline2 ) ;
writeFileAndBumpMtim e ( certFile , "-----BEGIN CERTIFICATE-----\nMODIFIED1\n-----END CERTIFICATE-----\n" , baseline1 ) ;
writeFileAndBumpMtim e ( cert2File , "-----BEGIN CERTIFICATE-----\nMODIFIED2\n-----END CERTIFICATE-----\n" , baseline2 ) ;
ReflectionTestUtils . invokeMethod ( certificateReloadManager , "checkCertificates" ) ;
@ -224,9 +220,7 @@ public class CertificateReloadManagerTest {
for ( int i = 0 ; i < 5 ; i + + ) {
Files . writeString ( certFile , "-----BEGIN CERTIFICATE-----\nCERT_VERSION_" + i + "\n-----END CERTIFICATE-----\n" ) ;
}
await ( ) . atMost ( 2 , SECONDS )
. pollInterval ( 10 , MILLISECONDS )
. until ( ( ) - > mtime ( certFile ) ! = baseline ) ;
Files . setLastModifiedTime ( certFile , FileTime . fromMillis ( baseline + 1000 ) ) ;
ReflectionTestUtils . invokeMethod ( certificateReloadManager , "checkCertificates" ) ;
@ -242,7 +236,7 @@ public class CertificateReloadManagerTest {
certificateReloadManager . registerWatcher ( "test-cert" , certFile , reloadCount : : incrementAndGet ) ;
long baseline = mtime ( certFile ) ;
writeFileAndAwaitMtimeChang e ( certFile , "-----BEGIN CERTIFICATE-----\nMODIFIED\n-----END CERTIFICATE-----\n" , baseline ) ;
writeFileAndBumpMtim e ( certFile , "-----BEGIN CERTIFICATE-----\nMODIFIED\n-----END CERTIFICATE-----\n" , baseline ) ;
for ( int i = 0 ; i < 5 ; i + + ) {
new Thread ( ( ) - > {
@ -272,7 +266,7 @@ public class CertificateReloadManagerTest {
certificateReloadManager . registerWatcher ( "test-cert" , certFile , reloadCount : : incrementAndGet ) ;
long baseline = mtime ( certFile ) ;
writeFileAndAwaitMtimeChang e ( certFile , originalContent , baseline ) ;
writeFileAndBumpMtim e ( certFile , originalContent , baseline ) ;
ReflectionTestUtils . invokeMethod ( certificateReloadManager , "checkCertificates" ) ;
@ -289,7 +283,7 @@ public class CertificateReloadManagerTest {
} ) ;
long baseline = mtime ( certFile ) ;
writeFileAndAwaitMtimeChang e ( certFile , "-----BEGIN CERTIFICATE-----\nBAD_CERT\n-----END CERTIFICATE-----\n" , baseline ) ;
writeFileAndBumpMtim e ( certFile , "-----BEGIN CERTIFICATE-----\nBAD_CERT\n-----END CERTIFICATE-----\n" , baseline ) ;
for ( int i = 0 ; i < 15 ; i + + ) {
ReflectionTestUtils . invokeMethod ( certificateReloadManager , "checkCertificates" ) ;
@ -311,19 +305,41 @@ public class CertificateReloadManagerTest {
} ) ;
long baseline = mtime ( certFile ) ;
writeFileAndAwaitMtimeChang e ( certFile , "-----BEGIN CERTIFICATE-----\nBAD_CERT\n-----END CERTIFICATE-----\n" , baseline ) ;
writeFileAndBumpMtim e ( certFile , "-----BEGIN CERTIFICATE-----\nBAD_CERT\n-----END CERTIFICATE-----\n" , baseline ) ;
ReflectionTestUtils . invokeMethod ( certificateReloadManager , "checkCertificates" ) ;
assertThat ( reloadAttempts . get ( ) ) . isEqualTo ( 1 ) ;
shouldFail . set ( 0 ) ;
long baseline2 = mtime ( certFile ) ;
writeFileAndAwaitMtimeChang e ( certFile , "-----BEGIN CERTIFICATE-----\nGOOD_CERT\n-----END CERTIFICATE-----\n" , baseline2 ) ;
writeFileAndBumpMtim e ( certFile , "-----BEGIN CERTIFICATE-----\nGOOD_CERT\n-----END CERTIFICATE-----\n" , baseline2 ) ;
ReflectionTestUtils . invokeMethod ( certificateReloadManager , "checkCertificates" ) ;
assertThat ( reloadAttempts . get ( ) ) . isEqualTo ( 2 ) ;
}
@Test
public void givenContentChangedButMtimeUnchanged_whenCheckForChanges_thenShouldTriggerReload ( ) throws Exception {
// Bug fingerprint: a cert-manager rotation that lands in the same wall-clock millisecond as the
// watcher's recorded baseline mtime. Files.getLastModifiedTime().toMillis() truncates to the ms,
// so the rotated content shares the baseline mtime and an mtime-only gate would never re-hash it.
AtomicInteger reloadCount = new AtomicInteger ( 0 ) ;
certificateReloadManager . registerWatcher ( "test-cert" , certFile , reloadCount : : incrementAndGet ) ;
long baseline = mtime ( certFile ) ;
Files . writeString ( certFile , "-----BEGIN CERTIFICATE-----\nROTATED_SAME_MS\n-----END CERTIFICATE-----\n" ) ;
// Force the mtime back to the exact baseline millisecond — content changed, timestamp did not.
Files . setLastModifiedTime ( certFile , FileTime . fromMillis ( baseline ) ) ;
// Sanity guard: the watcher observes a timestamp identical to its recorded baseline.
assertThat ( mtime ( certFile ) ) . isEqualTo ( baseline ) ;
ReflectionTestUtils . invokeMethod ( certificateReloadManager , "checkCertificates" ) ;
assertThat ( reloadCount . get ( ) ) . isEqualTo ( 1 ) ;
}
@Test
public void givenCallbackHitMaxFailures_whenFileChangesToNewContent_thenShouldResetAndRetry ( ) throws Exception {
AtomicInteger reloadAttempts = new AtomicInteger ( 0 ) ;
@ -337,7 +353,7 @@ public class CertificateReloadManagerTest {
} ) ;
long baseline = mtime ( certFile ) ;
writeFileAndAwaitMtimeChang e ( certFile , "-----BEGIN CERTIFICATE-----\nBAD_CERT\n-----END CERTIFICATE-----\n" , baseline ) ;
writeFileAndBumpMtim e ( certFile , "-----BEGIN CERTIFICATE-----\nBAD_CERT\n-----END CERTIFICATE-----\n" , baseline ) ;
for ( int i = 0 ; i < 15 ; i + + ) {
ReflectionTestUtils . invokeMethod ( certificateReloadManager , "checkCertificates" ) ;
@ -346,7 +362,7 @@ public class CertificateReloadManagerTest {
shouldFail . set ( 0 ) ;
long baseline2 = mtime ( certFile ) ;
writeFileAndAwaitMtimeChang e ( certFile , "-----BEGIN CERTIFICATE-----\nFIXED_CERT\n-----END CERTIFICATE-----\n" , baseline2 ) ;
writeFileAndBumpMtim e ( certFile , "-----BEGIN CERTIFICATE-----\nFIXED_CERT\n-----END CERTIFICATE-----\n" , baseline2 ) ;
ReflectionTestUtils . invokeMethod ( certificateReloadManager , "checkCertificates" ) ;
assertThat ( reloadAttempts . get ( ) ) . isEqualTo ( 11 ) ;