@@ -0,0 +1,137 @@
|
||||
package com.yunhai.userbase.service.impl;
|
||||
|
||||
import com.yunhai.userbase.entity.SysUserGroupRel;
|
||||
import com.yunhai.userbase.entity.SysUserRoleRel;
|
||||
import com.yunhai.userbase.enums.StatusEnum;
|
||||
import com.yunhai.userbase.repository.SysGroupRepository;
|
||||
import com.yunhai.userbase.repository.SysUserCurrentRoleRepository;
|
||||
import com.yunhai.userbase.repository.SysUserGroupRelRepository;
|
||||
import com.yunhai.userbase.repository.SysUserRoleRelRepository;
|
||||
import com.yunhai.userbase.service.ISysGroupService;
|
||||
import com.yunhai.userbase.service.resolve.MainGroupResolver;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* IdentityService 主身份动态解析(resolveMainIdentity / resolveMainIdentityByUser)单元测试。
|
||||
* 规则:主组 → 主校 → 该校正常态身份行中 sortNo 最小、并列取 id 最小。
|
||||
*/
|
||||
class IdentityServiceTest {
|
||||
|
||||
@InjectMocks
|
||||
private IdentityService identityService;
|
||||
|
||||
@Mock
|
||||
private SysUserRoleRelRepository sysUserRoleRelRepository;
|
||||
@Mock
|
||||
private SysUserCurrentRoleRepository sysUserCurrentRoleRepository;
|
||||
@Mock
|
||||
private SysGroupRepository sysGroupRepository;
|
||||
@Mock
|
||||
private SysUserGroupRelRepository sysUserGroupRelRepository;
|
||||
@Mock
|
||||
private MainGroupResolver mainGroupResolver;
|
||||
@Mock
|
||||
private ISysGroupService sysGroupService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
private SysUserGroupRel groupRel(Integer groupId, Integer isMain) {
|
||||
SysUserGroupRel r = new SysUserGroupRel();
|
||||
r.setUserId(1);
|
||||
r.setGroupId(groupId);
|
||||
r.setIsMain(isMain);
|
||||
return r;
|
||||
}
|
||||
|
||||
private SysUserRoleRel roleRel(Integer id, Integer userId, Integer schoolId, Integer sortNo) {
|
||||
SysUserRoleRel r = new SysUserRoleRel();
|
||||
r.setId(id);
|
||||
r.setUserId(userId);
|
||||
r.setSchoolId(schoolId);
|
||||
r.setSortNo(sortNo);
|
||||
r.setStatus(StatusEnum.NORMAL);
|
||||
return r;
|
||||
}
|
||||
|
||||
// ===================== 单用户 resolveMainIdentity =====================
|
||||
|
||||
/** 无组关系 → null */
|
||||
@Test
|
||||
void resolveMainIdentity_noGroup_returnsNull() {
|
||||
when(mainGroupResolver.resolve(1)).thenReturn(Optional.empty());
|
||||
assertNull(identityService.resolveMainIdentity(1));
|
||||
}
|
||||
|
||||
/** 主校无身份行(身份行全在其它学校)→ null */
|
||||
@Test
|
||||
void resolveMainIdentity_mainSchoolHasNoRows_returnsNull() {
|
||||
when(mainGroupResolver.resolve(1)).thenReturn(Optional.of(groupRel(16, 1)));
|
||||
when(sysGroupService.resolveSchoolId(16)).thenReturn(16);
|
||||
when(sysUserRoleRelRepository.findByUserIdAndStatus(1, StatusEnum.NORMAL))
|
||||
.thenReturn(Collections.singletonList(roleRel(10, 1, 20, 0)));
|
||||
assertNull(identityService.resolveMainIdentity(1));
|
||||
}
|
||||
|
||||
/** 主校多行:sortNo 最小、并列取 id 最小 */
|
||||
@Test
|
||||
void resolveMainIdentity_picksMinSortNoThenMinId() {
|
||||
when(mainGroupResolver.resolve(1)).thenReturn(Optional.of(groupRel(16, 1)));
|
||||
when(sysGroupService.resolveSchoolId(16)).thenReturn(16);
|
||||
List<SysUserRoleRel> rels = Arrays.asList(
|
||||
roleRel(30, 1, 16, 2),
|
||||
roleRel(31, 1, 16, 1),
|
||||
roleRel(25, 1, 16, 1), // 与 31 同 sortNo → 取 id 小者
|
||||
roleRel(10, 1, 20, 0)); // 其它学校,不参与
|
||||
when(sysUserRoleRelRepository.findByUserIdAndStatus(1, StatusEnum.NORMAL)).thenReturn(rels);
|
||||
SysUserRoleRel main = identityService.resolveMainIdentity(1);
|
||||
assertEquals(Integer.valueOf(25), main.getId());
|
||||
}
|
||||
|
||||
// ===================== 批量 resolveMainIdentityByUser =====================
|
||||
|
||||
/** 批量:3 次查询路径,无主组/主校无行的用户跳过 */
|
||||
@Test
|
||||
void resolveMainIdentityByUser_skipsUsersWithoutMain() {
|
||||
Map<Integer, SysUserGroupRel> mainGroupMap = new HashMap<>();
|
||||
mainGroupMap.put(1, groupRel(16, 1)); // 用户 1:主组 16
|
||||
mainGroupMap.put(2, groupRel(20, 1)); // 用户 2:主组 20
|
||||
when(mainGroupResolver.resolveByUserIds(Arrays.asList(1, 2, 3))).thenReturn(mainGroupMap);
|
||||
Map<Integer, Integer> schoolByGroup = new HashMap<>();
|
||||
schoolByGroup.put(16, 16);
|
||||
schoolByGroup.put(20, 20);
|
||||
when(sysGroupService.resolveSchoolIds(Arrays.asList(16, 20))).thenReturn(schoolByGroup);
|
||||
when(sysUserRoleRelRepository.findByUserIdInAndStatus(Arrays.asList(1, 2, 3), StatusEnum.NORMAL))
|
||||
.thenReturn(Arrays.asList(
|
||||
roleRel(30, 1, 16, 1),
|
||||
roleRel(31, 1, 16, 2),
|
||||
roleRel(40, 2, 99, 0))); // 用户 2 主校 20 无行 → 跳过
|
||||
Map<Integer, SysUserRoleRel> result = identityService.resolveMainIdentityByUser(Arrays.asList(1, 2, 3));
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(Integer.valueOf(30), result.get(1).getId());
|
||||
}
|
||||
|
||||
/** 批量:空入参 → 空映射 */
|
||||
@Test
|
||||
void resolveMainIdentityByUser_emptyInput_returnsEmptyMap() {
|
||||
assertTrue(identityService.resolveMainIdentityByUser(Collections.emptyList()).isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package com.yunhai.userbase.service.impl;
|
||||
|
||||
import com.yunhai.platform.utils.exception.DException;
|
||||
import com.yunhai.userbase.entity.SysGroup;
|
||||
import com.yunhai.userbase.enums.GroupTypeEnum;
|
||||
import com.yunhai.userbase.repository.SysGroupRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
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.anyInt;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* SysGroupService.resolveSchoolId 单元测试(《用户身份-学校维度落地方案》§3 核心规则)。
|
||||
* <br>规则:沿祖先链(含自身)——① 最近的学校组 → 该校组 id;② 无学校组则最近的区域组 → 该区域组 id;
|
||||
* ③ 都没有 → 根节点 id。
|
||||
*/
|
||||
class SysGroupServiceIdentityTest {
|
||||
|
||||
@InjectMocks
|
||||
private SysGroupService sysGroupService;
|
||||
|
||||
@Mock
|
||||
private SysGroupRepository sysGroupRepository;
|
||||
|
||||
private SysGroup group(Integer id, Integer parentId, GroupTypeEnum type) {
|
||||
SysGroup g = new SysGroup();
|
||||
g.setId(id);
|
||||
g.setParentId(parentId);
|
||||
g.setType(type);
|
||||
return g;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
// 根节点:parent_id=0,id=100(唯一真实根)
|
||||
when(sysGroupRepository.findByParentIdOrderBySortAsc(0)).thenReturn(Collections.singletonList(group(100, 0, null)));
|
||||
}
|
||||
|
||||
private void stubChain(SysGroup... chain) {
|
||||
for (SysGroup g : chain) {
|
||||
when(sysGroupRepository.findById(g.getId())).thenReturn(Optional.of(g));
|
||||
}
|
||||
}
|
||||
|
||||
/** 组自身即学校组 -> 返回自身 id */
|
||||
@Test
|
||||
void resolveSchoolId_selfIsSchool() {
|
||||
stubChain(group(10, 100, GroupTypeEnum.SCHOOL_GROUP));
|
||||
assertEquals(Integer.valueOf(10), sysGroupService.resolveSchoolId(10));
|
||||
}
|
||||
|
||||
/** 链上最近学校组:普通组(7) -> 教师组(8) -> 学校组(10) -> 根 */
|
||||
@Test
|
||||
void resolveSchoolId_nearestSchoolInChain() {
|
||||
stubChain(
|
||||
group(7, 8, GroupTypeEnum.NORMAL_GROUP),
|
||||
group(8, 10, GroupTypeEnum.TEACHER_GROUP),
|
||||
group(10, 100, GroupTypeEnum.SCHOOL_GROUP));
|
||||
assertEquals(Integer.valueOf(10), sysGroupService.resolveSchoolId(7));
|
||||
}
|
||||
|
||||
/** 无学校组但链上有区域组:普通组(7) -> 学段组(6) -> 区域组(5) -> 根 -> 返回区域组 id */
|
||||
@Test
|
||||
void resolveSchoolId_noSchool_fallbackArea() {
|
||||
stubChain(
|
||||
group(7, 6, GroupTypeEnum.NORMAL_GROUP),
|
||||
group(6, 5, GroupTypeEnum.STAGE_GROUP),
|
||||
group(5, 100, GroupTypeEnum.AREA_GROUP));
|
||||
assertEquals(Integer.valueOf(5), sysGroupService.resolveSchoolId(7));
|
||||
}
|
||||
|
||||
/** 学校组优先于其上方的区域组:普通组 -> 学校组 -> 学段组 -> 区域组 -> 返回学校组 id */
|
||||
@Test
|
||||
void resolveSchoolId_schoolWinsOverAreaAbove() {
|
||||
stubChain(
|
||||
group(7, 10, GroupTypeEnum.NORMAL_GROUP),
|
||||
group(10, 6, GroupTypeEnum.SCHOOL_GROUP),
|
||||
group(6, 5, GroupTypeEnum.STAGE_GROUP),
|
||||
group(5, 100, GroupTypeEnum.AREA_GROUP));
|
||||
assertEquals(Integer.valueOf(10), sysGroupService.resolveSchoolId(7));
|
||||
}
|
||||
|
||||
/** 区域组自身:无学校组、最近区域组即自身 -> 返回自身 */
|
||||
@Test
|
||||
void resolveSchoolId_selfIsArea() {
|
||||
stubChain(group(5, 100, GroupTypeEnum.AREA_GROUP));
|
||||
assertEquals(Integer.valueOf(5), sysGroupService.resolveSchoolId(5));
|
||||
}
|
||||
|
||||
/** 直属根节点(无学校/区域)-> 根节点 id */
|
||||
@Test
|
||||
void resolveSchoolId_rootDirectFallbackToRoot() {
|
||||
stubChain(group(7, 100, GroupTypeEnum.NORMAL_GROUP));
|
||||
assertEquals(Integer.valueOf(100), sysGroupService.resolveSchoolId(7));
|
||||
}
|
||||
|
||||
/** 组不存在(找不到实体)-> 兜底根节点 id(与「无学校/区域回根」一致) */
|
||||
@Test
|
||||
void resolveSchoolId_missingGroupFallbackToRoot() {
|
||||
when(sysGroupRepository.findById(anyInt())).thenReturn(Optional.empty());
|
||||
assertEquals(Integer.valueOf(100), sysGroupService.resolveSchoolId(999));
|
||||
}
|
||||
|
||||
/** groupId 为空 -> 抛异常 */
|
||||
@Test
|
||||
void resolveSchoolId_nullGroupThrows() {
|
||||
assertThrows(DException.class, () -> sysGroupService.resolveSchoolId(null));
|
||||
}
|
||||
|
||||
// ===================== 批量 resolveSchoolIds =====================
|
||||
|
||||
/** 批量结果与单查一致:学校组优先 → 区域组 → 根 */
|
||||
@Test
|
||||
void resolveSchoolIds_batchMatchesSingle() {
|
||||
List<SysGroup> all = Arrays.asList(
|
||||
group(100, 0, null),
|
||||
group(5, 100, GroupTypeEnum.AREA_GROUP),
|
||||
group(10, 5, GroupTypeEnum.SCHOOL_GROUP),
|
||||
group(8, 10, GroupTypeEnum.TEACHER_GROUP),
|
||||
group(7, 8, GroupTypeEnum.NORMAL_GROUP),
|
||||
group(20, 100, GroupTypeEnum.SCHOOL_GROUP));
|
||||
when(sysGroupRepository.findAll()).thenReturn(all);
|
||||
Map<Integer, Integer> result = sysGroupService.resolveSchoolIds(Arrays.asList(7, 5, 20));
|
||||
assertEquals(3, result.size());
|
||||
assertEquals(Integer.valueOf(10), result.get(7)); // 教师组 → 学校组 10
|
||||
assertEquals(Integer.valueOf(5), result.get(5)); // 区域组自身
|
||||
assertEquals(Integer.valueOf(20), result.get(20)); // 学校组自身
|
||||
}
|
||||
|
||||
/** 组不存在(悬挂关系)→ 根节点 id(与 resolveSchoolId 单查一致) */
|
||||
@Test
|
||||
void resolveSchoolIds_missingGroupFallbackToRoot() {
|
||||
when(sysGroupRepository.findAll()).thenReturn(Collections.singletonList(group(100, 0, null)));
|
||||
Map<Integer, Integer> result = sysGroupService.resolveSchoolIds(Collections.singletonList(999));
|
||||
assertEquals(Integer.valueOf(100), result.get(999));
|
||||
}
|
||||
|
||||
/** 空入参 → 空映射 */
|
||||
@Test
|
||||
void resolveSchoolIds_emptyInputReturnsEmptyMap() {
|
||||
assertTrue(sysGroupService.resolveSchoolIds(Collections.emptyList()).isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.yunhai.userbase.service.impl;
|
||||
|
||||
import com.yunhai.userbase.entity.SysGroup;
|
||||
import com.yunhai.userbase.entity.SysUser;
|
||||
import com.yunhai.userbase.entity.SysUserCurrentRole;
|
||||
import com.yunhai.userbase.entity.SysUserGroupRel;
|
||||
import com.yunhai.userbase.entity.SysUserRoleRel;
|
||||
import com.yunhai.userbase.repository.SysGroupRepository;
|
||||
import com.yunhai.userbase.repository.SysUserCurrentRoleRepository;
|
||||
import com.yunhai.userbase.repository.SysUserGroupRelRepository;
|
||||
import com.yunhai.userbase.repository.SysUserRepository;
|
||||
import com.yunhai.userbase.repository.SysUserRoleRelRepository;
|
||||
import com.yunhai.userbase.service.ISysGroupService;
|
||||
import com.yunhai.userbase.service.resolve.MainGroupResolver;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* SysUserService 单元测试:主归属组三级降级决策(changeGroups)+
|
||||
* 转学后身份对齐回归(P3 去字段化后 ①②③ 保留段仍生效)。
|
||||
*/
|
||||
class SysUserServiceTest {
|
||||
|
||||
@InjectMocks
|
||||
private SysUserService sysUserService;
|
||||
|
||||
@Mock
|
||||
private SysUserRepository sysUserRepository;
|
||||
@Mock
|
||||
private SysGroupRepository sysGroupRepository;
|
||||
@Mock
|
||||
private SysUserGroupRelRepository sysUserGroupRelRepository;
|
||||
@Mock
|
||||
private SysUserRoleRelRepository sysUserRoleRelRepository;
|
||||
@Mock
|
||||
private SysUserCurrentRoleRepository sysUserCurrentRoleRepository;
|
||||
@Mock
|
||||
private ISysGroupService sysGroupService;
|
||||
@Mock
|
||||
private MainGroupResolver mainGroupResolver;
|
||||
@Mock
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
@Mock
|
||||
private IdentityService identityService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
private SysUserGroupRel rel(Integer groupId, Integer isMain) {
|
||||
SysUserGroupRel r = new SysUserGroupRel();
|
||||
r.setGroupId(groupId);
|
||||
r.setIsMain(isMain);
|
||||
return r;
|
||||
}
|
||||
|
||||
private SysUserRoleRel roleRel(Integer id, Integer userId, Integer schoolId) {
|
||||
SysUserRoleRel r = new SysUserRoleRel();
|
||||
r.setId(id);
|
||||
r.setUserId(userId);
|
||||
r.setSchoolId(schoolId);
|
||||
return r;
|
||||
}
|
||||
|
||||
// ===================== 主归属组三级降级决策 =====================
|
||||
|
||||
/** ① 显式 mainGroupId 优先(不受旧主组影响) */
|
||||
@Test
|
||||
void decide_whenExplicitMainGroupId_shouldUseIt() {
|
||||
Integer main = SysUserService.decideEffectiveMainGroupId(
|
||||
30, Arrays.asList(16, 30), rel(16, 1));
|
||||
assertEquals(Integer.valueOf(30), main);
|
||||
}
|
||||
|
||||
/** ② 未传时沿用旧主组(旧主组 ∈ 新集合)——「主组没被勾掉就不动」 */
|
||||
@Test
|
||||
void decide_whenNoExplicitAndOldMainInNewGroups_shouldKeepOldMain() {
|
||||
Integer main = SysUserService.decideEffectiveMainGroupId(
|
||||
null, Arrays.asList(29, 16), rel(16, 1));
|
||||
assertEquals(Integer.valueOf(16), main);
|
||||
}
|
||||
|
||||
/** ② 未传、无旧主组(脏数据)→ ③ groupIds 首项 */
|
||||
@Test
|
||||
void decide_whenNoExplicitAndNoOldMain_shouldFallbackToFirstGroup() {
|
||||
Integer main = SysUserService.decideEffectiveMainGroupId(
|
||||
null, Arrays.asList(29, 16), null);
|
||||
assertEquals(Integer.valueOf(29), main);
|
||||
}
|
||||
|
||||
/** ③ 旧主组被勾掉(不在新集合)→ groupIds 首项(现状行为兜底) */
|
||||
@Test
|
||||
void decide_whenOldMainRemovedFromNewGroups_shouldFallbackToFirstGroup() {
|
||||
Integer main = SysUserService.decideEffectiveMainGroupId(
|
||||
null, Arrays.asList(29, 30), rel(16, 1));
|
||||
assertEquals(Integer.valueOf(29), main);
|
||||
}
|
||||
|
||||
// ===================== 转学后身份对齐回归(P3 去字段化后 ①②③ 保留段) =====================
|
||||
|
||||
/**
|
||||
* changeGroups 转学场景(新组 30 替代旧组 16/20):删除 ④ is_main 置位段后,
|
||||
* ① 学校集合推导、② 孤儿身份行清理(旧校 16 身份行删)、③ 激活身份滞留清理
|
||||
* (current 指向旧校 16 删)必须仍生效;主校 30 身份行保留。
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void changeGroups_afterTransfer_shouldKeepOrphanAndCurrentStaleCleanup() {
|
||||
SysUser user = new SysUser();
|
||||
user.setId(1);
|
||||
SysGroup group30 = new SysGroup();
|
||||
group30.setId(30);
|
||||
|
||||
SysUserGroupRel oldMain = rel(16, 1);
|
||||
oldMain.setId(1);
|
||||
SysUserGroupRel oldExtra = rel(20, 0);
|
||||
oldExtra.setId(2);
|
||||
List<SysUserGroupRel> oldRels = Arrays.asList(oldMain, oldExtra);
|
||||
SysUserGroupRel newRel = new SysUserGroupRel();
|
||||
newRel.setUserId(1);
|
||||
newRel.setGroupId(30);
|
||||
List<SysUserGroupRel> newRels = Collections.singletonList(newRel);
|
||||
|
||||
when(sysUserRepository.findAllById(Collections.singletonList(1)))
|
||||
.thenReturn(Collections.singletonList(user));
|
||||
when(sysGroupRepository.findAllById(Collections.singletonList(30)))
|
||||
.thenReturn(Collections.singletonList(group30));
|
||||
// 改组前读旧关系(status 继承)→ 对齐时读新关系(schoolSet 推导)
|
||||
when(sysUserGroupRelRepository.findByUserId(1)).thenReturn(oldRels, newRels);
|
||||
when(mainGroupResolver.resolve(oldRels)).thenReturn(oldMain);
|
||||
when(sysGroupService.resolveSchoolId(30)).thenReturn(30);
|
||||
|
||||
SysUserRoleRel orphan = roleRel(10, 1, 16);
|
||||
SysUserRoleRel kept = roleRel(11, 1, 30);
|
||||
when(sysUserRoleRelRepository.findByUserId(1)).thenReturn(Arrays.asList(orphan, kept));
|
||||
|
||||
SysUserCurrentRole current = new SysUserCurrentRole();
|
||||
current.setUserId(1);
|
||||
current.setSchoolId(16);
|
||||
when(sysUserCurrentRoleRepository.findByUserId(1)).thenReturn(Optional.of(current));
|
||||
|
||||
sysUserService.changeGroups(Collections.singletonList(1), Collections.singletonList(30), 30);
|
||||
|
||||
// ① 新组关系落库:目标组 30 置主(显式 mainGroupId)
|
||||
ArgumentCaptor<List<SysUserGroupRel>> groupRelCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(sysUserGroupRelRepository).saveAll(groupRelCaptor.capture());
|
||||
List<SysUserGroupRel> savedRels = groupRelCaptor.getValue();
|
||||
assertEquals(1, savedRels.size());
|
||||
assertEquals(Integer.valueOf(1), savedRels.get(0).getIsMain());
|
||||
|
||||
// ② 孤儿身份行(旧校 16)删除、主校 30 行保留(identity 断言,规避实体 equals 恒真问题)
|
||||
ArgumentCaptor<SysUserRoleRel> deletedCaptor = ArgumentCaptor.forClass(SysUserRoleRel.class);
|
||||
verify(sysUserRoleRelRepository).delete(deletedCaptor.capture());
|
||||
assertSame(orphan, deletedCaptor.getValue());
|
||||
ArgumentCaptor<List<SysUserRoleRel>> roleRelCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(sysUserRoleRelRepository).saveAll(roleRelCaptor.capture());
|
||||
List<SysUserRoleRel> remaining = roleRelCaptor.getValue();
|
||||
assertEquals(1, remaining.size());
|
||||
assertSame(kept, remaining.get(0));
|
||||
|
||||
// ③ 激活身份滞留清理:current 指向旧校 16(不在新 schoolSet {30})→ 删除
|
||||
verify(sysUserCurrentRoleRepository).delete(current);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.yunhai.userbase.service.resolve;
|
||||
|
||||
import com.yunhai.userbase.entity.SysUserGroupRel;
|
||||
import com.yunhai.userbase.repository.SysUserGroupRelRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* MainGroupResolver 单元测试:验证统一兜底契约
|
||||
* 「isMain=1 取 id 最小;无 isMain=1 取 id 最小;空列表 → null」。
|
||||
*/
|
||||
class MainGroupResolverTest {
|
||||
|
||||
@InjectMocks
|
||||
private MainGroupResolver mainGroupResolver;
|
||||
|
||||
@Mock
|
||||
private SysUserGroupRelRepository sysUserGroupRelRepository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
/** 构造关联行(id、userId、groupId、isMain) */
|
||||
private SysUserGroupRel rel(Integer id, Integer userId, Integer groupId, Integer isMain) {
|
||||
SysUserGroupRel r = new SysUserGroupRel();
|
||||
r.setId(id);
|
||||
r.setUserId(userId);
|
||||
r.setGroupId(groupId);
|
||||
r.setIsMain(isMain);
|
||||
return r;
|
||||
}
|
||||
|
||||
// ===================== 纯函数 resolve(List) =====================
|
||||
|
||||
/** 空列表 → null(resolve(null) 显式转 List 消除重载歧义) */
|
||||
@Test
|
||||
void resolve_whenEmptyList_shouldReturnNull() {
|
||||
assertNull(mainGroupResolver.resolve(Collections.emptyList()));
|
||||
assertNull(mainGroupResolver.resolve((List<SysUserGroupRel>) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolve_whenNoMain_shouldPickMinId() {
|
||||
// 全部非主:取 id 最小(第一条顺序依赖被消除)
|
||||
List<SysUserGroupRel> rels = Arrays.asList(
|
||||
rel(7, 1, 30, 0),
|
||||
rel(3, 1, 16, 0),
|
||||
rel(9, 1, 29, 0));
|
||||
SysUserGroupRel main = mainGroupResolver.resolve(rels);
|
||||
assertEquals(Integer.valueOf(3), main.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolve_whenMultipleMain_shouldPickMinIdAmongMains() {
|
||||
// 脏数据多主:isMain=1 中取 id 最小(id=3 优先于 id=9)
|
||||
List<SysUserGroupRel> rels = Arrays.asList(
|
||||
rel(5, 1, 30, 0),
|
||||
rel(9, 1, 29, 1),
|
||||
rel(3, 1, 16, 1));
|
||||
SysUserGroupRel main = mainGroupResolver.resolve(rels);
|
||||
assertEquals(Integer.valueOf(3), main.getId());
|
||||
assertEquals(Integer.valueOf(16), main.getGroupId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolve_whenNullGroupId_shouldSkipDirtyRow() {
|
||||
// groupId 为 null 的关系行(脏数据)不参与选择
|
||||
List<SysUserGroupRel> rels = Arrays.asList(
|
||||
rel(1, 1, null, 1),
|
||||
rel(10, 1, 29, 0));
|
||||
SysUserGroupRel main = mainGroupResolver.resolve(rels);
|
||||
assertEquals(Integer.valueOf(10), main.getId());
|
||||
|
||||
// 全部为 null groupId → null
|
||||
assertNull(mainGroupResolver.resolve(Collections.singletonList(rel(1, 1, null, 1))));
|
||||
}
|
||||
|
||||
// ===================== 单用户 resolve(Integer) =====================
|
||||
|
||||
@Test
|
||||
void resolve_byUserId_whenNoRels_shouldReturnEmpty() {
|
||||
when(sysUserGroupRelRepository.findByUserId(1)).thenReturn(Collections.emptyList());
|
||||
assertFalse(mainGroupResolver.resolve(1).isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolve_byUserId_shouldDelegateToPureFunction() {
|
||||
when(sysUserGroupRelRepository.findByUserId(1)).thenReturn(Arrays.asList(
|
||||
rel(7, 1, 30, 0),
|
||||
rel(2, 1, 16, 1)));
|
||||
Optional<SysUserGroupRel> main = mainGroupResolver.resolve(1);
|
||||
assertTrue(main.isPresent());
|
||||
assertEquals(Integer.valueOf(2), main.get().getId());
|
||||
}
|
||||
|
||||
// ===================== 批量 resolveByUserIds =====================
|
||||
|
||||
@Test
|
||||
void resolveByUserIds_whenEmptyInput_shouldReturnEmptyMap() {
|
||||
assertTrue(mainGroupResolver.resolveByUserIds(Collections.emptyList()).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveByUserIds_shouldResolvePerUserAndSkipUsersWithoutRels() {
|
||||
when(sysUserGroupRelRepository.findByUserIdIn(Arrays.asList(1, 2, 3))).thenReturn(Arrays.asList(
|
||||
rel(5, 1, 30, 0),
|
||||
rel(1, 1, 16, 1),
|
||||
rel(2, 2, 29, 0)));
|
||||
Map<Integer, SysUserGroupRel> result = mainGroupResolver.resolveByUserIds(Arrays.asList(1, 2, 3));
|
||||
assertEquals(2, result.size());
|
||||
// 用户 1:多主/无主混合,取 isMain=1 中 id 最小
|
||||
assertEquals(Integer.valueOf(1), result.get(1).getId());
|
||||
// 用户 2:无主兜底 id 最小
|
||||
assertEquals(Integer.valueOf(2), result.get(2).getId());
|
||||
// 用户 3:无组关系,不出现
|
||||
assertFalse(result.containsKey(3));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user