diff --git a/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/service/impl/TenantBindServiceImplTest.java b/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/service/impl/TenantBindServiceImplTest.java new file mode 100644 index 000000000..c2b9afd79 --- /dev/null +++ b/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/service/impl/TenantBindServiceImplTest.java @@ -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 . + */ + +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); + } +} diff --git a/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/service/impl/TenantServiceImplTest.java b/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/service/impl/TenantServiceImplTest.java new file mode 100644 index 000000000..868ed9764 --- /dev/null +++ b/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/service/impl/TenantServiceImplTest.java @@ -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 . + */ + +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 doPage = new Page<>(1, 10); + when(tenantManager.page(any(Page.class), any(LambdaQueryWrapper.class))).thenReturn(doPage); + Page boPage = new Page<>(1, 10); + when(tenantBuilder.buildBOPageByDOPage(doPage)).thenReturn(boPage); + + Page 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 doPage = new Page<>(2, 50); + when(tenantManager.page(any(Page.class), any(LambdaQueryWrapper.class))).thenReturn(doPage); + Page 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); + } +} diff --git a/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/service/impl/UserLoginServiceImplTest.java b/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/service/impl/UserLoginServiceImplTest.java new file mode 100644 index 000000000..cdd2e97db --- /dev/null +++ b/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/service/impl/UserLoginServiceImplTest.java @@ -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 . + */ + +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); + } +} diff --git a/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/service/impl/UserPasswordServiceImplTest.java b/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/service/impl/UserPasswordServiceImplTest.java new file mode 100644 index 000000000..29d0ba254 --- /dev/null +++ b/dc3-common/dc3-common-auth/src/test/java/io/github/pnoker/common/auth/service/impl/UserPasswordServiceImplTest.java @@ -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 . + */ + +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 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 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); + } +}