From 84faf4ac66efe0766d8bbf5bb9924835fd47eca5 Mon Sep 17 00:00:00 2001 From: Luca Schumacher Date: Tue, 16 Jun 2026 22:19:12 +0200 Subject: [PATCH 1/2] feat(user): make use case classes internal and add test configurations --- .../user/usecase/ActivateUserUseCaseImpl.kt | 2 +- .../AdminResetPasswordUserUseCaseImpl.kt | 2 +- .../user/usecase/ChangeUserRoleUseCaseImpl.kt | 2 +- .../user/usecase/CreateUserUseCaseImpl.kt | 2 +- .../user/usecase/DeactivateUserUseCaseImpl.kt | 2 +- .../user/usecase/GetUsersPagedUseCaseImpl.kt | 2 +- .../RequestUserPasswordChangeUseCaseImpl.kt | 4 +- .../usecase/ResetPasswordUserUseCaseImpl.kt | 2 +- bootstrap/build.gradle | 2 + .../integration/config/TestLogger.kt | 33 ++ .../integration/config/TestPasswordHasher.kt | 15 + .../integration/config/TestPresenter.kt | 17 + .../config/TestUserIntegrationConfig.kt | 104 ++++++ .../user/ActivateUserUseCaseTest.kt | 160 +++++++++ .../user/AdminResetPasswordUserUseCaseTest.kt | 165 ++++++++++ .../user/ChangeUserRoleUseCaseTest.kt | 199 ++++++++++++ .../integration/user/CreateUserUseCaseTest.kt | 304 ++++++++++++++++++ .../user/DeactivateUserUseCaseTest.kt | 160 +++++++++ .../integration/user/EditUserUseCaseTest.kt | 295 +++++++++++++++++ .../integration/user/GetUserUseCaseTest.kt | 96 ++++++ .../user/GetUsersPagedUseCaseTest.kt | 261 +++++++++++++++ .../integration/user/LoginUserUseCaseTest.kt | 97 ++++++ .../RequestUserPasswordChangeUseCaseTest.kt | 163 ++++++++++ .../user/ResetPasswordUserUseCaseTest.kt | 118 +++++++ .../src/test/resources/application-test.yml | 19 ++ .../web/user/controller/UserController.kt | 1 - 26 files changed, 2217 insertions(+), 10 deletions(-) create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestLogger.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPasswordHasher.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPresenter.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestUserIntegrationConfig.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ActivateUserUseCaseTest.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/AdminResetPasswordUserUseCaseTest.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ChangeUserRoleUseCaseTest.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/CreateUserUseCaseTest.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/DeactivateUserUseCaseTest.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/EditUserUseCaseTest.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUserUseCaseTest.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUsersPagedUseCaseTest.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/LoginUserUseCaseTest.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/RequestUserPasswordChangeUseCaseTest.kt create mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ResetPasswordUserUseCaseTest.kt create mode 100644 bootstrap/src/test/resources/application-test.yml diff --git a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/ActivateUserUseCaseImpl.kt b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/ActivateUserUseCaseImpl.kt index e2db595f..91d387f0 100644 --- a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/ActivateUserUseCaseImpl.kt +++ b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/ActivateUserUseCaseImpl.kt @@ -14,7 +14,7 @@ import com.alleslocker.backend.domain.user.UserRole import java.time.Instant import kotlin.math.log -class ActivateUserUseCaseImpl( +internal class ActivateUserUseCaseImpl( val userGateway: UserGateway, val logger: Logger, ) : ActivateUserUseCase { diff --git a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/AdminResetPasswordUserUseCaseImpl.kt b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/AdminResetPasswordUserUseCaseImpl.kt index 9dc00754..1f1dec6c 100644 --- a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/AdminResetPasswordUserUseCaseImpl.kt +++ b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/AdminResetPasswordUserUseCaseImpl.kt @@ -16,7 +16,7 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserRole import java.time.Instant -class AdminResetPasswordUserUseCaseImpl( +internal class AdminResetPasswordUserUseCaseImpl( private val userGateway: UserGateway, private val passwordHasher: PasswordHasher, private val passwordGeneratorService: PasswordGeneratorService, diff --git a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/ChangeUserRoleUseCaseImpl.kt b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/ChangeUserRoleUseCaseImpl.kt index 184abf7a..d701d89f 100644 --- a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/ChangeUserRoleUseCaseImpl.kt +++ b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/ChangeUserRoleUseCaseImpl.kt @@ -13,7 +13,7 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserRole import java.time.Instant -class ChangeUserRoleUseCaseImpl( +internal class ChangeUserRoleUseCaseImpl( private val userGateway: UserGateway, private val logger: Logger, ) : ChangeUserRoleUseCase { diff --git a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/CreateUserUseCaseImpl.kt b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/CreateUserUseCaseImpl.kt index 23668ece..5f7ca685 100644 --- a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/CreateUserUseCaseImpl.kt +++ b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/CreateUserUseCaseImpl.kt @@ -21,7 +21,7 @@ import com.alleslocker.backend.domain.user.UserRole import com.alleslocker.backend.domain.user.Username import java.time.Instant -class CreateUserUseCaseImpl( +internal class CreateUserUseCaseImpl( private val userGateway: UserGateway, private val logger: Logger, private val passwordGeneratorService: PasswordGeneratorService, diff --git a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/DeactivateUserUseCaseImpl.kt b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/DeactivateUserUseCaseImpl.kt index 258a4fb7..8ce7063c 100644 --- a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/DeactivateUserUseCaseImpl.kt +++ b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/DeactivateUserUseCaseImpl.kt @@ -13,7 +13,7 @@ import com.alleslocker.backend.domain.user.UserRole import java.time.Instant import kotlin.math.log -class DeactivateUserUseCaseImpl( +internal class DeactivateUserUseCaseImpl( val userGateway: UserGateway, val logger: Logger, ) : DeactivateUserUseCase { diff --git a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/GetUsersPagedUseCaseImpl.kt b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/GetUsersPagedUseCaseImpl.kt index 3c53cbd0..7a8086c8 100644 --- a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/GetUsersPagedUseCaseImpl.kt +++ b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/GetUsersPagedUseCaseImpl.kt @@ -8,7 +8,7 @@ import com.alleslocker.backend.application.user.dto.response.GetUsersPagedRespon import com.alleslocker.backend.application.user.gateway.UserGateway import com.alleslocker.backend.application.user.mapper.toDto -class GetUsersPagedUseCaseImpl( +internal class GetUsersPagedUseCaseImpl( private val userGateway: UserGateway, private val logger: Logger, ) : GetUsersPagedUseCase { diff --git a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/RequestUserPasswordChangeUseCaseImpl.kt b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/RequestUserPasswordChangeUseCaseImpl.kt index dcdebef1..3496ec05 100644 --- a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/RequestUserPasswordChangeUseCaseImpl.kt +++ b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/RequestUserPasswordChangeUseCaseImpl.kt @@ -12,7 +12,7 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserRole import java.time.Instant -class RequestUserPasswordChangeUseCaseImpl( +internal class RequestUserPasswordChangeUseCaseImpl( private val userGateway: UserGateway, private val logger: Logger, ) : RequestUserPasswordChangeUseCase { @@ -22,7 +22,7 @@ class RequestUserPasswordChangeUseCaseImpl( ) { val requestorId = try { - UserId(request.userId) + UserId(request.requestorId) } catch (e: IllegalArgumentException) { presenter.presentFailure(ErrorResponse.BadRequest("Invalid id: ${e.message}")) return diff --git a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/ResetPasswordUserUseCaseImpl.kt b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/ResetPasswordUserUseCaseImpl.kt index 27b0582f..a075f00c 100644 --- a/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/ResetPasswordUserUseCaseImpl.kt +++ b/application/src/main/kotlin/com/alleslocker/backend/application/user/usecase/ResetPasswordUserUseCaseImpl.kt @@ -10,7 +10,7 @@ import com.alleslocker.backend.application.user.gateway.UserGateway import com.alleslocker.backend.domain.user.PasswordHash import com.alleslocker.backend.domain.user.UserId -class ResetPasswordUserUseCaseImpl( +internal class ResetPasswordUserUseCaseImpl( private val passwordHasher: PasswordHasher, private val logger: Logger, private val userGateway: UserGateway, diff --git a/bootstrap/build.gradle b/bootstrap/build.gradle index e75334a1..41a6a41c 100644 --- a/bootstrap/build.gradle +++ b/bootstrap/build.gradle @@ -20,4 +20,6 @@ dependencies { testImplementation(libs.kotest.assertions) testImplementation(libs.kotest.runner) testImplementation(libs.kotest.spring) + testImplementation(libs.h2) + testImplementation(libs.spring.boot.test) } diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestLogger.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestLogger.kt new file mode 100644 index 00000000..65ad3350 --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestLogger.kt @@ -0,0 +1,33 @@ +package com.alleslocker.backend.bootstrap.integration.config + +import com.alleslocker.backend.application.common.Logger +import com.alleslocker.backend.domain.auditlog.AuditLog + +class TestLogger : Logger { + val auditLogs = mutableListOf() + val infoMessages = mutableListOf() + val errorMessages = mutableListOf() + + fun clear() { + auditLogs.clear() + infoMessages.clear() + errorMessages.clear() + } + + override fun info(message: String) { + infoMessages.add(message) + } + + override fun error( + message: String, + throwable: Throwable?, + ) { + errorMessages.add(message) + } + + override fun audit(auditLog: AuditLog) { + auditLogs.add(auditLog) + } + + override fun debug(message: String) {} +} diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPasswordHasher.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPasswordHasher.kt new file mode 100644 index 00000000..1e648891 --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPasswordHasher.kt @@ -0,0 +1,15 @@ +package com.alleslocker.backend.bootstrap.integration.config + +import com.alleslocker.backend.application.common.security.PasswordHasher +import java.security.MessageDigest + +class TestPasswordHasher : PasswordHasher { + private val digest = MessageDigest.getInstance("SHA-256") + + override fun hash(raw: String): String = digest.digest(raw.toByteArray()).joinToString("") { "%02x".format(it) } + + override fun verify( + raw: String, + hash: String, + ): Boolean = hash(raw) == hash +} diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPresenter.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPresenter.kt new file mode 100644 index 00000000..f16ddf1f --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPresenter.kt @@ -0,0 +1,17 @@ +package com.alleslocker.backend.bootstrap.integration.config + +import com.alleslocker.backend.application.common.ErrorResponse +import com.alleslocker.backend.application.common.OutputBoundary + +class TestPresenter : OutputBoundary { + var response: R? = null + var error: ErrorResponse? = null + + override fun present(response: R) { + this.response = response + } + + override fun presentFailure(error: ErrorResponse) { + this.error = error + } +} diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestUserIntegrationConfig.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestUserIntegrationConfig.kt new file mode 100644 index 00000000..b273d625 --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestUserIntegrationConfig.kt @@ -0,0 +1,104 @@ +package com.alleslocker.backend.bootstrap.integration.config + +import com.alleslocker.backend.application.accessgrant.adapter.AccessGrantAdapter +import com.alleslocker.backend.application.common.Logger +import com.alleslocker.backend.application.common.factory.AdapterFactory +import com.alleslocker.backend.application.common.factory.GatewayFactory +import com.alleslocker.backend.application.common.factory.UseCaseFactory +import com.alleslocker.backend.application.common.factory.UseCaseFactoryImpl +import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.lock.adapter.LockAdapter +import com.alleslocker.backend.application.person.adapter.PersonAdapter +import com.alleslocker.backend.application.vendor.adapter.VendorConnectionAdapter +import com.alleslocker.backend.application.vendor.adapter.VendorSpecificDefinitionsAdapter +import com.alleslocker.backend.lockconnector.common.AdapterFactoryImpl +import com.alleslocker.backend.persistence.accessgrant.adapter.AccessGrantGatewayAdapter +import com.alleslocker.backend.persistence.auditlog.adapter.AuditLogGatewayAdapter +import com.alleslocker.backend.persistence.factory.GatewayFactoryImpl +import com.alleslocker.backend.persistence.lock.adapter.LockGatewayAdapter +import com.alleslocker.backend.persistence.person.adapter.PersonGatewayAdapter +import com.alleslocker.backend.persistence.user.adapter.UserGatewayAdapter +import com.alleslocker.backend.persistence.vendor.adapter.VendorDataGatewayAdapter +import org.mockito.Mockito.mock +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Primary +import org.springframework.data.jpa.repository.config.EnableJpaRepositories + +@TestConfiguration(proxyBeanMethods = false) +@ComponentScan(basePackages = ["com.alleslocker.backend.persistence"]) +@EnableJpaRepositories(basePackages = ["com.alleslocker.backend.persistence"]) +@EntityScan(basePackages = ["com.alleslocker.backend.persistence"]) +open class TestUserIntegrationConfig { + @Bean + fun passwordHasher(): PasswordHasher = TestPasswordHasher() + + @Bean + @Primary + fun testLogger(): Logger = TestLogger() + + @Bean + fun lockAdapter(): LockAdapter = mock(LockAdapter::class.java) + + @Bean + fun personAdapter(): PersonAdapter = mock(PersonAdapter::class.java) + + @Bean + fun accessGrantAdapter(): AccessGrantAdapter = mock(AccessGrantAdapter::class.java) + + @Bean + fun vendorConnectionAdapter(): VendorConnectionAdapter = mock(VendorConnectionAdapter::class.java) + + @Bean + fun vendorSpecificDefinitionsAdapter(): VendorSpecificDefinitionsAdapter = + mock(VendorSpecificDefinitionsAdapter::class.java) + + @Bean + fun adapterFactory( + lockAdapter: LockAdapter, + personAdapter: PersonAdapter, + accessGrantAdapter: AccessGrantAdapter, + vendorConnectionAdapter: VendorConnectionAdapter, + vendorSpecificDefinitionsAdapter: VendorSpecificDefinitionsAdapter, + ): AdapterFactory = + AdapterFactoryImpl( + listOf( + lockAdapter, + personAdapter, + accessGrantAdapter, + vendorConnectionAdapter, + vendorSpecificDefinitionsAdapter, + ), + ) + + @Bean + fun gatewayFactory( + userGatewayAdapter: UserGatewayAdapter, + personGatewayAdapter: PersonGatewayAdapter, + lockGatewayAdapter: LockGatewayAdapter, + accessGrantGatewayAdapter: AccessGrantGatewayAdapter, + vendorDataGatewayAdapter: VendorDataGatewayAdapter, + auditLogGatewayAdapter: AuditLogGatewayAdapter, + ): GatewayFactory = + GatewayFactoryImpl( + listOf( + userGatewayAdapter, + personGatewayAdapter, + lockGatewayAdapter, + accessGrantGatewayAdapter, + vendorDataGatewayAdapter, + auditLogGatewayAdapter, + ), + ) + + @Bean + fun useCaseFactory( + gatewayFactory: GatewayFactory, + adapterFactory: AdapterFactory, + passwordHasher: PasswordHasher, + logger: Logger, + ): UseCaseFactory = UseCaseFactoryImpl(gatewayFactory, adapterFactory, passwordHasher, logger) +} diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ActivateUserUseCaseTest.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ActivateUserUseCaseTest.kt new file mode 100644 index 00000000..91693ca0 --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ActivateUserUseCaseTest.kt @@ -0,0 +1,160 @@ +package com.alleslocker.backend.bootstrap.integration.user + +import com.alleslocker.backend.application.common.ErrorResponse +import com.alleslocker.backend.application.common.factory.UseCaseFactory +import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.user.dto.request.ActivateUserRequestDto +import com.alleslocker.backend.application.user.gateway.UserGateway +import com.alleslocker.backend.application.user.usecase.ActivateUserUseCase +import com.alleslocker.backend.bootstrap.integration.config.TestLogger +import com.alleslocker.backend.bootstrap.integration.config.TestPresenter +import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig +import com.alleslocker.backend.domain.user.PasswordHash +import com.alleslocker.backend.domain.user.User +import com.alleslocker.backend.domain.user.UserEmail +import com.alleslocker.backend.domain.user.UserFirstname +import com.alleslocker.backend.domain.user.UserId +import com.alleslocker.backend.domain.user.UserLastname +import com.alleslocker.backend.domain.user.UserRole +import com.alleslocker.backend.domain.user.Username +import com.alleslocker.backend.persistence.user.repository.UserRepository +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.types.shouldBeInstanceOf +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles + +@SpringBootTest(classes = [TestUserIntegrationConfig::class]) +@ActiveProfiles("test") +class ActivateUserUseCaseTest( + @Autowired private val useCaseFactory: UseCaseFactory, + @Autowired private val userGateway: UserGateway, + @Autowired private val userRepository: UserRepository, + @Autowired private val passwordHasher: PasswordHasher, + @Autowired private val testLogger: TestLogger, +) : FreeSpec({ + + val useCase: ActivateUserUseCase = useCaseFactory.make(ActivateUserUseCase::class) + + lateinit var adminId: String + lateinit var targetUserId: String + + beforeEach { + userRepository.deleteAll() + testLogger.clear() + val admin = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.ADMIN, + firstname = UserFirstname("Admin"), + lastname = UserLastname("User"), + username = Username("admin"), + email = UserEmail("admin@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("admin123")), + isActive = true, + mustChangePassword = false, + ), + ) + adminId = admin.id.value + + val targetUser = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Max"), + lastname = UserLastname("Mustermann"), + username = Username("mmuster"), + email = UserEmail("max@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = false, + mustChangePassword = false, + ), + ) + targetUserId = targetUser.id.value + } + + "should activate a deactivated user" { + val presenter = TestPresenter() + + useCase.execute( + ActivateUserRequestDto(requestorId = adminId, userId = targetUserId), + presenter, + ) + + presenter.error shouldBe null + val updatedUser = userGateway.findById(UserId(targetUserId)) + updatedUser!!.isActive shouldBe true + testLogger.auditLogs.size shouldBe 1 + } + + "should reject activating already active user" { + userGateway.save( + userGateway.findById(UserId(targetUserId))!!.copy(isActive = true), + ) + + val presenter = TestPresenter() + useCase.execute( + ActivateUserRequestDto(requestorId = adminId, userId = targetUserId), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject activation by non-admin" { + val regularUser = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Regular"), + lastname = UserLastname("User"), + username = Username("regular"), + email = UserEmail("regular@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + + val presenter = TestPresenter() + useCase.execute( + ActivateUserRequestDto(requestorId = regularUser.id.value, userId = targetUserId), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject activation of non-existent user" { + val presenter = TestPresenter() + useCase.execute( + ActivateUserRequestDto(requestorId = adminId, userId = "non-existent-id"), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject activation with non-existent requestor" { + val presenter = TestPresenter() + useCase.execute( + ActivateUserRequestDto(requestorId = "non-existent-id", userId = targetUserId), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + }) diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/AdminResetPasswordUserUseCaseTest.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/AdminResetPasswordUserUseCaseTest.kt new file mode 100644 index 00000000..afe08a08 --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/AdminResetPasswordUserUseCaseTest.kt @@ -0,0 +1,165 @@ +package com.alleslocker.backend.bootstrap.integration.user + +import com.alleslocker.backend.application.common.ErrorResponse +import com.alleslocker.backend.application.common.factory.UseCaseFactory +import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.user.dto.request.AdminResetPasswordUserRequestDto +import com.alleslocker.backend.application.user.dto.response.AdminResetPasswordUserResponseDto +import com.alleslocker.backend.application.user.gateway.UserGateway +import com.alleslocker.backend.application.user.usecase.AdminResetPasswordUserUseCase +import com.alleslocker.backend.bootstrap.integration.config.TestLogger +import com.alleslocker.backend.bootstrap.integration.config.TestPresenter +import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig +import com.alleslocker.backend.domain.user.PasswordHash +import com.alleslocker.backend.domain.user.User +import com.alleslocker.backend.domain.user.UserEmail +import com.alleslocker.backend.domain.user.UserFirstname +import com.alleslocker.backend.domain.user.UserId +import com.alleslocker.backend.domain.user.UserLastname +import com.alleslocker.backend.domain.user.UserRole +import com.alleslocker.backend.domain.user.Username +import com.alleslocker.backend.persistence.user.repository.UserRepository +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.comparables.shouldBeGreaterThan +import io.kotest.matchers.comparables.shouldBeGreaterThanOrEqualTo +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.types.shouldBeInstanceOf +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles + +@SpringBootTest(classes = [TestUserIntegrationConfig::class]) +@ActiveProfiles("test") +class AdminResetPasswordUserUseCaseTest( + @Autowired private val useCaseFactory: UseCaseFactory, + @Autowired private val userGateway: UserGateway, + @Autowired private val userRepository: UserRepository, + @Autowired private val passwordHasher: PasswordHasher, + @Autowired private val testLogger: TestLogger, +) : FreeSpec({ + + val useCase: AdminResetPasswordUserUseCase = + useCaseFactory.make(AdminResetPasswordUserUseCase::class) + + lateinit var adminId: String + lateinit var targetUserId: String + + beforeEach { + userRepository.deleteAll() + testLogger.clear() + val admin = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.ADMIN, + firstname = UserFirstname("Admin"), + lastname = UserLastname("User"), + username = Username("admin"), + email = UserEmail("admin@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("admin123")), + isActive = true, + mustChangePassword = false, + ), + ) + adminId = admin.id.value + + val targetUser = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Max"), + lastname = UserLastname("Mustermann"), + username = Username("mmuster"), + email = UserEmail("max@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("oldPass123")), + isActive = true, + mustChangePassword = false, + ), + ) + targetUserId = targetUser.id.value + } + + "should reset password for user" { + val presenter = TestPresenter() + + useCase.execute( + AdminResetPasswordUserRequestDto(requestorId = adminId, userId = targetUserId), + presenter, + ) + + presenter.error shouldBe null + presenter.response shouldNotBe null + presenter.response!!.userId shouldBe targetUserId + presenter.response!!.password.length shouldBeGreaterThan 0 + + val updatedUser = userGateway.findById(UserId(targetUserId)) + updatedUser!!.mustChangePassword shouldBe true + passwordHasher.verify(presenter.response!!.password, updatedUser.passwordHash.value) shouldBe true + + testLogger.auditLogs.size shouldBe 1 + } + + "should reject reset by non-admin" { + val regularUser = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Regular"), + lastname = UserLastname("User"), + username = Username("regular"), + email = UserEmail("regular@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + + val presenter = TestPresenter() + useCase.execute( + AdminResetPasswordUserRequestDto(requestorId = regularUser.id.value, userId = targetUserId), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject reset of non-existent user" { + val presenter = TestPresenter() + useCase.execute( + AdminResetPasswordUserRequestDto(requestorId = adminId, userId = "non-existent-id"), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject reset with non-existent requestor" { + val presenter = TestPresenter() + useCase.execute( + AdminResetPasswordUserRequestDto(requestorId = "non-existent-id", userId = targetUserId), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "generated password should be at least 8 characters" { + val presenter = TestPresenter() + useCase.execute( + AdminResetPasswordUserRequestDto(requestorId = adminId, userId = targetUserId), + presenter, + ) + + presenter.error shouldBe null + presenter.response!!.password.length shouldBeGreaterThanOrEqualTo 8 + } + }) diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ChangeUserRoleUseCaseTest.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ChangeUserRoleUseCaseTest.kt new file mode 100644 index 00000000..17929778 --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ChangeUserRoleUseCaseTest.kt @@ -0,0 +1,199 @@ +package com.alleslocker.backend.bootstrap.integration.user + +import com.alleslocker.backend.application.common.ErrorResponse +import com.alleslocker.backend.application.common.factory.UseCaseFactory +import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.user.dto.UserRoleDto +import com.alleslocker.backend.application.user.dto.request.ChangeUserRoleRequestDto +import com.alleslocker.backend.application.user.gateway.UserGateway +import com.alleslocker.backend.application.user.usecase.ChangeUserRoleUseCase +import com.alleslocker.backend.bootstrap.integration.config.TestLogger +import com.alleslocker.backend.bootstrap.integration.config.TestPresenter +import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig +import com.alleslocker.backend.domain.user.PasswordHash +import com.alleslocker.backend.domain.user.User +import com.alleslocker.backend.domain.user.UserEmail +import com.alleslocker.backend.domain.user.UserFirstname +import com.alleslocker.backend.domain.user.UserId +import com.alleslocker.backend.domain.user.UserLastname +import com.alleslocker.backend.domain.user.UserRole +import com.alleslocker.backend.domain.user.Username +import com.alleslocker.backend.persistence.user.repository.UserRepository +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.types.shouldBeInstanceOf +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles + +@SpringBootTest(classes = [TestUserIntegrationConfig::class]) +@ActiveProfiles("test") +class ChangeUserRoleUseCaseTest( + @Autowired private val useCaseFactory: UseCaseFactory, + @Autowired private val userGateway: UserGateway, + @Autowired private val userRepository: UserRepository, + @Autowired private val passwordHasher: PasswordHasher, + @Autowired private val testLogger: TestLogger, +) : FreeSpec({ + + val useCase: ChangeUserRoleUseCase = useCaseFactory.make(ChangeUserRoleUseCase::class) + + lateinit var adminId: String + lateinit var targetUserId: String + + beforeEach { + userRepository.deleteAll() + testLogger.clear() + val admin = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.ADMIN, + firstname = UserFirstname("Admin"), + lastname = UserLastname("User"), + username = Username("admin"), + email = UserEmail("admin@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("admin123")), + isActive = true, + mustChangePassword = false, + ), + ) + adminId = admin.id.value + + val targetUser = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Max"), + lastname = UserLastname("Mustermann"), + username = Username("mmuster"), + email = UserEmail("max@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + targetUserId = targetUser.id.value + } + + "should change USER to ADMIN" { + val presenter = TestPresenter() + + useCase.execute( + ChangeUserRoleRequestDto( + requestorId = adminId, + userId = targetUserId, + role = UserRoleDto.ADMIN, + ), + presenter, + ) + + presenter.error shouldBe null + val updatedUser = userGateway.findById(UserId(targetUserId)) + updatedUser!!.role shouldBe UserRole.ADMIN + testLogger.auditLogs.size shouldBe 1 + } + + "should change ADMIN to USER" { + userGateway.save( + userGateway.findById(UserId(targetUserId))!!.copy(role = UserRole.ADMIN), + ) + + val presenter = TestPresenter() + useCase.execute( + ChangeUserRoleRequestDto( + requestorId = adminId, + userId = targetUserId, + role = UserRoleDto.USER, + ), + presenter, + ) + + presenter.error shouldBe null + val updatedUser = userGateway.findById(UserId(targetUserId)) + updatedUser!!.role shouldBe UserRole.USER + } + + "should reject setting the same role" { + val presenter = TestPresenter() + useCase.execute( + ChangeUserRoleRequestDto( + requestorId = adminId, + userId = targetUserId, + role = UserRoleDto.USER, + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + presenter.error!!.message shouldContain "already this role" + } + + "should reject role change by non-admin" { + val regularUser = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Regular"), + lastname = UserLastname("User"), + username = Username("regular"), + email = UserEmail("regular@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + + val presenter = TestPresenter() + useCase.execute( + ChangeUserRoleRequestDto( + requestorId = regularUser.id.value, + userId = targetUserId, + role = UserRoleDto.ADMIN, + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject role change of non-existent user" { + val presenter = TestPresenter() + useCase.execute( + ChangeUserRoleRequestDto( + requestorId = adminId, + userId = "non-existent-id", + role = UserRoleDto.ADMIN, + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject role change with non-existent requestor" { + val presenter = TestPresenter() + useCase.execute( + ChangeUserRoleRequestDto( + requestorId = "non-existent-id", + userId = targetUserId, + role = UserRoleDto.ADMIN, + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + }) diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/CreateUserUseCaseTest.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/CreateUserUseCaseTest.kt new file mode 100644 index 00000000..32fd1aa3 --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/CreateUserUseCaseTest.kt @@ -0,0 +1,304 @@ +package com.alleslocker.backend.bootstrap.integration.user + +import com.alleslocker.backend.application.common.ErrorResponse +import com.alleslocker.backend.application.common.factory.UseCaseFactory +import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.user.dto.request.CreateUserRequestDto +import com.alleslocker.backend.application.user.dto.response.CreateUserResponseDto +import com.alleslocker.backend.application.user.gateway.UserGateway +import com.alleslocker.backend.application.user.usecase.CreateUserUseCase +import com.alleslocker.backend.bootstrap.integration.config.TestLogger +import com.alleslocker.backend.bootstrap.integration.config.TestPresenter +import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig +import com.alleslocker.backend.domain.user.PasswordHash +import com.alleslocker.backend.domain.user.User +import com.alleslocker.backend.domain.user.UserEmail +import com.alleslocker.backend.domain.user.UserFirstname +import com.alleslocker.backend.domain.user.UserId +import com.alleslocker.backend.domain.user.UserLastname +import com.alleslocker.backend.domain.user.UserRole +import com.alleslocker.backend.domain.user.Username +import com.alleslocker.backend.persistence.user.repository.UserRepository +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.comparables.shouldBeGreaterThan +import io.kotest.matchers.comparables.shouldBeGreaterThanOrEqualTo +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.types.shouldBeInstanceOf +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles + +@SpringBootTest(classes = [TestUserIntegrationConfig::class]) +@ActiveProfiles("test") +class CreateUserUseCaseTest( + @Autowired private val useCaseFactory: UseCaseFactory, + @Autowired private val userGateway: UserGateway, + @Autowired private val userRepository: UserRepository, + @Autowired private val passwordHasher: PasswordHasher, + @Autowired private val testLogger: TestLogger, +) : FreeSpec({ + + val useCase: CreateUserUseCase = + useCaseFactory.make(CreateUserUseCase::class) + + lateinit var adminId: String + + beforeEach { + userRepository.deleteAll() + testLogger.clear() + val admin = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.ADMIN, + firstname = UserFirstname("Admin"), + lastname = UserLastname("User"), + username = Username("admin"), + email = UserEmail("admin@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("admin123")), + isActive = true, + mustChangePassword = false, + ), + ) + adminId = admin.id.value + } + + "should create a user as admin" { + val presenter = TestPresenter() + + useCase.execute( + CreateUserRequestDto( + requestorId = adminId, + firstname = "Max", + lastname = "Mustermann", + username = "mmuster", + email = "max@test.de", + ), + presenter, + ) + + presenter.error shouldBe null + presenter.response shouldNotBe null + presenter.response!!.id shouldNotBe "" + presenter.response!!.password.length shouldBeGreaterThan 0 + + val savedUser = userGateway.findById(UserId(presenter.response!!.id)) + savedUser shouldNotBe null + savedUser!!.mustChangePassword shouldBe true + savedUser.isActive shouldBe true + savedUser.username.value shouldBe "mmuster" + savedUser.email.value shouldBe "max@test.de" + passwordHasher.verify(presenter.response!!.password, savedUser.passwordHash.value) shouldBe true + + testLogger.auditLogs.size shouldBe 1 + testLogger.auditLogs[0].message.value shouldContain "mmuster" + } + + "should reject duplicate username" { + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Existing"), + lastname = UserLastname("User"), + username = Username("mmuster"), + email = UserEmail("other@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + + val presenter = TestPresenter() + useCase.execute( + CreateUserRequestDto( + requestorId = adminId, + firstname = "Max", + lastname = "Mustermann", + username = "mmuster", + email = "max@test.de", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + presenter.error!!.message shouldContain "username" + } + + "should reject duplicate email" { + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Existing"), + lastname = UserLastname("User"), + username = Username("existing"), + email = UserEmail("max@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + + val presenter = TestPresenter() + useCase.execute( + CreateUserRequestDto( + requestorId = adminId, + firstname = "Max", + lastname = "Mustermann", + username = "mmuster", + email = "max@test.de", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + presenter.error!!.message shouldContain "email" + } + + "should reject non-admin requestor" { + val regularUser = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Regular"), + lastname = UserLastname("User"), + username = Username("regular"), + email = UserEmail("regular@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + + val presenter = TestPresenter() + useCase.execute( + CreateUserRequestDto( + requestorId = regularUser.id.value, + firstname = "Max", + lastname = "Mustermann", + username = "mmuster", + email = "max@test.de", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject requestor that does not exist" { + val presenter = TestPresenter() + useCase.execute( + CreateUserRequestDto( + requestorId = "non-existent-id", + firstname = "Max", + lastname = "Mustermann", + username = "mmuster", + email = "max@test.de", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject invalid firstname (blank)" { + val presenter = TestPresenter() + useCase.execute( + CreateUserRequestDto( + requestorId = adminId, + firstname = "", + lastname = "Mustermann", + username = "mmuster", + email = "max@test.de", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject invalid email format" { + val presenter = TestPresenter() + useCase.execute( + CreateUserRequestDto( + requestorId = adminId, + firstname = "Max", + lastname = "Mustermann", + username = "mmuster", + email = "invalid-email", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject username shorter than 3 characters" { + val presenter = TestPresenter() + useCase.execute( + CreateUserRequestDto( + requestorId = adminId, + firstname = "Max", + lastname = "Mustermann", + username = "ab", + email = "max@test.de", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject username with special characters" { + val presenter = TestPresenter() + useCase.execute( + CreateUserRequestDto( + requestorId = adminId, + firstname = "Max", + lastname = "Mustermann", + username = "user name!", + email = "max@test.de", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "generated password should be at least 8 characters" { + val presenter = TestPresenter() + useCase.execute( + CreateUserRequestDto( + requestorId = adminId, + firstname = "Max", + lastname = "Mustermann", + username = "mmuster", + email = "max@test.de", + ), + presenter, + ) + + presenter.error shouldBe null + presenter.response!!.password.length shouldBeGreaterThanOrEqualTo 8 + } + }) diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/DeactivateUserUseCaseTest.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/DeactivateUserUseCaseTest.kt new file mode 100644 index 00000000..6de34fc5 --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/DeactivateUserUseCaseTest.kt @@ -0,0 +1,160 @@ +package com.alleslocker.backend.bootstrap.integration.user + +import com.alleslocker.backend.application.common.ErrorResponse +import com.alleslocker.backend.application.common.factory.UseCaseFactory +import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.user.dto.request.DeactivateUserRequestDto +import com.alleslocker.backend.application.user.gateway.UserGateway +import com.alleslocker.backend.application.user.usecase.DeactivateUserUseCase +import com.alleslocker.backend.bootstrap.integration.config.TestLogger +import com.alleslocker.backend.bootstrap.integration.config.TestPresenter +import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig +import com.alleslocker.backend.domain.user.PasswordHash +import com.alleslocker.backend.domain.user.User +import com.alleslocker.backend.domain.user.UserEmail +import com.alleslocker.backend.domain.user.UserFirstname +import com.alleslocker.backend.domain.user.UserId +import com.alleslocker.backend.domain.user.UserLastname +import com.alleslocker.backend.domain.user.UserRole +import com.alleslocker.backend.domain.user.Username +import com.alleslocker.backend.persistence.user.repository.UserRepository +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.types.shouldBeInstanceOf +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles + +@SpringBootTest(classes = [TestUserIntegrationConfig::class]) +@ActiveProfiles("test") +class DeactivateUserUseCaseTest( + @Autowired private val useCaseFactory: UseCaseFactory, + @Autowired private val userGateway: UserGateway, + @Autowired private val userRepository: UserRepository, + @Autowired private val passwordHasher: PasswordHasher, + @Autowired private val testLogger: TestLogger, +) : FreeSpec({ + + val useCase: DeactivateUserUseCase = useCaseFactory.make(DeactivateUserUseCase::class) + + lateinit var adminId: String + lateinit var targetUserId: String + + beforeEach { + userRepository.deleteAll() + testLogger.clear() + val admin = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.ADMIN, + firstname = UserFirstname("Admin"), + lastname = UserLastname("User"), + username = Username("admin"), + email = UserEmail("admin@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("admin123")), + isActive = true, + mustChangePassword = false, + ), + ) + adminId = admin.id.value + + val targetUser = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Max"), + lastname = UserLastname("Mustermann"), + username = Username("mmuster"), + email = UserEmail("max@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + targetUserId = targetUser.id.value + } + + "should deactivate an active user" { + val presenter = TestPresenter() + + useCase.execute( + DeactivateUserRequestDto(requestorId = adminId, userId = targetUserId), + presenter, + ) + + presenter.error shouldBe null + val updatedUser = userGateway.findById(UserId(targetUserId)) + updatedUser!!.isActive shouldBe false + testLogger.auditLogs.size shouldBe 1 + } + + "should reject deactivating already deactivated user" { + userGateway.save( + userGateway.findById(UserId(targetUserId))!!.copy(isActive = false), + ) + + val presenter = TestPresenter() + useCase.execute( + DeactivateUserRequestDto(requestorId = adminId, userId = targetUserId), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject deactivation by non-admin" { + val regularUser = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Regular"), + lastname = UserLastname("User"), + username = Username("regular"), + email = UserEmail("regular@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + + val presenter = TestPresenter() + useCase.execute( + DeactivateUserRequestDto(requestorId = regularUser.id.value, userId = targetUserId), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject deactivation of non-existent user" { + val presenter = TestPresenter() + useCase.execute( + DeactivateUserRequestDto(requestorId = adminId, userId = "non-existent-id"), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject deactivation with non-existent requestor" { + val presenter = TestPresenter() + useCase.execute( + DeactivateUserRequestDto(requestorId = "non-existent-id", userId = targetUserId), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + }) diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/EditUserUseCaseTest.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/EditUserUseCaseTest.kt new file mode 100644 index 00000000..318ddc2a --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/EditUserUseCaseTest.kt @@ -0,0 +1,295 @@ +package com.alleslocker.backend.bootstrap.integration.user + +import com.alleslocker.backend.application.common.ErrorResponse +import com.alleslocker.backend.application.common.factory.UseCaseFactory +import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.user.dto.request.EditUserRequestDto +import com.alleslocker.backend.application.user.gateway.UserGateway +import com.alleslocker.backend.application.user.usecase.EditUserUseCase +import com.alleslocker.backend.bootstrap.integration.config.TestLogger +import com.alleslocker.backend.bootstrap.integration.config.TestPresenter +import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig +import com.alleslocker.backend.domain.user.PasswordHash +import com.alleslocker.backend.domain.user.User +import com.alleslocker.backend.domain.user.UserEmail +import com.alleslocker.backend.domain.user.UserFirstname +import com.alleslocker.backend.domain.user.UserId +import com.alleslocker.backend.domain.user.UserLastname +import com.alleslocker.backend.domain.user.UserRole +import com.alleslocker.backend.domain.user.Username +import com.alleslocker.backend.persistence.user.repository.UserRepository +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.types.shouldBeInstanceOf +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles + +@SpringBootTest(classes = [TestUserIntegrationConfig::class]) +@ActiveProfiles("test") +class EditUserUseCaseTest( + @Autowired private val useCaseFactory: UseCaseFactory, + @Autowired private val userGateway: UserGateway, + @Autowired private val userRepository: UserRepository, + @Autowired private val passwordHasher: PasswordHasher, + @Autowired private val testLogger: TestLogger, +) : FreeSpec({ + + val useCase: EditUserUseCase = useCaseFactory.make(EditUserUseCase::class) + + lateinit var adminId: String + lateinit var targetUserId: String + + beforeEach { + userRepository.deleteAll() + testLogger.clear() + val admin = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.ADMIN, + firstname = UserFirstname("Admin"), + lastname = UserLastname("User"), + username = Username("admin"), + email = UserEmail("admin@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("admin123")), + isActive = true, + mustChangePassword = false, + ), + ) + adminId = admin.id.value + + val targetUser = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Max"), + lastname = UserLastname("Mustermann"), + username = Username("mmuster"), + email = UserEmail("max@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + targetUserId = targetUser.id.value + } + + "should update firstname" { + val presenter = TestPresenter() + + useCase.execute( + EditUserRequestDto( + requestorId = adminId, + userId = targetUserId, + firstname = "Maximilian", + ), + presenter, + ) + + presenter.error shouldBe null + val updatedUser = userGateway.findById(UserId(targetUserId)) + updatedUser!!.firstname.value shouldBe "Maximilian" + updatedUser.lastname.value shouldBe "Mustermann" + updatedUser.username.value shouldBe "mmuster" + updatedUser.email.value shouldBe "max@test.de" + testLogger.auditLogs.size shouldBe 1 + } + + "should update multiple fields at once" { + val presenter = TestPresenter() + + useCase.execute( + EditUserRequestDto( + requestorId = adminId, + userId = targetUserId, + firstname = "Maximilian", + lastname = "Muster", + username = "mmuster2", + email = "maximilian@test.de", + ), + presenter, + ) + + presenter.error shouldBe null + val updatedUser = userGateway.findById(UserId(targetUserId)) + updatedUser!!.firstname.value shouldBe "Maximilian" + updatedUser.lastname.value shouldBe "Muster" + updatedUser.username.value shouldBe "mmuster2" + updatedUser.email.value shouldBe "maximilian@test.de" + } + + "should reject edit by non-admin" { + val regularUser = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Regular"), + lastname = UserLastname("User"), + username = Username("regular"), + email = UserEmail("regular@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + + val presenter = TestPresenter() + useCase.execute( + EditUserRequestDto( + requestorId = regularUser.id.value, + userId = targetUserId, + firstname = "Changed", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject edit of non-existent user" { + val presenter = TestPresenter() + useCase.execute( + EditUserRequestDto( + requestorId = adminId, + userId = "non-existent-id", + firstname = "Changed", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject edit with non-existent requestor" { + val presenter = TestPresenter() + useCase.execute( + EditUserRequestDto( + requestorId = "non-existent-id", + userId = targetUserId, + firstname = "Changed", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject duplicate email" { + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Other"), + lastname = UserLastname("User"), + username = Username("other"), + email = UserEmail("other@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + + val presenter = TestPresenter() + useCase.execute( + EditUserRequestDto( + requestorId = adminId, + userId = targetUserId, + email = "other@test.de", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + presenter.error!!.message shouldContain "email" + } + + "should reject duplicate username" { + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Other"), + lastname = UserLastname("User"), + username = Username("other"), + email = UserEmail("other@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + + val presenter = TestPresenter() + useCase.execute( + EditUserRequestDto( + requestorId = adminId, + userId = targetUserId, + username = "other", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + presenter.error!!.message shouldContain "username" + } + + "should allow setting own email (no uniqueness conflict)" { + val presenter = TestPresenter() + useCase.execute( + EditUserRequestDto( + requestorId = adminId, + userId = targetUserId, + email = "max@test.de", + ), + presenter, + ) + + presenter.error shouldBe null + } + + "should allow setting own username (no uniqueness conflict)" { + val presenter = TestPresenter() + useCase.execute( + EditUserRequestDto( + requestorId = adminId, + userId = targetUserId, + username = "mmuster", + ), + presenter, + ) + + presenter.error shouldBe null + } + + "should be a no-op when all fields are null" { + val presenter = TestPresenter() + useCase.execute( + EditUserRequestDto( + requestorId = adminId, + userId = targetUserId, + ), + presenter, + ) + + presenter.error shouldBe null + val updatedUser = userGateway.findById(UserId(targetUserId)) + updatedUser!!.firstname.value shouldBe "Max" + updatedUser.lastname.value shouldBe "Mustermann" + updatedUser.username.value shouldBe "mmuster" + updatedUser.email.value shouldBe "max@test.de" + } + }) diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUserUseCaseTest.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUserUseCaseTest.kt new file mode 100644 index 00000000..78a70b6b --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUserUseCaseTest.kt @@ -0,0 +1,96 @@ +package com.alleslocker.backend.bootstrap.integration.user + +import com.alleslocker.backend.application.common.ErrorResponse +import com.alleslocker.backend.application.common.factory.UseCaseFactory +import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.user.dto.request.GetUserRequestDto +import com.alleslocker.backend.application.user.dto.response.GetUserResponseDto +import com.alleslocker.backend.application.user.gateway.UserGateway +import com.alleslocker.backend.application.user.usecase.GetUserUseCase +import com.alleslocker.backend.bootstrap.integration.config.TestPresenter +import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig +import com.alleslocker.backend.domain.user.PasswordHash +import com.alleslocker.backend.domain.user.User +import com.alleslocker.backend.domain.user.UserEmail +import com.alleslocker.backend.domain.user.UserFirstname +import com.alleslocker.backend.domain.user.UserId +import com.alleslocker.backend.domain.user.UserLastname +import com.alleslocker.backend.domain.user.UserRole +import com.alleslocker.backend.domain.user.Username +import com.alleslocker.backend.persistence.user.repository.UserRepository +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.types.shouldBeInstanceOf +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles + +@SpringBootTest(classes = [TestUserIntegrationConfig::class]) +@ActiveProfiles("test") +class GetUserUseCaseTest( + @Autowired private val useCaseFactory: UseCaseFactory, + @Autowired private val userGateway: UserGateway, + @Autowired private val userRepository: UserRepository, + @Autowired private val passwordHasher: PasswordHasher, +) : FreeSpec({ + + val useCase: GetUserUseCase = useCaseFactory.make(GetUserUseCase::class) + + lateinit var savedUserId: String + + beforeEach { + userRepository.deleteAll() + val saved = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Max"), + lastname = UserLastname("Mustermann"), + username = Username("mmuster"), + email = UserEmail("max@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("secure123")), + isActive = true, + mustChangePassword = false, + ), + ) + savedUserId = saved.id.value + } + + "should return user by id" { + val presenter = TestPresenter() + + useCase.execute(GetUserRequestDto(id = savedUserId), presenter) + + presenter.error shouldBe null + presenter.response shouldNotBe null + presenter.response!!.user.id shouldBe savedUserId + presenter.response!!.user.username shouldBe "mmuster" + presenter.response!!.user.email shouldBe "max@test.de" + presenter.response!!.user.firstname shouldBe "Max" + presenter.response!!.user.lastname shouldBe "Mustermann" + presenter.response!!.user.isActive shouldBe true + presenter.response!!.user.mustChangePassword shouldBe false + } + + "should return not found for non-existent id" { + val presenter = TestPresenter() + + useCase.execute(GetUserRequestDto(id = "non-existent-id"), presenter) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should return bad request for empty id" { + val presenter = TestPresenter() + + useCase.execute(GetUserRequestDto(id = ""), presenter) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + }) diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUsersPagedUseCaseTest.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUsersPagedUseCaseTest.kt new file mode 100644 index 00000000..65e4cc0d --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUsersPagedUseCaseTest.kt @@ -0,0 +1,261 @@ +package com.alleslocker.backend.bootstrap.integration.user + +import com.alleslocker.backend.application.common.ErrorResponse +import com.alleslocker.backend.application.common.factory.UseCaseFactory +import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.user.dto.filter.UserFilterDto +import com.alleslocker.backend.application.user.dto.request.GetUsersPagedRequestDto +import com.alleslocker.backend.application.user.dto.response.GetUsersPagedResponseDto +import com.alleslocker.backend.application.user.gateway.UserGateway +import com.alleslocker.backend.application.user.usecase.GetUsersPagedUseCase +import com.alleslocker.backend.bootstrap.integration.config.TestPresenter +import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig +import com.alleslocker.backend.domain.user.PasswordHash +import com.alleslocker.backend.domain.user.User +import com.alleslocker.backend.domain.user.UserEmail +import com.alleslocker.backend.domain.user.UserFirstname +import com.alleslocker.backend.domain.user.UserId +import com.alleslocker.backend.domain.user.UserLastname +import com.alleslocker.backend.domain.user.UserRole +import com.alleslocker.backend.domain.user.Username +import com.alleslocker.backend.persistence.user.repository.UserRepository +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.types.shouldBeInstanceOf +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles + +@SpringBootTest(classes = [TestUserIntegrationConfig::class]) +@ActiveProfiles("test") +class GetUsersPagedUseCaseTest( + @Autowired private val useCaseFactory: UseCaseFactory, + @Autowired private val userGateway: UserGateway, + @Autowired private val userRepository: UserRepository, + @Autowired private val passwordHasher: PasswordHasher, +) : FreeSpec({ + + val useCase: GetUsersPagedUseCase = useCaseFactory.make(GetUsersPagedUseCase::class) + + beforeEach { + userRepository.deleteAll() + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Max"), + lastname = UserLastname("Mustermann"), + username = Username("mmuster"), + email = UserEmail("max@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.ADMIN, + firstname = UserFirstname("Anna"), + lastname = UserLastname("Admin"), + username = Username("aadmin"), + email = UserEmail("anna@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass456")), + isActive = true, + mustChangePassword = false, + ), + ) + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Tom"), + lastname = UserLastname("Test"), + username = Username("ttest"), + email = UserEmail("tom@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass789")), + isActive = false, + mustChangePassword = true, + ), + ) + } + + "should return all users on page 0 with size 10" { + val presenter = TestPresenter() + + useCase.execute( + GetUsersPagedRequestDto( + requesterId = "", + filter = UserFilterDto(), + page = 0, + size = 10, + ), + presenter, + ) + + presenter.error shouldBe null + presenter.response shouldNotBe null + presenter.response!! + .page.content.size shouldBe 3 + presenter.response!!.page.totalElements shouldBe 3 + presenter.response!!.page.totalPages shouldBe 1 + } + + "should paginate correctly" { + val presenter = TestPresenter() + + useCase.execute( + GetUsersPagedRequestDto( + requesterId = "", + filter = UserFilterDto(), + page = 0, + size = 1, + ), + presenter, + ) + + presenter.error shouldBe null + presenter.response!! + .page.content.size shouldBe 1 + presenter.response!!.page.totalElements shouldBe 3 + presenter.response!!.page.totalPages shouldBe 3 + presenter.response!!.page.hasNext() shouldBe true + } + + "should filter by search term (firstname)" { + val presenter = TestPresenter() + + useCase.execute( + GetUsersPagedRequestDto( + requesterId = "", + filter = UserFilterDto(search = "Max"), + page = 0, + size = 10, + ), + presenter, + ) + + presenter.error shouldBe null + presenter.response!! + .page.content.size shouldBe 1 + presenter.response!! + .page.content[0] + .username shouldBe "mmuster" + } + + "should filter by search term (email)" { + val presenter = TestPresenter() + + useCase.execute( + GetUsersPagedRequestDto( + requesterId = "", + filter = UserFilterDto(search = "anna@test.de"), + page = 0, + size = 10, + ), + presenter, + ) + + presenter.error shouldBe null + presenter.response!! + .page.content.size shouldBe 1 + presenter.response!! + .page.content[0] + .username shouldBe "aadmin" + } + + "should filter by search term (lastname)" { + val presenter = TestPresenter() + + useCase.execute( + GetUsersPagedRequestDto( + requesterId = "", + filter = UserFilterDto(search = "Mustermann"), + page = 0, + size = 10, + ), + presenter, + ) + + presenter.error shouldBe null + presenter.response!! + .page.content.size shouldBe 1 + presenter.response!! + .page.content[0] + .username shouldBe "mmuster" + } + + "should return empty page when filter matches nothing" { + val presenter = TestPresenter() + + useCase.execute( + GetUsersPagedRequestDto( + requesterId = "", + filter = UserFilterDto(search = "xxxxxxxxx"), + page = 0, + size = 10, + ), + presenter, + ) + + presenter.error shouldBe null + presenter.response!! + .page.content.size shouldBe 0 + presenter.response!!.page.totalElements shouldBe 0 + } + + "should reject negative page" { + val presenter = TestPresenter() + + useCase.execute( + GetUsersPagedRequestDto( + requesterId = "", + filter = UserFilterDto(), + page = -1, + size = 10, + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject size of 0" { + val presenter = TestPresenter() + + useCase.execute( + GetUsersPagedRequestDto( + requesterId = "", + filter = UserFilterDto(), + page = 0, + size = 0, + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject size greater than 500" { + val presenter = TestPresenter() + + useCase.execute( + GetUsersPagedRequestDto( + requesterId = "", + filter = UserFilterDto(), + page = 0, + size = 501, + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + }) diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/LoginUserUseCaseTest.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/LoginUserUseCaseTest.kt new file mode 100644 index 00000000..df480c5c --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/LoginUserUseCaseTest.kt @@ -0,0 +1,97 @@ +package com.alleslocker.backend.bootstrap.integration.user + +import com.alleslocker.backend.application.common.ErrorResponse +import com.alleslocker.backend.application.common.factory.UseCaseFactory +import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.user.dto.request.LoginUserRequestDto +import com.alleslocker.backend.application.user.dto.response.LoginUserResponseDto +import com.alleslocker.backend.application.user.gateway.UserGateway +import com.alleslocker.backend.application.user.usecase.LoginUserUseCase +import com.alleslocker.backend.bootstrap.integration.config.TestPresenter +import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig +import com.alleslocker.backend.domain.user.PasswordHash +import com.alleslocker.backend.domain.user.User +import com.alleslocker.backend.domain.user.UserEmail +import com.alleslocker.backend.domain.user.UserFirstname +import com.alleslocker.backend.domain.user.UserId +import com.alleslocker.backend.domain.user.UserLastname +import com.alleslocker.backend.domain.user.UserRole +import com.alleslocker.backend.domain.user.Username +import com.alleslocker.backend.persistence.user.repository.UserRepository +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.types.shouldBeInstanceOf +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles + +@SpringBootTest(classes = [TestUserIntegrationConfig::class]) +@ActiveProfiles("test") +class LoginUserUseCaseTest( + @Autowired private val useCaseFactory: UseCaseFactory, + @Autowired private val userGateway: UserGateway, + @Autowired private val userRepository: UserRepository, + @Autowired private val passwordHasher: PasswordHasher, +) : FreeSpec({ + + val useCase: LoginUserUseCase = useCaseFactory.make(LoginUserUseCase::class) + + beforeEach { + userRepository.deleteAll() + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Max"), + lastname = UserLastname("Mustermann"), + username = Username("mmuster"), + email = UserEmail("max@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("secure123")), + isActive = true, + mustChangePassword = false, + ), + ) + } + + "should login with correct credentials" { + val presenter = TestPresenter() + + useCase.execute(LoginUserRequestDto(username = "mmuster", password = "secure123"), presenter) + + presenter.error shouldBe null + presenter.response shouldNotBe null + presenter.response!!.userId shouldNotBe "" + } + + "should fail with wrong password" { + val presenter = TestPresenter() + + useCase.execute(LoginUserRequestDto(username = "mmuster", password = "wrong"), presenter) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + presenter.error!!.message shouldBe "Invalid password" + } + + "should fail with non-existent username" { + val presenter = TestPresenter() + + useCase.execute(LoginUserRequestDto(username = "nobody", password = "secure123"), presenter) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + presenter.error!!.message shouldBe "User doesn't exist" + } + + "should fail with empty username" { + val presenter = TestPresenter() + + useCase.execute(LoginUserRequestDto(username = "", password = "secure123"), presenter) + + presenter.response shouldBe null + presenter.error shouldNotBe null + } + }) diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/RequestUserPasswordChangeUseCaseTest.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/RequestUserPasswordChangeUseCaseTest.kt new file mode 100644 index 00000000..1e679cae --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/RequestUserPasswordChangeUseCaseTest.kt @@ -0,0 +1,163 @@ +package com.alleslocker.backend.bootstrap.integration.user + +import com.alleslocker.backend.application.common.ErrorResponse +import com.alleslocker.backend.application.common.factory.UseCaseFactory +import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.user.dto.request.RequestUserPasswordChangeRequestDto +import com.alleslocker.backend.application.user.gateway.UserGateway +import com.alleslocker.backend.application.user.usecase.RequestUserPasswordChangeUseCase +import com.alleslocker.backend.bootstrap.integration.config.TestLogger +import com.alleslocker.backend.bootstrap.integration.config.TestPresenter +import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig +import com.alleslocker.backend.domain.user.PasswordHash +import com.alleslocker.backend.domain.user.User +import com.alleslocker.backend.domain.user.UserEmail +import com.alleslocker.backend.domain.user.UserFirstname +import com.alleslocker.backend.domain.user.UserId +import com.alleslocker.backend.domain.user.UserLastname +import com.alleslocker.backend.domain.user.UserRole +import com.alleslocker.backend.domain.user.Username +import com.alleslocker.backend.persistence.user.repository.UserRepository +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.types.shouldBeInstanceOf +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles + +@SpringBootTest(classes = [TestUserIntegrationConfig::class]) +@ActiveProfiles("test") +class RequestUserPasswordChangeUseCaseTest( + @Autowired private val useCaseFactory: UseCaseFactory, + @Autowired private val userGateway: UserGateway, + @Autowired private val userRepository: UserRepository, + @Autowired private val passwordHasher: PasswordHasher, + @Autowired private val testLogger: TestLogger, +) : FreeSpec({ + + val useCase: RequestUserPasswordChangeUseCase = + useCaseFactory.make(RequestUserPasswordChangeUseCase::class) + + lateinit var adminId: String + lateinit var targetUserId: String + + beforeEach { + userRepository.deleteAll() + testLogger.clear() + val admin = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.ADMIN, + firstname = UserFirstname("Admin"), + lastname = UserLastname("User"), + username = Username("admin"), + email = UserEmail("admin@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("admin123")), + isActive = true, + mustChangePassword = false, + ), + ) + adminId = admin.id.value + + val targetUser = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Max"), + lastname = UserLastname("Mustermann"), + username = Username("mmuster"), + email = UserEmail("max@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + targetUserId = targetUser.id.value + } + + "should request password change for user" { + val presenter = TestPresenter() + + useCase.execute( + RequestUserPasswordChangeRequestDto(requestorId = adminId, userId = targetUserId), + presenter, + ) + + presenter.error shouldBe null + val updatedUser = userGateway.findById(UserId(targetUserId)) + updatedUser!!.mustChangePassword shouldBe true + testLogger.auditLogs.size shouldBe 1 + } + + "should reject if user already requested to change password" { + userGateway.save( + userGateway.findById(UserId(targetUserId))!!.copy(mustChangePassword = true), + ) + + val presenter = TestPresenter() + useCase.execute( + RequestUserPasswordChangeRequestDto(requestorId = adminId, userId = targetUserId), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + presenter.error!!.message shouldContain "already requested" + } + + "should reject request by non-admin" { + val regularUser = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Regular"), + lastname = UserLastname("User"), + username = Username("regular"), + email = UserEmail("regular@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("pass123")), + isActive = true, + mustChangePassword = false, + ), + ) + + val presenter = TestPresenter() + useCase.execute( + RequestUserPasswordChangeRequestDto(requestorId = regularUser.id.value, userId = targetUserId), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject request for non-existent user" { + val presenter = TestPresenter() + useCase.execute( + RequestUserPasswordChangeRequestDto(requestorId = adminId, userId = "non-existent-id"), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + + "should reject request with non-existent requestor" { + val presenter = TestPresenter() + useCase.execute( + RequestUserPasswordChangeRequestDto(requestorId = "non-existent-id", userId = targetUserId), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + }) diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ResetPasswordUserUseCaseTest.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ResetPasswordUserUseCaseTest.kt new file mode 100644 index 00000000..efe7c028 --- /dev/null +++ b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ResetPasswordUserUseCaseTest.kt @@ -0,0 +1,118 @@ +package com.alleslocker.backend.bootstrap.integration.user + +import com.alleslocker.backend.application.common.ErrorResponse +import com.alleslocker.backend.application.common.factory.UseCaseFactory +import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.user.dto.request.ResetPasswordUserRequestDto +import com.alleslocker.backend.application.user.dto.response.ResetPasswordUserResponseDto +import com.alleslocker.backend.application.user.gateway.UserGateway +import com.alleslocker.backend.application.user.usecase.ResetPasswordUserUseCase +import com.alleslocker.backend.bootstrap.integration.config.TestPresenter +import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig +import com.alleslocker.backend.domain.user.PasswordHash +import com.alleslocker.backend.domain.user.User +import com.alleslocker.backend.domain.user.UserEmail +import com.alleslocker.backend.domain.user.UserFirstname +import com.alleslocker.backend.domain.user.UserId +import com.alleslocker.backend.domain.user.UserLastname +import com.alleslocker.backend.domain.user.UserRole +import com.alleslocker.backend.domain.user.Username +import com.alleslocker.backend.persistence.user.repository.UserRepository +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.types.shouldBeInstanceOf +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles + +@SpringBootTest(classes = [TestUserIntegrationConfig::class]) +@ActiveProfiles("test") +class ResetPasswordUserUseCaseTest( + @Autowired private val useCaseFactory: UseCaseFactory, + @Autowired private val userGateway: UserGateway, + @Autowired private val userRepository: UserRepository, + @Autowired private val passwordHasher: PasswordHasher, +) : FreeSpec({ + + val useCase: ResetPasswordUserUseCase = + useCaseFactory.make(ResetPasswordUserUseCase::class) + + lateinit var userId: String + + beforeEach { + userRepository.deleteAll() + val saved = + userGateway.save( + User( + id = UserId.generate(), + role = UserRole.USER, + firstname = UserFirstname("Max"), + lastname = UserLastname("Mustermann"), + username = Username("mmuster"), + email = UserEmail("max@test.de"), + passwordHash = PasswordHash(passwordHasher.hash("oldPass123")), + isActive = true, + mustChangePassword = false, + ), + ) + userId = saved.id.value + } + + "should reset password with correct old password" { + val presenter = TestPresenter() + + useCase.execute( + ResetPasswordUserRequestDto( + requestorId = userId, + oldPassword = "oldPass123", + newPassword = "newPass456", + ), + presenter, + ) + + presenter.error shouldBe null + presenter.response shouldNotBe null + presenter.response!!.userId shouldBe userId + + val updatedUser = userGateway.findById(UserId(userId)) + updatedUser!!.mustChangePassword shouldBe false + passwordHasher.verify("newPass456", updatedUser.passwordHash.value) shouldBe true + passwordHasher.verify("oldPass123", updatedUser.passwordHash.value) shouldBe false + } + + "should reject wrong old password" { + val presenter = TestPresenter() + + useCase.execute( + ResetPasswordUserRequestDto( + requestorId = userId, + oldPassword = "wrongPassword", + newPassword = "newPass456", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + presenter.error!!.message shouldBe "Invalid old password" + } + + "should reject reset for non-existent user" { + val presenter = TestPresenter() + + useCase.execute( + ResetPasswordUserRequestDto( + requestorId = "non-existent-id", + oldPassword = "oldPass123", + newPassword = "newPass456", + ), + presenter, + ) + + presenter.response shouldBe null + presenter.error shouldNotBe null + presenter.error!!.shouldBeInstanceOf() + } + }) diff --git a/bootstrap/src/test/resources/application-test.yml b/bootstrap/src/test/resources/application-test.yml new file mode 100644 index 00000000..03c726c8 --- /dev/null +++ b/bootstrap/src/test/resources/application-test.yml @@ -0,0 +1,19 @@ +spring: + main: + allow-bean-definition-overriding: true + datasource: + url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MariaDB;NON_KEYWORDS=USER,VALUE + driver-class-name: org.h2.Driver + jpa: + hibernate: + ddl-auto: create-drop + database-platform: org.hibernate.dialect.H2Dialect + properties: + hibernate: + show_sql: false + autoconfigure: + exclude: org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration + +app: + security: + master-key: A7wGBfP2Iz2tYU26/jBz4wUmfM+gpgGqAPTVezyXygY= diff --git a/web/src/main/kotlin/com/alleslocker/backend/web/user/controller/UserController.kt b/web/src/main/kotlin/com/alleslocker/backend/web/user/controller/UserController.kt index 4168bba2..ed69453b 100644 --- a/web/src/main/kotlin/com/alleslocker/backend/web/user/controller/UserController.kt +++ b/web/src/main/kotlin/com/alleslocker/backend/web/user/controller/UserController.kt @@ -5,7 +5,6 @@ import com.alleslocker.backend.application.common.factory.UseCaseFactory import com.alleslocker.backend.application.user.usecase.ActivateUserUseCase import com.alleslocker.backend.application.user.usecase.AdminResetPasswordUserUseCase import com.alleslocker.backend.application.user.usecase.ChangeUserRoleUseCase -import com.alleslocker.backend.application.user.usecase.ChangeUserRoleUseCaseImpl import com.alleslocker.backend.application.user.usecase.CreateUserUseCase import com.alleslocker.backend.application.user.usecase.DeactivateUserUseCase import com.alleslocker.backend.application.user.usecase.EditUserUseCase From 6c8d7c9abb8515bd529bc1170fba94d15ea9fa6e Mon Sep 17 00:00:00 2001 From: Luca Schumacher Date: Tue, 16 Jun 2026 22:40:30 +0200 Subject: [PATCH 2/2] feat(build): update test dependencies for improved testing capabilities --- application/build.gradle | 5 + .../application}/CleanArchitectureTest.kt | 2 +- .../integration/config/TestAdapterFactory.kt | 31 +++ .../integration/config/TestGatewayFactory.kt | 33 +++ .../integration/config/TestLogger.kt | 2 +- .../integration/config/TestPasswordHasher.kt | 2 +- .../integration/config/TestPresenter.kt | 2 +- .../config/TestUserGatewayAdapter.kt | 200 ++++++++++++++++++ .../integration/config/UserTestContext.kt | 24 +++ .../user/ActivateUserUseCaseTest.kt | 55 ++--- .../user/AdminResetPasswordUserUseCaseTest.kt | 54 ++--- .../user/ChangeUserRoleUseCaseTest.kt | 57 ++--- .../integration/user/CreateUserUseCaseTest.kt | 59 ++---- .../user/DeactivateUserUseCaseTest.kt | 55 ++--- .../integration/user/EditUserUseCaseTest.kt | 61 ++---- .../integration/user/GetUserUseCaseTest.kt | 33 +-- .../user/GetUsersPagedUseCaseTest.kt | 41 ++-- .../integration/user/LoginUserUseCaseTest.kt | 33 +-- .../RequestUserPasswordChangeUseCaseTest.kt | 56 ++--- .../user/ResetPasswordUserUseCaseTest.kt | 40 ++-- bootstrap/build.gradle | 6 - .../config/TestUserIntegrationConfig.kt | 104 --------- .../src/test/resources/application-test.yml | 19 -- 23 files changed, 489 insertions(+), 485 deletions(-) rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/CleanArchitectureTest.kt (98%) create mode 100644 application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestAdapterFactory.kt create mode 100644 application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestGatewayFactory.kt rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/config/TestLogger.kt (92%) rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/config/TestPasswordHasher.kt (87%) rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/config/TestPresenter.kt (86%) create mode 100644 application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestUserGatewayAdapter.kt create mode 100644 application/src/test/kotlin/com/alleslocker/backend/application/integration/config/UserTestContext.kt rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/user/ActivateUserUseCaseTest.kt (71%) rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/user/AdminResetPasswordUserUseCaseTest.kt (72%) rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/user/ChangeUserRoleUseCaseTest.kt (74%) rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/user/CreateUserUseCaseTest.kt (82%) rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/user/DeactivateUserUseCaseTest.kt (71%) rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/user/EditUserUseCaseTest.kt (81%) rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/user/GetUserUseCaseTest.kt (70%) rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/user/GetUsersPagedUseCaseTest.kt (84%) rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/user/LoginUserUseCaseTest.kt (70%) rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/user/RequestUserPasswordChangeUseCaseTest.kt (71%) rename {bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap => application/src/test/kotlin/com/alleslocker/backend/application}/integration/user/ResetPasswordUserUseCaseTest.kt (68%) delete mode 100644 bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestUserIntegrationConfig.kt delete mode 100644 bootstrap/src/test/resources/application-test.yml diff --git a/application/build.gradle b/application/build.gradle index 4a2c3cbc..ff75c345 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -1,3 +1,8 @@ dependencies { implementation(project(":domain")) + testImplementation(libs.h2) + testImplementation(libs.konsist) + testImplementation(libs.kotest.engine) + testImplementation(libs.kotest.assertions) + testImplementation(libs.kotest.runner) } diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/CleanArchitectureTest.kt b/application/src/test/kotlin/com/alleslocker/backend/application/CleanArchitectureTest.kt similarity index 98% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/CleanArchitectureTest.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/CleanArchitectureTest.kt index b5113bd5..00326ba7 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/CleanArchitectureTest.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/CleanArchitectureTest.kt @@ -1,4 +1,4 @@ -package com.alleslocker.backend.bootstrap +package com.alleslocker.backend.application import com.lemonappdev.konsist.api.Konsist import com.lemonappdev.konsist.api.architecture.KoArchitectureCreator.assertArchitecture diff --git a/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestAdapterFactory.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestAdapterFactory.kt new file mode 100644 index 00000000..02a8f01b --- /dev/null +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestAdapterFactory.kt @@ -0,0 +1,31 @@ +package com.alleslocker.backend.application.integration.config + +import com.alleslocker.backend.application.common.adapter.Adapter +import com.alleslocker.backend.application.common.factory.AdapterFactory +import java.lang.reflect.Proxy +import kotlin.reflect.KClass + +class TestAdapterFactory( + private val adapters: List, +) : AdapterFactory { + override fun make(adapter: KClass): T { + val match = adapters.firstOrNull { adapter.isInstance(it) } + if (match != null) { + @Suppress("UNCHECKED_CAST") + return match as T + } + + @Suppress("UNCHECKED_CAST") + return Proxy.newProxyInstance( + adapter.java.classLoader, + arrayOf(adapter.java), + ) { _, method, _ -> + when (method.returnType) { + Boolean::class.javaPrimitiveType -> false + Int::class.javaPrimitiveType -> 0 + Long::class.javaPrimitiveType -> 0L + else -> null + } + } as T + } +} diff --git a/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestGatewayFactory.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestGatewayFactory.kt new file mode 100644 index 00000000..248289ae --- /dev/null +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestGatewayFactory.kt @@ -0,0 +1,33 @@ +package com.alleslocker.backend.application.integration.config + +import com.alleslocker.backend.application.common.factory.GatewayFactory +import com.alleslocker.backend.application.common.gateway.Gateway +import java.lang.reflect.Proxy +import kotlin.reflect.KClass + +class TestGatewayFactory( + private val gateways: List, +) : GatewayFactory { + override fun make(gateway: KClass): T { + val match = gateways.firstOrNull { gateway.isInstance(it) } + if (match != null) { + @Suppress("UNCHECKED_CAST") + return match as T + } + + @Suppress("UNCHECKED_CAST") + return Proxy.newProxyInstance( + gateway.java.classLoader, + arrayOf(gateway.java), + ) { _, method, _ -> + when (method.returnType) { + Boolean::class.javaPrimitiveType -> false + Int::class.javaPrimitiveType -> 0 + Long::class.javaPrimitiveType -> 0L + else -> null + } + } as T + } + + override fun migrate() {} +} diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestLogger.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestLogger.kt similarity index 92% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestLogger.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestLogger.kt index 65ad3350..32bef574 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestLogger.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestLogger.kt @@ -1,4 +1,4 @@ -package com.alleslocker.backend.bootstrap.integration.config +package com.alleslocker.backend.application.integration.config import com.alleslocker.backend.application.common.Logger import com.alleslocker.backend.domain.auditlog.AuditLog diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPasswordHasher.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestPasswordHasher.kt similarity index 87% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPasswordHasher.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestPasswordHasher.kt index 1e648891..115ae654 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPasswordHasher.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestPasswordHasher.kt @@ -1,4 +1,4 @@ -package com.alleslocker.backend.bootstrap.integration.config +package com.alleslocker.backend.application.integration.config import com.alleslocker.backend.application.common.security.PasswordHasher import java.security.MessageDigest diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPresenter.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestPresenter.kt similarity index 86% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPresenter.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestPresenter.kt index f16ddf1f..186f109c 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestPresenter.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestPresenter.kt @@ -1,4 +1,4 @@ -package com.alleslocker.backend.bootstrap.integration.config +package com.alleslocker.backend.application.integration.config import com.alleslocker.backend.application.common.ErrorResponse import com.alleslocker.backend.application.common.OutputBoundary diff --git a/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestUserGatewayAdapter.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestUserGatewayAdapter.kt new file mode 100644 index 00000000..c808526f --- /dev/null +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/TestUserGatewayAdapter.kt @@ -0,0 +1,200 @@ +package com.alleslocker.backend.application.integration.config + +import com.alleslocker.backend.application.common.model.Page +import com.alleslocker.backend.application.user.dto.filter.UserFilterDto +import com.alleslocker.backend.application.user.gateway.UserGateway +import com.alleslocker.backend.domain.user.PasswordHash +import com.alleslocker.backend.domain.user.User +import com.alleslocker.backend.domain.user.UserEmail +import com.alleslocker.backend.domain.user.UserFirstname +import com.alleslocker.backend.domain.user.UserId +import com.alleslocker.backend.domain.user.UserLastname +import com.alleslocker.backend.domain.user.UserRole +import com.alleslocker.backend.domain.user.Username +import java.sql.Connection +import java.sql.DriverManager + +class TestUserGatewayAdapter : UserGateway { + private val connection: Connection = DriverManager.getConnection(JDBC_URL) + + init { + createTable() + } + + fun deleteAll() { + connection.createStatement().use { stmt -> + stmt.execute("""DELETE FROM "user" """) + } + } + + private fun createTable() { + connection.createStatement().use { stmt -> + stmt.execute( + """ + CREATE TABLE IF NOT EXISTS "user" ( + id VARCHAR(255) PRIMARY KEY, + role VARCHAR(50) NOT NULL, + firstname VARCHAR(100) NOT NULL, + lastname VARCHAR(100) NOT NULL, + username VARCHAR(100) NOT NULL UNIQUE, + email VARCHAR(255) NOT NULL UNIQUE, + password_hash VARCHAR(255) NOT NULL, + is_active BOOLEAN NOT NULL, + must_change_password BOOLEAN NOT NULL + ) + """.trimIndent(), + ) + } + } + + override fun save(entity: User): User { + val existing = findById(entity.id) + if (existing != null) { + connection + .prepareStatement( + """ + UPDATE "user" SET role=?, firstname=?, lastname=?, username=?, email=?, password_hash=?, is_active=?, must_change_password=? + WHERE id=? + """.trimIndent(), + ).use { stmt -> + stmt.setString(1, entity.role.name) + stmt.setString(2, entity.firstname.value) + stmt.setString(3, entity.lastname.value) + stmt.setString(4, entity.username.value) + stmt.setString(5, entity.email.value) + stmt.setString(6, entity.passwordHash.value) + stmt.setBoolean(7, entity.isActive) + stmt.setBoolean(8, entity.mustChangePassword) + stmt.setString(9, entity.id.value) + stmt.executeUpdate() + } + } else { + connection + .prepareStatement( + """ + INSERT INTO "user" (id, role, firstname, lastname, username, email, password_hash, is_active, must_change_password) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + """.trimIndent(), + ).use { stmt -> + stmt.setString(1, entity.id.value) + stmt.setString(2, entity.role.name) + stmt.setString(3, entity.firstname.value) + stmt.setString(4, entity.lastname.value) + stmt.setString(5, entity.username.value) + stmt.setString(6, entity.email.value) + stmt.setString(7, entity.passwordHash.value) + stmt.setBoolean(8, entity.isActive) + stmt.setBoolean(9, entity.mustChangePassword) + stmt.executeUpdate() + } + } + return entity + } + + override fun deleteById(id: UserId) { + connection.prepareStatement("""DELETE FROM "user" WHERE id=?""").use { stmt -> + stmt.setString(1, id.value) + stmt.executeUpdate() + } + } + + override fun findById(id: UserId): User? { + connection.prepareStatement("""SELECT * FROM "user" WHERE id=?""").use { stmt -> + stmt.setString(1, id.value) + stmt.executeQuery().use { rs -> + if (rs.next()) return mapRow(rs) + } + } + return null + } + + override fun exists(id: UserId): Boolean = findById(id) != null + + override fun findByUsername(username: String): User? { + connection.prepareStatement("""SELECT * FROM "user" WHERE username=?""").use { stmt -> + stmt.setString(1, username) + stmt.executeQuery().use { rs -> + if (rs.next()) return mapRow(rs) + } + } + return null + } + + override fun findByEmail(email: String): User? { + connection.prepareStatement("""SELECT * FROM "user" WHERE email=?""").use { stmt -> + stmt.setString(1, email) + stmt.executeQuery().use { rs -> + if (rs.next()) return mapRow(rs) + } + } + return null + } + + override fun existsByEmail(email: String): Boolean = findByEmail(email) != null + + override fun existsByUsername(username: String): Boolean = findByUsername(username) != null + + override fun getAllUsersPaged( + filter: UserFilterDto, + page: Int, + size: Int, + ): Page { + val search = filter.search + val whereClause = + if (search != null) { + "WHERE LOWER(firstname) LIKE ? OR LOWER(lastname) LIKE ? OR LOWER(email) LIKE ? OR LOWER(username) LIKE ?" + } else { + "" + } + + val totalElements = + connection.prepareStatement("""SELECT COUNT(*) FROM "user" $whereClause""").use { countStmt -> + if (search != null) { + val pattern = "%${search.lowercase()}%" + (1..4).forEach { countStmt.setString(it, pattern) } + } + countStmt.executeQuery().use { rs -> if (rs.next()) rs.getLong(1) else 0L } + } + + val totalPages = ((totalElements + size.toLong() - 1) / size).toInt() + val offset = page * size + + val dataSql = """SELECT * FROM "user" $whereClause ORDER BY id DESC LIMIT ? OFFSET ?""" + val content = + connection.prepareStatement(dataSql).use { dataStmt -> + if (search != null) { + val pattern = "%${search.lowercase()}%" + (1..4).forEach { dataStmt.setString(it, pattern) } + dataStmt.setInt(5, size) + dataStmt.setInt(6, offset) + } else { + dataStmt.setInt(1, size) + dataStmt.setInt(2, offset) + } + dataStmt.executeQuery().use { rs -> + val list = mutableListOf() + while (rs.next()) list.add(mapRow(rs)) + list + } + } + + return Page(content, page, size, totalElements, totalPages) + } + + private fun mapRow(rs: java.sql.ResultSet): User = + User( + id = UserId(rs.getString("id")), + role = UserRole.valueOf(rs.getString("role")), + firstname = UserFirstname(rs.getString("firstname")), + lastname = UserLastname(rs.getString("lastname")), + username = Username(rs.getString("username")), + email = UserEmail(rs.getString("email")), + passwordHash = PasswordHash(rs.getString("password_hash")), + isActive = rs.getBoolean("is_active"), + mustChangePassword = rs.getBoolean("must_change_password"), + ) + + companion object { + private const val JDBC_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1" + } +} diff --git a/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/UserTestContext.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/UserTestContext.kt new file mode 100644 index 00000000..96e50d45 --- /dev/null +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/config/UserTestContext.kt @@ -0,0 +1,24 @@ +package com.alleslocker.backend.application.integration.config + +import com.alleslocker.backend.application.common.adapter.Adapter +import com.alleslocker.backend.application.common.factory.UseCaseFactory +import com.alleslocker.backend.application.common.factory.UseCaseFactoryImpl +import com.alleslocker.backend.application.common.gateway.Gateway +import com.alleslocker.backend.application.common.security.PasswordHasher + +data class UserTestContext( + val userGateway: TestUserGatewayAdapter, + val passwordHasher: PasswordHasher, + val logger: TestLogger, + val useCaseFactory: UseCaseFactory, +) + +fun createUserTestContext(): UserTestContext { + val userGateway = TestUserGatewayAdapter() + val passwordHasher = TestPasswordHasher() + val logger = TestLogger() + val gatewayFactory = TestGatewayFactory(listOf(userGateway)) + val adapterFactory = TestAdapterFactory(emptyList()) + val useCaseFactory = UseCaseFactoryImpl(gatewayFactory, adapterFactory, passwordHasher, logger) + return UserTestContext(userGateway, passwordHasher, logger, useCaseFactory) +} diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ActivateUserUseCaseTest.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/ActivateUserUseCaseTest.kt similarity index 71% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ActivateUserUseCaseTest.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/user/ActivateUserUseCaseTest.kt index 91693ca0..cdcc68b0 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ActivateUserUseCaseTest.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/ActivateUserUseCaseTest.kt @@ -1,14 +1,10 @@ -package com.alleslocker.backend.bootstrap.integration.user +package com.alleslocker.backend.application.integration.user import com.alleslocker.backend.application.common.ErrorResponse -import com.alleslocker.backend.application.common.factory.UseCaseFactory -import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.integration.config.TestPresenter +import com.alleslocker.backend.application.integration.config.createUserTestContext import com.alleslocker.backend.application.user.dto.request.ActivateUserRequestDto -import com.alleslocker.backend.application.user.gateway.UserGateway import com.alleslocker.backend.application.user.usecase.ActivateUserUseCase -import com.alleslocker.backend.bootstrap.integration.config.TestLogger -import com.alleslocker.backend.bootstrap.integration.config.TestPresenter -import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig import com.alleslocker.backend.domain.user.PasswordHash import com.alleslocker.backend.domain.user.User import com.alleslocker.backend.domain.user.UserEmail @@ -17,35 +13,24 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserLastname import com.alleslocker.backend.domain.user.UserRole import com.alleslocker.backend.domain.user.Username -import com.alleslocker.backend.persistence.user.repository.UserRepository import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.types.shouldBeInstanceOf -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ActiveProfiles - -@SpringBootTest(classes = [TestUserIntegrationConfig::class]) -@ActiveProfiles("test") -class ActivateUserUseCaseTest( - @Autowired private val useCaseFactory: UseCaseFactory, - @Autowired private val userGateway: UserGateway, - @Autowired private val userRepository: UserRepository, - @Autowired private val passwordHasher: PasswordHasher, - @Autowired private val testLogger: TestLogger, -) : FreeSpec({ - - val useCase: ActivateUserUseCase = useCaseFactory.make(ActivateUserUseCase::class) + +class ActivateUserUseCaseTest : + FreeSpec({ + val ctx = createUserTestContext() + val useCase: ActivateUserUseCase = ctx.useCaseFactory.make(ActivateUserUseCase::class) lateinit var adminId: String lateinit var targetUserId: String beforeEach { - userRepository.deleteAll() - testLogger.clear() + ctx.userGateway.deleteAll() + ctx.logger.clear() val admin = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.ADMIN, @@ -53,7 +38,7 @@ class ActivateUserUseCaseTest( lastname = UserLastname("User"), username = Username("admin"), email = UserEmail("admin@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("admin123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("admin123")), isActive = true, mustChangePassword = false, ), @@ -61,7 +46,7 @@ class ActivateUserUseCaseTest( adminId = admin.id.value val targetUser = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -69,7 +54,7 @@ class ActivateUserUseCaseTest( lastname = UserLastname("Mustermann"), username = Username("mmuster"), email = UserEmail("max@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = false, mustChangePassword = false, ), @@ -86,14 +71,14 @@ class ActivateUserUseCaseTest( ) presenter.error shouldBe null - val updatedUser = userGateway.findById(UserId(targetUserId)) + val updatedUser = ctx.userGateway.findById(UserId(targetUserId)) updatedUser!!.isActive shouldBe true - testLogger.auditLogs.size shouldBe 1 + ctx.logger.auditLogs.size shouldBe 1 } "should reject activating already active user" { - userGateway.save( - userGateway.findById(UserId(targetUserId))!!.copy(isActive = true), + ctx.userGateway.save( + ctx.userGateway.findById(UserId(targetUserId))!!.copy(isActive = true), ) val presenter = TestPresenter() @@ -109,7 +94,7 @@ class ActivateUserUseCaseTest( "should reject activation by non-admin" { val regularUser = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -117,7 +102,7 @@ class ActivateUserUseCaseTest( lastname = UserLastname("User"), username = Username("regular"), email = UserEmail("regular@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/AdminResetPasswordUserUseCaseTest.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/AdminResetPasswordUserUseCaseTest.kt similarity index 72% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/AdminResetPasswordUserUseCaseTest.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/user/AdminResetPasswordUserUseCaseTest.kt index afe08a08..3f59009d 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/AdminResetPasswordUserUseCaseTest.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/AdminResetPasswordUserUseCaseTest.kt @@ -1,15 +1,11 @@ -package com.alleslocker.backend.bootstrap.integration.user +package com.alleslocker.backend.application.integration.user import com.alleslocker.backend.application.common.ErrorResponse -import com.alleslocker.backend.application.common.factory.UseCaseFactory -import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.integration.config.TestPresenter +import com.alleslocker.backend.application.integration.config.createUserTestContext import com.alleslocker.backend.application.user.dto.request.AdminResetPasswordUserRequestDto import com.alleslocker.backend.application.user.dto.response.AdminResetPasswordUserResponseDto -import com.alleslocker.backend.application.user.gateway.UserGateway import com.alleslocker.backend.application.user.usecase.AdminResetPasswordUserUseCase -import com.alleslocker.backend.bootstrap.integration.config.TestLogger -import com.alleslocker.backend.bootstrap.integration.config.TestPresenter -import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig import com.alleslocker.backend.domain.user.PasswordHash import com.alleslocker.backend.domain.user.User import com.alleslocker.backend.domain.user.UserEmail @@ -18,38 +14,26 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserLastname import com.alleslocker.backend.domain.user.UserRole import com.alleslocker.backend.domain.user.Username -import com.alleslocker.backend.persistence.user.repository.UserRepository import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.comparables.shouldBeGreaterThan import io.kotest.matchers.comparables.shouldBeGreaterThanOrEqualTo import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.types.shouldBeInstanceOf -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ActiveProfiles - -@SpringBootTest(classes = [TestUserIntegrationConfig::class]) -@ActiveProfiles("test") -class AdminResetPasswordUserUseCaseTest( - @Autowired private val useCaseFactory: UseCaseFactory, - @Autowired private val userGateway: UserGateway, - @Autowired private val userRepository: UserRepository, - @Autowired private val passwordHasher: PasswordHasher, - @Autowired private val testLogger: TestLogger, -) : FreeSpec({ - - val useCase: AdminResetPasswordUserUseCase = - useCaseFactory.make(AdminResetPasswordUserUseCase::class) + +class AdminResetPasswordUserUseCaseTest : + FreeSpec({ + val ctx = createUserTestContext() + val useCase: AdminResetPasswordUserUseCase = ctx.useCaseFactory.make(AdminResetPasswordUserUseCase::class) lateinit var adminId: String lateinit var targetUserId: String beforeEach { - userRepository.deleteAll() - testLogger.clear() + ctx.userGateway.deleteAll() + ctx.logger.clear() val admin = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.ADMIN, @@ -57,7 +41,7 @@ class AdminResetPasswordUserUseCaseTest( lastname = UserLastname("User"), username = Username("admin"), email = UserEmail("admin@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("admin123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("admin123")), isActive = true, mustChangePassword = false, ), @@ -65,7 +49,7 @@ class AdminResetPasswordUserUseCaseTest( adminId = admin.id.value val targetUser = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -73,7 +57,7 @@ class AdminResetPasswordUserUseCaseTest( lastname = UserLastname("Mustermann"), username = Username("mmuster"), email = UserEmail("max@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("oldPass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("oldPass123")), isActive = true, mustChangePassword = false, ), @@ -94,16 +78,16 @@ class AdminResetPasswordUserUseCaseTest( presenter.response!!.userId shouldBe targetUserId presenter.response!!.password.length shouldBeGreaterThan 0 - val updatedUser = userGateway.findById(UserId(targetUserId)) + val updatedUser = ctx.userGateway.findById(UserId(targetUserId)) updatedUser!!.mustChangePassword shouldBe true - passwordHasher.verify(presenter.response!!.password, updatedUser.passwordHash.value) shouldBe true + ctx.passwordHasher.verify(presenter.response!!.password, updatedUser.passwordHash.value) shouldBe true - testLogger.auditLogs.size shouldBe 1 + ctx.logger.auditLogs.size shouldBe 1 } "should reject reset by non-admin" { val regularUser = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -111,7 +95,7 @@ class AdminResetPasswordUserUseCaseTest( lastname = UserLastname("User"), username = Username("regular"), email = UserEmail("regular@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ChangeUserRoleUseCaseTest.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/ChangeUserRoleUseCaseTest.kt similarity index 74% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ChangeUserRoleUseCaseTest.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/user/ChangeUserRoleUseCaseTest.kt index 17929778..51562491 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ChangeUserRoleUseCaseTest.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/ChangeUserRoleUseCaseTest.kt @@ -1,15 +1,11 @@ -package com.alleslocker.backend.bootstrap.integration.user +package com.alleslocker.backend.application.integration.user import com.alleslocker.backend.application.common.ErrorResponse -import com.alleslocker.backend.application.common.factory.UseCaseFactory -import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.integration.config.TestPresenter +import com.alleslocker.backend.application.integration.config.createUserTestContext import com.alleslocker.backend.application.user.dto.UserRoleDto import com.alleslocker.backend.application.user.dto.request.ChangeUserRoleRequestDto -import com.alleslocker.backend.application.user.gateway.UserGateway import com.alleslocker.backend.application.user.usecase.ChangeUserRoleUseCase -import com.alleslocker.backend.bootstrap.integration.config.TestLogger -import com.alleslocker.backend.bootstrap.integration.config.TestPresenter -import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig import com.alleslocker.backend.domain.user.PasswordHash import com.alleslocker.backend.domain.user.User import com.alleslocker.backend.domain.user.UserEmail @@ -18,36 +14,25 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserLastname import com.alleslocker.backend.domain.user.UserRole import com.alleslocker.backend.domain.user.Username -import com.alleslocker.backend.persistence.user.repository.UserRepository import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.shouldBeInstanceOf -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ActiveProfiles - -@SpringBootTest(classes = [TestUserIntegrationConfig::class]) -@ActiveProfiles("test") -class ChangeUserRoleUseCaseTest( - @Autowired private val useCaseFactory: UseCaseFactory, - @Autowired private val userGateway: UserGateway, - @Autowired private val userRepository: UserRepository, - @Autowired private val passwordHasher: PasswordHasher, - @Autowired private val testLogger: TestLogger, -) : FreeSpec({ - - val useCase: ChangeUserRoleUseCase = useCaseFactory.make(ChangeUserRoleUseCase::class) + +class ChangeUserRoleUseCaseTest : + FreeSpec({ + val ctx = createUserTestContext() + val useCase: ChangeUserRoleUseCase = ctx.useCaseFactory.make(ChangeUserRoleUseCase::class) lateinit var adminId: String lateinit var targetUserId: String beforeEach { - userRepository.deleteAll() - testLogger.clear() + ctx.userGateway.deleteAll() + ctx.logger.clear() val admin = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.ADMIN, @@ -55,7 +40,7 @@ class ChangeUserRoleUseCaseTest( lastname = UserLastname("User"), username = Username("admin"), email = UserEmail("admin@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("admin123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("admin123")), isActive = true, mustChangePassword = false, ), @@ -63,7 +48,7 @@ class ChangeUserRoleUseCaseTest( adminId = admin.id.value val targetUser = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -71,7 +56,7 @@ class ChangeUserRoleUseCaseTest( lastname = UserLastname("Mustermann"), username = Username("mmuster"), email = UserEmail("max@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), @@ -92,14 +77,14 @@ class ChangeUserRoleUseCaseTest( ) presenter.error shouldBe null - val updatedUser = userGateway.findById(UserId(targetUserId)) + val updatedUser = ctx.userGateway.findById(UserId(targetUserId)) updatedUser!!.role shouldBe UserRole.ADMIN - testLogger.auditLogs.size shouldBe 1 + ctx.logger.auditLogs.size shouldBe 1 } "should change ADMIN to USER" { - userGateway.save( - userGateway.findById(UserId(targetUserId))!!.copy(role = UserRole.ADMIN), + ctx.userGateway.save( + ctx.userGateway.findById(UserId(targetUserId))!!.copy(role = UserRole.ADMIN), ) val presenter = TestPresenter() @@ -113,7 +98,7 @@ class ChangeUserRoleUseCaseTest( ) presenter.error shouldBe null - val updatedUser = userGateway.findById(UserId(targetUserId)) + val updatedUser = ctx.userGateway.findById(UserId(targetUserId)) updatedUser!!.role shouldBe UserRole.USER } @@ -136,7 +121,7 @@ class ChangeUserRoleUseCaseTest( "should reject role change by non-admin" { val regularUser = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -144,7 +129,7 @@ class ChangeUserRoleUseCaseTest( lastname = UserLastname("User"), username = Username("regular"), email = UserEmail("regular@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/CreateUserUseCaseTest.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/CreateUserUseCaseTest.kt similarity index 82% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/CreateUserUseCaseTest.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/user/CreateUserUseCaseTest.kt index 32fd1aa3..2bb0b24f 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/CreateUserUseCaseTest.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/CreateUserUseCaseTest.kt @@ -1,15 +1,11 @@ -package com.alleslocker.backend.bootstrap.integration.user +package com.alleslocker.backend.application.integration.user import com.alleslocker.backend.application.common.ErrorResponse -import com.alleslocker.backend.application.common.factory.UseCaseFactory -import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.integration.config.TestPresenter +import com.alleslocker.backend.application.integration.config.createUserTestContext import com.alleslocker.backend.application.user.dto.request.CreateUserRequestDto import com.alleslocker.backend.application.user.dto.response.CreateUserResponseDto -import com.alleslocker.backend.application.user.gateway.UserGateway import com.alleslocker.backend.application.user.usecase.CreateUserUseCase -import com.alleslocker.backend.bootstrap.integration.config.TestLogger -import com.alleslocker.backend.bootstrap.integration.config.TestPresenter -import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig import com.alleslocker.backend.domain.user.PasswordHash import com.alleslocker.backend.domain.user.User import com.alleslocker.backend.domain.user.UserEmail @@ -18,7 +14,6 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserLastname import com.alleslocker.backend.domain.user.UserRole import com.alleslocker.backend.domain.user.Username -import com.alleslocker.backend.persistence.user.repository.UserRepository import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.comparables.shouldBeGreaterThan import io.kotest.matchers.comparables.shouldBeGreaterThanOrEqualTo @@ -26,30 +21,19 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.shouldBeInstanceOf -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ActiveProfiles -@SpringBootTest(classes = [TestUserIntegrationConfig::class]) -@ActiveProfiles("test") -class CreateUserUseCaseTest( - @Autowired private val useCaseFactory: UseCaseFactory, - @Autowired private val userGateway: UserGateway, - @Autowired private val userRepository: UserRepository, - @Autowired private val passwordHasher: PasswordHasher, - @Autowired private val testLogger: TestLogger, -) : FreeSpec({ - - val useCase: CreateUserUseCase = - useCaseFactory.make(CreateUserUseCase::class) +class CreateUserUseCaseTest : + FreeSpec({ + val ctx = createUserTestContext() + val useCase: CreateUserUseCase = ctx.useCaseFactory.make(CreateUserUseCase::class) lateinit var adminId: String beforeEach { - userRepository.deleteAll() - testLogger.clear() + ctx.userGateway.deleteAll() + ctx.logger.clear() val admin = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.ADMIN, @@ -57,7 +41,7 @@ class CreateUserUseCaseTest( lastname = UserLastname("User"), username = Username("admin"), email = UserEmail("admin@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("admin123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("admin123")), isActive = true, mustChangePassword = false, ), @@ -84,20 +68,21 @@ class CreateUserUseCaseTest( presenter.response!!.id shouldNotBe "" presenter.response!!.password.length shouldBeGreaterThan 0 - val savedUser = userGateway.findById(UserId(presenter.response!!.id)) + val savedUser = ctx.userGateway.findById(UserId(presenter.response!!.id)) savedUser shouldNotBe null savedUser!!.mustChangePassword shouldBe true savedUser.isActive shouldBe true savedUser.username.value shouldBe "mmuster" savedUser.email.value shouldBe "max@test.de" - passwordHasher.verify(presenter.response!!.password, savedUser.passwordHash.value) shouldBe true + ctx.passwordHasher.verify(presenter.response!!.password, savedUser.passwordHash.value) shouldBe true - testLogger.auditLogs.size shouldBe 1 - testLogger.auditLogs[0].message.value shouldContain "mmuster" + ctx.logger.auditLogs.size shouldBe 1 + ctx.logger.auditLogs[0] + .message.value shouldContain "mmuster" } "should reject duplicate username" { - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -105,7 +90,7 @@ class CreateUserUseCaseTest( lastname = UserLastname("User"), username = Username("mmuster"), email = UserEmail("other@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), @@ -130,7 +115,7 @@ class CreateUserUseCaseTest( } "should reject duplicate email" { - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -138,7 +123,7 @@ class CreateUserUseCaseTest( lastname = UserLastname("User"), username = Username("existing"), email = UserEmail("max@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), @@ -164,7 +149,7 @@ class CreateUserUseCaseTest( "should reject non-admin requestor" { val regularUser = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -172,7 +157,7 @@ class CreateUserUseCaseTest( lastname = UserLastname("User"), username = Username("regular"), email = UserEmail("regular@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/DeactivateUserUseCaseTest.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/DeactivateUserUseCaseTest.kt similarity index 71% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/DeactivateUserUseCaseTest.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/user/DeactivateUserUseCaseTest.kt index 6de34fc5..5efcce09 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/DeactivateUserUseCaseTest.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/DeactivateUserUseCaseTest.kt @@ -1,14 +1,10 @@ -package com.alleslocker.backend.bootstrap.integration.user +package com.alleslocker.backend.application.integration.user import com.alleslocker.backend.application.common.ErrorResponse -import com.alleslocker.backend.application.common.factory.UseCaseFactory -import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.integration.config.TestPresenter +import com.alleslocker.backend.application.integration.config.createUserTestContext import com.alleslocker.backend.application.user.dto.request.DeactivateUserRequestDto -import com.alleslocker.backend.application.user.gateway.UserGateway import com.alleslocker.backend.application.user.usecase.DeactivateUserUseCase -import com.alleslocker.backend.bootstrap.integration.config.TestLogger -import com.alleslocker.backend.bootstrap.integration.config.TestPresenter -import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig import com.alleslocker.backend.domain.user.PasswordHash import com.alleslocker.backend.domain.user.User import com.alleslocker.backend.domain.user.UserEmail @@ -17,35 +13,24 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserLastname import com.alleslocker.backend.domain.user.UserRole import com.alleslocker.backend.domain.user.Username -import com.alleslocker.backend.persistence.user.repository.UserRepository import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.types.shouldBeInstanceOf -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ActiveProfiles - -@SpringBootTest(classes = [TestUserIntegrationConfig::class]) -@ActiveProfiles("test") -class DeactivateUserUseCaseTest( - @Autowired private val useCaseFactory: UseCaseFactory, - @Autowired private val userGateway: UserGateway, - @Autowired private val userRepository: UserRepository, - @Autowired private val passwordHasher: PasswordHasher, - @Autowired private val testLogger: TestLogger, -) : FreeSpec({ - - val useCase: DeactivateUserUseCase = useCaseFactory.make(DeactivateUserUseCase::class) + +class DeactivateUserUseCaseTest : + FreeSpec({ + val ctx = createUserTestContext() + val useCase: DeactivateUserUseCase = ctx.useCaseFactory.make(DeactivateUserUseCase::class) lateinit var adminId: String lateinit var targetUserId: String beforeEach { - userRepository.deleteAll() - testLogger.clear() + ctx.userGateway.deleteAll() + ctx.logger.clear() val admin = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.ADMIN, @@ -53,7 +38,7 @@ class DeactivateUserUseCaseTest( lastname = UserLastname("User"), username = Username("admin"), email = UserEmail("admin@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("admin123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("admin123")), isActive = true, mustChangePassword = false, ), @@ -61,7 +46,7 @@ class DeactivateUserUseCaseTest( adminId = admin.id.value val targetUser = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -69,7 +54,7 @@ class DeactivateUserUseCaseTest( lastname = UserLastname("Mustermann"), username = Username("mmuster"), email = UserEmail("max@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), @@ -86,14 +71,14 @@ class DeactivateUserUseCaseTest( ) presenter.error shouldBe null - val updatedUser = userGateway.findById(UserId(targetUserId)) + val updatedUser = ctx.userGateway.findById(UserId(targetUserId)) updatedUser!!.isActive shouldBe false - testLogger.auditLogs.size shouldBe 1 + ctx.logger.auditLogs.size shouldBe 1 } "should reject deactivating already deactivated user" { - userGateway.save( - userGateway.findById(UserId(targetUserId))!!.copy(isActive = false), + ctx.userGateway.save( + ctx.userGateway.findById(UserId(targetUserId))!!.copy(isActive = false), ) val presenter = TestPresenter() @@ -109,7 +94,7 @@ class DeactivateUserUseCaseTest( "should reject deactivation by non-admin" { val regularUser = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -117,7 +102,7 @@ class DeactivateUserUseCaseTest( lastname = UserLastname("User"), username = Username("regular"), email = UserEmail("regular@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/EditUserUseCaseTest.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/EditUserUseCaseTest.kt similarity index 81% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/EditUserUseCaseTest.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/user/EditUserUseCaseTest.kt index 318ddc2a..82646ccb 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/EditUserUseCaseTest.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/EditUserUseCaseTest.kt @@ -1,14 +1,10 @@ -package com.alleslocker.backend.bootstrap.integration.user +package com.alleslocker.backend.application.integration.user import com.alleslocker.backend.application.common.ErrorResponse -import com.alleslocker.backend.application.common.factory.UseCaseFactory -import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.integration.config.TestPresenter +import com.alleslocker.backend.application.integration.config.createUserTestContext import com.alleslocker.backend.application.user.dto.request.EditUserRequestDto -import com.alleslocker.backend.application.user.gateway.UserGateway import com.alleslocker.backend.application.user.usecase.EditUserUseCase -import com.alleslocker.backend.bootstrap.integration.config.TestLogger -import com.alleslocker.backend.bootstrap.integration.config.TestPresenter -import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig import com.alleslocker.backend.domain.user.PasswordHash import com.alleslocker.backend.domain.user.User import com.alleslocker.backend.domain.user.UserEmail @@ -17,36 +13,25 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserLastname import com.alleslocker.backend.domain.user.UserRole import com.alleslocker.backend.domain.user.Username -import com.alleslocker.backend.persistence.user.repository.UserRepository import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.shouldBeInstanceOf -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ActiveProfiles -@SpringBootTest(classes = [TestUserIntegrationConfig::class]) -@ActiveProfiles("test") -class EditUserUseCaseTest( - @Autowired private val useCaseFactory: UseCaseFactory, - @Autowired private val userGateway: UserGateway, - @Autowired private val userRepository: UserRepository, - @Autowired private val passwordHasher: PasswordHasher, - @Autowired private val testLogger: TestLogger, -) : FreeSpec({ - - val useCase: EditUserUseCase = useCaseFactory.make(EditUserUseCase::class) +class EditUserUseCaseTest : + FreeSpec({ + val ctx = createUserTestContext() + val useCase: EditUserUseCase = ctx.useCaseFactory.make(EditUserUseCase::class) lateinit var adminId: String lateinit var targetUserId: String beforeEach { - userRepository.deleteAll() - testLogger.clear() + ctx.userGateway.deleteAll() + ctx.logger.clear() val admin = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.ADMIN, @@ -54,7 +39,7 @@ class EditUserUseCaseTest( lastname = UserLastname("User"), username = Username("admin"), email = UserEmail("admin@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("admin123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("admin123")), isActive = true, mustChangePassword = false, ), @@ -62,7 +47,7 @@ class EditUserUseCaseTest( adminId = admin.id.value val targetUser = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -70,7 +55,7 @@ class EditUserUseCaseTest( lastname = UserLastname("Mustermann"), username = Username("mmuster"), email = UserEmail("max@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), @@ -91,12 +76,12 @@ class EditUserUseCaseTest( ) presenter.error shouldBe null - val updatedUser = userGateway.findById(UserId(targetUserId)) + val updatedUser = ctx.userGateway.findById(UserId(targetUserId)) updatedUser!!.firstname.value shouldBe "Maximilian" updatedUser.lastname.value shouldBe "Mustermann" updatedUser.username.value shouldBe "mmuster" updatedUser.email.value shouldBe "max@test.de" - testLogger.auditLogs.size shouldBe 1 + ctx.logger.auditLogs.size shouldBe 1 } "should update multiple fields at once" { @@ -115,7 +100,7 @@ class EditUserUseCaseTest( ) presenter.error shouldBe null - val updatedUser = userGateway.findById(UserId(targetUserId)) + val updatedUser = ctx.userGateway.findById(UserId(targetUserId)) updatedUser!!.firstname.value shouldBe "Maximilian" updatedUser.lastname.value shouldBe "Muster" updatedUser.username.value shouldBe "mmuster2" @@ -124,7 +109,7 @@ class EditUserUseCaseTest( "should reject edit by non-admin" { val regularUser = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -132,7 +117,7 @@ class EditUserUseCaseTest( lastname = UserLastname("User"), username = Username("regular"), email = UserEmail("regular@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), @@ -186,7 +171,7 @@ class EditUserUseCaseTest( } "should reject duplicate email" { - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -194,7 +179,7 @@ class EditUserUseCaseTest( lastname = UserLastname("User"), username = Username("other"), email = UserEmail("other@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), @@ -217,7 +202,7 @@ class EditUserUseCaseTest( } "should reject duplicate username" { - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -225,7 +210,7 @@ class EditUserUseCaseTest( lastname = UserLastname("User"), username = Username("other"), email = UserEmail("other@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), @@ -286,7 +271,7 @@ class EditUserUseCaseTest( ) presenter.error shouldBe null - val updatedUser = userGateway.findById(UserId(targetUserId)) + val updatedUser = ctx.userGateway.findById(UserId(targetUserId)) updatedUser!!.firstname.value shouldBe "Max" updatedUser.lastname.value shouldBe "Mustermann" updatedUser.username.value shouldBe "mmuster" diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUserUseCaseTest.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/GetUserUseCaseTest.kt similarity index 70% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUserUseCaseTest.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/user/GetUserUseCaseTest.kt index 78a70b6b..f8ebd1a8 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUserUseCaseTest.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/GetUserUseCaseTest.kt @@ -1,14 +1,11 @@ -package com.alleslocker.backend.bootstrap.integration.user +package com.alleslocker.backend.application.integration.user import com.alleslocker.backend.application.common.ErrorResponse -import com.alleslocker.backend.application.common.factory.UseCaseFactory -import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.integration.config.TestPresenter +import com.alleslocker.backend.application.integration.config.createUserTestContext import com.alleslocker.backend.application.user.dto.request.GetUserRequestDto import com.alleslocker.backend.application.user.dto.response.GetUserResponseDto -import com.alleslocker.backend.application.user.gateway.UserGateway import com.alleslocker.backend.application.user.usecase.GetUserUseCase -import com.alleslocker.backend.bootstrap.integration.config.TestPresenter -import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig import com.alleslocker.backend.domain.user.PasswordHash import com.alleslocker.backend.domain.user.User import com.alleslocker.backend.domain.user.UserEmail @@ -17,32 +14,22 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserLastname import com.alleslocker.backend.domain.user.UserRole import com.alleslocker.backend.domain.user.Username -import com.alleslocker.backend.persistence.user.repository.UserRepository import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.types.shouldBeInstanceOf -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ActiveProfiles -@SpringBootTest(classes = [TestUserIntegrationConfig::class]) -@ActiveProfiles("test") -class GetUserUseCaseTest( - @Autowired private val useCaseFactory: UseCaseFactory, - @Autowired private val userGateway: UserGateway, - @Autowired private val userRepository: UserRepository, - @Autowired private val passwordHasher: PasswordHasher, -) : FreeSpec({ - - val useCase: GetUserUseCase = useCaseFactory.make(GetUserUseCase::class) +class GetUserUseCaseTest : + FreeSpec({ + val ctx = createUserTestContext() + val useCase: GetUserUseCase = ctx.useCaseFactory.make(GetUserUseCase::class) lateinit var savedUserId: String beforeEach { - userRepository.deleteAll() + ctx.userGateway.deleteAll() val saved = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -50,7 +37,7 @@ class GetUserUseCaseTest( lastname = UserLastname("Mustermann"), username = Username("mmuster"), email = UserEmail("max@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("secure123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("secure123")), isActive = true, mustChangePassword = false, ), diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUsersPagedUseCaseTest.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/GetUsersPagedUseCaseTest.kt similarity index 84% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUsersPagedUseCaseTest.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/user/GetUsersPagedUseCaseTest.kt index 65e4cc0d..ff598246 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/GetUsersPagedUseCaseTest.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/GetUsersPagedUseCaseTest.kt @@ -1,15 +1,12 @@ -package com.alleslocker.backend.bootstrap.integration.user +package com.alleslocker.backend.application.integration.user import com.alleslocker.backend.application.common.ErrorResponse -import com.alleslocker.backend.application.common.factory.UseCaseFactory -import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.integration.config.TestPresenter +import com.alleslocker.backend.application.integration.config.createUserTestContext import com.alleslocker.backend.application.user.dto.filter.UserFilterDto import com.alleslocker.backend.application.user.dto.request.GetUsersPagedRequestDto import com.alleslocker.backend.application.user.dto.response.GetUsersPagedResponseDto -import com.alleslocker.backend.application.user.gateway.UserGateway import com.alleslocker.backend.application.user.usecase.GetUsersPagedUseCase -import com.alleslocker.backend.bootstrap.integration.config.TestPresenter -import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig import com.alleslocker.backend.domain.user.PasswordHash import com.alleslocker.backend.domain.user.User import com.alleslocker.backend.domain.user.UserEmail @@ -18,29 +15,19 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserLastname import com.alleslocker.backend.domain.user.UserRole import com.alleslocker.backend.domain.user.Username -import com.alleslocker.backend.persistence.user.repository.UserRepository import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.types.shouldBeInstanceOf -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ActiveProfiles -@SpringBootTest(classes = [TestUserIntegrationConfig::class]) -@ActiveProfiles("test") -class GetUsersPagedUseCaseTest( - @Autowired private val useCaseFactory: UseCaseFactory, - @Autowired private val userGateway: UserGateway, - @Autowired private val userRepository: UserRepository, - @Autowired private val passwordHasher: PasswordHasher, -) : FreeSpec({ - - val useCase: GetUsersPagedUseCase = useCaseFactory.make(GetUsersPagedUseCase::class) +class GetUsersPagedUseCaseTest : + FreeSpec({ + val ctx = createUserTestContext() + val useCase: GetUsersPagedUseCase = ctx.useCaseFactory.make(GetUsersPagedUseCase::class) beforeEach { - userRepository.deleteAll() - userGateway.save( + ctx.userGateway.deleteAll() + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -48,12 +35,12 @@ class GetUsersPagedUseCaseTest( lastname = UserLastname("Mustermann"), username = Username("mmuster"), email = UserEmail("max@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), ) - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.ADMIN, @@ -61,12 +48,12 @@ class GetUsersPagedUseCaseTest( lastname = UserLastname("Admin"), username = Username("aadmin"), email = UserEmail("anna@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass456")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass456")), isActive = true, mustChangePassword = false, ), ) - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -74,7 +61,7 @@ class GetUsersPagedUseCaseTest( lastname = UserLastname("Test"), username = Username("ttest"), email = UserEmail("tom@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass789")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass789")), isActive = false, mustChangePassword = true, ), diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/LoginUserUseCaseTest.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/LoginUserUseCaseTest.kt similarity index 70% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/LoginUserUseCaseTest.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/user/LoginUserUseCaseTest.kt index df480c5c..7785d11a 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/LoginUserUseCaseTest.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/LoginUserUseCaseTest.kt @@ -1,14 +1,11 @@ -package com.alleslocker.backend.bootstrap.integration.user +package com.alleslocker.backend.application.integration.user import com.alleslocker.backend.application.common.ErrorResponse -import com.alleslocker.backend.application.common.factory.UseCaseFactory -import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.integration.config.TestPresenter +import com.alleslocker.backend.application.integration.config.createUserTestContext import com.alleslocker.backend.application.user.dto.request.LoginUserRequestDto import com.alleslocker.backend.application.user.dto.response.LoginUserResponseDto -import com.alleslocker.backend.application.user.gateway.UserGateway import com.alleslocker.backend.application.user.usecase.LoginUserUseCase -import com.alleslocker.backend.bootstrap.integration.config.TestPresenter -import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig import com.alleslocker.backend.domain.user.PasswordHash import com.alleslocker.backend.domain.user.User import com.alleslocker.backend.domain.user.UserEmail @@ -17,29 +14,19 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserLastname import com.alleslocker.backend.domain.user.UserRole import com.alleslocker.backend.domain.user.Username -import com.alleslocker.backend.persistence.user.repository.UserRepository import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.types.shouldBeInstanceOf -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ActiveProfiles -@SpringBootTest(classes = [TestUserIntegrationConfig::class]) -@ActiveProfiles("test") -class LoginUserUseCaseTest( - @Autowired private val useCaseFactory: UseCaseFactory, - @Autowired private val userGateway: UserGateway, - @Autowired private val userRepository: UserRepository, - @Autowired private val passwordHasher: PasswordHasher, -) : FreeSpec({ - - val useCase: LoginUserUseCase = useCaseFactory.make(LoginUserUseCase::class) +class LoginUserUseCaseTest : + FreeSpec({ + val ctx = createUserTestContext() + val useCase: LoginUserUseCase = ctx.useCaseFactory.make(LoginUserUseCase::class) beforeEach { - userRepository.deleteAll() - userGateway.save( + ctx.userGateway.deleteAll() + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -47,7 +34,7 @@ class LoginUserUseCaseTest( lastname = UserLastname("Mustermann"), username = Username("mmuster"), email = UserEmail("max@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("secure123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("secure123")), isActive = true, mustChangePassword = false, ), diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/RequestUserPasswordChangeUseCaseTest.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/RequestUserPasswordChangeUseCaseTest.kt similarity index 71% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/RequestUserPasswordChangeUseCaseTest.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/user/RequestUserPasswordChangeUseCaseTest.kt index 1e679cae..f88a44da 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/RequestUserPasswordChangeUseCaseTest.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/RequestUserPasswordChangeUseCaseTest.kt @@ -1,14 +1,10 @@ -package com.alleslocker.backend.bootstrap.integration.user +package com.alleslocker.backend.application.integration.user import com.alleslocker.backend.application.common.ErrorResponse -import com.alleslocker.backend.application.common.factory.UseCaseFactory -import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.integration.config.TestPresenter +import com.alleslocker.backend.application.integration.config.createUserTestContext import com.alleslocker.backend.application.user.dto.request.RequestUserPasswordChangeRequestDto -import com.alleslocker.backend.application.user.gateway.UserGateway import com.alleslocker.backend.application.user.usecase.RequestUserPasswordChangeUseCase -import com.alleslocker.backend.bootstrap.integration.config.TestLogger -import com.alleslocker.backend.bootstrap.integration.config.TestPresenter -import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig import com.alleslocker.backend.domain.user.PasswordHash import com.alleslocker.backend.domain.user.User import com.alleslocker.backend.domain.user.UserEmail @@ -17,37 +13,25 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserLastname import com.alleslocker.backend.domain.user.UserRole import com.alleslocker.backend.domain.user.Username -import com.alleslocker.backend.persistence.user.repository.UserRepository import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.shouldBeInstanceOf -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ActiveProfiles - -@SpringBootTest(classes = [TestUserIntegrationConfig::class]) -@ActiveProfiles("test") -class RequestUserPasswordChangeUseCaseTest( - @Autowired private val useCaseFactory: UseCaseFactory, - @Autowired private val userGateway: UserGateway, - @Autowired private val userRepository: UserRepository, - @Autowired private val passwordHasher: PasswordHasher, - @Autowired private val testLogger: TestLogger, -) : FreeSpec({ - - val useCase: RequestUserPasswordChangeUseCase = - useCaseFactory.make(RequestUserPasswordChangeUseCase::class) + +class RequestUserPasswordChangeUseCaseTest : + FreeSpec({ + val ctx = createUserTestContext() + val useCase: RequestUserPasswordChangeUseCase = ctx.useCaseFactory.make(RequestUserPasswordChangeUseCase::class) lateinit var adminId: String lateinit var targetUserId: String beforeEach { - userRepository.deleteAll() - testLogger.clear() + ctx.userGateway.deleteAll() + ctx.logger.clear() val admin = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.ADMIN, @@ -55,7 +39,7 @@ class RequestUserPasswordChangeUseCaseTest( lastname = UserLastname("User"), username = Username("admin"), email = UserEmail("admin@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("admin123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("admin123")), isActive = true, mustChangePassword = false, ), @@ -63,7 +47,7 @@ class RequestUserPasswordChangeUseCaseTest( adminId = admin.id.value val targetUser = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -71,7 +55,7 @@ class RequestUserPasswordChangeUseCaseTest( lastname = UserLastname("Mustermann"), username = Username("mmuster"), email = UserEmail("max@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), @@ -88,14 +72,14 @@ class RequestUserPasswordChangeUseCaseTest( ) presenter.error shouldBe null - val updatedUser = userGateway.findById(UserId(targetUserId)) + val updatedUser = ctx.userGateway.findById(UserId(targetUserId)) updatedUser!!.mustChangePassword shouldBe true - testLogger.auditLogs.size shouldBe 1 + ctx.logger.auditLogs.size shouldBe 1 } "should reject if user already requested to change password" { - userGateway.save( - userGateway.findById(UserId(targetUserId))!!.copy(mustChangePassword = true), + ctx.userGateway.save( + ctx.userGateway.findById(UserId(targetUserId))!!.copy(mustChangePassword = true), ) val presenter = TestPresenter() @@ -112,7 +96,7 @@ class RequestUserPasswordChangeUseCaseTest( "should reject request by non-admin" { val regularUser = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -120,7 +104,7 @@ class RequestUserPasswordChangeUseCaseTest( lastname = UserLastname("User"), username = Username("regular"), email = UserEmail("regular@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("pass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("pass123")), isActive = true, mustChangePassword = false, ), diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ResetPasswordUserUseCaseTest.kt b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/ResetPasswordUserUseCaseTest.kt similarity index 68% rename from bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ResetPasswordUserUseCaseTest.kt rename to application/src/test/kotlin/com/alleslocker/backend/application/integration/user/ResetPasswordUserUseCaseTest.kt index efe7c028..be4df8e7 100644 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/user/ResetPasswordUserUseCaseTest.kt +++ b/application/src/test/kotlin/com/alleslocker/backend/application/integration/user/ResetPasswordUserUseCaseTest.kt @@ -1,14 +1,11 @@ -package com.alleslocker.backend.bootstrap.integration.user +package com.alleslocker.backend.application.integration.user import com.alleslocker.backend.application.common.ErrorResponse -import com.alleslocker.backend.application.common.factory.UseCaseFactory -import com.alleslocker.backend.application.common.security.PasswordHasher +import com.alleslocker.backend.application.integration.config.TestPresenter +import com.alleslocker.backend.application.integration.config.createUserTestContext import com.alleslocker.backend.application.user.dto.request.ResetPasswordUserRequestDto import com.alleslocker.backend.application.user.dto.response.ResetPasswordUserResponseDto -import com.alleslocker.backend.application.user.gateway.UserGateway import com.alleslocker.backend.application.user.usecase.ResetPasswordUserUseCase -import com.alleslocker.backend.bootstrap.integration.config.TestPresenter -import com.alleslocker.backend.bootstrap.integration.config.TestUserIntegrationConfig import com.alleslocker.backend.domain.user.PasswordHash import com.alleslocker.backend.domain.user.User import com.alleslocker.backend.domain.user.UserEmail @@ -17,33 +14,22 @@ import com.alleslocker.backend.domain.user.UserId import com.alleslocker.backend.domain.user.UserLastname import com.alleslocker.backend.domain.user.UserRole import com.alleslocker.backend.domain.user.Username -import com.alleslocker.backend.persistence.user.repository.UserRepository import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.types.shouldBeInstanceOf -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ActiveProfiles -@SpringBootTest(classes = [TestUserIntegrationConfig::class]) -@ActiveProfiles("test") -class ResetPasswordUserUseCaseTest( - @Autowired private val useCaseFactory: UseCaseFactory, - @Autowired private val userGateway: UserGateway, - @Autowired private val userRepository: UserRepository, - @Autowired private val passwordHasher: PasswordHasher, -) : FreeSpec({ - - val useCase: ResetPasswordUserUseCase = - useCaseFactory.make(ResetPasswordUserUseCase::class) +class ResetPasswordUserUseCaseTest : + FreeSpec({ + val ctx = createUserTestContext() + val useCase: ResetPasswordUserUseCase = ctx.useCaseFactory.make(ResetPasswordUserUseCase::class) lateinit var userId: String beforeEach { - userRepository.deleteAll() + ctx.userGateway.deleteAll() val saved = - userGateway.save( + ctx.userGateway.save( User( id = UserId.generate(), role = UserRole.USER, @@ -51,7 +37,7 @@ class ResetPasswordUserUseCaseTest( lastname = UserLastname("Mustermann"), username = Username("mmuster"), email = UserEmail("max@test.de"), - passwordHash = PasswordHash(passwordHasher.hash("oldPass123")), + passwordHash = PasswordHash(ctx.passwordHasher.hash("oldPass123")), isActive = true, mustChangePassword = false, ), @@ -75,10 +61,10 @@ class ResetPasswordUserUseCaseTest( presenter.response shouldNotBe null presenter.response!!.userId shouldBe userId - val updatedUser = userGateway.findById(UserId(userId)) + val updatedUser = ctx.userGateway.findById(UserId(userId)) updatedUser!!.mustChangePassword shouldBe false - passwordHasher.verify("newPass456", updatedUser.passwordHash.value) shouldBe true - passwordHasher.verify("oldPass123", updatedUser.passwordHash.value) shouldBe false + ctx.passwordHasher.verify("newPass456", updatedUser.passwordHash.value) shouldBe true + ctx.passwordHasher.verify("oldPass123", updatedUser.passwordHash.value) shouldBe false } "should reject wrong old password" { diff --git a/bootstrap/build.gradle b/bootstrap/build.gradle index 41a6a41c..37f9a9a4 100644 --- a/bootstrap/build.gradle +++ b/bootstrap/build.gradle @@ -15,11 +15,5 @@ dependencies { implementation(libs.spring.boot.jpa) implementation(libs.mariadb.java.client) - testImplementation(libs.konsist) - testImplementation(libs.kotest.engine) - testImplementation(libs.kotest.assertions) - testImplementation(libs.kotest.runner) testImplementation(libs.kotest.spring) - testImplementation(libs.h2) - testImplementation(libs.spring.boot.test) } diff --git a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestUserIntegrationConfig.kt b/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestUserIntegrationConfig.kt deleted file mode 100644 index b273d625..00000000 --- a/bootstrap/src/test/kotlin/com/alleslocker/backend/bootstrap/integration/config/TestUserIntegrationConfig.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.alleslocker.backend.bootstrap.integration.config - -import com.alleslocker.backend.application.accessgrant.adapter.AccessGrantAdapter -import com.alleslocker.backend.application.common.Logger -import com.alleslocker.backend.application.common.factory.AdapterFactory -import com.alleslocker.backend.application.common.factory.GatewayFactory -import com.alleslocker.backend.application.common.factory.UseCaseFactory -import com.alleslocker.backend.application.common.factory.UseCaseFactoryImpl -import com.alleslocker.backend.application.common.security.PasswordHasher -import com.alleslocker.backend.application.lock.adapter.LockAdapter -import com.alleslocker.backend.application.person.adapter.PersonAdapter -import com.alleslocker.backend.application.vendor.adapter.VendorConnectionAdapter -import com.alleslocker.backend.application.vendor.adapter.VendorSpecificDefinitionsAdapter -import com.alleslocker.backend.lockconnector.common.AdapterFactoryImpl -import com.alleslocker.backend.persistence.accessgrant.adapter.AccessGrantGatewayAdapter -import com.alleslocker.backend.persistence.auditlog.adapter.AuditLogGatewayAdapter -import com.alleslocker.backend.persistence.factory.GatewayFactoryImpl -import com.alleslocker.backend.persistence.lock.adapter.LockGatewayAdapter -import com.alleslocker.backend.persistence.person.adapter.PersonGatewayAdapter -import com.alleslocker.backend.persistence.user.adapter.UserGatewayAdapter -import com.alleslocker.backend.persistence.vendor.adapter.VendorDataGatewayAdapter -import org.mockito.Mockito.mock -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.boot.autoconfigure.domain.EntityScan -import org.springframework.boot.test.context.TestConfiguration -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.Primary -import org.springframework.data.jpa.repository.config.EnableJpaRepositories - -@TestConfiguration(proxyBeanMethods = false) -@ComponentScan(basePackages = ["com.alleslocker.backend.persistence"]) -@EnableJpaRepositories(basePackages = ["com.alleslocker.backend.persistence"]) -@EntityScan(basePackages = ["com.alleslocker.backend.persistence"]) -open class TestUserIntegrationConfig { - @Bean - fun passwordHasher(): PasswordHasher = TestPasswordHasher() - - @Bean - @Primary - fun testLogger(): Logger = TestLogger() - - @Bean - fun lockAdapter(): LockAdapter = mock(LockAdapter::class.java) - - @Bean - fun personAdapter(): PersonAdapter = mock(PersonAdapter::class.java) - - @Bean - fun accessGrantAdapter(): AccessGrantAdapter = mock(AccessGrantAdapter::class.java) - - @Bean - fun vendorConnectionAdapter(): VendorConnectionAdapter = mock(VendorConnectionAdapter::class.java) - - @Bean - fun vendorSpecificDefinitionsAdapter(): VendorSpecificDefinitionsAdapter = - mock(VendorSpecificDefinitionsAdapter::class.java) - - @Bean - fun adapterFactory( - lockAdapter: LockAdapter, - personAdapter: PersonAdapter, - accessGrantAdapter: AccessGrantAdapter, - vendorConnectionAdapter: VendorConnectionAdapter, - vendorSpecificDefinitionsAdapter: VendorSpecificDefinitionsAdapter, - ): AdapterFactory = - AdapterFactoryImpl( - listOf( - lockAdapter, - personAdapter, - accessGrantAdapter, - vendorConnectionAdapter, - vendorSpecificDefinitionsAdapter, - ), - ) - - @Bean - fun gatewayFactory( - userGatewayAdapter: UserGatewayAdapter, - personGatewayAdapter: PersonGatewayAdapter, - lockGatewayAdapter: LockGatewayAdapter, - accessGrantGatewayAdapter: AccessGrantGatewayAdapter, - vendorDataGatewayAdapter: VendorDataGatewayAdapter, - auditLogGatewayAdapter: AuditLogGatewayAdapter, - ): GatewayFactory = - GatewayFactoryImpl( - listOf( - userGatewayAdapter, - personGatewayAdapter, - lockGatewayAdapter, - accessGrantGatewayAdapter, - vendorDataGatewayAdapter, - auditLogGatewayAdapter, - ), - ) - - @Bean - fun useCaseFactory( - gatewayFactory: GatewayFactory, - adapterFactory: AdapterFactory, - passwordHasher: PasswordHasher, - logger: Logger, - ): UseCaseFactory = UseCaseFactoryImpl(gatewayFactory, adapterFactory, passwordHasher, logger) -} diff --git a/bootstrap/src/test/resources/application-test.yml b/bootstrap/src/test/resources/application-test.yml deleted file mode 100644 index 03c726c8..00000000 --- a/bootstrap/src/test/resources/application-test.yml +++ /dev/null @@ -1,19 +0,0 @@ -spring: - main: - allow-bean-definition-overriding: true - datasource: - url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MariaDB;NON_KEYWORDS=USER,VALUE - driver-class-name: org.h2.Driver - jpa: - hibernate: - ddl-auto: create-drop - database-platform: org.hibernate.dialect.H2Dialect - properties: - hibernate: - show_sql: false - autoconfigure: - exclude: org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration - -app: - security: - master-key: A7wGBfP2Iz2tYU26/jBz4wUmfM+gpgGqAPTVezyXygY=