fix: 修复强制重置密码流程与表单状态

- 修正强制改密跳转及免旧密码的服务端校验

- 统一双端表单错误布局与更新按钮加载状态

- 补充认证服务和强制改密路由测试
This commit is contained in:
2026-07-30 15:03:53 +08:00
parent 03f45212ef
commit 1b40829135
20 changed files with 535 additions and 117 deletions

View File

@@ -0,0 +1,176 @@
package tech.easyflow.auth.service.impl;
import cn.hutool.crypto.digest.BCrypt;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.service.SysAccountService;
import java.lang.reflect.Field;
import java.math.BigInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* {@link AuthServiceImpl} 密码修改规则测试。
*/
public class AuthServiceImplPasswordTest {
private static final BigInteger ACCOUNT_ID = BigInteger.valueOf(10);
private static final String CURRENT_PASSWORD = "Current!123";
private static final String NEW_PASSWORD = "Changed!456";
/**
* 验证强制重置状态下Web 会话无需提交当前密码即可更新。
*
* @throws Exception 注入测试依赖失败
*/
@Test
public void shouldAllowForcedResetWithoutCurrentPasswordForWebSession() throws Exception {
SysAccountService accountService = mock(SysAccountService.class);
AuthServiceImpl authService = createService(accountService);
SysAccount account = buildAccount(true);
when(accountService.getById(ACCOUNT_ID)).thenReturn(account);
when(accountService.updateById(any(SysAccount.class))).thenReturn(true);
authService.updateOwnPassword(
ACCOUNT_ID,
null,
NEW_PASSWORD,
NEW_PASSWORD,
AuthLoginSessionPolicy.WEB_DEVICE
);
ArgumentCaptor<SysAccount> updateCaptor = ArgumentCaptor.forClass(SysAccount.class);
verify(accountService).updateById(updateCaptor.capture());
SysAccount update = updateCaptor.getValue();
assertTrue(BCrypt.checkpw(NEW_PASSWORD, update.getPassword()));
assertFalse(update.getPasswordResetRequired());
assertEquals(ACCOUNT_ID, update.getModifiedBy());
}
/**
* 验证普通改密仍需校验当前密码。
*
* @throws Exception 注入测试依赖失败
*/
@Test
public void shouldRequireCurrentPasswordForRegularChange() throws Exception {
SysAccountService accountService = mock(SysAccountService.class);
AuthServiceImpl authService = createService(accountService);
when(accountService.getById(ACCOUNT_ID)).thenReturn(buildAccount(false));
BusinessException exception = expectBusinessException(() -> authService.updateOwnPassword(
ACCOUNT_ID,
"Wrong!123",
NEW_PASSWORD,
NEW_PASSWORD,
AuthLoginSessionPolicy.WEB_DEVICE
));
assertEquals(400, exception.getHttpStatus());
assertEquals("密码不正确", exception.getMessage());
verify(accountService, never()).updateById(any(SysAccount.class));
}
/**
* 验证 API Key 会话不能使用免当前密码的强制重置流程。
*
* @throws Exception 注入测试依赖失败
*/
@Test
public void shouldRejectApiKeyForcedReset() throws Exception {
SysAccountService accountService = mock(SysAccountService.class);
AuthServiceImpl authService = createService(accountService);
when(accountService.getById(ACCOUNT_ID)).thenReturn(buildAccount(true));
BusinessException exception = expectBusinessException(() -> authService.updateOwnPassword(
ACCOUNT_ID,
null,
NEW_PASSWORD,
NEW_PASSWORD,
AuthLoginSessionPolicy.API_KEY_DEVICE
));
assertEquals(403, exception.getHttpStatus());
assertEquals("请使用账号密码登录后重置密码", exception.getMessage());
verify(accountService, never()).updateById(any(SysAccount.class));
}
/**
* 验证普通改密在当前密码正确时仍可成功更新。
*
* @throws Exception 注入测试依赖失败
*/
@Test
public void shouldUpdateRegularPasswordWithCorrectCurrentPassword() throws Exception {
SysAccountService accountService = mock(SysAccountService.class);
AuthServiceImpl authService = createService(accountService);
when(accountService.getById(ACCOUNT_ID)).thenReturn(buildAccount(false));
when(accountService.updateById(any(SysAccount.class))).thenReturn(true);
authService.updateOwnPassword(
ACCOUNT_ID,
CURRENT_PASSWORD,
NEW_PASSWORD,
NEW_PASSWORD,
AuthLoginSessionPolicy.WEB_DEVICE
);
verify(accountService).updateById(any(SysAccount.class));
}
/**
* 构造测试账号。
*
* @param passwordResetRequired 是否要求重置密码
* @return 测试账号
*/
private SysAccount buildAccount(boolean passwordResetRequired) {
SysAccount account = new SysAccount();
account.setId(ACCOUNT_ID);
account.setPassword(BCrypt.hashpw(CURRENT_PASSWORD));
account.setPasswordResetRequired(passwordResetRequired);
return account;
}
/**
* 创建并注入账号服务。
*
* @param accountService 账号服务
* @return 认证服务
* @throws Exception 注入测试依赖失败
*/
private AuthServiceImpl createService(SysAccountService accountService) throws Exception {
AuthServiceImpl authService = new AuthServiceImpl();
Field field = AuthServiceImpl.class.getDeclaredField("sysAccountService");
field.setAccessible(true);
field.set(authService, accountService);
return authService;
}
/**
* 执行操作并返回业务异常。
*
* @param action 待执行操作
* @return 捕获的业务异常
*/
private BusinessException expectBusinessException(Runnable action) {
try {
action.run();
fail("预期抛出 BusinessException");
return null;
} catch (BusinessException exception) {
return exception;
}
}
}