mirror of
https://gitee.com/pnoker/iot-dc3.git
synced 2026-11-01 18:51:53 +08:00
test(auth): cover security-critical service impls with mockito
Add Mockito-based unit tests for the four ServiceImpl classes that the TokenServiceImpl pipeline transitively trusts. Other ServiceImpls in dc3-common-auth follow the same MyBatis-Plus CRUD shape and will be covered as Testcontainers PG slice tests in a later stage where the real schema can validate the lambda query wrappers end-to-end. - TenantServiceImplTest (16): save / remove / update / selectById / selectByCode / selectByPage. Covers DuplicateException on save, NotFoundException on remove and update, AddException / DeleteException / UpdateException when the manager returns false, the same-row duplicate check during update, and Pages defaulting / preservation. - UserLoginServiceImplTest (12): the standard CRUD trio plus the four selectByLoginName branches (blank + throw / blank + silent / not-found + throw / happy path) and the three checkLoginNameValid outcomes (unknown / disabled / enabled). - UserPasswordServiceImplTest (9): the CRUD trio plus the password hashing assertion via DecodeUtil.md5 (save and update both rehash before persistence). Reset-password is asserted to set AlgorithmConstant.DEFAULT_PASSWORD before delegating to update. - TenantBindServiceImplTest (8): CRUD trio plus the selectByTenantIdAndUserId branches and the null-id short circuit on listUserIdsByTenantId. The happy path of listUserIdsByTenantId requires mybatis-plus lambda metadata not available without a real ORM context and is deferred to the slice test stage. Verified locally: 45 new cases green; dc3-common-auth now ships 69 unit tests across biz + service tiers.
This commit is contained in:
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.auth.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import io.github.pnoker.common.auth.dal.TenantBindManager;
|
||||
import io.github.pnoker.common.auth.entity.bo.TenantBindBO;
|
||||
import io.github.pnoker.common.auth.entity.builder.TenantBindBuilder;
|
||||
import io.github.pnoker.common.auth.entity.model.TenantBindDO;
|
||||
import io.github.pnoker.common.exception.AddException;
|
||||
import io.github.pnoker.common.exception.DeleteException;
|
||||
import io.github.pnoker.common.exception.DuplicateException;
|
||||
import io.github.pnoker.common.exception.NotFoundException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TenantBindServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private TenantBindBuilder tenantBindBuilder;
|
||||
|
||||
@Mock
|
||||
private TenantBindManager tenantBindManager;
|
||||
|
||||
@InjectMocks
|
||||
private TenantBindServiceImpl service;
|
||||
|
||||
private TenantBindBO bo;
|
||||
private TenantBindDO doRow;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
bo = new TenantBindBO();
|
||||
setField(bo, "id", 5L);
|
||||
setField(bo, "tenantId", 1L);
|
||||
setField(bo, "userId", 7L);
|
||||
|
||||
doRow = new TenantBindDO();
|
||||
doRow.setId(5L);
|
||||
doRow.setTenantId(1L);
|
||||
doRow.setUserId(7L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveSucceedsWhenNoDuplicate() {
|
||||
when(tenantBindManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
when(tenantBindBuilder.buildDOByBO(bo)).thenReturn(doRow);
|
||||
when(tenantBindManager.save(doRow)).thenReturn(true);
|
||||
assertThatNoException().isThrownBy(() -> service.save(bo));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveRejectsDuplicateBinding() {
|
||||
when(tenantBindManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
assertThatThrownBy(() -> service.save(bo)).isInstanceOf(DuplicateException.class);
|
||||
verify(tenantBindManager, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveThrowsAddExceptionWhenManagerReturnsFalse() {
|
||||
when(tenantBindManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
when(tenantBindBuilder.buildDOByBO(bo)).thenReturn(doRow);
|
||||
when(tenantBindManager.save(doRow)).thenReturn(false);
|
||||
assertThatThrownBy(() -> service.save(bo)).isInstanceOf(AddException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeRejectsUnknownId() {
|
||||
when(tenantBindManager.getById(5L)).thenReturn(null);
|
||||
assertThatThrownBy(() -> service.remove(5L)).isInstanceOf(NotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeThrowsDeleteExceptionWhenManagerReturnsFalse() {
|
||||
when(tenantBindManager.getById(5L)).thenReturn(doRow);
|
||||
when(tenantBindManager.removeById(5L)).thenReturn(false);
|
||||
assertThatThrownBy(() -> service.remove(5L)).isInstanceOf(DeleteException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectByTenantIdAndUserIdReturnsBoFromDal() {
|
||||
when(tenantBindManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
when(tenantBindBuilder.buildBOByDO(doRow)).thenReturn(bo);
|
||||
assertThat(service.selectByTenantIdAndUserId(1L, 7L)).isSameAs(bo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectByTenantIdAndUserIdReturnsNullWhenMissing() {
|
||||
when(tenantBindManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
when(tenantBindBuilder.buildBOByDO(null)).thenReturn(null);
|
||||
assertThat(service.selectByTenantIdAndUserId(1L, 7L)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void listUserIdsByTenantIdReturnsEmptyForNull() {
|
||||
assertThat(service.listUserIdsByTenantId(null)).isEmpty();
|
||||
verify(tenantBindManager, never()).listObjs(any(LambdaQueryWrapper.class), any());
|
||||
}
|
||||
|
||||
// The happy-path of listUserIdsByTenantId requires mybatis-plus lambda metadata
|
||||
// which is unavailable in pure Mockito; that branch is exercised by the
|
||||
// Testcontainers PG slice test introduced in a later stage.
|
||||
|
||||
private static void setField(Object target, String name, Object value) throws Exception {
|
||||
Field field = findField(target.getClass(), name);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
private static Field findField(Class<?> type, String name) throws NoSuchFieldException {
|
||||
Class<?> current = type;
|
||||
while (current != null) {
|
||||
try {
|
||||
return current.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(name);
|
||||
}
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.auth.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.github.pnoker.common.auth.dal.TenantManager;
|
||||
import io.github.pnoker.common.auth.entity.bo.TenantBO;
|
||||
import io.github.pnoker.common.auth.entity.builder.TenantBuilder;
|
||||
import io.github.pnoker.common.auth.entity.model.TenantDO;
|
||||
import io.github.pnoker.common.auth.entity.query.TenantQuery;
|
||||
import io.github.pnoker.common.entity.common.Pages;
|
||||
import io.github.pnoker.common.exception.AddException;
|
||||
import io.github.pnoker.common.exception.DeleteException;
|
||||
import io.github.pnoker.common.exception.DuplicateException;
|
||||
import io.github.pnoker.common.exception.NotFoundException;
|
||||
import io.github.pnoker.common.exception.UpdateException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TenantServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private TenantBuilder tenantBuilder;
|
||||
|
||||
@Mock
|
||||
private TenantManager tenantManager;
|
||||
|
||||
@InjectMocks
|
||||
private TenantServiceImpl service;
|
||||
|
||||
private TenantBO bo;
|
||||
private TenantDO doRow;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
bo = new TenantBO();
|
||||
setField(bo, "id", 1L);
|
||||
bo.setTenantName("Acme");
|
||||
bo.setTenantCode("acme");
|
||||
|
||||
doRow = new TenantDO();
|
||||
doRow.setId(1L);
|
||||
doRow.setTenantName("Acme");
|
||||
doRow.setTenantCode("acme");
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveSucceedsWhenManagerAccepts() {
|
||||
when(tenantManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
when(tenantBuilder.buildDOByBO(bo)).thenReturn(doRow);
|
||||
when(tenantManager.save(doRow)).thenReturn(true);
|
||||
|
||||
assertThatNoException().isThrownBy(() -> service.save(bo));
|
||||
verify(tenantManager).save(doRow);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveRejectsDuplicate() {
|
||||
when(tenantManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
assertThatThrownBy(() -> service.save(bo))
|
||||
.isInstanceOf(DuplicateException.class)
|
||||
.hasMessageContaining("Tenant has been duplicated");
|
||||
verify(tenantManager, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveThrowsAddExceptionWhenManagerReturnsFalse() {
|
||||
when(tenantManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
when(tenantBuilder.buildDOByBO(bo)).thenReturn(doRow);
|
||||
when(tenantManager.save(doRow)).thenReturn(false);
|
||||
assertThatThrownBy(() -> service.save(bo)).isInstanceOf(AddException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeSucceedsForExistingId() {
|
||||
when(tenantManager.getById(1L)).thenReturn(doRow);
|
||||
when(tenantManager.removeById(1L)).thenReturn(true);
|
||||
assertThatNoException().isThrownBy(() -> service.remove(1L));
|
||||
verify(tenantManager).removeById(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeRejectsUnknownId() {
|
||||
when(tenantManager.getById(99L)).thenReturn(null);
|
||||
assertThatThrownBy(() -> service.remove(99L))
|
||||
.isInstanceOf(NotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeThrowsDeleteExceptionWhenManagerReturnsFalse() {
|
||||
when(tenantManager.getById(1L)).thenReturn(doRow);
|
||||
when(tenantManager.removeById(1L)).thenReturn(false);
|
||||
assertThatThrownBy(() -> service.remove(1L)).isInstanceOf(DeleteException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateRejectsUnknownId() {
|
||||
when(tenantManager.getById(1L)).thenReturn(null);
|
||||
assertThatThrownBy(() -> service.update(bo)).isInstanceOf(NotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateRejectsDuplicateAcrossDifferentRow() {
|
||||
when(tenantManager.getById(1L)).thenReturn(doRow);
|
||||
TenantDO other = new TenantDO();
|
||||
other.setId(2L);
|
||||
when(tenantManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(other);
|
||||
assertThatThrownBy(() -> service.update(bo)).isInstanceOf(DuplicateException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateAcceptsSameRowOnDuplicateCheck() {
|
||||
when(tenantManager.getById(1L)).thenReturn(doRow);
|
||||
when(tenantManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
when(tenantBuilder.buildDOByBO(bo)).thenReturn(doRow);
|
||||
when(tenantManager.updateById(doRow)).thenReturn(true);
|
||||
assertThatNoException().isThrownBy(() -> service.update(bo));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateThrowsUpdateExceptionWhenManagerReturnsFalse() {
|
||||
when(tenantManager.getById(1L)).thenReturn(doRow);
|
||||
when(tenantManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
when(tenantBuilder.buildDOByBO(bo)).thenReturn(doRow);
|
||||
when(tenantManager.updateById(doRow)).thenReturn(false);
|
||||
assertThatThrownBy(() -> service.update(bo)).isInstanceOf(UpdateException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectByIdReturnsBoForKnownId() {
|
||||
when(tenantManager.getById(1L)).thenReturn(doRow);
|
||||
when(tenantBuilder.buildBOByDO(doRow)).thenReturn(bo);
|
||||
assertThat(service.selectById(1L)).isSameAs(bo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectByIdRejectsUnknownId() {
|
||||
when(tenantManager.getById(99L)).thenReturn(null);
|
||||
assertThatThrownBy(() -> service.selectById(99L)).isInstanceOf(NotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectByCodeFiltersOnEnabledTenants() {
|
||||
when(tenantManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
when(tenantBuilder.buildBOByDO(doRow)).thenReturn(bo);
|
||||
assertThat(service.selectByCode("acme")).isSameAs(bo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectByCodeReturnsNullWhenNoMatch() {
|
||||
when(tenantManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
when(tenantBuilder.buildBOByDO(null)).thenReturn(null);
|
||||
assertThat(service.selectByCode("missing")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectByPageInjectsDefaultPagesWhenMissing() {
|
||||
TenantQuery query = new TenantQuery();
|
||||
Page<TenantDO> doPage = new Page<>(1, 10);
|
||||
when(tenantManager.page(any(Page.class), any(LambdaQueryWrapper.class))).thenReturn(doPage);
|
||||
Page<TenantBO> boPage = new Page<>(1, 10);
|
||||
when(tenantBuilder.buildBOPageByDOPage(doPage)).thenReturn(boPage);
|
||||
|
||||
Page<TenantBO> result = service.selectByPage(query);
|
||||
|
||||
assertThat(query.getPage()).isNotNull();
|
||||
assertThat(result).isSameAs(boPage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectByPagePreservesProvidedPagination() {
|
||||
TenantQuery query = new TenantQuery();
|
||||
Pages pages = new Pages();
|
||||
pages.setCurrent(2);
|
||||
pages.setSize(50);
|
||||
query.setPage(pages);
|
||||
Page<TenantDO> doPage = new Page<>(2, 50);
|
||||
when(tenantManager.page(any(Page.class), any(LambdaQueryWrapper.class))).thenReturn(doPage);
|
||||
Page<TenantBO> boPage = new Page<>(2, 50);
|
||||
when(tenantBuilder.buildBOPageByDOPage(doPage)).thenReturn(boPage);
|
||||
|
||||
assertThat(service.selectByPage(query)).isSameAs(boPage);
|
||||
assertThat(query.getPage()).isSameAs(pages);
|
||||
}
|
||||
|
||||
private static void setField(Object target, String name, Object value) throws Exception {
|
||||
Field field = findField(target.getClass(), name);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
private static Field findField(Class<?> type, String name) throws NoSuchFieldException {
|
||||
Class<?> current = type;
|
||||
while (current != null) {
|
||||
try {
|
||||
return current.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(name);
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.auth.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import io.github.pnoker.common.auth.dal.UserLoginManager;
|
||||
import io.github.pnoker.common.auth.entity.bo.UserLoginBO;
|
||||
import io.github.pnoker.common.auth.entity.builder.UserLoginBuilder;
|
||||
import io.github.pnoker.common.auth.entity.model.UserLoginDO;
|
||||
import io.github.pnoker.common.auth.service.TenantBindService;
|
||||
import io.github.pnoker.common.enums.EnableFlagEnum;
|
||||
import io.github.pnoker.common.exception.AddException;
|
||||
import io.github.pnoker.common.exception.DeleteException;
|
||||
import io.github.pnoker.common.exception.DuplicateException;
|
||||
import io.github.pnoker.common.exception.EmptyException;
|
||||
import io.github.pnoker.common.exception.NotFoundException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UserLoginServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private UserLoginBuilder userLoginBuilder;
|
||||
|
||||
@Mock
|
||||
private UserLoginManager userLoginManager;
|
||||
|
||||
@Mock
|
||||
private TenantBindService tenantBindService;
|
||||
|
||||
@InjectMocks
|
||||
private UserLoginServiceImpl service;
|
||||
|
||||
private UserLoginBO bo;
|
||||
private UserLoginDO doRow;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
bo = new UserLoginBO();
|
||||
setField(bo, "id", 7L);
|
||||
setField(bo, "userId", 1L);
|
||||
setField(bo, "loginName", "alice");
|
||||
|
||||
doRow = new UserLoginDO();
|
||||
doRow.setId(7L);
|
||||
doRow.setUserId(1L);
|
||||
doRow.setLoginName("alice");
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveSucceedsWhenNoDuplicate() {
|
||||
when(userLoginManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
when(userLoginBuilder.buildDOByBO(bo)).thenReturn(doRow);
|
||||
when(userLoginManager.save(doRow)).thenReturn(true);
|
||||
assertThatNoException().isThrownBy(() -> service.save(bo));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveRejectsDuplicateLoginName() {
|
||||
when(userLoginManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
assertThatThrownBy(() -> service.save(bo)).isInstanceOf(DuplicateException.class);
|
||||
verify(userLoginManager, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveThrowsAddExceptionWhenManagerReturnsFalse() {
|
||||
when(userLoginManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
when(userLoginBuilder.buildDOByBO(bo)).thenReturn(doRow);
|
||||
when(userLoginManager.save(doRow)).thenReturn(false);
|
||||
assertThatThrownBy(() -> service.save(bo)).isInstanceOf(AddException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeRejectsUnknownId() {
|
||||
when(userLoginManager.getById(7L)).thenReturn(null);
|
||||
assertThatThrownBy(() -> service.remove(7L)).isInstanceOf(NotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeThrowsDeleteExceptionWhenManagerReturnsFalse() {
|
||||
when(userLoginManager.getById(7L)).thenReturn(doRow);
|
||||
when(userLoginManager.removeById(7L)).thenReturn(false);
|
||||
assertThatThrownBy(() -> service.remove(7L)).isInstanceOf(DeleteException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectByLoginNameThrowsEmptyExceptionForBlankWhenAsked() {
|
||||
assertThatThrownBy(() -> service.selectByLoginName("", true))
|
||||
.isInstanceOf(EmptyException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectByLoginNameReturnsNullForBlankWhenSilent() {
|
||||
assertThat(service.selectByLoginName(null, false)).isNull();
|
||||
assertThat(service.selectByLoginName("", false)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectByLoginNameThrowsNotFoundWhenAskedAndMissing() {
|
||||
when(userLoginManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
assertThatThrownBy(() -> service.selectByLoginName("missing", true))
|
||||
.isInstanceOf(NotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectByLoginNameReturnsBoForExistingEnabledLogin() {
|
||||
when(userLoginManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
when(userLoginBuilder.buildBOByDO(doRow)).thenReturn(bo);
|
||||
assertThat(service.selectByLoginName("alice", false)).isSameAs(bo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkLoginNameValidReturnsFalseForUnknownLogin() {
|
||||
when(userLoginManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
assertThat(service.checkLoginNameValid("ghost")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkLoginNameValidReturnsTrueForEnabledLogin() throws Exception {
|
||||
setField(bo, "enableFlag", EnableFlagEnum.ENABLE);
|
||||
when(userLoginManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
when(userLoginBuilder.buildBOByDO(doRow)).thenReturn(bo);
|
||||
assertThat(service.checkLoginNameValid("alice")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkLoginNameValidReturnsFalseForDisabledLogin() throws Exception {
|
||||
setField(bo, "enableFlag", EnableFlagEnum.DISABLE);
|
||||
when(userLoginManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
when(userLoginBuilder.buildBOByDO(doRow)).thenReturn(bo);
|
||||
assertThat(service.checkLoginNameValid("alice")).isFalse();
|
||||
}
|
||||
|
||||
private static void setField(Object target, String name, Object value) throws Exception {
|
||||
Field field = findField(target.getClass(), name);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
private static Field findField(Class<?> type, String name) throws NoSuchFieldException {
|
||||
Class<?> current = type;
|
||||
while (current != null) {
|
||||
try {
|
||||
return current.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(name);
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 2016-present the IoT DC3 original author or authors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.github.pnoker.common.auth.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import io.github.pnoker.common.auth.dal.UserPasswordManager;
|
||||
import io.github.pnoker.common.auth.entity.bo.UserPasswordBO;
|
||||
import io.github.pnoker.common.auth.entity.builder.UserPasswordBuilder;
|
||||
import io.github.pnoker.common.auth.entity.model.UserPasswordDO;
|
||||
import io.github.pnoker.common.constant.common.AlgorithmConstant;
|
||||
import io.github.pnoker.common.exception.AddException;
|
||||
import io.github.pnoker.common.exception.DeleteException;
|
||||
import io.github.pnoker.common.exception.DuplicateException;
|
||||
import io.github.pnoker.common.exception.NotFoundException;
|
||||
import io.github.pnoker.common.exception.UpdateException;
|
||||
import io.github.pnoker.common.utils.DecodeUtil;
|
||||
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.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UserPasswordServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private UserPasswordBuilder userPasswordBuilder;
|
||||
|
||||
@Mock
|
||||
private UserPasswordManager userPasswordManager;
|
||||
|
||||
@InjectMocks
|
||||
private UserPasswordServiceImpl service;
|
||||
|
||||
private UserPasswordBO bo;
|
||||
private UserPasswordDO doRow;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
bo = new UserPasswordBO();
|
||||
setField(bo, "id", 9L);
|
||||
setField(bo, "loginPassword", "secret");
|
||||
|
||||
doRow = new UserPasswordDO();
|
||||
doRow.setId(9L);
|
||||
doRow.setLoginPassword("secret");
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveHashesPasswordBeforePersisting() {
|
||||
when(userPasswordManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
when(userPasswordBuilder.buildDOByBO(bo)).thenReturn(doRow);
|
||||
when(userPasswordManager.save(doRow)).thenReturn(true);
|
||||
|
||||
service.save(bo);
|
||||
|
||||
ArgumentCaptor<UserPasswordDO> captor = ArgumentCaptor.forClass(UserPasswordDO.class);
|
||||
verify(userPasswordManager).save(captor.capture());
|
||||
assertThat(captor.getValue().getLoginPassword())
|
||||
.isEqualTo(DecodeUtil.md5("secret"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveRejectsDuplicate() {
|
||||
when(userPasswordManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
assertThatThrownBy(() -> service.save(bo)).isInstanceOf(DuplicateException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveThrowsAddExceptionWhenManagerReturnsFalse() {
|
||||
when(userPasswordManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
when(userPasswordBuilder.buildDOByBO(bo)).thenReturn(doRow);
|
||||
when(userPasswordManager.save(any(UserPasswordDO.class))).thenReturn(false);
|
||||
assertThatThrownBy(() -> service.save(bo)).isInstanceOf(AddException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeRejectsUnknownId() {
|
||||
when(userPasswordManager.getById(9L)).thenReturn(null);
|
||||
assertThatThrownBy(() -> service.remove(9L)).isInstanceOf(NotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeThrowsDeleteExceptionWhenManagerReturnsFalse() {
|
||||
when(userPasswordManager.getById(9L)).thenReturn(doRow);
|
||||
when(userPasswordManager.removeById(9L)).thenReturn(false);
|
||||
assertThatThrownBy(() -> service.remove(9L)).isInstanceOf(DeleteException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateRehashesPassword() {
|
||||
when(userPasswordManager.getById(9L)).thenReturn(doRow);
|
||||
when(userPasswordManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
when(userPasswordBuilder.buildDOByBO(bo)).thenReturn(doRow);
|
||||
when(userPasswordManager.updateById(any(UserPasswordDO.class))).thenReturn(true);
|
||||
|
||||
service.update(bo);
|
||||
|
||||
ArgumentCaptor<UserPasswordDO> captor = ArgumentCaptor.forClass(UserPasswordDO.class);
|
||||
verify(userPasswordManager).updateById(captor.capture());
|
||||
assertThat(captor.getValue().getLoginPassword())
|
||||
.isEqualTo(DecodeUtil.md5("secret"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateThrowsUpdateExceptionWhenManagerReturnsFalse() {
|
||||
when(userPasswordManager.getById(9L)).thenReturn(doRow);
|
||||
when(userPasswordManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
when(userPasswordBuilder.buildDOByBO(bo)).thenReturn(doRow);
|
||||
when(userPasswordManager.updateById(any(UserPasswordDO.class))).thenReturn(false);
|
||||
assertThatThrownBy(() -> service.update(bo)).isInstanceOf(UpdateException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetPasswordReplacesValueWithDefaultBeforeUpdate() throws Exception {
|
||||
UserPasswordBO restored = new UserPasswordBO();
|
||||
setField(restored, "id", 9L);
|
||||
setField(restored, "loginPassword", "old");
|
||||
|
||||
when(userPasswordManager.getById(9L)).thenReturn(doRow, doRow);
|
||||
when(userPasswordBuilder.buildBOByDO(doRow)).thenReturn(restored);
|
||||
when(userPasswordManager.getOne(any(LambdaQueryWrapper.class))).thenReturn(doRow);
|
||||
when(userPasswordBuilder.buildDOByBO(restored)).thenReturn(doRow);
|
||||
when(userPasswordManager.updateById(any(UserPasswordDO.class))).thenReturn(true);
|
||||
|
||||
assertThatNoException().isThrownBy(() -> service.restPassword(9L));
|
||||
assertThat(restored.getLoginPassword()).isEqualTo(AlgorithmConstant.DEFAULT_PASSWORD);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetPasswordIsNoopWhenRecordMissing() {
|
||||
when(userPasswordManager.getById(9L)).thenReturn(null);
|
||||
assertThatThrownBy(() -> service.restPassword(9L)).isInstanceOf(NotFoundException.class);
|
||||
}
|
||||
|
||||
private static void setField(Object target, String name, Object value) throws Exception {
|
||||
Field field = findField(target.getClass(), name);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
private static Field findField(Class<?> type, String name) throws NoSuchFieldException {
|
||||
Class<?> current = type;
|
||||
while (current != null) {
|
||||
try {
|
||||
return current.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user