This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 00e094c9 fix: refresh K8s certificate expiry status (#765)
00e094c9 is described below

commit 00e094c939062b3fd6edf437c8f4cda1e136ec8e
Author: Rui <[email protected]>
AuthorDate: Mon Aug 3 11:38:53 2026 +0800

    fix: refresh K8s certificate expiry status (#765)
---
 .../studio/cluster/k8s/K8sCertService.java         |  52 ++++++++--
 .../studio/cluster/k8s/K8sCertServiceTest.java     | 107 ++++++++++++++++++---
 2 files changed, 139 insertions(+), 20 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertService.java
index 8230abdc..88206e44 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertService.java
@@ -19,31 +19,49 @@ package org.apache.rocketmq.studio.cluster.k8s;
 import org.apache.rocketmq.studio.common.domain.enums.CertStatus;
 import org.apache.rocketmq.studio.common.domain.enums.CertType;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
-import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.time.Clock;
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
 @Slf4j
 @Service
-@RequiredArgsConstructor
 public class K8sCertService {
 
+    private static final int EXPIRING_THRESHOLD_DAYS = 30;
+
     private final K8sCertRepository k8sCertRepository;
+    private final Clock clock;
+
+    @Autowired
+    public K8sCertService(K8sCertRepository k8sCertRepository) {
+        this(k8sCertRepository, Clock.systemDefaultZone());
+    }
+
+    K8sCertService(K8sCertRepository k8sCertRepository, Clock clock) {
+        this.k8sCertRepository = k8sCertRepository;
+        this.clock = clock;
+    }
 
     public List<K8sCertVO> listCerts() {
         log.info("Listing all K8s certificates");
-        return k8sCertRepository.findAll();
+        LocalDateTime now = LocalDateTime.now(clock);
+        return k8sCertRepository.findAll().stream()
+                .map(cert -> refreshExpirationState(cert, now))
+                .collect(Collectors.toCollection(ArrayList::new));
     }
 
     public K8sCertVO createCert(CreateCertDTO command) {
         log.info("Creating K8s certificate: {}", command.getName());
 
-        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime now = LocalDateTime.now(clock);
         LocalDateTime notAfter = now.plusYears(1);
 
         K8sCertVO cert = K8sCertVO.builder()
@@ -91,9 +109,10 @@ public class K8sCertService {
         if (command.getSan() != null) {
             updated.setSan(command.getSan());
         }
-        updated.setUpdatedAt(LocalDateTime.now());
+        LocalDateTime now = LocalDateTime.now(clock);
+        updated.setUpdatedAt(now);
 
-        K8sCertVO saved = k8sCertRepository.save(updated);
+        K8sCertVO saved = 
k8sCertRepository.save(refreshExpirationState(updated, now));
         log.info("K8s certificate updated: {} (id={})", saved.getName(), 
saved.getId());
         return saved;
     }
@@ -103,7 +122,7 @@ public class K8sCertService {
         K8sCertVO existing = k8sCertRepository.findById(command.getId())
                 .orElseThrow(() -> new BusinessException(404, "Certificate not 
found: " + command.getId()));
 
-        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime now = LocalDateTime.now(clock);
         LocalDateTime notAfter = now.plusYears(1);
 
         K8sCertVO renewed = copyOf(existing);
@@ -126,6 +145,25 @@ public class K8sCertService {
         log.info("K8s certificate deleted: {}", command.getId());
     }
 
+    private K8sCertVO refreshExpirationState(K8sCertVO cert, LocalDateTime 
now) {
+        K8sCertVO refreshed = copyOf(cert);
+        LocalDateTime notAfter = refreshed.getNotAfter();
+        if (notAfter == null) {
+            return refreshed;
+        }
+
+        int daysRemaining = (int) ChronoUnit.DAYS.between(now, notAfter);
+        refreshed.setDaysRemaining(daysRemaining);
+        if (!notAfter.isAfter(now)) {
+            refreshed.setStatus(CertStatus.expired);
+        } else if (daysRemaining <= EXPIRING_THRESHOLD_DAYS) {
+            refreshed.setStatus(CertStatus.expiring);
+        } else {
+            refreshed.setStatus(CertStatus.valid);
+        }
+        return refreshed;
+    }
+
     private K8sCertVO copyOf(K8sCertVO cert) {
         K8sCertVO copy = K8sCertVO.builder()
                 .name(cert.getName())
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertServiceTest.java
index b023935f..3bd4d515 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/k8s/K8sCertServiceTest.java
@@ -23,11 +23,13 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
+import java.time.Clock;
+import java.time.Instant;
 import java.time.LocalDateTime;
+import java.time.ZoneOffset;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -42,16 +44,19 @@ import static org.mockito.Mockito.when;
 @ExtendWith(MockitoExtension.class)
 class K8sCertServiceTest {
 
+    private static final Clock CLOCK = Clock.fixed(
+            Instant.parse("2025-07-01T00:00:00Z"), ZoneOffset.UTC);
+
     @Mock
     private K8sCertRepository k8sCertRepository;
 
-    @InjectMocks
     private K8sCertService k8sCertService;
 
     private K8sCertVO sampleCert;
 
     @BeforeEach
     void setUp() {
+        k8sCertService = new K8sCertService(k8sCertRepository, CLOCK);
         sampleCert = K8sCertVO.builder()
                 .name("rocketmq-tls")
                 .namespace("mq-system")
@@ -99,6 +104,35 @@ class K8sCertServiceTest {
         assertThat(result).isEmpty();
     }
 
+    @Test
+    void listCertsShouldRefreshTimeDerivedExpiryFields() {
+        LocalDateTime now = LocalDateTime.now(CLOCK);
+        sampleCert.setNotAfter(now.minusDays(1));
+        sampleCert.setStatus(CertStatus.valid);
+        sampleCert.setDaysRemaining(180);
+        K8sCertVO expiresNowCert = copyWithExpiry("cert-expires-now", now,
+                CertStatus.valid, 180);
+        K8sCertVO expiringCert = copyWithExpiry("cert-expiring", 
now.plusDays(30),
+                CertStatus.expired, -1);
+        K8sCertVO validCert = copyWithExpiry("cert-valid", now.plusDays(31),
+                CertStatus.expired, -1);
+        when(k8sCertRepository.findAll())
+                .thenReturn(List.of(sampleCert, expiresNowCert, expiringCert, 
validCert));
+
+        List<K8sCertVO> result = k8sCertService.listCerts();
+
+        assertThat(result).extracting(K8sCertVO::getStatus)
+                .containsExactly(CertStatus.expired, CertStatus.expired,
+                        CertStatus.expiring, CertStatus.valid);
+        assertThat(result).extracting(K8sCertVO::getDaysRemaining)
+                .containsExactly(-1, 0, 30, 31);
+        assertThat(result.get(0)).isNotSameAs(sampleCert);
+        assertThat(sampleCert.getStatus()).isEqualTo(CertStatus.valid);
+        assertThat(sampleCert.getDaysRemaining()).isEqualTo(180);
+        assertThat(expiringCert.getStatus()).isEqualTo(CertStatus.expired);
+        assertThat(expiringCert.getDaysRemaining()).isEqualTo(-1);
+    }
+
     @Test
     void createCertShouldCreateAndSaveCert() {
         CreateCertDTO command = CreateCertDTO.builder()
@@ -119,6 +153,7 @@ class K8sCertServiceTest {
         });
 
         K8sCertVO result = k8sCertService.createCert(command);
+        LocalDateTime now = LocalDateTime.now(CLOCK);
 
         assertThat(result.getName()).isEqualTo("new-tls-cert");
         assertThat(result.getNamespace()).isEqualTo("default");
@@ -127,9 +162,11 @@ class K8sCertServiceTest {
         assertThat(result.getIssuer()).isEqualTo("vault");
         assertThat(result.getStatus()).isEqualTo(CertStatus.valid);
         assertThat(result.getSan()).containsExactly("svc.example.com");
-        assertThat(result.getNotBefore()).isNotNull();
-        assertThat(result.getNotAfter()).isAfter(result.getNotBefore());
-        assertThat(result.getDaysRemaining()).isGreaterThan(0);
+        assertThat(result.getNotBefore()).isEqualTo(now);
+        assertThat(result.getNotAfter()).isEqualTo(now.plusYears(1));
+        assertThat(result.getDaysRemaining()).isEqualTo(365);
+        assertThat(result.getCreatedAt()).isEqualTo(now);
+        assertThat(result.getUpdatedAt()).isEqualTo(now);
         verify(k8sCertRepository).save(any(K8sCertVO.class));
     }
 
@@ -149,9 +186,10 @@ class K8sCertServiceTest {
         k8sCertService.createCert(command);
 
         K8sCertVO saved = captor.getValue();
-        assertThat(saved.getNotAfter()).isAfter(saved.getNotBefore());
-        long expectedDays = 
java.time.temporal.ChronoUnit.DAYS.between(saved.getNotBefore(), 
saved.getNotAfter());
-        assertThat(saved.getDaysRemaining()).isEqualTo((int) expectedDays);
+        LocalDateTime now = LocalDateTime.now(CLOCK);
+        assertThat(saved.getNotBefore()).isEqualTo(now);
+        assertThat(saved.getNotAfter()).isEqualTo(now.plusYears(1));
+        assertThat(saved.getDaysRemaining()).isEqualTo(365);
     }
 
     @Test
@@ -179,7 +217,7 @@ class K8sCertServiceTest {
         assertThat(result.getSan()).containsExactly("new.example.com");
         assertThat(result.getId()).isEqualTo("cert-1");
         assertThat(result.getCreatedAt()).isEqualTo(LocalDateTime.of(2024, 12, 
1, 0, 0));
-        assertThat(result.getUpdatedAt()).isAfter(sampleCert.getUpdatedAt());
+        assertThat(result.getUpdatedAt()).isEqualTo(LocalDateTime.now(CLOCK));
         assertThat(result).isNotSameAs(sampleCert);
         assertThat(sampleCert.getName()).isEqualTo("rocketmq-tls");
         assertThat(sampleCert.getType()).isEqualTo(CertType.TLS);
@@ -187,6 +225,28 @@ class K8sCertServiceTest {
         verify(k8sCertRepository).save(any(K8sCertVO.class));
     }
 
+    @Test
+    void updateCertShouldRefreshTimeDerivedExpiryFields() {
+        LocalDateTime now = LocalDateTime.now(CLOCK);
+        sampleCert.setNotAfter(now.minusDays(1));
+        sampleCert.setStatus(CertStatus.valid);
+        sampleCert.setDaysRemaining(180);
+        
when(k8sCertRepository.findById("cert-1")).thenReturn(Optional.of(sampleCert));
+        
when(k8sCertRepository.save(any(K8sCertVO.class))).thenAnswer(invocation -> 
invocation.getArgument(0));
+        UpdateCertDTO command = UpdateCertDTO.builder()
+                .id("cert-1")
+                .name("updated-expired-cert")
+                .build();
+
+        K8sCertVO result = k8sCertService.updateCert(command);
+
+        assertThat(result.getStatus()).isEqualTo(CertStatus.expired);
+        assertThat(result.getDaysRemaining()).isEqualTo(-1);
+        assertThat(sampleCert.getStatus()).isEqualTo(CertStatus.valid);
+        assertThat(sampleCert.getDaysRemaining()).isEqualTo(180);
+        verify(k8sCertRepository).save(result);
+    }
+
     @Test
     void updateCertShouldPreserveExistingFieldsWhenCommandFieldsAreNull() {
         
when(k8sCertRepository.findById("cert-1")).thenReturn(Optional.of(sampleCert));
@@ -253,12 +313,13 @@ class K8sCertServiceTest {
         RenewCertDTO command = RenewCertDTO.builder().id("cert-1").build();
 
         K8sCertVO result = k8sCertService.renewCert(command);
+        LocalDateTime now = LocalDateTime.now(CLOCK);
 
         assertThat(result.getStatus()).isEqualTo(CertStatus.valid);
-        assertThat(result.getDaysRemaining()).isGreaterThan(0);
-        assertThat(result.getNotBefore()).isNotNull();
-        assertThat(result.getNotAfter()).isAfter(result.getNotBefore());
-        assertThat(result.getUpdatedAt()).isNotNull();
+        assertThat(result.getDaysRemaining()).isEqualTo(365);
+        assertThat(result.getNotBefore()).isEqualTo(now);
+        assertThat(result.getNotAfter()).isEqualTo(now.plusYears(1));
+        assertThat(result.getUpdatedAt()).isEqualTo(now);
         assertThat(result.getId()).isEqualTo("cert-1");
         assertThat(result.getCreatedAt()).isEqualTo(sampleCert.getCreatedAt());
         assertThat(result).isNotSameAs(sampleCert);
@@ -324,4 +385,24 @@ class K8sCertServiceTest {
                 .isInstanceOf(BusinessException.class)
                 .hasMessageContaining("Certificate not found: nonexistent");
     }
+
+    private K8sCertVO copyWithExpiry(String id, LocalDateTime notAfter, 
CertStatus status,
+                                     int daysRemaining) {
+        K8sCertVO cert = K8sCertVO.builder()
+                .name(sampleCert.getName())
+                .namespace(sampleCert.getNamespace())
+                .cluster(sampleCert.getCluster())
+                .type(sampleCert.getType())
+                .issuer(sampleCert.getIssuer())
+                .notBefore(sampleCert.getNotBefore())
+                .notAfter(notAfter)
+                .status(status)
+                .daysRemaining(daysRemaining)
+                .san(sampleCert.getSan())
+                .build();
+        cert.setId(id);
+        cert.setCreatedAt(sampleCert.getCreatedAt());
+        cert.setUpdatedAt(sampleCert.getUpdatedAt());
+        return cert;
+    }
 }

Reply via email to