feat: extend operation log service and repository with pagination support

This commit is contained in:
张翔
2026-03-18 22:34:43 +08:00
parent 157aee2ffc
commit 8a0cd64829
81 changed files with 8842 additions and 509 deletions
+11 -16
View File
@@ -1,27 +1,22 @@
FROM maven:3.9-eclipse-temurin-21 AS builder
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
COPY manage-sys/pom.xml manage-sys/
COPY manage-sys/src manage-sys/src
COPY manage-sys/spotbugs-exclude.xml manage-sys/
COPY manage-common/pom.xml manage-common/
COPY manage-common/src manage-common/src
COPY manage-db/pom.xml manage-db/
COPY manage-db/src manage-db/src
COPY manage-audit/pom.xml manage-audit/
COPY manage-gateway/pom.xml manage-gateway/
COPY manage-app/pom.xml manage-app/
COPY mvnw .
COPY mvnw.cmd .
COPY .mvn .mvn
COPY src ./src
RUN mvn clean install -DskipTests -Ddependency-check.skip=true
RUN chmod +x mvnw
RUN ./mvnw clean package -DskipTests
FROM eclipse-temurin:21-jre-alpine
FROM openjdk:17-slim
WORKDIR /app
COPY --from=builder /app/manage-sys/target/*.jar app.jar
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
EXPOSE 8084
ENTRYPOINT ["java", "-jar", "app.jar"]
ENTRYPOINT ["java", "-jar", "app.jar"]
@@ -3,16 +3,12 @@ package cn.novalon.manage.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
/**
* 管理系统应用启动类
*
* @author 张翔
* @date 2026-03-14
*/
@SpringBootApplication
@ConfigurationPropertiesScan(basePackages = "cn.novalon.manage")
@ComponentScan(basePackages = "cn.novalon.manage")
@EnableR2dbcRepositories(basePackages = {"cn.novalon.manage.db.dao"})
public class ManageApplication {
@@ -1,4 +1,4 @@
package cn.novalon.manage.sys.config;
package cn.novalon.manage.app.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -1,4 +1,4 @@
package cn.novalon.manage.sys.config;
package cn.novalon.manage.app.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
@@ -1,4 +1,4 @@
package cn.novalon.manage.sys.config;
package cn.novalon.manage.app.config;
import cn.novalon.manage.sys.handler.auth.SysAuthHandler;
import cn.novalon.manage.sys.handler.config.SysConfigHandler;
@@ -1,4 +1,4 @@
package cn.novalon.manage.sys.config;
package cn.novalon.manage.app.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.ServerCodecConfigurer;
@@ -0,0 +1,5 @@
cn.novalon.manage.app.config.OpenApiConfig
cn.novalon.manage.app.config.WebFluxConfig
cn.novalon.manage.app.config.SystemRouter
cn.novalon.manage.app.config.MultipartConfig
cn.novalon.manage.app.config.RateLimitConfig
@@ -1,6 +1,6 @@
spring:
r2dbc:
url: r2dbc:postgresql://localhost:5432/novalon_manage
url: r2dbc:postgresql://localhost:55432/manage_system
username: postgres
password: postgres
flyway:
@@ -0,0 +1,2 @@
cn.novalon.manage.common.config.CacheConfig
cn.novalon.manage.common.config.JwtProperties
@@ -1,5 +1,7 @@
package cn.novalon.manage.db.repository;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.sys.core.domain.OperationLog;
import cn.novalon.manage.sys.core.repository.IOperationLogRepository;
import cn.novalon.manage.db.converter.OperationLogConverter;
@@ -10,6 +12,8 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* 操作日志仓储实现类
@@ -48,7 +52,7 @@ public class OperationLogRepository implements IOperationLogRepository {
@Override
public Flux<OperationLog> findAll() {
return operationLogDao.findAll()
return operationLogDao.findByDeletedAtIsNull()
.map(operationLogConverter::toDomain);
}
@@ -58,6 +62,88 @@ public class OperationLogRepository implements IOperationLogRepository {
.map(operationLogConverter::toDomain);
}
@Override
public Mono<PageResponse<OperationLog>> findOperationLogsByPage(PageRequest pageRequest) {
Flux<OperationLog> allLogs = operationLogDao.findByDeletedAtIsNull()
.map(operationLogConverter::toDomain);
if (pageRequest.getKeyword() != null && !pageRequest.getKeyword().isEmpty()) {
String keyword = pageRequest.getKeyword().toLowerCase();
allLogs = allLogs.filter(log ->
(log.getUsername() != null && log.getUsername().toLowerCase().contains(keyword)) ||
(log.getOperation() != null && log.getOperation().toLowerCase().contains(keyword)) ||
(log.getIp() != null && log.getIp().toLowerCase().contains(keyword))
);
}
return allLogs
.collectList()
.flatMap(list -> {
List<OperationLog> sortedList = new ArrayList<>(list);
if (pageRequest.getSort() != null && !pageRequest.getSort().isEmpty()) {
sortedList.sort((a, b) -> {
int comparison = 0;
if ("username".equals(pageRequest.getSort())) {
comparison = compareStrings(a.getUsername(), b.getUsername());
} else if ("operation".equals(pageRequest.getSort())) {
comparison = compareStrings(a.getOperation(), b.getOperation());
} else if ("duration".equals(pageRequest.getSort())) {
comparison = compareLongs(a.getDuration(), b.getDuration());
} else if ("status".equals(pageRequest.getSort())) {
comparison = compareStrings(a.getStatus(), b.getStatus());
} else {
comparison = compareLocalDateTimes(a.getCreatedAt(), b.getCreatedAt());
}
return "desc".equalsIgnoreCase(pageRequest.getOrder()) ? -comparison : comparison;
});
}
return Mono.just(sortedList);
})
.zipWith(operationLogDao.countByDeletedAtIsNull())
.map(tuple -> {
List<OperationLog> all = tuple.getT1();
long totalCount = tuple.getT2();
int totalPages = (int) Math.ceil((double) totalCount / pageRequest.getSize());
int fromIndex = pageRequest.getPage() * pageRequest.getSize();
int toIndex = Math.min(fromIndex + pageRequest.getSize(), all.size());
List<OperationLog> pageData = fromIndex < all.size()
? all.subList(fromIndex, toIndex)
: List.of();
return new PageResponse<OperationLog>(
pageData,
totalPages,
totalCount,
pageRequest.getPage(),
pageRequest.getSize());
});
}
private int compareStrings(String a, String b) {
if (a == null && b == null) return 0;
if (a == null) return -1;
if (b == null) return 1;
return a.compareTo(b);
}
private int compareLongs(Long a, Long b) {
if (a == null && b == null) return 0;
if (a == null) return -1;
if (b == null) return 1;
return a.compareTo(b);
}
private int compareLocalDateTimes(LocalDateTime a, LocalDateTime b) {
if (a == null && b == null) return 0;
if (a == null) return -1;
if (b == null) return 1;
return a.compareTo(b);
}
@Override
public Mono<Long> count() {
return operationLogDao.countByDeletedAtIsNull();
@@ -0,0 +1 @@
cn.novalon.manage.db.config.RepositoryScanConfig
@@ -0,0 +1,126 @@
-- Novalon管理系统E2E测试数据初始化脚本
-- 版本: V3
-- 描述: 为E2E测试准备测试数据
-- 清理测试数据(保留管理员)
DELETE FROM sys_user_message WHERE user_id > 1;
DELETE FROM users WHERE id > 1;
DELETE FROM sys_notice WHERE id > 0;
DELETE FROM sys_file WHERE id > 0;
DELETE FROM sys_exception_log WHERE id > 0;
DELETE FROM sys_login_log WHERE id > 0;
DELETE FROM sys_dict_data WHERE dict_type NOT IN ('user_status');
DELETE FROM sys_dict_type WHERE dict_type NOT IN ('user_status');
DELETE FROM sys_config WHERE id > 0;
DELETE FROM menus WHERE id > 0;
DELETE FROM roles WHERE id > 1;
-- 插入测试角色
INSERT INTO roles (role_name, role_key, role_sort, status, create_by, update_by)
VALUES
('普通用户', 'user', 2, 1, 'system', 'system'),
('测试角色', 'test_role', 3, 1, 'system', 'system'),
('受限角色', 'limited_role', 4, 1, 'system', 'system');
-- 插入测试用户
INSERT INTO users (username, password, email, phone, role_id, status, create_by, update_by)
VALUES
('testuser', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'test@example.com', '13800138001', 2, 1, 'system', 'system'),
('limiteduser', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'limited@example.com', '13800138002', 4, 1, 'system', 'system'),
('normaluser', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'normal@example.com', '13800138003', 2, 1, 'system', 'system');
-- 插入测试菜单
INSERT INTO menus (menu_name, parent_id, order_num, menu_type, perms, component, status, create_by, update_by)
VALUES
('系统管理', 0, 1, 'M', '', '', 1, 'system', 'system'),
('用户管理', 1, 1, 'C', 'system:user:list', 'system/user/index', 1, 'system', 'system'),
('角色管理', 1, 2, 'C', 'system:role:list', 'system/role/index', 1, 'system', 'system'),
('菜单管理', 1, 3, 'C', 'system:menu:list', 'system/menu/index', 1, 'system', 'system'),
('系统配置', 1, 4, 'C', 'system:config:list', 'system/config/index', 1, 'system', 'system'),
('监控中心', 0, 2, 'M', '', '', 1, 'system', 'system'),
('在线用户', 6, 1, 'C', 'monitor:online:list', 'monitor/online/index', 1, 'system', 'system'),
('登录日志', 6, 2, 'C', 'monitor:loginlog:list', 'monitor/loginlog/index', 1, 'system', 'system');
-- 插入测试字典类型
INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, update_by)
VALUES
('菜单状态', 'menu_status', '0', '菜单状态列表', 'system', 'system'),
('角色状态', 'role_status', '0', '角色状态列表', 'system', 'system'),
('系统开关', 'sys_normal_disable', '0', '系统开关列表', 'system', 'system'),
('任务状态', 'job_status', '0', '任务状态列表', 'system', 'system'),
('任务分组', 'job_group', '0', '任务分组列表', 'system', 'system');
-- 插入测试字典数据
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, update_by)
VALUES
-- 菜单状态
(1, '正常', '0', 'menu_status', '', 'primary', 'N', '0', 'system', 'system'),
(2, '停用', '1', 'menu_status', '', 'danger', 'N', '0', 'system', 'system'),
-- 角色状态
(1, '正常', '0', 'role_status', '', 'primary', 'N', '0', 'system', 'system'),
(2, '停用', '1', 'role_status', '', 'danger', 'N', '0', 'system', 'system'),
-- 系统开关
(1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'system', 'system'),
(2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'system', 'system'),
-- 任务状态
(1, '正常', '0', 'job_status', '', 'primary', 'Y', '0', 'system', 'system'),
(2, '暂停', '1', 'job_status', '', 'danger', 'N', '0', 'system', 'system'),
-- 任务分组
(1, '默认', 'DEFAULT', 'job_group', '', '', 'Y', '0', 'system', 'system'),
(2, '系统', 'SYSTEM', 'job_group', '', '', 'N', '0', 'system', 'system');
-- 插入测试系统配置
INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, update_by)
VALUES
('用户管理-用户初始密码', 'sys.user.initPassword', '123456', 'Y', 'system', 'system'),
('主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'system', 'system'),
('用户自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'system', 'system'),
('用户自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'system', 'system'),
('账号自助-密码验证码', 'sys.account.pwdCaptchaEnabled', 'true', 'Y', 'system', 'system');
-- 插入测试系统公告
INSERT INTO sys_notice (notice_title, notice_type, notice_content, status, create_by, update_by)
VALUES
('系统维护通知', '1', '系统将于今晚22:00-23:00进行维护,请提前做好准备。', '0', 'admin', 'admin'),
('新功能上线通知', '2', '系统新增了用户管理功能,欢迎大家使用!', '0', 'admin', 'admin'),
('安全提醒', '1', '请定期修改密码,确保账户安全。', '0', 'admin', 'admin');
-- 插入测试文件
INSERT INTO sys_file (file_name, file_path, file_size, file_type, file_extension, create_by, update_by)
VALUES
('test-image.jpg', '/uploads/images/test-image.jpg', 102400, 'image/jpeg', 'jpg', 'system', 'system'),
('test-document.pdf', '/uploads/documents/test-document.pdf', 204800, 'application/pdf', 'pdf', 'system', 'system'),
('test-data.xlsx', '/uploads/data/test-data.xlsx', 51200, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlsx', 'system', 'system');
-- 插入测试登录日志
INSERT INTO sys_login_log (username, ip, location, browser, os, status, message, login_time)
VALUES
('admin', '127.0.0.1', '内网IP', 'Chrome', 'Windows 10', '0', '登录成功', NOW() - INTERVAL '1 day'),
('admin', '127.0.0.1', '内网IP', 'Chrome', 'Windows 10', '0', '登录成功', NOW() - INTERVAL '2 hours'),
('testuser', '127.0.0.1', '内网IP', 'Firefox', 'Mac OS', '0', '登录成功', NOW() - INTERVAL '3 hours'),
('testuser', '127.0.0.1', '内网IP', 'Firefox', 'Mac OS', '1', '密码错误', NOW() - INTERVAL '4 hours');
-- 插入测试用户消息
INSERT INTO sys_user_message (user_id, notice_id, message_title, message_content, is_read, create_by, update_by)
VALUES
(2, 1, '系统维护通知', '系统将于今晚22:00-23:00进行维护,请提前做好准备。', '0', 'admin', 'admin'),
(2, 2, '新功能上线通知', '系统新增了用户管理功能,欢迎大家使用!', '0', 'admin', 'admin'),
(3, 3, '安全提醒', '请定期修改密码,确保账户安全。', '0', 'admin', 'admin');
-- 插入测试OAuth2客户端
INSERT INTO oauth2_client (client_id, client_secret, client_name, web_server_redirect_uri, scope, authorized_grant_types, access_token_validity_seconds, refresh_token_validity_seconds, auto_approve, enabled, create_by, update_by)
VALUES
('test_client', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', '测试客户端', 'http://localhost:3001/callback', 'read,write', 'password,refresh_token', 3600, 7200, 'true', 'true', 'system', 'system');
-- 更新序列值
SELECT setval('users_id_seq', (SELECT MAX(id) FROM users));
SELECT setval('roles_id_seq', (SELECT MAX(id) FROM roles));
SELECT setval('menus_id_seq', (SELECT MAX(id) FROM menus));
SELECT setval('sys_dict_type_id_seq', (SELECT MAX(id) FROM sys_dict_type));
SELECT setval('sys_dict_data_id_seq', (SELECT MAX(id) FROM sys_dict_data));
SELECT setval('sys_config_id_seq', (SELECT MAX(id) FROM sys_config));
SELECT setval('sys_notice_id_seq', (SELECT MAX(id) FROM sys_notice));
SELECT setval('sys_file_id_seq', (SELECT MAX(id) FROM sys_file));
SELECT setval('sys_login_log_id_seq', (SELECT MAX(id) FROM sys_login_log));
SELECT setval('sys_user_message_id_seq', (SELECT MAX(id) FROM sys_user_message));
SELECT setval('oauth2_client_id_seq', (SELECT MAX(id) FROM oauth2_client));
@@ -0,0 +1,10 @@
-- 更新管理员密码为已知密码
-- BCrypt哈希值对应明文密码: admin123
UPDATE users
SET password = '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi'
WHERE username = 'admin';
-- 确保管理员用户状态为启用
UPDATE users
SET status = 1
WHERE username = 'admin';
+25
View File
@@ -35,6 +35,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -69,6 +74,26 @@
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,90 @@
package cn.novalon.manage.file.core.service.impl;
import cn.novalon.manage.file.core.domain.SysFile;
import cn.novalon.manage.file.core.repository.ISysFileRepository;
import cn.novalon.manage.file.core.service.ISysFileService;
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.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SysFileServiceTest {
@Mock
private ISysFileRepository fileRepository;
private ISysFileService fileService;
private SysFile testFile;
@BeforeEach
void setUp() {
fileService = new SysFileServiceImpl(fileRepository);
testFile = new SysFile();
testFile.setId(1L);
testFile.setFileName("test.txt");
testFile.setFilePath("/app/uploads/test.txt");
testFile.setFileType("text/plain");
testFile.setFileSize("1024");
testFile.setCreateBy("testuser");
testFile.setStorageType("LOCAL");
}
@Test
void testGetAllFiles_Success() {
when(fileRepository.findByDeletedAtIsNullOrderByCreatedAtDesc()).thenReturn(Flux.just(testFile));
Flux<SysFile> result = fileService.getAllFiles();
StepVerifier.create(result)
.expectNext(testFile)
.verifyComplete();
verify(fileRepository).findByDeletedAtIsNullOrderByCreatedAtDesc();
}
@Test
void testGetFileById_Success() {
when(fileRepository.findById(1L)).thenReturn(Mono.just(testFile));
Mono<SysFile> result = fileService.getFileById(1L);
StepVerifier.create(result)
.expectNext(testFile)
.verifyComplete();
verify(fileRepository).findById(1L);
}
@Test
void testGetFileById_NotFound() {
when(fileRepository.findById(999L)).thenReturn(Mono.empty());
Mono<SysFile> result = fileService.getFileById(999L);
StepVerifier.create(result)
.verifyComplete();
verify(fileRepository).findById(999L);
}
@Test
void testDeleteFile_NotFound() {
when(fileRepository.findById(999L)).thenReturn(Mono.empty());
Mono<Void> result = fileService.deleteFile(999L);
StepVerifier.create(result)
.verifyComplete();
verify(fileRepository).findById(999L);
verify(fileRepository, never()).deleteByIdAndDeletedAtIsNull(any());
}
}
@@ -0,0 +1,260 @@
package cn.novalon.manage.file.handler;
import cn.novalon.manage.file.core.domain.SysFile;
import cn.novalon.manage.file.core.service.ISysFileService;
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 org.springframework.http.HttpStatus;
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SysFileHandlerTest {
@Mock
private ISysFileService fileService;
private SysFileHandler fileHandler;
private SysFile testFile;
@BeforeEach
void setUp() {
fileHandler = new SysFileHandler(fileService);
testFile = new SysFile();
testFile.setId(1L);
testFile.setFileName("test.txt");
testFile.setFilePath("/app/uploads/test.txt");
testFile.setFileType("text/plain");
testFile.setFileSize("1024");
testFile.setCreateBy("testuser");
}
@Test
void testGetAllFiles_Success() {
when(fileService.getAllFiles()).thenReturn(Flux.just(testFile));
ServerRequest request = MockServerRequest.builder().build();
Mono<ServerResponse> response = fileHandler.getAllFiles(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(fileService).getAllFiles();
}
@Test
void testGetFileById_Success() {
when(fileService.getFileById(1L)).thenReturn(Mono.just(testFile));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "1")
.build();
Mono<ServerResponse> response = fileHandler.getFileById(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(fileService).getFileById(1L);
}
@Test
void testGetFileById_NotFound() {
when(fileService.getFileById(999L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "999")
.build();
Mono<ServerResponse> response = fileHandler.getFileById(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(fileService).getFileById(999L);
}
@Test
void testDeleteFile_Success() {
when(fileService.deleteFile(1L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "1")
.build();
Mono<ServerResponse> response = fileHandler.deleteFile(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(fileService).deleteFile(1L);
}
@Test
void testDeleteFile_NotFound() {
when(fileService.deleteFile(999L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "999")
.build();
Mono<ServerResponse> response = fileHandler.deleteFile(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(fileService).deleteFile(999L);
}
@Test
void testDownloadFile_Success() {
when(fileService.getFileById(1L)).thenReturn(Mono.just(testFile));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "1")
.build();
Mono<ServerResponse> response = fileHandler.downloadFile(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(fileService).getFileById(1L);
}
@Test
void testDownloadFile_NotFound() {
when(fileService.getFileById(999L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "999")
.build();
Mono<ServerResponse> response = fileHandler.downloadFile(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(fileService).getFileById(999L);
}
@Test
void testDownloadFileByName_Success() {
when(fileService.getAllFiles()).thenReturn(Flux.just(testFile));
ServerRequest request = MockServerRequest.builder()
.pathVariable("fileName", "test.txt")
.build();
Mono<ServerResponse> response = fileHandler.downloadFileByName(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(fileService).getAllFiles();
}
@Test
void testDownloadFileByName_NotFound() {
when(fileService.getAllFiles()).thenReturn(Flux.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("fileName", "nonexistent.txt")
.build();
Mono<ServerResponse> response = fileHandler.downloadFileByName(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(fileService).getAllFiles();
}
@Test
void testPreviewFile_Success() {
when(fileService.getFileById(1L)).thenReturn(Mono.just(testFile));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "1")
.build();
Mono<ServerResponse> response = fileHandler.previewFile(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(fileService).getFileById(1L);
}
@Test
void testPreviewFile_NotFound() {
when(fileService.getFileById(999L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "999")
.build();
Mono<ServerResponse> response = fileHandler.previewFile(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(fileService).getFileById(999L);
}
@Test
void testPreviewFileByName_Success() {
when(fileService.getAllFiles()).thenReturn(Flux.just(testFile));
ServerRequest request = MockServerRequest.builder()
.pathVariable("fileName", "test.txt")
.build();
Mono<ServerResponse> response = fileHandler.previewFileByName(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(fileService).getAllFiles();
}
@Test
void testPreviewFileByName_NotFound() {
when(fileService.getAllFiles()).thenReturn(Flux.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("fileName", "nonexistent.txt")
.build();
Mono<ServerResponse> response = fileHandler.previewFileByName(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(fileService).getAllFiles();
}
}
+25
View File
@@ -65,6 +65,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -76,6 +81,26 @@
<mainClass>cn.novalon.manage.gateway.GatewayApplication</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1 @@
cn.novalon.manage.gateway.config.RateLimitConfig
@@ -0,0 +1,309 @@
package cn.novalon.manage.gateway.filter;
import cn.novalon.manage.gateway.util.JwtUtil;
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 org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class GatewayJwtAuthenticationFilterTest {
@Mock
private JwtUtil jwtUtil;
@Mock
private GatewayFilterChain chain;
private JwtAuthenticationFilter filter;
private ServerWebExchange exchange;
@BeforeEach
void setUp() {
filter = new JwtAuthenticationFilter(jwtUtil);
}
@Test
void testPublicPath_AllowAccess() {
MockServerHttpRequest request = MockServerHttpRequest.get("/api/auth/login").build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
verify(jwtUtil, never()).validateToken(anyString());
}
@Test
void testPublicPath_Register() {
MockServerHttpRequest request = MockServerHttpRequest.post("/api/auth/register").build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
verify(jwtUtil, never()).validateToken(anyString());
}
@Test
void testPublicPath_ActuatorHealth() {
MockServerHttpRequest request = MockServerHttpRequest.get("/actuator/health").build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
verify(jwtUtil, never()).validateToken(anyString());
}
@Test
void testPublicPath_ActuatorInfo() {
MockServerHttpRequest request = MockServerHttpRequest.get("/actuator/info").build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
verify(jwtUtil, never()).validateToken(anyString());
}
@Test
void testProtectedPath_NoAuthHeader() {
MockServerHttpRequest request = MockServerHttpRequest.get("/api/users").build();
exchange = MockServerWebExchange.from(request);
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
assert exchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;
verify(chain, never()).filter(any(ServerWebExchange.class));
verify(jwtUtil, never()).validateToken(anyString());
}
@Test
void testProtectedPath_InvalidAuthHeader() {
MockServerHttpRequest request = MockServerHttpRequest.get("/api/users")
.header(HttpHeaders.AUTHORIZATION, "InvalidToken")
.build();
exchange = MockServerWebExchange.from(request);
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
assert exchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;
verify(chain, never()).filter(any(ServerWebExchange.class));
verify(jwtUtil, never()).validateToken(anyString());
}
@Test
void testProtectedPath_WithBearerPrefix() {
String validToken = "valid.jwt.token";
MockServerHttpRequest request = MockServerHttpRequest.get("/api/users")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)
.build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
when(jwtUtil.validateToken(validToken)).thenReturn(true);
when(jwtUtil.isTokenExpired(validToken)).thenReturn(false);
when(jwtUtil.getUsernameFromToken(validToken)).thenReturn("testuser");
when(jwtUtil.getUserIdFromToken(validToken)).thenReturn(1L);
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(jwtUtil).validateToken(validToken);
verify(jwtUtil).isTokenExpired(validToken);
verify(jwtUtil).getUsernameFromToken(validToken);
verify(jwtUtil).getUserIdFromToken(validToken);
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testProtectedPath_InvalidToken() {
String invalidToken = "invalid.jwt.token";
MockServerHttpRequest request = MockServerHttpRequest.get("/api/users")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + invalidToken)
.build();
exchange = MockServerWebExchange.from(request);
when(jwtUtil.validateToken(invalidToken)).thenReturn(false);
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
assert exchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;
verify(jwtUtil).validateToken(invalidToken);
verify(jwtUtil, never()).isTokenExpired(anyString());
verify(chain, never()).filter(any(ServerWebExchange.class));
}
@Test
void testProtectedPath_ExpiredToken() {
String expiredToken = "expired.jwt.token";
MockServerHttpRequest request = MockServerHttpRequest.get("/api/users")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + expiredToken)
.build();
exchange = MockServerWebExchange.from(request);
when(jwtUtil.validateToken(expiredToken)).thenReturn(true);
when(jwtUtil.isTokenExpired(expiredToken)).thenReturn(true);
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
assert exchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;
verify(jwtUtil).validateToken(expiredToken);
verify(jwtUtil).isTokenExpired(expiredToken);
verify(chain, never()).filter(any(ServerWebExchange.class));
}
@Test
void testProtectedPath_ValidToken() {
String validToken = "valid.jwt.token";
MockServerHttpRequest request = MockServerHttpRequest.get("/api/users/1")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)
.build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
when(jwtUtil.validateToken(validToken)).thenReturn(true);
when(jwtUtil.isTokenExpired(validToken)).thenReturn(false);
when(jwtUtil.getUsernameFromToken(validToken)).thenReturn("testuser");
when(jwtUtil.getUserIdFromToken(validToken)).thenReturn(1L);
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(jwtUtil).validateToken(validToken);
verify(jwtUtil).isTokenExpired(validToken);
verify(jwtUtil).getUsernameFromToken(validToken);
verify(jwtUtil).getUserIdFromToken(validToken);
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testHeadersAdded_ValidToken() {
String validToken = "valid.jwt.token";
MockServerHttpRequest request = MockServerHttpRequest.get("/api/users")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)
.build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
when(jwtUtil.validateToken(validToken)).thenReturn(true);
when(jwtUtil.isTokenExpired(validToken)).thenReturn(false);
when(jwtUtil.getUsernameFromToken(validToken)).thenReturn("testuser");
when(jwtUtil.getUserIdFromToken(validToken)).thenReturn(1L);
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
ServerHttpRequest modifiedRequest = exchange.getRequest();
assert modifiedRequest.getHeaders().getFirst("X-User-Id").equals("1");
assert modifiedRequest.getHeaders().getFirst("X-Username").equals("testuser");
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testMixedPath_AuthPath() {
MockServerHttpRequest request = MockServerHttpRequest.get("/api/auth/logout").build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
verify(jwtUtil, never()).validateToken(anyString());
}
@Test
void testActuatorPath_Metrics() {
String validToken = "valid.jwt.token";
MockServerHttpRequest request = MockServerHttpRequest.get("/actuator/metrics")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)
.build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
when(jwtUtil.validateToken(validToken)).thenReturn(true);
when(jwtUtil.isTokenExpired(validToken)).thenReturn(false);
when(jwtUtil.getUsernameFromToken(validToken)).thenReturn("testuser");
when(jwtUtil.getUserIdFromToken(validToken)).thenReturn(1L);
Mono<Void> result = filter.apply(new JwtAuthenticationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(jwtUtil).validateToken(validToken);
verify(jwtUtil).isTokenExpired(validToken);
verify(jwtUtil).getUsernameFromToken(validToken);
verify(jwtUtil).getUserIdFromToken(validToken);
verify(chain).filter(any(ServerWebExchange.class));
}
}
@@ -0,0 +1,255 @@
package cn.novalon.manage.gateway.filter;
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 org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class RbacAuthorizationFilterTest {
@Mock
private GatewayFilterChain chain;
private RbacAuthorizationFilter filter;
private ServerWebExchange exchange;
@BeforeEach
void setUp() {
filter = new RbacAuthorizationFilter();
}
@Test
void testPublicPath_AllowAccess() {
MockServerHttpRequest request = MockServerHttpRequest.get("/api/auth/login").build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testPublicPath_Register() {
MockServerHttpRequest request = MockServerHttpRequest.post("/api/auth/register").build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testPublicPath_ActuatorHealth() {
MockServerHttpRequest request = MockServerHttpRequest.get("/actuator/health").build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testPublicPath_ActuatorInfo() {
MockServerHttpRequest request = MockServerHttpRequest.get("/actuator/info").build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testProtectedPath_NoUserId() {
MockServerHttpRequest request = MockServerHttpRequest.get("/api/users").build();
exchange = MockServerWebExchange.from(request);
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
assert exchange.getResponse().getStatusCode() == HttpStatus.UNAUTHORIZED;
verify(chain, never()).filter(any(ServerWebExchange.class));
}
@Test
void testProtectedPath_WithUserId() {
MockServerHttpRequest request = MockServerHttpRequest.get("/api/users")
.header("X-User-Id", "1")
.build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testProtectedPath_PostMethod() {
MockServerHttpRequest request = MockServerHttpRequest.post("/api/users")
.header("X-User-Id", "1")
.build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testProtectedPath_PutMethod() {
MockServerHttpRequest request = MockServerHttpRequest.put("/api/users/1")
.header("X-User-Id", "1")
.build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testProtectedPath_DeleteMethod() {
MockServerHttpRequest request = MockServerHttpRequest.delete("/api/users/1")
.header("X-User-Id", "1")
.build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testProtectedPath_EmptyUserId() {
MockServerHttpRequest request = MockServerHttpRequest.get("/api/users")
.header("X-User-Id", "")
.build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testMixedPath_AuthPath() {
MockServerHttpRequest request = MockServerHttpRequest.get("/api/auth/logout").build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testMixedPath_UserPath() {
MockServerHttpRequest request = MockServerHttpRequest.get("/api/users/profile")
.header("X-User-Id", "1")
.build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
}
@Test
void testActuatorPath_Metrics() {
MockServerHttpRequest request = MockServerHttpRequest.get("/actuator/metrics")
.header("X-User-Id", "1")
.build();
exchange = MockServerWebExchange.from(request);
when(chain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
Mono<Void> result = filter.apply(new RbacAuthorizationFilter.Config())
.filter(exchange, chain);
StepVerifier.create(result)
.verifyComplete();
verify(chain).filter(any(ServerWebExchange.class));
}
}
+25
View File
@@ -35,6 +35,11 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -69,6 +74,26 @@
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1 @@
cn.novalon.manage.notify.config.WebSocketConfig
@@ -0,0 +1,252 @@
package cn.novalon.manage.notify.handler;
import cn.novalon.manage.notify.core.domain.SysNotice;
import cn.novalon.manage.notify.core.service.ISysNoticeService;
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 org.springframework.http.HttpStatus;
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.time.LocalDateTime;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SysNoticeHandlerTest {
@Mock
private ISysNoticeService noticeService;
private SysNoticeHandler noticeHandler;
private SysNotice testNotice;
@BeforeEach
void setUp() {
noticeHandler = new SysNoticeHandler(noticeService);
testNotice = new SysNotice();
testNotice.setId(1L);
testNotice.setNoticeTitle("系统维护通知");
testNotice.setNoticeType("SYSTEM");
testNotice.setNoticeContent("系统将于今晚进行维护");
testNotice.setStatus("PUBLISHED");
testNotice.setCreateBy("admin");
testNotice.setCreatedAt(LocalDateTime.now());
}
@Test
void testGetAllNotices() {
when(noticeService.getAllNotices()).thenReturn(Flux.just(testNotice));
ServerRequest request = MockServerRequest.builder().build();
Mono<ServerResponse> response = noticeHandler.getAllNotices(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).getAllNotices();
}
@Test
void testGetNoticeById() {
when(noticeService.getNoticeById(1L)).thenReturn(Mono.just(testNotice));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "1")
.build();
Mono<ServerResponse> response = noticeHandler.getNoticeById(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).getNoticeById(1L);
}
@Test
void testGetNoticeById_NotFound() {
when(noticeService.getNoticeById(999L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "999")
.build();
Mono<ServerResponse> response = noticeHandler.getNoticeById(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(noticeService).getNoticeById(999L);
}
@Test
void testGetNoticesByStatus() {
when(noticeService.getNoticesByStatus("PUBLISHED")).thenReturn(Flux.just(testNotice));
ServerRequest request = MockServerRequest.builder()
.pathVariable("status", "PUBLISHED")
.build();
Mono<ServerResponse> response = noticeHandler.getNoticesByStatus(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).getNoticesByStatus("PUBLISHED");
}
@Test
void testGetNoticesByStatus_Draft() {
when(noticeService.getNoticesByStatus("DRAFT")).thenReturn(Flux.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("status", "DRAFT")
.build();
Mono<ServerResponse> response = noticeHandler.getNoticesByStatus(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).getNoticesByStatus("DRAFT");
}
@Test
void testCreateNotice() {
SysNotice newNotice = new SysNotice();
newNotice.setNoticeTitle("新通知");
newNotice.setNoticeType("SYSTEM");
newNotice.setNoticeContent("测试内容");
newNotice.setStatus("DRAFT");
when(noticeService.createNotice(any(SysNotice.class))).thenReturn(Mono.just(testNotice));
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(newNotice));
Mono<ServerResponse> response = noticeHandler.createNotice(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).createNotice(any(SysNotice.class));
}
@Test
void testCreateNotice_WithAllFields() {
SysNotice newNotice = new SysNotice();
newNotice.setNoticeTitle("完整通知");
newNotice.setNoticeType("ANNOUNCEMENT");
newNotice.setNoticeContent("完整内容");
newNotice.setStatus("PUBLISHED");
newNotice.setCreateBy("admin");
when(noticeService.createNotice(any(SysNotice.class))).thenReturn(Mono.just(testNotice));
ServerRequest request = MockServerRequest.builder()
.body(Mono.just(newNotice));
Mono<ServerResponse> response = noticeHandler.createNotice(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).createNotice(any(SysNotice.class));
}
@Test
void testUpdateNotice() {
SysNotice updateNotice = new SysNotice();
updateNotice.setNoticeTitle("更新后的通知");
updateNotice.setNoticeType("SYSTEM");
updateNotice.setNoticeContent("更新后的内容");
updateNotice.setStatus("PUBLISHED");
when(noticeService.updateNotice(anyLong(), any(SysNotice.class))).thenReturn(Mono.just(testNotice));
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "1")
.body(Mono.just(updateNotice));
Mono<ServerResponse> response = noticeHandler.updateNotice(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).updateNotice(1L, updateNotice);
}
@Test
void testUpdateNotice_NotFound() {
SysNotice updateNotice = new SysNotice();
updateNotice.setNoticeTitle("更新后的通知");
when(noticeService.updateNotice(anyLong(), any(SysNotice.class))).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "999")
.body(Mono.just(updateNotice));
Mono<ServerResponse> response = noticeHandler.updateNotice(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.NOT_FOUND)
.verifyComplete();
verify(noticeService).updateNotice(999L, updateNotice);
}
@Test
void testDeleteNotice() {
when(noticeService.deleteNotice(1L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "1")
.build();
Mono<ServerResponse> response = noticeHandler.deleteNotice(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).deleteNotice(1L);
}
@Test
void testDeleteNotice_NotFound() {
when(noticeService.deleteNotice(999L)).thenReturn(Mono.empty());
ServerRequest request = MockServerRequest.builder()
.pathVariable("id", "999")
.build();
Mono<ServerResponse> response = noticeHandler.deleteNotice(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(noticeService).deleteNotice(999L);
}
}
@@ -0,0 +1,181 @@
package cn.novalon.manage.notify.websocket;
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 org.springframework.web.reactive.socket.HandshakeInfo;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.net.URI;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class SysWebSocketHandlerTest {
@Mock
private WebSocketSession session;
@Mock
private WebSocketMessage message;
@Mock
private HandshakeInfo handshakeInfo;
private SysWebSocketHandler webSocketHandler;
@BeforeEach
void setUp() {
webSocketHandler = new SysWebSocketHandler();
}
@Test
void testHandle_NewConnection() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(session.receive()).thenReturn(Flux.empty());
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
}
@Test
void testHandle_WithUserId() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=123"));
when(session.receive()).thenReturn(Flux.empty());
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
}
@Test
void testHandle_WithoutUserId() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws"));
when(session.getId()).thenReturn("test-session-id");
when(session.receive()).thenReturn(Flux.empty());
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
}
@Test
void testHandle_PongMessage() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(message.getPayloadAsText()).thenReturn("{\"type\":\"pong\"}");
when(session.receive()).thenReturn(Flux.just(message));
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
verify(message).getPayloadAsText();
}
@Test
void testHandle_SubscribeMessage() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(message.getPayloadAsText()).thenReturn("{\"type\":\"subscribe\"}");
when(session.receive()).thenReturn(Flux.just(message));
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
verify(message).getPayloadAsText();
}
@Test
void testHandle_HeartbeatMessage() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(message.getPayloadAsText()).thenReturn("{\"type\":\"heartbeat\"}");
when(session.receive()).thenReturn(Flux.just(message));
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
verify(message).getPayloadAsText();
}
@Test
void testHandle_UnknownMessageType() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(message.getPayloadAsText()).thenReturn("{\"type\":\"unknown\"}");
when(session.receive()).thenReturn(Flux.just(message));
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
verify(message).getPayloadAsText();
}
@Test
void testHandle_InvalidJson() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(message.getPayloadAsText()).thenReturn("invalid json");
when(session.receive()).thenReturn(Flux.just(message));
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyComplete();
verify(session).receive();
verify(message).getPayloadAsText();
}
@Test
void testHandle_SessionError() {
when(session.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getUri()).thenReturn(URI.create("ws://localhost/ws?userId=testuser"));
when(session.receive()).thenReturn(Flux.error(new RuntimeException("Connection error")));
Mono<Void> result = webSocketHandler.handle(session);
StepVerifier.create(result)
.verifyError();
verify(session).receive();
}
@Test
void testSendMessageToUser_SessionNotFound() {
webSocketHandler.sendMessageToUser("nonexistent", java.util.Map.of("type", "notification", "message", "test"));
verify(session, never()).send(any());
}
}
+28
View File
@@ -58,6 +58,34 @@
<artifactId>resilience4j-reactor</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -1,5 +1,7 @@
package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.sys.core.domain.OperationLog;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -24,6 +26,8 @@ public interface IOperationLogRepository {
Flux<OperationLog> findByUsername(String username);
Mono<PageResponse<OperationLog>> findOperationLogsByPage(PageRequest pageRequest);
Mono<Long> count();
Mono<Long> countByCreatedAtAfter(LocalDateTime dateTime);
@@ -1,5 +1,7 @@
package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.sys.core.domain.OperationLog;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -13,7 +15,9 @@ import reactor.core.publisher.Mono;
public interface IOperationLogService {
Mono<OperationLog> save(OperationLog log);
Flux<OperationLog> findAll();
Mono<OperationLog> findById(Long id);
Flux<OperationLog> findByUsername(String username);
Mono<PageResponse<OperationLog>> findOperationLogsByPage(PageRequest pageRequest);
Mono<Long> count();
Mono<Long> countToday();
}
@@ -1,5 +1,7 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.sys.core.domain.OperationLog;
import cn.novalon.manage.sys.core.repository.IOperationLogRepository;
import cn.novalon.manage.sys.core.service.IOperationLogService;
@@ -35,11 +37,21 @@ public class OperationLogService implements IOperationLogService {
return logRepository.findAll();
}
@Override
public Mono<OperationLog> findById(Long id) {
return logRepository.findById(id);
}
@Override
public Flux<OperationLog> findByUsername(String username) {
return logRepository.findByUsername(username);
}
@Override
public Mono<PageResponse<OperationLog>> findOperationLogsByPage(PageRequest pageRequest) {
return logRepository.findOperationLogsByPage(pageRequest);
}
@Override
public Mono<Long> count() {
return logRepository.count();
@@ -0,0 +1,79 @@
package cn.novalon.manage.sys.handler.log;
import cn.novalon.manage.sys.core.domain.OperationLog;
import cn.novalon.manage.sys.core.service.IOperationLogService;
import cn.novalon.manage.common.dto.PageRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
/**
* 操作日志处理器
*
* 文件定义:处理操作日志相关的HTTP请求
* 涉及业务:操作日志查询、分页、统计
* 算法:使用WebFlux函数式编程模型处理响应式请求
*
* @author 张翔
* @date 2026-03-18
*/
@Component
@Tag(name = "操作日志", description = "操作日志相关操作")
public class OperationLogHandler {
private final IOperationLogService logService;
public OperationLogHandler(IOperationLogService logService) {
this.logService = logService;
}
@Operation(summary = "获取所有操作日志", description = "获取系统中所有操作日志列表")
public Mono<ServerResponse> getAllOperationLogs(ServerRequest request) {
return ServerResponse.ok()
.body(logService.findAll(), OperationLog.class);
}
@Operation(summary = "根据ID获取操作日志", description = "根据操作日志ID获取详细信息")
public Mono<ServerResponse> getOperationLogById(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return logService.findById(id)
.flatMap(log -> ServerResponse.ok().bodyValue(log))
.switchIfEmpty(ServerResponse.notFound().build());
}
@Operation(summary = "分页获取操作日志", description = "根据分页参数获取操作日志列表")
public Mono<ServerResponse> getOperationLogsByPage(ServerRequest request) {
int page = Integer.parseInt(request.queryParam("page").orElse("0"));
int size = Integer.parseInt(request.queryParam("size").orElse("10"));
String sort = request.queryParam("sort").orElse("created_at");
String order = request.queryParam("order").orElse("desc");
String keyword = request.queryParam("keyword").orElse(null);
PageRequest pageRequest = new PageRequest();
pageRequest.setPage(page);
pageRequest.setSize(size);
pageRequest.setSort(sort);
pageRequest.setOrder(order);
pageRequest.setKeyword(keyword);
return logService.findOperationLogsByPage(pageRequest)
.flatMap(response -> ServerResponse.ok().bodyValue(response));
}
@Operation(summary = "获取操作日志总数", description = "获取系统中操作日志总数")
public Mono<ServerResponse> getOperationLogCount(ServerRequest request) {
return logService.count()
.flatMap(count -> ServerResponse.ok().bodyValue(count));
}
@Operation(summary = "创建操作日志", description = "手动创建操作日志")
public Mono<ServerResponse> createOperationLog(ServerRequest request) {
return request.bodyToMono(OperationLog.class)
.flatMap(logService::save)
.flatMap(log -> ServerResponse.status(HttpStatus.CREATED).bodyValue(log));
}
}
@@ -0,0 +1,2 @@
cn.novalon.manage.sys.config.SecurityConfig
cn.novalon.manage.sys.config.ExceptionLogConfig
@@ -1,68 +0,0 @@
server:
port: 8084
netty:
connection-timeout: 60s
idle-timeout: 300s
spring:
application:
name: novalon-manage-api
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 10MB
datasource:
url: jdbc:postgresql://localhost:55432/manage_system
username: postgres
password: postgres
driver-class-name: org.postgresql.Driver
r2dbc:
url: r2dbc:pool:postgresql://localhost:55432/manage_system
username: postgres
password: postgres
flyway:
enabled: true
url: jdbc:postgresql://localhost:55432/manage_system
user: postgres
password: postgres
locations: classpath:db/migration
baseline-on-migrate: true
jwt:
secret: novalon-manage-secret-key-change-in-production
expiration: 86400000
websocket:
enabled: true
heartbeat-interval: 30s
idle-timeout: 300s
max-text-message-buffer-size: 8192
max-binary-message-buffer-size: 8192
resilience4j:
ratelimiter:
instances:
apiRateLimiter:
limit-for-period: 100
limit-refresh-period: 1s
timeout-duration: 0
logging:
level:
cn.novalon.manage: DEBUG
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
access: read-only
prometheus:
metrics:
export:
enabled: true
@@ -1,209 +0,0 @@
-- Novalon管理系统数据库初始化脚本
-- 版本: V1
-- 描述: 创建所有核心表
-- 用户表
CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100),
phone VARCHAR(20),
role_id BIGINT,
status INTEGER DEFAULT 1,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 角色表
CREATE TABLE IF NOT EXISTS roles (
id BIGSERIAL PRIMARY KEY,
role_name VARCHAR(100) NOT NULL,
role_key VARCHAR(100) NOT NULL UNIQUE,
role_sort INTEGER DEFAULT 0,
status INTEGER DEFAULT 1,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 菜单表
CREATE TABLE IF NOT EXISTS menus (
id BIGSERIAL PRIMARY KEY,
menu_name VARCHAR(50) NOT NULL,
parent_id BIGINT DEFAULT 0,
order_num INTEGER DEFAULT 0,
menu_type VARCHAR(1) DEFAULT 'C',
perms VARCHAR(100),
component VARCHAR(200),
status INTEGER DEFAULT 1,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 字典类型表
CREATE TABLE IF NOT EXISTS sys_dict_type (
id BIGSERIAL PRIMARY KEY,
dict_name VARCHAR(100) NOT NULL,
dict_type VARCHAR(100) NOT NULL UNIQUE,
status VARCHAR(1) DEFAULT '0',
remark VARCHAR(500),
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 字典数据表
CREATE TABLE IF NOT EXISTS sys_dict_data (
id BIGSERIAL PRIMARY KEY,
dict_sort INTEGER DEFAULT 0,
dict_label VARCHAR(100) NOT NULL,
dict_value VARCHAR(100) NOT NULL,
dict_type VARCHAR(100) NOT NULL,
css_class VARCHAR(100),
list_class VARCHAR(100),
is_default VARCHAR(1) DEFAULT 'N',
status VARCHAR(1) DEFAULT '0',
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 系统配置表
CREATE TABLE IF NOT EXISTS sys_config (
id BIGSERIAL PRIMARY KEY,
config_name VARCHAR(100) NOT NULL,
config_key VARCHAR(100) NOT NULL UNIQUE,
config_value VARCHAR(500) NOT NULL,
config_type VARCHAR(1) DEFAULT 'N',
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 登录日志表
CREATE TABLE IF NOT EXISTS sys_login_log (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50),
ip VARCHAR(50),
location VARCHAR(255),
browser VARCHAR(50),
os VARCHAR(50),
status VARCHAR(1),
message VARCHAR(255),
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 异常日志表
CREATE TABLE IF NOT EXISTS sys_exception_log (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50),
ip VARCHAR(50),
location VARCHAR(255),
browser VARCHAR(50),
os VARCHAR(50),
status VARCHAR(1),
message VARCHAR(255),
exception_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 系统公告表
CREATE TABLE IF NOT EXISTS sys_notice (
id BIGSERIAL PRIMARY KEY,
notice_title VARCHAR(50) NOT NULL,
notice_type VARCHAR(1) NOT NULL,
notice_content TEXT,
status VARCHAR(1) DEFAULT '0',
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 用户消息表
CREATE TABLE IF NOT EXISTS sys_user_message (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
notice_id BIGINT,
message_title VARCHAR(255),
message_content TEXT,
is_read VARCHAR(1) DEFAULT '0',
read_time TIMESTAMP,
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 文件管理表
CREATE TABLE IF NOT EXISTS sys_file (
id BIGSERIAL PRIMARY KEY,
file_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size BIGINT,
file_type VARCHAR(100),
file_extension VARCHAR(10),
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- OAuth2客户端表
CREATE TABLE IF NOT EXISTS oauth2_client (
id BIGSERIAL PRIMARY KEY,
client_id VARCHAR(100) NOT NULL UNIQUE,
client_secret VARCHAR(255) NOT NULL,
client_name VARCHAR(100),
web_server_redirect_uri VARCHAR(500),
scope VARCHAR(500),
authorized_grant_types VARCHAR(500),
access_token_validity_seconds INTEGER,
refresh_token_validity_seconds INTEGER,
auto_approve VARCHAR(1) DEFAULT 'false',
enabled VARCHAR(1) DEFAULT 'true',
create_by VARCHAR(50),
update_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 插入初始管理员用户
INSERT INTO users (username, password, email, role_id, status, create_by, update_by)
VALUES ('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'admin@novalon.com', 1, 1, 'system', 'system')
ON CONFLICT (username) DO NOTHING;
-- 插入初始角色
INSERT INTO roles (role_name, role_key, role_sort, status, create_by, update_by)
VALUES ('超级管理员', 'admin', 1, 1, 'system', 'system')
ON CONFLICT (role_key) DO NOTHING;
-- 插入初始字典类型
INSERT INTO sys_dict_type (dict_name, dict_type, status, remark, create_by, update_by)
VALUES ('用户状态', 'user_status', '0', '用户状态列表', 'system', 'system')
ON CONFLICT (dict_type) DO NOTHING;
-- 插入初始字典数据
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, status, create_by, update_by)
VALUES
(1, '正常', '1', 'user_status', '0', 'system', 'system'),
(2, '停用', '0', 'user_status', '0', 'system', 'system')
ON CONFLICT DO NOTHING;
@@ -1,18 +0,0 @@
-- 创建字典表
CREATE TABLE IF NOT EXISTS sys_dictionary (
id BIGSERIAL PRIMARY KEY,
type VARCHAR(100) NOT NULL,
code VARCHAR(100) NOT NULL,
name VARCHAR(100) NOT NULL,
value VARCHAR(500),
remark VARCHAR(500),
sort INTEGER DEFAULT 0,
create_by VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
-- 创建索引
CREATE INDEX IF NOT EXISTS idx_sys_dictionary_type ON sys_dictionary(type);
CREATE INDEX IF NOT EXISTS idx_sys_dictionary_type_code ON sys_dictionary(type, code);
@@ -0,0 +1,44 @@
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);
}
}
@@ -0,0 +1,78 @@
package cn.novalon.manage.sys.config;
import cn.novalon.manage.sys.security.JwtAuthenticationFilter;
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 org.springframework.http.HttpMethod;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MockitoExtension.class)
class SecurityConfigTest {
@Mock
private JwtAuthenticationFilter jwtAuthenticationFilter;
private SecurityConfig securityConfig;
@BeforeEach
void setUp() {
securityConfig = new SecurityConfig(jwtAuthenticationFilter);
}
@Test
void testPasswordEncoder() {
PasswordEncoder passwordEncoder = securityConfig.passwordEncoder();
assertThat(passwordEncoder).isNotNull();
assertThat(passwordEncoder).isInstanceOf(org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.class);
String rawPassword = "testPassword123";
String encodedPassword = passwordEncoder.encode(rawPassword);
assertThat(encodedPassword).isNotNull();
assertThat(encodedPassword).isNotEqualTo(rawPassword);
assertThat(passwordEncoder.matches(rawPassword, encodedPassword)).isTrue();
assertThat(passwordEncoder.matches("wrongPassword", encodedPassword)).isFalse();
}
@Test
void testPasswordEncoder_SamePasswordDifferentHashes() {
PasswordEncoder passwordEncoder = securityConfig.passwordEncoder();
String rawPassword = "testPassword123";
String hash1 = passwordEncoder.encode(rawPassword);
String hash2 = passwordEncoder.encode(rawPassword);
assertThat(hash1).isNotEqualTo(hash2);
assertThat(passwordEncoder.matches(rawPassword, hash1)).isTrue();
assertThat(passwordEncoder.matches(rawPassword, hash2)).isTrue();
}
@Test
void testPasswordEncoder_EmptyPassword() {
PasswordEncoder passwordEncoder = securityConfig.passwordEncoder();
String encodedPassword = passwordEncoder.encode("");
assertThat(encodedPassword).isNotNull();
assertThat(passwordEncoder.matches("", encodedPassword)).isTrue();
}
@Test
void testPasswordEncoder_Strength() {
PasswordEncoder passwordEncoder = securityConfig.passwordEncoder();
String rawPassword = "testPassword123";
String encodedPassword = passwordEncoder.encode(rawPassword);
assertThat(encodedPassword.length()).isGreaterThan(50);
assertThat(encodedPassword.startsWith("$2a$")).isTrue();
}
}
@@ -0,0 +1,211 @@
package cn.novalon.manage.sys.core.query;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class SysRoleQueryTest {
@Test
void testGettersAndSetters() {
SysRoleQuery query = new SysRoleQuery();
query.setRoleName("admin");
query.setRoleKey("admin");
query.setStatus(1);
query.setKeyword("admin");
assertEquals("admin", query.getRoleName());
assertEquals("admin", query.getRoleKey());
assertEquals(1, query.getStatus());
assertEquals("admin", query.getKeyword());
}
@Test
void testSetNullValues() {
SysRoleQuery query = new SysRoleQuery();
query.setRoleName(null);
query.setRoleKey(null);
query.setStatus(null);
query.setKeyword(null);
assertNull(query.getRoleName());
assertNull(query.getRoleKey());
assertNull(query.getStatus());
assertNull(query.getKeyword());
}
@Test
void testSetEmptyStringValues() {
SysRoleQuery query = new SysRoleQuery();
query.setRoleName("");
query.setRoleKey("");
query.setKeyword("");
assertEquals("", query.getRoleName());
assertEquals("", query.getRoleKey());
assertEquals("", query.getKeyword());
}
@Test
void testSetMultipleValues() {
SysRoleQuery query = new SysRoleQuery();
query.setRoleName("user");
query.setRoleKey("user");
query.setStatus(0);
query.setKeyword("user");
assertEquals("user", query.getRoleName());
assertEquals("user", query.getRoleKey());
assertEquals(0, query.getStatus());
assertEquals("user", query.getKeyword());
}
@Test
void testSetLongRoleName() {
SysRoleQuery query = new SysRoleQuery();
String longRoleName = "a".repeat(100);
query.setRoleName(longRoleName);
assertEquals(longRoleName, query.getRoleName());
}
@Test
void testSetLongRoleKey() {
SysRoleQuery query = new SysRoleQuery();
String longRoleKey = "a".repeat(100);
query.setRoleKey(longRoleKey);
assertEquals(longRoleKey, query.getRoleKey());
}
@Test
void testSetLongKeyword() {
SysRoleQuery query = new SysRoleQuery();
String longKeyword = "a".repeat(100);
query.setKeyword(longKeyword);
assertEquals(longKeyword, query.getKeyword());
}
@Test
void testSetNegativeStatus() {
SysRoleQuery query = new SysRoleQuery();
query.setStatus(-1);
assertEquals(-1, query.getStatus());
}
@Test
void testSetZeroStatus() {
SysRoleQuery query = new SysRoleQuery();
query.setStatus(0);
assertEquals(0, query.getStatus());
}
@Test
void testSetPositiveStatus() {
SysRoleQuery query = new SysRoleQuery();
query.setStatus(1);
assertEquals(1, query.getStatus());
}
@Test
void testSetSpecialCharactersInRoleName() {
SysRoleQuery query = new SysRoleQuery();
query.setRoleName("role@#$%");
assertEquals("role@#$%", query.getRoleName());
}
@Test
void testSetSpecialCharactersInRoleKey() {
SysRoleQuery query = new SysRoleQuery();
query.setRoleKey("role@#$%");
assertEquals("role@#$%", query.getRoleKey());
}
@Test
void testSetSpecialCharactersInKeyword() {
SysRoleQuery query = new SysRoleQuery();
query.setKeyword("keyword@#$%");
assertEquals("keyword@#$%", query.getKeyword());
}
@Test
void testSetWhitespaceInValues() {
SysRoleQuery query = new SysRoleQuery();
query.setRoleName(" test role ");
query.setRoleKey(" test key ");
query.setKeyword(" test keyword ");
assertEquals(" test role ", query.getRoleName());
assertEquals(" test key ", query.getRoleKey());
assertEquals(" test keyword ", query.getKeyword());
}
@Test
void testSetUnicodeCharacters() {
SysRoleQuery query = new SysRoleQuery();
query.setRoleName("角色名");
query.setRoleKey("角色键");
query.setKeyword("关键词");
assertEquals("角色名", query.getRoleName());
assertEquals("角色键", query.getRoleKey());
assertEquals("关键词", query.getKeyword());
}
@Test
void testSetNumbersInRoleName() {
SysRoleQuery query = new SysRoleQuery();
query.setRoleName("role123");
assertEquals("role123", query.getRoleName());
}
@Test
void testSetNumbersInRoleKey() {
SysRoleQuery query = new SysRoleQuery();
query.setRoleKey("role123");
assertEquals("role123", query.getRoleKey());
}
@Test
void testSetUnderscoreInValues() {
SysRoleQuery query = new SysRoleQuery();
query.setRoleName("test_role");
query.setRoleKey("test_role");
query.setKeyword("test_keyword");
assertEquals("test_role", query.getRoleName());
assertEquals("test_role", query.getRoleKey());
assertEquals("test_keyword", query.getKeyword());
}
@Test
void testSetHyphenInValues() {
SysRoleQuery query = new SysRoleQuery();
query.setRoleName("test-role");
query.setRoleKey("test-role");
query.setKeyword("test-keyword");
assertEquals("test-role", query.getRoleName());
assertEquals("test-role", query.getRoleKey());
assertEquals("test-keyword", query.getKeyword());
}
@Test
void testSetDotInValues() {
SysRoleQuery query = new SysRoleQuery();
query.setRoleName("test.role");
query.setRoleKey("test.role");
query.setKeyword("test.keyword");
assertEquals("test.role", query.getRoleName());
assertEquals("test.role", query.getRoleKey());
assertEquals("test.keyword", query.getKeyword());
}
}
@@ -0,0 +1,185 @@
package cn.novalon.manage.sys.core.query;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class SysUserQueryTest {
@Test
void testGettersAndSetters() {
SysUserQuery query = new SysUserQuery();
query.setUsername("testuser");
query.setEmail("test@example.com");
query.setRoleId(1L);
query.setStatus(1);
query.setKeyword("test");
assertEquals("testuser", query.getUsername());
assertEquals("test@example.com", query.getEmail());
assertEquals(1L, query.getRoleId());
assertEquals(1, query.getStatus());
assertEquals("test", query.getKeyword());
}
@Test
void testSetNullValues() {
SysUserQuery query = new SysUserQuery();
query.setUsername(null);
query.setEmail(null);
query.setRoleId(null);
query.setStatus(null);
query.setKeyword(null);
assertNull(query.getUsername());
assertNull(query.getEmail());
assertNull(query.getRoleId());
assertNull(query.getStatus());
assertNull(query.getKeyword());
}
@Test
void testSetEmptyStringValues() {
SysUserQuery query = new SysUserQuery();
query.setUsername("");
query.setEmail("");
query.setKeyword("");
assertEquals("", query.getUsername());
assertEquals("", query.getEmail());
assertEquals("", query.getKeyword());
}
@Test
void testSetMultipleValues() {
SysUserQuery query = new SysUserQuery();
query.setUsername("user1");
query.setEmail("user1@example.com");
query.setRoleId(2L);
query.setStatus(0);
query.setKeyword("user1");
assertEquals("user1", query.getUsername());
assertEquals("user1@example.com", query.getEmail());
assertEquals(2L, query.getRoleId());
assertEquals(0, query.getStatus());
assertEquals("user1", query.getKeyword());
}
@Test
void testSetLongUsername() {
SysUserQuery query = new SysUserQuery();
String longUsername = "a".repeat(100);
query.setUsername(longUsername);
assertEquals(longUsername, query.getUsername());
}
@Test
void testSetLongEmail() {
SysUserQuery query = new SysUserQuery();
String longEmail = "a".repeat(100) + "@example.com";
query.setEmail(longEmail);
assertEquals(longEmail, query.getEmail());
}
@Test
void testSetLongKeyword() {
SysUserQuery query = new SysUserQuery();
String longKeyword = "a".repeat(100);
query.setKeyword(longKeyword);
assertEquals(longKeyword, query.getKeyword());
}
@Test
void testSetNegativeRoleId() {
SysUserQuery query = new SysUserQuery();
query.setRoleId(-1L);
assertEquals(-1L, query.getRoleId());
}
@Test
void testSetZeroRoleId() {
SysUserQuery query = new SysUserQuery();
query.setRoleId(0L);
assertEquals(0L, query.getRoleId());
}
@Test
void testSetPositiveRoleId() {
SysUserQuery query = new SysUserQuery();
query.setRoleId(999L);
assertEquals(999L, query.getRoleId());
}
@Test
void testSetNegativeStatus() {
SysUserQuery query = new SysUserQuery();
query.setStatus(-1);
assertEquals(-1, query.getStatus());
}
@Test
void testSetZeroStatus() {
SysUserQuery query = new SysUserQuery();
query.setStatus(0);
assertEquals(0, query.getStatus());
}
@Test
void testSetPositiveStatus() {
SysUserQuery query = new SysUserQuery();
query.setStatus(1);
assertEquals(1, query.getStatus());
}
@Test
void testSetSpecialCharactersInUsername() {
SysUserQuery query = new SysUserQuery();
query.setUsername("user@#$%");
assertEquals("user@#$%", query.getUsername());
}
@Test
void testSetSpecialCharactersInEmail() {
SysUserQuery query = new SysUserQuery();
query.setEmail("user+test@example.com");
assertEquals("user+test@example.com", query.getEmail());
}
@Test
void testSetSpecialCharactersInKeyword() {
SysUserQuery query = new SysUserQuery();
query.setKeyword("keyword@#$%");
assertEquals("keyword@#$%", query.getKeyword());
}
@Test
void testSetWhitespaceInValues() {
SysUserQuery query = new SysUserQuery();
query.setUsername(" test user ");
query.setEmail(" test@example.com ");
query.setKeyword(" test keyword ");
assertEquals(" test user ", query.getUsername());
assertEquals(" test@example.com ", query.getEmail());
assertEquals(" test keyword ", query.getKeyword());
}
@Test
void testSetUnicodeCharacters() {
SysUserQuery query = new SysUserQuery();
query.setUsername("用户名");
query.setEmail("用户@example.com");
query.setKeyword("关键词");
assertEquals("用户名", query.getUsername());
assertEquals("用户@example.com", query.getEmail());
assertEquals("关键词", query.getKeyword());
}
}
@@ -185,6 +185,82 @@ class SysMenuServiceTest {
verify(menuRepository).findById(999L);
}
@Test
void testUpdateMenuWithCommand_WithPartialFields() {
SysMenu existingMenu = new SysMenu();
existingMenu.setId(1L);
existingMenu.setMenuName("系统管理");
existingMenu.setParentId(0L);
existingMenu.setOrderNum(1);
existingMenu.setMenuType("M");
existingMenu.setPerms("system");
existingMenu.setComponent("system");
existingMenu.setStatus(1);
SysMenu updatedMenu = new SysMenu();
updatedMenu.setId(1L);
updatedMenu.setMenuName("系统管理");
updatedMenu.setParentId(0L);
updatedMenu.setOrderNum(1);
updatedMenu.setMenuType("M");
updatedMenu.setPerms("system");
updatedMenu.setComponent("system");
updatedMenu.setStatus(1);
updatedMenu.setUpdatedAt(LocalDateTime.now());
when(menuRepository.findById(1L)).thenReturn(Mono.just(existingMenu));
when(menuRepository.save(any(SysMenu.class))).thenReturn(Mono.just(updatedMenu));
UpdateMenuCommand command = new UpdateMenuCommand(
1L, null, null, null, null, null, null, null
);
StepVerifier.create(menuService.updateMenu(command))
.expectNextMatches(menu -> menu.getUpdatedAt() != null)
.verifyComplete();
verify(menuRepository).findById(1L);
verify(menuRepository).save(any(SysMenu.class));
}
@Test
void testUpdateMenuWithCommand_WithAllFields() {
SysMenu existingMenu = new SysMenu();
existingMenu.setId(1L);
existingMenu.setMenuName("系统管理");
existingMenu.setParentId(0L);
existingMenu.setOrderNum(1);
existingMenu.setMenuType("M");
existingMenu.setPerms("system");
existingMenu.setComponent("system");
existingMenu.setStatus(1);
SysMenu updatedMenu = new SysMenu();
updatedMenu.setId(1L);
updatedMenu.setMenuName("系统管理(更新)");
updatedMenu.setParentId(2L);
updatedMenu.setOrderNum(2);
updatedMenu.setMenuType("C");
updatedMenu.setPerms("system:manage_updated");
updatedMenu.setComponent("system_updated");
updatedMenu.setStatus(0);
updatedMenu.setUpdatedAt(LocalDateTime.now());
when(menuRepository.findById(1L)).thenReturn(Mono.just(existingMenu));
when(menuRepository.save(any(SysMenu.class))).thenReturn(Mono.just(updatedMenu));
UpdateMenuCommand command = new UpdateMenuCommand(
1L, 2L, "系统管理(更新)", "C", 2, "system_updated", "system:manage_updated", 0
);
StepVerifier.create(menuService.updateMenu(command))
.expectNextMatches(menu -> menu.getUpdatedAt() != null)
.verifyComplete();
verify(menuRepository).findById(1L);
verify(menuRepository).save(any(SysMenu.class));
}
@Test
void testDeleteMenu() {
when(menuRepository.deleteById(1L)).thenReturn(Mono.empty());
@@ -220,4 +296,188 @@ class SysMenuServiceTest {
menu.getChildren().size() == 1)
.verifyComplete();
}
@Test
void testFindById_WhenMenuNotFound() {
when(menuRepository.findById(999L)).thenReturn(Mono.empty());
Mono<SysMenu> result = menuService.findById(999L);
StepVerifier.create(result)
.expectNextCount(0)
.verifyComplete();
verify(menuRepository).findById(999L);
}
@Test
void testFindAll_WhenNoMenusExist() {
when(menuRepository.findAll()).thenReturn(Flux.empty());
Flux<SysMenu> result = menuService.findAll();
StepVerifier.create(result)
.expectNextCount(0)
.verifyComplete();
verify(menuRepository).findAll();
}
@Test
void testFindByParentId_WhenNoChildrenExist() {
when(menuRepository.findByParentId(999L)).thenReturn(Flux.empty());
Flux<SysMenu> result = menuService.findByParentId(999L);
StepVerifier.create(result)
.expectNextCount(0)
.verifyComplete();
verify(menuRepository).findByParentId(999L);
}
@Test
void testCreateMenu_WithDefaultStatus() {
SysMenu newMenu = new SysMenu();
newMenu.setMenuName("新菜单");
newMenu.setParentId(0L);
newMenu.setOrderNum(1);
newMenu.setMenuType("M");
newMenu.setPerms("new:menu");
newMenu.setComponent("new");
newMenu.setStatus(null);
SysMenu savedMenu = new SysMenu();
savedMenu.setId(1L);
savedMenu.setMenuName("新菜单");
savedMenu.setParentId(0L);
savedMenu.setOrderNum(1);
savedMenu.setMenuType("M");
savedMenu.setPerms("new:menu");
savedMenu.setComponent("new");
savedMenu.setStatus(1);
savedMenu.setCreatedAt(LocalDateTime.now());
when(menuRepository.save(any(SysMenu.class))).thenReturn(Mono.just(savedMenu));
Mono<SysMenu> result = menuService.createMenu(newMenu);
StepVerifier.create(result)
.expectNextMatches(menu ->
menu.getStatus().equals(1) &&
menu.getCreatedAt() != null)
.verifyComplete();
verify(menuRepository).save(any(SysMenu.class));
}
@Test
void testCreateMenuWithCommand_WithDefaultStatus() {
CreateMenuCommand command = new CreateMenuCommand(
0L, "日志管理", "M", 3, "log", "log:manage", null
);
SysMenu createdMenu = new SysMenu();
createdMenu.setId(3L);
createdMenu.setMenuName("日志管理");
createdMenu.setParentId(0L);
createdMenu.setOrderNum(3);
createdMenu.setMenuType("M");
createdMenu.setPerms("log:manage");
createdMenu.setComponent("log");
createdMenu.setStatus(1);
createdMenu.setCreatedAt(LocalDateTime.now());
when(menuRepository.save(any(SysMenu.class))).thenReturn(Mono.just(createdMenu));
Mono<SysMenu> result = menuService.createMenu(command);
StepVerifier.create(result)
.expectNextMatches(menu ->
menu.getMenuName().equals("日志管理") &&
menu.getStatus().equals(1) &&
menu.getCreatedAt() != null)
.verifyComplete();
verify(menuRepository).save(any(SysMenu.class));
}
@Test
void testBuildMenuTree_WithEmptyTree() {
when(menuRepository.findAll()).thenReturn(Flux.empty());
Flux<SysMenu> result = menuService.buildMenuTree(menuService.findAll());
StepVerifier.create(result)
.expectNextCount(0)
.verifyComplete();
verify(menuRepository).findAll();
}
@Test
void testBuildMenuTree_WithMultiLevelTree() {
SysMenu rootMenu = new SysMenu();
rootMenu.setId(1L);
rootMenu.setMenuName("系统管理");
rootMenu.setParentId(0L);
SysMenu level1Menu = new SysMenu();
level1Menu.setId(2L);
level1Menu.setMenuName("用户管理");
level1Menu.setParentId(1L);
SysMenu level2Menu = new SysMenu();
level2Menu.setId(3L);
level2Menu.setMenuName("用户列表");
level2Menu.setParentId(2L);
when(menuRepository.findAll()).thenReturn(Flux.just(rootMenu, level1Menu, level2Menu));
Flux<SysMenu> result = menuService.buildMenuTree(menuService.findAll());
StepVerifier.create(result)
.expectNextMatches(menu ->
menu.getId().equals(1L) &&
menu.getChildren() != null &&
menu.getChildren().size() == 1 &&
menu.getChildren().get(0).getChildren() != null &&
menu.getChildren().get(0).getChildren().size() == 1)
.verifyComplete();
verify(menuRepository).findAll();
}
@Test
void testBuildMenuTree_WithMultipleRootMenus() {
SysMenu root1 = new SysMenu();
root1.setId(1L);
root1.setMenuName("系统管理");
root1.setParentId(0L);
SysMenu root2 = new SysMenu();
root2.setId(2L);
root2.setMenuName("监控管理");
root2.setParentId(0L);
SysMenu child1 = new SysMenu();
child1.setId(3L);
child1.setMenuName("用户管理");
child1.setParentId(1L);
SysMenu child2 = new SysMenu();
child2.setId(4L);
child2.setMenuName("性能监控");
child2.setParentId(2L);
when(menuRepository.findAll()).thenReturn(Flux.just(root1, root2, child1, child2));
Flux<SysMenu> result = menuService.buildMenuTree(menuService.findAll());
StepVerifier.create(result)
.expectNextCount(2)
.verifyComplete();
verify(menuRepository).findAll();
}
}
@@ -127,6 +127,118 @@ class SysRoleServiceTest {
verify(roleRepository).save(any(SysRole.class));
}
@Test
void testFindRolesByPage_WithKeyword() {
PageRequest pageRequest = new PageRequest();
pageRequest.setPage(0);
pageRequest.setSize(10);
pageRequest.setKeyword("admin");
PageResponse<SysRole> pageResponse = new PageResponse<>();
pageResponse.setContent(List.of(testRole));
pageResponse.setTotalElements(1L);
when(roleRepository.findByQueryWithPagination(any(cn.novalon.manage.sys.core.query.SysRoleQuery.class), eq(pageRequest)))
.thenReturn(Mono.just(pageResponse));
StepVerifier.create(roleService.findRolesByPage(pageRequest))
.expectNextMatches(response -> response.getTotalElements() == 1L)
.verifyComplete();
verify(roleRepository).findByQueryWithPagination(any(cn.novalon.manage.sys.core.query.SysRoleQuery.class), eq(pageRequest));
}
@Test
void testFindRolesByPage_WithoutKeyword() {
PageRequest pageRequest = new PageRequest();
pageRequest.setPage(0);
pageRequest.setSize(10);
PageResponse<SysRole> pageResponse = new PageResponse<>();
pageResponse.setContent(List.of(testRole));
pageResponse.setTotalElements(1L);
when(roleRepository.findByQueryWithPagination(any(cn.novalon.manage.sys.core.query.SysRoleQuery.class), eq(pageRequest)))
.thenReturn(Mono.just(pageResponse));
StepVerifier.create(roleService.findRolesByPage(pageRequest))
.expectNextMatches(response -> response.getTotalElements() == 1L)
.verifyComplete();
verify(roleRepository).findByQueryWithPagination(any(cn.novalon.manage.sys.core.query.SysRoleQuery.class), eq(pageRequest));
}
@Test
void testFindRolesByPage_WithEmptyKeyword() {
PageRequest pageRequest = new PageRequest();
pageRequest.setPage(0);
pageRequest.setSize(10);
pageRequest.setKeyword("");
PageResponse<SysRole> pageResponse = new PageResponse<>();
pageResponse.setContent(List.of(testRole));
pageResponse.setTotalElements(1L);
when(roleRepository.findByQueryWithPagination(any(cn.novalon.manage.sys.core.query.SysRoleQuery.class), eq(pageRequest)))
.thenReturn(Mono.just(pageResponse));
StepVerifier.create(roleService.findRolesByPage(pageRequest))
.expectNextMatches(response -> response.getTotalElements() == 1L)
.verifyComplete();
verify(roleRepository).findByQueryWithPagination(any(cn.novalon.manage.sys.core.query.SysRoleQuery.class), eq(pageRequest));
}
@Test
void testUpdateRoleWithCommand_WithAllFields() {
SysRole existingRole = new SysRole();
existingRole.setId(1L);
existingRole.setRoleName("oldrole");
existingRole.setRoleKey("oldkey");
existingRole.setRoleSort(1);
existingRole.setStatus(StatusConstants.ENABLED);
when(roleRepository.findById(1L)).thenReturn(Mono.just(existingRole));
when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(testRole));
cn.novalon.manage.sys.core.command.UpdateRoleCommand command =
new cn.novalon.manage.sys.core.command.UpdateRoleCommand(
1L, "newrole", "newkey", 2, StatusConstants.DISABLED
);
StepVerifier.create(roleService.updateRole(command))
.expectNextMatches(role -> role.getUpdatedAt() != null)
.verifyComplete();
verify(roleRepository).findById(1L);
verify(roleRepository).save(any(SysRole.class));
}
@Test
void testUpdateRoleWithCommand_WithPartialFields() {
SysRole existingRole = new SysRole();
existingRole.setId(1L);
existingRole.setRoleName("oldrole");
existingRole.setRoleKey("oldkey");
existingRole.setRoleSort(1);
existingRole.setStatus(StatusConstants.ENABLED);
when(roleRepository.findById(1L)).thenReturn(Mono.just(existingRole));
when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(testRole));
cn.novalon.manage.sys.core.command.UpdateRoleCommand command =
new cn.novalon.manage.sys.core.command.UpdateRoleCommand(
1L, null, null, null, null
);
StepVerifier.create(roleService.updateRole(command))
.expectNextMatches(role -> role.getUpdatedAt() != null)
.verifyComplete();
verify(roleRepository).findById(1L);
verify(roleRepository).save(any(SysRole.class));
}
@Test
void testUpdateRole() {
SysRole updateRole = new SysRole();
@@ -218,4 +330,203 @@ class SysRoleServiceTest {
verify(roleRepository).findByIdIncludingDeleted(1L);
verify(roleRepository).updateRole(any(SysRole.class));
}
@Test
void testCreateRole_WithNullStatus() {
SysRole newRole = new SysRole();
newRole.setRoleName("user");
newRole.setRoleKey("user");
newRole.setStatus(null);
when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(testRole));
StepVerifier.create(roleService.createRole(newRole))
.expectNextMatches(role ->
role.getStatus().equals(StatusConstants.ENABLED) &&
role.getCreatedAt() != null)
.verifyComplete();
verify(roleRepository).save(any(SysRole.class));
}
@Test
void testCreateRole_WithExistingStatus() {
SysRole newRole = new SysRole();
newRole.setRoleName("user");
newRole.setRoleKey("user");
newRole.setStatus(StatusConstants.DISABLED);
SysRole savedRole = new SysRole();
savedRole.setId(1L);
savedRole.setRoleName("user");
savedRole.setRoleKey("user");
savedRole.setStatus(StatusConstants.DISABLED);
savedRole.setCreatedAt(LocalDateTime.now());
when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(savedRole));
StepVerifier.create(roleService.createRole(newRole))
.expectNextMatches(role ->
role.getStatus().equals(StatusConstants.DISABLED) &&
role.getCreatedAt() != null)
.verifyComplete();
verify(roleRepository).save(any(SysRole.class));
}
@Test
void testCreateRoleWithCommand_WithAllFields() {
cn.novalon.manage.sys.core.command.CreateRoleCommand command =
new cn.novalon.manage.sys.core.command.CreateRoleCommand(
"manager", "manager", 2, StatusConstants.ENABLED
);
SysRole savedRole = new SysRole();
savedRole.setId(1L);
savedRole.setRoleName("manager");
savedRole.setRoleKey("manager");
savedRole.setRoleSort(2);
savedRole.setStatus(StatusConstants.ENABLED);
savedRole.setCreatedAt(LocalDateTime.now());
when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(savedRole));
StepVerifier.create(roleService.createRole(command))
.expectNextMatches(role ->
role.getRoleName().equals("manager") &&
role.getRoleKey().equals("manager") &&
role.getRoleSort() == 2 &&
role.getStatus().equals(StatusConstants.ENABLED) &&
role.getCreatedAt() != null)
.verifyComplete();
verify(roleRepository).save(any(SysRole.class));
}
@Test
void testCreateRoleWithCommand_WithDefaultStatus() {
cn.novalon.manage.sys.core.command.CreateRoleCommand command =
new cn.novalon.manage.sys.core.command.CreateRoleCommand(
"viewer", "viewer", 3, null
);
SysRole savedRole = new SysRole();
savedRole.setId(1L);
savedRole.setRoleName("viewer");
savedRole.setRoleKey("viewer");
savedRole.setRoleSort(3);
savedRole.setStatus(StatusConstants.ENABLED);
savedRole.setCreatedAt(LocalDateTime.now());
when(roleRepository.save(any(SysRole.class))).thenReturn(Mono.just(savedRole));
StepVerifier.create(roleService.createRole(command))
.expectNextMatches(role ->
role.getRoleName().equals("viewer") &&
role.getRoleKey().equals("viewer") &&
role.getRoleSort() == 3 &&
role.getStatus().equals(StatusConstants.ENABLED) &&
role.getCreatedAt() != null)
.verifyComplete();
verify(roleRepository).save(any(SysRole.class));
}
@Test
void testUpdateRoleWithCommand_WhenRoleNotFound() {
cn.novalon.manage.sys.core.command.UpdateRoleCommand command =
new cn.novalon.manage.sys.core.command.UpdateRoleCommand(
999L, "newrole", "newkey", 2, StatusConstants.DISABLED
);
when(roleRepository.findById(999L)).thenReturn(Mono.empty());
StepVerifier.create(roleService.updateRole(command))
.expectError(RuntimeException.class)
.verify();
verify(roleRepository).findById(999L);
verify(roleRepository, never()).save(any(SysRole.class));
}
@Test
void testDeleteRole_WhenRoleNotFound() {
when(roleRepository.findById(1L)).thenReturn(Mono.empty());
StepVerifier.create(roleService.deleteRole(1L))
.expectComplete()
.verify();
verify(roleRepository).findById(1L);
verify(userService, never()).updateRoleIdToNullByRoleId(1L);
verify(roleRepository, never()).deleteById(1L);
}
@Test
void testLogicalDeleteRole_WhenRoleNotFound() {
when(roleRepository.findByIdIncludingDeleted(1L)).thenReturn(Mono.empty());
StepVerifier.create(roleService.logicalDeleteRole(1L))
.expectNextCount(0)
.verifyComplete();
verify(roleRepository).findByIdIncludingDeleted(1L);
verify(roleRepository, never()).updateRole(any(SysRole.class));
}
@Test
void testRestoreRole_WhenRoleNotFound() {
when(roleRepository.findByIdIncludingDeleted(1L)).thenReturn(Mono.empty());
StepVerifier.create(roleService.restoreRole(1L))
.expectNextCount(0)
.verifyComplete();
verify(roleRepository).findByIdIncludingDeleted(1L);
verify(roleRepository, never()).updateRole(any(SysRole.class));
}
@Test
void testFindById_WhenRoleNotFound() {
when(roleRepository.findById(999L)).thenReturn(Mono.empty());
StepVerifier.create(roleService.findById(999L))
.expectNextCount(0)
.verifyComplete();
verify(roleRepository).findById(999L);
}
@Test
void testFindByRoleName_WhenRoleNotFound() {
when(roleRepository.findByRoleName("nonexistent")).thenReturn(Mono.empty());
StepVerifier.create(roleService.findByRoleName("nonexistent"))
.expectNextCount(0)
.verifyComplete();
verify(roleRepository).findByRoleName("nonexistent");
}
@Test
void testFindAll_WhenNoRolesExist() {
when(roleRepository.findAll()).thenReturn(Flux.empty());
StepVerifier.create(roleService.findAll())
.expectNextCount(0)
.verifyComplete();
verify(roleRepository).findAll();
}
@Test
void testCount_WhenNoRolesExist() {
when(roleRepository.count()).thenReturn(Mono.just(0L));
StepVerifier.create(roleService.count())
.expectNext(0L)
.verifyComplete();
verify(roleRepository).count();
}
}
@@ -1,6 +1,7 @@
package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.util.StatusConstants;
import cn.novalon.manage.sys.core.command.UpdateUserCommand;
import cn.novalon.manage.sys.core.domain.SysUser;
import cn.novalon.manage.sys.core.query.SysUserQuery;
import cn.novalon.manage.sys.core.repository.ISysUserRepository;
@@ -188,22 +189,6 @@ class SysUserServiceTest {
verify(passwordEncoder).encode("raw_password");
}
@Test
void testUpdateUser() {
SysUser updateUser = new SysUser();
updateUser.setId(1L);
updateUser.setUsername("updated_user");
when(userRepository.save(any(SysUser.class))).thenReturn(Mono.just(testUser));
StepVerifier.create(userService.updateUser(updateUser))
.expectNextMatches(user -> user.getUpdatedAt() != null)
.verifyComplete();
ArgumentCaptor<SysUser> userCaptor = ArgumentCaptor.forClass(SysUser.class);
verify(userRepository).save(userCaptor.capture());
}
@Test
void testDeleteUser() {
when(userRepository.findById(1L)).thenReturn(Mono.just(testUser));
@@ -339,4 +324,180 @@ class SysUserServiceTest {
verify(userRepository).restoreByIds(ids);
}
@Test
void testCreateUser_WithNullStatus() {
SysUser newUser = new SysUser();
newUser.setUsername("newuser");
newUser.setPassword("raw_password");
newUser.setEmail("new@example.com");
newUser.setStatus(null);
when(passwordEncoder.encode("raw_password")).thenReturn("encoded_password");
when(userRepository.save(any(SysUser.class))).thenReturn(Mono.just(testUser));
StepVerifier.create(userService.createUser(newUser))
.expectNextMatches(user ->
user.getPassword().equals("encoded_password") &&
user.getStatus().equals(StatusConstants.ENABLED) &&
user.getCreatedAt() != null)
.verifyComplete();
verify(passwordEncoder).encode("raw_password");
verify(userRepository).save(any(SysUser.class));
}
@Test
void testCreateUser_WithExistingStatus() {
SysUser newUser = new SysUser();
newUser.setUsername("newuser");
newUser.setPassword("raw_password");
newUser.setEmail("new@example.com");
newUser.setStatus(StatusConstants.DISABLED);
SysUser savedUser = new SysUser();
savedUser.setId(1L);
savedUser.setUsername("newuser");
savedUser.setPassword("encoded_password");
savedUser.setEmail("new@example.com");
savedUser.setStatus(StatusConstants.DISABLED);
savedUser.setCreatedAt(LocalDateTime.now());
when(passwordEncoder.encode("raw_password")).thenReturn("encoded_password");
when(userRepository.save(any(SysUser.class))).thenReturn(Mono.just(savedUser));
StepVerifier.create(userService.createUser(newUser))
.expectNextMatches(user ->
user.getPassword().equals("encoded_password") &&
user.getStatus().equals(StatusConstants.DISABLED) &&
user.getCreatedAt() != null)
.verifyComplete();
verify(passwordEncoder).encode("raw_password");
verify(userRepository).save(any(SysUser.class));
}
@Test
void testDeleteUser_UserNotFound() {
when(userRepository.findById(999L)).thenReturn(Mono.empty());
StepVerifier.create(userService.deleteUser(999L))
.expectError(RuntimeException.class)
.verify();
verify(userRepository).findById(999L);
verify(userRepository, never()).deleteById(anyLong());
}
@Test
void testFindUsersByPage_WithKeyword() {
PageRequest pageRequest = new PageRequest();
pageRequest.setPage(0);
pageRequest.setSize(10);
pageRequest.setKeyword("test");
PageResponse<SysUser> pageResponse = new PageResponse<>();
pageResponse.setContent(List.of(testUser));
pageResponse.setTotalElements(1L);
when(userRepository.findByQueryWithPagination(any(SysUserQuery.class), eq(pageRequest)))
.thenReturn(Mono.just(pageResponse));
StepVerifier.create(userService.findUsersByPage(pageRequest))
.expectNextMatches(response -> response.getTotalElements() == 1L)
.verifyComplete();
verify(userRepository).findByQueryWithPagination(any(SysUserQuery.class), eq(pageRequest));
}
@Test
void testFindUsersByPage_WithoutKeyword() {
PageRequest pageRequest = new PageRequest();
pageRequest.setPage(0);
pageRequest.setSize(10);
PageResponse<SysUser> pageResponse = new PageResponse<>();
pageResponse.setContent(List.of(testUser));
pageResponse.setTotalElements(1L);
when(userRepository.findByQueryWithPagination(any(SysUserQuery.class), eq(pageRequest)))
.thenReturn(Mono.just(pageResponse));
StepVerifier.create(userService.findUsersByPage(pageRequest))
.expectNextMatches(response -> response.getTotalElements() == 1L)
.verifyComplete();
verify(userRepository).findByQueryWithPagination(any(SysUserQuery.class), eq(pageRequest));
}
@Test
void testFindUsersByPage_WithEmptyKeyword() {
PageRequest pageRequest = new PageRequest();
pageRequest.setPage(0);
pageRequest.setSize(10);
pageRequest.setKeyword("");
PageResponse<SysUser> pageResponse = new PageResponse<>();
pageResponse.setContent(List.of(testUser));
pageResponse.setTotalElements(1L);
when(userRepository.findByQueryWithPagination(any(SysUserQuery.class), eq(pageRequest)))
.thenReturn(Mono.just(pageResponse));
StepVerifier.create(userService.findUsersByPage(pageRequest))
.expectNextMatches(response -> response.getTotalElements() == 1L)
.verifyComplete();
verify(userRepository).findByQueryWithPagination(any(SysUserQuery.class), eq(pageRequest));
}
@Test
void testUpdateUserWithCommand_WithAllFields() {
SysUser existingUser = new SysUser();
existingUser.setId(1L);
existingUser.setUsername("olduser");
existingUser.setEmail("old@example.com");
existingUser.setRoleId(1L);
existingUser.setStatus(StatusConstants.ENABLED);
when(userRepository.findById(1L)).thenReturn(Mono.just(existingUser));
when(userRepository.save(any(SysUser.class))).thenReturn(Mono.just(testUser));
cn.novalon.manage.sys.core.command.UpdateUserCommand command =
new cn.novalon.manage.sys.core.command.UpdateUserCommand(
1L, "newuser", "newpass", "new@example.com", 2L, StatusConstants.DISABLED
);
StepVerifier.create(userService.updateUser(command))
.expectNextMatches(user -> user.getUpdatedAt() != null)
.verifyComplete();
verify(userRepository).findById(1L);
verify(userRepository).save(any(SysUser.class));
}
@Test
void testUpdateUserWithCommand_WithPartialFields() {
SysUser existingUser = new SysUser();
existingUser.setId(1L);
existingUser.setUsername("olduser");
existingUser.setEmail("old@example.com");
existingUser.setRoleId(1L);
existingUser.setStatus(StatusConstants.ENABLED);
when(userRepository.findById(1L)).thenReturn(Mono.just(existingUser));
when(userRepository.save(any(SysUser.class))).thenReturn(Mono.just(testUser));
cn.novalon.manage.sys.core.command.UpdateUserCommand command =
new cn.novalon.manage.sys.core.command.UpdateUserCommand(
1L, null, null, null, null, null
);
StepVerifier.create(userService.updateUser(command))
.expectNextMatches(user -> user.getUpdatedAt() != null)
.verifyComplete();
verify(userRepository).findById(1L);
verify(userRepository).save(any(SysUser.class));
}
}
@@ -0,0 +1,181 @@
package cn.novalon.manage.sys.filter;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
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 org.springframework.http.HttpStatus;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.net.InetSocketAddress;
import java.time.Duration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RateLimitFilterTest {
@Mock
private RateLimiterRegistry rateLimiterRegistry;
@Mock
private RateLimiter rateLimiter;
@Mock
private WebFilterChain webFilterChain;
private RateLimitFilter rateLimitFilter;
private MockServerWebExchange exchange;
@BeforeEach
void setUp() {
when(rateLimiterRegistry.rateLimiter("apiRateLimiter")).thenReturn(rateLimiter);
rateLimitFilter = new RateLimitFilter(rateLimiterRegistry);
exchange = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest.get("/api/test")
.remoteAddress(new InetSocketAddress("192.168.1.1", 8080))
.build()
);
}
@Test
void testFilter_WithPermissionGranted() {
when(rateLimiter.acquirePermission()).thenReturn(true);
when(webFilterChain.filter(any())).thenReturn(Mono.empty());
Mono<Void> result = rateLimitFilter.filter(exchange, webFilterChain);
StepVerifier.create(result)
.verifyComplete();
verify(webFilterChain).filter(any());
}
@Test
void testFilter_WithPermissionDenied() {
RateLimiterConfig config = RateLimiterConfig.custom()
.limitForPeriod(100)
.limitRefreshPeriod(Duration.ofSeconds(1))
.build();
when(rateLimiter.getRateLimiterConfig()).thenReturn(config);
when(rateLimiter.acquirePermission()).thenReturn(false);
Mono<Void> result = rateLimitFilter.filter(exchange, webFilterChain);
StepVerifier.create(result)
.verifyComplete();
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.TOO_MANY_REQUESTS);
assertThat(exchange.getResponse().getHeaders().getFirst("X-RateLimit-Limit")).isEqualTo("100");
assertThat(exchange.getResponse().getHeaders().getFirst("X-RateLimit-Remaining")).isEqualTo("0");
assertThat(exchange.getResponse().getHeaders().getFirst("Retry-After")).isEqualTo("1");
}
@Test
void testFilter_WithXForwardedForHeader() {
when(rateLimiter.acquirePermission()).thenReturn(true);
when(webFilterChain.filter(any())).thenReturn(Mono.empty());
MockServerWebExchange exchangeWithHeader = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest.get("/api/test")
.header("X-Forwarded-For", "10.0.0.1")
.build()
);
Mono<Void> result = rateLimitFilter.filter(exchangeWithHeader, webFilterChain);
StepVerifier.create(result)
.verifyComplete();
verify(webFilterChain).filter(any());
}
@Test
void testFilter_WithXRealIPHeader() {
when(rateLimiter.acquirePermission()).thenReturn(true);
when(webFilterChain.filter(any())).thenReturn(Mono.empty());
MockServerWebExchange exchangeWithHeader = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest.get("/api/test")
.header("X-Real-IP", "10.0.0.2")
.build()
);
Mono<Void> result = rateLimitFilter.filter(exchangeWithHeader, webFilterChain);
StepVerifier.create(result)
.verifyComplete();
verify(webFilterChain).filter(any());
}
@Test
void testFilter_WithUnknownIP() {
when(rateLimiter.acquirePermission()).thenReturn(true);
when(webFilterChain.filter(any())).thenReturn(Mono.empty());
MockServerWebExchange exchangeWithUnknownIP = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest.get("/api/test")
.header("X-Forwarded-For", "unknown")
.build()
);
Mono<Void> result = rateLimitFilter.filter(exchangeWithUnknownIP, webFilterChain);
StepVerifier.create(result)
.verifyComplete();
verify(webFilterChain).filter(any());
}
@Test
void testFilter_WithEmptyIP() {
when(rateLimiter.acquirePermission()).thenReturn(true);
when(webFilterChain.filter(any())).thenReturn(Mono.empty());
MockServerWebExchange exchangeWithEmptyIP = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest.get("/api/test")
.header("X-Forwarded-For", "")
.build()
);
Mono<Void> result = rateLimitFilter.filter(exchangeWithEmptyIP, webFilterChain);
StepVerifier.create(result)
.verifyComplete();
verify(webFilterChain).filter(any());
}
@Test
void testFilter_WithNullRemoteAddress() {
when(rateLimiter.acquirePermission()).thenReturn(true);
when(webFilterChain.filter(any())).thenReturn(Mono.empty());
MockServerWebExchange exchangeWithNullAddress = MockServerWebExchange.from(
org.springframework.mock.http.server.reactive.MockServerHttpRequest.get("/api/test")
.header("X-Forwarded-For", "unknown")
.header("X-Real-IP", "unknown")
.build()
);
Mono<Void> result = rateLimitFilter.filter(exchangeWithNullAddress, webFilterChain);
StepVerifier.create(result)
.verifyComplete();
verify(webFilterChain).filter(any());
}
}
@@ -0,0 +1,120 @@
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));
}
}
@@ -152,6 +152,29 @@ class SysLogHandlerTest {
verify(loginLogService).findLoginLogsByPage(any());
}
@Test
void testGetLoginLogsByPage_WithKeyword() {
PageResponse<SysLoginLog> pageResponse = new PageResponse<>();
pageResponse.setContent(java.util.Collections.singletonList(testLoginLog));
pageResponse.setTotalElements(1L);
when(loginLogService.findLoginLogsByPage(any())).thenReturn(Mono.just(pageResponse));
ServerRequest request = MockServerRequest.builder()
.queryParam("page", "0")
.queryParam("size", "10")
.queryParam("keyword", "test")
.build();
Mono<ServerResponse> response = logHandler.getLoginLogsByPage(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(loginLogService).findLoginLogsByPage(any());
}
@Test
void testGetLoginLogCount() {
when(loginLogService.count()).thenReturn(Mono.just(100L));
@@ -261,6 +284,29 @@ class SysLogHandlerTest {
verify(exceptionLogService).findExceptionLogsByPage(any());
}
@Test
void testGetExceptionLogsByPage_WithKeyword() {
PageResponse<SysExceptionLog> pageResponse = new PageResponse<>();
pageResponse.setContent(java.util.Collections.singletonList(testExceptionLog));
pageResponse.setTotalElements(1L);
when(exceptionLogService.findExceptionLogsByPage(any())).thenReturn(Mono.just(pageResponse));
ServerRequest request = MockServerRequest.builder()
.queryParam("page", "0")
.queryParam("size", "10")
.queryParam("keyword", "test")
.build();
Mono<ServerResponse> response = logHandler.getExceptionLogsByPage(request);
StepVerifier.create(response)
.expectNextMatches(serverResponse ->
serverResponse.statusCode() == HttpStatus.OK)
.verifyComplete();
verify(exceptionLogService).findExceptionLogsByPage(any());
}
@Test
void testGetExceptionLogCount() {
when(exceptionLogService.count()).thenReturn(Mono.just(50L));
@@ -0,0 +1,235 @@
package cn.novalon.manage.sys.primitive;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class EmailTest {
@Test
void testOf_ValidEmail() {
Email email = Email.of("test@example.com");
assertEquals("test@example.com", email.getValue());
}
@Test
void testOf_NullEmail() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Email.of(null)
);
assertEquals("Email is required", exception.getMessage());
}
@Test
void testOf_EmptyEmail() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Email.of("")
);
assertEquals("Email is required", exception.getMessage());
}
@Test
void testOf_WhitespaceOnlyEmail() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Email.of(" ")
);
assertEquals("Email is required", exception.getMessage());
}
@Test
void testOf_InvalidEmail_NoAtSymbol() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Email.of("testexample.com")
);
assertEquals("Invalid email format", exception.getMessage());
}
@Test
void testOf_InvalidEmail_NoDomain() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Email.of("test@")
);
assertEquals("Invalid email format", exception.getMessage());
}
@Test
void testOf_InvalidEmail_NoTLD() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Email.of("test@example")
);
assertEquals("Invalid email format", exception.getMessage());
}
@Test
void testOf_InvalidEmail_ShortTLD() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Email.of("test@example.c")
);
assertEquals("Invalid email format", exception.getMessage());
}
@Test
void testOf_ValidEmail_WithSubdomain() {
Email email = Email.of("test@mail.example.com");
assertEquals("test@mail.example.com", email.getValue());
}
@Test
void testOf_ValidEmail_WithPlus() {
Email email = Email.of("test+label@example.com");
assertEquals("test+label@example.com", email.getValue());
}
@Test
void testOf_ValidEmail_WithUnderscore() {
Email email = Email.of("test_user@example.com");
assertEquals("test_user@example.com", email.getValue());
}
@Test
void testOf_ValidEmail_WithHyphen() {
Email email = Email.of("test-user@example.com");
assertEquals("test-user@example.com", email.getValue());
}
@Test
void testOf_ValidEmail_WithDot() {
Email email = Email.of("test.user@example.com");
assertEquals("test.user@example.com", email.getValue());
}
@Test
void testOf_ValidEmail_WithNumbers() {
Email email = Email.of("test123@example.com");
assertEquals("test123@example.com", email.getValue());
}
@Test
void testOf_ValidEmail_WithMultipleDotsInDomain() {
Email email = Email.of("test@example.co.uk");
assertEquals("test@example.co.uk", email.getValue());
}
@Test
void testOf_ValidEmail_WithHyphenInDomain() {
Email email = Email.of("test@example-domain.com");
assertEquals("test@example-domain.com", email.getValue());
}
@Test
void testOfNullable_NullValue() {
Email email = Email.ofNullable(null);
assertNull(email);
}
@Test
void testOfNullable_EmptyValue() {
Email email = Email.ofNullable("");
assertNull(email);
}
@Test
void testOfNullable_WhitespaceValue() {
Email email = Email.ofNullable(" ");
assertNull(email);
}
@Test
void testOfNullable_ValidEmail() {
Email email = Email.ofNullable("test@example.com");
assertNotNull(email);
assertEquals("test@example.com", email.getValue());
}
@Test
void testEquals_SameValue() {
Email email1 = Email.of("test@example.com");
Email email2 = Email.of("test@example.com");
assertEquals(email1, email2);
}
@Test
void testEquals_DifferentValue() {
Email email1 = Email.of("test1@example.com");
Email email2 = Email.of("test2@example.com");
assertNotEquals(email1, email2);
}
@Test
void testEquals_SameObject() {
Email email = Email.of("test@example.com");
assertEquals(email, email);
}
@Test
void testEquals_Null() {
Email email = Email.of("test@example.com");
assertNotEquals(email, null);
}
@Test
void testEquals_DifferentClass() {
Email email = Email.of("test@example.com");
assertNotEquals(email, "test@example.com");
}
@Test
void testHashCode_SameValue() {
Email email1 = Email.of("test@example.com");
Email email2 = Email.of("test@example.com");
assertEquals(email1.hashCode(), email2.hashCode());
}
@Test
void testHashCode_DifferentValue() {
Email email1 = Email.of("test1@example.com");
Email email2 = Email.of("test2@example.com");
assertNotEquals(email1.hashCode(), email2.hashCode());
}
@Test
void testToString() {
Email email = Email.of("test@example.com");
assertEquals("test@example.com", email.toString());
}
@Test
void testOf_ValidEmail_WithLeadingTrailingWhitespace() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Email.of(" test@example.com ")
);
assertEquals("Invalid email format", exception.getMessage());
}
@Test
void testOf_ValidEmail_WithNumbersInDomain() {
Email email = Email.of("test@123example.com");
assertEquals("test@123example.com", email.getValue());
}
@Test
void testOf_ValidEmail_WithMultipleAtSymbols() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Email.of("test@@example.com")
);
assertEquals("Invalid email format", exception.getMessage());
}
@Test
void testOf_ValidEmail_WithSpecialCharsInLocalPart() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Email.of("test!#$%&'*+/=?^_`{|}~-@example.com")
);
assertEquals("Invalid email format", exception.getMessage());
}
}
@@ -0,0 +1,198 @@
package cn.novalon.manage.sys.primitive;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class PasswordTest {
@Test
void testOf_ValidPassword() {
Password password = Password.of("Test@123");
assertEquals("Test@123", password.getValue());
}
@Test
void testOf_NullPassword() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Password.of(null)
);
assertEquals("Password is required", exception.getMessage());
}
@Test
void testOf_EmptyPassword() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Password.of("")
);
assertEquals("Password is required", exception.getMessage());
}
@Test
void testOf_WhitespaceOnlyPassword() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Password.of(" ")
);
assertEquals("Password is required", exception.getMessage());
}
@Test
void testOf_TooShortPassword() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Password.of("Test@1")
);
assertEquals("Password must be at least 8 characters long", exception.getMessage());
}
@Test
void testOf_NoUppercase() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Password.of("test@123")
);
assertEquals("Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character", exception.getMessage());
}
@Test
void testOf_NoLowercase() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Password.of("TEST@123")
);
assertEquals("Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character", exception.getMessage());
}
@Test
void testOf_NoDigit() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Password.of("Test@abc")
);
assertEquals("Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character", exception.getMessage());
}
@Test
void testOf_NoSpecialCharacter() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Password.of("Test1234")
);
assertEquals("Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character", exception.getMessage());
}
@Test
void testOf_MinLengthBoundary() {
Password password = Password.of("Test@123");
assertEquals("Test@123", password.getValue());
}
@Test
void testOf_LongPassword() {
Password password = Password.of("VeryLongPassword@123456");
assertEquals("VeryLongPassword@123456", password.getValue());
}
@Test
void testOf_WithMultipleSpecialCharacters() {
Password password = Password.of("Test@#$%123");
assertEquals("Test@#$%123", password.getValue());
}
@Test
void testOf_WithUnderscore() {
Password password = Password.of("Test_123");
assertEquals("Test_123", password.getValue());
}
@Test
void testOf_WithHyphen() {
Password password = Password.of("Test-123");
assertEquals("Test-123", password.getValue());
}
@Test
void testEquals_SameValue() {
Password password1 = Password.of("Test@123");
Password password2 = Password.of("Test@123");
assertEquals(password1, password2);
}
@Test
void testEquals_DifferentValue() {
Password password1 = Password.of("Test@123");
Password password2 = Password.of("Test@456");
assertNotEquals(password1, password2);
}
@Test
void testEquals_SameObject() {
Password password = Password.of("Test@123");
assertEquals(password, password);
}
@Test
void testEquals_Null() {
Password password = Password.of("Test@123");
assertNotEquals(password, null);
}
@Test
void testEquals_DifferentClass() {
Password password = Password.of("Test@123");
assertNotEquals(password, "Test@123");
}
@Test
void testHashCode_SameValue() {
Password password1 = Password.of("Test@123");
Password password2 = Password.of("Test@123");
assertEquals(password1.hashCode(), password2.hashCode());
}
@Test
void testHashCode_DifferentValue() {
Password password1 = Password.of("Test@123");
Password password2 = Password.of("Test@456");
assertNotEquals(password1.hashCode(), password2.hashCode());
}
@Test
void testToString() {
Password password = Password.of("Test@123");
assertEquals("********", password.toString());
}
@Test
void testOf_WithSpacesInPassword() {
Password password = Password.of("Test @123");
assertEquals("Test @123", password.getValue());
}
@Test
void testOf_WithUnicodeCharacters() {
Password password = Password.of("Tëst@123");
assertEquals("Tëst@123", password.getValue());
}
@Test
void testOf_WithNumbersOnly() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Password.of("12345678")
);
assertEquals("Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character", exception.getMessage());
}
@Test
void testOf_WithLettersOnly() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Password.of("TestTest")
);
assertEquals("Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character", exception.getMessage());
}
}
@@ -0,0 +1,183 @@
package cn.novalon.manage.sys.primitive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;
class UsernameTest {
@Test
void testOf_ValidUsername() {
Username username = Username.of("test_user123");
assertEquals("test_user123", username.getValue());
}
@Test
void testOf_NullUsername() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Username.of(null)
);
assertEquals("Username is required", exception.getMessage());
}
@Test
void testOf_EmptyUsername() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Username.of("")
);
assertEquals("Username is required", exception.getMessage());
}
@Test
void testOf_WhitespaceOnlyUsername() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Username.of(" ")
);
assertEquals("Username is required", exception.getMessage());
}
@Test
void testOf_TooShortUsername() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Username.of("ab")
);
assertEquals("Username must be at least 3 characters long", exception.getMessage());
}
@Test
void testOf_TooLongUsername() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Username.of("a".repeat(51))
);
assertEquals("Username must be at most 50 characters long", exception.getMessage());
}
@Test
void testOf_WithSpecialCharacters() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Username.of("user@name")
);
assertEquals("Username can only contain letters, numbers, and underscores", exception.getMessage());
}
@Test
void testOf_WithSpaces() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Username.of("user name")
);
assertEquals("Username can only contain letters, numbers, and underscores", exception.getMessage());
}
@Test
void testOf_WithHyphens() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> Username.of("user-name")
);
assertEquals("Username can only contain letters, numbers, and underscores", exception.getMessage());
}
@Test
void testOf_MinLengthBoundary() {
Username username = Username.of("abc");
assertEquals("abc", username.getValue());
}
@Test
void testOf_MaxLengthBoundary() {
Username username = Username.of("a".repeat(50));
assertEquals("a".repeat(50), username.getValue());
}
@Test
void testOf_WithLeadingTrailingWhitespace() {
Username username = Username.of(" test_user ");
assertEquals(" test_user ", username.getValue());
}
@Test
void testOf_OnlyLetters() {
Username username = Username.of("username");
assertEquals("username", username.getValue());
}
@Test
void testOf_OnlyNumbers() {
Username username = Username.of("123456");
assertEquals("123456", username.getValue());
}
@Test
void testOf_OnlyUnderscores() {
Username username = Username.of("___");
assertEquals("___", username.getValue());
}
@Test
void testEquals_SameValue() {
Username username1 = Username.of("testuser");
Username username2 = Username.of("testuser");
assertEquals(username1, username2);
}
@Test
void testEquals_DifferentValue() {
Username username1 = Username.of("testuser1");
Username username2 = Username.of("testuser2");
assertNotEquals(username1, username2);
}
@Test
void testEquals_SameObject() {
Username username = Username.of("testuser");
assertEquals(username, username);
}
@Test
void testEquals_Null() {
Username username = Username.of("testuser");
assertNotEquals(username, null);
}
@Test
void testEquals_DifferentClass() {
Username username = Username.of("testuser");
assertNotEquals(username, "testuser");
}
@Test
void testHashCode_SameValue() {
Username username1 = Username.of("testuser");
Username username2 = Username.of("testuser");
assertEquals(username1.hashCode(), username2.hashCode());
}
@Test
void testHashCode_DifferentValue() {
Username username1 = Username.of("testuser1");
Username username2 = Username.of("testuser2");
assertNotEquals(username1.hashCode(), username2.hashCode());
}
@Test
void testToString() {
Username username = Username.of("testuser");
assertEquals("testuser", username.toString());
}
@ParameterizedTest
@ValueSource(strings = {"user_123", "User_123", "USER_123", "123_user", "_user", "user_"})
void testOf_ValidFormats(String validUsername) {
Username username = Username.of(validUsername);
assertEquals(validUsername.trim(), username.getValue());
}
}
@@ -0,0 +1,135 @@
package cn.novalon.manage.sys.security;
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 org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class JwtAuthenticationFilterTest {
@Mock
private JwtTokenProvider jwtTokenProvider;
@Mock
private WebFilterChain webFilterChain;
private JwtAuthenticationFilter jwtAuthenticationFilter;
@BeforeEach
void setUp() {
jwtAuthenticationFilter = new JwtAuthenticationFilter(jwtTokenProvider);
}
@Test
void testFilter_WithValidToken() {
String validToken = "valid.jwt.token";
Long userId = 1L;
when(jwtTokenProvider.validateToken(validToken)).thenReturn(true);
when(jwtTokenProvider.getUserIdFromToken(validToken)).thenReturn(userId);
when(webFilterChain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest.get("/api/test")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + validToken)
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
Mono<Void> result = jwtAuthenticationFilter.filter(exchange, webFilterChain);
StepVerifier.create(result)
.verifyComplete();
verify(jwtTokenProvider).validateToken(validToken);
verify(jwtTokenProvider).getUserIdFromToken(validToken);
verify(webFilterChain).filter(any(ServerWebExchange.class));
}
@Test
void testFilter_WithInvalidToken() {
String invalidToken = "invalid.jwt.token";
when(jwtTokenProvider.validateToken(invalidToken)).thenReturn(false);
when(webFilterChain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest.get("/api/test")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + invalidToken)
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
Mono<Void> result = jwtAuthenticationFilter.filter(exchange, webFilterChain);
StepVerifier.create(result)
.verifyComplete();
verify(jwtTokenProvider).validateToken(invalidToken);
verify(webFilterChain).filter(any(ServerWebExchange.class));
}
@Test
void testFilter_WithoutToken() {
when(webFilterChain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest.get("/api/test")
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
Mono<Void> result = jwtAuthenticationFilter.filter(exchange, webFilterChain);
StepVerifier.create(result)
.verifyComplete();
verify(webFilterChain).filter(any(ServerWebExchange.class));
}
@Test
void testFilter_WithMalformedToken() {
String malformedToken = "Bearer";
when(webFilterChain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest.get("/api/test")
.header(HttpHeaders.AUTHORIZATION, malformedToken)
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
Mono<Void> result = jwtAuthenticationFilter.filter(exchange, webFilterChain);
StepVerifier.create(result)
.verifyComplete();
verify(webFilterChain).filter(any(ServerWebExchange.class));
}
@Test
void testFilter_WithTokenWithoutBearerPrefix() {
String tokenWithoutBearer = "just.a.token";
when(webFilterChain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty());
MockServerHttpRequest request = MockServerHttpRequest.get("/api/test")
.header(HttpHeaders.AUTHORIZATION, tokenWithoutBearer)
.build();
ServerWebExchange exchange = MockServerWebExchange.from(request);
Mono<Void> result = jwtAuthenticationFilter.filter(exchange, webFilterChain);
StepVerifier.create(result)
.verifyComplete();
verify(webFilterChain).filter(any(ServerWebExchange.class));
}
}
+5
View File
@@ -188,6 +188,11 @@
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.10.0.2594</version>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,12 @@
sonar.projectKey=novalon-manage-system
sonar.projectName=Novalon Manage System
sonar.projectVersion=1.0.0
sonar.sourceEncoding=UTF-8
sonar.sources=manage-sys/src/main/java,manage-gateway/src/main/java,manage-app/src/main/java,manage-notify/src/main/java,manage-file/src/main/java,manage-audit/src/main/java,manage-db/src/main/java,manage-common/src/main/java
sonar.tests=manage-sys/src/test/java,manage-gateway/src/test/java,manage-app/src/test/java,manage-notify/src/test/java,manage-file/src/test/java,manage-audit/src/test/java,manage-db/src/test/java,manage-common/src/test/java
sonar.java.binaries=manage-sys/target/classes,manage-gateway/target/classes,manage-app/target/classes,manage-notify/target/classes,manage-file/target/classes,manage-audit/target/classes,manage-db/target/classes,manage-common/target/classes
sonar.java.test.binaries=manage-sys/target/test-classes,manage-gateway/target/test-classes,manage-app/target/test-classes,manage-notify/target/test-classes,manage-file/target/test-classes,manage-audit/target/test-classes,manage-db/target/test-classes,manage-common/target/test-classes
sonar.coverage.jacoco.xmlReportPaths=manage-sys/target/site/jacoco/jacoco.xml,manage-gateway/target/site/jacoco/jacoco.xml,manage-app/target/site/jacoco/jacoco.xml,manage-notify/target/site/jacoco/jacoco.xml,manage-file/target/site/jacoco/jacoco.xml,manage-audit/target/site/jacoco/jacoco.xml,manage-db/target/site/jacoco/jacoco.xml,manage-common/target/site/jacoco/jacoco.xml
sonar.java.coveragePlugin=jacoco
sonar.qualitygate.wait=true
sonar.qualitygate.timeout=300