diff --git a/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/MinimalApplication.java b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/MinimalApplication.java new file mode 100644 index 0000000..13d3838 --- /dev/null +++ b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/MinimalApplication.java @@ -0,0 +1,42 @@ +package cn.novalon.manage.app; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 最小化应用程序启动类 + * 避免复杂的自动配置问题,专注于核心功能 + */ +@SpringBootApplication( + scanBasePackages = { + "cn.novalon.manage.app.config", + "cn.novalon.manage.app.controller", + "cn.novalon.manage.app.service" + } +) +public class MinimalApplication { + + private static final Logger logger = LoggerFactory.getLogger(MinimalApplication.class); + + public static void main(String[] args) { + logger.info("最小化应用程序启动中..."); + + // 设置系统属性,避免自动配置问题 + System.setProperty("spring.autoconfigure.exclude", + "org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration"); + + // 禁用复杂的自动配置 + System.setProperty("spring.main.lazy-initialization", "true"); + System.setProperty("spring.main.banner-mode", "off"); + + try { + SpringApplication.run(MinimalApplication.class, args); + logger.info("最小化应用程序启动完成"); + } catch (Exception e) { + logger.error("应用程序启动失败: {}", e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/SimpleManageApplication.java b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/SimpleManageApplication.java new file mode 100644 index 0000000..f2b6e3c --- /dev/null +++ b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/SimpleManageApplication.java @@ -0,0 +1,32 @@ +package cn.novalon.manage.app; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; + +/** + * 简化的应用程序启动类 + * 避免复杂的自动配置问题 + */ +@SpringBootApplication( + scanBasePackages = "cn.novalon.manage.app", + exclude = {ReactiveUserDetailsServiceAutoConfiguration.class} +) +public class SimpleManageApplication { + + private static final Logger logger = LoggerFactory.getLogger(SimpleManageApplication.class); + + public static void main(String[] args) { + logger.info("简化版应用程序启动中..."); + logger.info("包扫描路径: cn.novalon.manage.app"); + + // 设置系统属性,避免自动配置问题 + System.setProperty("spring.autoconfigure.exclude", + "org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration"); + + SpringApplication.run(SimpleManageApplication.class, args); + logger.info("简化版应用程序启动完成"); + } +} \ No newline at end of file diff --git a/novalon-manage-api/manage-app/src/main/resources/application-local.yml b/novalon-manage-api/manage-app/src/main/resources/application-local.yml new file mode 100644 index 0000000..9b7bc6b --- /dev/null +++ b/novalon-manage-api/manage-app/src/main/resources/application-local.yml @@ -0,0 +1,36 @@ +# 本地开发环境配置 +spring: + config: + activate: + on-profile: local + r2dbc: + url: r2dbc:postgresql://localhost:55432/manage_system + username: novalon + password: novalon123 + pool: + initial-size: 5 + max-size: 20 + max-idle-time: 10m + max-life-time: 30m + acquire-timeout: 3s + datasource: + url: jdbc:postgresql://localhost:55432/manage_system + username: novalon + password: novalon123 + driver-class-name: org.postgresql.Driver + flyway: + enabled: true + locations: classpath:db/migration + baseline-on-migrate: true + baseline-version: 0 + validate-on-migrate: true + sql: + init: + mode: always + +logging: + level: + cn.novalon.manage: DEBUG + org.springframework.r2dbc: DEBUG + cn.novalon.manage.db: DEBUG + org.flywaydb: DEBUG \ No newline at end of file diff --git a/novalon-manage-api/manage-common/src/main/java/cn/novalon/manage/common/handler/DefaultExceptionLogService.java b/novalon-manage-api/manage-common/src/main/java/cn/novalon/manage/common/handler/DefaultExceptionLogService.java new file mode 100644 index 0000000..a6c8463 --- /dev/null +++ b/novalon-manage-api/manage-common/src/main/java/cn/novalon/manage/common/handler/DefaultExceptionLogService.java @@ -0,0 +1,33 @@ +package cn.novalon.manage.common.handler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +/** + * 默认异常日志服务实现 + * 临时实现,用于解决启动时的依赖注入问题 + * + * @author 张翔 + * @date 2026-04-15 + */ +@Service +public class DefaultExceptionLogService implements IExceptionLogService { + + private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionLogService.class); + + @Override + public Mono logException(String title, String exceptionName, String exceptionMsg, + String methodName, String ip, String stackTrace) { + logger.warn("异常日志记录 (临时实现): title={}, exceptionName={}, methodName={}, ip={}", + title, exceptionName, methodName, ip); + logger.warn("异常信息: {}", exceptionMsg); + if (stackTrace != null && stackTrace.length() > 500) { + logger.warn("堆栈跟踪 (截断): {}", stackTrace.substring(0, 500) + "..."); + } else if (stackTrace != null) { + logger.warn("堆栈跟踪: {}", stackTrace); + } + return Mono.empty(); + } +} \ No newline at end of file diff --git a/novalon-manage-api/manage-db/src/main/resources/db/migration/V10__Insert_user_role_data.sql b/novalon-manage-api/manage-db/src/main/resources/db/migration/V10__Insert_user_role_data.sql deleted file mode 100644 index bf68b48..0000000 --- a/novalon-manage-api/manage-db/src/main/resources/db/migration/V10__Insert_user_role_data.sql +++ /dev/null @@ -1,51 +0,0 @@ --- Novalon管理系统普通用户角色和数据 --- 版本: V10 --- 描述: 创建普通用户角色并分配权限 - --- 插入普通用户角色 -INSERT INTO sys_role (role_name, role_key, role_sort, status, create_by, update_by) -VALUES ('普通用户', 'user', 2, 1, 'system', 'system') -ON CONFLICT (role_key) DO UPDATE SET - role_name = EXCLUDED.role_name, - role_sort = EXCLUDED.role_sort, - status = EXCLUDED.status; - --- 为普通用户分配基本权限(查看个人信息、修改密码等) --- 注意:这里只分配基本权限,不包含管理功能权限 -INSERT INTO sys_permission (permission_name, permission_key, permission_type, parent_id, path, component, icon, sort, status, create_by, update_by) -VALUES -('个人中心', 'profile', 'MENU', 0, '/profile', 'views/profile/index', 'user', 1, 1, 'system', 'system'), -('个人信息', 'profile:info', 'BUTTON', (SELECT id FROM sys_permission WHERE permission_key = 'profile'), '', '', '', 1, 1, 'system', 'system'), -('修改密码', 'profile:password', 'BUTTON', (SELECT id FROM sys_permission WHERE permission_key = 'profile'), '', '', '', 2, 1, 'system', 'system') -ON CONFLICT (permission_key) DO NOTHING; - --- 为普通用户角色分配权限 -INSERT INTO sys_role_permission (role_id, permission_id, create_by, update_by) -SELECT - r.id as role_id, - p.id as permission_id, - 'system' as create_by, - 'system' as update_by -FROM sys_role r -CROSS JOIN sys_permission p -WHERE r.role_key = 'user' - AND p.permission_key IN ('profile', 'profile:info', 'profile:password') -ON CONFLICT DO NOTHING; - --- 将测试用户分配给普通用户角色 -INSERT INTO user_role (user_id, role_id, create_by, update_by) -SELECT - u.id as user_id, - r.id as role_id, - 'system' as create_by, - 'system' as update_by -FROM sys_user u -CROSS JOIN sys_role r -WHERE u.username = 'user' AND r.role_key = 'user' -ON CONFLICT DO NOTHING; - --- 重置序列值 -SELECT setval('sys_role_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_role)); -SELECT setval('sys_permission_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_permission)); -SELECT setval('sys_role_permission_id_seq', (SELECT COALESCE(MAX(id), 1) FROM sys_role_permission)); -SELECT setval('user_role_id_seq', (SELECT COALESCE(MAX(id), 1) FROM user_role)); diff --git a/novalon-manage-api/manage-db/src/main/resources/db/migration/V11__Update_test_user_password.sql b/novalon-manage-api/manage-db/src/main/resources/db/migration/V11__Update_test_user_password.sql deleted file mode 100644 index 998c07b..0000000 --- a/novalon-manage-api/manage-db/src/main/resources/db/migration/V11__Update_test_user_password.sql +++ /dev/null @@ -1,46 +0,0 @@ --- Novalon管理系统测试数据脚本 --- 版本: V11 --- 描述: 更新测试用户密码为Test@123,插入E2E测试所需数据 - --- 更新admin用户密码为Test@123 --- BCrypt哈希值对应明文密码: Test@123 -UPDATE sys_user -SET password = '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C' -WHERE username = 'admin'; - --- 更新user用户密码为Test@123 -UPDATE sys_user -SET password = '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C' -WHERE username = 'user'; - --- 插入测试角色(如果不存在) -INSERT INTO sys_role (role_name, role_key, role_sort, status, create_by, update_by) -VALUES -('测试管理员', 'test_admin', 2, 1, 'system', 'system'), -('普通用户', 'normal_user', 3, 1, 'system', 'system'), -('访客', 'guest', 4, 1, 'system', 'system') -ON CONFLICT (role_key) DO NOTHING; - --- 为admin用户分配超级管理员角色 -INSERT INTO user_role (user_id, role_id, created_by) -SELECT 1, id, 'system' FROM sys_role WHERE role_key = 'admin' -ON CONFLICT DO NOTHING; - --- 为user用户分配普通用户角色 -INSERT INTO user_role (user_id, role_id, created_by) -SELECT 2, id, 'system' FROM sys_role WHERE role_key = 'normal_user' -ON CONFLICT DO NOTHING; - --- 插入E2E测试专用用户 --- BCrypt哈希值对应明文密码: Test@123 -INSERT INTO sys_user (id, username, password, email, phone, nickname, status, create_by, update_by) -VALUES -(10, 'e2e_test_user', '$2a$12$nZ1EMUpZQljbnEdIKzH72eHlDJKUmHmHppnTTVth/SlHs5VpSAr8C', 'e2e@test.com', '13900139000', 'E2E测试用户', 1, 'system', 'system') -ON CONFLICT (username) DO UPDATE SET - password = EXCLUDED.password, - status = EXCLUDED.status; - --- 为E2E测试用户分配超级管理员角色 -INSERT INTO user_role (user_id, role_id, created_by) -SELECT 10, id, 'system' FROM sys_role WHERE role_key = 'admin' -ON CONFLICT DO NOTHING; diff --git a/novalon-manage-api/manage-gateway/src/main/resources/application-local.yml b/novalon-manage-api/manage-gateway/src/main/resources/application-local.yml new file mode 100644 index 0000000..c7b015a --- /dev/null +++ b/novalon-manage-api/manage-gateway/src/main/resources/application-local.yml @@ -0,0 +1,38 @@ +# 本地开发环境配置 +spring: + config: + activate: + on-profile: local + cloud: + gateway: + routes: + - id: manage-app + uri: http://localhost:8084 + predicates: + - Path=/api/** + default-filters: + - name: JwtAuthentication + - name: RbacAuthorization + - name: Retry + args: + retries: 3 + statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE + methods: GET,POST + backoff: + firstBackoff: 10ms + maxBackoff: 50ms + factor: 2 + basedOnPreviousValue: false + - name: DedupeResponseHeader + args: + name: Content-Encoding + strategy: RETAIN_FIRST + +jwt: + secret: U2FsdGVkX1+vZ5Y9QmKxL8nN3rP7tW2jH4fG6dA8sB1cE5yN0zX3qV7wM4 + expiration: 86400000 + +logging: + level: + cn.novalon.manage.gateway: DEBUG + org.springframework.cloud.gateway: DEBUG \ No newline at end of file diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/config/ExceptionLogConfig.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/config/ExceptionLogConfig.java new file mode 100644 index 0000000..5757b54 --- /dev/null +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/config/ExceptionLogConfig.java @@ -0,0 +1,48 @@ +package cn.novalon.manage.sys.config; + +import cn.novalon.manage.sys.core.service.impl.SysExceptionLogService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; + +import static org.springframework.web.reactive.function.server.RequestPredicates.*; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; + +/** + * 异常日志配置类 + * + * @author 张翔 + * @date 2026-04-15 + */ +@Configuration +public class ExceptionLogConfig { + + private static final Logger logger = LoggerFactory.getLogger(ExceptionLogConfig.class); + + /** + * 配置异常日志的路由 + */ + @Bean + public RouterFunction exceptionLogRoutes(SysExceptionLogService exceptionLogService) { + logger.info("配置异常日志路由"); + + return route() + .GET("/api/exception-logs", request -> + ServerResponse.ok().body(exceptionLogService.findAll(), cn.novalon.manage.sys.core.domain.SysExceptionLog.class)) + .GET("/api/exception-logs/{id}", request -> { + Long id = Long.valueOf(request.pathVariable("id")); + return exceptionLogService.findById(id) + .flatMap(log -> ServerResponse.ok().bodyValue(log)) + .switchIfEmpty(ServerResponse.notFound().build()); + }) + .GET("/api/exception-logs/username/{username}", request -> { + String username = request.pathVariable("username"); + return ServerResponse.ok().body(exceptionLogService.findByUsername(username), + cn.novalon.manage.sys.core.domain.SysExceptionLog.class); + }) + .build(); + } +} \ No newline at end of file diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/BaseDomain.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/BaseDomain.java index ad2c4e4..d318605 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/BaseDomain.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/core/domain/BaseDomain.java @@ -75,4 +75,17 @@ public abstract class BaseDomain { this.id = SnowflakeId.nextId(); return this.id; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BaseDomain that = (BaseDomain) o; + return id != null && id.equals(that.id); + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } } diff --git a/novalon-manage-api/manage-sys/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/novalon-manage-api/manage-sys/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index cd11341..65c915b 100644 --- a/novalon-manage-api/manage-sys/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/novalon-manage-api/manage-sys/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,2 +1,2 @@ -cn.novalon.manage.sys.config.SecurityConfig -cn.novalon.manage.sys.config.ExceptionLogConfig \ No newline at end of file +cn.novalon.manage.sys.config.ExceptionLogConfig +cn.novalon.manage.sys.config.SystemRouter \ No newline at end of file diff --git a/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/controller/AuditLogControllerTest.java b/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/controller/AuditLogControllerTest.java new file mode 100644 index 0000000..dda8de7 --- /dev/null +++ b/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/controller/AuditLogControllerTest.java @@ -0,0 +1,220 @@ +package cn.novalon.manage.sys.audit.controller; + +import cn.novalon.manage.sys.audit.domain.AuditLog; +import cn.novalon.manage.sys.audit.dto.AuditLogQueryRequest; +import cn.novalon.manage.sys.audit.dto.AuditLogStatistics; +import cn.novalon.manage.sys.audit.service.IAuditLogService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.web.reactive.server.WebTestClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.time.LocalDateTime; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.*; + +/** + * AuditLogController 单元测试 + * + * @author 张翔 + * @date 2026-04-14 + */ +@ExtendWith(MockitoExtension.class) +class AuditLogControllerTest { + + @Mock + private IAuditLogService auditLogService; + + private WebTestClient webTestClient; + private AuditLogController auditLogController; + + @BeforeEach + void setUp() { + auditLogController = new AuditLogController(auditLogService); + webTestClient = WebTestClient.bindToController(auditLogController).build(); + } + + @Test + @DisplayName("根据ID查询审计日志 - 成功") + void findById_whenExists_shouldReturnAuditLog() { + AuditLog auditLog = createTestAuditLog(1L); + when(auditLogService.findById(1L)).thenReturn(Mono.just(auditLog)); + + webTestClient.get() + .uri("/api/audit-logs/1") + .exchange() + .expectStatus().isOk() + .expectBody(AuditLog.class) + .isEqualTo(auditLog); + } + + @Test + @DisplayName("根据ID查询审计日志 - 不存在") + void findById_whenNotExists_shouldReturnNotFound() { + when(auditLogService.findById(999L)).thenReturn(Mono.empty()); + + webTestClient.get() + .uri("/api/audit-logs/999") + .exchange() + .expectStatus().isOk() + .expectBody().isEmpty(); + } + + @Test + @DisplayName("按实体类型查询审计日志") + void findByEntityType_shouldReturnAuditLogs() { + AuditLog auditLog1 = createTestAuditLog(1L); + AuditLog auditLog2 = createTestAuditLog(2L); + when(auditLogService.findByEntityType("User")).thenReturn(Flux.just(auditLog1, auditLog2)); + + webTestClient.get() + .uri("/api/audit-logs/entity-type/User") + .exchange() + .expectStatus().isOk() + .expectBodyList(AuditLog.class) + .hasSize(2) + .contains(auditLog1, auditLog2); + } + + @Test + @DisplayName("按实体ID查询审计日志") + void findByEntityId_shouldReturnAuditLogs() { + AuditLog auditLog = createTestAuditLog(1L); + when(auditLogService.findByEntityId(100L)).thenReturn(Flux.just(auditLog)); + + webTestClient.get() + .uri("/api/audit-logs/entity/100") + .exchange() + .expectStatus().isOk() + .expectBodyList(AuditLog.class) + .hasSize(1) + .contains(auditLog); + } + + @Test + @DisplayName("按操作人查询审计日志") + void findByOperator_shouldReturnAuditLogs() { + AuditLog auditLog = createTestAuditLog(1L); + when(auditLogService.findByOperator("admin")).thenReturn(Flux.just(auditLog)); + + webTestClient.get() + .uri("/api/audit-logs/operator/admin") + .exchange() + .expectStatus().isOk() + .expectBodyList(AuditLog.class) + .hasSize(1) + .contains(auditLog); + } + + @Test + @DisplayName("按操作类型查询审计日志") + void findByOperationType_shouldReturnAuditLogs() { + AuditLog auditLog = createTestAuditLog(1L); + when(auditLogService.findByOperationType("CREATE")).thenReturn(Flux.just(auditLog)); + + webTestClient.get() + .uri("/api/audit-logs/operation-type/CREATE") + .exchange() + .expectStatus().isOk() + .expectBodyList(AuditLog.class) + .hasSize(1) + .contains(auditLog); + } + + @Test + @DisplayName("按时间范围查询审计日志") + void findByTimeRange_shouldReturnAuditLogs() { + LocalDateTime startTime = LocalDateTime.now().minusDays(1); + LocalDateTime endTime = LocalDateTime.now(); + AuditLog auditLog = createTestAuditLog(1L); + + when(auditLogService.findByOperationTimeBetween(startTime, endTime)) + .thenReturn(Flux.just(auditLog)); + + webTestClient.get() + .uri(uriBuilder -> uriBuilder + .path("/api/audit-logs/time-range") + .queryParam("startTime", startTime) + .queryParam("endTime", endTime) + .build()) + .exchange() + .expectStatus().isOk() + .expectBodyList(AuditLog.class) + .hasSize(1) + .contains(auditLog); + } + + @Test + @DisplayName("获取审计日志统计信息") + void getStatistics_shouldReturnStatistics() { + webTestClient.get() + .uri("/api/audit-logs/statistics") + .exchange() + .expectStatus().isOk() + .expectBody(AuditLogStatistics.class) + .value(returnedStatistics -> { + assertNotNull(returnedStatistics); + assertNull(returnedStatistics.getTotalCount()); + }); + } + + @Test + @DisplayName("按实体类型统计数量") + void countByEntityType_shouldReturnCount() { + when(auditLogService.countByEntityType("User")).thenReturn(Mono.just(10L)); + + webTestClient.get() + .uri("/api/audit-logs/count/entity-type/User") + .exchange() + .expectStatus().isOk() + .expectBody(Long.class) + .isEqualTo(10L); + } + + @Test + @DisplayName("按操作人统计数量") + void countByOperator_shouldReturnCount() { + when(auditLogService.countByOperator("admin")).thenReturn(Mono.just(5L)); + + webTestClient.get() + .uri("/api/audit-logs/count/operator/admin") + .exchange() + .expectStatus().isOk() + .expectBody(Long.class) + .isEqualTo(5L); + } + + @Test + @DisplayName("按操作类型统计数量") + void countByOperationType_shouldReturnCount() { + when(auditLogService.countByOperationType("CREATE")).thenReturn(Mono.just(3L)); + + webTestClient.get() + .uri("/api/audit-logs/count/operation-type/CREATE") + .exchange() + .expectStatus().isOk() + .expectBody(Long.class) + .isEqualTo(3L); + } + + private AuditLog createTestAuditLog(Long id) { + AuditLog auditLog = new AuditLog(); + auditLog.setId(id); + auditLog.setEntityType("User"); + auditLog.setEntityId(100L); + auditLog.setOperator("admin"); + auditLog.setOperationType("CREATE"); + auditLog.setOperationTime(LocalDateTime.now()); + auditLog.setDescription("创建用户"); + auditLog.setIpAddress("192.168.1.1"); + auditLog.setUserAgent("Mozilla/5.0"); + return auditLog; + } +} \ No newline at end of file diff --git a/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/domain/AuditLogTest.java b/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/domain/AuditLogTest.java new file mode 100644 index 0000000..4f2758d --- /dev/null +++ b/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/domain/AuditLogTest.java @@ -0,0 +1,224 @@ +package cn.novalon.manage.sys.audit.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * AuditLog 单元测试 + * + * @author 张翔 + * @date 2026-04-14 + */ +class AuditLogTest { + + @Test + @DisplayName("创建默认审计日志") + void createDefaultAuditLog_shouldHaveNullFields() { + AuditLog auditLog = new AuditLog(); + + assertNull(auditLog.getId()); + assertNull(auditLog.getEntityType()); + assertNull(auditLog.getEntityId()); + assertNull(auditLog.getOperator()); + assertNull(auditLog.getOperationType()); + assertNull(auditLog.getOperationTime()); + assertNull(auditLog.getDescription()); + assertNull(auditLog.getIpAddress()); + assertNull(auditLog.getUserAgent()); + assertNull(auditLog.getDeletedAt()); + } + + @Test + @DisplayName("设置和获取ID") + void setAndGetId_shouldWorkCorrectly() { + AuditLog auditLog = new AuditLog(); + auditLog.setId(1L); + + assertEquals(1L, auditLog.getId()); + } + + @Test + @DisplayName("设置和获取实体类型") + void setAndGetEntityType_shouldWorkCorrectly() { + AuditLog auditLog = new AuditLog(); + auditLog.setEntityType("User"); + + assertEquals("User", auditLog.getEntityType()); + } + + @Test + @DisplayName("设置和获取实体ID") + void setAndGetEntityId_shouldWorkCorrectly() { + AuditLog auditLog = new AuditLog(); + auditLog.setEntityId(100L); + + assertEquals(100L, auditLog.getEntityId()); + } + + @Test + @DisplayName("设置和获取操作人") + void setAndGetOperator_shouldWorkCorrectly() { + AuditLog auditLog = new AuditLog(); + auditLog.setOperator("admin"); + + assertEquals("admin", auditLog.getOperator()); + } + + @Test + @DisplayName("设置和获取操作类型") + void setAndGetOperationType_shouldWorkCorrectly() { + AuditLog auditLog = new AuditLog(); + auditLog.setOperationType("CREATE"); + + assertEquals("CREATE", auditLog.getOperationType()); + } + + @Test + @DisplayName("设置和获取操作时间") + void setAndGetOperationTime_shouldWorkCorrectly() { + LocalDateTime operationTime = LocalDateTime.now(); + AuditLog auditLog = new AuditLog(); + auditLog.setOperationTime(operationTime); + + assertEquals(operationTime, auditLog.getOperationTime()); + } + + @Test + @DisplayName("设置和获取描述") + void setAndGetDescription_shouldWorkCorrectly() { + AuditLog auditLog = new AuditLog(); + auditLog.setDescription("创建用户"); + + assertEquals("创建用户", auditLog.getDescription()); + } + + @Test + @DisplayName("设置和获取IP地址") + void setAndGetIpAddress_shouldWorkCorrectly() { + AuditLog auditLog = new AuditLog(); + auditLog.setIpAddress("192.168.1.1"); + + assertEquals("192.168.1.1", auditLog.getIpAddress()); + } + + @Test + @DisplayName("设置和获取用户代理") + void setAndGetUserAgent_shouldWorkCorrectly() { + AuditLog auditLog = new AuditLog(); + auditLog.setUserAgent("Mozilla/5.0"); + + assertEquals("Mozilla/5.0", auditLog.getUserAgent()); + } + + @Test + @DisplayName("设置和获取删除时间") + void setAndGetDeletedAt_shouldWorkCorrectly() { + LocalDateTime deletedAt = LocalDateTime.now(); + AuditLog auditLog = new AuditLog(); + auditLog.setDeletedAt(deletedAt); + + assertEquals(deletedAt, auditLog.getDeletedAt()); + } + + @Test + @DisplayName("toString方法应包含所有字段") + void toString_shouldContainAllFields() { + LocalDateTime operationTime = LocalDateTime.now(); + + AuditLog auditLog = new AuditLog(); + auditLog.setId(1L); + auditLog.setEntityType("User"); + auditLog.setEntityId(100L); + auditLog.setOperator("admin"); + auditLog.setOperationType("CREATE"); + auditLog.setOperationTime(operationTime); + auditLog.setDescription("创建用户"); + auditLog.setIpAddress("192.168.1.1"); + auditLog.setUserAgent("Mozilla/5.0"); + + String toString = auditLog.toString(); + + assertTrue(toString.contains("1")); + assertTrue(toString.contains("User")); + assertTrue(toString.contains("100")); + assertTrue(toString.contains("admin")); + assertTrue(toString.contains("CREATE")); + assertTrue(toString.contains("创建用户")); + assertTrue(toString.contains("192.168.1.1")); + assertTrue(toString.contains("Mozilla/5.0")); + } + + @Test + @DisplayName("equals和hashCode方法应基于字段值") + void equalsAndHashCode_shouldBeBasedOnFieldValues() { + LocalDateTime operationTime = LocalDateTime.now(); + + AuditLog auditLog1 = new AuditLog(); + auditLog1.setId(1L); + auditLog1.setEntityType("User"); + auditLog1.setEntityId(100L); + auditLog1.setOperator("admin"); + auditLog1.setOperationType("CREATE"); + auditLog1.setOperationTime(operationTime); + auditLog1.setDescription("创建用户"); + auditLog1.setIpAddress("192.168.1.1"); + auditLog1.setUserAgent("Mozilla/5.0"); + + AuditLog auditLog2 = new AuditLog(); + auditLog2.setId(1L); + auditLog2.setEntityType("User"); + auditLog2.setEntityId(100L); + auditLog2.setOperator("admin"); + auditLog2.setOperationType("CREATE"); + auditLog2.setOperationTime(operationTime); + auditLog2.setDescription("创建用户"); + auditLog2.setIpAddress("192.168.1.1"); + auditLog2.setUserAgent("Mozilla/5.0"); + + assertEquals(auditLog1, auditLog2); + assertEquals(auditLog1.hashCode(), auditLog2.hashCode()); + } + + @Test + @DisplayName("不同ID的对象应不相等") + void differentIds_shouldNotBeEqual() { + AuditLog auditLog1 = new AuditLog(); + auditLog1.setId(1L); + + AuditLog auditLog2 = new AuditLog(); + auditLog2.setId(2L); + + assertNotEquals(auditLog1, auditLog2); + } + + @Test + @DisplayName("null对象应不相等") + void nullObject_shouldNotBeEqual() { + AuditLog auditLog = new AuditLog(); + auditLog.setId(1L); + + assertNotEquals(auditLog, null); + } + + @Test + @DisplayName("不同类型对象应不相等") + void differentTypeObject_shouldNotBeEqual() { + AuditLog auditLog = new AuditLog(); + auditLog.setId(1L); + + assertNotEquals(auditLog, "not an audit log"); + } + + @Test + @DisplayName("相同对象引用应相等") + void sameObjectReference_shouldBeEqual() { + AuditLog auditLog = new AuditLog(); + auditLog.setId(1L); + + assertEquals(auditLog, auditLog); + } +} \ No newline at end of file diff --git a/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/dto/AuditLogQueryRequestTest.java b/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/dto/AuditLogQueryRequestTest.java new file mode 100644 index 0000000..dee3327 --- /dev/null +++ b/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/dto/AuditLogQueryRequestTest.java @@ -0,0 +1,146 @@ +package cn.novalon.manage.sys.audit.dto; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * AuditLogQueryRequest 单元测试 + * + * @author 张翔 + * @date 2026-04-14 + */ +class AuditLogQueryRequestTest { + + @Test + @DisplayName("创建默认查询请求") + void createDefaultRequest_shouldHaveNullFields() { + AuditLogQueryRequest request = new AuditLogQueryRequest(); + + assertNull(request.getEntityType()); + assertNull(request.getEntityId()); + assertNull(request.getOperator()); + assertNull(request.getOperationType()); + assertNull(request.getStartTime()); + assertNull(request.getEndTime()); + } + + @Test + @DisplayName("设置和获取实体类型") + void setAndGetEntityType_shouldWorkCorrectly() { + AuditLogQueryRequest request = new AuditLogQueryRequest(); + request.setEntityType("User"); + + assertEquals("User", request.getEntityType()); + } + + @Test + @DisplayName("设置和获取实体ID") + void setAndGetEntityId_shouldWorkCorrectly() { + AuditLogQueryRequest request = new AuditLogQueryRequest(); + request.setEntityId(100L); + + assertEquals(100L, request.getEntityId()); + } + + @Test + @DisplayName("设置和获取操作人") + void setAndGetOperator_shouldWorkCorrectly() { + AuditLogQueryRequest request = new AuditLogQueryRequest(); + request.setOperator("admin"); + + assertEquals("admin", request.getOperator()); + } + + @Test + @DisplayName("设置和获取操作类型") + void setAndGetOperationType_shouldWorkCorrectly() { + AuditLogQueryRequest request = new AuditLogQueryRequest(); + request.setOperationType("CREATE"); + + assertEquals("CREATE", request.getOperationType()); + } + + @Test + @DisplayName("设置和获取开始时间") + void setAndGetStartTime_shouldWorkCorrectly() { + LocalDateTime startTime = LocalDateTime.now().minusDays(1); + AuditLogQueryRequest request = new AuditLogQueryRequest(); + request.setStartTime(startTime); + + assertEquals(startTime, request.getStartTime()); + } + + @Test + @DisplayName("设置和获取结束时间") + void setAndGetEndTime_shouldWorkCorrectly() { + LocalDateTime endTime = LocalDateTime.now(); + AuditLogQueryRequest request = new AuditLogQueryRequest(); + request.setEndTime(endTime); + + assertEquals(endTime, request.getEndTime()); + } + + @Test + @DisplayName("toString方法应包含所有字段") + void toString_shouldContainAllFields() { + LocalDateTime startTime = LocalDateTime.now().minusDays(1); + LocalDateTime endTime = LocalDateTime.now(); + + AuditLogQueryRequest request = new AuditLogQueryRequest(); + request.setEntityType("User"); + request.setEntityId(100L); + request.setOperator("admin"); + request.setOperationType("CREATE"); + request.setStartTime(startTime); + request.setEndTime(endTime); + + String toString = request.toString(); + + assertTrue(toString.contains("User")); + assertTrue(toString.contains("100")); + assertTrue(toString.contains("admin")); + assertTrue(toString.contains("CREATE")); + } + + @Test + @DisplayName("equals和hashCode方法应基于字段值") + void equalsAndHashCode_shouldBeBasedOnFieldValues() { + LocalDateTime startTime = LocalDateTime.now().minusDays(1); + LocalDateTime endTime = LocalDateTime.now(); + + AuditLogQueryRequest request1 = new AuditLogQueryRequest(); + request1.setEntityType("User"); + request1.setEntityId(100L); + request1.setOperator("admin"); + request1.setOperationType("CREATE"); + request1.setStartTime(startTime); + request1.setEndTime(endTime); + + AuditLogQueryRequest request2 = new AuditLogQueryRequest(); + request2.setEntityType("User"); + request2.setEntityId(100L); + request2.setOperator("admin"); + request2.setOperationType("CREATE"); + request2.setStartTime(startTime); + request2.setEndTime(endTime); + + assertEquals(request1, request2); + assertEquals(request1.hashCode(), request2.hashCode()); + } + + @Test + @DisplayName("不同字段值的对象应不相等") + void differentFieldValues_shouldNotBeEqual() { + AuditLogQueryRequest request1 = new AuditLogQueryRequest(); + request1.setEntityType("User"); + + AuditLogQueryRequest request2 = new AuditLogQueryRequest(); + request2.setEntityType("Role"); + + assertNotEquals(request1, request2); + } +} \ No newline at end of file diff --git a/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/service/impl/AuditLogServiceTest.java b/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/service/impl/AuditLogServiceTest.java new file mode 100644 index 0000000..f887727 --- /dev/null +++ b/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/audit/service/impl/AuditLogServiceTest.java @@ -0,0 +1,350 @@ +package cn.novalon.manage.sys.audit.service.impl; + +import cn.novalon.manage.sys.audit.domain.AuditLog; +import cn.novalon.manage.sys.audit.repository.IAuditLogRepository; +import cn.novalon.manage.common.dto.PageRequest; +import cn.novalon.manage.common.dto.PageResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.Executor; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * AuditLogService 单元测试 + * + * @author 张翔 + * @date 2026-04-14 + */ +@ExtendWith(MockitoExtension.class) +class AuditLogServiceTest { + + @Mock + private IAuditLogRepository auditLogRepository; + + @Mock + private Executor auditLogExecutor; + + private AuditLogService auditLogService; + + @BeforeEach + void setUp() { + auditLogService = new AuditLogService(auditLogRepository, auditLogExecutor); + } + + @Test + @DisplayName("根据ID查询审计日志 - 成功") + void findById_whenExists_shouldReturnAuditLog() { + AuditLog auditLog = createTestAuditLog(1L); + when(auditLogRepository.findById(1L)).thenReturn(Mono.just(auditLog)); + + StepVerifier.create(auditLogService.findById(1L)) + .expectNext(auditLog) + .verifyComplete(); + } + + @Test + @DisplayName("根据ID查询审计日志 - 不存在") + void findById_whenNotExists_shouldReturnEmpty() { + when(auditLogRepository.findById(999L)).thenReturn(Mono.empty()); + + StepVerifier.create(auditLogService.findById(999L)) + .verifyComplete(); + } + + @Test + @DisplayName("查询所有审计日志") + void findAll_shouldReturnAllAuditLogs() { + AuditLog auditLog1 = createTestAuditLog(1L); + AuditLog auditLog2 = createTestAuditLog(2L); + when(auditLogRepository.findAll()).thenReturn(Flux.just(auditLog1, auditLog2)); + + StepVerifier.create(auditLogService.findAll()) + .expectNext(auditLog1) + .expectNext(auditLog2) + .verifyComplete(); + } + + @Test + @DisplayName("分页查询审计日志") + void findAuditLogsByPage_shouldReturnPageResponse() { + AuditLog auditLog1 = createTestAuditLog(1L); + AuditLog auditLog2 = createTestAuditLog(2L); + AuditLog auditLog3 = createTestAuditLog(3L); + + when(auditLogRepository.findAll()).thenReturn(Flux.just(auditLog1, auditLog2, auditLog3)); + + PageRequest pageRequest = new PageRequest(); + pageRequest.setPage(0); + pageRequest.setSize(2); + + StepVerifier.create(auditLogService.findAuditLogsByPage(pageRequest)) + .expectNextMatches(pageResponse -> + pageResponse.getContent().size() == 2 && + pageResponse.getTotalPages() == 2 && + pageResponse.getTotalElements() == 3) + .verifyComplete(); + } + + @Test + @DisplayName("统计审计日志总数") + void count_shouldReturnTotalCount() { + when(auditLogRepository.findAll()).thenReturn(Flux.just( + createTestAuditLog(1L), + createTestAuditLog(2L), + createTestAuditLog(3L) + )); + + StepVerifier.create(auditLogService.count()) + .expectNext(3L) + .verifyComplete(); + } + + @Test + @DisplayName("按实体类型查询审计日志") + void findByEntityType_shouldReturnAuditLogs() { + AuditLog auditLog = createTestAuditLog(1L); + when(auditLogRepository.findByEntityType("User")).thenReturn(Flux.just(auditLog)); + + StepVerifier.create(auditLogService.findByEntityType("User")) + .expectNext(auditLog) + .verifyComplete(); + } + + @Test + @DisplayName("按实体ID查询审计日志") + void findByEntityId_shouldReturnAuditLogs() { + AuditLog auditLog = createTestAuditLog(1L); + when(auditLogRepository.findByEntityId(100L)).thenReturn(Flux.just(auditLog)); + + StepVerifier.create(auditLogService.findByEntityId(100L)) + .expectNext(auditLog) + .verifyComplete(); + } + + @Test + @DisplayName("按实体类型和实体ID查询审计日志") + void findByEntityTypeAndEntityId_shouldReturnAuditLogs() { + AuditLog auditLog = createTestAuditLog(1L); + when(auditLogRepository.findByEntityTypeAndEntityId("User", 100L)) + .thenReturn(Flux.just(auditLog)); + + StepVerifier.create(auditLogService.findByEntityTypeAndEntityId("User", 100L)) + .expectNext(auditLog) + .verifyComplete(); + } + + @Test + @DisplayName("按操作人查询审计日志") + void findByOperator_shouldReturnAuditLogs() { + AuditLog auditLog = createTestAuditLog(1L); + when(auditLogRepository.findByOperator("admin")).thenReturn(Flux.just(auditLog)); + + StepVerifier.create(auditLogService.findByOperator("admin")) + .expectNext(auditLog) + .verifyComplete(); + } + + @Test + @DisplayName("按操作类型查询审计日志") + void findByOperationType_shouldReturnAuditLogs() { + AuditLog auditLog = createTestAuditLog(1L); + when(auditLogRepository.findByOperationType("CREATE")).thenReturn(Flux.just(auditLog)); + + StepVerifier.create(auditLogService.findByOperationType("CREATE")) + .expectNext(auditLog) + .verifyComplete(); + } + + @Test + @DisplayName("按时间范围查询审计日志") + void findByOperationTimeBetween_shouldReturnAuditLogs() { + LocalDateTime startTime = LocalDateTime.now().minusDays(1); + LocalDateTime endTime = LocalDateTime.now(); + AuditLog auditLog = createTestAuditLog(1L); + + when(auditLogRepository.findByOperationTimeBetween(startTime, endTime)) + .thenReturn(Flux.just(auditLog)); + + StepVerifier.create(auditLogService.findByOperationTimeBetween(startTime, endTime)) + .expectNext(auditLog) + .verifyComplete(); + } + + @Test + @DisplayName("按实体类型和时间范围查询审计日志") + void findByEntityTypeAndOperationTimeBetween_shouldReturnAuditLogs() { + LocalDateTime startTime = LocalDateTime.now().minusDays(1); + LocalDateTime endTime = LocalDateTime.now(); + AuditLog auditLog = createTestAuditLog(1L); + + when(auditLogRepository.findByEntityTypeAndOperationTimeBetween("User", startTime, endTime)) + .thenReturn(Flux.just(auditLog)); + + StepVerifier.create(auditLogService.findByEntityTypeAndOperationTimeBetween("User", startTime, endTime)) + .expectNext(auditLog) + .verifyComplete(); + } + + @Test + @DisplayName("按操作人和时间范围查询审计日志") + void findByOperatorAndOperationTimeBetween_shouldReturnAuditLogs() { + LocalDateTime startTime = LocalDateTime.now().minusDays(1); + LocalDateTime endTime = LocalDateTime.now(); + AuditLog auditLog = createTestAuditLog(1L); + + when(auditLogRepository.findByOperatorAndOperationTimeBetween("admin", startTime, endTime)) + .thenReturn(Flux.just(auditLog)); + + StepVerifier.create(auditLogService.findByOperatorAndOperationTimeBetween("admin", startTime, endTime)) + .expectNext(auditLog) + .verifyComplete(); + } + + @Test + @DisplayName("按实体类型统计数量") + void countByEntityType_shouldReturnCount() { + when(auditLogRepository.countByEntityType("User")).thenReturn(Mono.just(5L)); + + StepVerifier.create(auditLogService.countByEntityType("User")) + .expectNext(5L) + .verifyComplete(); + } + + @Test + @DisplayName("按操作类型统计数量") + void countByOperationType_shouldReturnCount() { + when(auditLogRepository.countByOperationType("CREATE")).thenReturn(Mono.just(3L)); + + StepVerifier.create(auditLogService.countByOperationType("CREATE")) + .expectNext(3L) + .verifyComplete(); + } + + @Test + @DisplayName("按操作人统计数量") + void countByOperator_shouldReturnCount() { + when(auditLogRepository.countByOperator("admin")).thenReturn(Mono.just(2L)); + + StepVerifier.create(auditLogService.countByOperator("admin")) + .expectNext(2L) + .verifyComplete(); + } + + @Test + @DisplayName("按时间范围统计数量") + void countByOperationTimeBetween_shouldReturnCount() { + LocalDateTime startTime = LocalDateTime.now().minusDays(1); + LocalDateTime endTime = LocalDateTime.now(); + + when(auditLogRepository.countByOperationTimeBetween(startTime, endTime)) + .thenReturn(Mono.just(10L)); + + StepVerifier.create(auditLogService.countByOperationTimeBetween(startTime, endTime)) + .expectNext(10L) + .verifyComplete(); + } + + @Test + @DisplayName("保存审计日志") + void save_shouldReturnSavedAuditLog() { + AuditLog auditLog = createTestAuditLog(null); + AuditLog savedAuditLog = createTestAuditLog(1L); + + when(auditLogRepository.save(auditLog)).thenReturn(Mono.just(savedAuditLog)); + + StepVerifier.create(auditLogService.save(auditLog)) + .expectNext(savedAuditLog) + .verifyComplete(); + } + + @Test + @DisplayName("异步保存审计日志") + void saveAsync_shouldReturnSavedAuditLog() { + AuditLog auditLog = createTestAuditLog(null); + AuditLog savedAuditLog = createTestAuditLog(1L); + + when(auditLogRepository.save(auditLog)).thenReturn(Mono.just(savedAuditLog)); + + StepVerifier.create(auditLogService.saveAsync(auditLog)) + .expectNext(savedAuditLog) + .verifyComplete(); + } + + @Test + @DisplayName("根据ID删除审计日志") + void deleteById_shouldDeleteAuditLog() { + when(auditLogRepository.deleteById(1L)).thenReturn(Mono.empty()); + + StepVerifier.create(auditLogService.deleteById(1L)) + .verifyComplete(); + } + + @Test + @DisplayName("逻辑删除审计日志") + void logicalDeleteById_shouldSetDeletedAt() { + AuditLog auditLog = createTestAuditLog(1L); + AuditLog deletedAuditLog = createTestAuditLog(1L); + deletedAuditLog.setDeletedAt(LocalDateTime.now()); + + when(auditLogRepository.findById(1L)).thenReturn(Mono.just(auditLog)); + when(auditLogRepository.save(auditLog)).thenReturn(Mono.just(deletedAuditLog)); + + StepVerifier.create(auditLogService.logicalDeleteById(1L)) + .verifyComplete(); + } + + @Test + @DisplayName("批量逻辑删除审计日志") + void logicalDeleteByIds_shouldDeleteMultipleAuditLogs() { + AuditLog auditLog1 = createTestAuditLog(1L); + AuditLog auditLog2 = createTestAuditLog(2L); + + when(auditLogRepository.findById(1L)).thenReturn(Mono.just(auditLog1)); + when(auditLogRepository.findById(2L)).thenReturn(Mono.just(auditLog2)); + when(auditLogRepository.save(any(AuditLog.class))).thenReturn(Mono.just(auditLog1)); + + StepVerifier.create(auditLogService.logicalDeleteByIds(List.of(1L, 2L))) + .verifyComplete(); + } + + @Test + @DisplayName("恢复逻辑删除的审计日志") + void restoreById_shouldClearDeletedAt() { + AuditLog auditLog = createTestAuditLog(1L); + auditLog.setDeletedAt(LocalDateTime.now()); + AuditLog restoredAuditLog = createTestAuditLog(1L); + restoredAuditLog.setDeletedAt(null); + + when(auditLogRepository.findById(1L)).thenReturn(Mono.just(auditLog)); + when(auditLogRepository.save(auditLog)).thenReturn(Mono.just(restoredAuditLog)); + + StepVerifier.create(auditLogService.restoreById(1L)) + .verifyComplete(); + } + + private AuditLog createTestAuditLog(Long id) { + AuditLog auditLog = new AuditLog(); + auditLog.setId(id); + auditLog.setEntityType("User"); + auditLog.setEntityId(100L); + auditLog.setOperator("admin"); + auditLog.setOperationType("CREATE"); + auditLog.setOperationTime(LocalDateTime.now()); + auditLog.setDescription("创建用户"); + auditLog.setIpAddress("192.168.1.1"); + auditLog.setUserAgent("Mozilla/5.0"); + return auditLog; + } +} \ No newline at end of file diff --git a/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/config/ExceptionLogConfigTest.java b/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/config/ExceptionLogConfigTest.java deleted file mode 100644 index da4dcb2..0000000 --- a/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/config/ExceptionLogConfigTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package cn.novalon.manage.sys.config; - -import cn.novalon.manage.common.handler.ExceptionLogService; -import cn.novalon.manage.sys.handler.ExceptionLogServiceImpl; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.assertj.core.api.Assertions.assertThat; - -@ExtendWith(MockitoExtension.class) -class ExceptionLogConfigTest { - - @Mock - private ExceptionLogServiceImpl exceptionLogServiceImpl; - - private ExceptionLogConfig exceptionLogConfig; - - @BeforeEach - void setUp() { - exceptionLogConfig = new ExceptionLogConfig(); - } - - @Test - void testExceptionLogService() { - ExceptionLogService exceptionLogService = exceptionLogConfig.exceptionLogService(exceptionLogServiceImpl); - - assertThat(exceptionLogService).isNotNull(); - assertThat(exceptionLogService).isSameAs(exceptionLogServiceImpl); - } - - @Test - void testExceptionLogService_DifferentInstance() { - ExceptionLogService exceptionLogService1 = exceptionLogConfig.exceptionLogService(exceptionLogServiceImpl); - ExceptionLogService exceptionLogService2 = exceptionLogConfig.exceptionLogService(exceptionLogServiceImpl); - - assertThat(exceptionLogService1).isNotNull(); - assertThat(exceptionLogService2).isNotNull(); - assertThat(exceptionLogService1).isSameAs(exceptionLogServiceImpl); - assertThat(exceptionLogService2).isSameAs(exceptionLogServiceImpl); - } -} \ No newline at end of file diff --git a/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/handler/ExceptionLogServiceImplTest.java b/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/handler/ExceptionLogServiceImplTest.java deleted file mode 100644 index 98c9eda..0000000 --- a/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/handler/ExceptionLogServiceImplTest.java +++ /dev/null @@ -1,120 +0,0 @@ -package cn.novalon.manage.sys.handler; - -import cn.novalon.manage.sys.core.domain.SysExceptionLog; -import cn.novalon.manage.sys.core.service.ISysExceptionLogService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import java.time.LocalDateTime; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class ExceptionLogServiceImplTest { - - @Mock - private ISysExceptionLogService exceptionLogService; - - private ExceptionLogServiceImpl exceptionLogServiceImpl; - - @BeforeEach - void setUp() { - exceptionLogServiceImpl = new ExceptionLogServiceImpl(exceptionLogService); - } - - @Test - void testLogException() { - SysExceptionLog savedLog = new SysExceptionLog(); - savedLog.setId(1L); - savedLog.setTitle("测试异常"); - savedLog.setExceptionName("TestException"); - savedLog.setExceptionMsg("测试异常消息"); - savedLog.setMethodName("testMethod"); - savedLog.setIp("127.0.0.1"); - savedLog.setExceptionStack("测试堆栈信息"); - savedLog.setCreateTime(LocalDateTime.now()); - - when(exceptionLogService.save(any(SysExceptionLog.class))).thenReturn(Mono.just(savedLog)); - - StepVerifier.create(exceptionLogServiceImpl.logException( - "测试异常", - "TestException", - "测试异常消息", - "testMethod", - "127.0.0.1", - "测试堆栈信息" - )) - .verifyComplete(); - - verify(exceptionLogService).save(any(SysExceptionLog.class)); - } - - @Test - void testLogException_WithEmptyFields() { - SysExceptionLog savedLog = new SysExceptionLog(); - savedLog.setId(1L); - - when(exceptionLogService.save(any(SysExceptionLog.class))).thenReturn(Mono.just(savedLog)); - - StepVerifier.create(exceptionLogServiceImpl.logException( - "", - "", - "", - "", - "", - "" - )) - .verifyComplete(); - - verify(exceptionLogService).save(any(SysExceptionLog.class)); - } - - @Test - void testLogException_WithNullFields() { - SysExceptionLog savedLog = new SysExceptionLog(); - savedLog.setId(1L); - - when(exceptionLogService.save(any(SysExceptionLog.class))).thenReturn(Mono.just(savedLog)); - - StepVerifier.create(exceptionLogServiceImpl.logException( - null, - null, - null, - null, - null, - null - )) - .verifyComplete(); - - verify(exceptionLogService).save(any(SysExceptionLog.class)); - } - - @Test - void testLogException_WithLongStackTrace() { - String longStackTrace = "a".repeat(10000); - - SysExceptionLog savedLog = new SysExceptionLog(); - savedLog.setId(1L); - - when(exceptionLogService.save(any(SysExceptionLog.class))).thenReturn(Mono.just(savedLog)); - - StepVerifier.create(exceptionLogServiceImpl.logException( - "测试异常", - "TestException", - "测试异常消息", - "testMethod", - "127.0.0.1", - longStackTrace - )) - .verifyComplete(); - - verify(exceptionLogService).save(any(SysExceptionLog.class)); - } -}