test(G4): 巡店删除 ResolveService 补 13 个契约测试(该模块此前零测试文件)
覆盖:用户校验、索引未命中/同名冲突拒绝、重复添加幂等、他人记录不可删(同文案防探测)、 匹配结果去重保序、count 空值兜底。
This commit is contained in:
+238
@@ -0,0 +1,238 @@
|
||||
package com.nanri.aiimage.modules.patroldelete.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.common.model.vo.ProductRiskCandidateVo;
|
||||
import com.nanri.aiimage.common.model.vo.ProductRiskMatchShopsVo;
|
||||
import com.nanri.aiimage.modules.patroldelete.mapper.PatrolDeleteConditionMapper;
|
||||
import com.nanri.aiimage.modules.patroldelete.mapper.PatrolDeleteShopCandidateMapper;
|
||||
import com.nanri.aiimage.modules.patroldelete.model.entity.PatrolDeleteShopCandidateEntity;
|
||||
import com.nanri.aiimage.modules.productrisk.model.dto.ProductRiskCandidateAddRequest;
|
||||
import com.nanri.aiimage.modules.productrisk.model.dto.ProductRiskMatchShopsRequest;
|
||||
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopMatchResultVo;
|
||||
import com.nanri.aiimage.modules.ziniao.service.ZiniaoShopIndexService;
|
||||
import com.nanri.aiimage.modules.ziniao.service.ZiniaoShopSwitchService;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 巡店删除的备选店铺解析(2026-09 审查 G4:该模块此前零测试文件,且出过
|
||||
* "逐国上报失败仍判成功"的事故,先从有明确契约的 ResolveService 补起)。
|
||||
*/
|
||||
class PatrolDeleteResolveServiceTest {
|
||||
|
||||
@BeforeAll
|
||||
static void initializeMybatisMetadata() {
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), ""),
|
||||
PatrolDeleteShopCandidateEntity.class);
|
||||
}
|
||||
|
||||
private final PatrolDeleteShopCandidateMapper candidateMapper = mock(PatrolDeleteShopCandidateMapper.class);
|
||||
private final PatrolDeleteConditionMapper conditionMapper = mock(PatrolDeleteConditionMapper.class);
|
||||
private final ZiniaoShopSwitchService shopSwitchService = mock(ZiniaoShopSwitchService.class);
|
||||
private final PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
|
||||
|
||||
private PatrolDeleteResolveService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
service = new PatrolDeleteResolveService(candidateMapper, conditionMapper, shopSwitchService, transactionManager);
|
||||
when(transactionManager.getTransaction(any())).thenReturn(mock(TransactionStatus.class));
|
||||
when(shopSwitchService.normalizeShopName(anyString())).thenAnswer(invocation ->
|
||||
((String) invocation.getArgument(0)).trim());
|
||||
}
|
||||
|
||||
private static ProductRiskCandidateAddRequest addRequest(Long userId, String shopName) {
|
||||
ProductRiskCandidateAddRequest request = new ProductRiskCandidateAddRequest();
|
||||
request.setUserId(userId);
|
||||
request.setShopName(shopName);
|
||||
return request;
|
||||
}
|
||||
|
||||
private static ZiniaoShopMatchResultVo matched(String shopName) {
|
||||
ZiniaoShopMatchResultVo vo = new ZiniaoShopMatchResultVo();
|
||||
vo.setMatched(true);
|
||||
vo.setShopName(shopName);
|
||||
vo.setMatchStatus(ZiniaoShopIndexService.MATCH_STATUS_MATCHED);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Test
|
||||
void listCandidatesRejectsInvalidUser() {
|
||||
assertThrows(BusinessException.class, () -> service.listCandidates(null));
|
||||
assertThrows(BusinessException.class, () -> service.listCandidates(0L));
|
||||
verify(candidateMapper, never()).selectList(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void listCandidatesMapsRowsToViews() {
|
||||
PatrolDeleteShopCandidateEntity row = new PatrolDeleteShopCandidateEntity();
|
||||
row.setId(7L);
|
||||
row.setUserId(23L);
|
||||
row.setShopName("测试店铺");
|
||||
row.setCreatedAt(LocalDateTime.now());
|
||||
when(candidateMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of(row));
|
||||
|
||||
List<ProductRiskCandidateVo> views = service.listCandidates(23L);
|
||||
|
||||
assertEquals(1, views.size());
|
||||
assertEquals(7L, views.getFirst().getId());
|
||||
assertEquals("测试店铺", views.getFirst().getShopName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void addCandidateRejectsBlankShopName() {
|
||||
when(shopSwitchService.normalizeShopName(" ")).thenReturn("");
|
||||
|
||||
assertThrows(BusinessException.class, () -> service.addCandidate(addRequest(23L, " ")));
|
||||
verify(candidateMapper, never()).insert(any(PatrolDeleteShopCandidateEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addCandidateRejectsWhenIndexMisses() {
|
||||
ZiniaoShopMatchResultVo miss = new ZiniaoShopMatchResultVo();
|
||||
miss.setMatched(false);
|
||||
miss.setMatchMessage("店铺索引未命中,无法加入备选");
|
||||
when(shopSwitchService.findIndexedStoreByName("未收录店铺", false)).thenReturn(miss);
|
||||
|
||||
BusinessException exception = assertThrows(BusinessException.class,
|
||||
() -> service.addCandidate(addRequest(23L, "未收录店铺")));
|
||||
|
||||
assertEquals("店铺索引未命中,无法加入备选", exception.getMessage());
|
||||
verify(candidateMapper, never()).insert(any(PatrolDeleteShopCandidateEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addCandidateRejectsConflictStatus() {
|
||||
ZiniaoShopMatchResultVo conflict = matched("同名店铺");
|
||||
conflict.setMatchStatus(ZiniaoShopIndexService.MATCH_STATUS_CONFLICT);
|
||||
conflict.setMatchMessage("存在多个同名店铺,请人工确认");
|
||||
when(shopSwitchService.findIndexedStoreByName("同名店铺", false)).thenReturn(conflict);
|
||||
|
||||
BusinessException exception = assertThrows(BusinessException.class,
|
||||
() -> service.addCandidate(addRequest(23L, "同名店铺")));
|
||||
|
||||
assertEquals("存在多个同名店铺,请人工确认", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void addCandidateIsIdempotentForExistingShop() {
|
||||
when(shopSwitchService.findIndexedStoreByName("已存在店铺", false)).thenReturn(matched("已存在店铺"));
|
||||
PatrolDeleteShopCandidateEntity existing = new PatrolDeleteShopCandidateEntity();
|
||||
existing.setId(31L);
|
||||
existing.setUserId(23L);
|
||||
existing.setShopName("已存在店铺");
|
||||
existing.setCreatedAt(LocalDateTime.now());
|
||||
when(candidateMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(existing);
|
||||
|
||||
ProductRiskCandidateVo vo = service.addCandidate(addRequest(23L, " 已存在店铺 "));
|
||||
|
||||
assertEquals(31L, vo.getId(), "重复添加返回既有记录");
|
||||
verify(candidateMapper, never()).insert(any(PatrolDeleteShopCandidateEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void addCandidateInsertsWhenIndexMatched() {
|
||||
when(shopSwitchService.findIndexedStoreByName("新店铺", false)).thenReturn(matched("新店铺"));
|
||||
when(candidateMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
when(candidateMapper.insert(any(PatrolDeleteShopCandidateEntity.class))).thenAnswer(invocation -> {
|
||||
PatrolDeleteShopCandidateEntity entity = invocation.getArgument(0);
|
||||
entity.setId(88L);
|
||||
return 1;
|
||||
});
|
||||
|
||||
ProductRiskCandidateVo vo = service.addCandidate(addRequest(23L, "新店铺"));
|
||||
|
||||
assertEquals(88L, vo.getId());
|
||||
assertEquals("新店铺", vo.getShopName());
|
||||
verify(candidateMapper).insert(any(PatrolDeleteShopCandidateEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteCandidateRejectsAnotherUsersRecord() {
|
||||
PatrolDeleteShopCandidateEntity other = new PatrolDeleteShopCandidateEntity();
|
||||
other.setId(5L);
|
||||
other.setUserId(99L);
|
||||
when(candidateMapper.selectById(5L)).thenReturn(other);
|
||||
|
||||
BusinessException exception = assertThrows(BusinessException.class,
|
||||
() -> service.deleteCandidate(23L, 5L));
|
||||
|
||||
assertEquals("记录不存在", exception.getMessage(), "他人记录与不存在同文案,避免探测");
|
||||
verify(candidateMapper, never()).deleteById(5L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteCandidateRemovesOwnRecord() {
|
||||
PatrolDeleteShopCandidateEntity own = new PatrolDeleteShopCandidateEntity();
|
||||
own.setId(5L);
|
||||
own.setUserId(23L);
|
||||
when(candidateMapper.selectById(5L)).thenReturn(own);
|
||||
|
||||
service.deleteCandidate(23L, 5L);
|
||||
|
||||
verify(candidateMapper).deleteById(5L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchShopsRejectsEmptyAfterNormalization() {
|
||||
ProductRiskMatchShopsRequest request = new ProductRiskMatchShopsRequest();
|
||||
request.setUserId(23L);
|
||||
request.setShopNames(List.of(" ", ""));
|
||||
|
||||
assertThrows(BusinessException.class, () -> service.matchShops(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchShopsDeduplicatesAndKeepsOrder() {
|
||||
ProductRiskMatchShopsRequest request = new ProductRiskMatchShopsRequest();
|
||||
request.setUserId(23L);
|
||||
request.setShopNames(List.of(" A ", "B", "A"));
|
||||
when(shopSwitchService.findIndexedStoreByName(anyString(), anyBoolean())).thenAnswer(invocation ->
|
||||
matched((String) invocation.getArgument(0)));
|
||||
|
||||
ProductRiskMatchShopsVo vo = service.matchShops(request);
|
||||
|
||||
assertEquals(2, vo.getItems().size(), "重复店铺名只匹配一次");
|
||||
assertEquals("A", vo.getItems().get(0).getShopName());
|
||||
assertEquals("B", vo.getItems().get(1).getShopName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void countCandidatesTreatsNullCountAsZero() {
|
||||
when(candidateMapper.selectCount(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
|
||||
assertEquals(0L, service.countCandidates(23L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void countCandidatesRejectsInvalidUser() {
|
||||
assertThrows(BusinessException.class, () -> service.countCandidates(-1L));
|
||||
assertTrue(true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user