feat(产品需求): 产品和产品需求相关的测试类

This commit is contained in:
dk
2026-04-28 16:50:04 +08:00
parent 846348e1aa
commit 9ad7e063c0
2 changed files with 708 additions and 0 deletions

View File

@@ -0,0 +1,671 @@
package com.njcn.rdms.module.project.service.product;
import com.njcn.rdms.framework.common.exception.ServiceException;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.framework.test.core.ut.BaseMockitoUnitTest;
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.*;
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementModuleDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementStatusLogDO;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO;
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductRequirementMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductRequirementModuleMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductRequirementStatusLogMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
/**
* 产品需求 Service 单元测试
*/
class ProductRequirementServiceImplTest extends BaseMockitoUnitTest {
@InjectMocks
private ProductRequirementServiceImpl requirementService;
@Mock
private ProductRequirementMapper requirementMapper;
@Mock
private ProductRequirementModuleMapper moduleMapper;
@Mock
private ProductRequirementStatusLogMapper statusLogMapper;
@Mock
private BizAuditLogMapper bizAuditLogMapper;
@Mock
private ObjectStatusTransitionMapper statusTransitionMapper;
@Mock
private ObjectStatusModelMapper statusModelMapper;
// ========== 创建需求测试 ==========
@Test
void createRequirement_withoutReview_shouldCreateWithPendingDispatchStatus() {
Long loginUserId = 1001L;
Long defaultModuleId = 50L;
ProductRequirementSaveReqVO reqVO = new ProductRequirementSaveReqVO();
reqVO.setProductId(100L);
reqVO.setTitle("测试需求");
reqVO.setDescription("测试描述");
reqVO.setCategory("function");
reqVO.setReviewRequired(0); // 不需要评审
reqVO.setPriority(1);
reqVO.setProposerId(2001L);
reqVO.setCurrentHandlerUserId(2002L);
reqVO.setCompletionDate(LocalDateTime.now());
// 模拟"全部需求"模块存在parentId = 0L 的根模块)
ProductRequirementModuleDO defaultModule = createDefaultModule(defaultModuleId, 100L);
when(moduleMapper.selectByProductIdAndParentId(100L, 0L)).thenReturn(defaultModule);
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId, "测试人")) {
requirementService.createRequirement(reqVO);
}
ArgumentCaptor<ProductRequirementDO> captor = ArgumentCaptor.forClass(ProductRequirementDO.class);
verify(requirementMapper, times(1)).insert(captor.capture());
ProductRequirementDO created = captor.getValue();
assertEquals("pending_dispatch", created.getStatusCode()); // 不需要评审时初始状态为待分流
assertEquals("manual", created.getSourceType());
assertEquals(0L, created.getParentId());
assertEquals("测试需求", created.getTitle());
assertEquals(defaultModuleId, created.getModuleId()); // 未选择模块时自动归属到"全部需求"模块
assertEquals(100L, created.getProductId());
}
@Test
void createRequirement_withReview_shouldCreateWithPendingReviewStatus() {
Long loginUserId = 1001L;
Long defaultModuleId = 50L;
ProductRequirementSaveReqVO reqVO = new ProductRequirementSaveReqVO();
reqVO.setProductId(100L);
reqVO.setTitle("需评审的需求");
reqVO.setCategory("function");
reqVO.setReviewRequired(1); // 需要评审
reqVO.setPriority(2);
reqVO.setProposerId(2001L);
reqVO.setCompletionDate(LocalDateTime.now());
// 模拟"全部需求"模块存在parentId = 0L 的根模块)
ProductRequirementModuleDO defaultModule = createDefaultModule(defaultModuleId, 100L);
when(moduleMapper.selectByProductIdAndParentId(100L, 0L)).thenReturn(defaultModule);
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId, "测试人")) {
requirementService.createRequirement(reqVO);
}
ArgumentCaptor<ProductRequirementDO> captor = ArgumentCaptor.forClass(ProductRequirementDO.class);
verify(requirementMapper, times(1)).insert(captor.capture());
ProductRequirementDO created = captor.getValue();
assertEquals("pending_review", created.getStatusCode()); // 需要评审时初始状态为待评审
assertEquals(defaultModuleId, created.getModuleId()); // 未选择模块时自动归属到"全部需求"模块
}
// ========== 更新需求测试 ==========
@Test
void updateRequirement_whenTerminalStatus_shouldThrowException() {
Long requirementId = 1001L;
ProductRequirementDO requirement = createRequirement(requirementId, 100L, "已关闭需求",
"closed", 0L, 0);
ProductRequirementUpdateReqVO reqVO = new ProductRequirementUpdateReqVO();
reqVO.setId(requirementId);
reqVO.setProductId(100L);
reqVO.setTitle("修改标题");
reqVO.setCategory("function");
reqVO.setReviewRequired(0);
reqVO.setPriority(1);
reqVO.setProposerId(2001L);
reqVO.setCompletionDate(LocalDateTime.now());
when(requirementMapper.selectById(requirementId)).thenReturn(requirement);
ServiceException ex = assertThrows(ServiceException.class,
() -> requirementService.updateRequirement(reqVO));
assertEquals(ErrorCodeConstants.REQUIREMENT_STATUS_NOT_ALLOW_EDIT.getCode(), ex.getCode());
verify(requirementMapper, never()).updateById(any(ProductRequirementDO.class));
}
@Test
void updateRequirement_whenNormalStatus_shouldUpdateSuccessfully() {
Long requirementId = 1002L;
Long loginUserId = 1001L;
Long defaultModuleId = 50L;
ProductRequirementDO requirement = createRequirement(requirementId, 100L, "待分流需求",
"pending_dispatch", 0L, 0);
ProductRequirementUpdateReqVO reqVO = new ProductRequirementUpdateReqVO();
reqVO.setId(requirementId);
reqVO.setProductId(100L);
reqVO.setTitle("修改后的标题");
reqVO.setCategory("security");
reqVO.setReviewRequired(0);
reqVO.setPriority(2);
reqVO.setProposerId(2001L);
reqVO.setCompletionDate(LocalDateTime.now());
// 模拟"全部需求"模块存在未选择模块时自动归属parentId = 0L 的根模块)
ProductRequirementModuleDO defaultModule = createDefaultModule(defaultModuleId, 100L);
when(moduleMapper.selectByProductIdAndParentId(100L, 0L)).thenReturn(defaultModule);
when(requirementMapper.selectById(requirementId)).thenReturn(requirement);
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId, "测试人")) {
requirementService.updateRequirement(reqVO);
}
ArgumentCaptor<ProductRequirementDO> captor = ArgumentCaptor.forClass(ProductRequirementDO.class);
verify(requirementMapper, times(1)).updateById(captor.capture());
assertEquals(defaultModuleId, captor.getValue().getModuleId()); // 未选择模块时自动归属到"全部需求"模块
verify(bizAuditLogMapper, times(1)).insert(any(BizAuditLogDO.class));
}
// ========== 状态变更测试 ==========
@Test
void changeRequirementStatus_whenActionAllowed_shouldUpdateStatusAndWriteLogs() {
Long requirementId = 1003L;
Long loginUserId = 1001L;
ProductRequirementDO requirement = createRequirement(requirementId, 100L, "待分流需求",
"pending_dispatch", 0L, 0);
ProductRequirementStatusActionReqVO reqVO = new ProductRequirementStatusActionReqVO();
reqVO.setId(requirementId);
reqVO.setActionCode("dispatch");
reqVO.setReason(null);
when(requirementMapper.selectById(requirementId)).thenReturn(requirement);
when(statusTransitionMapper.selectByObjectTypeAndFromStatusAndAction(
"product_requirement", "pending_dispatch", "dispatch"))
.thenReturn(createTransition("dispatch", "implementing", false));
when(requirementMapper.updateStatusByIdAndStatus(requirementId, "pending_dispatch", "implementing", null))
.thenReturn(1);
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId, "测试人")) {
requirementService.changeRequirementStatus(reqVO);
}
verify(requirementMapper, times(1))
.updateStatusByIdAndStatus(requirementId, "pending_dispatch", "implementing", null);
verify(statusLogMapper, times(1)).insert(any(ProductRequirementStatusLogDO.class));
verify(bizAuditLogMapper, times(1)).insert(any(BizAuditLogDO.class));
}
@Test
void changeRequirementStatus_whenActionNotAllowed_shouldThrowException() {
Long requirementId = 1004L;
ProductRequirementDO requirement = createRequirement(requirementId, 100L, "已关闭需求",
"closed", 0L, 0);
ProductRequirementStatusActionReqVO reqVO = new ProductRequirementStatusActionReqVO();
reqVO.setId(requirementId);
reqVO.setActionCode("dispatch");
when(requirementMapper.selectById(requirementId)).thenReturn(requirement);
when(statusTransitionMapper.selectByObjectTypeAndFromStatusAndAction(
"product_requirement", "closed", "dispatch"))
.thenReturn(null);
ServiceException ex = assertThrows(ServiceException.class,
() -> requirementService.changeRequirementStatus(reqVO));
assertEquals(ErrorCodeConstants.REQUIREMENT_STATUS_ACTION_NOT_ALLOWED.getCode(), ex.getCode());
}
@Test
void changeRequirementStatus_whenReasonRequiredButMissing_shouldThrowException() {
Long requirementId = 1005L;
ProductRequirementDO requirement = createRequirement(requirementId, 100L, "待分流需求",
"pending_dispatch", 0L, 0);
ProductRequirementStatusActionReqVO reqVO = new ProductRequirementStatusActionReqVO();
reqVO.setId(requirementId);
reqVO.setActionCode("cancel");
reqVO.setReason(" "); // 空原因
when(requirementMapper.selectById(requirementId)).thenReturn(requirement);
when(statusTransitionMapper.selectByObjectTypeAndFromStatusAndAction(
"product_requirement", "pending_dispatch", "cancel"))
.thenReturn(createTransition("cancel", "cancelled", true));
ServiceException ex = assertThrows(ServiceException.class,
() -> requirementService.changeRequirementStatus(reqVO));
assertEquals(ErrorCodeConstants.REQUIREMENT_STATUS_ACTION_REASON_REQUIRED.getCode(), ex.getCode());
}
@Test
void changeRequirementStatus_whenConcurrentModified_shouldThrowException() {
Long requirementId = 1006L;
Long loginUserId = 1001L;
ProductRequirementDO requirement = createRequirement(requirementId, 100L, "待分流需求",
"pending_dispatch", 0L, 0);
ProductRequirementStatusActionReqVO reqVO = new ProductRequirementStatusActionReqVO();
reqVO.setId(requirementId);
reqVO.setActionCode("dispatch");
when(requirementMapper.selectById(requirementId)).thenReturn(requirement);
when(statusTransitionMapper.selectByObjectTypeAndFromStatusAndAction(
"product_requirement", "pending_dispatch", "dispatch"))
.thenReturn(createTransition("dispatch", "implementing", false));
when(requirementMapper.updateStatusByIdAndStatus(requirementId, "pending_dispatch", "implementing", null))
.thenReturn(0); // 并发修改,更新失败
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId, "测试人")) {
ServiceException ex = assertThrows(ServiceException.class,
() -> requirementService.changeRequirementStatus(reqVO));
assertEquals(ErrorCodeConstants.REQUIREMENT_STATUS_CONCURRENT_MODIFIED.getCode(), ex.getCode());
}
verify(statusLogMapper, never()).insert(any(ProductRequirementStatusLogDO.class));
verify(bizAuditLogMapper, never()).insert(any(BizAuditLogDO.class));
}
// ========== 删除需求测试 ==========
@Test
void deleteRequirement_shouldDeleteByConditionAndWriteLogs() {
Long requirementId = 1007L;
Long loginUserId = 1001L;
ProductRequirementDO requirement = createRequirement(requirementId, 100L, "待分流需求",
"pending_dispatch", 0L, 0);
when(requirementMapper.selectById(requirementId)).thenReturn(requirement);
when(requirementMapper.deleteByIdAndStatus(requirementId, "pending_dispatch")).thenReturn(1);
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId, "测试人")) {
requirementService.deleteRequirement(requirementId, 100L);
}
verify(requirementMapper, times(1)).deleteByIdAndStatus(requirementId, "pending_dispatch");
verify(bizAuditLogMapper, times(1)).insert(any(BizAuditLogDO.class));
}
// ========== 拆分需求测试 ==========
@Test
void splitRequirement_whenParentPendingDispatch_shouldChangeParentToImplementing() {
Long parentId = 1008L;
Long loginUserId = 1001L;
ProductRequirementDO parent = createRequirement(parentId, 100L, "大需求",
"pending_dispatch", 0L, 0);
parent.setModuleId(50L);
parent.setProposerId(2001L);
ProductRequirementSplitReqVO reqVO = new ProductRequirementSplitReqVO();
reqVO.setParentId(parentId);
reqVO.setProductId(100L);
reqVO.setTitle("子需求");
reqVO.setCategory("function");
reqVO.setPriority(1);
reqVO.setCompletionDate(LocalDateTime.now());
when(requirementMapper.selectById(parentId)).thenReturn(parent);
when(requirementMapper.updateStatusByIdAndStatus(parentId, "pending_dispatch", "implementing", null))
.thenReturn(1);
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId, "测试人")) {
requirementService.splitRequirement(reqVO);
}
// 验证子需求被创建
ArgumentCaptor<ProductRequirementDO> childCaptor = ArgumentCaptor.forClass(ProductRequirementDO.class);
verify(requirementMapper, times(1)).insert(childCaptor.capture());
ProductRequirementDO child = childCaptor.getValue();
assertEquals(parentId, child.getParentId());
assertEquals("pending_dispatch", child.getStatusCode());
assertEquals(parent.getModuleId(), child.getModuleId());
assertEquals(parent.getProposerId(), child.getProposerId());
// 验证父需求状态变为实施中
verify(requirementMapper, times(1))
.updateStatusByIdAndStatus(parentId, "pending_dispatch", "implementing", null);
verify(statusLogMapper, times(1)).insert(any(ProductRequirementStatusLogDO.class));
}
@Test
void splitRequirement_whenParentImplementing_shouldNotChangeParentStatus() {
Long parentId = 1009L;
Long loginUserId = 1001L;
ProductRequirementDO parent = createRequirement(parentId, 100L, "实施中的大需求",
"implementing", 0L, 0);
parent.setModuleId(50L);
parent.setProposerId(2001L);
ProductRequirementSplitReqVO reqVO = new ProductRequirementSplitReqVO();
reqVO.setParentId(parentId);
reqVO.setProductId(100L);
reqVO.setTitle("子需求2");
reqVO.setCategory("function");
reqVO.setPriority(1);
reqVO.setCompletionDate(LocalDateTime.now());
when(requirementMapper.selectById(parentId)).thenReturn(parent);
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId, "测试人")) {
requirementService.splitRequirement(reqVO);
}
// 父需求已经是实施中,不需要再更新状态
verify(requirementMapper, never())
.updateStatusByIdAndStatus(parentId, "implementing", "implementing", null);
}
@Test
void splitRequirement_whenParentNotAllowSplit_shouldThrowException() {
Long parentId = 1010L;
ProductRequirementDO parent = createRequirement(parentId, 100L, "已验收的大需求",
"accepted", 0L, 0);
ProductRequirementSplitReqVO reqVO = new ProductRequirementSplitReqVO();
reqVO.setParentId(parentId);
reqVO.setProductId(100L);
reqVO.setTitle("子需求");
reqVO.setCategory("function");
reqVO.setPriority(1);
reqVO.setCompletionDate(LocalDateTime.now());
when(requirementMapper.selectById(parentId)).thenReturn(parent);
ServiceException ex = assertThrows(ServiceException.class,
() -> requirementService.splitRequirement(reqVO));
assertEquals(ErrorCodeConstants.REQUIREMENT_PARENT_NOT_ALLOW_SPLIT.getCode(), ex.getCode());
verify(requirementMapper, never()).insert(any(ProductRequirementDO.class));
}
// ========== 关闭需求测试 ==========
@Test
void closeRequirement_whenAccepted_shouldCloseSuccessfully() {
Long requirementId = 1011L;
Long loginUserId = 1001L;
ProductRequirementDO requirement = createRequirement(requirementId, 100L, "已验收需求",
"accepted", 0L, 0);
ProductRequirementCloseReqVO reqVO = new ProductRequirementCloseReqVO();
reqVO.setId(requirementId);
reqVO.setProductId(100L);
reqVO.setReason("需求已完成");
when(requirementMapper.selectById(requirementId)).thenReturn(requirement);
when(requirementMapper.selectListByParentId(requirementId)).thenReturn(Collections.emptyList());
when(requirementMapper.updateStatusByIdAndStatus(requirementId, "accepted", "closed", "需求已完成"))
.thenReturn(1);
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId, "测试人")) {
requirementService.closeRequirement(reqVO);
}
verify(requirementMapper, times(1))
.updateStatusByIdAndStatus(requirementId, "accepted", "closed", "需求已完成");
verify(statusLogMapper, times(1)).insert(any(ProductRequirementStatusLogDO.class));
verify(bizAuditLogMapper, times(1)).insert(any(BizAuditLogDO.class));
}
@Test
void closeRequirement_whenNotAccepted_shouldThrowException() {
Long requirementId = 1012L;
ProductRequirementDO requirement = createRequirement(requirementId, 100L, "实施中需求",
"implementing", 0L, 0);
ProductRequirementCloseReqVO reqVO = new ProductRequirementCloseReqVO();
reqVO.setId(requirementId);
reqVO.setProductId(100L);
reqVO.setReason("需求已完成");
when(requirementMapper.selectById(requirementId)).thenReturn(requirement);
ServiceException ex = assertThrows(ServiceException.class,
() -> requirementService.closeRequirement(reqVO));
assertEquals(ErrorCodeConstants.REQUIREMENT_STATUS_NOT_ALLOW_CLOSE.getCode(), ex.getCode());
}
@Test
void closeRequirement_withChildren_shouldCascadeCloseAcceptedChildren() {
Long parentId = 1013L;
Long childId = 1014L;
Long loginUserId = 1001L;
ProductRequirementDO parent = createRequirement(parentId, 100L, "父需求",
"accepted", 0L, 0);
ProductRequirementDO child = createRequirement(childId, 100L, "子需求",
"accepted", parentId, 0);
ProductRequirementCloseReqVO reqVO = new ProductRequirementCloseReqVO();
reqVO.setId(parentId);
reqVO.setProductId(100L);
reqVO.setReason("全部验收完成");
when(requirementMapper.selectById(parentId)).thenReturn(parent);
when(requirementMapper.selectListByParentId(parentId)).thenReturn(List.of(child));
when(requirementMapper.updateStatusByIdAndStatus(childId, "accepted", "closed", "全部验收完成"))
.thenReturn(1);
when(requirementMapper.updateStatusByIdAndStatus(parentId, "accepted", "closed", "全部验收完成"))
.thenReturn(1);
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId, "测试人")) {
requirementService.closeRequirement(reqVO);
}
// 子需求被关闭
verify(requirementMapper, times(1))
.updateStatusByIdAndStatus(childId, "accepted", "closed", "全部验收完成");
// 父需求被关闭
verify(requirementMapper, times(1))
.updateStatusByIdAndStatus(parentId, "accepted", "closed", "全部验收完成");
}
@Test
void closeRequirement_withChildNotAllowClose_shouldThrowException() {
Long parentId = 1015L;
ProductRequirementDO parent = createRequirement(parentId, 100L, "父需求",
"accepted", 0L, 0);
ProductRequirementDO child = createRequirement(1016L, 100L, "实施中子需求",
"implementing", parentId, 0);
ProductRequirementCloseReqVO reqVO = new ProductRequirementCloseReqVO();
reqVO.setId(parentId);
reqVO.setProductId(100L);
reqVO.setReason("全部验收完成");
when(requirementMapper.selectById(parentId)).thenReturn(parent);
when(requirementMapper.selectListByParentId(parentId)).thenReturn(List.of(child));
ServiceException ex = assertThrows(ServiceException.class,
() -> requirementService.closeRequirement(reqVO));
assertEquals(ErrorCodeConstants.REQUIREMENT_CHILD_NOT_ALLOW_CLOSE.getCode(), ex.getCode());
}
// ========== 模块管理测试 ==========
@Test
void createRequirementModule_whenNameDuplicate_shouldThrowException() {
Long productId = 100L;
ProductRequirementModuleDO existModule = new ProductRequirementModuleDO();
existModule.setId(50L);
existModule.setProductId(productId);
existModule.setModuleName("核心功能");
ProductRequirementModuleSaveReqVO reqVO = new ProductRequirementModuleSaveReqVO();
reqVO.setProductId(productId);
reqVO.setModuleName("核心功能");
when(moduleMapper.selectByProductIdAndModuleName(productId, "核心功能")).thenReturn(existModule);
ServiceException ex = assertThrows(ServiceException.class,
() -> requirementService.createRequirementModule(reqVO));
assertEquals(ErrorCodeConstants.REQUIREMENT_MODULE_NAME_DUPLICATE.getCode(), ex.getCode());
}
@Test
void deleteRequirementModule_whenHasNonTerminalRequirements_shouldThrowException() {
Long moduleId = 50L;
Long productId = 100L;
ProductRequirementModuleDO module = new ProductRequirementModuleDO();
module.setId(moduleId);
module.setProductId(productId);
module.setModuleName("核心功能");
ProductRequirementDO nonTerminalReq = createRequirement(2001L, productId, "实施中需求",
"implementing", 0L, 0);
nonTerminalReq.setModuleId(moduleId);
when(moduleMapper.selectById(moduleId)).thenReturn(module);
when(requirementMapper.selectListByModuleId(moduleId)).thenReturn(List.of(nonTerminalReq));
ServiceException ex = assertThrows(ServiceException.class,
() -> requirementService.deleteRequirementModule(moduleId, productId));
assertEquals(ErrorCodeConstants.REQUIREMENT_MODULE_HAS_NON_TERMINAL_REQUIREMENTS.getCode(), ex.getCode());
verify(moduleMapper, never()).deleteById(moduleId);
}
@Test
void deleteRequirementModule_whenAllTerminal_shouldDeleteSuccessfully() {
Long moduleId = 50L;
Long productId = 100L;
Long loginUserId = 1001L;
ProductRequirementModuleDO module = new ProductRequirementModuleDO();
module.setId(moduleId);
module.setProductId(productId);
module.setModuleName("核心功能");
ProductRequirementDO closedReq = createRequirement(2001L, productId, "已关闭需求",
"closed", 0L, 0);
closedReq.setModuleId(moduleId);
when(moduleMapper.selectById(moduleId)).thenReturn(module);
when(requirementMapper.selectListByModuleId(moduleId)).thenReturn(List.of(closedReq));
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId, "测试人")) {
requirementService.deleteRequirementModule(moduleId, productId);
}
verify(requirementMapper, times(1)).deleteById(2001L);
verify(moduleMapper, times(1)).deleteById(moduleId);
}
// ========== resolveModuleId 测试 ==========
@Test
void resolveModuleId_whenModuleIdIsNull_shouldReturnDefaultModuleId() {
Long defaultModuleId = 50L;
Long productId = 100L;
ProductRequirementModuleDO defaultModule = createDefaultModule(defaultModuleId, productId);
when(moduleMapper.selectByProductIdAndParentId(productId, 0L)).thenReturn(defaultModule);
Long result = requirementService.resolveModuleId(null, productId);
assertEquals(defaultModuleId, result);
}
@Test
void resolveModuleId_whenModuleIdProvided_shouldReturnSameId() {
Long moduleId = 60L;
Long productId = 100L;
// 传入有效moduleId时直接返回不查询数据库
Long result = requirementService.resolveModuleId(moduleId, productId);
assertEquals(moduleId, result);
verify(moduleMapper, never()).selectByProductIdAndParentId(any(), any());
}
// ========== isAllRequirementsModule 测试 ==========
@Test
void isAllRequirementsModule_whenRootModule_shouldReturnTrue() {
Long moduleId = 50L;
ProductRequirementModuleDO rootModule = createDefaultModule(moduleId, 100L);
when(moduleMapper.selectById(moduleId)).thenReturn(rootModule);
boolean result = requirementService.isAllRequirementsModule(moduleId);
assertEquals(true, result);
}
@Test
void isAllRequirementsModule_whenChildModule_shouldReturnFalse() {
Long moduleId = 60L;
ProductRequirementModuleDO childModule = new ProductRequirementModuleDO();
childModule.setId(moduleId);
childModule.setProductId(100L);
childModule.setParentId(50L); // parentId != 0
childModule.setModuleName("子模块");
when(moduleMapper.selectById(moduleId)).thenReturn(childModule);
boolean result = requirementService.isAllRequirementsModule(moduleId);
assertEquals(false, result);
}
@Test
void isAllRequirementsModule_whenModuleNotExists_shouldReturnFalse() {
Long moduleId = 70L;
when(moduleMapper.selectById(moduleId)).thenReturn(null);
boolean result = requirementService.isAllRequirementsModule(moduleId);
assertEquals(false, result);
}
@Test
void resolveModuleId_whenDefaultModuleNotExists_shouldThrowException() {
Long productId = 100L;
when(moduleMapper.selectByProductIdAndParentId(productId, 0L)).thenReturn(null);
ServiceException ex = assertThrows(ServiceException.class,
() -> requirementService.resolveModuleId(null, productId));
assertEquals(ErrorCodeConstants.REQUIREMENT_MODULE_NOT_EXISTS.getCode(), ex.getCode());
}
// ========== 辅助方法 ==========
private ProductRequirementDO createRequirement(Long id, Long productId, String title,
String statusCode, Long parentId, Integer reviewRequired) {
ProductRequirementDO requirement = new ProductRequirementDO();
requirement.setId(id);
requirement.setProductId(productId);
requirement.setTitle(title);
requirement.setStatusCode(statusCode);
requirement.setParentId(parentId);
requirement.setReviewRequired(reviewRequired);
requirement.setCategory("function");
requirement.setSourceType("manual");
requirement.setPriority(1);
requirement.setProposerId(2001L);
requirement.setSort(0);
return requirement;
}
private ProductRequirementModuleDO createDefaultModule(Long id, Long productId) {
ProductRequirementModuleDO module = new ProductRequirementModuleDO();
module.setId(id);
module.setProductId(productId);
module.setParentId(0L);
module.setModuleName("全部需求");
module.setRemark("自动创建的模块");
module.setSort(0);
return module;
}
private ObjectStatusTransitionDO createTransition(String actionCode, String toStatus, boolean needReason) {
ObjectStatusTransitionDO transition = new ObjectStatusTransitionDO();
transition.setActionCode(actionCode);
transition.setToStatusCode(toStatus);
transition.setNeedReason(needReason);
return transition;
}
private MockedStatic<SecurityFrameworkUtils> mockLoginUser(Long loginUserId, String nickname) {
MockedStatic<SecurityFrameworkUtils> mockedStatic = mockStatic(SecurityFrameworkUtils.class);
mockedStatic.when(SecurityFrameworkUtils::getLoginUserId).thenReturn(loginUserId);
mockedStatic.when(SecurityFrameworkUtils::getLoginUserNickname).thenReturn(nickname);
return mockedStatic;
}
}

View File

@@ -66,6 +66,43 @@ class ProductServiceImplTest extends BaseMockitoUnitTest {
private ObjectPermissionApi objectPermissionApi;
@Mock
private AdminUserApi adminUserApi;
@Mock
private com.njcn.rdms.module.project.dal.mysql.product.ProductRequirementModuleMapper requirementModuleMapper;
@Test
void createProduct_shouldCreateDefaultRequirementModule() {
Long loginUserId = 3001L;
ProductSaveReqVO reqVO = new ProductSaveReqVO();
reqVO.setDirectionCode("direction_value");
reqVO.setName("新产品");
reqVO.setManagerUserId(2001L);
reqVO.setDescription("产品描述");
when(productMapper.selectByName("新产品")).thenReturn(null);
when(adminUserApi.getUser(2001L)).thenReturn(
success(new com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO()));
try (MockedStatic<SecurityFrameworkUtils> mockedStatic = mockLoginUser(loginUserId, "测试人")) {
productService.createProduct(reqVO);
}
// 验证产品已创建
ArgumentCaptor<ProductDO> productCaptor = ArgumentCaptor.forClass(ProductDO.class);
verify(productMapper, times(1)).insert(productCaptor.capture());
ProductDO createdProduct = productCaptor.getValue();
assertEquals("新产品", createdProduct.getName());
// 验证"全部需求"模块已自动创建
ArgumentCaptor<com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementModuleDO> moduleCaptor =
ArgumentCaptor.forClass(com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementModuleDO.class);
verify(requirementModuleMapper, times(1)).insert(moduleCaptor.capture());
com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementModuleDO defaultModule = moduleCaptor.getValue();
assertEquals(0L, defaultModule.getParentId());
assertEquals(createdProduct.getId(), defaultModule.getProductId());
assertEquals("全部需求", defaultModule.getModuleName());
assertEquals("自动创建的模块", defaultModule.getRemark());
assertEquals(0, defaultModule.getSort());
}
@Test
void updateProductBaseInfo_shouldOnlyUpdateBaseInfoAndRecordFieldChanges() {