Files
baiyue-firstleg/auth/src/test/java/com/baiyue/auth/service/AccountManagementServiceTest.java
T
2026-08-08 10:31:44 +08:00

218 lines
9.3 KiB
Java

package com.baiyue.auth.service;
import com.baiyue.auth.dto.AccountManagementDtos.ChangePasswordRequest;
import com.baiyue.auth.dto.AccountManagementDtos.ForcedPasswordChangeRequest;
import com.baiyue.auth.dto.AccountManagementDtos.CreateAccountRequest;
import com.baiyue.auth.dto.AccountManagementDtos.UpdateAccountProfileRequest;
import com.baiyue.auth.dto.AccountManagementDtos.UpdateAccountStatusRequest;
import com.baiyue.auth.dto.AccountManagementDtos.UpdateAccountScopeRequest;
import com.baiyue.auth.entity.AuthAccount;
import com.baiyue.auth.repository.AuthAccountRepository;
import com.baiyue.common.core.exception.BizException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import jakarta.annotation.Resource;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.time.LocalDateTime;
import java.util.Set;
import java.util.List;
@DisplayName("账号管理服务")
@SpringJUnitConfig(classes = {AccountManagementService.class, AccountManagementServiceTest.TestConfig.class})
class AccountManagementServiceTest {
@MockBean private AuthAccountRepository repository;
@MockBean private TokenSessionStore sessions;
@Resource private PasswordEncoder encoder;
@Resource private AccountManagementService service;
private AuthAccount account;
@BeforeEach void setUp() {
account = new AuthAccount(); account.setId(10L); account.setPasswordHash(encoder.encode("OldPass1"));
account.setTokenVersion(2); account.setAccountStatus("ACTIVE");
when(repository.findById(10L)).thenReturn(account);
}
@Test void changePasswordValidatesCurrentPasswordAndRevokesAllSessions() {
service.changePassword(10L, new ChangePasswordRequest("OldPass1", "NewPass2"));
assertTrue(encoder.matches("NewPass2", account.getPasswordHash()));
assertEquals(3, account.getTokenVersion());
verify(repository).save(account);
verify(sessions).revokeAll(10L);
}
@Test void changePasswordDoesNotPersistWhenCurrentPasswordIsInvalid() {
assertThrows(BizException.class,
() -> service.changePassword(10L, new ChangePasswordRequest("WrongPass1", "NewPass2")));
verify(repository, never()).save(any());
verify(sessions, never()).revokeAll(any());
}
@Test void forcedPasswordChangeRequiresResetFlagAndClearsIt() {
account.setPasswordChangeRequired(true);
service.forcedChangePassword(10L, new ForcedPasswordChangeRequest("NewPass2"));
assertTrue(encoder.matches("NewPass2", account.getPasswordHash()));
assertFalse(account.getPasswordChangeRequired());
verify(repository).save(account);
verify(sessions).revokeAll(10L);
}
@Test void normalPasswordChangeIsRejectedUntilForcedChangeIsCompleted() {
account.setPasswordChangeRequired(true);
assertThrows(BizException.class,
() -> service.changePassword(10L, new ChangePasswordRequest("OldPass1", "NewPass2")));
verify(repository, never()).save(any());
}
@Test void disableAccountRevokesAllSessions() {
service.updateStatus(10L, new UpdateAccountStatusRequest("DISABLED"));
assertEquals("DISABLED", account.getAccountStatus());
verify(sessions).revokeAll(10L);
}
@Test void createPlatformAccountPersistsPlatformAppCodeAndPasswordHashOnly() {
when(repository.findById(20L)).thenReturn(null);
when(repository.existsByUsername("platform.user")).thenReturn(false);
var result = service.create(new CreateAccountRequest(20L, "platform.user", "Password1",
"平台用户", 0L, 30L, "PLATFORM"));
assertEquals("platform.user", result.username());
var captor = org.mockito.ArgumentCaptor.forClass(AuthAccount.class);
verify(repository).insert(captor.capture());
assertEquals("PLATFORM", captor.getValue().getDefaultAppCode());
assertTrue(encoder.matches("Password1", captor.getValue().getPasswordHash()));
assertNotEquals("Password1", captor.getValue().getPasswordHash());
}
@Test void usernameAvailabilityUsesTheAuthAccountRepositoryAsTheFactSource() {
when(repository.existsByUsername("13800138000")).thenReturn(true);
assertFalse(service.usernameAvailable("13800138000"));
assertTrue(service.usernameAvailable("13900139000"));
}
@Test void createDisabledAccountAndSummaryExposeLastLoginAndScope() {
when(repository.findById(20L)).thenReturn(null);
when(repository.existsByUsername("merchant.user")).thenReturn(false);
var created = service.create(new CreateAccountRequest(20L, "merchant.user", "Password1",
"企业用户", 10001L, 30L, "MERCHANT", "DISABLED"));
var captor = org.mockito.ArgumentCaptor.forClass(AuthAccount.class);
verify(repository).insert(captor.capture());
AuthAccount stored = captor.getValue();
stored.setLastLoginAt(LocalDateTime.of(2026, 6, 1, 10, 30));
when(repository.findById(20L)).thenReturn(stored);
var summary = service.summary(20L);
assertEquals("DISABLED", created.status());
assertEquals(10001L, summary.tenantId());
assertEquals(30L, summary.membershipId());
assertEquals(LocalDateTime.of(2026, 6, 1, 10, 30), summary.lastLoginAt());
}
@Test void updateScopeChangesTenantAndRevokesExistingSessions() {
account.setDefaultTenantId(10001L); account.setOperatorTenantId(10001L);
account.setDefaultMembershipId(30L); account.setDefaultAppCode("MERCHANT");
service.updateScope(10L, new UpdateAccountScopeRequest(10002L, 30L, "MERCHANT"));
assertEquals(10002L, account.getDefaultTenantId());
assertEquals(10002L, account.getOperatorTenantId());
verify(repository).save(account);
verify(sessions).revokeAll(10L);
}
@Test void createAccountRetriesIdempotentlyForSameAccountAndOwner() {
account.setUsername("platform.user"); account.setDisplayName("平台用户");
account.setDefaultTenantId(0L); account.setOperatorTenantId(0L);
account.setDefaultMembershipId(30L); account.setDefaultAppCode("PLATFORM"); account.setDeleted(0);
var result = service.create(new CreateAccountRequest(10L, "platform.user", "Password1",
"平台用户", 0L, 30L, "PLATFORM"));
assertEquals(10L, result.accountId());
verify(repository, never()).insert(any());
}
@Test void synchronizeDisplayNameRevokesExistingSessions() {
account.setDisplayName("旧名称");
service.updateProfile(10L, new UpdateAccountProfileRequest("新名称"));
assertEquals("新名称", account.getDisplayName());
verify(repository).save(account);
verify(sessions).revokeAll(10L);
}
@Test void rbacChangeRevokesAccessAndRefreshSessionsAndBumpsTokenVersions() {
AuthAccount second = new AuthAccount();
second.setId(11L); second.setTokenVersion(7); second.setAccountStatus("ACTIVE");
when(repository.findById(11L)).thenReturn(second);
service.revokeSessions(Set.of(10L, 11L));
assertEquals(3, account.getTokenVersion());
assertEquals(8, second.getTokenVersion());
verify(repository).save(account);
verify(repository).save(second);
verify(sessions).revokeAll(10L);
verify(sessions).revokeAll(11L);
}
@Test void archiveTenantDisablesEveryMerchantAccountAndRevokesSessions() {
AuthAccount owner = merchantAccount(20L, 10001L, "ACTIVE", 3);
AuthAccount member = merchantAccount(21L, 10001L, "DISABLED", null);
when(repository.findMerchantByTenant(10001L)).thenReturn(List.of(owner, member));
service.archiveTenant(10001L);
assertEquals("DISABLED", owner.getAccountStatus());
assertEquals(4, owner.getTokenVersion());
assertEquals("DISABLED", member.getAccountStatus());
assertEquals(1, member.getTokenVersion());
verify(repository).save(owner);
verify(repository).save(member);
verify(sessions).revokeAll(20L);
verify(sessions).revokeAll(21L);
}
@Test void archiveTenantIsSuccessfulWhenCustomerHasNoAccounts() {
when(repository.findMerchantByTenant(10001L)).thenReturn(List.of());
service.archiveTenant(10001L);
verify(repository, never()).save(any());
verify(sessions, never()).revokeAll(any());
}
private AuthAccount merchantAccount(long id, long tenantId, String status, Integer tokenVersion) {
AuthAccount value = new AuthAccount();
value.setId(id);
value.setDefaultTenantId(tenantId);
value.setDefaultAppCode("MERCHANT");
value.setAccountStatus(status);
value.setTokenVersion(tokenVersion);
return value;
}
@Configuration
static class TestConfig {
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(4);
}
}
}