feat: extend operation log service and repository with pagination support
This commit is contained in:
@@ -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"]
|
||||
+2
-6
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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;
|
||||
|
||||
+5
@@ -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:
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
cn.novalon.manage.common.config.CacheConfig
|
||||
cn.novalon.manage.common.config.JwtProperties
|
||||
+87
-1
@@ -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();
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
cn.novalon.manage.db.config.RepositoryScanConfig
|
||||
+126
@@ -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));
|
||||
+10
@@ -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';
|
||||
@@ -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>
|
||||
|
||||
+90
@@ -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());
|
||||
}
|
||||
}
|
||||
+260
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
cn.novalon.manage.gateway.config.RateLimitConfig
|
||||
+309
@@ -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));
|
||||
}
|
||||
}
|
||||
+255
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
cn.novalon.manage.notify.config.WebSocketConfig
|
||||
+252
@@ -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);
|
||||
}
|
||||
}
|
||||
+181
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
+4
@@ -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);
|
||||
|
||||
+4
@@ -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();
|
||||
}
|
||||
|
||||
+12
@@ -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();
|
||||
|
||||
+79
@@ -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));
|
||||
}
|
||||
}
|
||||
+2
@@ -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
|
||||
-209
@@ -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;
|
||||
-18
@@ -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);
|
||||
+44
@@ -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);
|
||||
}
|
||||
}
|
||||
+78
@@ -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();
|
||||
}
|
||||
}
|
||||
+211
@@ -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());
|
||||
}
|
||||
}
|
||||
+185
@@ -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());
|
||||
}
|
||||
}
|
||||
+260
@@ -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();
|
||||
}
|
||||
}
|
||||
+311
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+177
-16
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+181
@@ -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());
|
||||
}
|
||||
}
|
||||
+120
@@ -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));
|
||||
}
|
||||
}
|
||||
+46
@@ -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));
|
||||
|
||||
+235
@@ -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());
|
||||
}
|
||||
}
|
||||
+198
@@ -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());
|
||||
}
|
||||
}
|
||||
+183
@@ -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());
|
||||
}
|
||||
}
|
||||
+135
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user