暂存提交

This commit is contained in:
2026-07-30 18:01:36 +08:00
commit 2f4e159356
368 changed files with 20370 additions and 0 deletions
@@ -0,0 +1,20 @@
package com.baiyue.iam.controller;
import org.junit.jupiter.api.Test;
import org.springframework.web.util.pattern.PathPatternParser;
import org.springframework.http.server.PathContainer;
import static org.assertj.core.api.Assertions.assertThat;
/** 防止成员数字主键路由再次拦截固定的角色分配路径。 */
class MerchantMemberRouteTest {
private final PathPatternParser parser = new PathPatternParser();
@Test
void 成员编辑路由只接受数字主键() {
var pattern = parser.parse("/api/merchant/v1/iam/members/{membershipId:\\d+}");
assertThat(pattern.matches(PathContainer.parsePath("/api/merchant/v1/iam/members/123"))).isTrue();
assertThat(pattern.matches(PathContainer.parsePath("/api/merchant/v1/iam/members/roles"))).isFalse();
}
}
@@ -0,0 +1,64 @@
package com.baiyue.iam.service;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.iam.dto.AuthorizationDtos.SnapshotRequest;
import com.baiyue.iam.mapper.AuthorizationQueryMapper;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class AuthorizationSnapshotServiceTest {
@Test
void loadsRolesAndPermissionsForActiveMerchantIdentity() {
AuthorizationQueryMapper mapper = mock(AuthorizationQueryMapper.class);
when(mapper.countActiveIdentity(42L, 7L, 9L)).thenReturn(1);
when(mapper.findRoles(42L, 7L, 9L, "MERCHANT"))
.thenReturn(List.of("ADMIN", "OPERATOR"));
when(mapper.findPermissions(42L, 7L, 9L, "MERCHANT"))
.thenReturn(List.of("order:read", "order:create"));
var result = service(mapper)
.load(new SnapshotRequest(42L, 7L, 9L, "merchant"));
assertThat(result.getAppCode()).isEqualTo("MERCHANT");
assertThat(result.getClientType()).isEqualTo("MERCHANT");
assertThat(result.getRoles()).containsExactlyInAnyOrder("ADMIN", "OPERATOR");
assertThat(result.getPermissions()).containsExactlyInAnyOrder("order:read", "order:create");
}
@Test
void rejectsPlatformIdentityOutsidePlatformTenant() {
AuthorizationQueryMapper mapper = mock(AuthorizationQueryMapper.class);
assertThatThrownBy(() -> service(mapper)
.load(new SnapshotRequest(42L, 7L, 9L, "PLATFORM")))
.isInstanceOfSatisfying(BizException.class,
exception -> assertThat(exception.getResultCode()).isEqualTo(ResultCode.FORBIDDEN));
}
@Test
void rejectsInactiveMembership() {
AuthorizationQueryMapper mapper = mock(AuthorizationQueryMapper.class);
when(mapper.countActiveIdentity(42L, 7L, 9L)).thenReturn(0);
assertThatThrownBy(() -> service(mapper)
.load(new SnapshotRequest(42L, 7L, 9L, "MERCHANT")))
.isInstanceOfSatisfying(BizException.class,
exception -> assertThat(exception.getResultCode()).isEqualTo(ResultCode.FORBIDDEN));
verify(mapper).countActiveIdentity(42L, 7L, 9L);
}
private AuthorizationSnapshotService service(AuthorizationQueryMapper mapper) {
AuthorizationSnapshotService service = new AuthorizationSnapshotService();
ReflectionTestUtils.setField(service, "mapper", mapper);
return service;
}
}
@@ -0,0 +1,94 @@
package com.baiyue.iam.service;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.iam.client.AuthAccountClient;
import com.baiyue.iam.dto.MemberDtos.*;
import com.baiyue.iam.entity.IamMembership;
import com.baiyue.iam.entity.IamUser;
import com.baiyue.iam.mapper.IamRoleMapper;
import com.baiyue.iam.repository.MemberRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
class MemberServiceTest {
private final MemberRepository repository = mock(MemberRepository.class);
private final IamRoleMapper roleMapper = mock(IamRoleMapper.class);
private final AuthAccountClient auth = mock(AuthAccountClient.class);
private final MemberService service = new MemberService(repository, roleMapper, auth, "internal-token");
private IamMembership member;
private IamUser user;
@BeforeEach void setUp() {
member = new IamMembership(); member.setId(20L); member.setTenantId(100L); member.setUserId(30L);
member.setMemberType("EMPLOYEE"); member.setStatus("ACTIVE");
user = new IamUser(); user.setId(30L); user.setAuthUserId(40L); user.setDisplayName("测试成员");
when(repository.findByTenantAndId(100L, 20L)).thenReturn(member);
when(repository.findUser(30L)).thenReturn(user);
when(repository.findRoleIds(100L, 20L)).thenReturn(List.of(50L));
}
@Test void 成员详情返回联系方式和已有角色() {
user.setPhone("13800138000"); user.setEmail("member@example.com");
MemberResponse result = service.detail(100L, 20L);
assertEquals("测试成员", result.displayName());
assertEquals("13800138000", result.phone());
assertEquals(List.of(50L), result.roleIds());
verifyNoInteractions(auth);
}
@Test void 跨租户查询被拒绝() {
when(repository.findByTenantAndId(200L, 20L)).thenReturn(null);
assertThrows(BizException.class, () -> service.detail(200L, 20L));
}
@Test void 企业所有者不可停用() {
member.setMemberType("OWNER");
BizException exception = assertThrows(BizException.class, () -> service.changeStatus(100L, 20L,
new ChangeMemberStatusRequest("DISABLED"), 20L));
assertEquals(ResultCode.FORBIDDEN, exception.getResultCode());
verify(auth, never()).status(anyString(), anyLong(), any());
}
@Test void 停用成员同步认证账号并保存本地状态() {
when(auth.status(anyString(), eq(40L), any())).thenReturn(ApiResponse.success(null));
MemberResponse result = service.changeStatus(100L, 20L,
new ChangeMemberStatusRequest("DISABLED"), 99L);
assertEquals("DISABLED", result.status());
verify(repository).save(member);
verify(repository).saveUser(user);
}
@Test void 认证服务失败时不得写入本地状态() {
when(auth.status(anyString(), eq(40L), any())).thenThrow(new RuntimeException("网络失败"));
assertThrows(RuntimeException.class, () -> service.changeStatus(100L, 20L,
new ChangeMemberStatusRequest("DISABLED"), 99L));
verify(repository, never()).save(member);
}
@Test void 企业所有者不能通过成员编辑接口降级或变更角色() {
member.setMemberType("OWNER");
UpdateMemberRequest request = new UpdateMemberRequest("所有者", null, null,
"EMPLOYEE", null, List.of());
assertThrows(BizException.class, () -> service.update(100L, 20L, request));
verify(repository, never()).replaceRoles(anyLong(), anyLong(), anyList());
}
@Test void 企业所有者密码不能被成员管理员重置() {
member.setMemberType("OWNER");
assertThrows(BizException.class, () -> service.resetPassword(100L, 20L,
new ResetMemberPasswordRequest("OwnerNew123")));
verify(auth, never()).reset(anyString(), anyLong(), any());
}
}
@@ -0,0 +1,31 @@
package com.baiyue.iam.service;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.iam.dto.ProfileDtos.UpdatePersonalProfileRequest;
import com.baiyue.iam.entity.IamMembership;
import com.baiyue.iam.entity.IamUser;
import com.baiyue.iam.repository.MemberRepository;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class PersonalProfileServiceTest {
private final MemberRepository repository = mock(MemberRepository.class);
private final PersonalProfileService service = new PersonalProfileService(repository);
@Test void 只能修改当前租户中的本人资料() {
IamMembership membership = new IamMembership(); membership.setUserId(3L); membership.setStatus("ACTIVE");
IamUser user = new IamUser(); user.setId(3L); user.setAuthUserId(2L);
when(repository.findByTenantAndAuthUser(1L, 2L)).thenReturn(membership);
when(repository.findUser(3L)).thenReturn(user);
var result = service.update(1L, 2L, "tester",
new UpdatePersonalProfileRequest("新姓名", "13800138000", "a@example.com"));
assertEquals("新姓名", result.displayName());
verify(repository).saveUser(user);
}
@Test void 跨租户身份被拒绝() {
assertThrows(BizException.class, () -> service.get(9L, 2L, "tester"));
}
}
@@ -0,0 +1,282 @@
package com.baiyue.iam.service;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.iam.dto.RbacDtos.AssignRolesRequest;
import com.baiyue.iam.dto.RbacDtos.GrantPermissionsRequest;
import com.baiyue.iam.dto.RbacDtos.CreateRoleRequest;
import com.baiyue.iam.dto.RbacDtos.UpdateRoleRequest;
import com.baiyue.iam.dto.RbacDtos.ChangeRoleStatusRequest;
import com.baiyue.iam.dto.RbacDtos.CreatePermissionRequest;
import com.baiyue.iam.entity.IamMembership;
import com.baiyue.iam.entity.IamPermission;
import com.baiyue.iam.entity.IamRole;
import com.baiyue.iam.repository.MemberRepository;
import com.baiyue.iam.repository.RbacRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class RbacServiceTest {
@Mock private RbacRepository repository;
@Mock private MemberRepository memberRepository;
private RbacService service;
@BeforeEach
void setUp() {
service = new RbacService();
ReflectionTestUtils.setField(service, "repository", repository);
ReflectionTestUtils.setField(service, "memberRepository", memberRepository);
}
@Test
void 角色列表返回当前权限用于安全回显() {
IamRole role = role(11L, 100L, "MERCHANT", "CUSTOM");
when(repository.findRoles(100L, "MERCHANT")).thenReturn(List.of(role));
when(repository.findRolePermissionIds(100L, 11L)).thenReturn(Set.of(21L, 22L));
var result = service.roles(100L, "MERCHANT");
assertEquals(Set.of(21L, 22L), result.get(0).permissionIds());
assertEquals(100L, result.get(0).tenantId());
}
@Test
void 权限列表同码时优先当前租户并去除全局模板重复项() {
IamPermission global = permission(21L, 0L, "merchant:member:list");
IamPermission tenant = permission(22L, 100L, "merchant:member:list");
IamPermission fallback = permission(23L, 0L, "merchant:member:status");
when(repository.findPermissions(100L, "MERCHANT"))
.thenReturn(List.of(global, fallback, tenant));
var result = service.permissions(100L, "MERCHANT");
assertEquals(2, result.size());
assertEquals(22L, result.get(0).getId());
assertEquals(23L, result.get(1).getId());
}
@Test
void 跨租户角色访问被拒绝() {
when(repository.findRole(11L, 200L, "MERCHANT")).thenReturn(null);
BizException exception = assertThrows(BizException.class,
() -> service.role(200L, "MERCHANT", 11L));
assertEquals(ResultCode.FORBIDDEN, exception.getResultCode());
}
@Test
void 系统角色禁止删除() {
when(repository.findRole(11L, 0L, "PLATFORM"))
.thenReturn(role(11L, 0L, "PLATFORM", "SYSTEM"));
BizException exception = assertThrows(BizException.class,
() -> service.deleteRole(0L, "PLATFORM", 11L));
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
verify(repository, never()).deleteRole(anyLong(), any(), anyLong());
}
@Test
void 已分配成员的自定义角色禁止删除() {
when(repository.findRole(11L, 100L, "MERCHANT"))
.thenReturn(role(11L, 100L, "MERCHANT", "CUSTOM"));
when(repository.isRoleAssigned(100L, 11L)).thenReturn(true);
BizException exception = assertThrows(BizException.class,
() -> service.deleteRole(100L, "MERCHANT", 11L));
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
verify(repository, never()).deleteRole(anyLong(), any(), anyLong());
}
@Test
void 空权限集合可以明确清空授权() {
when(repository.findRole(11L, 100L, "MERCHANT"))
.thenReturn(role(11L, 100L, "MERCHANT", "CUSTOM"));
service.grantPermissions(100L, "MERCHANT", new GrantPermissionsRequest(11L, Set.of()));
verify(repository).replaceRolePermissions(100L, 11L, Set.of());
}
@Test
void 空角色集合可以明确清空成员授权() {
IamMembership membership = new IamMembership();
membership.setId(31L);
membership.setTenantId(100L);
when(memberRepository.findById(31L)).thenReturn(membership);
service.assignRoles(100L, "MERCHANT", new AssignRolesRequest(31L, Set.of()));
verify(repository).replaceMemberRoles(100L, "MERCHANT", 31L, Set.of());
}
@Test
void 成员角色回显只返回当前应用域角色() {
IamMembership membership = new IamMembership();
membership.setId(31L);
membership.setTenantId(100L);
when(memberRepository.findById(31L)).thenReturn(membership);
when(repository.findMemberRoleIds(100L, 31L)).thenReturn(Set.of(11L, 12L));
when(repository.findRole(11L, 100L, "MERCHANT"))
.thenReturn(role(11L, 100L, "MERCHANT", "CUSTOM"));
when(repository.findRole(12L, 100L, "MERCHANT")).thenReturn(null);
var result = service.memberRoles(100L, "MERCHANT", 31L);
assertEquals(Set.of(11L), result.roleIds());
}
@Test
void 同一租户同一应用角色编码重复时返回冲突() {
when(repository.existsRoleCode(100L, "MERCHANT", "ORDER_ADMIN", null)).thenReturn(true);
BizException exception = assertThrows(BizException.class, () -> service.createRole(
100L, "MERCHANT", new CreateRoleRequest("ORDER_ADMIN", "订单管理员", null)));
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
verify(repository, never()).insertRole(any());
}
@Test
void 停用角色不得继续分配给成员() {
IamMembership membership = new IamMembership();
membership.setId(31L);
membership.setTenantId(100L);
when(memberRepository.findById(31L)).thenReturn(membership);
IamRole disabled = role(11L, 100L, "MERCHANT", "CUSTOM");
disabled.setStatus("DISABLED");
when(repository.findRole(11L, 100L, "MERCHANT")).thenReturn(disabled);
BizException exception = assertThrows(BizException.class, () -> service.assignRoles(
100L, "MERCHANT", new AssignRolesRequest(31L, Set.of(11L))));
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
verify(repository, never()).replaceMemberRoles(anyLong(), any(), anyLong(), any());
}
@Test
void 自定义角色可以编辑和停用并保持权限回显() {
IamRole role = role(11L, 100L, "MERCHANT", "CUSTOM");
when(repository.findRole(11L, 100L, "MERCHANT")).thenReturn(role);
when(repository.findRolePermissionIds(100L, 11L)).thenReturn(Set.of(21L));
var updated = service.updateRole(100L, "MERCHANT", 11L,
new UpdateRoleRequest("新名称", "新说明"));
var disabled = service.changeStatus(100L, "MERCHANT", 11L,
new ChangeRoleStatusRequest("DISABLED"));
assertEquals("新名称", updated.roleName());
assertEquals(Set.of(21L), updated.permissionIds());
assertEquals("DISABLED", disabled.status());
verify(repository, times(2)).updateRole(role);
}
@Test
void 未使用的自定义角色可以安全删除() {
when(repository.findRole(11L, 100L, "MERCHANT"))
.thenReturn(role(11L, 100L, "MERCHANT", "CUSTOM"));
when(repository.isRoleAssigned(100L, 11L)).thenReturn(false);
service.deleteRole(100L, "MERCHANT", 11L);
verify(repository).deleteRole(100L, "MERCHANT", 11L);
}
@Test
void 企业所有者角色不能通过普通成员授权接口修改() {
IamMembership owner = new IamMembership();
owner.setId(31L);
owner.setTenantId(100L);
owner.setMemberType("OWNER");
when(memberRepository.findById(31L)).thenReturn(owner);
BizException exception = assertThrows(BizException.class, () -> service.assignRoles(
100L, "MERCHANT", new AssignRolesRequest(31L, Set.of())));
assertEquals(ResultCode.FORBIDDEN, exception.getResultCode());
verify(repository, never()).replaceMemberRoles(anyLong(), any(), anyLong(), any());
}
@Test
void 系统角色权限不能通过普通授权接口修改() {
when(repository.findRole(11L, 0L, "PLATFORM"))
.thenReturn(role(11L, 0L, "PLATFORM", "SYSTEM"));
BizException exception = assertThrows(BizException.class, () -> service.grantPermissions(
0L, "PLATFORM", new GrantPermissionsRequest(11L, Set.of())));
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
verify(repository, never()).replaceRolePermissions(anyLong(), anyLong(), any());
}
@Test
void 企业所有者系统角色不能分配给普通成员() {
IamMembership member = new IamMembership();
member.setId(31L);
member.setTenantId(100L);
member.setMemberType("EMPLOYEE");
when(memberRepository.findById(31L)).thenReturn(member);
IamRole ownerRole = role(11L, 100L, "MERCHANT", "SYSTEM");
ownerRole.setRoleCode("MERCHANT_OWNER");
when(repository.findRole(11L, 100L, "MERCHANT")).thenReturn(ownerRole);
BizException exception = assertThrows(BizException.class, () -> service.assignRoles(
100L, "MERCHANT", new AssignRolesRequest(31L, Set.of(11L))));
assertEquals(ResultCode.FORBIDDEN, exception.getResultCode());
verify(repository, never()).replaceMemberRoles(anyLong(), any(), anyLong(), any());
}
@Test
void 权限编码重复时返回稳定冲突且不写数据库() {
when(repository.existsPermissionCode(100L, "MERCHANT", "merchant:role:test"))
.thenReturn(true);
CreatePermissionRequest request = new CreatePermissionRequest(
"BUTTON", "merchant:role:test", "测试按钮", 0L, null, null, 1);
BizException exception = assertThrows(BizException.class,
() -> service.createPermission(100L, "MERCHANT", request));
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
verify(repository, never()).insertPermission(any());
}
private IamRole role(Long id, Long tenantId, String appCode, String roleType) {
IamRole role = new IamRole();
role.setId(id);
role.setTenantId(tenantId);
role.setAppCode(appCode);
role.setRoleCode("ROLE_" + id);
role.setRoleName("测试角色");
role.setRoleType(roleType);
role.setStatus("ACTIVE");
return role;
}
private IamPermission permission(Long id, Long tenantId, String code) {
IamPermission permission = new IamPermission();
permission.setId(id);
permission.setTenantId(tenantId);
permission.setAppCode("MERCHANT");
permission.setPermissionCode(code);
permission.setPermissionName(code);
permission.setPermissionType("BUTTON");
permission.setStatus("ACTIVE");
return permission;
}
}