diff --git a/server/src/main/java/com/rocketmq/studio/ops/audit/AuditService.java b/server/src/main/java/com/rocketmq/studio/ops/audit/AuditService.java index f5968b8c..e1d98b89 100644 --- a/server/src/main/java/com/rocketmq/studio/ops/audit/AuditService.java +++ b/server/src/main/java/com/rocketmq/studio/ops/audit/AuditService.java @@ -17,6 +17,7 @@ package com.rocketmq.studio.ops.audit; import com.rocketmq.studio.common.domain.PageResult; +import com.rocketmq.studio.common.exception.BusinessException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -38,6 +39,7 @@ public class AuditService { public PageResult queryLogs(int page, int pageSize, String search, String operationType, String startDate, String endDate, String result) { + validatePagination(page, pageSize); log.info("Querying audit logs, page={}, pageSize={}, search={}, operationType={}, result={}", page, pageSize, search, operationType, result); @@ -47,8 +49,9 @@ public PageResult queryLogs(int page, int pageSize, String search List allRecords = auditRepository.findAll(search, operationType, start, end, result); long total = allRecords.size(); - int fromIndex = Math.min((page - 1) * pageSize, allRecords.size()); - int toIndex = Math.min(fromIndex + pageSize, allRecords.size()); + long offset = (long) (page - 1) * pageSize; + int fromIndex = (int) Math.min(offset, allRecords.size()); + int toIndex = (int) Math.min((long) fromIndex + pageSize, allRecords.size()); List pageRecords = allRecords.subList(fromIndex, toIndex); return PageResult.of(pageRecords, total, page, pageSize); @@ -56,11 +59,23 @@ public PageResult queryLogs(int page, int pageSize, String search public int cleanupLogs(int beforeDays) { + if (beforeDays <= 0) { + throw new BusinessException(400, "beforeDays must be greater than 0"); + } log.info("Cleaning up audit logs older than {} days", beforeDays); LocalDateTime cutoff = LocalDateTime.now().minusDays(beforeDays); return auditRepository.deleteBefore(cutoff); } + private void validatePagination(int page, int pageSize) { + if (page <= 0) { + throw new BusinessException(400, "page must be greater than 0"); + } + if (pageSize <= 0) { + throw new BusinessException(400, "pageSize must be greater than 0"); + } + } + private LocalDateTime parseDate(String dateStr, boolean startOfDay) { if (dateStr == null || dateStr.isEmpty()) { return null; diff --git a/server/src/test/java/com/rocketmq/studio/ops/audit/AuditServiceTest.java b/server/src/test/java/com/rocketmq/studio/ops/audit/AuditServiceTest.java index e0446328..2abcb631 100644 --- a/server/src/test/java/com/rocketmq/studio/ops/audit/AuditServiceTest.java +++ b/server/src/test/java/com/rocketmq/studio/ops/audit/AuditServiceTest.java @@ -17,6 +17,7 @@ package com.rocketmq.studio.ops.audit; import com.rocketmq.studio.common.domain.PageResult; +import com.rocketmq.studio.common.exception.BusinessException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -30,6 +31,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; @@ -107,6 +109,35 @@ void queryLogsShouldReturnEmptyWhenPageExceedsTotal() { assertThat(result.getTotal()).isEqualTo(1); } + @Test + void queryLogsShouldRejectNonPositivePage() { + assertThatThrownBy(() -> auditService.queryLogs(0, 10, null, null, null, null, null)) + .isInstanceOf(BusinessException.class) + .hasMessage("page must be greater than 0") + .satisfies(ex -> assertThat(((BusinessException) ex).getCode()).isEqualTo(400)); + } + + @Test + void queryLogsShouldRejectNonPositivePageSize() { + assertThatThrownBy(() -> auditService.queryLogs(1, 0, null, null, null, null, null)) + .isInstanceOf(BusinessException.class) + .hasMessage("pageSize must be greater than 0") + .satisfies(ex -> assertThat(((BusinessException) ex).getCode()).isEqualTo(400)); + } + + @Test + void queryLogsShouldAvoidOffsetOverflow() { + AuditRecordVO record = AuditRecordVO.builder().operationType("CREATE").build(); + when(auditRepository.findAll(isNull(), isNull(), isNull(), isNull(), isNull())) + .thenReturn(List.of(record)); + + PageResult result = auditService.queryLogs( + Integer.MAX_VALUE, Integer.MAX_VALUE, null, null, null, null, null); + + assertThat(result.getItems()).isEmpty(); + assertThat(result.getTotal()).isEqualTo(1); + } + @Test void queryLogsShouldPassSearchFilterToRepository() { when(auditRepository.findAll(eq("topic-a"), isNull(), isNull(), isNull(), isNull())) @@ -191,6 +222,18 @@ void cleanupLogsShouldReturnZeroWhenNoOldRecords() { assertThat(result).isZero(); } + @Test + void cleanupLogsShouldRejectNonPositiveRetention() { + assertThatThrownBy(() -> auditService.cleanupLogs(0)) + .isInstanceOf(BusinessException.class) + .hasMessage("beforeDays must be greater than 0") + .satisfies(ex -> assertThat(((BusinessException) ex).getCode()).isEqualTo(400)); + + assertThatThrownBy(() -> auditService.cleanupLogs(-1)) + .isInstanceOf(BusinessException.class) + .hasMessage("beforeDays must be greater than 0"); + } + @Test void queryLogsShouldHandleAllFiltersTogether() { when(auditRepository.findAll(eq("admin"), eq("DELETE"), any(LocalDateTime.class),