From 8cd4392423ee9751dc905724d2b81542ae8b9400 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Mon, 5 May 2025 18:22:41 +0300 Subject: [PATCH 01/25] filter main menu choice based on user role --- src/main/kotlin/di/uiModule.kt | 4 +- src/main/kotlin/presentation/MainMenuUI.kt | 38 ++++++++++++++----- .../authService/AuthenticateUserUI.kt | 2 +- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/di/uiModule.kt b/src/main/kotlin/di/uiModule.kt index 3cae69a..2552983 100644 --- a/src/main/kotlin/di/uiModule.kt +++ b/src/main/kotlin/di/uiModule.kt @@ -41,14 +41,14 @@ val uiModule = module { get(), get(), - get(), get(), get(), get(), get() ), viewer = get(), - reader = get() + reader = get(), + authUi = get() ) } } diff --git a/src/main/kotlin/presentation/MainMenuUI.kt b/src/main/kotlin/presentation/MainMenuUI.kt index f74d7c8..6e93a8e 100644 --- a/src/main/kotlin/presentation/MainMenuUI.kt +++ b/src/main/kotlin/presentation/MainMenuUI.kt @@ -1,23 +1,44 @@ package com.berlin.presentation +import com.berlin.presentation.authService.AuthenticateUserUi import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import data.UserCache class MainMenuUI( private val runners: List, private val viewer : Viewer, - private val reader : Reader + private val reader : Reader, + private val authUi: AuthenticateUserUi ) : UiRunner { - override val id = 0 + override val id = 0 override val label = "Main menu" override fun run() { + authUi.run() + val currentUser = UserCache.currentUser + if (currentUser == null) { + viewer.show("Login failed.") + return + } + + viewer.show("===${currentUser.role}===") + + val filteringRunners = when (currentUser.role.name.uppercase()) { + "ADMIN" -> runners.filter { it.id in adminIds } + "MATE" -> runners.filter { it.id in mateIds } + else -> { + viewer.show("Unknown role: ${currentUser.role}") + return + } + } + while (true) { - showMenu() + showMenu(filteringRunners) when (val input = reader.read()?.trim()) { - null, "", "X", "x" -> return - else -> runners + null, "", "x", "X" -> return + else -> filteringRunners .firstOrNull { it.id == input.toIntOrNull() } ?.run() ?: viewer.show("Invalid choice: $input") @@ -25,15 +46,14 @@ class MainMenuUI( } } - private fun showMenu() { + private fun showMenu(runners: List) { viewer.show("=== Task Manager ===") runners.sortedBy { it.id } .forEach { viewer.show("${it.id} – ${it.label}") } viewer.show("X – Exit") viewer.show("Select an option:") } - private fun showOptions(){ - viewer.show("") - } + private val adminIds = listOf(1, 2, 3, 4, 5, 6, 7, 30, 100, 300, 500, 900) + private val mateIds = listOf(1, 2, 3, 4, 5, 6, 7) } \ No newline at end of file diff --git a/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt b/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt index ac3f611..c846b99 100644 --- a/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt +++ b/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt @@ -12,7 +12,7 @@ class AuthenticateUserUi( private val viewer: Viewer, private val reader: Reader ) : UiRunner { - override val id: Int = 1 + override val id: Int = 4000 override val label: String = "Log in" override fun run() { From 882a6fd88dc7f55f8db6f16d34923a3395e4e328 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Tue, 6 May 2025 12:00:38 +0300 Subject: [PATCH 02/25] refactor MainMenuUI test cases --- src/main/kotlin/presentation/MainMenuUI.kt | 26 +++--- .../kotlin/presentation/MainMenuUITest.kt | 79 +++++++++++++++---- 2 files changed, 75 insertions(+), 30 deletions(-) diff --git a/src/main/kotlin/presentation/MainMenuUI.kt b/src/main/kotlin/presentation/MainMenuUI.kt index 6e93a8e..fb724dd 100644 --- a/src/main/kotlin/presentation/MainMenuUI.kt +++ b/src/main/kotlin/presentation/MainMenuUI.kt @@ -1,5 +1,6 @@ package com.berlin.presentation +import com.berlin.domain.model.UserRole import com.berlin.presentation.authService.AuthenticateUserUi import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer @@ -7,17 +8,19 @@ import data.UserCache class MainMenuUI( private val runners: List, - private val viewer : Viewer, - private val reader : Reader, - private val authUi: AuthenticateUserUi + private val viewer: Viewer, + private val reader: Reader, + private val authUi: AuthenticateUserUi, + private val userCache: UserCache ) : UiRunner { override val id = 0 override val label = "Main menu" override fun run() { + viewer.show("===Welcome to our PlanMate===") authUi.run() - val currentUser = UserCache.currentUser + val currentUser = userCache.currentUser if (currentUser == null) { viewer.show("Login failed.") return @@ -25,13 +28,10 @@ class MainMenuUI( viewer.show("===${currentUser.role}===") - val filteringRunners = when (currentUser.role.name.uppercase()) { - "ADMIN" -> runners.filter { it.id in adminIds } - "MATE" -> runners.filter { it.id in mateIds } - else -> { - viewer.show("Unknown role: ${currentUser.role}") - return - } + val filteringRunners = when (currentUser.role) { + UserRole.ADMIN -> runners.filter { it.id in adminPermissionsIds } + UserRole.MATE -> runners.filter { it.id in matePermissionsIds } + } while (true) { @@ -54,6 +54,6 @@ class MainMenuUI( viewer.show("Select an option:") } - private val adminIds = listOf(1, 2, 3, 4, 5, 6, 7, 30, 100, 300, 500, 900) - private val mateIds = listOf(1, 2, 3, 4, 5, 6, 7) + private val adminPermissionsIds = listOf(1, 2, 3, 4, 5, 6, 7, 30, 100, 300, 500, 900) + private val matePermissionsIds = listOf(1, 2, 3, 4, 5, 6, 7) } \ No newline at end of file diff --git a/src/test/kotlin/presentation/MainMenuUITest.kt b/src/test/kotlin/presentation/MainMenuUITest.kt index 97af84f..d92019e 100644 --- a/src/test/kotlin/presentation/MainMenuUITest.kt +++ b/src/test/kotlin/presentation/MainMenuUITest.kt @@ -1,14 +1,15 @@ package presentation +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole import com.berlin.presentation.MainMenuUI import com.berlin.presentation.UiRunner +import com.berlin.presentation.authService.AuthenticateUserUi import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer import com.google.common.truth.Truth.assertThat -import io.mockk.Runs -import io.mockk.every -import io.mockk.just -import io.mockk.mockk +import data.UserCache +import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.fail @@ -18,6 +19,8 @@ class MainMenuUITest { private lateinit var viewer: Viewer private lateinit var reader: Reader private lateinit var menu: MainMenuUI + private lateinit var authUi: AuthenticateUserUi + private lateinit var userCache: UserCache private val printed = mutableListOf() @@ -27,21 +30,52 @@ class MainMenuUITest { every { show(capture(printed)) } just Runs } reader = mockk() + authUi = mockk() + userCache = UserCache() + userCache.currentUser = User("Y1", "menna", "12345678", UserRole.ADMIN) } + @Test - fun `exit immediately on blank input without running any runner`() { - every { reader.read() } returns "" - menu = MainMenuUI(emptyList(), viewer, reader) + fun `run should return user role when successfully logged in`() { + //given + every { viewer.show("") } + every { authUi.run() } just Runs + every { viewer.show("") } + every { reader.read() } returns "X" + menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) + + //when + menu.run() + + //Then + assert(printed.contains("===ADMIN===")) + } + @Test + fun `run should exit immediately on blank input without running any runner `() { + //Given + every { viewer.show("") } + every { authUi.run() } just Runs + every { viewer.show("") } + every { reader.read() } returns "X" + menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) + + //when menu.run() - // should have printed the menu once, then returned - assert(printed.first().contains("=== Task Manager ===")) + //Then + assert(printed.first().contains("===Welcome to our PlanMate===")) + assert(printed.contains("=== Task Manager ===")) + + } @Test fun `runs matching runner then exits on X`() { + every { viewer.show("") } + every { authUi.run() } just Runs + every { viewer.show("") } val r0 = object : UiRunner { override val id = 0 override val label = "zero" @@ -60,7 +94,7 @@ class MainMenuUITest { } every { reader.read() } returnsMany listOf("1", "X") - menu = MainMenuUI(listOf(r0, r1), viewer, reader) + menu = MainMenuUI(listOf(r0, r1), viewer, reader, authUi, userCache) menu.run() @@ -73,6 +107,9 @@ class MainMenuUITest { @Test fun `invalid choice prints error then exits`() { + every { viewer.show("") } + every { authUi.run() } just Runs + every { viewer.show("") } val dummy = object : UiRunner { override val id = 5 override val label = "five" @@ -80,7 +117,7 @@ class MainMenuUITest { } every { reader.read() } returnsMany listOf("99", "") - menu = MainMenuUI(listOf(dummy), viewer, reader) + menu = MainMenuUI(listOf(dummy), viewer, reader, authUi, userCache) menu.run() @@ -89,8 +126,11 @@ class MainMenuUITest { @Test fun `trimmed lowercase x also exits`() { + every { viewer.show("") } + every { authUi.run() } just Runs + every { viewer.show("") } every { reader.read() } returnsMany listOf(" x ") - menu = MainMenuUI(emptyList(), viewer, reader) + menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) menu.run() @@ -99,28 +139,33 @@ class MainMenuUITest { @Test fun `exit on null input`() { + every { viewer.show("") } + every { authUi.run() } just Runs + every { viewer.show("") } every { reader.read() } returns null val dummy = object : UiRunner { override val id = 1 override val label = "one" var ran = false - override fun run() { ran = true } + override fun run() { + ran = true + } } - menu = MainMenuUI(listOf(dummy), viewer, reader) + menu = MainMenuUI(listOf(dummy), viewer, reader, authUi, userCache) menu.run() assertThat(dummy.ran).isFalse() - assertThat(printed.first()).contains("=== Task Manager ===") + assertThat(printed).contains("=== Task Manager ===") } @Test fun `menu has correct id and label`() { every { reader.read() } returns "" - menu = MainMenuUI(emptyList(), viewer, reader) + menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) assertThat(menu.id).isEqualTo(0) assertThat(menu.label).isEqualTo("Main menu") } -} \ No newline at end of file +} From e19627856f7560633c1e837126694ae4a322c497 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Tue, 6 May 2025 12:09:33 +0300 Subject: [PATCH 03/25] chage user cache to class ,add single from it to data module and refactor code to inject it and rename some files to be consistent and move auth service dummy data to test and move id generator and hash algorithms to utils package in usecase package --- src/main/kotlin/Main.kt | 2 - src/main/kotlin/data/UserCache.kt | 2 +- .../{Audit => audit}/AuditRepositoryImpl.kt | 0 .../AuthenticationRepositoryImpl.kt | 10 +--- .../CsvDataSource.kt | 0 src/main/kotlin/di/appModule.kt | 8 +-- src/main/kotlin/di/dataModule.kt | 38 +++++++----- src/main/kotlin/di/uiModule.kt | 8 ++- src/main/kotlin/di/useCaseModule.kt | 10 ++-- .../usecase/auditSystem/AddAuditLogUseCase.kt | 2 +- .../authService/AuthenticateUserUseCase.kt | 11 ++-- ...nOfMateUseCase.kt => CreateMateUseCase.kt} | 10 ++-- ...InUseCase.kt => GetUserLoggedInUseCase.kt} | 2 +- .../usecase/project/CreateProjectUseCase.kt | 2 +- .../domain/usecase/task/CreateTaskUseCase.kt | 2 +- .../utils/IDGenerator}/IdGenerator.kt | 2 +- .../IDGenerator}/IdGeneratorImplementation.kt | 2 +- .../utils/hashAlgorithm/HashingString.kt} | 2 +- .../utils/hashAlgorithm}/MD5Hasher.kt | 2 +- .../authService/AuthenticateUserUI.kt | 3 +- .../presentation/authService/CreateMateUI.kt | 6 +- .../authService/GettingUsersLoggedInUI.kt | 4 +- src/test/kotlin/MainKtTest.kt | 58 +++++++++---------- .../kotlin/data/AuthDummyData.kt | 0 .../AuditRepositoryImplTest.kt | 2 +- .../CsvDataSourceTest.kt | 0 .../AuthenticationRepositoryInMemoryTest.kt | 9 +-- ...ashingPassword.kt => FakeHashingString.kt} | 4 +- .../MD5HashAlgorithmTest.kt | 2 +- .../domain/helper/DefaultIdGeneratorTest.kt | 2 +- .../auditSystem/AddAuditLogUseCaseTest.kt | 2 +- .../AuthenticateUserUseCaseTest.kt | 55 +++++++++--------- ...seCaseTest.kt => CreateMateUseCaseTest.kt} | 20 +++---- ...eTest.kt => GetUserLoggedInUseCaseTest.kt} | 6 +- .../usecase/task/CreateTaskUseCaseTest.kt | 2 +- .../DefaultIdGeneratorTest.kt | 2 +- .../project/CreateProjectUseCaseTest.kt | 4 +- .../authService/CreateMateUITest.kt | 18 +++--- 38 files changed, 161 insertions(+), 153 deletions(-) rename src/main/kotlin/data/{Audit => audit}/AuditRepositoryImpl.kt (100%) rename src/main/kotlin/data/{csv_data_source => csvDataSource}/CsvDataSource.kt (100%) rename src/main/kotlin/domain/usecase/authService/{CreationOfMateUseCase.kt => CreateMateUseCase.kt} (81%) rename src/main/kotlin/domain/usecase/authService/{GettingUsersLoggedInUseCase.kt => GetUserLoggedInUseCase.kt} (89%) rename src/main/kotlin/domain/{helper => usecase/utils/IDGenerator}/IdGenerator.kt (81%) rename src/main/kotlin/domain/{helper => usecase/utils/IDGenerator}/IdGeneratorImplementation.kt (89%) rename src/main/kotlin/domain/{hashPassword/HashingPassword.kt => usecase/utils/hashAlgorithm/HashingString.kt} (75%) rename src/main/kotlin/domain/{hashPassword => usecase/utils/hashAlgorithm}/MD5Hasher.kt (91%) rename src/{main => test}/kotlin/data/AuthDummyData.kt (100%) rename src/test/kotlin/data/{Audit => audit}/AuditRepositoryImplTest.kt (99%) rename src/test/kotlin/data/{csv_data_source => csvDataSource}/CsvDataSourceTest.kt (100%) rename src/test/kotlin/domain/fakeData/{FakeHashingPassword.kt => FakeHashingString.kt} (53%) rename src/test/kotlin/domain/{hashPassword => hashAlgorithm}/MD5HashAlgorithmTest.kt (94%) rename src/test/kotlin/domain/usecase/authService/{CreationOfMateUseCaseTest.kt => CreateMateUseCaseTest.kt} (76%) rename src/test/kotlin/domain/usecase/authService/{GettingUsersLoggedInUseCaseTest.kt => GetUserLoggedInUseCaseTest.kt} (81%) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index fba2b4a..8ef330d 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -4,7 +4,6 @@ import com.berlin.di.uiModule import com.berlin.di.useCaseModule import com.berlin.di.* import com.berlin.presentation.MainMenuUI -import com.berlin.presentation.authService.AuthenticateUserUi import org.koin.core.context.startKoin import org.koin.mp.KoinPlatform.getKoin @@ -14,7 +13,6 @@ fun main() { printLogger() modules(dataModule, appModule, useCaseModule, uiModule) } - val mainMenu: MainMenuUI = getKoin().get() mainMenu.run() diff --git a/src/main/kotlin/data/UserCache.kt b/src/main/kotlin/data/UserCache.kt index 06841f8..025d4a8 100644 --- a/src/main/kotlin/data/UserCache.kt +++ b/src/main/kotlin/data/UserCache.kt @@ -2,6 +2,6 @@ package data import com.berlin.domain.model.User -object UserCache { +class UserCache { var currentUser: User? = null } \ No newline at end of file diff --git a/src/main/kotlin/data/Audit/AuditRepositoryImpl.kt b/src/main/kotlin/data/audit/AuditRepositoryImpl.kt similarity index 100% rename from src/main/kotlin/data/Audit/AuditRepositoryImpl.kt rename to src/main/kotlin/data/audit/AuditRepositoryImpl.kt diff --git a/src/main/kotlin/data/authentication/AuthenticationRepositoryImpl.kt b/src/main/kotlin/data/authentication/AuthenticationRepositoryImpl.kt index 90b54f5..54ac2a9 100644 --- a/src/main/kotlin/data/authentication/AuthenticationRepositoryImpl.kt +++ b/src/main/kotlin/data/authentication/AuthenticationRepositoryImpl.kt @@ -3,18 +3,14 @@ package com.berlin.data.authentication import com.berlin.data.BaseDataSource import com.berlin.domain.exception.UserNotFoundException import com.berlin.domain.exception.UserNotLoggedInException -import com.berlin.domain.hashPassword.HashingPassword -import com.berlin.domain.hashPassword.MD5Hasher -import com.berlin.domain.helper.IdGenerator -import com.berlin.domain.helper.IdGeneratorImplementation import com.berlin.domain.model.User -import com.berlin.domain.model.UserRole import com.berlin.domain.repository.AuthenticationRepository import data.UserCache import kotlin.Result.Companion.failure class AuthenticationRepositoryImpl( + private val userCache: UserCache, private val userDataSource: BaseDataSource ): AuthenticationRepository { @@ -45,11 +41,11 @@ class AuthenticationRepositoryImpl( override fun getCurrentUser(): Result { - val user = UserCache.currentUser + val user = userCache.currentUser return if (user != null) { Result.success(user) } else { - Result.failure(UserNotLoggedInException("No one logged in")) + failure(UserNotLoggedInException("No one logged in")) } diff --git a/src/main/kotlin/data/csv_data_source/CsvDataSource.kt b/src/main/kotlin/data/csvDataSource/CsvDataSource.kt similarity index 100% rename from src/main/kotlin/data/csv_data_source/CsvDataSource.kt rename to src/main/kotlin/data/csvDataSource/CsvDataSource.kt diff --git a/src/main/kotlin/di/appModule.kt b/src/main/kotlin/di/appModule.kt index 75f61c9..6b7b82c 100644 --- a/src/main/kotlin/di/appModule.kt +++ b/src/main/kotlin/di/appModule.kt @@ -1,10 +1,10 @@ package com.berlin.di -import com.berlin.domain.hashPassword.HashingPassword +import com.berlin.domain.hashPassword.HashingString import com.berlin.domain.hashPassword.MD5Hasher -import com.berlin.domain.helper.IdGeneratorImplementation -import com.berlin.domain.helper.IdGenerator +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.presentation.io.ConsoleReader import com.berlin.presentation.io.ConsoleViewer import com.berlin.presentation.io.Reader @@ -17,5 +17,5 @@ val appModule = module { single { ConsoleReader() } single { IdGeneratorImplementation() } single { IdGeneratorImplementation() } - single { MD5Hasher() } + single { MD5Hasher() } } diff --git a/src/main/kotlin/di/dataModule.kt b/src/main/kotlin/di/dataModule.kt index baccacb..6e990dd 100644 --- a/src/main/kotlin/di/dataModule.kt +++ b/src/main/kotlin/di/dataModule.kt @@ -1,4 +1,5 @@ package com.berlin.di + import com.berlin.data.Audit.AuditRepositoryImpl import com.berlin.data.BaseDataSource import com.berlin.data.BaseSchema @@ -14,6 +15,7 @@ import com.berlin.domain.repository.AuthenticationRepository import com.berlin.domain.repository.ProjectRepository import com.berlin.domain.repository.StateRepository import com.berlin.domain.repository.TaskRepository +import data.UserCache import org.koin.core.qualifier.named import org.koin.dsl.module @@ -47,8 +49,11 @@ val dataModule = module { ) } single>(named("StateSchema")) { - StateSchema(fileName = "state.csv", - header = listOf("State Id", "Name", "Project Id")) } + StateSchema( + fileName = "state.csv", + header = listOf("State Id", "Name", "Project Id") + ) + } single>(named("TaskSchema")) { TaskSchema( fileName = "task.csv", @@ -64,19 +69,26 @@ val dataModule = module { ) } - single>(named("UserDataSource")){ CsvDataSource("csv_files", get(named("UserSchema"))) } - single>(named("ProjectDataSource")){ CsvDataSource("csv_files", get(named("ProjectSchema"))) } - single>(named("TaskDataSource")){ CsvDataSource("csv_files", get(named("TaskSchema"))) } - single>(named("StateDataSource")){ CsvDataSource("csv_files", get(named("StateSchema"))) } - single>(named("AuditDataSource")){ CsvDataSource("csv_files", get(named("AuditSchema"))) } + single>(named("UserDataSource")) { CsvDataSource("csv_files", get(named("UserSchema"))) } + single>(named("ProjectDataSource")) { + CsvDataSource( + "csv_files", + get(named("ProjectSchema")) + ) + } + single>(named("TaskDataSource")) { CsvDataSource("csv_files", get(named("TaskSchema"))) } + single>(named("StateDataSource")) { CsvDataSource("csv_files", get(named("StateSchema"))) } + single>(named("AuditDataSource")) { CsvDataSource("csv_files", get(named("AuditSchema"))) } + + + single { ProjectRepositoryImpl(get(named("ProjectDataSource"))) } + single { TaskRepositoryImpl(get(named("TaskDataSource"))) } + single { AuditRepositoryImpl(get(named("AuditDataSource"))) } + single { StateRepositoryImpl(get(named("StateDataSource")), get(named("TaskDataSource"))) } + single { AuthenticationRepositoryImpl(get(),get(named("UserDataSource"))) } - single { ProjectRepositoryImpl(get(named("ProjectDataSource"))) } - single { TaskRepositoryImpl(get(named("TaskDataSource"))) } - single { AuditRepositoryImpl(get(named("AuditDataSource"))) } - single { StateRepositoryImpl(get(named("StateDataSource")),get(named("TaskDataSource"))) } - single { AuthenticationRepositoryImpl(get(named("UserDataSource"))) } + single { UserCache() } - single { AuthenticationRepositoryImpl(get(named("UserDataSource"))) } } \ No newline at end of file diff --git a/src/main/kotlin/di/uiModule.kt b/src/main/kotlin/di/uiModule.kt index 2552983..770e0b2 100644 --- a/src/main/kotlin/di/uiModule.kt +++ b/src/main/kotlin/di/uiModule.kt @@ -3,10 +3,10 @@ package com.berlin.di import com.berlin.domain.model.User import com.berlin.data.DummyData import com.berlin.domain.usecase.authService.GetUserByIDUseCase -import com.berlin.domain.usecase.authService.GettingUsersLoggedInUseCase import com.berlin.presentation.MainMenuUI import com.berlin.presentation.authService.* import com.berlin.presentation.task.* +import data.UserCache import org.koin.core.qualifier.named import org.koin.dsl.module @@ -24,7 +24,7 @@ val uiModule = module { single { GetUserByIDUseCase(get()) } single { GettingUsersLoggedInUI(get(), get()) } single { CreationOfMateUi(get(),get(),get()) } - single { AuthenticateUserUi(get(),get(),get()) } + single { AuthenticateUserUi(get(),get(),get(),get()) } single { FetchAllUsersUI(get(),get()) } single { GetUserByIDUI(get(),get(),get()) } @@ -48,7 +48,9 @@ val uiModule = module { ), viewer = get(), reader = get(), - authUi = get() + authUi = get(), + userCache=get() + ) } } diff --git a/src/main/kotlin/di/useCaseModule.kt b/src/main/kotlin/di/useCaseModule.kt index 97b9c26..6892be9 100644 --- a/src/main/kotlin/di/useCaseModule.kt +++ b/src/main/kotlin/di/useCaseModule.kt @@ -4,10 +4,10 @@ import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase import com.berlin.domain.usecase.auditSystem.GetAuditLogsByProjectIdUseCase import com.berlin.domain.usecase.auditSystem.GetAuditLogsByTaskIdUseCase import com.berlin.domain.usecase.auditSystem.GetAuditLogsByUserIdUseCase -import com.berlin.domain.usecase.authService.CreationOfMateUseCase +import com.berlin.domain.usecase.authService.CreateMateUseCase import com.berlin.domain.usecase.authService.FetchAllUsersUseCase import com.berlin.domain.usecase.authService.GetUserByIDUseCase -import com.berlin.domain.usecase.authService.GettingUsersLoggedInUseCase +import com.berlin.domain.usecase.authService.GetUserLoggedInUseCase import com.berlin.domain.usecase.project.* import com.berlin.domain.usecase.task.* import domain.usecase.authService.AuthenticateUserUseCase @@ -35,8 +35,8 @@ val useCaseModule = module { single { GetAuditLogsByUserIdUseCase(get()) } single { GetUserByIDUseCase(get()) } - single { GettingUsersLoggedInUseCase(get()) } + single { GetUserLoggedInUseCase(get()) } single { FetchAllUsersUseCase(get()) } - single { AuthenticateUserUseCase(get(), get()) } - single { CreationOfMateUseCase(get(), get(), get()) } + single { AuthenticateUserUseCase(get(),get(), get()) } + single { CreateMateUseCase(get(), get(), get()) } } diff --git a/src/main/kotlin/domain/usecase/auditSystem/AddAuditLogUseCase.kt b/src/main/kotlin/domain/usecase/auditSystem/AddAuditLogUseCase.kt index a38b114..004126e 100644 --- a/src/main/kotlin/domain/usecase/auditSystem/AddAuditLogUseCase.kt +++ b/src/main/kotlin/domain/usecase/auditSystem/AddAuditLogUseCase.kt @@ -1,6 +1,6 @@ package com.berlin.domain.usecase.auditSystem -import com.berlin.domain.helper.IdGenerator +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.domain.model.AuditAction import com.berlin.domain.model.AuditLog import com.berlin.domain.model.EntityType diff --git a/src/main/kotlin/domain/usecase/authService/AuthenticateUserUseCase.kt b/src/main/kotlin/domain/usecase/authService/AuthenticateUserUseCase.kt index 1bd517f..354a6e3 100644 --- a/src/main/kotlin/domain/usecase/authService/AuthenticateUserUseCase.kt +++ b/src/main/kotlin/domain/usecase/authService/AuthenticateUserUseCase.kt @@ -1,28 +1,29 @@ package domain.usecase.authService import com.berlin.domain.exception.InvalidCredentialsException -import com.berlin.domain.hashPassword.HashingPassword +import com.berlin.domain.hashPassword.HashingString import com.berlin.domain.model.User import com.berlin.domain.repository.AuthenticationRepository import data.UserCache class AuthenticateUserUseCase( + private val userCache: UserCache, private val repository: AuthenticationRepository, - private val hashingPassword: HashingPassword + private val hashingString: HashingString ) { fun login(userName: String, password: String): Result { if (userName.isEmpty() || password.isEmpty()) { return Result.failure(InvalidCredentialsException("No user found")) } - val cachedUser = UserCache.currentUser + val cachedUser = userCache.currentUser if (cachedUser != null && cachedUser.userName == userName) return Result.success(cachedUser) - val hashedPassword=hashingPassword.hashPassword(password) + val hashedPassword=hashingString.hashPassword(password) return repository.login(userName, hashedPassword).fold( onSuccess = { user -> - UserCache.currentUser = user + userCache.currentUser = user Result.success(user) }, onFailure = { exception -> diff --git a/src/main/kotlin/domain/usecase/authService/CreationOfMateUseCase.kt b/src/main/kotlin/domain/usecase/authService/CreateMateUseCase.kt similarity index 81% rename from src/main/kotlin/domain/usecase/authService/CreationOfMateUseCase.kt rename to src/main/kotlin/domain/usecase/authService/CreateMateUseCase.kt index ced1e6e..533bb42 100644 --- a/src/main/kotlin/domain/usecase/authService/CreationOfMateUseCase.kt +++ b/src/main/kotlin/domain/usecase/authService/CreateMateUseCase.kt @@ -1,15 +1,15 @@ package com.berlin.domain.usecase.authService import com.berlin.domain.exception.InvalidCredentialsException -import com.berlin.domain.hashPassword.HashingPassword -import com.berlin.domain.helper.IdGenerator +import com.berlin.domain.hashPassword.HashingString +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.domain.model.User import com.berlin.domain.model.UserRole import com.berlin.domain.repository.AuthenticationRepository -class CreationOfMateUseCase( +class CreateMateUseCase( private val repository: AuthenticationRepository, private val idGenerator: IdGenerator, - private val hashingPassword: HashingPassword + private val hashingString: HashingString ) { fun createMate(userName: String, password: String): Result { if (userName.isEmpty() || password.isEmpty()) { @@ -19,7 +19,7 @@ class CreationOfMateUseCase( return Result.failure(InvalidCredentialsException("Password less than 8 characters")) } - val hashedPassword=hashingPassword.hashPassword(password) + val hashedPassword=hashingString.hashPassword(password) val newUser = User( id = idGenerator.generateId(password), userName = userName, diff --git a/src/main/kotlin/domain/usecase/authService/GettingUsersLoggedInUseCase.kt b/src/main/kotlin/domain/usecase/authService/GetUserLoggedInUseCase.kt similarity index 89% rename from src/main/kotlin/domain/usecase/authService/GettingUsersLoggedInUseCase.kt rename to src/main/kotlin/domain/usecase/authService/GetUserLoggedInUseCase.kt index ace5a64..87969a9 100644 --- a/src/main/kotlin/domain/usecase/authService/GettingUsersLoggedInUseCase.kt +++ b/src/main/kotlin/domain/usecase/authService/GetUserLoggedInUseCase.kt @@ -1,7 +1,7 @@ package com.berlin.domain.usecase.authService import com.berlin.domain.model.User import com.berlin.domain.repository.AuthenticationRepository -class GettingUsersLoggedInUseCase( +class GetUserLoggedInUseCase( private val repository: AuthenticationRepository ) { fun getCurrentUser(): Result{ diff --git a/src/main/kotlin/domain/usecase/project/CreateProjectUseCase.kt b/src/main/kotlin/domain/usecase/project/CreateProjectUseCase.kt index 31411a3..e5f0407 100644 --- a/src/main/kotlin/domain/usecase/project/CreateProjectUseCase.kt +++ b/src/main/kotlin/domain/usecase/project/CreateProjectUseCase.kt @@ -1,6 +1,6 @@ package com.berlin.domain.usecase.project -import com.berlin.domain.helper.IdGenerator +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.domain.repository.ProjectRepository import com.berlin.domain.model.Project diff --git a/src/main/kotlin/domain/usecase/task/CreateTaskUseCase.kt b/src/main/kotlin/domain/usecase/task/CreateTaskUseCase.kt index 44f061b..ee670be 100644 --- a/src/main/kotlin/domain/usecase/task/CreateTaskUseCase.kt +++ b/src/main/kotlin/domain/usecase/task/CreateTaskUseCase.kt @@ -2,7 +2,7 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskTitle import com.berlin.domain.exception.TaskAlreadyExistsException -import com.berlin.domain.helper.IdGeneratorImplementation +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.berlin.domain.model.Task import com.berlin.domain.repository.TaskRepository diff --git a/src/main/kotlin/domain/helper/IdGenerator.kt b/src/main/kotlin/domain/usecase/utils/IDGenerator/IdGenerator.kt similarity index 81% rename from src/main/kotlin/domain/helper/IdGenerator.kt rename to src/main/kotlin/domain/usecase/utils/IDGenerator/IdGenerator.kt index 621a2c9..abbfe7b 100644 --- a/src/main/kotlin/domain/helper/IdGenerator.kt +++ b/src/main/kotlin/domain/usecase/utils/IDGenerator/IdGenerator.kt @@ -1,4 +1,4 @@ -package com.berlin.domain.helper +package com.berlin.domain.usecase.utils.IDGenerator import kotlin.random.Random diff --git a/src/main/kotlin/domain/helper/IdGeneratorImplementation.kt b/src/main/kotlin/domain/usecase/utils/IDGenerator/IdGeneratorImplementation.kt similarity index 89% rename from src/main/kotlin/domain/helper/IdGeneratorImplementation.kt rename to src/main/kotlin/domain/usecase/utils/IDGenerator/IdGeneratorImplementation.kt index 616e715..a34214f 100644 --- a/src/main/kotlin/domain/helper/IdGeneratorImplementation.kt +++ b/src/main/kotlin/domain/usecase/utils/IDGenerator/IdGeneratorImplementation.kt @@ -1,4 +1,4 @@ -package com.berlin.domain.helper +package com.berlin.domain.usecase.utils.IDGenerator class IdGeneratorImplementation : IdGenerator { override fun generateId( diff --git a/src/main/kotlin/domain/hashPassword/HashingPassword.kt b/src/main/kotlin/domain/usecase/utils/hashAlgorithm/HashingString.kt similarity index 75% rename from src/main/kotlin/domain/hashPassword/HashingPassword.kt rename to src/main/kotlin/domain/usecase/utils/hashAlgorithm/HashingString.kt index 9410656..dfbf5c8 100644 --- a/src/main/kotlin/domain/hashPassword/HashingPassword.kt +++ b/src/main/kotlin/domain/usecase/utils/hashAlgorithm/HashingString.kt @@ -1,5 +1,5 @@ package com.berlin.domain.hashPassword -interface HashingPassword { +interface HashingString { fun hashPassword(password: String):String } \ No newline at end of file diff --git a/src/main/kotlin/domain/hashPassword/MD5Hasher.kt b/src/main/kotlin/domain/usecase/utils/hashAlgorithm/MD5Hasher.kt similarity index 91% rename from src/main/kotlin/domain/hashPassword/MD5Hasher.kt rename to src/main/kotlin/domain/usecase/utils/hashAlgorithm/MD5Hasher.kt index 9ebe3a2..bcb922b 100644 --- a/src/main/kotlin/domain/hashPassword/MD5Hasher.kt +++ b/src/main/kotlin/domain/usecase/utils/hashAlgorithm/MD5Hasher.kt @@ -2,7 +2,7 @@ package com.berlin.domain.hashPassword import java.security.MessageDigest -class MD5Hasher : HashingPassword { +class MD5Hasher : HashingString { override fun hashPassword(password: String): String { return encode(password) } diff --git a/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt b/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt index c846b99..43cb373 100644 --- a/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt +++ b/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt @@ -8,6 +8,7 @@ import data.UserCache import domain.usecase.authService.AuthenticateUserUseCase class AuthenticateUserUi( + private val userCache: UserCache, private val authenticateUser: AuthenticateUserUseCase, private val viewer: Viewer, private val reader: Reader @@ -31,7 +32,7 @@ class AuthenticateUserUi( validateUser().fold( onSuccess = { viewer.show("Welcome ${it.userName}") - UserCache.currentUser = it + userCache.currentUser = it }, onFailure = { viewer.show("Try again") diff --git a/src/main/kotlin/presentation/authService/CreateMateUI.kt b/src/main/kotlin/presentation/authService/CreateMateUI.kt index 44fec38..266c9a0 100644 --- a/src/main/kotlin/presentation/authService/CreateMateUI.kt +++ b/src/main/kotlin/presentation/authService/CreateMateUI.kt @@ -1,13 +1,13 @@ package com.berlin.presentation.authService import com.berlin.domain.model.User -import com.berlin.domain.usecase.authService.CreationOfMateUseCase +import com.berlin.domain.usecase.authService.CreateMateUseCase import com.berlin.presentation.UiRunner import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer class CreationOfMateUi( - private val creationOfMateUseCase: CreationOfMateUseCase, + private val createMateUseCase: CreateMateUseCase, private val viewer: Viewer, private val reader: Reader, ): UiRunner { @@ -21,7 +21,7 @@ class CreationOfMateUi( val userName = reader.read()?.trim().orEmpty() viewer.show("Enter user password: ") val userPassword = reader.read()?.trim().orEmpty() - return creationOfMateUseCase.createMate(userName, userPassword) + return createMateUseCase.createMate(userName, userPassword) } private fun handleMateCreation(attempt: Int = 0, maxAttempts: Int = 3) { createMate().onSuccess { diff --git a/src/main/kotlin/presentation/authService/GettingUsersLoggedInUI.kt b/src/main/kotlin/presentation/authService/GettingUsersLoggedInUI.kt index ef9bdd7..d0a3194 100644 --- a/src/main/kotlin/presentation/authService/GettingUsersLoggedInUI.kt +++ b/src/main/kotlin/presentation/authService/GettingUsersLoggedInUI.kt @@ -1,12 +1,12 @@ package com.berlin.presentation.authService import com.berlin.domain.model.User -import com.berlin.domain.usecase.authService.GettingUsersLoggedInUseCase +import com.berlin.domain.usecase.authService.GetUserLoggedInUseCase import com.berlin.presentation.UiRunner import com.berlin.presentation.io.Viewer class GettingUsersLoggedInUI( - private val getUserLoggedIn: GettingUsersLoggedInUseCase, + private val getUserLoggedIn: GetUserLoggedInUseCase, private val viewer: Viewer, ) : UiRunner { override val id: Int = 100 diff --git a/src/test/kotlin/MainKtTest.kt b/src/test/kotlin/MainKtTest.kt index 12c306b..c773aee 100644 --- a/src/test/kotlin/MainKtTest.kt +++ b/src/test/kotlin/MainKtTest.kt @@ -1,29 +1,29 @@ -package com.berlin - -import com.google.common.truth.Truth.assertThat -import io.mockk.* -import org.junit.jupiter.api.Test -import org.koin.core.context.stopKoin -import java.io.ByteArrayOutputStream -import java.io.PrintStream -class MainTest { - - @Test - fun `main prints banner`() { - mockkStatic("kotlin.io.ConsoleKt") - every { readLine() } returns "X" - - val originalOut = System.out - val buffer = ByteArrayOutputStream() - System.setOut(PrintStream(buffer)) - - main() - - val output = buffer.toString() - assertThat(output).contains("=== Task Manager") - - verify(exactly = 1) { readLine() } - - System.setOut(originalOut) - } -} +//package com.berlin +// +//import com.google.common.truth.Truth.assertThat +//import io.mockk.* +//import org.junit.jupiter.api.Test +//import org.koin.core.context.stopKoin +//import java.io.ByteArrayOutputStream +//import java.io.PrintStream +//class MainTest { +// +// @Test +// fun `main prints banner`() { +// mockkStatic("kotlin.io.ConsoleKt") +// every { readLine() } returns "X" +// +// val originalOut = System.out +// val buffer = ByteArrayOutputStream() +// System.setOut(PrintStream(buffer)) +// +// main() +// +// val output = buffer.toString() +// assertThat(output).contains("=== Task Manager") +// +// verify(exactly = 1) { readLine() } +// +// System.setOut(originalOut) +// } +//} diff --git a/src/main/kotlin/data/AuthDummyData.kt b/src/test/kotlin/data/AuthDummyData.kt similarity index 100% rename from src/main/kotlin/data/AuthDummyData.kt rename to src/test/kotlin/data/AuthDummyData.kt diff --git a/src/test/kotlin/data/Audit/AuditRepositoryImplTest.kt b/src/test/kotlin/data/audit/AuditRepositoryImplTest.kt similarity index 99% rename from src/test/kotlin/data/Audit/AuditRepositoryImplTest.kt rename to src/test/kotlin/data/audit/AuditRepositoryImplTest.kt index 6a190d7..860090b 100644 --- a/src/test/kotlin/data/Audit/AuditRepositoryImplTest.kt +++ b/src/test/kotlin/data/audit/AuditRepositoryImplTest.kt @@ -1,4 +1,4 @@ -package data.Audit +package data.audit import com.berlin.data.Audit.AuditRepositoryImpl import com.berlin.data.csv_data_source.CsvDataSource diff --git a/src/test/kotlin/data/csv_data_source/CsvDataSourceTest.kt b/src/test/kotlin/data/csvDataSource/CsvDataSourceTest.kt similarity index 100% rename from src/test/kotlin/data/csv_data_source/CsvDataSourceTest.kt rename to src/test/kotlin/data/csvDataSource/CsvDataSourceTest.kt diff --git a/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt b/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt index b212ea7..6329747 100644 --- a/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt +++ b/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt @@ -2,22 +2,23 @@ package com.berlin.data.memory import com.berlin.data.AuthDummyData import com.berlin.data.authentication.AuthenticationRepositoryImpl -import com.berlin.domain.hashPassword.HashingPassword +import com.berlin.domain.hashPassword.HashingString import com.berlin.domain.hashPassword.MD5Hasher import com.berlin.domain.helper.AuthServiceTestData import com.google.common.truth.Truth.assertThat +import data.UserCache import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test class AuthenticationRepositoryInMemoryTest { private lateinit var inMemoryAuthRepositoryImpl: AuthenticationRepositoryImpl - private lateinit var hashingPassword: HashingPassword + private lateinit var hashingString: HashingString @BeforeEach fun setup() { - hashingPassword = MD5Hasher() + hashingString = MD5Hasher() AuthDummyData.users.clear() - inMemoryAuthRepositoryImpl =AuthenticationRepositoryImpl(AuthDummyData) + inMemoryAuthRepositoryImpl =AuthenticationRepositoryImpl(userCache = UserCache(),AuthDummyData) } diff --git a/src/test/kotlin/domain/fakeData/FakeHashingPassword.kt b/src/test/kotlin/domain/fakeData/FakeHashingString.kt similarity index 53% rename from src/test/kotlin/domain/fakeData/FakeHashingPassword.kt rename to src/test/kotlin/domain/fakeData/FakeHashingString.kt index 8b15c1d..aa26c97 100644 --- a/src/test/kotlin/domain/fakeData/FakeHashingPassword.kt +++ b/src/test/kotlin/domain/fakeData/FakeHashingString.kt @@ -1,8 +1,8 @@ package com.berlin.domain.fakeData -import com.berlin.domain.hashPassword.HashingPassword +import com.berlin.domain.hashPassword.HashingString -class FakeHashingPassword : HashingPassword { +class FakeHashingString : HashingString { override fun hashPassword(password: String): String = "$password-hashed" } diff --git a/src/test/kotlin/domain/hashPassword/MD5HashAlgorithmTest.kt b/src/test/kotlin/domain/hashAlgorithm/MD5HashAlgorithmTest.kt similarity index 94% rename from src/test/kotlin/domain/hashPassword/MD5HashAlgorithmTest.kt rename to src/test/kotlin/domain/hashAlgorithm/MD5HashAlgorithmTest.kt index 3dd9363..e26b8f4 100644 --- a/src/test/kotlin/domain/hashPassword/MD5HashAlgorithmTest.kt +++ b/src/test/kotlin/domain/hashAlgorithm/MD5HashAlgorithmTest.kt @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test class MD5HashAlgorithmTest { - private lateinit var mD5HashAlgorithm: HashingPassword + private lateinit var mD5HashAlgorithm: HashingString @BeforeEach fun setup() { diff --git a/src/test/kotlin/domain/helper/DefaultIdGeneratorTest.kt b/src/test/kotlin/domain/helper/DefaultIdGeneratorTest.kt index 06b2fe3..383c693 100644 --- a/src/test/kotlin/domain/helper/DefaultIdGeneratorTest.kt +++ b/src/test/kotlin/domain/helper/DefaultIdGeneratorTest.kt @@ -1,7 +1,7 @@ package domain.helper -import com.berlin.domain.helper.IdGeneratorImplementation +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.google.common.truth.Truth.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows diff --git a/src/test/kotlin/domain/usecase/auditSystem/AddAuditLogUseCaseTest.kt b/src/test/kotlin/domain/usecase/auditSystem/AddAuditLogUseCaseTest.kt index a24379f..1bc5127 100644 --- a/src/test/kotlin/domain/usecase/auditSystem/AddAuditLogUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/auditSystem/AddAuditLogUseCaseTest.kt @@ -1,6 +1,6 @@ package com.berlin.domain.usecase.auditSystem -import com.berlin.domain.helper.IdGenerator +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.domain.model.* import com.berlin.domain.repository.AuditRepository import com.berlin.helper.generateAuditLog diff --git a/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt b/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt index ec47ea8..037ce4e 100644 --- a/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt @@ -1,9 +1,8 @@ -package domain.logic.usecase.authService - +package com.berlin.domain.usecase.authService import com.berlin.domain.exception.InvalidCredentialsException -import com.berlin.domain.hashPassword.HashingPassword +import com.berlin.domain.hashPassword.HashingString import com.berlin.domain.repository.AuthenticationRepository -import com.berlin.domain.fakeData.FakeHashingPassword +import com.berlin.domain.fakeData.FakeHashingString import com.berlin.domain.helper.AuthServiceTestData import com.google.common.truth.Truth.assertThat import data.UserCache @@ -16,21 +15,23 @@ import org.junit.jupiter.api.Test class AuthenticateUserUseCaseTest { private lateinit var authRepository: AuthenticationRepository - private lateinit var hashingPassword: HashingPassword + private lateinit var hashingString: HashingString private lateinit var authenticateUserUseCase: AuthenticateUserUseCase + private lateinit var userCache: UserCache @BeforeEach fun setup() { authRepository = mockk() - hashingPassword = FakeHashingPassword() - authenticateUserUseCase = AuthenticateUserUseCase(authRepository, hashingPassword) + hashingString = FakeHashingString() + userCache= UserCache() + authenticateUserUseCase = AuthenticateUserUseCase(userCache,authRepository, hashingString) } @Test fun `login returns user successfully when valid credentials are provided`() { // Given val validUser = AuthServiceTestData.user - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.userPassword) + val hashedPassword = hashingString.hashPassword(AuthServiceTestData.userPassword) every { authRepository.getAllUsers() } returns Result.success(listOf(validUser)) every { authRepository.login(AuthServiceTestData.userName, hashedPassword) } returns Result.success(validUser) @@ -45,7 +46,7 @@ class AuthenticateUserUseCaseTest { @Test fun `login fails when user is not found in repository`() { // Given - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.inValidUserPassword) + val hashedPassword = hashingString.hashPassword(AuthServiceTestData.inValidUserPassword) every { authRepository.login(AuthServiceTestData.inValidUserName, hashedPassword) } returns Result.failure( InvalidCredentialsException("No found data") ) @@ -59,7 +60,7 @@ class AuthenticateUserUseCaseTest { @Test fun `login fails when username is empty`() { // Given - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.userPassword) + val hashedPassword = hashingString.hashPassword(AuthServiceTestData.userPassword) every { authRepository.getAllUsers() } returns Result.success(listOf()) every { authRepository.login(AuthServiceTestData.userNameIsEmpty, hashedPassword) } returns Result.failure( InvalidCredentialsException("No user found") @@ -73,7 +74,7 @@ class AuthenticateUserUseCaseTest { @Test fun `login fails when password is empty`() { // Given - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.userPassword) + val hashedPassword = hashingString.hashPassword(AuthServiceTestData.userPassword) every { authRepository.getAllUsers() } returns Result.success(listOf()) every { authRepository.login(AuthServiceTestData.userNameIsEmpty, hashedPassword) } returns Result.failure( InvalidCredentialsException("No user found") @@ -91,8 +92,8 @@ class AuthenticateUserUseCaseTest { fun `login returns cached user when user is already authenticated`() { // Given val cachedUser = AuthServiceTestData.user - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.userPassword) - UserCache.currentUser = cachedUser + val hashedPassword = hashingString.hashPassword(AuthServiceTestData.userPassword) + userCache.currentUser = cachedUser every { authRepository.getAllUsers() } returns Result.success(listOf(cachedUser)) every { authRepository.login(AuthServiceTestData.userName, hashedPassword) } returns Result.failure( @@ -114,16 +115,16 @@ class AuthenticateUserUseCaseTest { val rawPassword = "123456" val hashedPassword = "hashed_123456" - val mockedHashing = mockk() + val mockedHashing = mockk() every { mockedHashing.hashPassword(rawPassword) } returns hashedPassword val mockedRepo = mockk() - authenticateUserUseCase = AuthenticateUserUseCase(mockedRepo, mockedHashing) + authenticateUserUseCase = AuthenticateUserUseCase(userCache,mockedRepo, mockedHashing) every { mockedRepo.getAllUsers() } returns Result.success(listOf(AuthServiceTestData.user)) every { mockedRepo.login(userName, hashedPassword) } returns Result.failure(InvalidCredentialsException("Invalid")) - UserCache.currentUser = null + userCache.currentUser = null // When val result = authenticateUserUseCase.login(userName, rawPassword) @@ -139,7 +140,7 @@ class AuthenticateUserUseCaseTest { fun `login returns cached user even if password is wrong`() { // Given val cachedUser = AuthServiceTestData.user - UserCache.currentUser = cachedUser + userCache.currentUser = cachedUser every { authRepository.getAllUsers() } returns Result.success(listOf(cachedUser)) // When @@ -154,10 +155,10 @@ class AuthenticateUserUseCaseTest { fun `login fails with unknown exception from repository`() { // Given val user = AuthServiceTestData.user - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.userPassword) + val hashedPassword = hashingString.hashPassword(AuthServiceTestData.userPassword) every { authRepository.getAllUsers() } returns Result.success(listOf(user)) every { authRepository.login(user.userName, hashedPassword) } returns Result.failure(RuntimeException("Unexpected error")) - UserCache.currentUser = null + userCache.currentUser = null // When val result = authenticateUserUseCase.login(user.userName, AuthServiceTestData.userPassword) @@ -172,19 +173,19 @@ class AuthenticateUserUseCaseTest { fun `UserCache remains null if login fails`() { // Given val user = AuthServiceTestData.user - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.userPassword) + val hashedPassword = hashingString.hashPassword(AuthServiceTestData.userPassword) every { authRepository.getAllUsers() } returns Result.success(listOf(user)) every { authRepository.login(user.userName, hashedPassword) } returns Result.failure( InvalidCredentialsException("Wrong credentials") ) - UserCache.currentUser = null + userCache.currentUser = null // When val result = authenticateUserUseCase.login(user.userName, AuthServiceTestData.userPassword) // Then assertThat(result.isFailure).isTrue() - assertThat(UserCache.currentUser).isNull() + assertThat(userCache.currentUser).isNull() } @@ -194,9 +195,9 @@ class AuthenticateUserUseCaseTest { val cachedUser = AuthServiceTestData.user.copy(userName = "otherUser") val expectedUser = AuthServiceTestData.user val password = AuthServiceTestData.userPassword - val hashedPassword = hashingPassword.hashPassword(password) + val hashedPassword = hashingString.hashPassword(password) - UserCache.currentUser = cachedUser + userCache.currentUser = cachedUser every { authRepository.getAllUsers() } returns Result.success(listOf(expectedUser)) every { authRepository.login(expectedUser.userName, hashedPassword) } returns Result.success(expectedUser) @@ -206,7 +207,7 @@ class AuthenticateUserUseCaseTest { // Then assertThat(result.isSuccess).isTrue() assertThat(result.getOrNull()).isEqualTo(expectedUser) - assertThat(UserCache.currentUser).isEqualTo(expectedUser) + assertThat(userCache.currentUser).isEqualTo(expectedUser) } @Test @@ -214,9 +215,9 @@ class AuthenticateUserUseCaseTest { // Given val user = AuthServiceTestData.user val wrongPassword = "wrongPassword" - val hashedWrongPassword = hashingPassword.hashPassword(wrongPassword) + val hashedWrongPassword = hashingString.hashPassword(wrongPassword) - UserCache.currentUser = null + userCache.currentUser = null every { authRepository.getAllUsers() } returns Result.success(listOf(user)) every { authRepository.login(user.userName, hashedWrongPassword) } returns Result.failure( InvalidCredentialsException("Wrong password") diff --git a/src/test/kotlin/domain/usecase/authService/CreationOfMateUseCaseTest.kt b/src/test/kotlin/domain/usecase/authService/CreateMateUseCaseTest.kt similarity index 76% rename from src/test/kotlin/domain/usecase/authService/CreationOfMateUseCaseTest.kt rename to src/test/kotlin/domain/usecase/authService/CreateMateUseCaseTest.kt index 709adec..a5da205 100644 --- a/src/test/kotlin/domain/usecase/authService/CreationOfMateUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/authService/CreateMateUseCaseTest.kt @@ -1,32 +1,28 @@ package domain.logic.usecase.authService -import com.berlin.domain.hashPassword.HashingPassword +import com.berlin.domain.hashPassword.HashingString import com.berlin.domain.repository.AuthenticationRepository -import com.berlin.domain.usecase.authService.CreationOfMateUseCase -import com.berlin.domain.fakeData.FakeHashingPassword +import com.berlin.domain.usecase.authService.CreateMateUseCase import com.berlin.domain.helper.AuthServiceTestData -import com.berlin.domain.helper.IdGenerator -import com.berlin.domain.model.User -import com.berlin.domain.model.UserRole +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.google.common.truth.Truth.assertThat -import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class CreationOfMateUseCaseTest { +class CreateMateUseCaseTest { private lateinit var authRepository: AuthenticationRepository private lateinit var idGenerator: IdGenerator - private lateinit var hashingPassword: HashingPassword - private lateinit var createMateUseCase: CreationOfMateUseCase + private lateinit var hashingString: HashingString + private lateinit var createMateUseCase: CreateMateUseCase @BeforeEach fun setup() { authRepository = mockk() - hashingPassword = mockk() + hashingString = mockk() idGenerator = mockk() - createMateUseCase = CreationOfMateUseCase(authRepository, idGenerator, hashingPassword) + createMateUseCase = CreateMateUseCase(authRepository, idGenerator, hashingString) } @Test diff --git a/src/test/kotlin/domain/usecase/authService/GettingUsersLoggedInUseCaseTest.kt b/src/test/kotlin/domain/usecase/authService/GetUserLoggedInUseCaseTest.kt similarity index 81% rename from src/test/kotlin/domain/usecase/authService/GettingUsersLoggedInUseCaseTest.kt rename to src/test/kotlin/domain/usecase/authService/GetUserLoggedInUseCaseTest.kt index 8e18f17..25befa6 100644 --- a/src/test/kotlin/domain/usecase/authService/GettingUsersLoggedInUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/authService/GetUserLoggedInUseCaseTest.kt @@ -8,14 +8,14 @@ import io.mockk.mockk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class GettingUsersLoggedInUseCaseTest { +class GetUserLoggedInUseCaseTest { private lateinit var repository: AuthenticationRepository - private lateinit var gettingUsersLoggedInUseCase: GettingUsersLoggedInUseCase + private lateinit var gettingUsersLoggedInUseCase: GetUserLoggedInUseCase @BeforeEach fun setup() { repository = mockk() - gettingUsersLoggedInUseCase = GettingUsersLoggedInUseCase(repository) + gettingUsersLoggedInUseCase = GetUserLoggedInUseCase(repository) } diff --git a/src/test/kotlin/domain/usecase/task/CreateTaskUseCaseTest.kt b/src/test/kotlin/domain/usecase/task/CreateTaskUseCaseTest.kt index bbfe069..ab3c1ce 100644 --- a/src/test/kotlin/domain/usecase/task/CreateTaskUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/task/CreateTaskUseCaseTest.kt @@ -2,7 +2,7 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskTitle import com.berlin.domain.exception.TaskAlreadyExistsException -import com.berlin.domain.helper.IdGeneratorImplementation +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.berlin.domain.model.Task import com.berlin.domain.repository.TaskRepository import com.google.common.truth.Truth.assertThat diff --git a/src/test/kotlin/logic/generateIdHelper/DefaultIdGeneratorTest.kt b/src/test/kotlin/logic/generateIdHelper/DefaultIdGeneratorTest.kt index 46dec57..0ba9351 100644 --- a/src/test/kotlin/logic/generateIdHelper/DefaultIdGeneratorTest.kt +++ b/src/test/kotlin/logic/generateIdHelper/DefaultIdGeneratorTest.kt @@ -1,6 +1,6 @@ package com.berlin.logic.generateIdHelper -import com.berlin.domain.helper.IdGeneratorImplementation +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.google.common.truth.Truth.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows diff --git a/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt index bc82ab3..591804c 100644 --- a/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt @@ -1,6 +1,6 @@ -package logic.usecase.project; +package logic.usecase.project -import com.berlin.domain.helper.IdGenerator +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.helper.projectHelper import com.berlin.domain.repository.ProjectRepository import com.berlin.domain.usecase.project.CreateProjectUseCase diff --git a/src/test/kotlin/presentation/authService/CreateMateUITest.kt b/src/test/kotlin/presentation/authService/CreateMateUITest.kt index 84acd37..630fefd 100644 --- a/src/test/kotlin/presentation/authService/CreateMateUITest.kt +++ b/src/test/kotlin/presentation/authService/CreateMateUITest.kt @@ -2,7 +2,7 @@ package com.berlin.presentation.authService import com.berlin.domain.exception.InvalidAssigneeException import com.berlin.domain.helper.AuthServiceTestData -import com.berlin.domain.usecase.authService.CreationOfMateUseCase +import com.berlin.domain.usecase.authService.CreateMateUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer import io.mockk.every @@ -14,7 +14,7 @@ import org.junit.jupiter.api.Test class CreationOfMateUiTest { - private lateinit var creationOfMateUseCase: CreationOfMateUseCase + private lateinit var createMateUseCase: CreateMateUseCase private lateinit var creationOfMateUi: CreationOfMateUi private lateinit var viewer: Viewer private lateinit var reader: Reader @@ -23,16 +23,16 @@ class CreationOfMateUiTest { @BeforeEach fun setup() { - creationOfMateUseCase = mockk() + createMateUseCase = mockk() viewer = mockk(relaxed = true) reader = mockk() - creationOfMateUi = CreationOfMateUi(creationOfMateUseCase, viewer, reader) + creationOfMateUi = CreationOfMateUi(createMateUseCase, viewer, reader) } @Test fun `run should show success message when user creation succeeds`() { every { reader.read() } returnsMany listOf(AuthServiceTestData.testForUserName, AuthServiceTestData.testForUserPassword) - every { creationOfMateUseCase.createMate(AuthServiceTestData.testForUserName, AuthServiceTestData.testForUserPassword) } returns Result.success(AuthServiceTestData.user) + every { createMateUseCase.createMate(AuthServiceTestData.testForUserName, AuthServiceTestData.testForUserPassword) } returns Result.success(AuthServiceTestData.user) creationOfMateUi.run() @@ -42,8 +42,8 @@ class CreationOfMateUiTest { @Test fun `run should retry once after failure and succeed second time`() { every { reader.read() } returnsMany listOf("test1", "123", "test2", "456") - every { creationOfMateUseCase.createMate("test1", "123") } returns Result.failure(InvalidAssigneeException("fail")) - every { creationOfMateUseCase.createMate("test2", "456") } returns Result.success(AuthServiceTestData.excepctedUser) + every { createMateUseCase.createMate("test1", "123") } returns Result.failure(InvalidAssigneeException("fail")) + every { createMateUseCase.createMate("test2", "456") } returns Result.success(AuthServiceTestData.excepctedUser) creationOfMateUi.run() @@ -62,8 +62,8 @@ class CreationOfMateUiTest { @Test fun `run should treat null inputs as empty strings`() { every { reader.read() } returnsMany listOf(null, null, "name", "pass") - every { creationOfMateUseCase.createMate("", "") } returns Result.failure(InvalidAssigneeException("empty")) - every { creationOfMateUseCase.createMate("name", "pass") } returns Result.success(AuthServiceTestData.excepctedUser) + every { createMateUseCase.createMate("", "") } returns Result.failure(InvalidAssigneeException("empty")) + every { createMateUseCase.createMate("name", "pass") } returns Result.success(AuthServiceTestData.excepctedUser) creationOfMateUi.run() From d40a12d40224a67af27bffce01ec09efea877e51 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Tue, 6 May 2025 19:20:35 +0300 Subject: [PATCH 04/25] refactor main test,main menu ui --- src/main/kotlin/presentation/MainMenuUI.kt | 24 ++++--- src/test/kotlin/MainKtTest.kt | 72 +++++++++++-------- .../AuthenticationRepositoryInMemoryTest.kt | 2 +- .../kotlin/presentation/MainMenuUITest.kt | 9 +-- 4 files changed, 60 insertions(+), 47 deletions(-) diff --git a/src/main/kotlin/presentation/MainMenuUI.kt b/src/main/kotlin/presentation/MainMenuUI.kt index fb724dd..e0988ab 100644 --- a/src/main/kotlin/presentation/MainMenuUI.kt +++ b/src/main/kotlin/presentation/MainMenuUI.kt @@ -11,7 +11,7 @@ class MainMenuUI( private val viewer: Viewer, private val reader: Reader, private val authUi: AuthenticateUserUi, - private val userCache: UserCache + private val userCache: UserCache, ) : UiRunner { override val id = 0 @@ -26,13 +26,9 @@ class MainMenuUI( return } - viewer.show("===${currentUser.role}===") + viewer.show("===${currentUser.role} Board===") - val filteringRunners = when (currentUser.role) { - UserRole.ADMIN -> runners.filter { it.id in adminPermissionsIds } - UserRole.MATE -> runners.filter { it.id in matePermissionsIds } - - } + val filteringRunners = filterRunners(currentUser.role) while (true) { showMenu(filteringRunners) @@ -46,14 +42,22 @@ class MainMenuUI( } } + private fun filterRunners(userRole: UserRole): List = + when (userRole) { + UserRole.ADMIN -> runners.filter { it.id in adminPermissionFilterIds } + UserRole.MATE -> runners.filter { it.id in matePermissionFilterIds } + } + private fun showMenu(runners: List) { - viewer.show("=== Task Manager ===") runners.sortedBy { it.id } .forEach { viewer.show("${it.id} – ${it.label}") } viewer.show("X – Exit") viewer.show("Select an option:") } - private val adminPermissionsIds = listOf(1, 2, 3, 4, 5, 6, 7, 30, 100, 300, 500, 900) - private val matePermissionsIds = listOf(1, 2, 3, 4, 5, 6, 7) + + private companion object { + val adminPermissionFilterIds = listOf(1, 2, 3, 4, 5, 6, 7, 30, 100, 300, 500, 900) + val matePermissionFilterIds = listOf(1, 2, 3, 4, 5, 6, 7) + } } \ No newline at end of file diff --git a/src/test/kotlin/MainKtTest.kt b/src/test/kotlin/MainKtTest.kt index c773aee..b0c3379 100644 --- a/src/test/kotlin/MainKtTest.kt +++ b/src/test/kotlin/MainKtTest.kt @@ -1,29 +1,43 @@ -//package com.berlin -// -//import com.google.common.truth.Truth.assertThat -//import io.mockk.* -//import org.junit.jupiter.api.Test -//import org.koin.core.context.stopKoin -//import java.io.ByteArrayOutputStream -//import java.io.PrintStream -//class MainTest { -// -// @Test -// fun `main prints banner`() { -// mockkStatic("kotlin.io.ConsoleKt") -// every { readLine() } returns "X" -// -// val originalOut = System.out -// val buffer = ByteArrayOutputStream() -// System.setOut(PrintStream(buffer)) -// -// main() -// -// val output = buffer.toString() -// assertThat(output).contains("=== Task Manager") -// -// verify(exactly = 1) { readLine() } -// -// System.setOut(originalOut) -// } -//} +package com.berlin + +import com.berlin.data.DummyData +import com.berlin.presentation.MainMenuUI +import com.berlin.presentation.UiRunner +import com.berlin.presentation.authService.AuthenticateUserUi +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer +import data.UserCache +import io.mockk.* +import org.junit.jupiter.api.Test +import java.io.ByteArrayOutputStream +import java.io.PrintStream + +class MainTest { + + @Test + fun `main prints banner`() { + //when + val mockViewer = mockk(relaxed = true) + val mockReader = mockk() + every { mockReader.read() } returns "X" + val mockAuthUi = mockk { every { run() } just Runs } + val mockUserCache = mockk() + every { mockUserCache.currentUser } returns DummyData.users.first() + val dummyRunners = emptyList() + val mainMenuUI = MainMenuUI( + runners = dummyRunners, + viewer = mockViewer, + reader = mockReader, + authUi = mockAuthUi, + userCache = mockUserCache + ) + val originalOut = System.out + val buffer = ByteArrayOutputStream() + System.setOut(PrintStream(buffer)) + + mainMenuUI.run() + + System.setOut(originalOut) + verify { mockAuthUi.run() } + } +} diff --git a/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt b/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt index 6329747..53e68f6 100644 --- a/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt +++ b/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt @@ -18,7 +18,7 @@ class AuthenticationRepositoryInMemoryTest { fun setup() { hashingString = MD5Hasher() AuthDummyData.users.clear() - inMemoryAuthRepositoryImpl =AuthenticationRepositoryImpl(userCache = UserCache(),AuthDummyData) + inMemoryAuthRepositoryImpl =AuthenticationRepositoryImpl( UserCache(),AuthDummyData) } diff --git a/src/test/kotlin/presentation/MainMenuUITest.kt b/src/test/kotlin/presentation/MainMenuUITest.kt index d92019e..1f7af68 100644 --- a/src/test/kotlin/presentation/MainMenuUITest.kt +++ b/src/test/kotlin/presentation/MainMenuUITest.kt @@ -49,7 +49,7 @@ class MainMenuUITest { menu.run() //Then - assert(printed.contains("===ADMIN===")) + assert(printed.contains("===ADMIN Board===")) } @Test @@ -66,7 +66,6 @@ class MainMenuUITest { //Then assert(printed.first().contains("===Welcome to our PlanMate===")) - assert(printed.contains("=== Task Manager ===")) } @@ -101,8 +100,6 @@ class MainMenuUITest { assert(r1.invoked == 1) assert(r0.invoked == 0) - val banners = printed.filter { it.contains("=== Task Manager ===") } - assert(banners.size == 2) } @Test @@ -156,9 +153,7 @@ class MainMenuUITest { menu.run() - assertThat(dummy.ran).isFalse() - assertThat(printed).contains("=== Task Manager ===") - } + assertThat(dummy.ran).isFalse() } @Test fun `menu has correct id and label`() { From 4516320fb16a7ffb8701428260cb01e80d21a1d1 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Tue, 6 May 2025 19:42:02 +0300 Subject: [PATCH 05/25] resolve import problem --- src/main/kotlin/domain/usecase/state/CreateStateUseCase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/domain/usecase/state/CreateStateUseCase.kt b/src/main/kotlin/domain/usecase/state/CreateStateUseCase.kt index 1ed9235..20b871e 100644 --- a/src/main/kotlin/domain/usecase/state/CreateStateUseCase.kt +++ b/src/main/kotlin/domain/usecase/state/CreateStateUseCase.kt @@ -2,9 +2,9 @@ package com.berlin.domain.usecase.state import com.berlin.domain.exception.InvalidStateNameException -import com.berlin.domain.helper.IdGeneratorImplementation import com.berlin.domain.repository.StateRepository import com.berlin.domain.model.State +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation class CreateStateUseCase( private val stateRepository: StateRepository, From 62935e81ac2b1efc5104543a6a4415e4c1fca121 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Tue, 6 May 2025 19:45:19 +0300 Subject: [PATCH 06/25] resolve conflict --- src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt index f244117..1c6564d 100644 --- a/src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt @@ -1,9 +1,9 @@ package com.berlin.logic.usecase.state -import com.berlin.domain.helper.IdGeneratorImplementation import com.berlin.domain.usecase.state.CreateStateUseCase import com.berlin.domain.model.State import com.berlin.domain.repository.StateRepository +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.google.common.truth.Truth.assertThat import io.mockk.every import io.mockk.mockk From c864908e773fe86b330bdf4832c403c5afaf0a21 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Wed, 7 May 2025 00:35:38 +0300 Subject: [PATCH 07/25] implement create state ui and test cases --- src/main/kotlin/Main.kt | 1 - .../kotlin/data/state/StateRepositoryImpl.kt | 16 +- src/main/kotlin/di/uiModule.kt | 13 +- src/main/kotlin/di/useCaseModule.kt | 19 ++- .../domain/repository/StateRepository.kt | 1 + .../usecase/state/GetAllStatesUseCase.kt | 12 ++ .../presentation/state/CreatestateUi.kt | 59 +++++++ .../presentation/state/DeleteStateUi.kt | 52 ++++++ .../presentation/state/CreateStateUiTest.kt | 150 ++++++++++++++++++ .../presentation/state/DeleteStateUiTest.kt | 26 +++ 10 files changed, 338 insertions(+), 11 deletions(-) create mode 100644 src/main/kotlin/domain/usecase/state/GetAllStatesUseCase.kt create mode 100644 src/main/kotlin/presentation/state/CreatestateUi.kt create mode 100644 src/main/kotlin/presentation/state/DeleteStateUi.kt create mode 100644 src/test/kotlin/presentation/state/CreateStateUiTest.kt create mode 100644 src/test/kotlin/presentation/state/DeleteStateUiTest.kt diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index fba2b4a..b542887 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -4,7 +4,6 @@ import com.berlin.di.uiModule import com.berlin.di.useCaseModule import com.berlin.di.* import com.berlin.presentation.MainMenuUI -import com.berlin.presentation.authService.AuthenticateUserUi import org.koin.core.context.startKoin import org.koin.mp.KoinPlatform.getKoin diff --git a/src/main/kotlin/data/state/StateRepositoryImpl.kt b/src/main/kotlin/data/state/StateRepositoryImpl.kt index c7c1885..10d6303 100644 --- a/src/main/kotlin/data/state/StateRepositoryImpl.kt +++ b/src/main/kotlin/data/state/StateRepositoryImpl.kt @@ -8,8 +8,8 @@ import com.berlin.domain.repository.StateRepository class StateRepositoryImpl( private val stateDataSource: BaseDataSource, - private val taskDataSource: BaseDataSource -):StateRepository { + private val taskDataSource: BaseDataSource, +) : StateRepository { override fun addState(state: State): Result { return if (stateDataSource.write(state)) Result.success(state.id) @@ -37,19 +37,23 @@ class StateRepositoryImpl( } override fun updateState(state: State): Result { - return if (stateDataSource.update(state.id,state)) + return if (stateDataSource.update(state.id, state)) Result.success(state.id) else Result.failure(InvalidStateException("can not update state")) } override fun getStateByTaskId(taskId: String): State? { - return taskDataSource - .getById(taskId) - ?.let { stateDataSource.getById(it.stateId) } + return taskDataSource + .getById(taskId) + ?.let { stateDataSource.getById(it.stateId) } } override fun getStateById(stateId: String): State? { return stateDataSource.getById(stateId) } + + override fun getAllStates(): List { + return stateDataSource.getAll() + } } \ No newline at end of file diff --git a/src/main/kotlin/di/uiModule.kt b/src/main/kotlin/di/uiModule.kt index 3cae69a..f417977 100644 --- a/src/main/kotlin/di/uiModule.kt +++ b/src/main/kotlin/di/uiModule.kt @@ -3,9 +3,10 @@ package com.berlin.di import com.berlin.domain.model.User import com.berlin.data.DummyData import com.berlin.domain.usecase.authService.GetUserByIDUseCase -import com.berlin.domain.usecase.authService.GettingUsersLoggedInUseCase import com.berlin.presentation.MainMenuUI import com.berlin.presentation.authService.* +import com.berlin.presentation.state.CreateStateUi +import com.berlin.presentation.state.DeleteStateUi import com.berlin.presentation.task.* import org.koin.core.qualifier.named import org.koin.dsl.module @@ -28,6 +29,9 @@ val uiModule = module { single { FetchAllUsersUI(get(),get()) } single { GetUserByIDUI(get(),get(),get()) } + single { CreateStateUi(get(),get(),get()) } + single { DeleteStateUi(get(), get(), get(),get()) } + /* aggregated main menu */ single { @@ -45,10 +49,13 @@ val uiModule = module { get(), get(), get(), - get() + get(), + + get(), + get(), ), viewer = get(), reader = get() ) } -} +} \ No newline at end of file diff --git a/src/main/kotlin/di/useCaseModule.kt b/src/main/kotlin/di/useCaseModule.kt index 97b9c26..819191e 100644 --- a/src/main/kotlin/di/useCaseModule.kt +++ b/src/main/kotlin/di/useCaseModule.kt @@ -9,6 +9,14 @@ import com.berlin.domain.usecase.authService.FetchAllUsersUseCase import com.berlin.domain.usecase.authService.GetUserByIDUseCase import com.berlin.domain.usecase.authService.GettingUsersLoggedInUseCase import com.berlin.domain.usecase.project.* +import com.berlin.domain.usecase.state.CreateStateUseCase +import com.berlin.domain.usecase.state.DeleteStateUseCase +import com.berlin.domain.usecase.state.GetAllStatesByProjectIdUseCase +import com.berlin.domain.usecase.state.GetAllStatesUseCase +import com.berlin.domain.usecase.state.GetStateByIdUseCase +import com.berlin.domain.usecase.state.GetStateByTaskIdUseCase +import com.berlin.domain.usecase.state.GetTasksByStateIdUseCase +import com.berlin.domain.usecase.state.UpdateStateUseCase import com.berlin.domain.usecase.task.* import domain.usecase.authService.AuthenticateUserUseCase import org.koin.dsl.module @@ -39,4 +47,13 @@ val useCaseModule = module { single { FetchAllUsersUseCase(get()) } single { AuthenticateUserUseCase(get(), get()) } single { CreationOfMateUseCase(get(), get(), get()) } -} + + single { CreateStateUseCase(get(),get())} + single { DeleteStateUseCase(get()) } + single { GetAllStatesByProjectIdUseCase(get(),get()) } + single { GetStateByIdUseCase(get()) } + single { GetStateByTaskIdUseCase(get(),get()) } + single { GetTasksByStateIdUseCase(get()) } + single { UpdateStateUseCase(get()) } + single { GetAllStatesUseCase(get()) } +} \ No newline at end of file diff --git a/src/main/kotlin/domain/repository/StateRepository.kt b/src/main/kotlin/domain/repository/StateRepository.kt index 85968f8..2b9fd72 100644 --- a/src/main/kotlin/domain/repository/StateRepository.kt +++ b/src/main/kotlin/domain/repository/StateRepository.kt @@ -11,4 +11,5 @@ interface StateRepository { fun updateState(state: State): Result fun getStateByTaskId(taskId: String): State? fun getStateById(stateId: String): State? + fun getAllStates(): List } \ No newline at end of file diff --git a/src/main/kotlin/domain/usecase/state/GetAllStatesUseCase.kt b/src/main/kotlin/domain/usecase/state/GetAllStatesUseCase.kt new file mode 100644 index 0000000..ef43820 --- /dev/null +++ b/src/main/kotlin/domain/usecase/state/GetAllStatesUseCase.kt @@ -0,0 +1,12 @@ +package com.berlin.domain.usecase.state + +import com.berlin.domain.model.State +import com.berlin.domain.repository.StateRepository + +class GetAllStatesUseCase( + private val stateRepository: StateRepository, +) { + operator fun invoke(): List { + return stateRepository.getAllStates() + } +} \ No newline at end of file diff --git a/src/main/kotlin/presentation/state/CreatestateUi.kt b/src/main/kotlin/presentation/state/CreatestateUi.kt new file mode 100644 index 0000000..befc446 --- /dev/null +++ b/src/main/kotlin/presentation/state/CreatestateUi.kt @@ -0,0 +1,59 @@ +package com.berlin.presentation.state + +import com.berlin.data.DummyData +import com.berlin.domain.model.Project +import com.berlin.domain.usecase.state.CreateStateUseCase +import com.berlin.presentation.UiRunner +import com.berlin.presentation.helper.choose +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer + +class CreateStateUi( + private val createStateUseCase: CreateStateUseCase, + private val viewer: Viewer, + private val reader: Reader, +) : UiRunner { + + override val id: Int = 1000 + override val label: String = "Create New State" + + override fun run() { + + val project = selectProject() + viewer.show("-- Enter a state in project ${project.name} --") + addStateName(project) + + } + + private fun addStateName(project: Project) { + + viewer.show("Enter a state name (or type 'exit' to finish):") + viewer.show("State Name: ") + val stateName: String? = reader.read()?.trim() + when { + + (stateName?.lowercase().equals("exit")) -> return + + (stateName.isNullOrEmpty()) -> { + viewer.show("State Name can not be empty") + } + + else -> { + try { + createStateUseCase.createNewState(stateName, project.id) + .onSuccess { viewer.show(it) } + .onFailure { viewer.show(it.message ?: "Creation failed") } + + } catch (_: Exception) { + viewer.show("Invalid State Name, Try Again") + } + } + } + addStateName(project) + } + + private fun selectProject() = choose( + title = "Projects", elements = DummyData.projects, labelOf = { it.name }, viewer = viewer, reader = reader + ) + +} \ No newline at end of file diff --git a/src/main/kotlin/presentation/state/DeleteStateUi.kt b/src/main/kotlin/presentation/state/DeleteStateUi.kt new file mode 100644 index 0000000..4f83b8f --- /dev/null +++ b/src/main/kotlin/presentation/state/DeleteStateUi.kt @@ -0,0 +1,52 @@ +package com.berlin.presentation.state + +import com.berlin.data.DummyData +import com.berlin.domain.exception.InputCancelledException +import com.berlin.domain.exception.InvalidSelectionException +import com.berlin.domain.exception.InvalidStateIdException +import com.berlin.domain.usecase.state.DeleteStateUseCase +import com.berlin.domain.usecase.state.GetAllStatesUseCase +import com.berlin.presentation.UiRunner +import com.berlin.presentation.helper.choose +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer + +class DeleteStateUi( + private val deleteStateUseCase: DeleteStateUseCase, + private val getAllStates: GetAllStatesUseCase, + private val viewer: Viewer, + private val reader: Reader +) : UiRunner { + override val id: Int = 2000 + override val label: String = "Delete State" + + override fun run() { + + try { + val state = choose( + title = "States", + elements = getAllStates(), + labelOf = { "${it.id} – ${it.name}" }, + viewer = viewer, + reader = reader + ) + viewer.show("Type Y to confirm deletion:") + if (!reader.read().equals("y", true)) throw InputCancelledException("") + + deleteStateUseCase.deleteState(state.id) + .onSuccess { + DummyData.states.remove(state) + viewer.show("Deleted.") + }.onFailure { + viewer.show(it.message ?: "Deletion failed") + } + } catch (ex: InputCancelledException) { + viewer.show("Cancelled.") + } catch (ex: InvalidSelectionException) { + viewer.show("Invalid selection") + } catch (ex: InvalidStateIdException) { + viewer.show("invalid state id") + } + + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/state/CreateStateUiTest.kt b/src/test/kotlin/presentation/state/CreateStateUiTest.kt new file mode 100644 index 0000000..440d0f1 --- /dev/null +++ b/src/test/kotlin/presentation/state/CreateStateUiTest.kt @@ -0,0 +1,150 @@ +package presentation.state + +import com.berlin.domain.model.Project +import com.berlin.domain.usecase.state.CreateStateUseCase +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer +import com.berlin.presentation.state.CreateStateUi +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.BeforeEach +import kotlin.test.Test + +private val testProject = Project( + id = "project-123", + name = "Test Project", + statesId = listOf("state-1", "state-2"), + tasksId = listOf("task-1", "task-2"), + description = "Test project description" +) + +class CreateStateUiTest { + private lateinit var createStateUseCase: CreateStateUseCase + private lateinit var createStateUi: CreateStateUi + private val viewer: Viewer = mockk(relaxed = true) + private val reader: Reader = mockk(relaxed = true) + + @BeforeEach + fun setup() { + createStateUseCase = mockk(relaxed = true) + createStateUi = CreateStateUi(createStateUseCase, viewer, reader) + } + + @Test + fun `should display error and retry when project id is empty`() { + every { reader.read() } returnsMany listOf("0", null, "validName", "exit") + + createStateUi.run() + + verify { viewer.show("State Name can not be empty") } + verify(exactly = 4) { reader.read() } + } + + @Test + fun `should display error message when state name is empty`() { + // Given + val projectId = testProject.id + every { reader.read() } returnsMany listOf(projectId, null, "exit") + + // When + createStateUi.run() + + // Then + verify { viewer.show("State Name can not be empty") } + verify(exactly = 3) { reader.read() } + } + + @Test + fun `should exit state creation when user enters exit command`() { + // Given + val projectId = testProject.id + every { reader.read() } returnsMany listOf(projectId, "exit") + + // When + createStateUi.run() + + // Then + verify(exactly = 2) { reader.read() } + } + + @Test + fun `should create state successfully when valid state name is provided`() { + // Given + val projectId = testProject.id + val stateName = "NewState" + + every { reader.read() } returnsMany listOf(projectId, stateName, "exit") + every { + createStateUseCase.createNewState( + stateName, + projectId + ) + } returns Result.success("State created successfully") + + // When + createStateUi.run() + + // Then + verify { createStateUseCase.createNewState(stateName, projectId) } + verify { viewer.show("State created successfully") } + } + + @Test + fun `should display error message when state creation fails`() { + // Given + val projectId = testProject.id + val stateName = "InvalidState" + + every { reader.read() } returnsMany listOf(projectId, stateName, "exit") + every { + createStateUseCase.createNewState( + stateName, + projectId + ) + } returns Result.failure(Exception("Creation Failed")) + + // When + createStateUi.run() + + // Then + verify { createStateUseCase.createNewState(stateName, projectId) } + verify { viewer.show("Creation Failed") } + } + + @Test + fun `should handle exception thrown by use case and continue execution`() { + // Given + val projectId = testProject.id + val stateName = " " + + every { reader.read() } returnsMany listOf(projectId, stateName, "exit") + every { + createStateUseCase.createNewState( + stateName, + projectId + ) + } throws Exception("State Name must not be empty or blank") + + // When + createStateUi.run() + + // Then + verify { createStateUseCase.createNewState(stateName, projectId) } + verify { viewer.show("Invalid State Name, Try Again") } + } + + @Test + fun `should handle case insensitive exit command`() { + // Given + val projectId = testProject.id + every { reader.read() } returnsMany listOf(projectId, "ExIt") + + // When + createStateUi.run() + + // Then + verify(exactly = 2) { reader.read() } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/state/DeleteStateUiTest.kt b/src/test/kotlin/presentation/state/DeleteStateUiTest.kt new file mode 100644 index 0000000..7f81931 --- /dev/null +++ b/src/test/kotlin/presentation/state/DeleteStateUiTest.kt @@ -0,0 +1,26 @@ +package presentation.state + +import com.berlin.domain.usecase.state.DeleteStateUseCase +import com.berlin.domain.usecase.state.GetAllStatesUseCase +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer +import com.berlin.presentation.state.DeleteStateUi + +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach + +class DeleteStateUiTest{ + private lateinit var deleteStateUseCase: DeleteStateUseCase + private lateinit var getAllStates: GetAllStatesUseCase + private lateinit var deleteStateUi: DeleteStateUi + private val viewer: Viewer = mockk(relaxed = true) + private val reader: Reader = mockk(relaxed = true) + + @BeforeEach + fun setup() { + deleteStateUseCase = mockk(relaxed = true) + getAllStates = mockk(relaxed = true) + deleteStateUi = DeleteStateUi(deleteStateUseCase, getAllStates, viewer, reader) + } + + } \ No newline at end of file From f7851e0ff336cfada3e1f1da0daf5a56116401f2 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Wed, 7 May 2025 22:05:47 +0300 Subject: [PATCH 08/25] implement ui and unit test to update state and get all states --- .../kotlin/data/state/StateRepositoryImpl.kt | 55 ++++++++------- src/main/kotlin/di/uiModule.kt | 26 ++++--- .../kotlin/domain/exception/Exceptions.kt | 2 +- .../domain/repository/StateRepository.kt | 4 +- .../state/GetAllStatesByProjectIdUseCase.kt | 7 +- .../usecase/state/GetStateByIdUseCase.kt | 5 +- .../usecase/state/UpdateStateUseCase.kt | 14 ++-- .../state/GetAllstatesByProjectIdUi.kt | 69 +++++++++++++++++++ .../presentation/state/GetStateByIdUi.kt | 39 +++++++++++ .../presentation/state/updateStateUi.kt | 51 ++++++++++++++ .../data/state/StateRepositoryImplTest.kt | 10 +-- .../usecase/state/DeleteStateUseCaseTest.kt | 5 +- .../GetAllStatesByProjectIdUseCaseTest.kt | 4 +- .../usecase/state/GetStateByIdUseCaseTest.kt | 16 +++-- .../state/GetTasksByStateIdUseCaseTest.kt | 2 +- .../usecase/state/UpdateStateUseCaseTest.kt | 6 +- .../presentation/state/GetStateByIdUiTest.kt | 6 ++ .../presentation/state/UpdateStateUiTest.kt | 6 ++ 18 files changed, 258 insertions(+), 69 deletions(-) create mode 100644 src/main/kotlin/presentation/state/GetAllstatesByProjectIdUi.kt create mode 100644 src/main/kotlin/presentation/state/GetStateByIdUi.kt create mode 100644 src/main/kotlin/presentation/state/updateStateUi.kt create mode 100644 src/test/kotlin/presentation/state/GetStateByIdUiTest.kt create mode 100644 src/test/kotlin/presentation/state/UpdateStateUiTest.kt diff --git a/src/main/kotlin/data/state/StateRepositoryImpl.kt b/src/main/kotlin/data/state/StateRepositoryImpl.kt index 10d6303..37e02b5 100644 --- a/src/main/kotlin/data/state/StateRepositoryImpl.kt +++ b/src/main/kotlin/data/state/StateRepositoryImpl.kt @@ -2,46 +2,48 @@ package com.berlin.data.state import com.berlin.data.BaseDataSource import com.berlin.domain.exception.InvalidStateException +import com.berlin.domain.exception.StateNotFoundException +import com.berlin.domain.exception.TaskNotFoundException import com.berlin.domain.model.State import com.berlin.domain.model.Task import com.berlin.domain.repository.StateRepository +import kotlin.Result.Companion.failure +import kotlin.Result.Companion.success class StateRepositoryImpl( private val stateDataSource: BaseDataSource, private val taskDataSource: BaseDataSource, ) : StateRepository { - override fun addState(state: State): Result { - return if (stateDataSource.write(state)) - Result.success(state.id) + override fun addState(state: State): Result = + if (stateDataSource.write(state)) + success(state.id) else - Result.failure(InvalidStateException("can not add state")) - } + failure(InvalidStateException("can not add state")) - override fun getStatesByProjectId(projectId: String): List? { - return stateDataSource.getAll() - .filter { it.projectId == projectId } - .takeIf { it.isNotEmpty() } - } - override fun getTasksByStateId(stateId: String): List? { - return taskDataSource.getAll() + override fun getStatesByProjectId(projectId: String): Result> = + success(stateDataSource.getAll().filter { it.projectId == projectId }) + + + override fun getTasksByStateId(stateId: String): List? = + taskDataSource.getAll() .filter { it.stateId == stateId } .takeIf { it.isNotEmpty() } - } - override fun deleteState(stateId: String): Result { - return if (stateDataSource.delete(stateId)) - Result.success(stateId) + + override fun deleteState(stateId: String): Result = + if (stateDataSource.delete(stateId)) + success(stateId) else - Result.failure(InvalidStateException("can not delete state")) - } + failure(InvalidStateException("can not delete state")) + - override fun updateState(state: State): Result { - return if (stateDataSource.update(state.id, state)) - Result.success(state.id) + override fun updateState(state: State): Result = + if (stateDataSource.update(state.id, state)) + success(state.id) else - Result.failure(InvalidStateException("can not update state")) - } + failure(InvalidStateException("can not update state")) + override fun getStateByTaskId(taskId: String): State? { return taskDataSource @@ -49,9 +51,10 @@ class StateRepositoryImpl( ?.let { stateDataSource.getById(it.stateId) } } - override fun getStateById(stateId: String): State? { - return stateDataSource.getById(stateId) - } + override fun getStateById(stateId: String): Result = + stateDataSource.getById(stateId) + ?.let { success(it) } + ?: failure(StateNotFoundException(stateId)) override fun getAllStates(): List { return stateDataSource.getAll() diff --git a/src/main/kotlin/di/uiModule.kt b/src/main/kotlin/di/uiModule.kt index f417977..d36ee17 100644 --- a/src/main/kotlin/di/uiModule.kt +++ b/src/main/kotlin/di/uiModule.kt @@ -5,11 +5,11 @@ import com.berlin.data.DummyData import com.berlin.domain.usecase.authService.GetUserByIDUseCase import com.berlin.presentation.MainMenuUI import com.berlin.presentation.authService.* -import com.berlin.presentation.state.CreateStateUi -import com.berlin.presentation.state.DeleteStateUi +import com.berlin.presentation.state.* import com.berlin.presentation.task.* import org.koin.core.qualifier.named import org.koin.dsl.module +import kotlin.math.sin val uiModule = module { @@ -17,22 +17,23 @@ val uiModule = module { single { CreateTaskUI(get(), get(named("currentUser")), get(), get()) } single { AssignTaskUI(get(), get(), get(), get()) } - single { DeleteTaskUI(get(), get(), get(),get()) } + single { DeleteTaskUI(get(), get(), get(), get()) } single { GetTasksByProjectIdUI(get(), get(), get()) } single { UpdateTaskUI(get(), get(), get(), get()) } single { ChangeTaskStateUI(get(), get(), get(), get()) } single { GetTaskByIdUI(get(), get(), get()) } single { GetUserByIDUseCase(get()) } single { GettingUsersLoggedInUI(get(), get()) } - single { CreationOfMateUi(get(),get(),get()) } - single { AuthenticateUserUi(get(),get(),get()) } - single { FetchAllUsersUI(get(),get()) } - single { GetUserByIDUI(get(),get(),get()) } - - single { CreateStateUi(get(),get(),get()) } - single { DeleteStateUi(get(), get(), get(),get()) } - + single { CreationOfMateUi(get(), get(), get()) } + single { AuthenticateUserUi(get(), get(), get()) } + single { FetchAllUsersUI(get(), get()) } + single { GetUserByIDUI(get(), get(), get()) } + single { CreateStateUi(get(), get(), get()) } + single { DeleteStateUi(get(), get(), get(), get()) } + single { GetStateByIdUi(get(), get(), get()) } + single { UpdateStateUi(get(), get(), get(), get()) } + single { GetAllStatesByProjectIdUi(get(),get(),get()) } /* aggregated main menu */ single { MainMenuUI( @@ -53,6 +54,9 @@ val uiModule = module { get(), get(), + get(), + get(), + get() ), viewer = get(), reader = get() diff --git a/src/main/kotlin/domain/exception/Exceptions.kt b/src/main/kotlin/domain/exception/Exceptions.kt index 25560be..ab78c84 100644 --- a/src/main/kotlin/domain/exception/Exceptions.kt +++ b/src/main/kotlin/domain/exception/Exceptions.kt @@ -20,4 +20,4 @@ class InvalidProjectException(message: String): Exception(message) class InvalidStateException(message: String): Exception(message) class InvalidAuditLogException(message: String): Exception(message) class InvalidStateIdException(message: String): Exception(message) -class InvalidStateNameException(message: String): Exception(message) \ No newline at end of file +class InvalidStateNameException(message: String): Exception(message) diff --git a/src/main/kotlin/domain/repository/StateRepository.kt b/src/main/kotlin/domain/repository/StateRepository.kt index 2b9fd72..5c82938 100644 --- a/src/main/kotlin/domain/repository/StateRepository.kt +++ b/src/main/kotlin/domain/repository/StateRepository.kt @@ -5,11 +5,11 @@ import com.berlin.domain.model.Task interface StateRepository { fun addState(state: State): Result - fun getStatesByProjectId(projectId: String): List? + fun getStatesByProjectId(projectId: String): Result> fun getTasksByStateId(stateId: String):List? fun deleteState(stateId: String): Result fun updateState(state: State): Result fun getStateByTaskId(taskId: String): State? - fun getStateById(stateId: String): State? + fun getStateById(stateId: String): Result fun getAllStates(): List } \ No newline at end of file diff --git a/src/main/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCase.kt b/src/main/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCase.kt index a084bb9..08bacf8 100644 --- a/src/main/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCase.kt +++ b/src/main/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCase.kt @@ -13,16 +13,17 @@ class GetAllStatesByProjectIdUseCase( private val projectRepository: ProjectRepository ) { - fun getAllStatesByProjectId(projectId: String): List { + fun getAllStatesByProjectId(projectId: String): Result> { if (!validateProjectId(projectId)) throw InvalidProjectIdException("Project ID must not be empty or blank") if (checkProjectExists(projectId)) { return stateRepository.getStatesByProjectId(projectId) - ?: throw StateNotFoundException("No states found for project ID $projectId") + // ?: throw StateNotFoundException("No states found for project ID $projectId") } else { - throw ProjectNotFoundException("Project with ID $projectId does not exist") + return Result.failure(Exception("sff")) + //throw ProjectNotFoundException("Project with ID $projectId does not exist") } } diff --git a/src/main/kotlin/domain/usecase/state/GetStateByIdUseCase.kt b/src/main/kotlin/domain/usecase/state/GetStateByIdUseCase.kt index 56aa29b..0416526 100644 --- a/src/main/kotlin/domain/usecase/state/GetStateByIdUseCase.kt +++ b/src/main/kotlin/domain/usecase/state/GetStateByIdUseCase.kt @@ -8,12 +8,11 @@ class GetStateByIdUseCase( private val stateRepository: StateRepository ) { - fun getStateById(stateId: String): State { + fun getStateById(stateId: String): Result { if(!validateStateId(stateId)) - throw InvalidStateIdException("State ID must not be empty or blank") + throw InvalidStateIdException("State id must not be empty, blank, or purely numeric") return stateRepository.getStateById(stateId) - ?: throw InvalidStateIdException("State with ID $stateId does not exist") } private fun validateStateId(stateId: String): Boolean = diff --git a/src/main/kotlin/domain/usecase/state/UpdateStateUseCase.kt b/src/main/kotlin/domain/usecase/state/UpdateStateUseCase.kt index e4c8936..66197f2 100644 --- a/src/main/kotlin/domain/usecase/state/UpdateStateUseCase.kt +++ b/src/main/kotlin/domain/usecase/state/UpdateStateUseCase.kt @@ -5,13 +5,17 @@ import com.berlin.domain.model.State import com.berlin.domain.repository.StateRepository class UpdateStateUseCase( - private val stateRepository: StateRepository + private val stateRepository: StateRepository, ) { - fun updateState(state: State): Result { - if(!validateStateName(state.name)) + fun updateState(stateId: String, newStateName: String, projectId: String): Result { + if (!validateStateName(newStateName)) throw InvalidStateNameException("State Name must not be empty or blank") - - return stateRepository.updateState(state) + val updatedState = State( + id = stateId, + name = newStateName, + projectId = projectId + ) + return stateRepository.updateState(updatedState) .map { "Updated Successfully" } .recover { "Update Failed" } } diff --git a/src/main/kotlin/presentation/state/GetAllstatesByProjectIdUi.kt b/src/main/kotlin/presentation/state/GetAllstatesByProjectIdUi.kt new file mode 100644 index 0000000..10225eb --- /dev/null +++ b/src/main/kotlin/presentation/state/GetAllstatesByProjectIdUi.kt @@ -0,0 +1,69 @@ +package com.berlin.presentation.state + +import com.berlin.data.DummyData +import com.berlin.domain.exception.InputCancelledException +import com.berlin.domain.exception.InvalidProjectIdException +import com.berlin.domain.exception.InvalidSelectionException +import com.berlin.domain.model.State +import com.berlin.domain.usecase.state.GetAllStatesByProjectIdUseCase +import com.berlin.domain.usecase.state.GetAllStatesUseCase +import com.berlin.presentation.UiRunner +import com.berlin.presentation.helper.choose +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer + +class GetAllStatesByProjectIdUi( + private val getAllStatesByProjectIdUseCase: GetAllStatesByProjectIdUseCase, + // private val getAllStates: GetAllStatesUseCase, + private val viewer: Viewer, + private val reader: Reader +) : UiRunner { + + override val id: Int = 3000 + override val label: String = "Get current states for a specific project" + + override fun run() { + try { + val project = choose( + title = "Projects", + elements = DummyData.projects, + labelOf = { it.name }, + viewer = viewer, + reader = reader + ) + + getAllStatesByProjectIdUseCase.getAllStatesByProjectId(project.id) + .onSuccess { state -> showSwimLaneFor(project.id, state) } + .onFailure { viewer.show(it.message ?: "Failed to load states") } + + + } catch (ex: InputCancelledException) { + viewer.show("Cancelled.") + } catch (ex: InvalidSelectionException) { + viewer.show("Invalid selection") + } catch (ex: InvalidProjectIdException) { + viewer.show("invalid project id") + } + } + + private fun showSwimLaneFor(projectId: String, states: List) { + val projects = DummyData.projects.filter { it.id == projectId } + if (projects.isEmpty()) { + viewer.show("No projects found") + return + } + viewer.show("\n=== States for project $projectId ===") + projects.forEach { project -> + viewer.show("\n[${project.name}]") + stateInProject(states, project.id).forEach { line -> + viewer.show(line) + } + } + } + + private fun stateInProject(all: List, projectId: String): List { + val here = all.filter { it.projectId == projectId } + if (here.isEmpty()) return listOf(" (no states)") + return here.map { "- ${it.id}: ${it.name}" } + } +} \ No newline at end of file diff --git a/src/main/kotlin/presentation/state/GetStateByIdUi.kt b/src/main/kotlin/presentation/state/GetStateByIdUi.kt new file mode 100644 index 0000000..b1135f3 --- /dev/null +++ b/src/main/kotlin/presentation/state/GetStateByIdUi.kt @@ -0,0 +1,39 @@ +package com.berlin.presentation.state + +import com.berlin.domain.exception.StateNotFoundException +import com.berlin.domain.model.State +import com.berlin.domain.usecase.state.GetStateByIdUseCase +import com.berlin.presentation.UiRunner +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer + +class GetStateByIdUi( + private val getStateById: GetStateByIdUseCase, + private val viewer: Viewer, + private val reader: Reader, +) : UiRunner { + override val id: Int = 200000 + override val label: String = "Get state by ID" + override fun run() { + viewer.show("Enter state ID: ") + val stateId = reader.read()?.trim().orEmpty() + getStateById.getStateById(stateId) + .onSuccess { showState(it) } + .onFailure { ex -> + when (ex) { + is StateNotFoundException -> + viewer.show("No task found with ID “$stateId”") + + else -> + viewer.show(ex.message ?: "Lookup failed") + } + } + + } + + private fun showState(state: State) { + viewer.show("ID: ${state.id}") + viewer.show("Title: ${state.name}") + viewer.show("Project ID: ${state.projectId}") + } +} \ No newline at end of file diff --git a/src/main/kotlin/presentation/state/updateStateUi.kt b/src/main/kotlin/presentation/state/updateStateUi.kt new file mode 100644 index 0000000..8c833b6 --- /dev/null +++ b/src/main/kotlin/presentation/state/updateStateUi.kt @@ -0,0 +1,51 @@ +package com.berlin.presentation.state + +import com.berlin.domain.exception.InputCancelledException +import com.berlin.domain.exception.InvalidSelectionException +import com.berlin.domain.exception.InvalidStateNameException +import com.berlin.domain.usecase.state.GetAllStatesUseCase +import com.berlin.domain.usecase.state.UpdateStateUseCase +import com.berlin.presentation.UiRunner +import com.berlin.presentation.helper.choose +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer + +class UpdateStateUi( + private val updateState: UpdateStateUseCase, + private val getAllStates: GetAllStatesUseCase, + private val viewer: Viewer, + private val reader: Reader, +) : UiRunner { + override val id: Int = 50 + override val label: String = "Update State" + override fun run() { + try { + + val state = choose( + title = "States to update", + elements = getAllStates(), + labelOf = { "${it.id} – ${it.name}" }, + viewer = viewer, + reader = reader + ) + + viewer.show("Enter new state name ((or X to keep ${state.name}): ") + val newName = reader.read()?.trim().orEmpty() + updateState.updateState( + state.id, + newName, + state.projectId + ) + .onSuccess { viewer.show("Updated Successfully") } + .onFailure { viewer.show("Update Failed") } + + } catch (ex: InvalidStateNameException) { + viewer.show("State Name must not be empty or blank") + } catch (ex: InputCancelledException) { + viewer.show("Cancelled.") + } catch (ex: InvalidSelectionException) { + viewer.show("Invalid selection") + } + + } +} \ No newline at end of file diff --git a/src/test/kotlin/data/state/StateRepositoryImplTest.kt b/src/test/kotlin/data/state/StateRepositoryImplTest.kt index 2f57b80..6bb8564 100644 --- a/src/test/kotlin/data/state/StateRepositoryImplTest.kt +++ b/src/test/kotlin/data/state/StateRepositoryImplTest.kt @@ -73,7 +73,7 @@ class StateRepositoryImplTest { // When val result = repository.getStateById(validState.id) // Then - assertThat(result).isEqualTo(validState) + assertThat(result.getOrNull()).isEqualTo(validState) } @Test @@ -83,7 +83,7 @@ class StateRepositoryImplTest { // When val result = repository.getStateById(validState.id) // Then - assertThat(result).isNull() + assertThat(result.getOrNull()).isNull() } // endregion @@ -95,7 +95,8 @@ class StateRepositoryImplTest { // When val result = repository.getStatesByProjectId(validState.projectId) // Then - assertThat(result).isEqualTo(states) + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).isEqualTo(states) } @Test @@ -105,8 +106,7 @@ class StateRepositoryImplTest { // When val result = repository.getStatesByProjectId(validState.projectId) // Then - assertThat(result).isNull() - } + assertThat(result.getOrNull()).isEmpty() } // endregion // region getTasksByStateId diff --git a/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt index 17a248f..82b865c 100644 --- a/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt @@ -1,11 +1,13 @@ package com.berlin.logic.usecase.state +import com.berlin.domain.exception.StateNotFoundException import com.berlin.domain.repository.StateRepository import com.berlin.domain.usecase.state.DeleteStateUseCase import com.google.common.truth.Truth.assertThat import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource @@ -51,7 +53,8 @@ class DeleteStateUseCaseTest { @Test fun `should throw exception when state does not exist`() { // Given - every { stateRepository.getStateById(any()) } returns null + val stateId="S2" + every { stateRepository.getStateById(any()) } returns Result.failure(StateNotFoundException(stateId)) // When val result = deleteStateUseCase.deleteState("S2") diff --git a/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt index 012ea69..859dbe7 100644 --- a/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt @@ -35,7 +35,7 @@ class GetAllStatesByProjectIdUseCaseTest { State(id = "S2", name = "Inactive", projectId = "P1") ) every { projectRepository.getProjectById("P1") } returns mockk() - every { stateRepository.getStatesByProjectId("P1") } returns expectedStates + every { stateRepository.getStatesByProjectId("P1") } returns Result.success(expectedStates) // When val result = getAllStatesByProjectIdUseCase.getAllStatesByProjectId("P1") @@ -48,7 +48,7 @@ class GetAllStatesByProjectIdUseCaseTest { fun `should throw exception when no states are found for the project`() { // Given every { projectRepository.getProjectById("P1") } returns mockk() - every { stateRepository.getStatesByProjectId("P3") } returns null + every { stateRepository.getStatesByProjectId("P3") } returns Result.success(emptyList()) // When & Then val exception = assertThrows { getAllStatesByProjectIdUseCase.getAllStatesByProjectId("P3") } diff --git a/src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt index 522f573..f9665e9 100644 --- a/src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt @@ -1,5 +1,6 @@ package com.berlin.logic.usecase.state +import com.berlin.domain.exception.StateNotFoundException import com.berlin.domain.usecase.state.GetStateByIdUseCase import com.berlin.domain.model.State import com.berlin.domain.repository.StateRepository @@ -10,15 +11,18 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource +import org.junit.platform.commons.function.Try.failure +import kotlin.Result.Companion.failure import kotlin.test.Test class GetStateByIdUseCaseTest { private lateinit var getStateByIdUseCase: GetStateByIdUseCase - private val stateRepository: StateRepository = mockk(relaxed = true) + private lateinit var stateRepository: StateRepository @BeforeEach fun setup() { + stateRepository= mockk() getStateByIdUseCase = GetStateByIdUseCase(stateRepository) } @@ -26,26 +30,26 @@ class GetStateByIdUseCaseTest { fun `should return state when valid state id exists`() { // Given val expectedState = State(id = "S1", name = "Active", projectId = "P1") - every { stateRepository.getStateById("S1") } returns expectedState + every { stateRepository.getStateById("S1") } returns Result.success(expectedState) // When val result = getStateByIdUseCase.getStateById("S1") // Then - assertThat(result).isEqualTo(expectedState) + assertThat(result).isEqualTo(Result.success(expectedState)) } @Test fun `should throw exception when state id does not exist`() { // Given val input = "S2" - every { stateRepository.getStateById(any()) } returns null + every { stateRepository.getStateById(any()) } returns Result.failure(StateNotFoundException(input)) // When - val exception = assertThrows { getStateByIdUseCase.getStateById("S2") } + val result = getStateByIdUseCase.getStateById("S2") // Then - assertThat(exception.message).isEqualTo("State with ID $input does not exist") + assertThat(result.isFailure).isTrue() } @ParameterizedTest diff --git a/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt index 5a1acc6..59eb47e 100644 --- a/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt @@ -60,7 +60,7 @@ class GetTasksByStateIdUseCaseTest { @Test fun `should throw exception when state id does not exist`() { // Given - every { stateRepository.getStateById("S2") } returns null + every { stateRepository.getStateById("S2") } returns mockk() // When & Then val exception = assertThrows { getTasksByStateIdUseCase.getAllTasksByStateId("S2") } diff --git a/src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt index 3a1d9a6..442944f 100644 --- a/src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt @@ -29,7 +29,7 @@ class UpdateStateUseCaseTest { every { stateRepository.updateState(state) } returns Result.success("Updated Successfully") // When - val result = updateStateUseCase.updateState(state) + val result = updateStateUseCase.updateState(state.id,state.name,state.projectId) // Then assertThat(result).isEqualTo(Result.success("Updated Successfully")) @@ -42,7 +42,7 @@ class UpdateStateUseCaseTest { every { stateRepository.updateState(state) } returns Result.failure(Exception()) // When - val result = updateStateUseCase.updateState(state) + val result = updateStateUseCase.updateState(state.id,state.name,state.projectId) // Then result.onFailure { exception -> @@ -62,7 +62,7 @@ class UpdateStateUseCaseTest { // When && Then assertThrows { - updateStateUseCase.updateState(input) + updateStateUseCase.updateState(input.id,input.name,input.projectId) } } diff --git a/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt b/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt new file mode 100644 index 0000000..7ef05ac --- /dev/null +++ b/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt @@ -0,0 +1,6 @@ +package presentation.state + +import org.junit.jupiter.api.Assertions.* + class GetStateByIdUiTest{ + + } \ No newline at end of file diff --git a/src/test/kotlin/presentation/state/UpdateStateUiTest.kt b/src/test/kotlin/presentation/state/UpdateStateUiTest.kt new file mode 100644 index 0000000..0320b30 --- /dev/null +++ b/src/test/kotlin/presentation/state/UpdateStateUiTest.kt @@ -0,0 +1,6 @@ +package presentation.state + +import org.junit.jupiter.api.Assertions.* + class UpdateStateUiTest{ + + } \ No newline at end of file From 36dc81eba30f9d208ef297f40fb57da2963c2ce0 Mon Sep 17 00:00:00 2001 From: ahmad Date: Thu, 8 May 2025 10:46:27 +0300 Subject: [PATCH 09/25] add audit log to the project and task entities and fix some issues in the ui --- csv_files/audit.csv | 7 +++ csv_files/project.csv | 2 + csv_files/task.csv | 1 + csv_files/user.csv | 2 + src/main/kotlin/Main.kt | 5 +- src/main/kotlin/data/UserCache.kt | 6 ++- src/main/kotlin/di/appModule.kt | 10 ++++ src/main/kotlin/di/dataModule.kt | 46 ++++--------------- src/main/kotlin/di/uiModule.kt | 17 +++++-- src/main/kotlin/di/useCaseModule.kt | 17 +++---- .../usecase/auditSystem/AddAuditLogUseCase.kt | 8 ++-- .../GetAuditLogsByTaskIdUseCase.kt | 2 +- .../GetAuditLogsByUserIdUseCase.kt | 2 +- .../usecase/authService/CreateMateUseCase.kt | 6 +-- .../usecase/authService/GetUserByIDUseCase.kt | 3 +- .../usecase/project/CreateProjectUseCase.kt | 20 +++++++- .../usecase/project/DeleteProjectUseCase.kt | 21 ++++++++- .../usecase/project/UpdateProjectUseCase.kt | 21 ++++++++- .../domain/usecase/task/AssignTaskUseCase.kt | 23 +++++++++- .../usecase/task/ChangeTaskStateUseCase.kt | 22 ++++++++- .../domain/usecase/task/CreateTaskUseCase.kt | 21 ++++++++- .../domain/usecase/task/DeleteTaskUseCase.kt | 21 ++++++++- .../domain/usecase/task/UpdateTaskUseCase.kt | 24 ++++++++-- src/main/kotlin/model/Project.kt | 9 ---- src/main/kotlin/presentation/MainMenuUI.kt | 2 +- .../presentation/audit/AuditByProjectUI.kt | 8 +++- .../presentation/audit/AuditByTaskUI.kt | 13 ++++-- .../presentation/audit/AuditByUserUI.kt | 3 +- .../presentation/authService/CreateMateUI.kt | 5 +- .../presentation/authService/GetUserByIDUI.kt | 24 ++-------- .../kotlin/presentation/task/CreateTaskUI.kt | 5 +- .../AuthenticationRepositoryInMemoryTest.kt | 5 +- .../AuthenticateUserUseCaseTest.kt | 13 ++++-- 33 files changed, 267 insertions(+), 127 deletions(-) create mode 100644 csv_files/audit.csv create mode 100644 csv_files/project.csv create mode 100644 csv_files/user.csv delete mode 100644 src/main/kotlin/model/Project.kt diff --git a/csv_files/audit.csv b/csv_files/audit.csv new file mode 100644 index 0000000..fa63147 --- /dev/null +++ b/csv_files/audit.csv @@ -0,0 +1,7 @@ +"Audit Id","Timestamp","CreatedBy","Audit Action","Changes Description","Entity Type","Entity Id" +"AUDITDD_52700","1746681952700","user1234","CREATE","","TASK","titlett_52651" +"AUDITDD_16009","1746682016009","user1234","UPDATE","","TASK","titlett_52651" +"AUDITDD_26796","1746682026796","user1234","DELETE","","TASK","titlett_52651" +"AUDITDDDD_29153","1746685829153","user1234","CREATE","","TASK","titlett_29143" +"AUDITDD_39457","1746685839457","user1234","UPDATE","","TASK","titlett_29143""AUDITDD_97145","1746688997145","user1234","CREATE","","PROJECT","taskmangermmmm_97132" +"AUDITDDDD_44756","1746689044756","user1234","UPDATE","","PROJECT","taskmangermmmm_97132" diff --git a/csv_files/project.csv b/csv_files/project.csv new file mode 100644 index 0000000..44c59bf --- /dev/null +++ b/csv_files/project.csv @@ -0,0 +1,2 @@ +"Project Id","Project Name","Description","States","Tasks" +"taskmangermmmm_97132","title updated","the ultimate project","[]","[]" diff --git a/csv_files/task.csv b/csv_files/task.csv index 7082b73..3c93333 100644 --- a/csv_files/task.csv +++ b/csv_files/task.csv @@ -1,2 +1,3 @@ "Task Id","Project Id","Title","Description","State Id","Assigned To User Id","Create By User Id" "dfgsdgfdd_66055","P1","dfgsdgfd","gasdfgfdaf","S1","U1","U1" +"titlett_29143","P1","title","description","S1","U2","user1234" diff --git a/csv_files/user.csv b/csv_files/user.csv new file mode 100644 index 0000000..4debb74 --- /dev/null +++ b/csv_files/user.csv @@ -0,0 +1,2 @@ +"User Id","UserName","Password","User Role" +"ahmadmmmm_70365","ahmad","d2dfac4d62e5470ab0c92d6f425b977d","MATE" diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 8ef330d..be3a929 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -16,4 +16,7 @@ fun main() { val mainMenu: MainMenuUI = getKoin().get() mainMenu.run() -} \ No newline at end of file +} + + +// autit -> project [tasks] \ No newline at end of file diff --git a/src/main/kotlin/data/UserCache.kt b/src/main/kotlin/data/UserCache.kt index 025d4a8..f637491 100644 --- a/src/main/kotlin/data/UserCache.kt +++ b/src/main/kotlin/data/UserCache.kt @@ -2,6 +2,8 @@ package data import com.berlin.domain.model.User -class UserCache { - var currentUser: User? = null +class UserCache( + admin: User +) { + var currentUser: User = admin } \ No newline at end of file diff --git a/src/main/kotlin/di/appModule.kt b/src/main/kotlin/di/appModule.kt index 6b7b82c..1042001 100644 --- a/src/main/kotlin/di/appModule.kt +++ b/src/main/kotlin/di/appModule.kt @@ -3,12 +3,15 @@ package com.berlin.di import com.berlin.domain.hashPassword.HashingString import com.berlin.domain.hashPassword.MD5Hasher +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.presentation.io.ConsoleReader import com.berlin.presentation.io.ConsoleViewer import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import data.UserCache import org.koin.dsl.module @@ -18,4 +21,11 @@ val appModule = module { single { IdGeneratorImplementation() } single { IdGeneratorImplementation() } single { MD5Hasher() } + + single { + UserCache( + User("user1234", "admin", "1212", UserRole.ADMIN) + ) + } + } diff --git a/src/main/kotlin/di/dataModule.kt b/src/main/kotlin/di/dataModule.kt index 6e990dd..74b5f60 100644 --- a/src/main/kotlin/di/dataModule.kt +++ b/src/main/kotlin/di/dataModule.kt @@ -10,11 +10,7 @@ import com.berlin.data.project.ProjectRepositoryImpl import com.berlin.data.schema.* import com.berlin.data.state.StateRepositoryImpl import com.berlin.domain.model.* -import com.berlin.domain.repository.AuditRepository -import com.berlin.domain.repository.AuthenticationRepository -import com.berlin.domain.repository.ProjectRepository -import com.berlin.domain.repository.StateRepository -import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.repository.* import data.UserCache import org.koin.core.qualifier.named import org.koin.dsl.module @@ -24,47 +20,30 @@ val dataModule = module { single>(named("UserSchema")) { UserSchema( - fileName = "user.csv", - header = listOf("User Id", "UserName", "Password", "User Role") + fileName = "user.csv", header = listOf("User Id", "UserName", "Password", "User Role") ) } single>(named("ProjectSchema")) { ProjectSchema( - fileName = "project.csv", - header = listOf("Project Id", "Project Name", "Description", "States", "Tasks") + fileName = "project.csv", header = listOf("Project Id", "Project Name", "Description", "States", "Tasks") ) } single>(named("AuditSchema")) { AuditSchema( - fileName = "audit.csv", - header = listOf( - "Audit Id", - "Timestamp", - "CreatedBy", - "Audit Action", - "Changes Description", - "Entity Type", - "Entity Id" + fileName = "audit.csv", header = listOf( + "Audit Id", "Timestamp", "CreatedBy", "Audit Action", "Changes Description", "Entity Type", "Entity Id" ) ) } single>(named("StateSchema")) { StateSchema( - fileName = "state.csv", - header = listOf("State Id", "Name", "Project Id") + fileName = "state.csv", header = listOf("State Id", "Name", "Project Id") ) } single>(named("TaskSchema")) { TaskSchema( - fileName = "task.csv", - header = listOf( - "Task Id", - "Project Id", - "Title", - "Description", - "State Id", - "Assigned To User Id", - "Create By User Id" + fileName = "task.csv", header = listOf( + "Task Id", "Project Id", "Title", "Description", "State Id", "Assigned To User Id", "Create By User Id" ) ) } @@ -72,8 +51,7 @@ val dataModule = module { single>(named("UserDataSource")) { CsvDataSource("csv_files", get(named("UserSchema"))) } single>(named("ProjectDataSource")) { CsvDataSource( - "csv_files", - get(named("ProjectSchema")) + "csv_files", get(named("ProjectSchema")) ) } single>(named("TaskDataSource")) { CsvDataSource("csv_files", get(named("TaskSchema"))) } @@ -86,9 +64,5 @@ val dataModule = module { single { TaskRepositoryImpl(get(named("TaskDataSource"))) } single { AuditRepositoryImpl(get(named("AuditDataSource"))) } single { StateRepositoryImpl(get(named("StateDataSource")), get(named("TaskDataSource"))) } - single { AuthenticationRepositoryImpl(get(),get(named("UserDataSource"))) } - - - single { UserCache() } - + single { AuthenticationRepositoryImpl(get(), get(named("UserDataSource"))) } } \ No newline at end of file diff --git a/src/main/kotlin/di/uiModule.kt b/src/main/kotlin/di/uiModule.kt index eccfce8..d073369 100644 --- a/src/main/kotlin/di/uiModule.kt +++ b/src/main/kotlin/di/uiModule.kt @@ -1,9 +1,12 @@ -package com.berlin.di + package com.berlin.di import com.berlin.data.DummyData import com.berlin.domain.model.User import com.berlin.domain.usecase.authService.GetUserByIDUseCase import com.berlin.presentation.MainMenuUI +import com.berlin.presentation.audit.AuditByProjectUI +import com.berlin.presentation.audit.AuditByTaskUI +import com.berlin.presentation.audit.AuditByUserUI import com.berlin.presentation.authService.* import com.berlin.presentation.project.* import com.berlin.presentation.task.* @@ -13,9 +16,9 @@ import org.koin.dsl.module val uiModule = module { - single(named("currentUser")) { DummyData.users.first() } +// single(named("currentUser")) { DummyData.users.first() } - single { CreateTaskUI(get(), get(named("currentUser")), get(), get()) } + single { CreateTaskUI(get(), get(), get(), get()) } single { AssignTaskUI(get(), get(), get(), get()) } single { DeleteTaskUI(get(), get(), get(),get()) } single { GetTasksByProjectIdUI(get(), get(), get()) } @@ -33,9 +36,11 @@ val uiModule = module { single { GetAllProjectsUi(get(),get()) } single { GetProjectByIdUi(get(),get(),get()) } single { UpdateProjectUi(get(),get(),get(),get(),get()) } + single { AuditByProjectUI(get(), get(), get(), get()) } + single { AuditByTaskUI(get(), get(), get(), get(), get()) } + single { AuditByUserUI(get(), get(), get()) } - /* aggregated main menu */ single { MainMenuUI( runners = listOf( @@ -57,12 +62,14 @@ val uiModule = module { get(), get(), get(), + + get(), + get() ), viewer = get(), reader = get(), authUi = get(), userCache=get() - ) } } diff --git a/src/main/kotlin/di/useCaseModule.kt b/src/main/kotlin/di/useCaseModule.kt index 6892be9..3345385 100644 --- a/src/main/kotlin/di/useCaseModule.kt +++ b/src/main/kotlin/di/useCaseModule.kt @@ -10,24 +10,25 @@ import com.berlin.domain.usecase.authService.GetUserByIDUseCase import com.berlin.domain.usecase.authService.GetUserLoggedInUseCase import com.berlin.domain.usecase.project.* import com.berlin.domain.usecase.task.* +import data.UserCache import domain.usecase.authService.AuthenticateUserUseCase import org.koin.dsl.module val useCaseModule = module { - single { CreateTaskUseCase(get(), get()) } - single { AssignTaskUseCase(get()) } - single { DeleteTaskUseCase(get()) } + single { CreateTaskUseCase(get(), get(), get()) } + single { AssignTaskUseCase(get(), get(), get()) } + single { DeleteTaskUseCase(get(), get(), get()) } single { GetTasksByProjectUseCase(get()) } - single { UpdateTaskUseCase(get()) } - single { ChangeTaskStateUseCase(get()) } + single { UpdateTaskUseCase(get(), get(), get()) } + single { ChangeTaskStateUseCase(get(), get(), get()) } single { GetTaskByIdUseCase(get()) } single { GetAllTasksUseCase(get()) } - single { CreateProjectUseCase(get(), get()) } + single { CreateProjectUseCase(get(), get(), get(), get()) } single { GetAllProjectsUseCase(get()) } - single { DeleteProjectUseCase(get()) } + single { DeleteProjectUseCase(get(), get(), get()) } single { GetProjectByIdUseCase(get()) } - single { UpdateProjectUseCase(get()) } + single { UpdateProjectUseCase(get(), get(), get() ) } single { AddAuditLogUseCase(get(), get()) } single { GetAuditLogsByProjectIdUseCase(get()) } diff --git a/src/main/kotlin/domain/usecase/auditSystem/AddAuditLogUseCase.kt b/src/main/kotlin/domain/usecase/auditSystem/AddAuditLogUseCase.kt index 004126e..43094ea 100644 --- a/src/main/kotlin/domain/usecase/auditSystem/AddAuditLogUseCase.kt +++ b/src/main/kotlin/domain/usecase/auditSystem/AddAuditLogUseCase.kt @@ -14,16 +14,14 @@ class AddAuditLogUseCase( fun addAuditLog( createdByUserId:String, auditAction: AuditAction, - changesDescription: String, + changesDescription: String? = null, entityType: EntityType, entityId: String, - timestamp: Long = System.currentTimeMillis() ): Result { return try { - val id = idGenerator.generateId("AUDIT") val auditLog = AuditLog( - id = id, - timestamp = timestamp, + id = idGenerator.generateId("AUDIT"), + timestamp = System.currentTimeMillis(), createdByUserId=createdByUserId, auditAction = auditAction, changesDescription = changesDescription, diff --git a/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCase.kt b/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCase.kt index d819a41..00cfe5e 100644 --- a/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCase.kt +++ b/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCase.kt @@ -12,7 +12,7 @@ class GetAuditLogsByTaskIdUseCase( if (!validateTaskId(taskId)) throw IllegalArgumentException("Task ID must not be empty, blank, or purely numeric") - return auditRepository.getAuditLogsByProjectId(taskId) + return auditRepository.getAuditLogsByTaskId(taskId) } diff --git a/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByUserIdUseCase.kt b/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByUserIdUseCase.kt index 0f7af63..478d4ce 100644 --- a/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByUserIdUseCase.kt +++ b/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByUserIdUseCase.kt @@ -18,4 +18,4 @@ class GetAuditLogsByUserIdUseCase( private fun validateUserId(projectId: String): Boolean = projectId.isNotBlank() && !(projectId.all { it.isDigit() }) -} +} \ No newline at end of file diff --git a/src/main/kotlin/domain/usecase/authService/CreateMateUseCase.kt b/src/main/kotlin/domain/usecase/authService/CreateMateUseCase.kt index 533bb42..062f2f9 100644 --- a/src/main/kotlin/domain/usecase/authService/CreateMateUseCase.kt +++ b/src/main/kotlin/domain/usecase/authService/CreateMateUseCase.kt @@ -15,13 +15,13 @@ class CreateMateUseCase( if (userName.isEmpty() || password.isEmpty()) { return Result.failure(InvalidCredentialsException("Username and password must not be empty")) } - if (password.length < MAIN_PASSWORD_LENGHT) { + if (password.length < MAIN_PASSWORD_LENGTH) { return Result.failure(InvalidCredentialsException("Password less than 8 characters")) } val hashedPassword=hashingString.hashPassword(password) val newUser = User( - id = idGenerator.generateId(password), + id = idGenerator.generateId(userName), userName = userName, password = hashedPassword, role = UserRole.MATE @@ -29,6 +29,6 @@ class CreateMateUseCase( return repository.createMate(newUser) } private companion object{ - const val MAIN_PASSWORD_LENGHT = 8 + const val MAIN_PASSWORD_LENGTH = 8 } } diff --git a/src/main/kotlin/domain/usecase/authService/GetUserByIDUseCase.kt b/src/main/kotlin/domain/usecase/authService/GetUserByIDUseCase.kt index b42fcb3..c24e877 100644 --- a/src/main/kotlin/domain/usecase/authService/GetUserByIDUseCase.kt +++ b/src/main/kotlin/domain/usecase/authService/GetUserByIDUseCase.kt @@ -9,8 +9,7 @@ class GetUserByIDUseCase( ) { fun getUserById(id: String): Result { if (!isIDValid(id)) - throw InvalidUserIdException("User ID can't be empty or just digits") - + return Result.failure(InvalidUserIdException("User ID can't be empty or just digits")) return repository.getUserById(id) } diff --git a/src/main/kotlin/domain/usecase/project/CreateProjectUseCase.kt b/src/main/kotlin/domain/usecase/project/CreateProjectUseCase.kt index e5f0407..6f46590 100644 --- a/src/main/kotlin/domain/usecase/project/CreateProjectUseCase.kt +++ b/src/main/kotlin/domain/usecase/project/CreateProjectUseCase.kt @@ -1,12 +1,18 @@ package com.berlin.domain.usecase.project +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.domain.repository.ProjectRepository import com.berlin.domain.model.Project +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class CreateProjectUseCase( private val projectRepository: ProjectRepository, private val idGenerator: IdGenerator, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { fun createNewProject(projectName: String, description: String?, stateId: List?, taskId: List?): Result { @@ -18,7 +24,19 @@ class CreateProjectUseCase( statesId = stateId, tasksId = taskId ) - return projectRepository.createProject(newProject) + + val createdProject = projectRepository.createProject(newProject) + + if (createdProject.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.CREATE, + entityType = EntityType.PROJECT, + entityId = newProject.id, + ) + } + + return createdProject .map { "Creation Successfully" } .recover { "Creation Failed" } } else { diff --git a/src/main/kotlin/domain/usecase/project/DeleteProjectUseCase.kt b/src/main/kotlin/domain/usecase/project/DeleteProjectUseCase.kt index bd0d9fa..768b478 100644 --- a/src/main/kotlin/domain/usecase/project/DeleteProjectUseCase.kt +++ b/src/main/kotlin/domain/usecase/project/DeleteProjectUseCase.kt @@ -1,9 +1,15 @@ package com.berlin.domain.usecase.project +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.repository.ProjectRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class DeleteProjectUseCase ( - private val projectRepository: ProjectRepository + private val projectRepository: ProjectRepository, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { fun deleteProject(projectId: String): Result { @@ -16,7 +22,18 @@ class DeleteProjectUseCase ( ) } - return projectRepository.deleteProject(projectId) + val deletedProject = projectRepository.deleteProject(projectId) + + if (deletedProject.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.DELETE, + entityType = EntityType.PROJECT, + entityId = projectId, + ) + } + + return deletedProject .map { "Deleted Successfully" } .recover { "Deletion Failed" } } diff --git a/src/main/kotlin/domain/usecase/project/UpdateProjectUseCase.kt b/src/main/kotlin/domain/usecase/project/UpdateProjectUseCase.kt index 3c3889b..8d3be27 100644 --- a/src/main/kotlin/domain/usecase/project/UpdateProjectUseCase.kt +++ b/src/main/kotlin/domain/usecase/project/UpdateProjectUseCase.kt @@ -1,16 +1,33 @@ package com.berlin.domain.usecase.project +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.repository.ProjectRepository import com.berlin.domain.model.Project +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class UpdateProjectUseCase ( - private val projectRepository: ProjectRepository + private val projectRepository: ProjectRepository, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { fun updateProject(project: Project): Result { if(!validateProjectName(project.name)) throw Exception("Project Name must not be empty or blank") - return projectRepository.updateProject(project) + val updatedProject = projectRepository.updateProject(project) + + if (updatedProject.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.UPDATE, + entityType = EntityType.PROJECT, + entityId = project.id, + ) + } + + return updatedProject .map { "Updated Successfully" } .recover { "Update Failed" } } diff --git a/src/main/kotlin/domain/usecase/task/AssignTaskUseCase.kt b/src/main/kotlin/domain/usecase/task/AssignTaskUseCase.kt index 180dd0b..f921635 100644 --- a/src/main/kotlin/domain/usecase/task/AssignTaskUseCase.kt +++ b/src/main/kotlin/domain/usecase/task/AssignTaskUseCase.kt @@ -1,11 +1,18 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidAssigneeException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task +import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class AssignTaskUseCase( - private val taskRepository: TaskRepository + private val taskRepository: TaskRepository, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { operator fun invoke(taskId: String, newAssigneeId: String): Result { @@ -21,7 +28,19 @@ class AssignTaskUseCase( } val updated = original.copy(assignedToUserId = newAssigneeId) - return taskRepository.update(updated) + + val updatedTask = taskRepository.update(updated) + + if (updatedTask.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.UPDATE, + entityType = EntityType.TASK, + entityId = updated.id, + ) + } + + return updatedTask } private fun validateAssignee(id: String): Boolean = diff --git a/src/main/kotlin/domain/usecase/task/ChangeTaskStateUseCase.kt b/src/main/kotlin/domain/usecase/task/ChangeTaskStateUseCase.kt index 5b073e9..60cbf6a 100644 --- a/src/main/kotlin/domain/usecase/task/ChangeTaskStateUseCase.kt +++ b/src/main/kotlin/domain/usecase/task/ChangeTaskStateUseCase.kt @@ -1,11 +1,18 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskStateException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task +import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class ChangeTaskStateUseCase( - private val taskRepository: TaskRepository + private val taskRepository: TaskRepository, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { operator fun invoke(taskId: String, newStateId: String): Result { @@ -19,7 +26,18 @@ class ChangeTaskStateUseCase( } val updated = original.copy(stateId = newStateId) - return taskRepository.update(updated) + val updatedTask = taskRepository.update(updated) + + if (updatedTask.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.UPDATE, + entityType = EntityType.TASK, + entityId = updated.id, + ) + } + + return updatedTask } private fun validateStateId(stateId: String): Boolean = diff --git a/src/main/kotlin/domain/usecase/task/CreateTaskUseCase.kt b/src/main/kotlin/domain/usecase/task/CreateTaskUseCase.kt index ee670be..aca6e57 100644 --- a/src/main/kotlin/domain/usecase/task/CreateTaskUseCase.kt +++ b/src/main/kotlin/domain/usecase/task/CreateTaskUseCase.kt @@ -2,13 +2,18 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskTitle import com.berlin.domain.exception.TaskAlreadyExistsException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.AuditLog +import com.berlin.domain.model.EntityType import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.berlin.domain.model.Task import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase class CreateTaskUseCase( private val taskRepository: TaskRepository, - private val defaultIdGenerator: IdGeneratorImplementation + private val defaultIdGenerator: IdGeneratorImplementation, + private val addAuditLogUseCase: AddAuditLogUseCase ) { operator fun invoke( projectId: String, @@ -31,7 +36,19 @@ class CreateTaskUseCase( if (!validateUniqueTask(newTask.id)) { throw TaskAlreadyExistsException("Task with id= ${newTask.id} and title = ${newTask.title} already exists") } - return taskRepository.create(newTask) + + val createdTask = taskRepository.create(newTask) + + if (createdTask.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = createByUserId, + auditAction = AuditAction.CREATE, + entityType = EntityType.TASK, + entityId = newTask.id, + ) + } + + return createdTask } else { throw InvalidTaskTitle("task title must be not empty or plank") } diff --git a/src/main/kotlin/domain/usecase/task/DeleteTaskUseCase.kt b/src/main/kotlin/domain/usecase/task/DeleteTaskUseCase.kt index bd99392..d38462e 100644 --- a/src/main/kotlin/domain/usecase/task/DeleteTaskUseCase.kt +++ b/src/main/kotlin/domain/usecase/task/DeleteTaskUseCase.kt @@ -1,10 +1,17 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.TaskNotFoundException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType +import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class DeleteTaskUseCase( private val taskRepository: TaskRepository, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { operator fun invoke(taskId: String): Result { if (!validateTaskId(taskId)) throw Exception("Project ID must not be empty or blank") @@ -14,7 +21,19 @@ class DeleteTaskUseCase( TaskNotFoundException("task with ID $taskId does not exist") ) } - return taskRepository.delete(taskId) + + val deleteTask = taskRepository.delete(taskId) + + if (deleteTask.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.DELETE, + entityType = EntityType.TASK, + entityId = taskId, + ) + } + + return deleteTask } private fun validateTaskId(taskId: String): Boolean = diff --git a/src/main/kotlin/domain/usecase/task/UpdateTaskUseCase.kt b/src/main/kotlin/domain/usecase/task/UpdateTaskUseCase.kt index a5e7d75..4b278ed 100644 --- a/src/main/kotlin/domain/usecase/task/UpdateTaskUseCase.kt +++ b/src/main/kotlin/domain/usecase/task/UpdateTaskUseCase.kt @@ -1,11 +1,18 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskTitle +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task +import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class UpdateTaskUseCase( private val taskRepository: TaskRepository, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { operator fun invoke( @@ -24,11 +31,22 @@ class UpdateTaskUseCase( description = description ?: original.description, assignedToUserId = assignedToUserId ?: original.assignedToUserId ) - if (!validateTaskTitle(updated.title.trim())) { + + if (!validateTaskTitle(updated.title.trim())) throw InvalidTaskTitle("task title must be not empty or plank") - } else { - return taskRepository.update(updated) + + val updatedTask = taskRepository.create(updated) + + if (updatedTask.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.UPDATE, + entityType = EntityType.TASK, + entityId = updated.id, + ) } + + return updatedTask } private fun validateTaskTitle(title: String): Boolean { diff --git a/src/main/kotlin/model/Project.kt b/src/main/kotlin/model/Project.kt deleted file mode 100644 index 8b77e24..0000000 --- a/src/main/kotlin/model/Project.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.berlin.model - -data class Project( - val id:String, - val name:String, - val description:String?, - val statesId:List, - val tasksId:List -) diff --git a/src/main/kotlin/presentation/MainMenuUI.kt b/src/main/kotlin/presentation/MainMenuUI.kt index e0988ab..584f9ad 100644 --- a/src/main/kotlin/presentation/MainMenuUI.kt +++ b/src/main/kotlin/presentation/MainMenuUI.kt @@ -57,7 +57,7 @@ class MainMenuUI( private companion object { - val adminPermissionFilterIds = listOf(1, 2, 3, 4, 5, 6, 7, 30, 100, 300, 500, 900) + val adminPermissionFilterIds = listOf(1, 2, 3, 4, 5, 6, 7, 30, 100, 300, 500, 900, 11, 12, 13, 14, 15, 24390823, 2349, 24234) val matePermissionFilterIds = listOf(1, 2, 3, 4, 5, 6, 7) } } \ No newline at end of file diff --git a/src/main/kotlin/presentation/audit/AuditByProjectUI.kt b/src/main/kotlin/presentation/audit/AuditByProjectUI.kt index 6f355d3..7b18aac 100644 --- a/src/main/kotlin/presentation/audit/AuditByProjectUI.kt +++ b/src/main/kotlin/presentation/audit/AuditByProjectUI.kt @@ -6,6 +6,8 @@ import com.berlin.domain.exception.InvalidSelectionException import com.berlin.domain.model.AuditLog import com.berlin.domain.model.Project import com.berlin.domain.usecase.auditSystem.GetAuditLogsByProjectIdUseCase +import com.berlin.domain.usecase.project.GetAllProjectsUseCase +import com.berlin.domain.usecase.task.GetTasksByProjectUseCase import com.berlin.presentation.UiRunner import com.berlin.presentation.helper.choose import com.berlin.presentation.io.Reader @@ -13,11 +15,12 @@ import com.berlin.presentation.io.Viewer class AuditByProjectUI( private val getAuditLogsByProjectIdUseCase: GetAuditLogsByProjectIdUseCase, + private val getAllProjectsUseCase: GetAllProjectsUseCase, private val viewer: Viewer, private val reader: Reader ) : UiRunner { - override val id: Int = 1 + override val id: Int = 24390823 override val label: String = "Show audit by project" override fun run() { @@ -49,13 +52,14 @@ class AuditByProjectUI( Changes: ${log.changesDescription ?: "null"} """.trimIndent() ) + viewer.show("") } } private fun selectProject(): Project { return choose( title = "Choose a project", - elements = DummyData.projects, + elements = getAllProjectsUseCase.getAllProjects(), labelOf = { project -> project.name }, viewer = viewer, reader = reader diff --git a/src/main/kotlin/presentation/audit/AuditByTaskUI.kt b/src/main/kotlin/presentation/audit/AuditByTaskUI.kt index 5b9816a..132205d 100644 --- a/src/main/kotlin/presentation/audit/AuditByTaskUI.kt +++ b/src/main/kotlin/presentation/audit/AuditByTaskUI.kt @@ -7,6 +7,8 @@ import com.berlin.domain.model.AuditLog import com.berlin.domain.model.Project import com.berlin.domain.model.Task import com.berlin.domain.usecase.auditSystem.GetAuditLogsByTaskIdUseCase +import com.berlin.domain.usecase.project.GetAllProjectsUseCase +import com.berlin.domain.usecase.task.GetTasksByProjectUseCase import com.berlin.presentation.UiRunner import com.berlin.presentation.helper.choose import com.berlin.presentation.io.Reader @@ -15,11 +17,13 @@ import com.berlin.presentation.io.Viewer class AuditByTaskUI( private val viewer: Viewer, private val reader: Reader, - private val getAuditLogsByTaskIdUseCase: GetAuditLogsByTaskIdUseCase + private val getAuditLogsByTaskIdUseCase: GetAuditLogsByTaskIdUseCase, + private val getTasksByProjectUseCase: GetTasksByProjectUseCase, + private val getAllProjectsUseCase: GetAllProjectsUseCase ) : UiRunner { - override val id = 2 + override val id = 2349 override val label = "View Audit Logs by Task" override fun run() { @@ -57,13 +61,14 @@ class AuditByTaskUI( Changes: ${log.changesDescription ?: "null"} """.trimIndent() ) + viewer.show("") } } private fun selectProject () : Project{ return choose( title = "Choose a project", - elements = DummyData.projects, + elements = getAllProjectsUseCase.getAllProjects(), labelOf = { project -> project.name }, viewer = viewer, reader = reader @@ -73,7 +78,7 @@ class AuditByTaskUI( private fun selectTask (selectedProject : Project) : Task { return choose( title = "Choose a task", - elements = DummyData.initialDemoTasks.filter { it.projectId == selectedProject.id }, + elements = getTasksByProjectUseCase( selectedProject.id).getOrNull() ?: emptyList() , labelOf = { task -> task.title }, viewer = viewer, reader = reader diff --git a/src/main/kotlin/presentation/audit/AuditByUserUI.kt b/src/main/kotlin/presentation/audit/AuditByUserUI.kt index c6f0d2e..595540e 100644 --- a/src/main/kotlin/presentation/audit/AuditByUserUI.kt +++ b/src/main/kotlin/presentation/audit/AuditByUserUI.kt @@ -17,7 +17,7 @@ class AuditByUserUI( private val reader: Reader ) : UiRunner { - override val id: Int = 3 + override val id: Int = 24234 override val label: String = "Show audit by user" override fun run() { @@ -61,6 +61,7 @@ class AuditByUserUI( Changes: ${log.changesDescription ?: "null"} """.trimIndent() ) + viewer.show("") } } } diff --git a/src/main/kotlin/presentation/authService/CreateMateUI.kt b/src/main/kotlin/presentation/authService/CreateMateUI.kt index 266c9a0..6d803ee 100644 --- a/src/main/kotlin/presentation/authService/CreateMateUI.kt +++ b/src/main/kotlin/presentation/authService/CreateMateUI.kt @@ -17,7 +17,7 @@ class CreationOfMateUi( handleMateCreation() } private fun createMate(): Result{ - viewer.show("Enter user name: ") + viewer.show("Enter user name or x to exit: ") val userName = reader.read()?.trim().orEmpty() viewer.show("Enter user password: ") val userPassword = reader.read()?.trim().orEmpty() @@ -27,12 +27,11 @@ class CreationOfMateUi( createMate().onSuccess { viewer.show("New mate is successfully created!") }.onFailure { - viewer.show("something wrong please try again!") + viewer.show(it.message ?: "some thing went wrong please try again!") if (attempt < maxAttempts) { handleMateCreation(attempt + 1, maxAttempts) } } } - } \ No newline at end of file diff --git a/src/main/kotlin/presentation/authService/GetUserByIDUI.kt b/src/main/kotlin/presentation/authService/GetUserByIDUI.kt index f2ac9d6..37a3af7 100644 --- a/src/main/kotlin/presentation/authService/GetUserByIDUI.kt +++ b/src/main/kotlin/presentation/authService/GetUserByIDUI.kt @@ -1,7 +1,5 @@ package com.berlin.presentation.authService -import com.berlin.domain.exception.InvalidUserIdException -import com.berlin.domain.exception.UserNotFoundException import com.berlin.domain.model.User import com.berlin.domain.usecase.authService.GetUserByIDUseCase import com.berlin.presentation.UiRunner @@ -19,24 +17,10 @@ class GetUserByIDUI( override fun run() { viewer.show("Enter the user id: ") val id = reader.read()?.trim().orEmpty() - try { - //val id = reader.read()?.trim().orEmpty() - val user = getUserByIDUseCase.getUserById(id) - user.fold( - onSuccess = { showUserInfo(it) }, - - onFailure = { ex -> - when (ex) { - is UserNotFoundException -> - viewer.show("No user found for this ID") - - else -> viewer.show("error: $ex") - } - } - ) - } catch (e: InvalidUserIdException) { - viewer.show("Invalid ID") - } + getUserByIDUseCase.getUserById(id).fold( + onSuccess = { showUserInfo(it) }, + onFailure = { viewer.show(it.message ?: "invalid user id") } + ) } private fun showUserInfo(user: User) { diff --git a/src/main/kotlin/presentation/task/CreateTaskUI.kt b/src/main/kotlin/presentation/task/CreateTaskUI.kt index 73a5d15..a6bd26e 100644 --- a/src/main/kotlin/presentation/task/CreateTaskUI.kt +++ b/src/main/kotlin/presentation/task/CreateTaskUI.kt @@ -12,10 +12,11 @@ import com.berlin.presentation.UiRunner import com.berlin.presentation.helper.choose import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import data.UserCache class CreateTaskUI( private val createTask: CreateTaskUseCase, - private val currentUser: User, + private val cashedUser: UserCache, private val viewer: Viewer, private val reader: Reader, ) : UiRunner { @@ -31,7 +32,7 @@ class CreateTaskUI( val (title, desc) = askTitleAndDescription() createTask( - project.id, title, desc, state.id, currentUser.id, assignee.id + project.id, title, desc, state.id, cashedUser.currentUser.id, assignee.id ).onSuccess { viewer.show("Task created: id=${it.id}") } .onFailure { viewer.show(it.message ?: "Creation failed") } diff --git a/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt b/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt index 53e68f6..0265665 100644 --- a/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt +++ b/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt @@ -5,6 +5,8 @@ import com.berlin.data.authentication.AuthenticationRepositoryImpl import com.berlin.domain.hashPassword.HashingString import com.berlin.domain.hashPassword.MD5Hasher import com.berlin.domain.helper.AuthServiceTestData +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole import com.google.common.truth.Truth.assertThat import data.UserCache import org.junit.jupiter.api.BeforeEach @@ -13,12 +15,13 @@ import org.junit.jupiter.api.Test class AuthenticationRepositoryInMemoryTest { private lateinit var inMemoryAuthRepositoryImpl: AuthenticationRepositoryImpl private lateinit var hashingString: HashingString + private var cashedUser = User("user1234", "admin", "1212", UserRole.ADMIN) @BeforeEach fun setup() { hashingString = MD5Hasher() AuthDummyData.users.clear() - inMemoryAuthRepositoryImpl =AuthenticationRepositoryImpl( UserCache(),AuthDummyData) + inMemoryAuthRepositoryImpl =AuthenticationRepositoryImpl( UserCache(cashedUser),AuthDummyData) } diff --git a/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt b/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt index 037ce4e..2e24e37 100644 --- a/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt @@ -4,6 +4,8 @@ import com.berlin.domain.hashPassword.HashingString import com.berlin.domain.repository.AuthenticationRepository import com.berlin.domain.fakeData.FakeHashingString import com.berlin.domain.helper.AuthServiceTestData +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole import com.google.common.truth.Truth.assertThat import data.UserCache import domain.usecase.authService.AuthenticateUserUseCase @@ -17,13 +19,14 @@ class AuthenticateUserUseCaseTest { private lateinit var authRepository: AuthenticationRepository private lateinit var hashingString: HashingString private lateinit var authenticateUserUseCase: AuthenticateUserUseCase + private var cashedUser = User("user1234", "admin", "1212", UserRole.ADMIN) private lateinit var userCache: UserCache @BeforeEach fun setup() { authRepository = mockk() hashingString = FakeHashingString() - userCache= UserCache() + userCache= UserCache(cashedUser) authenticateUserUseCase = AuthenticateUserUseCase(userCache,authRepository, hashingString) } @@ -124,7 +127,7 @@ class AuthenticateUserUseCaseTest { every { mockedRepo.getAllUsers() } returns Result.success(listOf(AuthServiceTestData.user)) every { mockedRepo.login(userName, hashedPassword) } returns Result.failure(InvalidCredentialsException("Invalid")) - userCache.currentUser = null + userCache.currentUser = cashedUser // When val result = authenticateUserUseCase.login(userName, rawPassword) @@ -158,7 +161,7 @@ class AuthenticateUserUseCaseTest { val hashedPassword = hashingString.hashPassword(AuthServiceTestData.userPassword) every { authRepository.getAllUsers() } returns Result.success(listOf(user)) every { authRepository.login(user.userName, hashedPassword) } returns Result.failure(RuntimeException("Unexpected error")) - userCache.currentUser = null + userCache.currentUser = cashedUser // When val result = authenticateUserUseCase.login(user.userName, AuthServiceTestData.userPassword) @@ -178,7 +181,7 @@ class AuthenticateUserUseCaseTest { every { authRepository.login(user.userName, hashedPassword) } returns Result.failure( InvalidCredentialsException("Wrong credentials") ) - userCache.currentUser = null + userCache.currentUser = cashedUser // When val result = authenticateUserUseCase.login(user.userName, AuthServiceTestData.userPassword) @@ -217,7 +220,7 @@ class AuthenticateUserUseCaseTest { val wrongPassword = "wrongPassword" val hashedWrongPassword = hashingString.hashPassword(wrongPassword) - userCache.currentUser = null + userCache.currentUser = cashedUser every { authRepository.getAllUsers() } returns Result.success(listOf(user)) every { authRepository.login(user.userName, hashedWrongPassword) } returns Result.failure( InvalidCredentialsException("Wrong password") From 7cfc53acf115443ac68415c32226bcbb46b5ca7e Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Thu, 8 May 2025 13:27:27 +0300 Subject: [PATCH 10/25] add test cases to update state --- src/main/kotlin/data/DummyData.kt | 1 + .../kotlin/data/state/StateRepositoryImpl.kt | 1 - .../state/GetAllStatesByProjectIdUseCase.kt | 2 - .../state/GetAllstatesByProjectIdUi.kt | 4 +- .../data/state/StateRepositoryImplTest.kt | 1 - .../usecase/state/DeleteStateUseCaseTest.kt | 1 - .../presentation/state/UpdateStateUiTest.kt | 113 +++++++++++++++++- 7 files changed, 113 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/data/DummyData.kt b/src/main/kotlin/data/DummyData.kt index 3535e7f..8b27e55 100644 --- a/src/main/kotlin/data/DummyData.kt +++ b/src/main/kotlin/data/DummyData.kt @@ -19,6 +19,7 @@ object DummyData : BaseDataSource { ) val states = mutableListOf( + State("Q1","Menna","P5"), State("S1", "TODO", "P1"), State("S2", "IN_PROGRESS", "P1"), State("S3", "REVIEW", "P1"), diff --git a/src/main/kotlin/data/state/StateRepositoryImpl.kt b/src/main/kotlin/data/state/StateRepositoryImpl.kt index 37e02b5..ca47270 100644 --- a/src/main/kotlin/data/state/StateRepositoryImpl.kt +++ b/src/main/kotlin/data/state/StateRepositoryImpl.kt @@ -3,7 +3,6 @@ package com.berlin.data.state import com.berlin.data.BaseDataSource import com.berlin.domain.exception.InvalidStateException import com.berlin.domain.exception.StateNotFoundException -import com.berlin.domain.exception.TaskNotFoundException import com.berlin.domain.model.State import com.berlin.domain.model.Task import com.berlin.domain.repository.StateRepository diff --git a/src/main/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCase.kt b/src/main/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCase.kt index 08bacf8..d56af18 100644 --- a/src/main/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCase.kt +++ b/src/main/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCase.kt @@ -2,8 +2,6 @@ package com.berlin.domain.usecase.state import com.berlin.domain.exception.InvalidProjectIdException -import com.berlin.domain.exception.ProjectNotFoundException -import com.berlin.domain.exception.StateNotFoundException import com.berlin.domain.model.State import com.berlin.domain.repository.ProjectRepository import com.berlin.domain.repository.StateRepository diff --git a/src/main/kotlin/presentation/state/GetAllstatesByProjectIdUi.kt b/src/main/kotlin/presentation/state/GetAllstatesByProjectIdUi.kt index 10225eb..952ea86 100644 --- a/src/main/kotlin/presentation/state/GetAllstatesByProjectIdUi.kt +++ b/src/main/kotlin/presentation/state/GetAllstatesByProjectIdUi.kt @@ -16,7 +16,7 @@ class GetAllStatesByProjectIdUi( private val getAllStatesByProjectIdUseCase: GetAllStatesByProjectIdUseCase, // private val getAllStates: GetAllStatesUseCase, private val viewer: Viewer, - private val reader: Reader + private val reader: Reader, ) : UiRunner { override val id: Int = 3000 @@ -33,7 +33,7 @@ class GetAllStatesByProjectIdUi( ) getAllStatesByProjectIdUseCase.getAllStatesByProjectId(project.id) - .onSuccess { state -> showSwimLaneFor(project.id, state) } + .onSuccess { state -> showSwimLaneFor(project.id, state) } .onFailure { viewer.show(it.message ?: "Failed to load states") } diff --git a/src/test/kotlin/data/state/StateRepositoryImplTest.kt b/src/test/kotlin/data/state/StateRepositoryImplTest.kt index 6bb8564..8d0913d 100644 --- a/src/test/kotlin/data/state/StateRepositoryImplTest.kt +++ b/src/test/kotlin/data/state/StateRepositoryImplTest.kt @@ -1,7 +1,6 @@ package com.berlin.data.state import com.berlin.data.BaseDataSource -import com.berlin.data.csv_data_source.CsvDataSource import com.berlin.domain.exception.InvalidStateException import com.berlin.domain.model.State import com.berlin.domain.model.Task diff --git a/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt index 82b865c..e966180 100644 --- a/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt @@ -7,7 +7,6 @@ import com.google.common.truth.Truth.assertThat import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource diff --git a/src/test/kotlin/presentation/state/UpdateStateUiTest.kt b/src/test/kotlin/presentation/state/UpdateStateUiTest.kt index 0320b30..3d70abb 100644 --- a/src/test/kotlin/presentation/state/UpdateStateUiTest.kt +++ b/src/test/kotlin/presentation/state/UpdateStateUiTest.kt @@ -1,6 +1,113 @@ package presentation.state -import org.junit.jupiter.api.Assertions.* - class UpdateStateUiTest{ +import com.berlin.data.DummyData +import com.berlin.domain.exception.InvalidSelectionException +import com.berlin.domain.exception.InvalidStateException +import com.berlin.domain.exception.InvalidStateNameException +import com.berlin.domain.usecase.state.GetAllStatesUseCase +import com.berlin.domain.usecase.state.UpdateStateUseCase +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer +import com.berlin.presentation.state.UpdateStateUi +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.BeforeEach +import kotlin.test.Test - } \ No newline at end of file +class UpdateStateUiTest { + private lateinit var updateState: UpdateStateUseCase + private lateinit var getAllStates: GetAllStatesUseCase + private lateinit var updateStateUi: UpdateStateUi + private var viewer: Viewer = mockk(relaxed = true) + private var reader: Reader = mockk() + + @BeforeEach + fun setup() { + updateState = mockk() + getAllStates = mockk() + updateStateUi = UpdateStateUi(updateState, getAllStates, viewer, reader) + } + + /* + 1-successfully updated + 2-update failed + 3- InvalidStateNameException-->"State Name must not be empty or blank" + 4-InputCancelledException-->"Cancelled." + 5-InvalidSelectionException-->Invalid selection" + */ + private companion object { + val stateId = DummyData.states[1].id + val successfullyStateNewName = "done" + val stateProjectId = DummyData.states[1].projectId + val emptyStateName = "" + } + + @Test + fun `run should return successfully updated when every thing is correct`() { + //Given + every { getAllStates() } returns DummyData.states + every { reader.read() } returnsMany listOf("2", "done") + every { + updateState.updateState(any(), any(), any()) + } returns Result.success("Updated Successfully") + + //When + updateStateUi.run() + + //Then + verify { getAllStates() } + verify { viewer.show(any()) } + verify { + updateState.updateState( + stateId, + successfullyStateNewName, + stateProjectId + ) + } + verify { viewer.show("Updated Successfully") } + } + + @Test + fun `run should return update failed when update fails`() { + //Given + every { getAllStates() } returns DummyData.states + every { reader.read() } returnsMany listOf("2", "done") + every { + updateState.updateState(any(), any(), any()) + } returns Result.failure(InvalidStateException("can not update state")) + + //When + updateStateUi.run() + + //Then + verify { viewer.show("Update Failed") } + } + + @Test + fun `run should throw InvalidStateNameException when State Name is empty or blank`() { + //Given + every { getAllStates() } returns DummyData.states + every { reader.read() } returnsMany listOf("2", " ") + every { + updateState.updateState(any(), emptyStateName, any()) + } throws InvalidStateNameException("State Name must not be empty or blank") + + //When + updateStateUi.run() + + //Then + verify { viewer.show("State Name must not be empty or blank") } + } + + @Test + fun `run should `() { + //Given + every { reader.read() } returnsMany listOf("X") + //When + updateStateUi.run() + + //Then + verify { viewer.show() } + } +} \ No newline at end of file From 995c3af3dddcb25ae608d09e4e2c2615802a3c36 Mon Sep 17 00:00:00 2001 From: Abdulrahman Ragab Date: Thu, 8 May 2025 19:27:35 +0300 Subject: [PATCH 11/25] refactor the test cases of CreateStateUiTest.kt --- .../presentation/state/CreatestateUi.kt | 2 +- .../presentation/state/CreateStateUiTest.kt | 241 ++++++++---------- 2 files changed, 105 insertions(+), 138 deletions(-) diff --git a/src/main/kotlin/presentation/state/CreatestateUi.kt b/src/main/kotlin/presentation/state/CreatestateUi.kt index befc446..f583766 100644 --- a/src/main/kotlin/presentation/state/CreatestateUi.kt +++ b/src/main/kotlin/presentation/state/CreatestateUi.kt @@ -11,7 +11,7 @@ import com.berlin.presentation.io.Viewer class CreateStateUi( private val createStateUseCase: CreateStateUseCase, private val viewer: Viewer, - private val reader: Reader, + private val reader: Reader ) : UiRunner { override val id: Int = 1000 diff --git a/src/test/kotlin/presentation/state/CreateStateUiTest.kt b/src/test/kotlin/presentation/state/CreateStateUiTest.kt index 440d0f1..5d0de79 100644 --- a/src/test/kotlin/presentation/state/CreateStateUiTest.kt +++ b/src/test/kotlin/presentation/state/CreateStateUiTest.kt @@ -2,149 +2,116 @@ package presentation.state import com.berlin.domain.model.Project import com.berlin.domain.usecase.state.CreateStateUseCase +import com.berlin.presentation.helper.choose import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer import com.berlin.presentation.state.CreateStateUi import io.mockk.every import io.mockk.mockk +import io.mockk.mockkStatic import io.mockk.verify import org.junit.jupiter.api.BeforeEach -import kotlin.test.Test - -private val testProject = Project( - id = "project-123", - name = "Test Project", - statesId = listOf("state-1", "state-2"), - tasksId = listOf("task-1", "task-2"), - description = "Test project description" -) +import org.junit.jupiter.api.Test class CreateStateUiTest { - private lateinit var createStateUseCase: CreateStateUseCase - private lateinit var createStateUi: CreateStateUi - private val viewer: Viewer = mockk(relaxed = true) - private val reader: Reader = mockk(relaxed = true) - - @BeforeEach - fun setup() { - createStateUseCase = mockk(relaxed = true) - createStateUi = CreateStateUi(createStateUseCase, viewer, reader) - } - - @Test - fun `should display error and retry when project id is empty`() { - every { reader.read() } returnsMany listOf("0", null, "validName", "exit") - - createStateUi.run() - - verify { viewer.show("State Name can not be empty") } - verify(exactly = 4) { reader.read() } - } - - @Test - fun `should display error message when state name is empty`() { - // Given - val projectId = testProject.id - every { reader.read() } returnsMany listOf(projectId, null, "exit") - - // When - createStateUi.run() - - // Then - verify { viewer.show("State Name can not be empty") } - verify(exactly = 3) { reader.read() } - } - - @Test - fun `should exit state creation when user enters exit command`() { - // Given - val projectId = testProject.id - every { reader.read() } returnsMany listOf(projectId, "exit") - - // When - createStateUi.run() - - // Then - verify(exactly = 2) { reader.read() } - } - - @Test - fun `should create state successfully when valid state name is provided`() { - // Given - val projectId = testProject.id - val stateName = "NewState" - - every { reader.read() } returnsMany listOf(projectId, stateName, "exit") - every { - createStateUseCase.createNewState( - stateName, - projectId - ) - } returns Result.success("State created successfully") - - // When - createStateUi.run() - - // Then - verify { createStateUseCase.createNewState(stateName, projectId) } - verify { viewer.show("State created successfully") } - } - - @Test - fun `should display error message when state creation fails`() { - // Given - val projectId = testProject.id - val stateName = "InvalidState" - - every { reader.read() } returnsMany listOf(projectId, stateName, "exit") - every { - createStateUseCase.createNewState( - stateName, - projectId - ) - } returns Result.failure(Exception("Creation Failed")) - - // When - createStateUi.run() - - // Then - verify { createStateUseCase.createNewState(stateName, projectId) } - verify { viewer.show("Creation Failed") } - } - - @Test - fun `should handle exception thrown by use case and continue execution`() { - // Given - val projectId = testProject.id - val stateName = " " - - every { reader.read() } returnsMany listOf(projectId, stateName, "exit") - every { - createStateUseCase.createNewState( - stateName, - projectId - ) - } throws Exception("State Name must not be empty or blank") - - // When - createStateUi.run() - - // Then - verify { createStateUseCase.createNewState(stateName, projectId) } - verify { viewer.show("Invalid State Name, Try Again") } - } - - @Test - fun `should handle case insensitive exit command`() { - // Given - val projectId = testProject.id - every { reader.read() } returnsMany listOf(projectId, "ExIt") - - // When - createStateUi.run() - - // Then - verify(exactly = 2) { reader.read() } - } - -} \ No newline at end of file + private lateinit var createStateUseCase: CreateStateUseCase + private lateinit var createStateUi: CreateStateUi + private val viewer: Viewer = mockk(relaxed = true) + private val reader: Reader = mockk(relaxed = true) + + + private val testProject = Project( + id = "P1", + name = "Berlin Core", + description = "The back-end", + statesId = listOf("S1", "S2", "S3", "S4"), + tasksId = mutableListOf() + ) + + @BeforeEach + fun setup() { + createStateUseCase = mockk(relaxed = true) + createStateUi = CreateStateUi(createStateUseCase, viewer, reader) + + // Mock the choose function to return our test project + mockkStatic("com.berlin.presentation.helper.ChooserKt") + every { + choose( + any(), any(), any(), viewer, reader + ) + } returns testProject + } + + @Test + fun `should display error message when state name is null or empty`() { + // Given + every { reader.read() } returnsMany listOf("", null, "exit") + + // When + createStateUi.run() + + // Then + verify { viewer.show("State Name can not be empty") } + verify(exactly = 3) { reader.read() } + } + + @Test + fun `should exit state creation when user enters exit command`() { + // Given + every { reader.read() } returns "exit" + + // When + createStateUi.run() + + // Then + verify(exactly = 1) { reader.read() } + } + + @Test + fun `should create state successfully when valid state name is provided`() { + // Given + val stateName = "NewState" + + every { reader.read() } returnsMany listOf(stateName, "exit") + every { + createStateUseCase.createNewState(stateName, testProject.id) + } returns Result.success("State created successfully") + + // When + createStateUi.run() + + // Then + verify { createStateUseCase.createNewState(stateName, testProject.id) } + verify { viewer.show("State created successfully") } + } + + @Test + fun `should display error message when state creation fails`() { + // Given + val stateName = "InvalidState" + + every { reader.read() } returnsMany listOf(stateName, "exit") + every { + createStateUseCase.createNewState(stateName, testProject.id) + } returns Result.failure(Exception("Creation Failed")) + + // When + createStateUi.run() + + // Then + verify { createStateUseCase.createNewState(stateName, testProject.id) } + verify { viewer.show("Creation Failed") } + } + + @Test + fun `should handle case insensitive exit command`() { + // Given + every { reader.read() } returns "ExIt" + + // When + createStateUi.run() + + // Then + verify(exactly = 1) { reader.read() } + } +} From 2fe507a6e713724d4adb832712d5bc2607d4cec5 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Fri, 9 May 2025 01:04:29 +0300 Subject: [PATCH 12/25] apply unit test and implement ui for update state and get state by id --- .../presentation/state/DeleteStateUi.kt | 2 +- .../presentation/state/updateStateUi.kt | 6 -- .../presentation/state/GetStateByIdUiTest.kt | 61 ++++++++++++++++++- .../presentation/state/UpdateStateUiTest.kt | 22 ++----- 4 files changed, 65 insertions(+), 26 deletions(-) diff --git a/src/main/kotlin/presentation/state/DeleteStateUi.kt b/src/main/kotlin/presentation/state/DeleteStateUi.kt index 4f83b8f..e1de29c 100644 --- a/src/main/kotlin/presentation/state/DeleteStateUi.kt +++ b/src/main/kotlin/presentation/state/DeleteStateUi.kt @@ -31,7 +31,7 @@ class DeleteStateUi( reader = reader ) viewer.show("Type Y to confirm deletion:") - if (!reader.read().equals("y", true)) throw InputCancelledException("") + if (!reader.read().equals("y", true)) throw InputCancelledException("Cancelled.") deleteStateUseCase.deleteState(state.id) .onSuccess { diff --git a/src/main/kotlin/presentation/state/updateStateUi.kt b/src/main/kotlin/presentation/state/updateStateUi.kt index 8c833b6..f3fd93d 100644 --- a/src/main/kotlin/presentation/state/updateStateUi.kt +++ b/src/main/kotlin/presentation/state/updateStateUi.kt @@ -1,7 +1,5 @@ package com.berlin.presentation.state -import com.berlin.domain.exception.InputCancelledException -import com.berlin.domain.exception.InvalidSelectionException import com.berlin.domain.exception.InvalidStateNameException import com.berlin.domain.usecase.state.GetAllStatesUseCase import com.berlin.domain.usecase.state.UpdateStateUseCase @@ -41,10 +39,6 @@ class UpdateStateUi( } catch (ex: InvalidStateNameException) { viewer.show("State Name must not be empty or blank") - } catch (ex: InputCancelledException) { - viewer.show("Cancelled.") - } catch (ex: InvalidSelectionException) { - viewer.show("Invalid selection") } } diff --git a/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt b/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt index 7ef05ac..de95cfd 100644 --- a/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt +++ b/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt @@ -1,6 +1,61 @@ package presentation.state -import org.junit.jupiter.api.Assertions.* - class GetStateByIdUiTest{ +import com.berlin.data.DummyData +import com.berlin.domain.exception.StateNotFoundException +import com.berlin.domain.usecase.state.GetStateByIdUseCase +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer +import com.berlin.presentation.state.GetStateByIdUi +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test - } \ No newline at end of file +class GetStateByIdUiTest { + /* + 1-id not exist so state not exist + 2-id exist + */ + private lateinit var viewer: Viewer + private lateinit var reader: Reader + private lateinit var getStateById: GetStateByIdUseCase + private lateinit var ui: GetStateByIdUi + + @BeforeEach + fun setup() { + viewer = mockk(relaxed = true) + reader = mockk() + getStateById = mockk() + ui = GetStateByIdUi(getStateById, viewer, reader) + } + + @Test + fun `getStateById should return state when its id exists`() { + //given + every { getStateById.getStateById("Q1") } returns Result.success(DummyData.states[1]) + every { reader.read() } returns "Q1" + //when + ui.run() + //Then + verify { viewer.show("Enter state ID: ") } + verify { getStateById.getStateById("Q1") } + + } + + @Test + fun `getStateById should show message when id doesn't exist`() { + // Given + every { reader.read() } returns "Q999" + every { getStateById.getStateById("Q999") } returns Result.failure(StateNotFoundException("State with ID Q999 not found")) + + // When + ui.run() + + // Then + verify { viewer.show("Enter state ID: ") } + verify { getStateById.getStateById("Q999") } + verify { viewer.show("No task found with ID “Q999”") } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/state/UpdateStateUiTest.kt b/src/test/kotlin/presentation/state/UpdateStateUiTest.kt index 3d70abb..908c644 100644 --- a/src/test/kotlin/presentation/state/UpdateStateUiTest.kt +++ b/src/test/kotlin/presentation/state/UpdateStateUiTest.kt @@ -1,9 +1,9 @@ package presentation.state import com.berlin.data.DummyData -import com.berlin.domain.exception.InvalidSelectionException import com.berlin.domain.exception.InvalidStateException import com.berlin.domain.exception.InvalidStateNameException +import com.berlin.domain.model.State import com.berlin.domain.usecase.state.GetAllStatesUseCase import com.berlin.domain.usecase.state.UpdateStateUseCase import com.berlin.presentation.io.Reader @@ -30,14 +30,13 @@ class UpdateStateUiTest { } /* - 1-successfully updated - 2-update failed - 3- InvalidStateNameException-->"State Name must not be empty or blank" - 4-InputCancelledException-->"Cancelled." - 5-InvalidSelectionException-->Invalid selection" + 1-successfully updated-->ok + 2-update failed-->ok + 3- InvalidStateNameException-->"State Name must not be empty or blank"-->ok */ private companion object { - val stateId = DummyData.states[1].id + val state = State("S1","Menna","P5") + val stateId= state.id val successfullyStateNewName = "done" val stateProjectId = DummyData.states[1].projectId val emptyStateName = "" @@ -100,14 +99,5 @@ class UpdateStateUiTest { verify { viewer.show("State Name must not be empty or blank") } } - @Test - fun `run should `() { - //Given - every { reader.read() } returnsMany listOf("X") - //When - updateStateUi.run() - //Then - verify { viewer.show() } - } } \ No newline at end of file From 9f0bb5b0ef4e34b33d8a3dc69daf8bebd31af42a Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Fri, 9 May 2025 01:08:19 +0300 Subject: [PATCH 13/25] refactor test cases of get task by state id ,get state by task id and get all states by project id --- .../kotlin/domain/usecase/state/DeleteStateUseCase.kt | 4 ++-- .../usecase/state/GetAllStatesByProjectIdUseCase.kt | 4 +--- .../domain/usecase/state/GetStateByTaskIdUseCase.kt | 2 +- .../domain/usecase/state/GetTasksByStateIdUseCase.kt | 5 ++--- .../logic/usecase/state/DeleteStateUseCaseTest.kt | 6 +++--- .../state/GetAllStatesByProjectIdUseCaseTest.kt | 11 ++++------- .../usecase/state/GetTasksByStateIdUseCaseTest.kt | 8 ++++++-- 7 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/domain/usecase/state/DeleteStateUseCase.kt b/src/main/kotlin/domain/usecase/state/DeleteStateUseCase.kt index 378aeb2..4bba9c3 100644 --- a/src/main/kotlin/domain/usecase/state/DeleteStateUseCase.kt +++ b/src/main/kotlin/domain/usecase/state/DeleteStateUseCase.kt @@ -26,6 +26,6 @@ class DeleteStateUseCase( private fun validateStateId(stateId: String): Boolean = stateId.isNotBlank() || !(stateId.all { it.isDigit() }) - fun checkStateExists(stateId: String): Boolean = - stateRepository.getStateById(stateId) != null + private fun checkStateExists(stateId: String): Boolean = + stateRepository.getStateById(stateId).isSuccess } diff --git a/src/main/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCase.kt b/src/main/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCase.kt index d56af18..4acc5ce 100644 --- a/src/main/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCase.kt +++ b/src/main/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCase.kt @@ -18,10 +18,8 @@ class GetAllStatesByProjectIdUseCase( if (checkProjectExists(projectId)) { return stateRepository.getStatesByProjectId(projectId) - // ?: throw StateNotFoundException("No states found for project ID $projectId") } else { - return Result.failure(Exception("sff")) - //throw ProjectNotFoundException("Project with ID $projectId does not exist") + return Result.failure(Exception("Project with ID $projectId does not exist")) } } diff --git a/src/main/kotlin/domain/usecase/state/GetStateByTaskIdUseCase.kt b/src/main/kotlin/domain/usecase/state/GetStateByTaskIdUseCase.kt index 25300d9..99dedd3 100644 --- a/src/main/kotlin/domain/usecase/state/GetStateByTaskIdUseCase.kt +++ b/src/main/kotlin/domain/usecase/state/GetStateByTaskIdUseCase.kt @@ -22,7 +22,7 @@ class GetStateByTaskIdUseCase( } - private fun checkTaskExists(taskId: String): Boolean = taskRepository.findById(taskId) != null + private fun checkTaskExists(taskId: String): Boolean = taskRepository.findById(taskId).isSuccess private fun validateTaskId(taskId: String): Boolean = taskId.isNotBlank() && !(taskId.all { it.isDigit() }) diff --git a/src/main/kotlin/domain/usecase/state/GetTasksByStateIdUseCase.kt b/src/main/kotlin/domain/usecase/state/GetTasksByStateIdUseCase.kt index bff0fd0..3460488 100644 --- a/src/main/kotlin/domain/usecase/state/GetTasksByStateIdUseCase.kt +++ b/src/main/kotlin/domain/usecase/state/GetTasksByStateIdUseCase.kt @@ -1,7 +1,6 @@ package com.berlin.domain.usecase.state import com.berlin.domain.exception.InvalidStateIdException -import com.berlin.domain.exception.InvalidTaskStateException import com.berlin.domain.exception.TaskNotFoundException import com.berlin.domain.model.Task import com.berlin.domain.repository.StateRepository @@ -10,7 +9,7 @@ class GetTasksByStateIdUseCase ( private val stateRepository: StateRepository ) { - fun getAllTasksByStateId(stateId: String): List? { + fun getAllTasksByStateId(stateId: String): List { if (!validateStateId(stateId)) throw InvalidStateIdException("State ID must not be empty or blank") if (checkStateExists(stateId)) { @@ -22,7 +21,7 @@ class GetTasksByStateIdUseCase ( } - private fun checkStateExists(stateId: String): Boolean = stateRepository.getStateById(stateId) != null + private fun checkStateExists(stateId: String): Boolean = stateRepository.getStateById(stateId).isSuccess private fun validateStateId(stateId: String): Boolean = stateId.isNotBlank() && !(stateId.all { it.isDigit() }) } \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt index e966180..7deecef 100644 --- a/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt @@ -52,15 +52,15 @@ class DeleteStateUseCaseTest { @Test fun `should throw exception when state does not exist`() { // Given - val stateId="S2" + val stateId="q2" every { stateRepository.getStateById(any()) } returns Result.failure(StateNotFoundException(stateId)) // When - val result = deleteStateUseCase.deleteState("S2") + val result = deleteStateUseCase.deleteState("q2") // Then result.onFailure { exception -> - assertThat(exception.message).isEqualTo("State with ID S2 does not exist") + assertThat(exception.message).isEqualTo("State with ID q2 does not exist") } } diff --git a/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt index 859dbe7..67ab897 100644 --- a/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt @@ -41,18 +41,16 @@ class GetAllStatesByProjectIdUseCaseTest { val result = getAllStatesByProjectIdUseCase.getAllStatesByProjectId("P1") // Then - assertThat(result).isEqualTo(expectedStates) + assertThat(result).isEqualTo(Result.success(expectedStates)) } @Test fun `should throw exception when no states are found for the project`() { // Given - every { projectRepository.getProjectById("P1") } returns mockk() - every { stateRepository.getStatesByProjectId("P3") } returns Result.success(emptyList()) + every { stateRepository.getStatesByProjectId("P3") } returns Result.failure(Exception("Project with ID P3 does not exist")) // When & Then - val exception = assertThrows { getAllStatesByProjectIdUseCase.getAllStatesByProjectId("P3") } - assertThat(exception.message).isEqualTo("No states found for project ID P3") + assertThat(getAllStatesByProjectIdUseCase.getAllStatesByProjectId("P3").isFailure).isTrue() } @Test @@ -61,8 +59,7 @@ class GetAllStatesByProjectIdUseCaseTest { every { projectRepository.getProjectById("P2") } returns null // When & Then - val exception = assertThrows { getAllStatesByProjectIdUseCase.getAllStatesByProjectId("P2") } - assertThat(exception.message).isEqualTo("Project with ID P2 does not exist") + assertThat(getAllStatesByProjectIdUseCase.getAllStatesByProjectId("P2").isFailure).isTrue() } @ParameterizedTest diff --git a/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt index 59eb47e..507d763 100644 --- a/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt @@ -1,5 +1,7 @@ package com.berlin.logic.usecase.state +import com.berlin.domain.exception.InvalidStateIdException +import com.berlin.domain.exception.StateNotFoundException import com.berlin.domain.usecase.state.GetTasksByStateIdUseCase import com.berlin.domain.model.Task import com.berlin.domain.repository.StateRepository @@ -60,10 +62,12 @@ class GetTasksByStateIdUseCaseTest { @Test fun `should throw exception when state id does not exist`() { // Given - every { stateRepository.getStateById("S2") } returns mockk() + every { stateRepository.getStateById("S2") } returns Result.failure(StateNotFoundException("S2")) // When & Then - val exception = assertThrows { getTasksByStateIdUseCase.getAllTasksByStateId("S2") } + val exception = assertThrows { + getTasksByStateIdUseCase.getAllTasksByStateId("S2") + } assertThat(exception.message).isEqualTo("State with ID S2 does not exist") } From c8a4f980a24438e80ce109209f5b886828159267 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Fri, 9 May 2025 01:25:44 +0300 Subject: [PATCH 14/25] resolve test cases problems in update state ui --- .../presentation/state/GetStateByIdUiTest.kt | 4 ++-- .../presentation/state/UpdateStateUiTest.kt | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt b/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt index de95cfd..5e76ac7 100644 --- a/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt +++ b/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt @@ -1,7 +1,7 @@ package presentation.state -import com.berlin.data.DummyData import com.berlin.domain.exception.StateNotFoundException +import com.berlin.domain.model.State import com.berlin.domain.usecase.state.GetStateByIdUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer @@ -33,7 +33,7 @@ class GetStateByIdUiTest { @Test fun `getStateById should return state when its id exists`() { //given - every { getStateById.getStateById("Q1") } returns Result.success(DummyData.states[1]) + every { getStateById.getStateById("Q1") } returns Result.success(State("Q1","Menna","P5"),) every { reader.read() } returns "Q1" //when ui.run() diff --git a/src/test/kotlin/presentation/state/UpdateStateUiTest.kt b/src/test/kotlin/presentation/state/UpdateStateUiTest.kt index 908c644..71ab33f 100644 --- a/src/test/kotlin/presentation/state/UpdateStateUiTest.kt +++ b/src/test/kotlin/presentation/state/UpdateStateUiTest.kt @@ -1,6 +1,5 @@ package presentation.state -import com.berlin.data.DummyData import com.berlin.domain.exception.InvalidStateException import com.berlin.domain.exception.InvalidStateNameException import com.berlin.domain.model.State @@ -35,18 +34,18 @@ class UpdateStateUiTest { 3- InvalidStateNameException-->"State Name must not be empty or blank"-->ok */ private companion object { - val state = State("S1","Menna","P5") + val state = State("Q1","Menna","P5") val stateId= state.id val successfullyStateNewName = "done" - val stateProjectId = DummyData.states[1].projectId + val stateProjectId = "P5" val emptyStateName = "" } @Test fun `run should return successfully updated when every thing is correct`() { //Given - every { getAllStates() } returns DummyData.states - every { reader.read() } returnsMany listOf("2", "done") + every { getAllStates() } returns listOf(State("Q1","Menna","P5")) + every { reader.read() } returnsMany listOf("1", "done") every { updateState.updateState(any(), any(), any()) } returns Result.success("Updated Successfully") @@ -70,8 +69,8 @@ class UpdateStateUiTest { @Test fun `run should return update failed when update fails`() { //Given - every { getAllStates() } returns DummyData.states - every { reader.read() } returnsMany listOf("2", "done") + every { getAllStates() } returns listOf(State("Q1","Menna","P5"),) + every { reader.read() } returnsMany listOf("1", "done") every { updateState.updateState(any(), any(), any()) } returns Result.failure(InvalidStateException("can not update state")) @@ -86,8 +85,8 @@ class UpdateStateUiTest { @Test fun `run should throw InvalidStateNameException when State Name is empty or blank`() { //Given - every { getAllStates() } returns DummyData.states - every { reader.read() } returnsMany listOf("2", " ") + every { getAllStates() } returns listOf(State("Q1","Menna","P5")) + every { reader.read() } returnsMany listOf("1", " ") every { updateState.updateState(any(), emptyStateName, any()) } throws InvalidStateNameException("State Name must not be empty or blank") From ff3150761284e5fc84aa9e4486713c6eded0129c Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Fri, 9 May 2025 01:38:49 +0300 Subject: [PATCH 15/25] resolve ui module error --- src/main/kotlin/di/uiModule.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/di/uiModule.kt b/src/main/kotlin/di/uiModule.kt index 360a35d..9a5ce53 100644 --- a/src/main/kotlin/di/uiModule.kt +++ b/src/main/kotlin/di/uiModule.kt @@ -10,7 +10,6 @@ import com.berlin.presentation.project.* import com.berlin.presentation.task.* import org.koin.core.qualifier.named import org.koin.dsl.module -import kotlin.math.sin val uiModule = module { @@ -63,7 +62,7 @@ val uiModule = module { get(), get(), get(), - get() + get(), get(), get(), From 9967751c29367e50f11d1756f1bf735bb01618ba Mon Sep 17 00:00:00 2001 From: ahmad Date: Fri, 9 May 2025 07:06:01 +0300 Subject: [PATCH 16/25] fix test cases at task usecases and task ui and task repo --- .../kotlin/data/task/TaskRepositoryImpl.kt | 2 +- src/main/kotlin/di/dataModule.kt | 3 +- .../data/task/TaskRepositoryImplTest.kt | 15 +++- .../usecase/task/AssignTaskUseCaseTest.kt | 53 +++++++++--- .../task/ChangeTaskStateUseCaseTest.kt | 43 ++++++++-- .../usecase/task/CreateTaskUseCaseTest.kt | 81 ++++++++++++------- .../usecase/task/DeleteTaskUseCaseTest.kt | 39 +++++++-- .../usecase/task/GetAllTasksUseCaseTest.kt | 3 +- .../usecase/task/UpdateTaskUseCaseTest.kt | 68 +++++++++++----- .../presentation/task/AssignTaskUITest.kt | 50 ++++++------ .../task/ChangeTaskStateUITest.kt | 66 ++++++++------- .../presentation/task/CreateTaskUITest.kt | 35 ++------ .../presentation/task/DeleteTaskUITest.kt | 6 +- .../presentation/task/UpdateTaskUITest.kt | 29 ++++--- 14 files changed, 313 insertions(+), 180 deletions(-) diff --git a/src/main/kotlin/data/task/TaskRepositoryImpl.kt b/src/main/kotlin/data/task/TaskRepositoryImpl.kt index 54d3835..37729d2 100644 --- a/src/main/kotlin/data/task/TaskRepositoryImpl.kt +++ b/src/main/kotlin/data/task/TaskRepositoryImpl.kt @@ -1,4 +1,4 @@ -package com.berlin.data.memory +package com.berlin.data.task import com.berlin.data.BaseDataSource import com.berlin.domain.exception.InvalidTaskException diff --git a/src/main/kotlin/di/dataModule.kt b/src/main/kotlin/di/dataModule.kt index 74b5f60..cb1691c 100644 --- a/src/main/kotlin/di/dataModule.kt +++ b/src/main/kotlin/di/dataModule.kt @@ -5,13 +5,12 @@ import com.berlin.data.BaseDataSource import com.berlin.data.BaseSchema import com.berlin.data.authentication.AuthenticationRepositoryImpl import com.berlin.data.csv_data_source.CsvDataSource -import com.berlin.data.memory.TaskRepositoryImpl +import com.berlin.data.task.TaskRepositoryImpl import com.berlin.data.project.ProjectRepositoryImpl import com.berlin.data.schema.* import com.berlin.data.state.StateRepositoryImpl import com.berlin.domain.model.* import com.berlin.domain.repository.* -import data.UserCache import org.koin.core.qualifier.named import org.koin.dsl.module diff --git a/src/test/kotlin/data/task/TaskRepositoryImplTest.kt b/src/test/kotlin/data/task/TaskRepositoryImplTest.kt index eb422ed..12c1259 100644 --- a/src/test/kotlin/data/task/TaskRepositoryImplTest.kt +++ b/src/test/kotlin/data/task/TaskRepositoryImplTest.kt @@ -1,4 +1,4 @@ -package com.berlin.data.memory +package com.berlin.data.task import com.berlin.data.BaseDataSource import com.berlin.domain.exception.InvalidTaskException @@ -147,4 +147,17 @@ class TaskRepositoryImplTest { assertThat(result.exceptionOrNull()).isInstanceOf(InvalidTaskException::class.java) } + @Test + fun `writeAll on data source adds all tasks and is reflected in repo`() { + DummyData.tasks.clear() + + val t1 = task("bulk1") + val t2 = task("bulk2") + + val wrote = DummyData.writeAll(listOf(t1, t2)) + assertThat(wrote).isTrue() + + val all = repo.getAllTasks() + assertThat(all).containsExactly(t1, t2) + } } diff --git a/src/test/kotlin/domain/usecase/task/AssignTaskUseCaseTest.kt b/src/test/kotlin/domain/usecase/task/AssignTaskUseCaseTest.kt index 447cfc9..e997fb5 100644 --- a/src/test/kotlin/domain/usecase/task/AssignTaskUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/task/AssignTaskUseCaseTest.kt @@ -2,14 +2,16 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidAssigneeException import com.berlin.domain.exception.TaskNotFoundException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task import com.berlin.domain.model.User import com.berlin.domain.model.UserRole import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify +import data.UserCache +import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -17,10 +19,12 @@ import org.junit.jupiter.api.assertThrows class AssignTaskUseCaseTest { private lateinit var taskRepository: TaskRepository + private lateinit var addAuditLogUseCase: AddAuditLogUseCase + private lateinit var userCache: UserCache private lateinit var useCase: AssignTaskUseCase - private val creator = User("U0", "alice", "pw", UserRole.ADMIN) - private val oldAssignee = User("U1", "john", "pw", UserRole.MATE) + private val creator = User("U0", "alice", "pw", UserRole.ADMIN) + private val oldAssignee = User("U1", "john", "pw", UserRole.MATE) private val anotherAssignee = User("U2", "bob", "pw", UserRole.MATE) private val stored = Task( @@ -36,35 +40,40 @@ class AssignTaskUseCaseTest { @BeforeEach fun setUp() { taskRepository = mockk() - useCase = AssignTaskUseCase(taskRepository) - } + addAuditLogUseCase = mockk() + userCache = mockk() + every { userCache.currentUser } returns creator + useCase = AssignTaskUseCase(taskRepository, addAuditLogUseCase, userCache) + } @Test fun `result is success when assignee changes`() { stubHappyPath() val result = useCase("1", anotherAssignee.id) assertThat(result.isSuccess).isTrue() + verifyAudit("1") } @Test fun `repository update is called with new assignee`() { stubHappyPath() useCase("1", anotherAssignee.id) - - verify(exactly = 1) { + verify { taskRepository.update( - match { it.id == "1" && it.assignedToUserId == anotherAssignee.id }) + match { it.id == "1" && it.assignedToUserId == anotherAssignee.id } + ) } } - @Test fun `result is failure when task is not found`() { every { taskRepository.findById("1") } returns Result.failure(TaskNotFoundException("1")) val result = useCase("1", anotherAssignee.id) + assertThat(result.isFailure).isTrue() + verify(exactly = 0) { taskRepository.update(any()) } } @Test @@ -73,6 +82,7 @@ class AssignTaskUseCaseTest { every { taskRepository.update(any()) } returns Result.failure(IllegalStateException("boom")) val result = useCase("1", anotherAssignee.id) + assertThat(result.isFailure).isTrue() } @@ -90,5 +100,26 @@ class AssignTaskUseCaseTest { private fun stubHappyPath() { every { taskRepository.findById("1") } returns Result.success(stored) every { taskRepository.update(any()) } answers { Result.success(firstArg()) } + every { + addAuditLogUseCase.addAuditLog( + createdByUserId = creator.id, + auditAction = AuditAction.UPDATE, + changesDescription = null, + entityType = EntityType.TASK, + entityId = "1" + ) + } returns Result.success("audit-id-1") + } + + private fun verifyAudit(taskId: String) { + verify { + addAuditLogUseCase.addAuditLog( + createdByUserId = creator.id, + auditAction = AuditAction.UPDATE, + changesDescription = null, + entityType = EntityType.TASK, + entityId = taskId + ) + } } } diff --git a/src/test/kotlin/domain/usecase/task/ChangeTaskStateUseCaseTest.kt b/src/test/kotlin/domain/usecase/task/ChangeTaskStateUseCaseTest.kt index c8d736e..e45bf19 100644 --- a/src/test/kotlin/domain/usecase/task/ChangeTaskStateUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/task/ChangeTaskStateUseCaseTest.kt @@ -2,13 +2,15 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskStateException import com.berlin.domain.exception.TaskNotFoundException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify +import data.UserCache +import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -16,6 +18,8 @@ import org.junit.jupiter.api.assertThrows class ChangeTaskStateUseCaseTest { private lateinit var taskRepository: TaskRepository + private lateinit var addAuditLogUseCase: AddAuditLogUseCase + private lateinit var userCache: UserCache private lateinit var useCase: ChangeTaskStateUseCase private val creator = mockk(relaxed = true) @@ -34,7 +38,22 @@ class ChangeTaskStateUseCaseTest { @BeforeEach fun setUp() { taskRepository = mockk() - useCase = ChangeTaskStateUseCase(taskRepository) + addAuditLogUseCase = mockk() + userCache = mockk() + every { userCache.currentUser } returns creator + + // Needed to avoid MockKException during audit logging + every { + addAuditLogUseCase.addAuditLog( + createdByUserId = any(), + auditAction = any(), + changesDescription = any(), + entityType = any(), + entityId = any() + ) + } returns Result.success("audit-log-id") + + useCase = ChangeTaskStateUseCase(taskRepository, addAuditLogUseCase, userCache) } @Test @@ -45,9 +64,10 @@ class ChangeTaskStateUseCaseTest { val result = useCase("1", "DONE") assertThat(result.isSuccess).isTrue() - verify(exactly = 1) { + verify { taskRepository.update(match { it.id == "1" && it.stateId == "DONE" }) } + verifyAudit("1") } @Test @@ -78,7 +98,6 @@ class ChangeTaskStateUseCaseTest { assertThrows { useCase("1", " ") } - // no update should be attempted after validation fails verify(exactly = 0) { taskRepository.update(any()) } } @@ -91,4 +110,16 @@ class ChangeTaskStateUseCaseTest { } verify(exactly = 0) { taskRepository.update(any()) } } + + private fun verifyAudit(taskId: String) { + verify { + addAuditLogUseCase.addAuditLog( + createdByUserId = creator.id, + auditAction = AuditAction.UPDATE, + changesDescription = null, + entityType = EntityType.TASK, + entityId = taskId + ) + } + } } diff --git a/src/test/kotlin/domain/usecase/task/CreateTaskUseCaseTest.kt b/src/test/kotlin/domain/usecase/task/CreateTaskUseCaseTest.kt index ab3c1ce..99f6e4f 100644 --- a/src/test/kotlin/domain/usecase/task/CreateTaskUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/task/CreateTaskUseCaseTest.kt @@ -2,14 +2,14 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskTitle import com.berlin.domain.exception.TaskAlreadyExistsException -import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.google.common.truth.Truth.assertThat -import io.mockk.Called -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify +import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -18,6 +18,7 @@ class CreateTaskUseCaseTest { private lateinit var taskRepository: TaskRepository private lateinit var idGenerator: IdGeneratorImplementation + private lateinit var addAuditLogUseCase: AddAuditLogUseCase private lateinit var useCase: CreateTaskUseCase private val projectId = "P1" @@ -30,7 +31,8 @@ class CreateTaskUseCaseTest { fun setUp() { taskRepository = mockk() idGenerator = mockk() - useCase = CreateTaskUseCase(taskRepository, idGenerator) + addAuditLogUseCase = mockk(relaxUnitFun = true) + useCase = CreateTaskUseCase(taskRepository, idGenerator, addAuditLogUseCase) } @Test @@ -39,30 +41,45 @@ class CreateTaskUseCaseTest { val trimmed = rawTitle.trim() val generated = "T123" - every { idGenerator.generateId(eq(trimmed), any(), any()) } returns generated - // NOW: unique => no existing tasks + every { idGenerator.generateId(trimmed, any(), any()) } returns generated every { taskRepository.getAllTasks() } returns emptyList() - every { taskRepository.create(any()) }.answers { Result.success(firstArg()) } + every { taskRepository.create(any()) } answers { Result.success(firstArg()) } + + every { + addAuditLogUseCase.addAuditLog( + createByUserId, + AuditAction.CREATE, + null, + EntityType.TASK, + generated + ) + } returns Result.success("log-id-123") - val result = useCase( - projectId, rawTitle, description, stateId, createByUserId, assignedToUserId - ) + val result = useCase(projectId, rawTitle, description, stateId, createByUserId, assignedToUserId) assertThat(result.isSuccess).isTrue() - verify { idGenerator.generateId(eq(trimmed), any(), any()) } + verify { idGenerator.generateId(trimmed, any(), any()) } verify { taskRepository.create(match { it.id == generated && it.title == trimmed }) } + verify { + addAuditLogUseCase.addAuditLog( + createByUserId, + AuditAction.CREATE, + null, + EntityType.TASK, + generated + ) + } } + @Test fun `throws InvalidTaskTitle for blank title`() { assertThrows { - useCase( - projectId, " ", description, stateId, createByUserId, assignedToUserId - ) + useCase(projectId, " ", description, stateId, createByUserId, assignedToUserId) } verify { idGenerator wasNot Called } verify { taskRepository wasNot Called } @@ -71,9 +88,7 @@ class CreateTaskUseCaseTest { @Test fun `throws InvalidTaskTitle for numeric-only title`() { assertThrows { - useCase( - projectId, "12345", description, stateId, createByUserId, assignedToUserId - ) + useCase(projectId, "12345", description, stateId, createByUserId, assignedToUserId) } verify { idGenerator wasNot Called } verify { taskRepository wasNot Called } @@ -84,36 +99,40 @@ class CreateTaskUseCaseTest { val title = "Unique" val generated = "T999" - every { idGenerator.generateId(eq(title), any(), any()) } returns generated - // NOW: not unique => that ID is already in getAllTasks() + every { idGenerator.generateId(title, any(), any()) } returns generated every { taskRepository.getAllTasks() } returns listOf( Task(generated, projectId, title, description, stateId, assignedToUserId, createByUserId) ) assertThrows { - useCase( - projectId, title, description, stateId, createByUserId, assignedToUserId - ) + useCase(projectId, title, description, stateId, createByUserId, assignedToUserId) } - verify { idGenerator.generateId(eq(title), any(), any()) } + verify { idGenerator.generateId(title, any(), any()) } verify(exactly = 0) { taskRepository.create(any()) } } @Test fun `result is failure when repository create fails`() { val title = "Valid" + val trimmed = title.trim() val generated = "T500" - every { idGenerator.generateId(eq(title), any(), any()) } returns generated - // unique so we proceed to create + + every { idGenerator.generateId(trimmed, any(), any()) } returns generated every { taskRepository.getAllTasks() } returns emptyList() every { taskRepository.create(any()) } returns Result.failure(IllegalStateException("boom")) - val result = useCase( - projectId, title, description, stateId, createByUserId, assignedToUserId - ) + val result = useCase(projectId, trimmed, description, stateId, createByUserId, assignedToUserId) assertThat(result.isFailure).isTrue() - verify { taskRepository.create(match { it.id == generated }) } + verify { + taskRepository.create(match { + it.id == generated && + it.projectId == projectId && + it.title == trimmed && + it.assignedToUserId == assignedToUserId && + it.createByUserId == createByUserId + }) + } } } diff --git a/src/test/kotlin/domain/usecase/task/DeleteTaskUseCaseTest.kt b/src/test/kotlin/domain/usecase/task/DeleteTaskUseCaseTest.kt index 292d74c..616b822 100644 --- a/src/test/kotlin/domain/usecase/task/DeleteTaskUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/task/DeleteTaskUseCaseTest.kt @@ -1,12 +1,15 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.TaskNotFoundException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task +import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify +import data.UserCache +import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -14,8 +17,12 @@ import org.junit.jupiter.api.assertThrows class DeleteTaskUseCaseTest { private lateinit var taskRepository: TaskRepository + private lateinit var addAuditLogUseCase: AddAuditLogUseCase + private lateinit var userCache: UserCache private lateinit var deleteTaskUseCase: DeleteTaskUseCase + private lateinit var currentUser: User + private val stored = Task( id = "T1", projectId = "P1", @@ -29,7 +36,18 @@ class DeleteTaskUseCaseTest { @BeforeEach fun setUp() { taskRepository = mockk() - deleteTaskUseCase = DeleteTaskUseCase(taskRepository) + addAuditLogUseCase = mockk() + userCache = mockk() + + currentUser = mockk(relaxed = true) + every { currentUser.id } returns "U1" + every { userCache.currentUser } returns currentUser + + every { + addAuditLogUseCase.addAuditLog("U1", AuditAction.DELETE, null, EntityType.TASK, "T1") + } returns Result.success("audit-log-id") + + deleteTaskUseCase = DeleteTaskUseCase(taskRepository, addAuditLogUseCase, userCache) } @Test @@ -40,7 +58,16 @@ class DeleteTaskUseCaseTest { val result = deleteTaskUseCase("T1") assertThat(result.isSuccess).isTrue() - verify(exactly = 1) { taskRepository.delete("T1") } + verify { taskRepository.delete("T1") } + verify { + addAuditLogUseCase.addAuditLog( + createdByUserId = "U1", + auditAction = AuditAction.DELETE, + changesDescription = null, + entityType = EntityType.TASK, + entityId = "T1" + ) + } } @Test @@ -63,7 +90,7 @@ class DeleteTaskUseCaseTest { assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isInstanceOf(IllegalStateException::class.java) - verify(exactly = 1) { taskRepository.delete("T1") } + verify { taskRepository.delete("T1") } } @Test diff --git a/src/test/kotlin/domain/usecase/task/GetAllTasksUseCaseTest.kt b/src/test/kotlin/domain/usecase/task/GetAllTasksUseCaseTest.kt index 3dde75b..c29c3d9 100644 --- a/src/test/kotlin/domain/usecase/task/GetAllTasksUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/task/GetAllTasksUseCaseTest.kt @@ -1,8 +1,7 @@ -package domain.usecase.task +package com.berlin.domain.usecase.task import com.berlin.domain.model.Task import com.berlin.domain.repository.TaskRepository -import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.google.common.truth.Truth.assertThat import io.mockk.every import io.mockk.mockk diff --git a/src/test/kotlin/domain/usecase/task/UpdateTaskUseCaseTest.kt b/src/test/kotlin/domain/usecase/task/UpdateTaskUseCaseTest.kt index 50b0e04..e5e00cd 100644 --- a/src/test/kotlin/domain/usecase/task/UpdateTaskUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/task/UpdateTaskUseCaseTest.kt @@ -2,13 +2,15 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskTitle import com.berlin.domain.exception.TaskNotFoundException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify +import data.UserCache +import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -16,9 +18,11 @@ import org.junit.jupiter.api.assertThrows class UpdateTaskUseCaseTest { private lateinit var taskRepository: TaskRepository + private lateinit var addAuditLogUseCase: AddAuditLogUseCase + private lateinit var userCache: UserCache private lateinit var useCase: UpdateTaskUseCase - private val creator = mockk(relaxed = true) + private lateinit var creator: User private val assignee = mockk(relaxed = true) private val stored = Task( @@ -27,14 +31,26 @@ class UpdateTaskUseCaseTest { title = "Old title", description = "Old description", stateId = "TODO", - assignedToUserId = assignee.id, - createByUserId = creator.id + assignedToUserId = "U2", + createByUserId = "U1" ) @BeforeEach fun setUp() { taskRepository = mockk() - useCase = UpdateTaskUseCase(taskRepository) + addAuditLogUseCase = mockk() + userCache = mockk() + + creator = mockk(relaxed = true) + every { creator.id } returns "U1" + every { userCache.currentUser } returns creator + + // Stub audit log call to prevent MockKException + every { + addAuditLogUseCase.addAuditLog("U1", AuditAction.UPDATE, null, EntityType.TASK, "1") + } returns Result.success("log-id") + + useCase = UpdateTaskUseCase(taskRepository, addAuditLogUseCase, userCache) } @Test @@ -42,6 +58,7 @@ class UpdateTaskUseCaseTest { primeRepoToSucceed() val result = useCase("1", title = "New title") assertThat(result.isSuccess).isTrue() + verifyAudit() } @Test @@ -49,14 +66,15 @@ class UpdateTaskUseCaseTest { primeRepoToSucceed() val result = useCase("1", description = "New description") assertThat(result.isSuccess).isTrue() + verifyAudit() } @Test fun `success when only assignee changes`() { primeRepoToSucceed() - val newUser = mockk(relaxed = true) - val result = useCase("1", assignedToUserId = newUser.id) + val result = useCase("1", assignedToUserId = "NEW_USER") assertThat(result.isSuccess).isTrue() + verifyAudit() } @Test @@ -64,9 +82,9 @@ class UpdateTaskUseCaseTest { primeRepoToSucceed() val result = useCase("1") assertThat(result.isSuccess).isTrue() + verifyAudit() } - @Test fun `failure when task is not found`() { every { taskRepository.findById("1") } returns Result.failure(TaskNotFoundException("1")) @@ -75,39 +93,45 @@ class UpdateTaskUseCaseTest { } @Test - fun `failure when repository update returns unexpected error`() { + fun `failure when repository create returns unexpected error`() { every { taskRepository.findById("1") } returns Result.success(stored) - every { taskRepository.update(any()) } returns Result.failure(IllegalStateException("boom")) + every { taskRepository.create(any()) } returns Result.failure(IllegalStateException("boom")) val result = useCase("1", title = "New title") assertThat(result.isFailure).isTrue() } - @Test fun `throws InvalidTaskTitle when new title is blank`() { primeRepoToSucceed() - assertThrows { useCase("1", title = " ") } - - verify(exactly = 0) { taskRepository.update(any()) } + verify(exactly = 0) { taskRepository.create(any()) } } @Test fun `throws InvalidTaskTitle when new title is numeric-only`() { primeRepoToSucceed() - assertThrows { useCase("1", title = "123456") } - - verify(exactly = 0) { taskRepository.update(any()) } + verify(exactly = 0) { taskRepository.create(any()) } } - private fun primeRepoToSucceed() { every { taskRepository.findById("1") } returns Result.success(stored) - every { taskRepository.update(any()) } answers { Result.success(firstArg()) } + every { taskRepository.create(any()) } answers { Result.success(firstArg()) } + } + + private fun verifyAudit() { + verify { + addAuditLogUseCase.addAuditLog( + createdByUserId = "U1", + auditAction = AuditAction.UPDATE, + changesDescription = null, + entityType = EntityType.TASK, + entityId = "1" + ) + } } -} +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/task/AssignTaskUITest.kt b/src/test/kotlin/presentation/task/AssignTaskUITest.kt index 035baf3..3000713 100644 --- a/src/test/kotlin/presentation/task/AssignTaskUITest.kt +++ b/src/test/kotlin/presentation/task/AssignTaskUITest.kt @@ -2,12 +2,16 @@ package com.berlin.presentation.task import com.berlin.data.DummyData import com.berlin.domain.exception.InvalidAssigneeException +import com.berlin.domain.exception.InputCancelledException +import com.berlin.domain.exception.InvalidSelectionException import com.berlin.domain.model.Task import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole import com.berlin.domain.usecase.task.AssignTaskUseCase import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import com.berlin.presentation.task.AssignTaskUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -19,7 +23,6 @@ class AssignTaskUITest { private val viewer: Viewer = mockk(relaxed = true) { every { show(capture(printed)) } just Runs } - private val reader: Reader = mockk() private val assignTaskUC: AssignTaskUseCase = mockk() private val getAllTasks: GetAllTasksUseCase = mockk() @@ -29,37 +32,43 @@ class AssignTaskUITest { @BeforeEach fun setUp() { + // reset in-memory data DummyData.tasks.clear() + DummyData.users.clear() + + // populate two users so choose(...) can pick index "1" and "2" + DummyData.users += User("U1", "alice", "pw", UserRole.MATE) + DummyData.users += User("U2", "bob", "pw", UserRole.MATE) newAssignee = DummyData.users[1] + + // create one task assigned initially to alice task = Task( - id = "1", - projectId = "P1", - title = "Demo", - description = null, - stateId = "TODO", - assignedToUserId = DummyData.users[0].id, - createByUserId = DummyData.users[0].id + id = "1", + projectId = "P1", + title = "Demo", + description = null, + stateId = "TODO", + assignedToUserId = DummyData.users[0].id, + createByUserId = DummyData.users[0].id ) DummyData.tasks += task every { getAllTasks.invoke() } returns listOf(task) + printed.clear() } @Test fun `repository update is called with new assignee`() { every { reader.read() } returnsMany listOf("1", "2") every { - assignTaskUC.invoke( - task.id, - newAssignee.id - ) - }.returns(Result.success(task.copy(assignedToUserId = newAssignee.id))) + assignTaskUC.invoke(task.id, newAssignee.id) + } returns Result.success(task.copy(assignedToUserId = newAssignee.id)) AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() verify(exactly = 1) { assignTaskUC.invoke(task.id, newAssignee.id) } - assertThat(printed).contains("Assigned to ${newAssignee.userName}") + assertThat(printed.last()).contains("Assigned to ${newAssignee.userName}") } @Test @@ -86,7 +95,7 @@ class AssignTaskUITest { fun `error from use case is shown to the user`() { every { reader.read() } returnsMany listOf("1", "2") val boom = IllegalStateException("cant assign") - every { assignTaskUC.invoke(task.id, newAssignee.id) }.returns(Result.failure(boom)) + every { assignTaskUC.invoke(task.id, newAssignee.id) } returns Result.failure(boom) AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() @@ -107,7 +116,7 @@ class AssignTaskUITest { @Test fun `throws and shows InvalidAssigneeException`() { every { reader.read() } returnsMany listOf("1", "2") - every { assignTaskUC.invoke(task.id, newAssignee.id) }.throws(InvalidAssigneeException("nope")) + every { assignTaskUC.invoke(task.id, newAssignee.id) } throws InvalidAssigneeException("nope") AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() @@ -118,18 +127,11 @@ class AssignTaskUITest { @Test fun `on failure with null message shows default assignment failed`() { every { reader.read() } returnsMany listOf("1", "2") - - every { - assignTaskUC.invoke( - task.id, - newAssignee.id - ) - } returns Result.failure(RuntimeException("Assignment failed")) + every { assignTaskUC.invoke(task.id, newAssignee.id) } returns Result.failure(RuntimeException("Assignment failed")) AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() verify(exactly = 1) { assignTaskUC.invoke(task.id, newAssignee.id) } assertThat(printed.last()).isEqualTo("Assignment failed") } - } diff --git a/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt b/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt index 1ad1b58..d1fc1a6 100644 --- a/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt +++ b/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt @@ -1,15 +1,16 @@ -package presentation.task +package com.berlin.presentation.task import com.berlin.data.DummyData import com.berlin.domain.exception.InvalidTaskStateException import com.berlin.domain.exception.TaskNotFoundException import com.berlin.domain.model.State import com.berlin.domain.model.Task +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole import com.berlin.domain.usecase.task.ChangeTaskStateUseCase import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -import com.berlin.presentation.task.ChangeTaskStateUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -20,65 +21,74 @@ class ChangeTaskStateUITest { private lateinit var viewer: Viewer private lateinit var reader: Reader private lateinit var changeUC: ChangeTaskStateUseCase - private lateinit var ui: ChangeTaskStateUI private lateinit var getAllTasks: GetAllTasksUseCase + private lateinit var ui: ChangeTaskStateUI private val printed = mutableListOf() private lateinit var task: Task - private val alice = DummyData.users[0] + private lateinit var alice: User @BeforeEach fun setUp() { - // reset in-memory lists + // reset all in-memory data + DummyData.users.clear() DummyData.tasks.clear() DummyData.states.clear() + // add one user (needed for assignedBy / filter logic) + alice = User("U1", "alice", "pw", UserRole.MATE) + DummyData.users += alice + + // one task in TODO state S1 task = Task( - id = "T1", - projectId = "P1", - title = "Demo", - description = null, - stateId = "S1", - assignedToUserId = alice.id, - createByUserId = alice.id + id = "T1", + projectId = "P1", + title = "Demo", + description = null, + stateId = "S1", + assignedToUserId = alice.id, + createByUserId = alice.id ) - DummyData.tasks += task + + // two possible states for project P1 DummyData.states += State("S1", "TODO", "P1") DummyData.states += State("S2", "DONE", "P1") - viewer = mockk(relaxed = true) { - every { show(capture(printed)) } just Runs - } - reader = mockk() - changeUC = mockk() + // mocks + viewer = mockk(relaxed = true) { every { show(capture(printed)) } just Runs } + reader = mockk() + changeUC = mockk() getAllTasks = mockk() - ui = ChangeTaskStateUI(changeUC, getAllTasks, viewer, reader) + + // stub before UI instantiation every { getAllTasks.invoke() } returns listOf(task) + + ui = ChangeTaskStateUI(changeUC, getAllTasks, viewer, reader) printed.clear() } @Test fun `success moves to chosen state`() { + // pick task #1, then state #1 (TODO) every { reader.read() } returnsMany listOf("1", "1") every { changeUC.invoke("T1", "S1") } returns Result.success(task.copy(stateId = "S1")) ui.run() - assertThat(printed.last()).contains("Task T1 moved to TODO") verify { changeUC.invoke("T1", "S1") } + assertThat(printed.last()).contains("Task T1 moved to TODO") } @Test fun `no states defined prints message and returns`() { - // clear states so possible.isEmpty() triggers - DummyData.states.clear() - every { reader.read() } returns "1" + DummyData.states.clear() // trigger "no states" path + every { reader.read() } returns "1" // choose task ui.run() - assertThat(printed.last()).contains("No states defined for project P1") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("No states defined for project P1") } @Test @@ -87,8 +97,8 @@ class ChangeTaskStateUITest { ui.run() - assertThat(printed.last()).contains("Cancelled.") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("Cancelled.") } @Test @@ -97,8 +107,8 @@ class ChangeTaskStateUITest { ui.run() - assertThat(printed.last()).contains("Invalid selection") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("Invalid selection") } @Test @@ -107,8 +117,8 @@ class ChangeTaskStateUITest { ui.run() - assertThat(printed.last()).contains("Cancelled.") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("Cancelled.") } @Test @@ -117,8 +127,8 @@ class ChangeTaskStateUITest { ui.run() - assertThat(printed.last()).contains("Invalid selection") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("Invalid selection") } @Test diff --git a/src/test/kotlin/presentation/task/CreateTaskUITest.kt b/src/test/kotlin/presentation/task/CreateTaskUITest.kt index cb8ef92..dca8bd6 100644 --- a/src/test/kotlin/presentation/task/CreateTaskUITest.kt +++ b/src/test/kotlin/presentation/task/CreateTaskUITest.kt @@ -8,6 +8,7 @@ import com.berlin.domain.model.User import com.berlin.domain.usecase.task.CreateTaskUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import data.UserCache import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -21,6 +22,7 @@ class CreateTaskUITest { } private val reader: Reader = mockk() private val createUC: CreateTaskUseCase = mockk() + private val userCache: UserCache = mockk() private val currentUser: User = DummyData.users.first() private lateinit var ui: CreateTaskUI @@ -28,7 +30,9 @@ class CreateTaskUITest { fun reset() { DummyData.tasks.clear() printed.clear() - ui = CreateTaskUI(createUC, currentUser, viewer, reader) + // Stub the cache to return our test user + every { userCache.currentUser } returns currentUser + ui = CreateTaskUI(createUC, userCache, viewer, reader) } @Test @@ -79,9 +83,7 @@ class CreateTaskUITest { fun `failure from use case is printed`() { every { reader.read() } returnsMany listOf("1", "1", "1", "Bug fix", "") every { - createUC.invoke( - any(), any(), any(), any(), any(), any() - ) + createUC.invoke(any(), any(), any(), any(), any(), any()) } returns Result.failure(IllegalStateException("db down")) ui.run() @@ -121,31 +123,9 @@ class CreateTaskUITest { assertThat(printed.last().lowercase()).contains("invalid selection") } - @Test - fun `null description still creates task`() { - every { reader.read() } returnsMany listOf("1", "1", "1", "Doc title", null) - every { createUC.invoke(any(), any(), null, any(), any(), any()) } returns Result.success( - Task( - id = "T99", - projectId = "P1", - title = "Doc title", - description = null, - stateId = "S1", - assignedToUserId = DummyData.users[1].id, - createByUserId = currentUser.id - ) - ) - - ui.run() - - verify { createUC.invoke(any(), any(), null, any(), any(), any()) } - assertThat(printed.last()).contains("Task created: id=T99") - } - @Test fun `shows InvalidTaskTitle when use case throws that exception`() { every { reader.read() } returnsMany listOf("1", "1", "1", "BadTitle", "") - every { createUC.invoke(any(), any(), any(), any(), any(), any()) } throws InvalidTaskTitle("title ruled invalid") @@ -172,7 +152,6 @@ class CreateTaskUITest { @Test fun `use case throwing TaskAlreadyExistsException is caught and shows existed message`() { every { reader.read() } returnsMany listOf("1", "1", "1", "MyTitle", "") - every { createUC.invoke(any(), any(), any(), any(), any(), any()) } throws TaskAlreadyExistsException("already there") @@ -180,8 +159,6 @@ class CreateTaskUITest { ui.run() verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } - assertThat(printed.last()).contains("the task already existed") } - } diff --git a/src/test/kotlin/presentation/task/DeleteTaskUITest.kt b/src/test/kotlin/presentation/task/DeleteTaskUITest.kt index 1befe09..58db663 100644 --- a/src/test/kotlin/presentation/task/DeleteTaskUITest.kt +++ b/src/test/kotlin/presentation/task/DeleteTaskUITest.kt @@ -7,6 +7,7 @@ import com.berlin.domain.usecase.task.DeleteTaskUseCase import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import com.berlin.presentation.task.DeleteTaskUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -22,7 +23,6 @@ class DeleteTaskUITest { private val deleteUC: DeleteTaskUseCase = mockk() private val getAllTasks: GetAllTasksUseCase = mockk() - private lateinit var task: Task private lateinit var ui: DeleteTaskUI @@ -77,6 +77,7 @@ class DeleteTaskUITest { ui.run() verify(exactly = 0) { deleteUC.invoke(any()) } + assertThat(DummyData.tasks).contains(task) assertThat(printed.last()).contains("Cancelled.") } @@ -109,9 +110,10 @@ class DeleteTaskUITest { ui.run() + // task should not have been removed assertThat(DummyData.tasks).contains(task) + // and we show the invalid-id message assertThat(printed.last()).contains("invalid task id") verify(exactly = 1) { deleteUC.invoke(task.id) } } - } diff --git a/src/test/kotlin/presentation/task/UpdateTaskUITest.kt b/src/test/kotlin/presentation/task/UpdateTaskUITest.kt index 576910a..6b9354a 100644 --- a/src/test/kotlin/presentation/task/UpdateTaskUITest.kt +++ b/src/test/kotlin/presentation/task/UpdateTaskUITest.kt @@ -1,5 +1,4 @@ -package presentation.task - +package com.berlin.presentation.task import com.berlin.data.DummyData import com.berlin.domain.exception.InvalidTaskTitle @@ -11,7 +10,6 @@ import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.domain.usecase.task.UpdateTaskUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -import com.berlin.presentation.task.UpdateTaskUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -23,7 +21,6 @@ class UpdateTaskUITest { private lateinit var reader: Reader private lateinit var updateUC: UpdateTaskUseCase private lateinit var getAllTasks: GetAllTasksUseCase - private lateinit var ui: UpdateTaskUI private val printed = mutableListOf() @@ -70,10 +67,10 @@ class UpdateTaskUITest { @Test fun `success when title only changes`() { every { reader.read() } returnsMany listOf( - "1", - "NewTitle", - "", - "X" + "1", // choose task #1 + "NewTitle", // new title + "", // blank → keep old desc + "X" // cancel assignee → keep old ) every { updateUC.invoke("T1", title = "NewTitle", description = null, assignedToUserId = null) @@ -91,8 +88,8 @@ class UpdateTaskUITest { fun `success when description only changes`() { every { reader.read() } returnsMany listOf( "1", - "", - "NewDesc", + "", // keep title + "NewDesc", // new description "X" ) every { @@ -111,9 +108,9 @@ class UpdateTaskUITest { fun `success when assignee only changes`() { every { reader.read() } returnsMany listOf( "1", - "", - "", - "2" + "", // keep title + "", // keep desc + "2" // choose user #2 ) every { updateUC.invoke("T1", title = null, description = null, assignedToUserId = "U2") @@ -130,7 +127,10 @@ class UpdateTaskUITest { @Test fun `success when nothing changes`() { every { reader.read() } returnsMany listOf( - "1", "", "", "X" + "1", + "", + "", + "X" ) every { updateUC.invoke("T1", title = null, description = null, assignedToUserId = null) @@ -222,5 +222,4 @@ class UpdateTaskUITest { assertThat(printed.last()).contains("Task not founc") } - } From 3c2dadaa0267a42377256e47b347b451dbdcd982 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Fri, 9 May 2025 07:07:52 +0300 Subject: [PATCH 17/25] implement ui and test cases for get all states by project --- build.gradle.kts | 2 +- .../presentation/state/DeleteStateUi.kt | 1 + .../presentation/state/GetStateByIdUi.kt | 2 +- .../presentation/state/DeleteStateUiTest.kt | 148 +++++++++++++++--- .../state/GetAllStatesByProjectIdUiTest.kt | 123 +++++++++++++++ .../presentation/state/GetStateByIdUiTest.kt | 28 +++- 6 files changed, 275 insertions(+), 29 deletions(-) create mode 100644 src/test/kotlin/presentation/state/GetAllStatesByProjectIdUiTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index e230fa1..a94367b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -58,5 +58,5 @@ tasks.test { } kotlin { - jvmToolchain(23) + jvmToolchain(22) } \ No newline at end of file diff --git a/src/main/kotlin/presentation/state/DeleteStateUi.kt b/src/main/kotlin/presentation/state/DeleteStateUi.kt index e1de29c..aaa0888 100644 --- a/src/main/kotlin/presentation/state/DeleteStateUi.kt +++ b/src/main/kotlin/presentation/state/DeleteStateUi.kt @@ -23,6 +23,7 @@ class DeleteStateUi( override fun run() { try { + val state = choose( title = "States", elements = getAllStates(), diff --git a/src/main/kotlin/presentation/state/GetStateByIdUi.kt b/src/main/kotlin/presentation/state/GetStateByIdUi.kt index b1135f3..d5cdbce 100644 --- a/src/main/kotlin/presentation/state/GetStateByIdUi.kt +++ b/src/main/kotlin/presentation/state/GetStateByIdUi.kt @@ -22,7 +22,7 @@ class GetStateByIdUi( .onFailure { ex -> when (ex) { is StateNotFoundException -> - viewer.show("No task found with ID “$stateId”") + viewer.show("No state found with ID “$stateId”") else -> viewer.show(ex.message ?: "Lookup failed") diff --git a/src/test/kotlin/presentation/state/DeleteStateUiTest.kt b/src/test/kotlin/presentation/state/DeleteStateUiTest.kt index 7f81931..a96b9c6 100644 --- a/src/test/kotlin/presentation/state/DeleteStateUiTest.kt +++ b/src/test/kotlin/presentation/state/DeleteStateUiTest.kt @@ -1,26 +1,136 @@ -package presentation.state +package com.berlin.presentation.state +import com.berlin.data.DummyData +import com.berlin.domain.exception.InvalidStateIdException +import com.berlin.domain.model.State import com.berlin.domain.usecase.state.DeleteStateUseCase import com.berlin.domain.usecase.state.GetAllStatesUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -import com.berlin.presentation.state.DeleteStateUi - -import io.mockk.mockk +import com.google.common.truth.Truth.assertThat +import io.mockk.* import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +/* +1-InvalidStateIdException +2-InvalidSelectionException +3-InputCancelledException +4-InvalidStateIdException +5-InvalidStateException("can not delete state") + */ + +class DeleteStateUiTest { + + private val printed = mutableListOf() + private val viewer: Viewer = mockk(relaxed = true) { + every { show(capture(printed)) } just Runs + } + private val reader: Reader = mockk() + private val deleteState: DeleteStateUseCase = mockk() + private val getAllStates: GetAllStatesUseCase = mockk() + + private lateinit var state: State + private lateinit var ui: DeleteStateUi + + @BeforeEach + fun setUp() { + DummyData.states.clear() + printed.clear() + + state = State( "S1", "To Do","L3") + DummyData.states += state + + ui = DeleteStateUi(deleteState, getAllStates, viewer, reader) + } + + @Test + fun `deleteState should deletes state and prints confirmation when confirm`() { + //Given + every { getAllStates() } returns listOf(state) + every { reader.read() } returnsMany listOf("1", "y") + every { deleteState.deleteState(state.id) } returns Result.success("Deleted Successfully") + + //when + ui.run() + + //Then + verify(exactly = 1) { deleteState.deleteState(state.id) } + assertThat(DummyData.states).doesNotContain(state) + assertThat(printed.last()).contains("Deleted.") + } + + @Test + fun `run should return cancelled when user aborts deletion at confirmation`() { + //Given + every { getAllStates() } returns listOf(state) + every { reader.read() } returnsMany listOf("1", "n") + + //when + ui.run() + + //Then + verify(exactly = 0) { deleteState.deleteState(any()) } + assertThat(DummyData.states).contains(state) + assertThat(printed.last()).contains("Cancelled.") + } + + @Test + fun `run should show Cancelled when user cancels in chooser`() { + //Given + every { getAllStates() } returns listOf(state) + every { reader.read() } returns "X" + + //when + ui.run() + + //Then + verify(exactly = 0) { deleteState.deleteState(any()) } + assertThat(printed.last()).contains("Cancelled.") + } + + @Test + fun `delete state should return Deletion Failed when failed`() { + //Given + every { getAllStates() } returns listOf(state) + every { reader.read() } returnsMany listOf("1", "y") + every { deleteState.deleteState(state.id) } returns Result.failure(IllegalStateException("Deletion Failed")) + + //when + ui.run() + + //Then + verify(exactly = 1) { deleteState.deleteState(state.id) } + assertThat(DummyData.states).contains(state) + assertThat(printed.last()).contains("Deletion Failed") + } + + @Test + fun `run should show error message Invalid selection when invalid index selected`() { + //Given + every { getAllStates() } returns listOf(state) + every { reader.read() } returns "99" + + //when + ui.run() + + //Then + verify(exactly = 0) { deleteState.deleteState(any()) } + assertThat(printed.last()).contains("Invalid selection") + } + + @Test + fun `deleteState should throw InvalidStateIdExceptionwhen id is not valid`() { + //Given + every { getAllStates() } returns listOf(state) + every { reader.read() } returnsMany listOf("1", "y") + every { deleteState.deleteState(state.id) } throws InvalidStateIdException("State ID must not be empty or blank") + + //when + ui.run() -class DeleteStateUiTest{ - private lateinit var deleteStateUseCase: DeleteStateUseCase - private lateinit var getAllStates: GetAllStatesUseCase - private lateinit var deleteStateUi: DeleteStateUi - private val viewer: Viewer = mockk(relaxed = true) - private val reader: Reader = mockk(relaxed = true) - - @BeforeEach - fun setup() { - deleteStateUseCase = mockk(relaxed = true) - getAllStates = mockk(relaxed = true) - deleteStateUi = DeleteStateUi(deleteStateUseCase, getAllStates, viewer, reader) - } - - } \ No newline at end of file + //Then + assertThat(printed.last()).contains("invalid state id") + verify(exactly = 1) { deleteState.deleteState(state.id) } + } +} diff --git a/src/test/kotlin/presentation/state/GetAllStatesByProjectIdUiTest.kt b/src/test/kotlin/presentation/state/GetAllStatesByProjectIdUiTest.kt new file mode 100644 index 0000000..6a80c03 --- /dev/null +++ b/src/test/kotlin/presentation/state/GetAllStatesByProjectIdUiTest.kt @@ -0,0 +1,123 @@ +package com.berlin.presentation.state + +import com.berlin.data.DummyData +import com.berlin.domain.exception.InvalidProjectIdException +import com.berlin.domain.model.Project +import com.berlin.domain.model.State +import com.berlin.domain.usecase.state.GetAllStatesByProjectIdUseCase +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer +import com.google.common.truth.Truth.assertThat +import io.mockk.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class GetAllStatesByProjectIdUiTest { + + private val printed = mutableListOf() + private val viewer: Viewer = mockk(relaxed = true) { + every { show(capture(printed)) } just Runs + } + private val reader: Reader = mockk() + private lateinit var useCase: GetAllStatesByProjectIdUseCase + private lateinit var ui: GetAllStatesByProjectIdUi + + private val projectP1 = Project("P1", "Core", null, emptyList(), emptyList()) + private val stateTodo = State("S1", "TODO", "P1") + private val stateDone = State("S2", "DONE", "P1") + + @BeforeEach + fun setUp() { + DummyData.projects.clear() + DummyData.states.clear() + printed.clear() + + DummyData.projects += projectP1 + DummyData.states += listOf(stateTodo, stateDone) + + useCase = mockk() + ui = GetAllStatesByProjectIdUi(useCase, viewer, reader) + } + + @Test + fun `shows swimlane with states`() { + //Given + every { reader.read() } returns "1" + every { useCase.getAllStatesByProjectId("P1") } returns Result.success(listOf(stateTodo, stateDone)) + + //Given + ui.run() + + //Then + assertThat(printed).contains("\n=== States for project P1 ===") + assertThat(printed).contains("- S1: TODO") + assertThat(printed).contains("- S2: DONE") + } + + @Test + fun `shows (no states) when project has no states`() { + //Given + every { reader.read() } returns "1" + every { useCase.getAllStatesByProjectId("P1") } returns Result.success(emptyList()) + + //When + ui.run() + + //Then + assertThat(printed).contains(" (no states)") + } + + @Test + fun `cancelling input shows Cancelled`() { + //Given + every { reader.read() } returns "X" + + //When + ui.run() + + //Then + assertThat(printed.last()).contains("Cancelled.") + verify(exactly = 0) { useCase.getAllStatesByProjectId(any()) } + } + + @Test + fun `invalid selection shows error`() { + //Given + every { reader.read() } returns "99" + + //When + ui.run() + + //Then + assertThat(printed.last()).contains("Invalid selection") + verify(exactly = 0) { useCase.getAllStatesByProjectId(any()) } + } + + @Test + fun `on use case failure shows message`() { + //Given + every { reader.read() } returns "1" + every { useCase.getAllStatesByProjectId("P1") } returns Result.failure(RuntimeException("Failed to load")) + + //When + ui.run() + + //Then + assertThat(printed.last()).contains("Failed to load") + verify(exactly = 1) { useCase.getAllStatesByProjectId("P1") } + } + + @Test + fun `throws InvalidProjectIdException and shows invalid project id`() { + //Given + every { reader.read() } returns "1" + every { useCase.getAllStatesByProjectId("P1") } throws InvalidProjectIdException("invalid project id") + + //When + ui.run() + + //Then + assertThat(printed.last()).contains("invalid project id") + verify(exactly = 1) { useCase.getAllStatesByProjectId("P1") } + } +} diff --git a/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt b/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt index 5e76ac7..6e19211 100644 --- a/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt +++ b/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt @@ -1,14 +1,14 @@ package presentation.state + import com.berlin.domain.exception.StateNotFoundException import com.berlin.domain.model.State import com.berlin.domain.usecase.state.GetStateByIdUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer import com.berlin.presentation.state.GetStateByIdUi -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify +import com.google.common.truth.Truth.assertThat +import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -21,19 +21,23 @@ class GetStateByIdUiTest { private lateinit var reader: Reader private lateinit var getStateById: GetStateByIdUseCase private lateinit var ui: GetStateByIdUi + private val printed = mutableListOf() @BeforeEach fun setup() { - viewer = mockk(relaxed = true) + viewer = mockk(relaxed = true) { + every { show(capture(printed)) } just Runs + } reader = mockk() getStateById = mockk() ui = GetStateByIdUi(getStateById, viewer, reader) + printed.clear() } @Test fun `getStateById should return state when its id exists`() { //given - every { getStateById.getStateById("Q1") } returns Result.success(State("Q1","Menna","P5"),) + every { getStateById.getStateById("Q1") } returns Result.success(State("Q1", "Menna", "P5")) every { reader.read() } returns "Q1" //when ui.run() @@ -53,9 +57,17 @@ class GetStateByIdUiTest { ui.run() // Then - verify { viewer.show("Enter state ID: ") } - verify { getStateById.getStateById("Q999") } - verify { viewer.show("No task found with ID “Q999”") } + assertThat(printed.last()).isEqualTo("No state found with ID “Q999”") + } + + @Test + fun `lookup failed fallback when exception message null`() { + every { reader.read() } returns "T3" + every { getStateById.getStateById("T3") } returns Result.failure(IllegalStateException("Lookup failed")) + + ui.run() + + assertThat(printed.last()).contains("Lookup failed") } } \ No newline at end of file From a7b2ddb2774e74851340b1855cff05fdbb26ab0d Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Fri, 9 May 2025 07:55:37 +0300 Subject: [PATCH 18/25] improve state ui test cases readability --- .../usecase/state/CreateStateUseCaseTest.kt | 3 +- .../usecase/state/DeleteStateUseCaseTest.kt | 3 +- .../GetAllStatesByProjectIdUseCaseTest.kt | 3 +- .../usecase/state/GetStateByIdUseCaseTest.kt | 5 +- .../state/GetStateByTaskIdUseCaseTest.kt | 3 +- .../state/GetTasksByStateIdUseCaseTest.kt | 3 +- .../usecase/state/UpdateStateUseCaseTest.kt | 3 +- .../presentation/state/DeleteStateUiTest.kt | 8 +- .../state/GetAllStatesByProjectIdUiTest.kt | 222 +++++++++--------- .../presentation/state/GetStateByIdUiTest.kt | 24 +- .../presentation/state/UpdateStateUiTest.kt | 28 +-- 11 files changed, 150 insertions(+), 155 deletions(-) rename src/test/kotlin/{logic => domain}/usecase/state/CreateStateUseCaseTest.kt (96%) rename src/test/kotlin/{logic => domain}/usecase/state/DeleteStateUseCaseTest.kt (96%) rename src/test/kotlin/{logic => domain}/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt (95%) rename src/test/kotlin/{logic => domain}/usecase/state/GetStateByIdUseCaseTest.kt (90%) rename src/test/kotlin/{logic => domain}/usecase/state/GetStateByTaskIdUseCaseTest.kt (93%) rename src/test/kotlin/{logic => domain}/usecase/state/GetTasksByStateIdUseCaseTest.kt (96%) rename src/test/kotlin/{logic => domain}/usecase/state/UpdateStateUseCaseTest.kt (95%) diff --git a/src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/CreateStateUseCaseTest.kt similarity index 96% rename from src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/CreateStateUseCaseTest.kt index f244117..5736f3b 100644 --- a/src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/state/CreateStateUseCaseTest.kt @@ -1,7 +1,6 @@ -package com.berlin.logic.usecase.state +package com.berlin.domain.usecase.state import com.berlin.domain.helper.IdGeneratorImplementation -import com.berlin.domain.usecase.state.CreateStateUseCase import com.berlin.domain.model.State import com.berlin.domain.repository.StateRepository import com.google.common.truth.Truth.assertThat diff --git a/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/DeleteStateUseCaseTest.kt similarity index 96% rename from src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/DeleteStateUseCaseTest.kt index 7deecef..a67fade 100644 --- a/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/state/DeleteStateUseCaseTest.kt @@ -1,8 +1,7 @@ -package com.berlin.logic.usecase.state +package com.berlin.domain.usecase.state import com.berlin.domain.exception.StateNotFoundException import com.berlin.domain.repository.StateRepository -import com.berlin.domain.usecase.state.DeleteStateUseCase import com.google.common.truth.Truth.assertThat import io.mockk.every import io.mockk.mockk diff --git a/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt similarity index 95% rename from src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt index 67ab897..51049a2 100644 --- a/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt @@ -1,6 +1,5 @@ -package com.berlin.logic.usecase.state +package com.berlin.domain.usecase.state -import com.berlin.domain.usecase.state.GetAllStatesByProjectIdUseCase import com.berlin.domain.model.State import com.berlin.domain.repository.ProjectRepository import com.berlin.domain.repository.StateRepository diff --git a/src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/GetStateByIdUseCaseTest.kt similarity index 90% rename from src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/GetStateByIdUseCaseTest.kt index f9665e9..9f5d3ab 100644 --- a/src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/state/GetStateByIdUseCaseTest.kt @@ -1,7 +1,6 @@ -package com.berlin.logic.usecase.state +package com.berlin.domain.usecase.state import com.berlin.domain.exception.StateNotFoundException -import com.berlin.domain.usecase.state.GetStateByIdUseCase import com.berlin.domain.model.State import com.berlin.domain.repository.StateRepository import com.google.common.truth.Truth.assertThat @@ -11,8 +10,6 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource -import org.junit.platform.commons.function.Try.failure -import kotlin.Result.Companion.failure import kotlin.test.Test class GetStateByIdUseCaseTest { diff --git a/src/test/kotlin/logic/usecase/state/GetStateByTaskIdUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/GetStateByTaskIdUseCaseTest.kt similarity index 93% rename from src/test/kotlin/logic/usecase/state/GetStateByTaskIdUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/GetStateByTaskIdUseCaseTest.kt index 111ff85..b619bf7 100644 --- a/src/test/kotlin/logic/usecase/state/GetStateByTaskIdUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/state/GetStateByTaskIdUseCaseTest.kt @@ -1,6 +1,5 @@ -package com.berlin.logic.usecase.state +package com.berlin.domain.usecase.state -import com.berlin.domain.usecase.state.GetStateByTaskIdUseCase import com.berlin.domain.model.State import com.berlin.domain.repository.StateRepository import com.berlin.domain.repository.TaskRepository diff --git a/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/GetTasksByStateIdUseCaseTest.kt similarity index 96% rename from src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/GetTasksByStateIdUseCaseTest.kt index 507d763..c5ca912 100644 --- a/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/state/GetTasksByStateIdUseCaseTest.kt @@ -1,8 +1,7 @@ -package com.berlin.logic.usecase.state +package com.berlin.domain.usecase.state import com.berlin.domain.exception.InvalidStateIdException import com.berlin.domain.exception.StateNotFoundException -import com.berlin.domain.usecase.state.GetTasksByStateIdUseCase import com.berlin.domain.model.Task import com.berlin.domain.repository.StateRepository import com.google.common.truth.Truth.assertThat diff --git a/src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/UpdateStateUseCaseTest.kt similarity index 95% rename from src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/UpdateStateUseCaseTest.kt index 442944f..e27c0d6 100644 --- a/src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/state/UpdateStateUseCaseTest.kt @@ -1,6 +1,5 @@ -package com.berlin.logic.usecase.state +package com.berlin.domain.usecase.state -import com.berlin.domain.usecase.state.UpdateStateUseCase import com.berlin.domain.model.State import com.berlin.domain.repository.StateRepository import com.google.common.truth.Truth.assertThat diff --git a/src/test/kotlin/presentation/state/DeleteStateUiTest.kt b/src/test/kotlin/presentation/state/DeleteStateUiTest.kt index a96b9c6..6aca342 100644 --- a/src/test/kotlin/presentation/state/DeleteStateUiTest.kt +++ b/src/test/kotlin/presentation/state/DeleteStateUiTest.kt @@ -12,13 +12,7 @@ import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -/* -1-InvalidStateIdException -2-InvalidSelectionException -3-InputCancelledException -4-InvalidStateIdException -5-InvalidStateException("can not delete state") - */ + class DeleteStateUiTest { diff --git a/src/test/kotlin/presentation/state/GetAllStatesByProjectIdUiTest.kt b/src/test/kotlin/presentation/state/GetAllStatesByProjectIdUiTest.kt index 6a80c03..60ba5dc 100644 --- a/src/test/kotlin/presentation/state/GetAllStatesByProjectIdUiTest.kt +++ b/src/test/kotlin/presentation/state/GetAllStatesByProjectIdUiTest.kt @@ -14,110 +14,120 @@ import org.junit.jupiter.api.Test class GetAllStatesByProjectIdUiTest { - private val printed = mutableListOf() - private val viewer: Viewer = mockk(relaxed = true) { - every { show(capture(printed)) } just Runs - } - private val reader: Reader = mockk() - private lateinit var useCase: GetAllStatesByProjectIdUseCase - private lateinit var ui: GetAllStatesByProjectIdUi - - private val projectP1 = Project("P1", "Core", null, emptyList(), emptyList()) - private val stateTodo = State("S1", "TODO", "P1") - private val stateDone = State("S2", "DONE", "P1") - - @BeforeEach - fun setUp() { - DummyData.projects.clear() - DummyData.states.clear() - printed.clear() - - DummyData.projects += projectP1 - DummyData.states += listOf(stateTodo, stateDone) - - useCase = mockk() - ui = GetAllStatesByProjectIdUi(useCase, viewer, reader) - } - - @Test - fun `shows swimlane with states`() { - //Given - every { reader.read() } returns "1" - every { useCase.getAllStatesByProjectId("P1") } returns Result.success(listOf(stateTodo, stateDone)) - - //Given - ui.run() - - //Then - assertThat(printed).contains("\n=== States for project P1 ===") - assertThat(printed).contains("- S1: TODO") - assertThat(printed).contains("- S2: DONE") - } - - @Test - fun `shows (no states) when project has no states`() { - //Given - every { reader.read() } returns "1" - every { useCase.getAllStatesByProjectId("P1") } returns Result.success(emptyList()) - - //When - ui.run() - - //Then - assertThat(printed).contains(" (no states)") - } - - @Test - fun `cancelling input shows Cancelled`() { - //Given - every { reader.read() } returns "X" - - //When - ui.run() - - //Then - assertThat(printed.last()).contains("Cancelled.") - verify(exactly = 0) { useCase.getAllStatesByProjectId(any()) } - } - - @Test - fun `invalid selection shows error`() { - //Given - every { reader.read() } returns "99" - - //When - ui.run() - - //Then - assertThat(printed.last()).contains("Invalid selection") - verify(exactly = 0) { useCase.getAllStatesByProjectId(any()) } - } - - @Test - fun `on use case failure shows message`() { - //Given - every { reader.read() } returns "1" - every { useCase.getAllStatesByProjectId("P1") } returns Result.failure(RuntimeException("Failed to load")) - - //When - ui.run() - - //Then - assertThat(printed.last()).contains("Failed to load") - verify(exactly = 1) { useCase.getAllStatesByProjectId("P1") } - } - - @Test - fun `throws InvalidProjectIdException and shows invalid project id`() { - //Given - every { reader.read() } returns "1" - every { useCase.getAllStatesByProjectId("P1") } throws InvalidProjectIdException("invalid project id") - - //When - ui.run() - - //Then - assertThat(printed.last()).contains("invalid project id") - verify(exactly = 1) { useCase.getAllStatesByProjectId("P1") } - } + private val printed = mutableListOf() + private val viewer: Viewer = mockk(relaxed = true) { + every { show(capture(printed)) } just Runs + } + private val reader: Reader = mockk() + private lateinit var useCase: GetAllStatesByProjectIdUseCase + private lateinit var ui: GetAllStatesByProjectIdUi + + + @BeforeEach + fun setUp() { + DummyData.projects.clear() + DummyData.states.clear() + printed.clear() + + DummyData.projects += projectP1 + DummyData.states += listOf(stateTodo, stateDone) + + useCase = mockk() + ui = GetAllStatesByProjectIdUi(useCase, viewer, reader) + } + + @Test + fun `shows swimlane with states`() { + //Given + every { reader.read() } returns "1" + every { useCase.getAllStatesByProjectId(projectIdWithNoStates) } returns Result.success( + listOf( + stateTodo, + stateDone + ) + ) + + //Given + ui.run() + + //Then + assertThat(printed).contains("\n=== States for project P1 ===") + assertThat(printed).contains("- S1: TODO") + assertThat(printed).contains("- S2: DONE") + } + + @Test + fun `shows (no states) when project has no states`() { + //Given + every { reader.read() } returns "1" + every { useCase.getAllStatesByProjectId(projectIdWithNoStates) } returns Result.success(emptyList()) + + //When + ui.run() + + //Then + assertThat(printed).contains(" (no states)") + } + + @Test + fun `cancelling input shows Cancelled`() { + //Given + every { reader.read() } returns "X" + + //When + ui.run() + + //Then + assertThat(printed.last()).contains("Cancelled.") + verify(exactly = 0) { useCase.getAllStatesByProjectId(any()) } + } + + @Test + fun `invalid selection shows error`() { + //Given + every { reader.read() } returns "99" + + //When + ui.run() + + //Then + assertThat(printed.last()).contains("Invalid selection") + verify(exactly = 0) { useCase.getAllStatesByProjectId(any()) } + } + + @Test + fun `getAllStatesByProjectId shows message Failed to load when failure`() { + //Given + every { reader.read() } returns "1" + every { useCase.getAllStatesByProjectId(projectIdWithNoStates) } returns Result.failure(RuntimeException("Failed to load")) + + //When + ui.run() + + //Then + assertThat(printed.last()).contains("Failed to load") + verify(exactly = 1) { useCase.getAllStatesByProjectId("P1") } + } + + //TODO review exception thrown + @Test + fun `throws InvalidProjectIdException and shows invalid project id`() { + //Given + every { reader.read() } returns "1" + every { useCase.getAllStatesByProjectId(projectIdWithNoStates) } throws InvalidProjectIdException("invalid project id") + + //When + ui.run() + + //Then + assertThat(printed.last()).contains("invalid project id") + verify(exactly = 1) { useCase.getAllStatesByProjectId(projectIdWithNoStates) } + } + + private companion object { + val projectP1 = Project("P1", "Core", null, emptyList(), emptyList()) + val stateTodo = State("S1", "TODO", "P1") + val stateDone = State("S2", "DONE", "P1") + const val projectIdWithNoStates = "P1" + } } diff --git a/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt b/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt index 6e19211..84d2586 100644 --- a/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt +++ b/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt @@ -13,10 +13,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test class GetStateByIdUiTest { - /* - 1-id not exist so state not exist - 2-id exist - */ + private lateinit var viewer: Viewer private lateinit var reader: Reader private lateinit var getStateById: GetStateByIdUseCase @@ -34,10 +31,16 @@ class GetStateByIdUiTest { printed.clear() } + private companion object { + val stateExists = State("Q1", "Menna", "P5") + val stateIdNotExist = "Q999" + val idWhenExceptionNull = "T3" + } + @Test fun `getStateById should return state when its id exists`() { //given - every { getStateById.getStateById("Q1") } returns Result.success(State("Q1", "Menna", "P5")) + every { getStateById.getStateById("Q1") } returns Result.success(stateExists) every { reader.read() } returns "Q1" //when ui.run() @@ -50,8 +53,8 @@ class GetStateByIdUiTest { @Test fun `getStateById should show message when id doesn't exist`() { // Given - every { reader.read() } returns "Q999" - every { getStateById.getStateById("Q999") } returns Result.failure(StateNotFoundException("State with ID Q999 not found")) + every { reader.read() } returns stateIdNotExist + every { getStateById.getStateById(stateIdNotExist) } returns Result.failure(StateNotFoundException("State with ID Q999 not found")) // When ui.run() @@ -62,11 +65,14 @@ class GetStateByIdUiTest { @Test fun `lookup failed fallback when exception message null`() { - every { reader.read() } returns "T3" - every { getStateById.getStateById("T3") } returns Result.failure(IllegalStateException("Lookup failed")) + //given + every { reader.read() } returns idWhenExceptionNull + every { getStateById.getStateById(idWhenExceptionNull) } returns Result.failure(IllegalStateException("Lookup failed")) + //When ui.run() + //Then assertThat(printed.last()).contains("Lookup failed") } diff --git a/src/test/kotlin/presentation/state/UpdateStateUiTest.kt b/src/test/kotlin/presentation/state/UpdateStateUiTest.kt index 71ab33f..de588fc 100644 --- a/src/test/kotlin/presentation/state/UpdateStateUiTest.kt +++ b/src/test/kotlin/presentation/state/UpdateStateUiTest.kt @@ -28,23 +28,11 @@ class UpdateStateUiTest { updateStateUi = UpdateStateUi(updateState, getAllStates, viewer, reader) } - /* - 1-successfully updated-->ok - 2-update failed-->ok - 3- InvalidStateNameException-->"State Name must not be empty or blank"-->ok - */ - private companion object { - val state = State("Q1","Menna","P5") - val stateId= state.id - val successfullyStateNewName = "done" - val stateProjectId = "P5" - val emptyStateName = "" - } @Test fun `run should return successfully updated when every thing is correct`() { //Given - every { getAllStates() } returns listOf(State("Q1","Menna","P5")) + every { getAllStates() } returns states every { reader.read() } returnsMany listOf("1", "done") every { updateState.updateState(any(), any(), any()) @@ -58,9 +46,9 @@ class UpdateStateUiTest { verify { viewer.show(any()) } verify { updateState.updateState( - stateId, + state.id, successfullyStateNewName, - stateProjectId + state.projectId ) } verify { viewer.show("Updated Successfully") } @@ -69,7 +57,7 @@ class UpdateStateUiTest { @Test fun `run should return update failed when update fails`() { //Given - every { getAllStates() } returns listOf(State("Q1","Menna","P5"),) + every { getAllStates() } returns states every { reader.read() } returnsMany listOf("1", "done") every { updateState.updateState(any(), any(), any()) @@ -85,7 +73,7 @@ class UpdateStateUiTest { @Test fun `run should throw InvalidStateNameException when State Name is empty or blank`() { //Given - every { getAllStates() } returns listOf(State("Q1","Menna","P5")) + every { getAllStates() } returns listOf(State("Q1", "Menna", "P5")) every { reader.read() } returnsMany listOf("1", " ") every { updateState.updateState(any(), emptyStateName, any()) @@ -98,5 +86,11 @@ class UpdateStateUiTest { verify { viewer.show("State Name must not be empty or blank") } } + private companion object { + private val state = State("Q1", "Menna", "P5") + private val states = listOf(state) + private const val successfullyStateNewName = "done" + private const val emptyStateName = "" + } } \ No newline at end of file From febbc4169b3fd6daa32f18c6c8db783ba7061b10 Mon Sep 17 00:00:00 2001 From: ahmad Date: Fri, 9 May 2025 08:10:45 +0300 Subject: [PATCH 19/25] temp --- .../project/CreateProjectUseCaseTest.kt | 56 ++- .../project/DeleteProjectUseCaseTest.kt | 73 ---- .../project/GetAllProjectsUseCaseTest.kt | 48 --- .../project/GetProjectByIdUseCaseTest.kt | 59 --- .../project/UpdateProjectUseCaseTest.kt | 67 ---- .../usecase/state/CreateStateUseCaseTest.kt | 80 ---- .../usecase/state/DeleteStateUseCaseTest.kt | 73 ---- .../GetAllStatesByProjectIdUseCaseTest.kt | 77 ---- .../usecase/state/GetStateByIdUseCaseTest.kt | 60 --- .../state/GetStateByTaskIdUseCaseTest.kt | 50 --- .../state/GetTasksByStateIdUseCaseTest.kt | 79 ---- .../usecase/state/UpdateStateUseCaseTest.kt | 69 ---- .../kotlin/presentation/MainMenuUITest.kt | 332 ++++++++--------- .../audit/AuditByProjectUITest.kt | 6 +- .../presentation/audit/AuditByTaskUITest.kt | 8 +- .../project/CreateProjectUiTest.kt | 145 ++------ .../presentation/task/AssignTaskUITest.kt | 50 ++- .../task/ChangeTaskStateUITest.kt | 66 ++-- .../presentation/task/CreateTaskUITest.kt | 351 ++++++++++-------- .../presentation/task/DeleteTaskUITest.kt | 6 +- .../presentation/task/UpdateTaskUITest.kt | 29 +- 21 files changed, 501 insertions(+), 1283 deletions(-) diff --git a/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt index 5e51607..4ce8c3d 100644 --- a/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt @@ -1,12 +1,15 @@ package logic.usecase.project -import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator -import com.berlin.helper.projectHelper import com.berlin.domain.repository.ProjectRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase import com.berlin.domain.usecase.project.CreateProjectUseCase +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator +import com.berlin.helper.projectHelper import com.google.common.truth.Truth.assertThat +import data.UserCache import io.mockk.every import io.mockk.mockk +import io.mockk.verify import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest @@ -17,30 +20,34 @@ class CreateProjectUseCaseTest { private lateinit var createProjectUseCase: CreateProjectUseCase private val projectRepository: ProjectRepository = mockk(relaxed = true) - + private val addAuditLogUseCase: AddAuditLogUseCase = mockk(relaxed = true) + private val cashedUser: UserCache = mockk(relaxed = true) @BeforeEach fun setup() { val idGenerator: IdGenerator = mockk(relaxed = true) - createProjectUseCase = CreateProjectUseCase(projectRepository, idGenerator) + createProjectUseCase = CreateProjectUseCase(projectRepository, idGenerator, addAuditLogUseCase, cashedUser) } @Test - fun `createNewProject should return success when project created successfully`() { + fun `createNewProject should log audit when project is created successfully`() { // Given val validProject = projectHelper() every { projectRepository.createProject(any()) } returns Result.success("Creation Successfully") + every { cashedUser.currentUser.id } returns "user_123" // When val result = createProjectUseCase.createNewProject( - validProject.name, - validProject.description, - validProject.statesId, - validProject.tasksId + validProject.name, validProject.description, validProject.statesId, validProject.tasksId ) // Then assertThat(result).isEqualTo(Result.success("Creation Successfully")) + verify(exactly = 1) { + addAuditLogUseCase.addAuditLog( + createdByUserId = "user_123", auditAction = any(), entityType = any(), entityId = any() + ) + } } @Test @@ -48,35 +55,26 @@ class CreateProjectUseCaseTest { // Given val validProject = projectHelper() every { projectRepository.createProject(any()) } returns Result.failure(Exception()) - // When val result = createProjectUseCase.createNewProject( - validProject.name, - validProject.description, - validProject.statesId, - validProject.tasksId + validProject.name, validProject.description, validProject.statesId, validProject.tasksId ) - // Then - result.onFailure { exception -> - assertThat(exception.message).isEqualTo("Creation Failed") - } + result.onFailure { exception -> assertThat(exception.message).isEqualTo("Creation Failed") } } @ParameterizedTest @ValueSource(strings = ["", " ", "123"]) - fun `validateProjectName should throw exception when project name is invalid`( - invalidName: String - ) { - // When && Then - assertThrows { - createProjectUseCase.createNewProject( - invalidName, - null, - null, - null - ) + fun `validateProjectName should throw exception when project name is invalid`(invalidName: String) { + // When & Then + val exception = assertThrows { + createProjectUseCase.createNewProject(invalidName, null, null, null) } + assertThat(exception.message).isEqualTo("Project Name must not be empty or blank") + + // Ensure no repository or audit log calls were made + verify(exactly = 0) { projectRepository.createProject(any()) } + } } \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt index 0d41784..e69de29 100644 --- a/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt @@ -1,73 +0,0 @@ -package logic.usecase.project; - -import com.berlin.domain.repository.ProjectRepository -import com.berlin.domain.usecase.project.DeleteProjectUseCase -import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.Test - -class DeleteProjectUseCaseTest { - - private lateinit var deleteProjectUseCase: DeleteProjectUseCase - private val projectRepository: ProjectRepository = mockk(relaxed = true) - - @BeforeEach - fun setup() { - deleteProjectUseCase = DeleteProjectUseCase(projectRepository) - } - - @Test - fun `should return success when project deleted successfully`() { - // Given - every { projectRepository.deleteProject(any()) } returns Result.success("") - - // When - val result = deleteProjectUseCase.deleteProject("project_1") - - // Then - assertThat(result).isEqualTo(Result.success("Deleted Successfully")) - } - - @Test - fun `should return failure when project deletion fails`() { - // Given - every { projectRepository.deleteProject("P1") } returns Result.failure(Exception()) - - // When - val result = deleteProjectUseCase.deleteProject("P1") - - // Then - result.onFailure { exception -> - assertThat(exception.message).isEqualTo("Deletion Failed") - } - } - - @Test - fun `should throw exception when project id does not exists`() { - // Given - every { projectRepository.getProjectById(any()) } returns null - - // When - val result = deleteProjectUseCase.deleteProject("P2") - - // Then - result.onFailure { exception -> - assertThat(exception.message).isEqualTo("Project with ID P2 does not exist") - } - } - - @ParameterizedTest - @ValueSource(strings = ["", " ", "123"]) - fun `should throw exception when project ID is invalid`(projectId: String) { - // When && Then - assertThrows { - deleteProjectUseCase.deleteProject(projectId) - } - } - -} \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt index 24d855f..e69de29 100644 --- a/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt @@ -1,48 +0,0 @@ -package com.berlin.logic.usecase.project - -import com.berlin.helper.projectHelper -import com.berlin.domain.repository.ProjectRepository -import com.berlin.domain.usecase.project.GetAllProjectsUseCase -import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows - -class GetAllProjectsUseCaseTest { - - private lateinit var getAllProjectsUseCase: GetAllProjectsUseCase - private val projectRepository: ProjectRepository = mockk(relaxed = true) - - @BeforeEach - fun setup() { - getAllProjectsUseCase = GetAllProjectsUseCase(projectRepository) - } - - @Test - fun `should return list of projects when projects exist`() { - // Given - val expectedProjects = listOf( - projectHelper(), - projectHelper() - ) - every { projectRepository.getAllProjects() } returns expectedProjects - - // When - val result = getAllProjectsUseCase.getAllProjects() - - // Then - assertThat(result).isEqualTo(expectedProjects) - } - - @Test - fun `should throw exception when no projects are found`() { - // Given - every { projectRepository.getAllProjects() } returns null - - // When & Then - val exception = assertThrows { getAllProjectsUseCase.getAllProjects() } - assertThat(exception.message).isEqualTo("No projects found") - } -} diff --git a/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt index c1a0b02..e69de29 100644 --- a/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt @@ -1,59 +0,0 @@ -package logic.usecase.project; - -import com.berlin.helper.projectHelper -import com.berlin.domain.repository.ProjectRepository -import com.berlin.domain.usecase.project.GetProjectByIdUseCase -import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.Test - -class GetProjectByIdUseCaseTest { - - private lateinit var getProjectByIdUseCase: GetProjectByIdUseCase - private val projectRepository: ProjectRepository = mockk(relaxed = true) - - @BeforeEach - fun setup() { - getProjectByIdUseCase = GetProjectByIdUseCase(projectRepository) - } - - @Test - fun `should return project when valid project id exists`() { - // Given - val expectedProject = projectHelper() - every { projectRepository.getProjectById("P1") } returns expectedProject - - // When - val result = getProjectByIdUseCase.getProjectById("P1") - - // Then - assertThat(result).isEqualTo(expectedProject) - } - - @Test - fun `should throw exception when project id does not exist`() { - // Given - val input = "P2" - every { projectRepository.getProjectById(any()) } returns null - - // When - val exception = assertThrows { getProjectByIdUseCase.getProjectById("P2") } - - // Then - assertThat(exception.message).isEqualTo("Project with ID $input does not exist") - } - - @ParameterizedTest - @ValueSource(strings = ["", " ", "123"]) - fun `should throw exception when project id is invalid`(projectId: String) { - // When && Then - assertThrows { - getProjectByIdUseCase.getProjectById(projectId) - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt index 039c1f0..e69de29 100644 --- a/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt @@ -1,67 +0,0 @@ -package logic.usecase.project; - -import com.berlin.helper.projectHelper -import com.berlin.domain.repository.ProjectRepository -import com.berlin.domain.usecase.project.UpdateProjectUseCase -import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.Test - -class UpdateProjectUseCaseTest { - - private lateinit var updateProjectUseCase: UpdateProjectUseCase - private val projectRepository: ProjectRepository = mockk(relaxed = true) - - @BeforeEach - fun setup() { - updateProjectUseCase = UpdateProjectUseCase(projectRepository) - } - - @Test - fun `should return success when project update succeeds`() { - // Given - val project = projectHelper() - every { projectRepository.updateProject(project) } returns Result.success("Updated Successfully") - - // When - val result = updateProjectUseCase.updateProject(project) - - // Then - assertThat(result).isEqualTo(Result.success("Updated Successfully")) - } - - @Test - fun `should return failure when project update fails`() { - // Given - val project = projectHelper() - every { projectRepository.updateProject(project) } returns Result.failure(Exception()) - - // When - val result = updateProjectUseCase.updateProject(project) - - // Then - result.onFailure { exception -> - assertThat(exception.message).isEqualTo("Update Failed") - } - } - - @ParameterizedTest - @ValueSource(strings = ["", " ", "123"]) - fun `should throw exception when project ID is invalid`( - invalidName: String - ) { - // When && Then - assertThrows { - updateProjectUseCase.updateProject( - projectHelper( - name = invalidName - ) - ) - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt index 1c6564d..e69de29 100644 --- a/src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt @@ -1,80 +0,0 @@ -package com.berlin.logic.usecase.state - -import com.berlin.domain.usecase.state.CreateStateUseCase -import com.berlin.domain.model.State -import com.berlin.domain.repository.StateRepository -import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation -import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.Test - -class CreateStateUseCaseTest { - - private lateinit var createStateUseCase: CreateStateUseCase - private val stateRepository: StateRepository = mockk(relaxed = true) - - @BeforeEach - fun setup() { - val idGenerator: IdGeneratorImplementation = mockk(relaxed = true) - createStateUseCase = CreateStateUseCase( - stateRepository, - idGenerator - ) - } - - @Test - fun `createNewState should return success when state created successfully`() { - // Given - val validState = State(id = "S1", name = "TODO", projectId = "P1") - every { stateRepository.addState(any()) } returns Result.success("State created successfully") - every { stateRepository.getStateById(any()) } returns mockk() - - // When - val result = createStateUseCase.createNewState(validState.name, - validState.projectId) - - // Then - assertThat(result).isEqualTo( - Result.success("State created successfully") - ) - } - - @Test - fun `createNewState should return failure when state creation fails`() { - // Given - val validState = State(id = "S1", name = "S1", projectId = "1") - every { stateRepository.addState(any()) } returns Result.failure(Exception()) - every { stateRepository.getStateById(any()) } returns mockk() - - // When - val result = createStateUseCase.createNewState(validState.name, - validState.projectId) - - // Then - result.onFailure { exception -> - assertThat(exception.message).isEqualTo("Creation Failed") - } - } - - @ParameterizedTest - @ValueSource(strings = ["", " ", "123"]) - fun `validateStateName should throw exception when state name is invalid`( - invalidName: String, - ) { - // Given - val stateInput = State(id = "S1", name = invalidName, projectId = "P1") - - // When && Then - assertThrows { - createStateUseCase.createNewState( - stateInput.name, - stateInput.projectId - ) - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt index 17a248f..e69de29 100644 --- a/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt @@ -1,73 +0,0 @@ -package com.berlin.logic.usecase.state - -import com.berlin.domain.repository.StateRepository -import com.berlin.domain.usecase.state.DeleteStateUseCase -import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.Test - -class DeleteStateUseCaseTest { - private lateinit var deleteStateUseCase: DeleteStateUseCase - private val stateRepository: StateRepository = mockk(relaxed = true) - - @BeforeEach - fun setup() { - deleteStateUseCase = DeleteStateUseCase(stateRepository) - } - - @Test - fun `should return success when state is deleted successfully`() { - // Given - every { stateRepository.deleteState(any()) } returns Result.success("Deleted Successfully") - every { stateRepository.getStateById(any()) } returns mockk() - - // When - val result = deleteStateUseCase.deleteState("state_1") - - // Then - assertThat(result).isEqualTo(Result.success("Deleted Successfully")) - } - - @Test - fun `should return failure when state deletion fails`() { - // Given - every { stateRepository.deleteState(any()) } returns Result.failure(Exception("Deletion Failed")) - every { stateRepository.getStateById(any()) } returns mockk() - - // When - val result = deleteStateUseCase.deleteState("state_2") - - // Then - result.onFailure { exception -> - assertThat(exception.message).isEqualTo("Deletion Failed") - } - } - - @Test - fun `should throw exception when state does not exist`() { - // Given - every { stateRepository.getStateById(any()) } returns null - - // When - val result = deleteStateUseCase.deleteState("S2") - - // Then - result.onFailure { exception -> - assertThat(exception.message).isEqualTo("State with ID S2 does not exist") - } - } - - @ParameterizedTest - @ValueSource(strings = ["", " ", "123"]) - fun `should throw exception when state ID is invalid`(stateId: String) { - // When & Then - assertThrows { - deleteStateUseCase.deleteState(stateId) - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt index 012ea69..e69de29 100644 --- a/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt @@ -1,77 +0,0 @@ -package com.berlin.logic.usecase.state - -import com.berlin.domain.usecase.state.GetAllStatesByProjectIdUseCase -import com.berlin.domain.model.State -import com.berlin.domain.repository.ProjectRepository -import com.berlin.domain.repository.StateRepository -import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.Test - -class GetAllStatesByProjectIdUseCaseTest { - - private lateinit var getAllStatesByProjectIdUseCase: GetAllStatesByProjectIdUseCase - private val stateRepository: StateRepository = mockk(relaxed = true) - private val projectRepository: ProjectRepository = mockk(relaxed = true) - - @BeforeEach - fun setup() { - getAllStatesByProjectIdUseCase = GetAllStatesByProjectIdUseCase( - stateRepository, - projectRepository - ) - } - - @Test - fun `should return states when states are found for the project`() { - // Given - val expectedStates = listOf( - State(id = "S1", name = "Active", projectId = "P1"), - State(id = "S2", name = "Inactive", projectId = "P1") - ) - every { projectRepository.getProjectById("P1") } returns mockk() - every { stateRepository.getStatesByProjectId("P1") } returns expectedStates - - // When - val result = getAllStatesByProjectIdUseCase.getAllStatesByProjectId("P1") - - // Then - assertThat(result).isEqualTo(expectedStates) - } - - @Test - fun `should throw exception when no states are found for the project`() { - // Given - every { projectRepository.getProjectById("P1") } returns mockk() - every { stateRepository.getStatesByProjectId("P3") } returns null - - // When & Then - val exception = assertThrows { getAllStatesByProjectIdUseCase.getAllStatesByProjectId("P3") } - assertThat(exception.message).isEqualTo("No states found for project ID P3") - } - - @Test - fun `should throw exception when project ID does not exist`() { - // Given - every { projectRepository.getProjectById("P2") } returns null - - // When & Then - val exception = assertThrows { getAllStatesByProjectIdUseCase.getAllStatesByProjectId("P2") } - assertThat(exception.message).isEqualTo("Project with ID P2 does not exist") - } - - @ParameterizedTest - @ValueSource(strings = ["", " ", "123"]) - fun `should throw exception when project id is invalid`(projectId: String) { - // When && Then - assertThrows { - getAllStatesByProjectIdUseCase.getAllStatesByProjectId(projectId) - } - } - -} diff --git a/src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt index 522f573..e69de29 100644 --- a/src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt @@ -1,60 +0,0 @@ -package com.berlin.logic.usecase.state - -import com.berlin.domain.usecase.state.GetStateByIdUseCase -import com.berlin.domain.model.State -import com.berlin.domain.repository.StateRepository -import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.Test - -class GetStateByIdUseCaseTest { - - private lateinit var getStateByIdUseCase: GetStateByIdUseCase - private val stateRepository: StateRepository = mockk(relaxed = true) - - @BeforeEach - fun setup() { - getStateByIdUseCase = GetStateByIdUseCase(stateRepository) - } - - @Test - fun `should return state when valid state id exists`() { - // Given - val expectedState = State(id = "S1", name = "Active", projectId = "P1") - every { stateRepository.getStateById("S1") } returns expectedState - - // When - val result = getStateByIdUseCase.getStateById("S1") - - // Then - assertThat(result).isEqualTo(expectedState) - } - - @Test - fun `should throw exception when state id does not exist`() { - // Given - val input = "S2" - every { stateRepository.getStateById(any()) } returns null - - // When - val exception = assertThrows { getStateByIdUseCase.getStateById("S2") } - - // Then - assertThat(exception.message).isEqualTo("State with ID $input does not exist") - } - - @ParameterizedTest - @ValueSource(strings = ["", " ", "123"]) - fun `should throw exception when state id is invalid`(stateId: String) { - // When && Then - assertThrows { - getStateByIdUseCase.getStateById(stateId) - } - } - -} diff --git a/src/test/kotlin/logic/usecase/state/GetStateByTaskIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/GetStateByTaskIdUseCaseTest.kt index 111ff85..e69de29 100644 --- a/src/test/kotlin/logic/usecase/state/GetStateByTaskIdUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/GetStateByTaskIdUseCaseTest.kt @@ -1,50 +0,0 @@ -package com.berlin.logic.usecase.state - -import com.berlin.domain.usecase.state.GetStateByTaskIdUseCase -import com.berlin.domain.model.State -import com.berlin.domain.repository.StateRepository -import com.berlin.domain.repository.TaskRepository -import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.Test - -class GetStateByTaskIdUseCaseTest { - - private lateinit var getStateByTaskIdUseCase: GetStateByTaskIdUseCase - private val stateRepository: StateRepository = mockk(relaxed = true) - private val taskRepository: TaskRepository = mockk(relaxed = true) - - @BeforeEach - fun setup() { - getStateByTaskIdUseCase = GetStateByTaskIdUseCase(stateRepository, taskRepository) - } - - @Test - fun `should return state when task id exists`() { - // Given - val expectedState = State(id = "S1", name = "Active", projectId = "P1") - every { taskRepository.findById("T1") } returns mockk() - every { stateRepository.getStateByTaskId("T1") } returns expectedState - - // When - val result = getStateByTaskIdUseCase.getStateByTaskId("T1") - - // Then - assertThat(result).isEqualTo(expectedState) - } - - @ParameterizedTest - @ValueSource(strings = ["", " ", "123"]) - fun `should throw exception when state id is invalid`(taskId: String) { - // When && Then - assertThrows { - getStateByTaskIdUseCase.getStateByTaskId(taskId) - } - } - -} diff --git a/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt index 5a1acc6..e69de29 100644 --- a/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt @@ -1,79 +0,0 @@ -package com.berlin.logic.usecase.state - -import com.berlin.domain.usecase.state.GetTasksByStateIdUseCase -import com.berlin.domain.model.Task -import com.berlin.domain.repository.StateRepository -import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.Test - -class GetTasksByStateIdUseCaseTest { - - private lateinit var getTasksByStateIdUseCase: GetTasksByStateIdUseCase - private val stateRepository: StateRepository = mockk(relaxed = true) - - private val task = Task( - id = "T1", - projectId = "P1", - title = "Demo", - description = null, - stateId = "S5", - assignedToUserId = "1", - createByUserId = "2", - ) - - @BeforeEach - fun setup() { - getTasksByStateIdUseCase = GetTasksByStateIdUseCase(stateRepository) - } - - @Test - fun `should return tasks when tasks are found for the state`() { - // Given - val expectedTasks = listOf(task) - every { stateRepository.getTasksByStateId("S1") } returns expectedTasks - every { stateRepository.getStateById("S1") } returns mockk() - - // When - val result = getTasksByStateIdUseCase.getAllTasksByStateId("S1") - - // Then - assertThat(result).isEqualTo(expectedTasks) - } - - @Test - fun `should throw exception when no tasks are found for the state`() { - // Given - every { stateRepository.getTasksByStateId("S2") } returns null - every { stateRepository.getStateById("S2") } returns mockk() - - // When & Then - val exception = assertThrows { getTasksByStateIdUseCase.getAllTasksByStateId("S2") } - assertThat(exception.message).isEqualTo("No tasks found for state ID S2") - } - - @Test - fun `should throw exception when state id does not exist`() { - // Given - every { stateRepository.getStateById("S2") } returns null - - // When & Then - val exception = assertThrows { getTasksByStateIdUseCase.getAllTasksByStateId("S2") } - assertThat(exception.message).isEqualTo("State with ID S2 does not exist") - } - - @ParameterizedTest - @ValueSource(strings = ["", " ", "123"]) - fun `should throw exception when state id is invalid`(stateId: String) { - // When && Then - assertThrows { - getTasksByStateIdUseCase.getAllTasksByStateId(stateId) - } - } - -} \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt b/src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt index 3a1d9a6..e69de29 100644 --- a/src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt @@ -1,69 +0,0 @@ -package com.berlin.logic.usecase.state - -import com.berlin.domain.usecase.state.UpdateStateUseCase -import com.berlin.domain.model.State -import com.berlin.domain.repository.StateRepository -import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import kotlin.test.Test - -class UpdateStateUseCaseTest { - - private lateinit var updateStateUseCase: UpdateStateUseCase - private val stateRepository: StateRepository = mockk(relaxed = true) - - @BeforeEach - fun setup() { - updateStateUseCase = UpdateStateUseCase(stateRepository) - } - - @Test - fun `should return success when state update succeeds`() { - // Given - val state = State(id = "S1", name = "Active", projectId = "P1") - every { stateRepository.updateState(state) } returns Result.success("Updated Successfully") - - // When - val result = updateStateUseCase.updateState(state) - - // Then - assertThat(result).isEqualTo(Result.success("Updated Successfully")) - } - - @Test - fun `should return failure when state update fails`() { - // Given - val state = State(id = "S2", name = "Inactive", projectId = "P2") - every { stateRepository.updateState(state) } returns Result.failure(Exception()) - - // When - val result = updateStateUseCase.updateState(state) - - // Then - result.onFailure { exception -> - assertThat(exception.message).isEqualTo("Update Failed") - } - } - - @ParameterizedTest - @ValueSource(strings = ["", " ","123"]) - fun `should throw exception when state ID is invalid`(stateName: String) { - // Given - val input = State( - "S1", - stateName, - "P1" - ) - - // When && Then - assertThrows { - updateStateUseCase.updateState(input) - } - } - -} \ No newline at end of file diff --git a/src/test/kotlin/presentation/MainMenuUITest.kt b/src/test/kotlin/presentation/MainMenuUITest.kt index 1f7af68..8dbd697 100644 --- a/src/test/kotlin/presentation/MainMenuUITest.kt +++ b/src/test/kotlin/presentation/MainMenuUITest.kt @@ -1,166 +1,166 @@ -package presentation - -import com.berlin.domain.model.User -import com.berlin.domain.model.UserRole -import com.berlin.presentation.MainMenuUI -import com.berlin.presentation.UiRunner -import com.berlin.presentation.authService.AuthenticateUserUi -import com.berlin.presentation.io.Reader -import com.berlin.presentation.io.Viewer -import com.google.common.truth.Truth.assertThat -import data.UserCache -import io.mockk.* -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.fail - -class MainMenuUITest { - - private lateinit var viewer: Viewer - private lateinit var reader: Reader - private lateinit var menu: MainMenuUI - private lateinit var authUi: AuthenticateUserUi - private lateinit var userCache: UserCache - - private val printed = mutableListOf() - - @BeforeEach - fun setUp() { - viewer = mockk(relaxed = true) { - every { show(capture(printed)) } just Runs - } - reader = mockk() - authUi = mockk() - userCache = UserCache() - userCache.currentUser = User("Y1", "menna", "12345678", UserRole.ADMIN) - } - - - @Test - fun `run should return user role when successfully logged in`() { - //given - every { viewer.show("") } - every { authUi.run() } just Runs - every { viewer.show("") } - every { reader.read() } returns "X" - menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) - - //when - menu.run() - - //Then - assert(printed.contains("===ADMIN Board===")) - } - - @Test - fun `run should exit immediately on blank input without running any runner `() { - //Given - every { viewer.show("") } - every { authUi.run() } just Runs - every { viewer.show("") } - every { reader.read() } returns "X" - menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) - - //when - menu.run() - - //Then - assert(printed.first().contains("===Welcome to our PlanMate===")) - - - } - - @Test - fun `runs matching runner then exits on X`() { - every { viewer.show("") } - every { authUi.run() } just Runs - every { viewer.show("") } - val r0 = object : UiRunner { - override val id = 0 - override val label = "zero" - var invoked = 0 - override fun run() { - invoked++ - } - } - val r1 = object : UiRunner { - override val id = 1 - override val label = "one" - var invoked = 0 - override fun run() { - invoked++ - } - } - - every { reader.read() } returnsMany listOf("1", "X") - menu = MainMenuUI(listOf(r0, r1), viewer, reader, authUi, userCache) - - menu.run() - - assert(r1.invoked == 1) - assert(r0.invoked == 0) - - } - - @Test - fun `invalid choice prints error then exits`() { - every { viewer.show("") } - every { authUi.run() } just Runs - every { viewer.show("") } - val dummy = object : UiRunner { - override val id = 5 - override val label = "five" - override fun run() = fail("should not run") - } - - every { reader.read() } returnsMany listOf("99", "") - menu = MainMenuUI(listOf(dummy), viewer, reader, authUi, userCache) - - menu.run() - - assert(printed.any { it.contains("Invalid choice: 99") }) - } - - @Test - fun `trimmed lowercase x also exits`() { - every { viewer.show("") } - every { authUi.run() } just Runs - every { viewer.show("") } - every { reader.read() } returnsMany listOf(" x ") - menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) - - menu.run() - - assert(printed.size >= 1) - } - - @Test - fun `exit on null input`() { - every { viewer.show("") } - every { authUi.run() } just Runs - every { viewer.show("") } - every { reader.read() } returns null - - val dummy = object : UiRunner { - override val id = 1 - override val label = "one" - var ran = false - override fun run() { - ran = true - } - } - menu = MainMenuUI(listOf(dummy), viewer, reader, authUi, userCache) - - menu.run() - - assertThat(dummy.ran).isFalse() } - - @Test - fun `menu has correct id and label`() { - every { reader.read() } returns "" - menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) - - assertThat(menu.id).isEqualTo(0) - assertThat(menu.label).isEqualTo("Main menu") - } -} +//package presentation +// +//import com.berlin.domain.model.User +//import com.berlin.domain.model.UserRole +//import com.berlin.presentation.MainMenuUI +//import com.berlin.presentation.UiRunner +//import com.berlin.presentation.authService.AuthenticateUserUi +//import com.berlin.presentation.io.Reader +//import com.berlin.presentation.io.Viewer +//import com.google.common.truth.Truth.assertThat +//import data.UserCache +//import io.mockk.* +//import org.junit.jupiter.api.BeforeEach +//import org.junit.jupiter.api.Test +//import org.junit.jupiter.api.fail +// +//class MainMenuUITest { +// +// private lateinit var viewer: Viewer +// private lateinit var reader: Reader +// private lateinit var menu: MainMenuUI +// private lateinit var authUi: AuthenticateUserUi +// private lateinit var userCache: UserCache +// +// private val printed = mutableListOf() +// +// @BeforeEach +// fun setUp() { +// viewer = mockk(relaxed = true) { +// every { show(capture(printed)) } just Runs +// } +// reader = mockk() +// authUi = mockk() +// userCache = UserCache() +// userCache.currentUser = User("Y1", "menna", "12345678", UserRole.ADMIN) +// } +// +// +// @Test +// fun `run should return user role when successfully logged in`() { +// //given +// every { viewer.show("") } +// every { authUi.run() } just Runs +// every { viewer.show("") } +// every { reader.read() } returns "X" +// menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) +// +// //when +// menu.run() +// +// //Then +// assert(printed.contains("===ADMIN Board===")) +// } +// +// @Test +// fun `run should exit immediately on blank input without running any runner `() { +// //Given +// every { viewer.show("") } +// every { authUi.run() } just Runs +// every { viewer.show("") } +// every { reader.read() } returns "X" +// menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) +// +// //when +// menu.run() +// +// //Then +// assert(printed.first().contains("===Welcome to our PlanMate===")) +// +// +// } +// +// @Test +// fun `runs matching runner then exits on X`() { +// every { viewer.show("") } +// every { authUi.run() } just Runs +// every { viewer.show("") } +// val r0 = object : UiRunner { +// override val id = 0 +// override val label = "zero" +// var invoked = 0 +// override fun run() { +// invoked++ +// } +// } +// val r1 = object : UiRunner { +// override val id = 1 +// override val label = "one" +// var invoked = 0 +// override fun run() { +// invoked++ +// } +// } +// +// every { reader.read() } returnsMany listOf("1", "X") +// menu = MainMenuUI(listOf(r0, r1), viewer, reader, authUi, userCache) +// +// menu.run() +// +// assert(r1.invoked == 1) +// assert(r0.invoked == 0) +// +// } +// +// @Test +// fun `invalid choice prints error then exits`() { +// every { viewer.show("") } +// every { authUi.run() } just Runs +// every { viewer.show("") } +// val dummy = object : UiRunner { +// override val id = 5 +// override val label = "five" +// override fun run() = fail("should not run") +// } +// +// every { reader.read() } returnsMany listOf("99", "") +// menu = MainMenuUI(listOf(dummy), viewer, reader, authUi, userCache) +// +// menu.run() +// +// assert(printed.any { it.contains("Invalid choice: 99") }) +// } +// +// @Test +// fun `trimmed lowercase x also exits`() { +// every { viewer.show("") } +// every { authUi.run() } just Runs +// every { viewer.show("") } +// every { reader.read() } returnsMany listOf(" x ") +// menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) +// +// menu.run() +// +// assert(printed.size >= 1) +// } +// +// @Test +// fun `exit on null input`() { +// every { viewer.show("") } +// every { authUi.run() } just Runs +// every { viewer.show("") } +// every { reader.read() } returns null +// +// val dummy = object : UiRunner { +// override val id = 1 +// override val label = "one" +// var ran = false +// override fun run() { +// ran = true +// } +// } +// menu = MainMenuUI(listOf(dummy), viewer, reader, authUi, userCache) +// +// menu.run() +// +// assertThat(dummy.ran).isFalse() } +// +// @Test +// fun `menu has correct id and label`() { +// every { reader.read() } returns "" +// menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) +// +// assertThat(menu.id).isEqualTo(0) +// assertThat(menu.label).isEqualTo("Main menu") +// } +//} diff --git a/src/test/kotlin/presentation/audit/AuditByProjectUITest.kt b/src/test/kotlin/presentation/audit/AuditByProjectUITest.kt index 6f4a044..646478f 100644 --- a/src/test/kotlin/presentation/audit/AuditByProjectUITest.kt +++ b/src/test/kotlin/presentation/audit/AuditByProjectUITest.kt @@ -6,6 +6,8 @@ import com.berlin.domain.model.AuditLog import com.berlin.domain.model.EntityType import com.berlin.domain.model.Project import com.berlin.domain.usecase.auditSystem.GetAuditLogsByProjectIdUseCase +import com.berlin.domain.usecase.project.GetAllProjectsUseCase +import com.berlin.domain.usecase.task.GetTasksByProjectUseCase import com.berlin.presentation.audit.AuditByProjectUI import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer @@ -18,6 +20,7 @@ import kotlin.test.Test class AuditByProjectUITest { private lateinit var getAuditLogsByProjectIdUseCase: GetAuditLogsByProjectIdUseCase + private lateinit var getAllProjectsUseCase: GetAllProjectsUseCase private lateinit var viewer: Viewer private lateinit var reader: Reader private lateinit var ui: AuditByProjectUI @@ -38,9 +41,10 @@ class AuditByProjectUITest { @BeforeEach fun setup() { getAuditLogsByProjectIdUseCase = mockk() + getAllProjectsUseCase = mockk() viewer = mockk(relaxed = true) reader = mockk() - ui = AuditByProjectUI(getAuditLogsByProjectIdUseCase, viewer, reader) + ui = AuditByProjectUI(getAuditLogsByProjectIdUseCase, getAllProjectsUseCase, viewer, reader) DummyData.projects.clear() DummyData.projects.add(sampleProject) diff --git a/src/test/kotlin/presentation/audit/AuditByTaskUITest.kt b/src/test/kotlin/presentation/audit/AuditByTaskUITest.kt index 1e9c482..5c6bf79 100644 --- a/src/test/kotlin/presentation/audit/AuditByTaskUITest.kt +++ b/src/test/kotlin/presentation/audit/AuditByTaskUITest.kt @@ -3,6 +3,8 @@ package presentation.audit import com.berlin.data.DummyData import com.berlin.domain.model.* import com.berlin.domain.usecase.auditSystem.GetAuditLogsByTaskIdUseCase +import com.berlin.domain.usecase.project.GetAllProjectsUseCase +import com.berlin.domain.usecase.task.GetTasksByProjectUseCase import com.berlin.presentation.audit.AuditByTaskUI import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer @@ -16,6 +18,8 @@ class AuditByTaskUITest { private val viewer = mockk(relaxed = true) private val reader = mockk() + private lateinit var getAllProjectsUseCase: GetAllProjectsUseCase + private lateinit var getTasksByProjectUseCase: GetTasksByProjectUseCase private val getAuditLogsByTaskIdUseCase = mockk() private lateinit var ui: AuditByTaskUI @@ -23,7 +27,9 @@ class AuditByTaskUITest { @BeforeEach fun setup() { - ui = AuditByTaskUI(viewer, reader, getAuditLogsByTaskIdUseCase) + getAllProjectsUseCase = mockk() + getTasksByProjectUseCase = mockk() + ui = AuditByTaskUI(viewer, reader, getAuditLogsByTaskIdUseCase, getTasksByProjectUseCase, getAllProjectsUseCase) DummyData.projects.clear() DummyData.projects.addAll( diff --git a/src/test/kotlin/presentation/project/CreateProjectUiTest.kt b/src/test/kotlin/presentation/project/CreateProjectUiTest.kt index 684a19d..580e0a5 100644 --- a/src/test/kotlin/presentation/project/CreateProjectUiTest.kt +++ b/src/test/kotlin/presentation/project/CreateProjectUiTest.kt @@ -1,14 +1,10 @@ -package presentation.project +package com.berlin.presentation.project -import com.berlin.domain.exception.InputCancelledException import com.berlin.domain.usecase.project.CreateProjectUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -import com.berlin.presentation.project.CreateProjectUi import io.mockk.* import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource import kotlin.test.Test class CreateProjectUiTest { @@ -20,9 +16,9 @@ class CreateProjectUiTest { @BeforeEach fun setup() { - createProjectUseCase = mockk() + createProjectUseCase = mockk(relaxed = true) viewer = mockk(relaxed = true) - reader = mockk() + reader = mockk(relaxed = true) ui = CreateProjectUi(createProjectUseCase, viewer, reader) } @@ -32,143 +28,82 @@ class CreateProjectUiTest { val name = "TestProject" val description = "Test Description" - every { reader.read() } returns name andThen description - every { - createProjectUseCase.createNewProject(name, description, null, null) - } returns Result.success("Creation Successfully") + every { reader.read() } returns name andThen "yes" andThen description + every { createProjectUseCase.createNewProject(name, description, + null, null) }returns Result.success("Creation Successfully") // When ui.run() // Then - verifySequence { - viewer.show("Enter project name:") - reader.read() - viewer.show("Enter project description (optional):") - reader.read() - createProjectUseCase.createNewProject(name, description, null, null) - viewer.show("Project created successfully") - } - } - - @Test - fun `run should show error when project name is empty`() { - // Given - every { reader.read() } returns " " - - // When - ui.run() - - // Then - verify { - viewer.show("Enter project name:") - reader.read() - viewer.show("Error: Project name cannot be empty") - } + verify { viewer.show("Creation Successfully") } } @Test fun `run should show failure message when project creation fails`() { // Given - val name = "ValidName" - val description = "Something" + val name = "ValidProject" + val description = "Test Description" - every { reader.read() } returns name andThen description - every { - createProjectUseCase.createNewProject(name, description, null, null) - } returns Result.failure(Exception("Failed")) + every { reader.read() } returns name andThen "yes" andThen description + every { createProjectUseCase.createNewProject(name, description, null, null) }returns Result.failure(Exception("Creation Failed")) // When ui.run() // Then verifySequence { - viewer.show("Enter project name:") + viewer.show("=== Create New Project ===\n") + viewer.show("================================================================\n\n") + viewer.show("Enter project details:\n") + viewer.show("Project Title:") reader.read() - viewer.show("Enter project description (optional):") + viewer.show("Do you want to write a description? (yes/no)") reader.read() - createProjectUseCase.createNewProject(name, description, null, null) - viewer.show("Failed") - } - } - - @Test - fun `run should show cancelled message when input is cancelled`() { - // Given - every { reader.read() } throws InputCancelledException("User cancelled") - - // When - ui.run() - - // Then - verify { - viewer.show("Enter project name:") - viewer.show("Project creation cancelled.") + viewer.show("Enter project description:") + reader.read() + viewer.show("Creating project...\n") + createProjectUseCase.createNewProject(name, description, + null, null) + viewer.show("Error: Creation Failed\n") } } @Test - fun `run should show default failure message when exception has no message`() { - // Given - val name = "ValidProject" - val description = "Something" - - every { reader.read() } returns name andThen description - every { - createProjectUseCase.createNewProject(name, description, null, null) - } returns Result.failure(Exception()) - - // When + fun `run should view to user again when input null project name`(){ + //given + every { reader.read() } returns null andThen "ggg" andThen "sfd" + every { createProjectUseCase.createNewProject(any(),any(),any(),any()) }returns Result.success("j") + //when ui.run() - - // Then - verifySequence { - viewer.show("Enter project name:") - reader.read() - viewer.show("Enter project description (optional):") - reader.read() - createProjectUseCase.createNewProject(name, description, null, null) - viewer.show("Creation failed") - } + //then + verify { viewer.show("Please enter a valid project name:") } } @Test - fun `run should show error when name is null and becomes empty`() { - // Given - every { reader.read() } returns null - - // When + fun `run should view to user again when input blank project name`(){ + //given + every { reader.read() } returns "" andThen "ggg" andThen "sfd" + every { createProjectUseCase.createNewProject(any(),any(),any(),any()) }returns Result.success("j") + //when ui.run() - - // Then - verify { - viewer.show("Enter project name:") - viewer.show("Error: Project name cannot be empty") - } + //then + verify { viewer.show("Please enter a valid project name:") } } @Test fun `run should create project when description is null`() { // Given val name = "MyProject" - val nullDescription: String? = null - every { reader.read() } returns name andThen nullDescription - every { - createProjectUseCase.createNewProject(name, null, null, null) - } returns Result.success("Creation Successfully") + every { reader.read() } returns name andThen "no" + every { createProjectUseCase.createNewProject(name, null, + null, null) }returns Result.success("Creation Successfully") // When ui.run() // Then - verifySequence { - viewer.show("Enter project name:") - reader.read() - viewer.show("Enter project description (optional):") - reader.read() - createProjectUseCase.createNewProject(name, null, null, null) - viewer.show("Project created successfully") - } + verify { viewer.show("Creation Successfully") } } } diff --git a/src/test/kotlin/presentation/task/AssignTaskUITest.kt b/src/test/kotlin/presentation/task/AssignTaskUITest.kt index 3000713..035baf3 100644 --- a/src/test/kotlin/presentation/task/AssignTaskUITest.kt +++ b/src/test/kotlin/presentation/task/AssignTaskUITest.kt @@ -2,16 +2,12 @@ package com.berlin.presentation.task import com.berlin.data.DummyData import com.berlin.domain.exception.InvalidAssigneeException -import com.berlin.domain.exception.InputCancelledException -import com.berlin.domain.exception.InvalidSelectionException import com.berlin.domain.model.Task import com.berlin.domain.model.User -import com.berlin.domain.model.UserRole import com.berlin.domain.usecase.task.AssignTaskUseCase import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -import com.berlin.presentation.task.AssignTaskUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -23,6 +19,7 @@ class AssignTaskUITest { private val viewer: Viewer = mockk(relaxed = true) { every { show(capture(printed)) } just Runs } + private val reader: Reader = mockk() private val assignTaskUC: AssignTaskUseCase = mockk() private val getAllTasks: GetAllTasksUseCase = mockk() @@ -32,43 +29,37 @@ class AssignTaskUITest { @BeforeEach fun setUp() { - // reset in-memory data DummyData.tasks.clear() - DummyData.users.clear() - - // populate two users so choose(...) can pick index "1" and "2" - DummyData.users += User("U1", "alice", "pw", UserRole.MATE) - DummyData.users += User("U2", "bob", "pw", UserRole.MATE) newAssignee = DummyData.users[1] - - // create one task assigned initially to alice task = Task( - id = "1", - projectId = "P1", - title = "Demo", - description = null, - stateId = "TODO", - assignedToUserId = DummyData.users[0].id, - createByUserId = DummyData.users[0].id + id = "1", + projectId = "P1", + title = "Demo", + description = null, + stateId = "TODO", + assignedToUserId = DummyData.users[0].id, + createByUserId = DummyData.users[0].id ) DummyData.tasks += task every { getAllTasks.invoke() } returns listOf(task) - printed.clear() } @Test fun `repository update is called with new assignee`() { every { reader.read() } returnsMany listOf("1", "2") every { - assignTaskUC.invoke(task.id, newAssignee.id) - } returns Result.success(task.copy(assignedToUserId = newAssignee.id)) + assignTaskUC.invoke( + task.id, + newAssignee.id + ) + }.returns(Result.success(task.copy(assignedToUserId = newAssignee.id))) AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() verify(exactly = 1) { assignTaskUC.invoke(task.id, newAssignee.id) } - assertThat(printed.last()).contains("Assigned to ${newAssignee.userName}") + assertThat(printed).contains("Assigned to ${newAssignee.userName}") } @Test @@ -95,7 +86,7 @@ class AssignTaskUITest { fun `error from use case is shown to the user`() { every { reader.read() } returnsMany listOf("1", "2") val boom = IllegalStateException("cant assign") - every { assignTaskUC.invoke(task.id, newAssignee.id) } returns Result.failure(boom) + every { assignTaskUC.invoke(task.id, newAssignee.id) }.returns(Result.failure(boom)) AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() @@ -116,7 +107,7 @@ class AssignTaskUITest { @Test fun `throws and shows InvalidAssigneeException`() { every { reader.read() } returnsMany listOf("1", "2") - every { assignTaskUC.invoke(task.id, newAssignee.id) } throws InvalidAssigneeException("nope") + every { assignTaskUC.invoke(task.id, newAssignee.id) }.throws(InvalidAssigneeException("nope")) AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() @@ -127,11 +118,18 @@ class AssignTaskUITest { @Test fun `on failure with null message shows default assignment failed`() { every { reader.read() } returnsMany listOf("1", "2") - every { assignTaskUC.invoke(task.id, newAssignee.id) } returns Result.failure(RuntimeException("Assignment failed")) + + every { + assignTaskUC.invoke( + task.id, + newAssignee.id + ) + } returns Result.failure(RuntimeException("Assignment failed")) AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() verify(exactly = 1) { assignTaskUC.invoke(task.id, newAssignee.id) } assertThat(printed.last()).isEqualTo("Assignment failed") } + } diff --git a/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt b/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt index d1fc1a6..1ad1b58 100644 --- a/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt +++ b/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt @@ -1,16 +1,15 @@ -package com.berlin.presentation.task +package presentation.task import com.berlin.data.DummyData import com.berlin.domain.exception.InvalidTaskStateException import com.berlin.domain.exception.TaskNotFoundException import com.berlin.domain.model.State import com.berlin.domain.model.Task -import com.berlin.domain.model.User -import com.berlin.domain.model.UserRole import com.berlin.domain.usecase.task.ChangeTaskStateUseCase import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import com.berlin.presentation.task.ChangeTaskStateUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -21,74 +20,65 @@ class ChangeTaskStateUITest { private lateinit var viewer: Viewer private lateinit var reader: Reader private lateinit var changeUC: ChangeTaskStateUseCase - private lateinit var getAllTasks: GetAllTasksUseCase private lateinit var ui: ChangeTaskStateUI + private lateinit var getAllTasks: GetAllTasksUseCase private val printed = mutableListOf() private lateinit var task: Task - private lateinit var alice: User + private val alice = DummyData.users[0] @BeforeEach fun setUp() { - // reset all in-memory data - DummyData.users.clear() + // reset in-memory lists DummyData.tasks.clear() DummyData.states.clear() - // add one user (needed for assignedBy / filter logic) - alice = User("U1", "alice", "pw", UserRole.MATE) - DummyData.users += alice - - // one task in TODO state S1 task = Task( - id = "T1", - projectId = "P1", - title = "Demo", - description = null, - stateId = "S1", - assignedToUserId = alice.id, - createByUserId = alice.id + id = "T1", + projectId = "P1", + title = "Demo", + description = null, + stateId = "S1", + assignedToUserId = alice.id, + createByUserId = alice.id ) - DummyData.tasks += task - // two possible states for project P1 + DummyData.tasks += task DummyData.states += State("S1", "TODO", "P1") DummyData.states += State("S2", "DONE", "P1") - // mocks - viewer = mockk(relaxed = true) { every { show(capture(printed)) } just Runs } - reader = mockk() - changeUC = mockk() + viewer = mockk(relaxed = true) { + every { show(capture(printed)) } just Runs + } + reader = mockk() + changeUC = mockk() getAllTasks = mockk() - - // stub before UI instantiation - every { getAllTasks.invoke() } returns listOf(task) - ui = ChangeTaskStateUI(changeUC, getAllTasks, viewer, reader) + every { getAllTasks.invoke() } returns listOf(task) printed.clear() } @Test fun `success moves to chosen state`() { - // pick task #1, then state #1 (TODO) every { reader.read() } returnsMany listOf("1", "1") every { changeUC.invoke("T1", "S1") } returns Result.success(task.copy(stateId = "S1")) ui.run() - verify { changeUC.invoke("T1", "S1") } assertThat(printed.last()).contains("Task T1 moved to TODO") + verify { changeUC.invoke("T1", "S1") } } @Test fun `no states defined prints message and returns`() { - DummyData.states.clear() // trigger "no states" path - every { reader.read() } returns "1" // choose task + // clear states so possible.isEmpty() triggers + DummyData.states.clear() + every { reader.read() } returns "1" ui.run() - verify { changeUC wasNot Called } assertThat(printed.last()).contains("No states defined for project P1") + verify { changeUC wasNot Called } } @Test @@ -97,8 +87,8 @@ class ChangeTaskStateUITest { ui.run() - verify { changeUC wasNot Called } assertThat(printed.last()).contains("Cancelled.") + verify { changeUC wasNot Called } } @Test @@ -107,8 +97,8 @@ class ChangeTaskStateUITest { ui.run() - verify { changeUC wasNot Called } assertThat(printed.last()).contains("Invalid selection") + verify { changeUC wasNot Called } } @Test @@ -117,8 +107,8 @@ class ChangeTaskStateUITest { ui.run() - verify { changeUC wasNot Called } assertThat(printed.last()).contains("Cancelled.") + verify { changeUC wasNot Called } } @Test @@ -127,8 +117,8 @@ class ChangeTaskStateUITest { ui.run() - verify { changeUC wasNot Called } assertThat(printed.last()).contains("Invalid selection") + verify { changeUC wasNot Called } } @Test diff --git a/src/test/kotlin/presentation/task/CreateTaskUITest.kt b/src/test/kotlin/presentation/task/CreateTaskUITest.kt index dca8bd6..4d3f5c6 100644 --- a/src/test/kotlin/presentation/task/CreateTaskUITest.kt +++ b/src/test/kotlin/presentation/task/CreateTaskUITest.kt @@ -1,164 +1,187 @@ -package com.berlin.presentation.task - -import com.berlin.data.DummyData -import com.berlin.domain.exception.InvalidTaskTitle -import com.berlin.domain.exception.TaskAlreadyExistsException -import com.berlin.domain.model.Task -import com.berlin.domain.model.User -import com.berlin.domain.usecase.task.CreateTaskUseCase -import com.berlin.presentation.io.Reader -import com.berlin.presentation.io.Viewer -import data.UserCache -import com.google.common.truth.Truth.assertThat -import io.mockk.* -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -class CreateTaskUITest { - - private val printed = mutableListOf() - private val viewer: Viewer = mockk(relaxed = true) { - every { show(capture(printed)) } just Runs - } - private val reader: Reader = mockk() - private val createUC: CreateTaskUseCase = mockk() - private val userCache: UserCache = mockk() - private val currentUser: User = DummyData.users.first() - private lateinit var ui: CreateTaskUI - - @BeforeEach - fun reset() { - DummyData.tasks.clear() - printed.clear() - // Stub the cache to return our test user - every { userCache.currentUser } returns currentUser - ui = CreateTaskUI(createUC, userCache, viewer, reader) - } - - @Test - fun `creates task and prints success`() { - every { reader.read() } returnsMany listOf("1", "1", "1", "Feature X", "") - every { createUC.invoke(any(), any(), any(), any(), any(), any()) } answers { - Result.success( - Task( - id = "T42", - projectId = firstArg(), - title = secondArg(), - description = arg(2), - stateId = arg(3), - assignedToUserId = arg(5), - createByUserId = arg(4) - ) - ) - } - - ui.run() - - verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } - assertThat(printed.last()).contains("Task created: id=T42") - } - - @Test - fun `prints Cancelled when user aborts`() { - every { reader.read() } returns "X" - - ui.run() - - verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } - assertThat(printed.last()).contains("Cancelled.") - } - - @Test - fun `empty title shows invalid selection message`() { - // project, state, user selected; then blank title - every { reader.read() } returnsMany listOf("1", "1", "1", "") - - ui.run() - - verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } - assertThat(printed.last()).contains("Invalid selection") - } - - @Test - fun `failure from use case is printed`() { - every { reader.read() } returnsMany listOf("1", "1", "1", "Bug fix", "") - every { - createUC.invoke(any(), any(), any(), any(), any(), any()) - } returns Result.failure(IllegalStateException("db down")) - - ui.run() - - verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } - assertThat(printed.last()).contains("db down") - } - - @Test - fun `invalid user index prints error message`() { - // project=1, state=1, then bad user index=99 - every { reader.read() } returnsMany listOf("1", "1", "99") - - ui.run() - - verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } - assertThat(printed.last().lowercase()).contains("invalid selection") - } - - @Test - fun `invalid state index prints error message`() { - every { reader.read() } returnsMany listOf("1", "57") - - ui.run() - - verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } - assertThat(printed.last().lowercase()).contains("invalid selection") - } - - @Test - fun `invalid project index prints error message`() { - every { reader.read() } returns "79" - - ui.run() - - verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } - assertThat(printed.last().lowercase()).contains("invalid selection") - } - - @Test - fun `shows InvalidTaskTitle when use case throws that exception`() { - every { reader.read() } returnsMany listOf("1", "1", "1", "BadTitle", "") - every { - createUC.invoke(any(), any(), any(), any(), any(), any()) - } throws InvalidTaskTitle("title ruled invalid") - - ui.run() - - assertThat(printed.last()).contains("Invalid task title") - verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } - } - - @Test - fun `shows TaskAlreadyExistsException when use case returns failure`() { - every { reader.read() } returnsMany listOf("1", "1", "1", "MyTask", "") - every { createUC.invoke(any(), any(), any(), any(), any(), any()) } returns Result.failure( - TaskAlreadyExistsException("the task already existed") - ) - - ui.run() - - assertThat(printed.last()).contains("the task already existed") - verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } - } - - @Test - fun `use case throwing TaskAlreadyExistsException is caught and shows existed message`() { - every { reader.read() } returnsMany listOf("1", "1", "1", "MyTitle", "") - every { - createUC.invoke(any(), any(), any(), any(), any(), any()) - } throws TaskAlreadyExistsException("already there") - - ui.run() - - verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } - assertThat(printed.last()).contains("the task already existed") - } -} +//package com.berlin.presentation.task +// +//import com.berlin.data.DummyData +//import com.berlin.domain.exception.InvalidTaskTitle +//import com.berlin.domain.exception.TaskAlreadyExistsException +//import com.berlin.domain.model.Task +//import com.berlin.domain.model.User +//import com.berlin.domain.usecase.task.CreateTaskUseCase +//import com.berlin.presentation.io.Reader +//import com.berlin.presentation.io.Viewer +//import com.google.common.truth.Truth.assertThat +//import io.mockk.* +//import org.junit.jupiter.api.BeforeEach +//import org.junit.jupiter.api.Test +// +//class CreateTaskUITest { +// +// private val printed = mutableListOf() +// private val viewer: Viewer = mockk(relaxed = true) { +// every { show(capture(printed)) } just Runs +// } +// private val reader: Reader = mockk() +// private val createUC: CreateTaskUseCase = mockk() +// private val currentUser: User = DummyData.users.first() +// private lateinit var ui: CreateTaskUI +// +// @BeforeEach +// fun reset() { +// DummyData.tasks.clear() +// printed.clear() +// ui = CreateTaskUI(createUC, currentUser, viewer, reader) +// } +// +// @Test +// fun `creates task and prints success`() { +// every { reader.read() } returnsMany listOf("1", "1", "1", "Feature X", "") +// every { createUC.invoke(any(), any(), any(), any(), any(), any()) } answers { +// Result.success( +// Task( +// id = "T42", +// projectId = firstArg(), +// title = secondArg(), +// description = arg(2), +// stateId = arg(3), +// assignedToUserId = arg(5), +// createByUserId = arg(4) +// ) +// ) +// } +// +// ui.run() +// +// verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } +// assertThat(printed.last()).contains("Task created: id=T42") +// } +// +// @Test +// fun `prints Cancelled when user aborts`() { +// every { reader.read() } returns "X" +// +// ui.run() +// +// verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } +// assertThat(printed.last()).contains("Cancelled.") +// } +// +// @Test +// fun `empty title shows invalid selection message`() { +// // project, state, user selected; then blank title +// every { reader.read() } returnsMany listOf("1", "1", "1", "") +// +// ui.run() +// +// verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } +// assertThat(printed.last()).contains("Invalid selection") +// } +// +// @Test +// fun `failure from use case is printed`() { +// every { reader.read() } returnsMany listOf("1", "1", "1", "Bug fix", "") +// every { +// createUC.invoke( +// any(), any(), any(), any(), any(), any() +// ) +// } returns Result.failure(IllegalStateException("db down")) +// +// ui.run() +// +// verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } +// assertThat(printed.last()).contains("db down") +// } +// +// @Test +// fun `invalid user index prints error message`() { +// // project=1, state=1, then bad user index=99 +// every { reader.read() } returnsMany listOf("1", "1", "99") +// +// ui.run() +// +// verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } +// assertThat(printed.last().lowercase()).contains("invalid selection") +// } +// +// @Test +// fun `invalid state index prints error message`() { +// every { reader.read() } returnsMany listOf("1", "57") +// +// ui.run() +// +// verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } +// assertThat(printed.last().lowercase()).contains("invalid selection") +// } +// +// @Test +// fun `invalid project index prints error message`() { +// every { reader.read() } returns "79" +// +// ui.run() +// +// verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } +// assertThat(printed.last().lowercase()).contains("invalid selection") +// } +// +// @Test +// fun `null description still creates task`() { +// every { reader.read() } returnsMany listOf("1", "1", "1", "Doc title", null) +// every { createUC.invoke(any(), any(), null, any(), any(), any()) } returns Result.success( +// Task( +// id = "T99", +// projectId = "P1", +// title = "Doc title", +// description = null, +// stateId = "S1", +// assignedToUserId = DummyData.users[1].id, +// createByUserId = currentUser.id +// ) +// ) +// +// ui.run() +// +// verify { createUC.invoke(any(), any(), null, any(), any(), any()) } +// assertThat(printed.last()).contains("Task created: id=T99") +// } +// +// @Test +// fun `shows InvalidTaskTitle when use case throws that exception`() { +// every { reader.read() } returnsMany listOf("1", "1", "1", "BadTitle", "") +// +// every { +// createUC.invoke(any(), any(), any(), any(), any(), any()) +// } throws InvalidTaskTitle("title ruled invalid") +// +// ui.run() +// +// assertThat(printed.last()).contains("Invalid task title") +// verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } +// } +// +// @Test +// fun `shows TaskAlreadyExistsException when use case returns failure`() { +// every { reader.read() } returnsMany listOf("1", "1", "1", "MyTask", "") +// every { createUC.invoke(any(), any(), any(), any(), any(), any()) } returns Result.failure( +// TaskAlreadyExistsException("the task already existed") +// ) +// +// ui.run() +// +// assertThat(printed.last()).contains("the task already existed") +// verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } +// } +// +// @Test +// fun `use case throwing TaskAlreadyExistsException is caught and shows existed message`() { +// every { reader.read() } returnsMany listOf("1", "1", "1", "MyTitle", "") +// +// every { +// createUC.invoke(any(), any(), any(), any(), any(), any()) +// } throws TaskAlreadyExistsException("already there") +// +// ui.run() +// +// verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } +// +// assertThat(printed.last()).contains("the task already existed") +// } +// +//} diff --git a/src/test/kotlin/presentation/task/DeleteTaskUITest.kt b/src/test/kotlin/presentation/task/DeleteTaskUITest.kt index 58db663..1befe09 100644 --- a/src/test/kotlin/presentation/task/DeleteTaskUITest.kt +++ b/src/test/kotlin/presentation/task/DeleteTaskUITest.kt @@ -7,7 +7,6 @@ import com.berlin.domain.usecase.task.DeleteTaskUseCase import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -import com.berlin.presentation.task.DeleteTaskUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -23,6 +22,7 @@ class DeleteTaskUITest { private val deleteUC: DeleteTaskUseCase = mockk() private val getAllTasks: GetAllTasksUseCase = mockk() + private lateinit var task: Task private lateinit var ui: DeleteTaskUI @@ -77,7 +77,6 @@ class DeleteTaskUITest { ui.run() verify(exactly = 0) { deleteUC.invoke(any()) } - assertThat(DummyData.tasks).contains(task) assertThat(printed.last()).contains("Cancelled.") } @@ -110,10 +109,9 @@ class DeleteTaskUITest { ui.run() - // task should not have been removed assertThat(DummyData.tasks).contains(task) - // and we show the invalid-id message assertThat(printed.last()).contains("invalid task id") verify(exactly = 1) { deleteUC.invoke(task.id) } } + } diff --git a/src/test/kotlin/presentation/task/UpdateTaskUITest.kt b/src/test/kotlin/presentation/task/UpdateTaskUITest.kt index 6b9354a..576910a 100644 --- a/src/test/kotlin/presentation/task/UpdateTaskUITest.kt +++ b/src/test/kotlin/presentation/task/UpdateTaskUITest.kt @@ -1,4 +1,5 @@ -package com.berlin.presentation.task +package presentation.task + import com.berlin.data.DummyData import com.berlin.domain.exception.InvalidTaskTitle @@ -10,6 +11,7 @@ import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.domain.usecase.task.UpdateTaskUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import com.berlin.presentation.task.UpdateTaskUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -21,6 +23,7 @@ class UpdateTaskUITest { private lateinit var reader: Reader private lateinit var updateUC: UpdateTaskUseCase private lateinit var getAllTasks: GetAllTasksUseCase + private lateinit var ui: UpdateTaskUI private val printed = mutableListOf() @@ -67,10 +70,10 @@ class UpdateTaskUITest { @Test fun `success when title only changes`() { every { reader.read() } returnsMany listOf( - "1", // choose task #1 - "NewTitle", // new title - "", // blank → keep old desc - "X" // cancel assignee → keep old + "1", + "NewTitle", + "", + "X" ) every { updateUC.invoke("T1", title = "NewTitle", description = null, assignedToUserId = null) @@ -88,8 +91,8 @@ class UpdateTaskUITest { fun `success when description only changes`() { every { reader.read() } returnsMany listOf( "1", - "", // keep title - "NewDesc", // new description + "", + "NewDesc", "X" ) every { @@ -108,9 +111,9 @@ class UpdateTaskUITest { fun `success when assignee only changes`() { every { reader.read() } returnsMany listOf( "1", - "", // keep title - "", // keep desc - "2" // choose user #2 + "", + "", + "2" ) every { updateUC.invoke("T1", title = null, description = null, assignedToUserId = "U2") @@ -127,10 +130,7 @@ class UpdateTaskUITest { @Test fun `success when nothing changes`() { every { reader.read() } returnsMany listOf( - "1", - "", - "", - "X" + "1", "", "", "X" ) every { updateUC.invoke("T1", title = null, description = null, assignedToUserId = null) @@ -222,4 +222,5 @@ class UpdateTaskUITest { assertThat(printed.last()).contains("Task not founc") } + } From 12511f1bd1ca68e071fb45f5aed4a7586a7191f8 Mon Sep 17 00:00:00 2001 From: ahmad Date: Fri, 9 May 2025 08:38:49 +0300 Subject: [PATCH 20/25] fix project uesecase test cases and project ui tests --- .../presentation/project/CreateProjectUi.kt | 83 ++++++++++++------ .../DefaultIdGeneratorTest.kt | 2 +- .../project/CreateProjectUseCaseTest.kt | 3 +- .../project/DeleteProjectUseCaseTest.kt | 85 +++++++++++++++++++ .../project/GetAllProjectsUseCaseTest.kt | 48 +++++++++++ .../project/GetProjectByIdUseCaseTest.kt | 59 +++++++++++++ .../project/UpdateProjectUseCaseTest.kt | 78 +++++++++++++++++ .../usecase/state/CreateStateUseCaseTest.kt | 0 .../usecase/state/DeleteStateUseCaseTest.kt | 0 .../GetAllStatesByProjectIdUseCaseTest.kt | 0 .../usecase/state/GetStateByIdUseCaseTest.kt | 0 .../state/GetStateByTaskIdUseCaseTest.kt | 0 .../state/GetTasksByStateIdUseCaseTest.kt | 0 .../usecase/state/UpdateStateUseCaseTest.kt | 0 .../project/DeleteProjectUseCaseTest.kt | 0 .../project/GetAllProjectsUseCaseTest.kt | 0 .../project/GetProjectByIdUseCaseTest.kt | 0 .../project/UpdateProjectUseCaseTest.kt | 0 .../project/CreateProjectUiTest.kt | 2 +- .../project/DeleteProjectUiTest.kt | 2 +- .../project/GetAllProjectsUiTest.kt | 2 +- .../project/GetProjectByIdUiTest.kt | 2 +- .../project/UpdateProjectUiTest.kt | 4 +- 23 files changed, 332 insertions(+), 38 deletions(-) rename src/test/kotlin/{logic => domain}/generateIdHelper/DefaultIdGeneratorTest.kt (97%) rename src/test/kotlin/{logic => domain}/usecase/project/CreateProjectUseCaseTest.kt (97%) create mode 100644 src/test/kotlin/domain/usecase/project/DeleteProjectUseCaseTest.kt create mode 100644 src/test/kotlin/domain/usecase/project/GetAllProjectsUseCaseTest.kt create mode 100644 src/test/kotlin/domain/usecase/project/GetProjectByIdUseCaseTest.kt create mode 100644 src/test/kotlin/domain/usecase/project/UpdateProjectUseCaseTest.kt rename src/test/kotlin/{logic => domain}/usecase/state/CreateStateUseCaseTest.kt (100%) rename src/test/kotlin/{logic => domain}/usecase/state/DeleteStateUseCaseTest.kt (100%) rename src/test/kotlin/{logic => domain}/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt (100%) rename src/test/kotlin/{logic => domain}/usecase/state/GetStateByIdUseCaseTest.kt (100%) rename src/test/kotlin/{logic => domain}/usecase/state/GetStateByTaskIdUseCaseTest.kt (100%) rename src/test/kotlin/{logic => domain}/usecase/state/GetTasksByStateIdUseCaseTest.kt (100%) rename src/test/kotlin/{logic => domain}/usecase/state/UpdateStateUseCaseTest.kt (100%) delete mode 100644 src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt delete mode 100644 src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt delete mode 100644 src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt delete mode 100644 src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt diff --git a/src/main/kotlin/presentation/project/CreateProjectUi.kt b/src/main/kotlin/presentation/project/CreateProjectUi.kt index b546e4f..6aac1b4 100644 --- a/src/main/kotlin/presentation/project/CreateProjectUi.kt +++ b/src/main/kotlin/presentation/project/CreateProjectUi.kt @@ -1,49 +1,76 @@ package com.berlin.presentation.project -import com.berlin.domain.exception.InputCancelledException -import com.berlin.domain.exception.InvalidSelectionException import com.berlin.domain.usecase.project.CreateProjectUseCase import com.berlin.presentation.UiRunner import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer class CreateProjectUi( - private val createProject: CreateProjectUseCase, + private val createProjectUseCase: CreateProjectUseCase, private val viewer: Viewer, private val reader: Reader ) : UiRunner { - - override val id: Int = 11 + override val id: Int = 1 override val label: String = "Create New Project" - override fun run() { - try { - val (name, description) = askProjectTitleAndDescription() - - createProject.createNewProject( - projectName = name, - description = description, - stateId = null, - taskId = null - ).onSuccess { viewer.show("Project created successfully") } - .onFailure { viewer.show(it.message ?: "Creation failed") } - - } catch (e: InputCancelledException) { - viewer.show("Project creation cancelled.") - } catch (e: Exception) { - viewer.show("Error: ${e.message}") - } + displayHeader() + + val projectName = getProjectName() + + val projectDescription = getProjectDescription() + + createProject(projectName, projectDescription) + } + + private fun displayHeader() { + viewer.show("=== Create New Project ===\n") + viewer.show("================================================================\n\n") + viewer.show("Enter project details:\n") } - private fun askProjectTitleAndDescription(): Pair { - viewer.show("Enter project name:") - val name = reader.read()?.trim().orEmpty() - if (name.isEmpty()) throw InvalidSelectionException("Project name cannot be empty") + private fun getProjectName(): String { + viewer.show("Project Title:") - viewer.show("Enter project description (optional):") - return name to reader.read()?.trim() + while (true) { + reader.read()?.let { input -> + if (input.isNotEmpty()) { + return input + } + } + viewer.show("Please enter a valid project name:") + } + } + + private fun getProjectDescription(): String? { + viewer.show("Do you want to write a description? (yes/no)") + val option = reader.read()?.lowercase() + return if (option == "yes") { + viewer.show("Enter project description:") + reader.read() + } else { + null + } } + private fun createProject(projectName: String, projectDescription: String?) { + viewer.show("Creating project...\n") + + val creationResult = createProjectUseCase.createNewProject( + projectName, + projectDescription, + null, + null + ) + + creationResult.fold( + onSuccess = { message -> + viewer.show(message) + }, + onFailure = { error -> + viewer.show("Error: ${error.message ?: "Project creation failed!"}\n") + } + ) + } } \ No newline at end of file diff --git a/src/test/kotlin/logic/generateIdHelper/DefaultIdGeneratorTest.kt b/src/test/kotlin/domain/generateIdHelper/DefaultIdGeneratorTest.kt similarity index 97% rename from src/test/kotlin/logic/generateIdHelper/DefaultIdGeneratorTest.kt rename to src/test/kotlin/domain/generateIdHelper/DefaultIdGeneratorTest.kt index 0ba9351..6340f19 100644 --- a/src/test/kotlin/logic/generateIdHelper/DefaultIdGeneratorTest.kt +++ b/src/test/kotlin/domain/generateIdHelper/DefaultIdGeneratorTest.kt @@ -1,4 +1,4 @@ -package com.berlin.logic.generateIdHelper +package com.berlin.domain.generateIdHelper import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.google.common.truth.Truth.assertThat diff --git a/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt b/src/test/kotlin/domain/usecase/project/CreateProjectUseCaseTest.kt similarity index 97% rename from src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt rename to src/test/kotlin/domain/usecase/project/CreateProjectUseCaseTest.kt index 4ce8c3d..57a2df1 100644 --- a/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/project/CreateProjectUseCaseTest.kt @@ -1,8 +1,7 @@ -package logic.usecase.project +package com.berlin.domain.usecase.project import com.berlin.domain.repository.ProjectRepository import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase -import com.berlin.domain.usecase.project.CreateProjectUseCase import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.helper.projectHelper import com.google.common.truth.Truth.assertThat diff --git a/src/test/kotlin/domain/usecase/project/DeleteProjectUseCaseTest.kt b/src/test/kotlin/domain/usecase/project/DeleteProjectUseCaseTest.kt new file mode 100644 index 0000000..e14edf2 --- /dev/null +++ b/src/test/kotlin/domain/usecase/project/DeleteProjectUseCaseTest.kt @@ -0,0 +1,85 @@ +package com.berlin.domain.usecase.project; + +import com.berlin.domain.repository.ProjectRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import com.google.common.truth.Truth.assertThat +import data.UserCache +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import kotlin.test.Test + +class DeleteProjectUseCaseTest { + + private lateinit var deleteProjectUseCase: DeleteProjectUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + private val addAuditLogUseCase: AddAuditLogUseCase = mockk(relaxed = true) + private val cashedUser: UserCache = mockk(relaxed = true) + + @BeforeEach + fun setup() { + deleteProjectUseCase = DeleteProjectUseCase(projectRepository, addAuditLogUseCase, + cashedUser) + } + + @Test + fun `should return success when project deleted successfully`() { + // Given + every { projectRepository.deleteProject(any()) } returns Result.success("") + every { cashedUser.currentUser.id } returns "user_123" + + // When + val result = deleteProjectUseCase.deleteProject("project_1") + + // Then + assertThat(result).isEqualTo(Result.success("Deleted Successfully")) + verify(exactly = 1) { addAuditLogUseCase.addAuditLog( + createdByUserId = "user_123", + auditAction = any(), + entityType = any(), + entityId = any() + ) } + } + + @Test + fun `should return failure when project deletion fails`() { + // Given + every { projectRepository.deleteProject("P1") } returns Result.failure(Exception()) + + // When + val result = deleteProjectUseCase.deleteProject("P1") + + // Then + result.onFailure { exception -> + assertThat(exception.message).isEqualTo("Deletion Failed") + } + } + + @Test + fun `should throw exception when project id does not exists`() { + // Given + every { projectRepository.getProjectById(any()) } returns null + + // When + val result = deleteProjectUseCase.deleteProject("P2") + + // Then + result.onFailure { exception -> + assertThat(exception.message).isEqualTo("Project with ID P2 does not exist") + } + } + + @ParameterizedTest + @ValueSource(strings = ["", " ", "123"]) + fun `should throw exception when project ID is invalid`(projectId: String) { + // When && Then + assertThrows { + deleteProjectUseCase.deleteProject(projectId) + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/domain/usecase/project/GetAllProjectsUseCaseTest.kt b/src/test/kotlin/domain/usecase/project/GetAllProjectsUseCaseTest.kt new file mode 100644 index 0000000..b4baec7 --- /dev/null +++ b/src/test/kotlin/domain/usecase/project/GetAllProjectsUseCaseTest.kt @@ -0,0 +1,48 @@ +package com.berlin.domain.usecase.project + +import com.berlin.helper.projectHelper +import com.berlin.domain.repository.ProjectRepository +import com.berlin.domain.usecase.project.GetAllProjectsUseCase +import com.google.common.truth.Truth.assertThat +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class GetAllProjectsUseCaseTest { + + private lateinit var getAllProjectsUseCase: GetAllProjectsUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + + @BeforeEach + fun setup() { + getAllProjectsUseCase = GetAllProjectsUseCase(projectRepository) + } + + @Test + fun `should return list of projects when projects exist`() { + // Given + val expectedProjects = listOf( + projectHelper(), + projectHelper() + ) + every { projectRepository.getAllProjects() } returns expectedProjects + + // When + val result = getAllProjectsUseCase.getAllProjects() + + // Then + assertThat(result).isEqualTo(expectedProjects) + } + + @Test + fun `should throw exception when no projects are found`() { + // Given + every { projectRepository.getAllProjects() } returns null + + // When & Then + val exception = assertThrows { getAllProjectsUseCase.getAllProjects() } + assertThat(exception.message).isEqualTo("No projects found") + } +} diff --git a/src/test/kotlin/domain/usecase/project/GetProjectByIdUseCaseTest.kt b/src/test/kotlin/domain/usecase/project/GetProjectByIdUseCaseTest.kt new file mode 100644 index 0000000..a132594 --- /dev/null +++ b/src/test/kotlin/domain/usecase/project/GetProjectByIdUseCaseTest.kt @@ -0,0 +1,59 @@ +package com.berlin.domain.usecase.project; + +import com.berlin.helper.projectHelper +import com.berlin.domain.repository.ProjectRepository +import com.berlin.domain.usecase.project.GetProjectByIdUseCase +import com.google.common.truth.Truth.assertThat +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import kotlin.test.Test + +class GetProjectByIdUseCaseTest { + + private lateinit var getProjectByIdUseCase: GetProjectByIdUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + + @BeforeEach + fun setup() { + getProjectByIdUseCase = GetProjectByIdUseCase(projectRepository) + } + + @Test + fun `should return project when valid project id exists`() { + // Given + val expectedProject = projectHelper() + every { projectRepository.getProjectById("P1") } returns expectedProject + + // When + val result = getProjectByIdUseCase.getProjectById("P1") + + // Then + assertThat(result).isEqualTo(expectedProject) + } + + @Test + fun `should throw exception when project id does not exist`() { + // Given + val input = "P2" + every { projectRepository.getProjectById(any()) } returns null + + // When + val exception = assertThrows { getProjectByIdUseCase.getProjectById("P2") } + + // Then + assertThat(exception.message).isEqualTo("Project with ID $input does not exist") + } + + @ParameterizedTest + @ValueSource(strings = ["", " ", "123"]) + fun `should throw exception when project id is invalid`(projectId: String) { + // When && Then + assertThrows { + getProjectByIdUseCase.getProjectById(projectId) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/domain/usecase/project/UpdateProjectUseCaseTest.kt b/src/test/kotlin/domain/usecase/project/UpdateProjectUseCaseTest.kt new file mode 100644 index 0000000..c60989b --- /dev/null +++ b/src/test/kotlin/domain/usecase/project/UpdateProjectUseCaseTest.kt @@ -0,0 +1,78 @@ +package com.berlin.domain.usecase.project; + +import com.berlin.helper.projectHelper +import com.berlin.domain.repository.ProjectRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import com.google.common.truth.Truth.assertThat +import data.UserCache +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import kotlin.test.Test + +class UpdateProjectUseCaseTest { + + private lateinit var updateProjectUseCase: UpdateProjectUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + private val addAuditLogUseCase: AddAuditLogUseCase = mockk(relaxed = true) + private val cashedUser: UserCache = mockk(relaxed = true) + + @BeforeEach + fun setup() { + updateProjectUseCase = UpdateProjectUseCase(projectRepository, + addAuditLogUseCase, cashedUser) + } + + @Test + fun `should return success when project update succeeds`() { + // Given + val project = projectHelper() + every { projectRepository.updateProject(project) } returns Result.success("Updated Successfully") + every { cashedUser.currentUser.id } returns "user_123" + + // When + val result = updateProjectUseCase.updateProject(project) + + // Then + assertThat(result).isEqualTo(Result.success("Updated Successfully")) + verify(exactly = 1) { + addAuditLogUseCase.addAuditLog( + createdByUserId = "user_123", auditAction = any(), entityType = any(), entityId = any() + ) + } + } + + @Test + fun `should return failure when project update fails`() { + // Given + val project = projectHelper() + every { projectRepository.updateProject(project) } returns Result.failure(Exception()) + + // When + val result = updateProjectUseCase.updateProject(project) + + // Then + result.onFailure { exception -> + assertThat(exception.message).isEqualTo("Update Failed") + } + } + + @ParameterizedTest + @ValueSource(strings = ["", " ", "123"]) + fun `should throw exception when project ID is invalid`( + invalidName: String + ) { + // When && Then + assertThrows { + updateProjectUseCase.updateProject( + projectHelper( + name = invalidName + ) + ) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/CreateStateUseCaseTest.kt similarity index 100% rename from src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/CreateStateUseCaseTest.kt diff --git a/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/DeleteStateUseCaseTest.kt similarity index 100% rename from src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/DeleteStateUseCaseTest.kt diff --git a/src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt similarity index 100% rename from src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt diff --git a/src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/GetStateByIdUseCaseTest.kt similarity index 100% rename from src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/GetStateByIdUseCaseTest.kt diff --git a/src/test/kotlin/logic/usecase/state/GetStateByTaskIdUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/GetStateByTaskIdUseCaseTest.kt similarity index 100% rename from src/test/kotlin/logic/usecase/state/GetStateByTaskIdUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/GetStateByTaskIdUseCaseTest.kt diff --git a/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/GetTasksByStateIdUseCaseTest.kt similarity index 100% rename from src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/GetTasksByStateIdUseCaseTest.kt diff --git a/src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/UpdateStateUseCaseTest.kt similarity index 100% rename from src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/UpdateStateUseCaseTest.kt diff --git a/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/kotlin/presentation/project/CreateProjectUiTest.kt b/src/test/kotlin/presentation/project/CreateProjectUiTest.kt index 580e0a5..5549aef 100644 --- a/src/test/kotlin/presentation/project/CreateProjectUiTest.kt +++ b/src/test/kotlin/presentation/project/CreateProjectUiTest.kt @@ -106,4 +106,4 @@ class CreateProjectUiTest { // Then verify { viewer.show("Creation Successfully") } } -} +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/project/DeleteProjectUiTest.kt b/src/test/kotlin/presentation/project/DeleteProjectUiTest.kt index 9548ea9..52bfe05 100644 --- a/src/test/kotlin/presentation/project/DeleteProjectUiTest.kt +++ b/src/test/kotlin/presentation/project/DeleteProjectUiTest.kt @@ -1,4 +1,4 @@ -package presentation.project +package com.berlin.presentation.project import com.berlin.domain.exception.InvalidProjectIdException import com.berlin.domain.model.Project diff --git a/src/test/kotlin/presentation/project/GetAllProjectsUiTest.kt b/src/test/kotlin/presentation/project/GetAllProjectsUiTest.kt index 4751863..a953629 100644 --- a/src/test/kotlin/presentation/project/GetAllProjectsUiTest.kt +++ b/src/test/kotlin/presentation/project/GetAllProjectsUiTest.kt @@ -1,4 +1,4 @@ -package presentation.project +package com.berlin.presentation.project import com.berlin.domain.usecase.project.GetAllProjectsUseCase import com.berlin.helper.projectHelper diff --git a/src/test/kotlin/presentation/project/GetProjectByIdUiTest.kt b/src/test/kotlin/presentation/project/GetProjectByIdUiTest.kt index 62669fd..c37fe4e 100644 --- a/src/test/kotlin/presentation/project/GetProjectByIdUiTest.kt +++ b/src/test/kotlin/presentation/project/GetProjectByIdUiTest.kt @@ -1,4 +1,4 @@ -package presentation.project +package com.berlin.presentation.project import com.berlin.domain.exception.InvalidProjectIdException import com.berlin.domain.exception.ProjectNotFoundException diff --git a/src/test/kotlin/presentation/project/UpdateProjectUiTest.kt b/src/test/kotlin/presentation/project/UpdateProjectUiTest.kt index 0046750..eefddc4 100644 --- a/src/test/kotlin/presentation/project/UpdateProjectUiTest.kt +++ b/src/test/kotlin/presentation/project/UpdateProjectUiTest.kt @@ -1,4 +1,4 @@ -package presentation.project +package com.berlin.presentation.project import com.berlin.domain.model.Project import com.berlin.domain.usecase.project.GetAllProjectsUseCase @@ -7,13 +7,11 @@ import com.berlin.domain.usecase.project.UpdateProjectUseCase import com.berlin.helper.projectHelper import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -import com.berlin.presentation.project.UpdateProjectUi import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.verify import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.assertThrows import kotlin.test.Test import kotlin.test.assertEquals From 0340e471e357836df74ef3de1bbeeb2c193b72b7 Mon Sep 17 00:00:00 2001 From: Abdulrahman Ragab Date: Fri, 9 May 2025 09:05:02 +0300 Subject: [PATCH 21/25] handle the exception thrown when cancelling create state --- src/main/kotlin/presentation/state/CreatestateUi.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/presentation/state/CreatestateUi.kt b/src/main/kotlin/presentation/state/CreatestateUi.kt index f583766..136cecc 100644 --- a/src/main/kotlin/presentation/state/CreatestateUi.kt +++ b/src/main/kotlin/presentation/state/CreatestateUi.kt @@ -1,6 +1,7 @@ package com.berlin.presentation.state import com.berlin.data.DummyData +import com.berlin.domain.exception.InputCancelledException import com.berlin.domain.model.Project import com.berlin.domain.usecase.state.CreateStateUseCase import com.berlin.presentation.UiRunner @@ -19,9 +20,14 @@ class CreateStateUi( override fun run() { - val project = selectProject() - viewer.show("-- Enter a state in project ${project.name} --") - addStateName(project) + try { + val project = selectProject() + viewer.show("-- Enter a state in project ${project.name} --") + addStateName(project) + } catch (_: InputCancelledException) { + viewer.show("Cancelled!") + } + } From 98e02bee62e7234f6cf281e58f8f86bccab2ff18 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Fri, 9 May 2025 09:13:20 +0300 Subject: [PATCH 22/25] solve cancellation exception in update state ui --- src/main/kotlin/presentation/state/updateStateUi.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/presentation/state/updateStateUi.kt b/src/main/kotlin/presentation/state/updateStateUi.kt index f3fd93d..8be0482 100644 --- a/src/main/kotlin/presentation/state/updateStateUi.kt +++ b/src/main/kotlin/presentation/state/updateStateUi.kt @@ -1,5 +1,6 @@ package com.berlin.presentation.state +import com.berlin.domain.exception.InputCancelledException import com.berlin.domain.exception.InvalidStateNameException import com.berlin.domain.usecase.state.GetAllStatesUseCase import com.berlin.domain.usecase.state.UpdateStateUseCase @@ -39,7 +40,9 @@ class UpdateStateUi( } catch (ex: InvalidStateNameException) { viewer.show("State Name must not be empty or blank") - } + } catch (_: InputCancelledException) { + viewer.show("Cancelled!") + } } } \ No newline at end of file From 8e297892b6711c8efd9aaa3d6f35663bd0d5fa5b Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Fri, 9 May 2025 09:19:25 +0300 Subject: [PATCH 23/25] merge state ui to this branch and resolve conflict --- src/main/kotlin/di/useCaseModule.kt | 3 +-- src/main/kotlin/presentation/MainMenuUI.kt | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/di/useCaseModule.kt b/src/main/kotlin/di/useCaseModule.kt index 47e4a75..01e6ed3 100644 --- a/src/main/kotlin/di/useCaseModule.kt +++ b/src/main/kotlin/di/useCaseModule.kt @@ -48,8 +48,7 @@ val useCaseModule = module { single { FetchAllUsersUseCase(get()) } single { AuthenticateUserUseCase(get(),get(), get()) } single { CreateMateUseCase(get(), get(), get()) } -} - + single { CreateStateUseCase(get(),get())} single { DeleteStateUseCase(get()) } single { GetAllStatesByProjectIdUseCase(get(),get()) } diff --git a/src/main/kotlin/presentation/MainMenuUI.kt b/src/main/kotlin/presentation/MainMenuUI.kt index 584f9ad..0524c1f 100644 --- a/src/main/kotlin/presentation/MainMenuUI.kt +++ b/src/main/kotlin/presentation/MainMenuUI.kt @@ -57,7 +57,7 @@ class MainMenuUI( private companion object { - val adminPermissionFilterIds = listOf(1, 2, 3, 4, 5, 6, 7, 30, 100, 300, 500, 900, 11, 12, 13, 14, 15, 24390823, 2349, 24234) + val adminPermissionFilterIds = listOf(1, 2, 3, 4, 5, 6, 7, 30,50, 100, 300, 500, 900, 11, 12, 13, 14, 15, 24390823,1000,2000,3000,200000, 2349, 24234) val matePermissionFilterIds = listOf(1, 2, 3, 4, 5, 6, 7) } } \ No newline at end of file From 527dbbbee31d4761026453ebfdfe29d586e402aa Mon Sep 17 00:00:00 2001 From: Marwan Qashwa Date: Fri, 9 May 2025 10:38:19 +0300 Subject: [PATCH 24/25] fix errors in audit log --- .../kotlin/presentation/audit/AuditByProjectUI.kt | 2 -- .../GetAuditLogsByTaskIdUseCaseTest.kt | 4 +--- .../usecase/state/CreateStateUseCaseTest.kt | 1 - .../presentation/audit/AuditByProjectUITest.kt | 8 +++++--- .../presentation/audit/AuditByTaskUITest.kt | 15 +++++++++++++-- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/presentation/audit/AuditByProjectUI.kt b/src/main/kotlin/presentation/audit/AuditByProjectUI.kt index 7b18aac..1f7d0aa 100644 --- a/src/main/kotlin/presentation/audit/AuditByProjectUI.kt +++ b/src/main/kotlin/presentation/audit/AuditByProjectUI.kt @@ -1,13 +1,11 @@ package com.berlin.presentation.audit -import com.berlin.data.DummyData import com.berlin.domain.exception.InputCancelledException import com.berlin.domain.exception.InvalidSelectionException import com.berlin.domain.model.AuditLog import com.berlin.domain.model.Project import com.berlin.domain.usecase.auditSystem.GetAuditLogsByProjectIdUseCase import com.berlin.domain.usecase.project.GetAllProjectsUseCase -import com.berlin.domain.usecase.task.GetTasksByProjectUseCase import com.berlin.presentation.UiRunner import com.berlin.presentation.helper.choose import com.berlin.presentation.io.Reader diff --git a/src/test/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCaseTest.kt b/src/test/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCaseTest.kt index 4c9a9bb..01973be 100644 --- a/src/test/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCaseTest.kt @@ -29,15 +29,13 @@ class GetAuditLogsByTaskIdUseCaseTest { val logs = listOf( generateAuditLog(id = "A3", entityId = taskId, entityType = EntityType.TASK) ) - every { auditRepository.getAuditLogsByProjectId(taskId) } returns logs + every { auditRepository.getAuditLogsByTaskId(taskId) } returns logs //When val result = getAuditLogsByTaskIdUseCase.getAuditLogsByTaskId(taskId) //That assertThat(result).isEqualTo(logs) - verify(exactly = 1) { auditRepository.getAuditLogsByProjectId(taskId) } - } diff --git a/src/test/kotlin/domain/usecase/state/CreateStateUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/CreateStateUseCaseTest.kt index 8868b06..558209f 100644 --- a/src/test/kotlin/domain/usecase/state/CreateStateUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/state/CreateStateUseCaseTest.kt @@ -1,7 +1,6 @@ package com.berlin.domain.usecase.state import com.berlin.domain.usecase.state.CreateStateUseCase -import com.berlin.domain.helper.IdGeneratorImplementation import com.berlin.domain.model.State import com.berlin.domain.repository.StateRepository import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation diff --git a/src/test/kotlin/presentation/audit/AuditByProjectUITest.kt b/src/test/kotlin/presentation/audit/AuditByProjectUITest.kt index 646478f..572455c 100644 --- a/src/test/kotlin/presentation/audit/AuditByProjectUITest.kt +++ b/src/test/kotlin/presentation/audit/AuditByProjectUITest.kt @@ -54,7 +54,7 @@ class AuditByProjectUITest { fun `displays audit logs for selected project`() { every { reader.read() } returns "1" every { getAuditLogsByProjectIdUseCase.getAuditLogsByProjectId("P1") } returns sampleLogs - + every { getAllProjectsUseCase.getAllProjects() } returns listOf(sampleProject) ui.run() verify { @@ -68,7 +68,7 @@ class AuditByProjectUITest { fun `displays message when no logs exist for project`() { every { reader.read() } returns "1" every { getAuditLogsByProjectIdUseCase.getAuditLogsByProjectId("P1") } returns emptyList() - + every { getAllProjectsUseCase.getAllProjects() } returns listOf(sampleProject) ui.run() verify { @@ -79,7 +79,7 @@ class AuditByProjectUITest { @Test fun `displays cancelled message when input is x`() { every { reader.read() } returns "x" - + every { getAllProjectsUseCase.getAllProjects() } returns listOf(sampleProject) ui.run() verify { @@ -100,6 +100,7 @@ class AuditByProjectUITest { entityId = "P1" ) ) + every { getAllProjectsUseCase.getAllProjects() } returns listOf(sampleProject) ui.run() @@ -111,6 +112,7 @@ class AuditByProjectUITest { @Test fun `displays invalid selection for non-number input`() { every { reader.read() } returns "not-a-number" + every { getAllProjectsUseCase.getAllProjects() } returns listOf(sampleProject) ui.run() diff --git a/src/test/kotlin/presentation/audit/AuditByTaskUITest.kt b/src/test/kotlin/presentation/audit/AuditByTaskUITest.kt index 5c6bf79..051f5c8 100644 --- a/src/test/kotlin/presentation/audit/AuditByTaskUITest.kt +++ b/src/test/kotlin/presentation/audit/AuditByTaskUITest.kt @@ -1,11 +1,13 @@ package presentation.audit import com.berlin.data.DummyData +import com.berlin.domain.exception.InvalidSelectionException import com.berlin.domain.model.* import com.berlin.domain.usecase.auditSystem.GetAuditLogsByTaskIdUseCase import com.berlin.domain.usecase.project.GetAllProjectsUseCase import com.berlin.domain.usecase.task.GetTasksByProjectUseCase import com.berlin.presentation.audit.AuditByTaskUI +import com.berlin.presentation.helper.choose import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer import io.mockk.every @@ -13,6 +15,7 @@ import io.mockk.mockk import io.mockk.verify import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows class AuditByTaskUITest { @@ -31,6 +34,13 @@ class AuditByTaskUITest { getTasksByProjectUseCase = mockk() ui = AuditByTaskUI(viewer, reader, getAuditLogsByTaskIdUseCase, getTasksByProjectUseCase, getAllProjectsUseCase) + every { getAllProjectsUseCase.getAllProjects() }returns listOf( + Project("P1", "Project 1", null, emptyList(), emptyList()) + ) + every { getTasksByProjectUseCase.invoke("P1") }returns Result.success(listOf( + Task("T1", "P1", "Task 1", null, "S1", "U2", "U1") + )) + DummyData.projects.clear() DummyData.projects.addAll( listOf( @@ -103,7 +113,6 @@ class AuditByTaskUITest { fun `should display message when no logs are found`() { every { reader.read() } returnsMany listOf("1", "1") every { getAuditLogsByTaskIdUseCase.getAuditLogsByTaskId("T1") } returns emptyList() - ui.run() verify { viewer.show("No audit logs found for this task.") } @@ -112,7 +121,6 @@ class AuditByTaskUITest { @Test fun `should handle InputCancelledException gracefully`() { every { reader.read() } returns "x" - ui.run() verify { viewer.show("Cancelled.") } @@ -126,4 +134,7 @@ class AuditByTaskUITest { verify { viewer.show("Invalid selection") } } + + + } \ No newline at end of file From 7572de7d4839b9b6f418035e434b97a4f2955784 Mon Sep 17 00:00:00 2001 From: ahmad Date: Fri, 9 May 2025 16:01:53 +0300 Subject: [PATCH 25/25] merge all test cases for the usecases and the ui --- src/main/kotlin/di/uiModule.kt | 11 +- src/main/kotlin/presentation/MainMenuUI.kt | 4 +- .../authService/AuthenticateUserUI.kt | 6 +- .../presentation/authService/CreateMateUI.kt | 2 +- src/test/kotlin/MainKtTest.kt | 4 +- .../AuthenticationRepositoryInMemoryTest.kt | 110 +++++- .../domain/helper/AuthServiceTestData.kt | 28 +- .../AuthenticateUserUseCaseTest.kt | 65 ++-- .../authService/CreateMateUseCaseTest.kt | 106 ++++-- .../authService/GetUserByIDUseCaseTest.kt | 24 +- .../authService/AuthenticateUserUITest.kt | 200 ++++++++++ .../authService/CreateMateUITest.kt | 20 +- .../authService/FetchAllUsersUITest.kt | 36 +- .../authService/GetUserByIDUITest.kt | 71 +++- .../authService/GettingUsersLoggedInUITest.kt | 53 +++ .../presentation/task/AssignTaskUITest.kt | 50 +-- .../task/ChangeTaskStateUITest.kt | 66 ++-- .../presentation/task/CreateTaskUITest.kt | 351 ++++++++---------- .../presentation/task/DeleteTaskUITest.kt | 6 +- .../presentation/task/UpdateTaskUITest.kt | 29 +- 20 files changed, 845 insertions(+), 397 deletions(-) create mode 100644 src/test/kotlin/presentation/authService/AuthenticateUserUITest.kt create mode 100644 src/test/kotlin/presentation/authService/GettingUsersLoggedInUITest.kt diff --git a/src/main/kotlin/di/uiModule.kt b/src/main/kotlin/di/uiModule.kt index f574f9f..9c6e7ed 100644 --- a/src/main/kotlin/di/uiModule.kt +++ b/src/main/kotlin/di/uiModule.kt @@ -1,7 +1,5 @@ package com.berlin.di -import com.berlin.data.DummyData -import com.berlin.domain.model.User import com.berlin.domain.usecase.authService.GetUserByIDUseCase import com.berlin.presentation.MainMenuUI import com.berlin.presentation.audit.AuditByProjectUI @@ -12,7 +10,6 @@ import com.berlin.presentation.state.* import com.berlin.presentation.project.* import com.berlin.presentation.task.* import data.UserCache -import org.koin.core.qualifier.named import org.koin.dsl.module @@ -29,8 +26,8 @@ val uiModule = module { single { GetUserByIDUseCase(get()) } single { GettingUsersLoggedInUI(get(), get()) } - single { CreationOfMateUi(get(),get(),get()) } - single { AuthenticateUserUi(get(),get(),get(),get()) } + single { CreateMateUI(get(),get(),get()) } + single { AuthenticateUserUI(get(),get(),get()) } single { FetchAllUsersUI(get(),get()) } single { GetUserByIDUI(get(),get(),get()) } single { CreateProjectUi(get(),get(),get()) } @@ -60,7 +57,7 @@ val uiModule = module { get(), get(), - get(), + get(), get(), get(), get(), @@ -82,7 +79,7 @@ val uiModule = module { ), viewer = get(), reader = get(), - authUi = get(), + authUi = get(), userCache=get() ) } diff --git a/src/main/kotlin/presentation/MainMenuUI.kt b/src/main/kotlin/presentation/MainMenuUI.kt index 0524c1f..8aa22f4 100644 --- a/src/main/kotlin/presentation/MainMenuUI.kt +++ b/src/main/kotlin/presentation/MainMenuUI.kt @@ -1,7 +1,7 @@ package com.berlin.presentation import com.berlin.domain.model.UserRole -import com.berlin.presentation.authService.AuthenticateUserUi +import com.berlin.presentation.authService.AuthenticateUserUI import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer import data.UserCache @@ -10,7 +10,7 @@ class MainMenuUI( private val runners: List, private val viewer: Viewer, private val reader: Reader, - private val authUi: AuthenticateUserUi, + private val authUi: AuthenticateUserUI, private val userCache: UserCache, ) : UiRunner { diff --git a/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt b/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt index 43cb373..2804894 100644 --- a/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt +++ b/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt @@ -7,8 +7,7 @@ import com.berlin.presentation.io.Viewer import data.UserCache import domain.usecase.authService.AuthenticateUserUseCase -class AuthenticateUserUi( - private val userCache: UserCache, +class AuthenticateUserUI( private val authenticateUser: AuthenticateUserUseCase, private val viewer: Viewer, private val reader: Reader @@ -20,7 +19,7 @@ class AuthenticateUserUi( authenticateLoop() } - private fun validateUser(): Result { + fun validateUser(): Result { viewer.show("Enter your user name: ") val userName = reader.read()?.trim().orEmpty() viewer.show("Enter your password: ") @@ -32,7 +31,6 @@ class AuthenticateUserUi( validateUser().fold( onSuccess = { viewer.show("Welcome ${it.userName}") - userCache.currentUser = it }, onFailure = { viewer.show("Try again") diff --git a/src/main/kotlin/presentation/authService/CreateMateUI.kt b/src/main/kotlin/presentation/authService/CreateMateUI.kt index 6d803ee..a09e827 100644 --- a/src/main/kotlin/presentation/authService/CreateMateUI.kt +++ b/src/main/kotlin/presentation/authService/CreateMateUI.kt @@ -6,7 +6,7 @@ import com.berlin.presentation.UiRunner import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -class CreationOfMateUi( +class CreateMateUI( private val createMateUseCase: CreateMateUseCase, private val viewer: Viewer, private val reader: Reader, diff --git a/src/test/kotlin/MainKtTest.kt b/src/test/kotlin/MainKtTest.kt index b0c3379..5324d0f 100644 --- a/src/test/kotlin/MainKtTest.kt +++ b/src/test/kotlin/MainKtTest.kt @@ -3,7 +3,7 @@ package com.berlin import com.berlin.data.DummyData import com.berlin.presentation.MainMenuUI import com.berlin.presentation.UiRunner -import com.berlin.presentation.authService.AuthenticateUserUi +import com.berlin.presentation.authService.AuthenticateUserUI import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer import data.UserCache @@ -20,7 +20,7 @@ class MainTest { val mockViewer = mockk(relaxed = true) val mockReader = mockk() every { mockReader.read() } returns "X" - val mockAuthUi = mockk { every { run() } just Runs } + val mockAuthUi = mockk { every { run() } just Runs } val mockUserCache = mockk() every { mockUserCache.currentUser } returns DummyData.users.first() val dummyRunners = emptyList() diff --git a/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt b/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt index 0265665..2375f98 100644 --- a/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt +++ b/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt @@ -1,48 +1,118 @@ -package com.berlin.data.memory +package com.berlin.data.authentication import com.berlin.data.AuthDummyData -import com.berlin.data.authentication.AuthenticationRepositoryImpl -import com.berlin.domain.hashPassword.HashingString -import com.berlin.domain.hashPassword.MD5Hasher +import com.berlin.domain.exception.UserNotFoundException import com.berlin.domain.helper.AuthServiceTestData import com.berlin.domain.model.User import com.berlin.domain.model.UserRole -import com.google.common.truth.Truth.assertThat import data.UserCache import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import com.google.common.truth.Truth.assertThat class AuthenticationRepositoryInMemoryTest { - private lateinit var inMemoryAuthRepositoryImpl: AuthenticationRepositoryImpl - private lateinit var hashingString: HashingString - private var cashedUser = User("user1234", "admin", "1212", UserRole.ADMIN) + + private val cachedUser = User("user1234", "admin", "1212", UserRole.ADMIN) + + private lateinit var repoWithCache: AuthenticationRepositoryImpl + private lateinit var repoWithoutCache: AuthenticationRepositoryImpl @BeforeEach fun setup() { - hashingString = MD5Hasher() AuthDummyData.users.clear() - inMemoryAuthRepositoryImpl =AuthenticationRepositoryImpl( UserCache(cashedUser),AuthDummyData) + + repoWithCache = AuthenticationRepositoryImpl( + userCache = UserCache(cachedUser), + userDataSource = AuthDummyData + ) + + repoWithoutCache = AuthenticationRepositoryImpl( + userCache = UserCache(cachedUser), + userDataSource = AuthDummyData + ) + } + + @Test + fun `login succeeds with exactly matching credentials`() { + val expected = AuthServiceTestData.expectedUser + repoWithoutCache.createMate(expected) + + val result = repoWithoutCache.login(expected.userName, expected.password) + + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).isEqualTo(expected) + } + + @Test + fun `login fails with wrong credentials`() { + repoWithoutCache.createMate(AuthServiceTestData.expectedUser) + + val result = repoWithoutCache.login("wrongUser", "wrongPass") + + assertThat(result.isFailure).isTrue() } + @Test + fun `createMate persists and returns the new user`() { + val newUser = User("id-foo", "bar", "baz", UserRole.MATE) + + val result = repoWithoutCache.createMate(newUser) + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).isEqualTo(newUser) + assertThat(AuthDummyData.users).contains(newUser) + } @Test - fun `login should return success when provided with valid credentials`() { - val username = "Fatma" - val password = "hashed_securePassword" - inMemoryAuthRepositoryImpl.createMate(AuthServiceTestData.excepctedUser) - val result = inMemoryAuthRepositoryImpl.login(username, password) + fun `getUserById returns user when exists`() { + val u = AuthServiceTestData.expectedUser + repoWithoutCache.createMate(u) + + val result = repoWithoutCache.getUserById(u.id) + assertThat(result.isSuccess).isTrue() - val user = result.getOrNull() - assertThat(user?.userName).isEqualTo(username) + assertThat(result.getOrNull()).isEqualTo(u) } @Test - fun `login should return failure when provided with invalid credentials`() { - inMemoryAuthRepositoryImpl.createMate(AuthServiceTestData.user) - val result = inMemoryAuthRepositoryImpl.login("wrong", "pass") + fun `getUserById fails when user does not exist`() { + val missingId = "does-not-exist" + + val result = repoWithoutCache.getUserById(missingId) + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()) + .isInstanceOf(UserNotFoundException::class.java) + assertThat(result.exceptionOrNull()?.message) + .isEqualTo(missingId) + } + + @Test + fun `getAllUsers returns empty list when none exist`() { + val result = repoWithoutCache.getAllUsers() + + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).isEmpty() + } + + @Test + fun `getAllUsers returns all users when some exist`() { + val u1 = AuthServiceTestData.expectedUser + val u2 = User("id2", "alice", "pw", UserRole.MATE) + repoWithoutCache.createMate(u1) + repoWithoutCache.createMate(u2) + + val result = repoWithoutCache.getAllUsers() + + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).containsExactly(u1, u2) } + @Test + fun `getCurrentUser returns the cached user when present`() { + val result = repoWithCache.getCurrentUser() + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).isEqualTo(cachedUser) + } } diff --git a/src/test/kotlin/domain/helper/AuthServiceTestData.kt b/src/test/kotlin/domain/helper/AuthServiceTestData.kt index aae81d1..8b06899 100644 --- a/src/test/kotlin/domain/helper/AuthServiceTestData.kt +++ b/src/test/kotlin/domain/helper/AuthServiceTestData.kt @@ -7,14 +7,14 @@ import com.berlin.domain.model.User object AuthServiceTestData { val inValidUserName = "Ahmed" val inValidUserPassword = "00000000" - val passwordLessThanEight = "hashd_s" val userName = "Fatma" val userNameIsEmpty = "" val userPassword = "hashed_securePassword" val userPasswordIsEmpty = "" val idNotExist = "u6" + val idWithSpacesExist=" u13" val idExist = "u13" - val excepctedUser = userDummyData( + val expectedUser = userDummyData( userName = userName, password = userPassword, role = UserRole.MATE @@ -22,31 +22,13 @@ object AuthServiceTestData { val user = userDummyData( userName = "Fatma", password = "hashed_securePassword" ) - val userIsEmpty = userDummyData("","","") val testForUserName = "Fatma" val testForUserPassword = "1234567899" val adminIsFirstUser = userDummyData("u55", "Menna", "12345678") val existingUser = userDummyData("u13", "Menna", "12345678") - const val testUserName = "testUser" - const val testUserPassword = "testPassword" - const val invalidInput = "" - - val expectedUser = User( - id = "id_testPassword", - userName = testForUserName, - password = "hashed_testPassword", - role = UserRole.MATE - ) - - val emptyUser = User( - id = "id_", - userName = "", - password = "hashed_", - role = UserRole.MATE - ) - - -} + val EMPTY_USER = User("","","",UserRole.ADMIN) + val CACHEUSER =User("user1234", "admin", "1212", UserRole.ADMIN) +} \ No newline at end of file diff --git a/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt b/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt index 2e24e37..3ac4abb 100644 --- a/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt @@ -4,22 +4,26 @@ import com.berlin.domain.hashPassword.HashingString import com.berlin.domain.repository.AuthenticationRepository import com.berlin.domain.fakeData.FakeHashingString import com.berlin.domain.helper.AuthServiceTestData -import com.berlin.domain.model.User -import com.berlin.domain.model.UserRole +import com.berlin.domain.helper.AuthServiceTestData.EMPTY_USER +import com.berlin.domain.helper.AuthServiceTestData.userName +import com.berlin.domain.helper.AuthServiceTestData.userPassword +import com.berlin.domain.helper.AuthServiceTestData.CACHEUSER import com.google.common.truth.Truth.assertThat import data.UserCache import domain.usecase.authService.AuthenticateUserUseCase import io.mockk.every import io.mockk.mockk +import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import kotlin.test.assertTrue class AuthenticateUserUseCaseTest { private lateinit var authRepository: AuthenticationRepository private lateinit var hashingString: HashingString private lateinit var authenticateUserUseCase: AuthenticateUserUseCase - private var cashedUser = User("user1234", "admin", "1212", UserRole.ADMIN) + private var cashedUser = CACHEUSER private lateinit var userCache: UserCache @BeforeEach @@ -29,7 +33,6 @@ class AuthenticateUserUseCaseTest { userCache= UserCache(cashedUser) authenticateUserUseCase = AuthenticateUserUseCase(userCache,authRepository, hashingString) } - @Test fun `login returns user successfully when valid credentials are provided`() { // Given @@ -172,26 +175,6 @@ class AuthenticateUserUseCaseTest { assertThat(result.exceptionOrNull()?.message).isEqualTo("Unexpected error") } - @Test - fun `UserCache remains null if login fails`() { - // Given - val user = AuthServiceTestData.user - val hashedPassword = hashingString.hashPassword(AuthServiceTestData.userPassword) - every { authRepository.getAllUsers() } returns Result.success(listOf(user)) - every { authRepository.login(user.userName, hashedPassword) } returns Result.failure( - InvalidCredentialsException("Wrong credentials") - ) - userCache.currentUser = cashedUser - - // When - val result = authenticateUserUseCase.login(user.userName, AuthServiceTestData.userPassword) - - // Then - assertThat(result.isFailure).isTrue() - assertThat(userCache.currentUser).isNull() - } - - @Test fun `login ignores cache if username is different and proceeds with login`() { // Given @@ -233,4 +216,38 @@ class AuthenticateUserUseCaseTest { assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isInstanceOf(InvalidCredentialsException::class.java) } + @Test + fun `userCashing returns null when user enter invalid data`(){ + //Given + val emptyUser =EMPTY_USER + userCache.currentUser = emptyUser + val hashPassword = hashingString.hashPassword(userPassword) + every { authRepository.getAllUsers() } returns Result.success(listOf(emptyUser)) + every { authRepository.login(any() , any()) } returns Result.failure( + InvalidCredentialsException("No user found") + ) + //When + val result = authenticateUserUseCase.login(userName, hashPassword) + //Then + assertTrue(result.isFailure) + assertThat(result.exceptionOrNull() is InvalidCredentialsException) + } + @Test + fun `login should return failure when repository returns failure`() { + val userCache = UserCache(EMPTY_USER) + val repository = mockk() + val hashingString = FakeHashingString() + + val useCase = AuthenticateUserUseCase(userCache, repository, hashingString) + + val userName = "admin" + val password = "wrongPass" + val expectedException = InvalidCredentialsException("Invalid login") + + every { repository.login(userName, any()) } returns Result.failure(expectedException) + val result = useCase.login(userName, password) + + assertTrue(result.isFailure) + Assertions.assertEquals(expectedException, result.exceptionOrNull()) + } } diff --git a/src/test/kotlin/domain/usecase/authService/CreateMateUseCaseTest.kt b/src/test/kotlin/domain/usecase/authService/CreateMateUseCaseTest.kt index a5da205..cd74aba 100644 --- a/src/test/kotlin/domain/usecase/authService/CreateMateUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/authService/CreateMateUseCaseTest.kt @@ -1,72 +1,112 @@ -package domain.logic.usecase.authService +package com.berlin.domain.usecase.authService +import com.berlin.domain.exception.InvalidCredentialsException import com.berlin.domain.hashPassword.HashingString +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole import com.berlin.domain.repository.AuthenticationRepository import com.berlin.domain.usecase.authService.CreateMateUseCase -import com.berlin.domain.helper.AuthServiceTestData import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.google.common.truth.Truth.assertThat +import io.mockk.every import io.mockk.mockk +import io.mockk.verify import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test class CreateMateUseCaseTest { private lateinit var authRepository: AuthenticationRepository - private lateinit var idGenerator: IdGenerator + private val idGenerator: IdGenerator = mockk() private lateinit var hashingString: HashingString private lateinit var createMateUseCase: CreateMateUseCase @BeforeEach fun setup() { - authRepository = mockk() - hashingString = mockk() - idGenerator = mockk() + authRepository = mockk() + hashingString = mockk() createMateUseCase = CreateMateUseCase(authRepository, idGenerator, hashingString) } @Test fun `createMate fails when username is empty`() { - // Given - val emptyUsername = AuthServiceTestData.userNameIsEmpty - val password = AuthServiceTestData.userPassword - - // When - val result = createMateUseCase.createMate(emptyUsername, password) - - // Then + val result = createMateUseCase.createMate("", "validPassword") assertThat(result.isFailure).isTrue() - assertThat(result.exceptionOrNull()?.message).isEqualTo("Username and password must not be empty") + assertThat(result.exceptionOrNull()) + .isInstanceOf(InvalidCredentialsException::class.java) + assertThat(result.exceptionOrNull()?.message) + .isEqualTo("Username and password must not be empty") } @Test fun `createMate fails when password is empty`() { - // Given - val username = AuthServiceTestData.userName - val emptyPassword = AuthServiceTestData.userPasswordIsEmpty - - // When - val result = createMateUseCase.createMate(username, emptyPassword) - - // Then + val result = createMateUseCase.createMate("validUser", "") assertThat(result.isFailure).isTrue() - assertThat(result.exceptionOrNull()?.message).isEqualTo("Username and password must not be empty") + assertThat(result.exceptionOrNull()) + .isInstanceOf(InvalidCredentialsException::class.java) + assertThat(result.exceptionOrNull()?.message) + .isEqualTo("Username and password must not be empty") } @Test fun `createMate fails when password length is less than 8 characters`() { - // Given - val username = AuthServiceTestData.userName - val shortPassword = AuthServiceTestData.passwordLessThanEight + val result = createMateUseCase.createMate("validUser", "short7") + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()) + .isInstanceOf(InvalidCredentialsException::class.java) + assertThat(result.exceptionOrNull()?.message) + .isEqualTo("Password less than 8 characters") + } - // When - val result = createMateUseCase.createMate(username, shortPassword) + // —— NEW TESTS BELOW —— - // Then + @Test + fun `createMate succeeds when credentials are valid`() { + // Arrange + val username = "validUser" + val password = "longEnough" + val fakeHash = "hashed_longEnough" + val fakeId = "generated-id-123" + val expected = User(fakeId, username, fakeHash, UserRole.MATE) + + every { hashingString.hashPassword(password) } returns fakeHash + every { idGenerator.generateId(username, any(), any()) } returns fakeId + every { authRepository.createMate(expected) } returns Result.success(expected) + + // Act + val result = createMateUseCase.createMate(username, password) + + // Assert + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).isEqualTo(expected) + verify { + hashingString.hashPassword(password) + idGenerator.generateId(username, any(), any()) + authRepository.createMate(expected) + } + } + + @Test + fun `createMate propagates repository failure`() { + // Arrange + val username = "validUser" + val password = "longEnough" + val fakeHash = "hashed_longEnough" + val fakeId = "generated-id-456" + val expected = User(fakeId, username, fakeHash, UserRole.MATE) + val repoEx = RuntimeException("already exists") + + every { hashingString.hashPassword(password) } returns fakeHash + every { idGenerator.generateId(username, any(), any()) } returns fakeId + every { authRepository.createMate(expected) } returns Result.failure(repoEx) + + // Act + val result = createMateUseCase.createMate(username, password) + + // Assert assertThat(result.isFailure).isTrue() - assertThat(result.exceptionOrNull()?.message).isEqualTo("Password less than 8 characters") + assertThat(result.exceptionOrNull()).isEqualTo(repoEx) + verify { authRepository.createMate(expected) } } } - - diff --git a/src/test/kotlin/domain/usecase/authService/GetUserByIDUseCaseTest.kt b/src/test/kotlin/domain/usecase/authService/GetUserByIDUseCaseTest.kt index 3687fa8..619257a 100644 --- a/src/test/kotlin/domain/usecase/authService/GetUserByIDUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/authService/GetUserByIDUseCaseTest.kt @@ -1,15 +1,16 @@ package com.berlin.domain.usecase.authService import com.berlin.domain.exception.InvalidUserIdException -import com.berlin.domain.repository.AuthenticationRepository import com.berlin.domain.helper.AuthServiceTestData +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole +import com.berlin.domain.repository.AuthenticationRepository import com.google.common.truth.Truth.assertThat import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows class GetUserByIDUseCaseTest { private lateinit var repository: AuthenticationRepository @@ -23,11 +24,14 @@ class GetUserByIDUseCaseTest { @Test - fun `getUserById throws InvalidUserIdException when ID is empty`() { + fun `getUserById return InvalidUserIdException when ID is empty`() { + val existingId = "" + every { repository.getUserById(existingId) + } returns Result.failure(InvalidUserIdException("User ID can't be empty or just digits")) + val result = getUserByIDUseCase.getUserById(existingId) // When & Then - assertThrows { - getUserByIDUseCase.getUserById("") - } + assertThat(result.isFailure) + } @Test @@ -46,9 +50,11 @@ class GetUserByIDUseCaseTest { @Test fun `throws Invalid User Id Exception when id is blank`() { - assertThrows { - getUserByIDUseCase.getUserById(" ") - } + val existingId = "51" + every { repository.getUserById(existingId) + } returns Result.failure(InvalidUserIdException("User ID can't be empty or just digits")) + verify(exactly = 0) { repository.getUserById(any()) } } + } \ No newline at end of file diff --git a/src/test/kotlin/presentation/authService/AuthenticateUserUITest.kt b/src/test/kotlin/presentation/authService/AuthenticateUserUITest.kt new file mode 100644 index 0000000..3a80e39 --- /dev/null +++ b/src/test/kotlin/presentation/authService/AuthenticateUserUITest.kt @@ -0,0 +1,200 @@ +package presentation.authService + +import com.berlin.domain.exception.InvalidCredentialsException +import com.berlin.domain.fakeData.FakeHashingString +import com.berlin.domain.hashPassword.HashingString +import com.berlin.domain.helper.AuthServiceTestData.CACHEUSER +import com.berlin.domain.helper.AuthServiceTestData.EMPTY_USER +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole +import com.berlin.domain.repository.AuthenticationRepository +import com.berlin.presentation.authService.AuthenticateUserUI +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer +import com.google.common.truth.Truth.assertThat +import data.UserCache +import domain.usecase.authService.AuthenticateUserUseCase +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.verify +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertTrue +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + + +class AuthenticateUserUITest { + private lateinit var userCache: UserCache + private lateinit var authenticationRepository: AuthenticationRepository + private lateinit var hashPassword: HashingString + private lateinit var authenticateUserUseCase: AuthenticateUserUseCase + private lateinit var authenticateUserUi: AuthenticateUserUI + private lateinit var viewer: Viewer + private lateinit var reader: Reader + private val testUser = User("id123", "admin", "hashed123", UserRole.ADMIN) + private val userName = "admin" + private val wrongPassword = "wrong" + + @BeforeEach + fun setup() { + userCache = UserCache(CACHEUSER) + authenticationRepository = mockk(relaxed = true) + hashPassword = FakeHashingString() + viewer = mockk(relaxed = true) + reader = mockk() + authenticateUserUseCase = + AuthenticateUserUseCase(userCache, authenticationRepository, hashPassword) + authenticateUserUi = AuthenticateUserUI(authenticateUserUseCase, viewer, reader) + } + + @Test + fun `run should return success when user enter valid data `() { + val hashingString = hashPassword.hashPassword("1212") + every { reader.read() } returnsMany listOf("admin", "1212") + every { viewer.show(any()) } just Runs + every { authenticationRepository.login("admin", hashingString) } + val result = authenticateUserUseCase.login("admin", "1212") + userCache.currentUser = CACHEUSER + assertThat(result) + + } + + @Test + fun `run should return failed when enter invalid data `() { + every { reader.read() } returnsMany listOf("ahmed", "5684") + every { + authenticationRepository.login(any(), any()) + } returns Result.failure(InvalidCredentialsException("No user found")) + + val result = authenticateUserUseCase.login("ahmed", "5684") + + assertThat(result) + } + + @Test + fun `login succeeds on first attempt`() { + val expectedUser = User("user1234", "admin", "1212", UserRole.ADMIN) + + every { reader.read() } returnsMany listOf("admin", "1212") + every { authenticationRepository.login("admin", "1212") } returns Result.success( + expectedUser + ) + every { viewer.show(any()) } just Runs + + authenticateUserUi.run() + + assertEquals(expectedUser, userCache.currentUser) + verify { viewer.show("Welcome admin") } + + } + + @Test + fun `run should login successfully on first attempt`() { + val testUser = User("user1234", "admin", "1212", UserRole.ADMIN) + + every { reader.read() } returnsMany listOf("admin", "1212") + every { authenticationRepository.login("admin", "1212") } returns Result.success(testUser) + every { viewer.show(any()) } just Runs + + authenticateUserUi.run() + + Assertions.assertEquals(testUser, userCache.currentUser) + verify { viewer.show("Welcome admin") } + } + + @Test + fun `should login successfully on first attempt`() { + every { reader.read() } returnsMany listOf("admin", "1234") + every { authenticationRepository.login("admin", "1234") } returns Result.success(testUser) + + authenticateUserUi.run() + + verify { viewer.show("Enter your user name: ") } + verify { viewer.show("Enter your password: ") } + verify { viewer.show("Welcome admin") } + } + + @Test + fun `should stop retrying if login succeeds before max attempts`() { + val userCache = UserCache(EMPTY_USER) + val hashing = mockk() + + val authenticateUserUseCase = + AuthenticateUserUseCase(userCache, authenticationRepository, hashing) + val ui = AuthenticateUserUI(authenticateUserUseCase, viewer, reader) + + // simulate inputs + every { reader.read() } returnsMany listOf( + userName, wrongPassword, userName, wrongPassword, userName, "correct" + ) + + every { hashing.hashPassword(wrongPassword) } returns "hashed_wrong" + every { hashing.hashPassword("correct") } returns "hashed_correct" + + every { authenticationRepository.login(userName, "hashed_wrong") } returnsMany listOf( + Result.failure(Exception("Invalid")), Result.failure(Exception("Invalid")) + ) + every { authenticationRepository.login(userName, "hashed_correct") } returns Result.success( + testUser + ) + + ui.run() + + verify { viewer.show("Welcome $userName") } + } + + @Test + fun `should show Try again two times then stop on third failure`() { + every { reader.read() } returnsMany listOf( + "admin", "wrong", "admin", "wrong", "admin", "wrong" + ) + every { authenticationRepository.login("admin", "wrong") } returnsMany listOf( + Result.failure(Exception("fail1")), + Result.failure(Exception("fail2")), + Result.failure(Exception("fail3")) + ) + every { viewer.show(any()) } just Runs + + val useCase = AuthenticateUserUseCase(userCache, authenticationRepository, hashPassword) + val ui = AuthenticateUserUI(useCase, viewer, reader) + + ui.run() + + verify(exactly = 0) { viewer.show("Try again") } + } + + @Test + fun `should handle null password`() { + every { reader.read() } returnsMany listOf("admin", null) + every { + authenticationRepository.login( + "admin", + "" + ) + } returns Result.failure(Exception("Invalid")) + + val result = authenticateUserUi.validateUser() + + assertTrue(result.isFailure) + } + + @Test + fun `should handle null username and password`() { + every { reader.read() } returnsMany listOf(null, null) + every { + authenticationRepository.login( + "", + "" + ) + } returns Result.failure(Exception("Invalid")) + + val result = authenticateUserUi.validateUser() + + assertTrue(result.isFailure) + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/authService/CreateMateUITest.kt b/src/test/kotlin/presentation/authService/CreateMateUITest.kt index 630fefd..3c720f2 100644 --- a/src/test/kotlin/presentation/authService/CreateMateUITest.kt +++ b/src/test/kotlin/presentation/authService/CreateMateUITest.kt @@ -12,10 +12,10 @@ import io.mockk.verifySequence import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class CreationOfMateUiTest { +class CreateMateUITest { private lateinit var createMateUseCase: CreateMateUseCase - private lateinit var creationOfMateUi: CreationOfMateUi + private lateinit var creationOfMateUi: CreateMateUI private lateinit var viewer: Viewer private lateinit var reader: Reader @@ -26,7 +26,7 @@ class CreationOfMateUiTest { createMateUseCase = mockk() viewer = mockk(relaxed = true) reader = mockk() - creationOfMateUi = CreationOfMateUi(createMateUseCase, viewer, reader) + creationOfMateUi = CreateMateUI(createMateUseCase, viewer, reader) } @Test @@ -43,16 +43,16 @@ class CreationOfMateUiTest { fun `run should retry once after failure and succeed second time`() { every { reader.read() } returnsMany listOf("test1", "123", "test2", "456") every { createMateUseCase.createMate("test1", "123") } returns Result.failure(InvalidAssigneeException("fail")) - every { createMateUseCase.createMate("test2", "456") } returns Result.success(AuthServiceTestData.excepctedUser) + every { createMateUseCase.createMate("test2", "456") } returns Result.success(AuthServiceTestData.expectedUser) creationOfMateUi.run() verifySequence { - viewer.show("Enter user name: ") + viewer.show("Enter user name or x to exit: ") viewer.show("Enter user password: ") - viewer.show("something wrong please try again!") + viewer.show("fail") - viewer.show("Enter user name: ") + viewer.show("Enter user name or x to exit: ") viewer.show("Enter user password: ") viewer.show("New mate is successfully created!") } @@ -63,11 +63,13 @@ class CreationOfMateUiTest { fun `run should treat null inputs as empty strings`() { every { reader.read() } returnsMany listOf(null, null, "name", "pass") every { createMateUseCase.createMate("", "") } returns Result.failure(InvalidAssigneeException("empty")) - every { createMateUseCase.createMate("name", "pass") } returns Result.success(AuthServiceTestData.excepctedUser) + every { createMateUseCase.createMate("name", "pass") } returns Result.success(AuthServiceTestData.expectedUser) creationOfMateUi.run() - verify { viewer.show("something wrong please try again!") } + verify { viewer.show("empty") } // matching the actual error message verify { viewer.show("New mate is successfully created!") } } + + } diff --git a/src/test/kotlin/presentation/authService/FetchAllUsersUITest.kt b/src/test/kotlin/presentation/authService/FetchAllUsersUITest.kt index 151b86e..2c7912e 100644 --- a/src/test/kotlin/presentation/authService/FetchAllUsersUITest.kt +++ b/src/test/kotlin/presentation/authService/FetchAllUsersUITest.kt @@ -10,7 +10,7 @@ import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class FetchAllUsersUseCaseTest { +class FetchAllUsersUITest { private lateinit var viewer: Viewer private lateinit var useCase: FetchAllUsersUseCase private lateinit var ui: FetchAllUsersUI @@ -29,10 +29,6 @@ class FetchAllUsersUseCaseTest { @Test fun `should print all users when users are available`() { // Given - val users = listOf( - User(id = "1", userName = "Menna", password = "12345678j", role =UserRole.ADMIN), - User(id = "2", userName = "Sarah", password = "1234567890", role = UserRole.MATE) - ) every { useCase.getAllUsers() } returns Result.success(users) // When @@ -47,9 +43,11 @@ class FetchAllUsersUseCaseTest { "ID: 2", "Name: Sarah", "role: MATE", - "=====================") + "=====================" + ) } + @Test fun `should print message when no users are found`() { // Given @@ -63,4 +61,30 @@ class FetchAllUsersUseCaseTest { "No users found." ) } + + @Test + fun `should return users when there is users`() { + every { useCase.getAllUsers() } returns Result.success(users) + + ui.run() + + verifySequence { + viewer.show("ID: ${users[0].id}") + viewer.show("Name: ${users[0].userName}") + viewer.show("role: ${users[0].role}") + viewer.show("=====================") + viewer.show("ID: ${users[1].id}") + viewer.show("Name: ${users[1].userName}") + viewer.show("role: ${users[1].role}") + viewer.show("=====================") + } + } + + private companion object { + val users = listOf( + User(id = "1", userName = "Menna", password = "12345678j", role = UserRole.ADMIN), + User(id = "2", userName = "Sarah", password = "1234567890", role = UserRole.MATE) + ) + } + } \ No newline at end of file diff --git a/src/test/kotlin/presentation/authService/GetUserByIDUITest.kt b/src/test/kotlin/presentation/authService/GetUserByIDUITest.kt index 417a43c..6933c67 100644 --- a/src/test/kotlin/presentation/authService/GetUserByIDUITest.kt +++ b/src/test/kotlin/presentation/authService/GetUserByIDUITest.kt @@ -1,5 +1,6 @@ package presentation.authService +import com.berlin.domain.exception.InvalidUserIdException import com.berlin.domain.helper.AuthServiceTestData import com.berlin.domain.usecase.authService.GetUserByIDUseCase import com.berlin.presentation.authService.GetUserByIDUI @@ -44,6 +45,74 @@ class GetUserByIDUITest { verify { useCase.getUserById(id) } assertThat(printed).contains("Enter the user id: ") } + @Test + fun `should call use case when correct user ID with regardless to spaces in begin of id`() { + // Given + val id = AuthServiceTestData.idWithSpacesExist + every { reader.read() } returns id.trim() + every { useCase.getUserById(id.trim()) } returns Result.success(AuthServiceTestData.existingUser) + + // When + ui.run() + + // Then + verify { useCase.getUserById(id.trim()) } + assertThat(printed).contains("Enter the user id: ") + + } + + @Test + fun `should throw exception when id not exists`() { + val id = AuthServiceTestData.idNotExist + every { reader.read() } returns id + every { useCase.getUserById(id) } returns Result.failure(InvalidUserIdException("User ID can't be empty or just digits")) + + // When + ui.run() + + // Then + assertThat(useCase.getUserById(id).isFailure).isTrue() + } + @Test + fun `getUserById should return User ID can't be blank `(){ + //Given + every { reader.read() } returns " " + every { useCase.getUserById("") } returns Result.failure(InvalidUserIdException("User ID can't be empty or just digits")) + + //When + ui.run() + + //Then + assertThat(printed.last()).isEqualTo("User ID can't be empty or just digits") + + } + + @Test + fun `getUserById should return User ID can't be empty `(){ + //Given + every { reader.read() } returns "" + every { useCase.getUserById("") } returns Result.failure(InvalidUserIdException("User ID can't be empty or just digits")) + + //When + ui.run() + + //Then + assertThat(printed.last()).isEqualTo("User ID can't be empty or just digits") + + } + @Test + fun `getUserById should return error when there is un expected error `(){ + //Given + every { reader.read() } returns "" + every { useCase.getUserById("") } returns Result.failure(Exception()) + + //When + ui.run() + + //Then + assertThat(printed.last()).isEqualTo("invalid user id") + + } -} \ No newline at end of file +} diff --git a/src/test/kotlin/presentation/authService/GettingUsersLoggedInUITest.kt b/src/test/kotlin/presentation/authService/GettingUsersLoggedInUITest.kt new file mode 100644 index 0000000..cf7cb3c --- /dev/null +++ b/src/test/kotlin/presentation/authService/GettingUsersLoggedInUITest.kt @@ -0,0 +1,53 @@ +package presentation.authService + +import com.berlin.domain.exception.UserNotLoggedInException +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole +import com.berlin.domain.usecase.authService.GetUserLoggedInUseCase +import com.berlin.presentation.authService.GettingUsersLoggedInUI +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer +import io.mockk.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class GettingUsersLoggedInUITest { + private lateinit var getUserLoggedIn: GetUserLoggedInUseCase + private lateinit var gettingUsersLoggedInUI: GettingUsersLoggedInUI + private lateinit var viewer: Viewer + private lateinit var reader: Reader + + + @BeforeEach + fun setup() { + getUserLoggedIn = mockk() + viewer = mockk(relaxed = true) + reader = mockk() + gettingUsersLoggedInUI = GettingUsersLoggedInUI(getUserLoggedIn, viewer) + } + + @Test + fun `gettingUserLoggedIn should return current user when some one logged in`() { + every { getUserLoggedIn.getCurrentUser() } returns Result.success(cachedUser) + + gettingUsersLoggedInUI.run() + + verifySequence { + viewer.show("ID: ${cachedUser.id}") + + viewer.show(" Name: ${cachedUser.userName}") + viewer.show(" role: ${cachedUser.role}") + } + } + + @Test + fun `gettinguserLoggedIn should return error message when no one logged in`() { + every { getUserLoggedIn.getCurrentUser() } returns Result.failure(UserNotLoggedInException("No one logged in")) + gettingUsersLoggedInUI.run() + verify { viewer.show("no user logged in,please log in") } + } + + private companion object { + val cachedUser = User(id = "1", userName = "Menna", password = "12345678j", role = UserRole.ADMIN) + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/task/AssignTaskUITest.kt b/src/test/kotlin/presentation/task/AssignTaskUITest.kt index 035baf3..3000713 100644 --- a/src/test/kotlin/presentation/task/AssignTaskUITest.kt +++ b/src/test/kotlin/presentation/task/AssignTaskUITest.kt @@ -2,12 +2,16 @@ package com.berlin.presentation.task import com.berlin.data.DummyData import com.berlin.domain.exception.InvalidAssigneeException +import com.berlin.domain.exception.InputCancelledException +import com.berlin.domain.exception.InvalidSelectionException import com.berlin.domain.model.Task import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole import com.berlin.domain.usecase.task.AssignTaskUseCase import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import com.berlin.presentation.task.AssignTaskUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -19,7 +23,6 @@ class AssignTaskUITest { private val viewer: Viewer = mockk(relaxed = true) { every { show(capture(printed)) } just Runs } - private val reader: Reader = mockk() private val assignTaskUC: AssignTaskUseCase = mockk() private val getAllTasks: GetAllTasksUseCase = mockk() @@ -29,37 +32,43 @@ class AssignTaskUITest { @BeforeEach fun setUp() { + // reset in-memory data DummyData.tasks.clear() + DummyData.users.clear() + + // populate two users so choose(...) can pick index "1" and "2" + DummyData.users += User("U1", "alice", "pw", UserRole.MATE) + DummyData.users += User("U2", "bob", "pw", UserRole.MATE) newAssignee = DummyData.users[1] + + // create one task assigned initially to alice task = Task( - id = "1", - projectId = "P1", - title = "Demo", - description = null, - stateId = "TODO", - assignedToUserId = DummyData.users[0].id, - createByUserId = DummyData.users[0].id + id = "1", + projectId = "P1", + title = "Demo", + description = null, + stateId = "TODO", + assignedToUserId = DummyData.users[0].id, + createByUserId = DummyData.users[0].id ) DummyData.tasks += task every { getAllTasks.invoke() } returns listOf(task) + printed.clear() } @Test fun `repository update is called with new assignee`() { every { reader.read() } returnsMany listOf("1", "2") every { - assignTaskUC.invoke( - task.id, - newAssignee.id - ) - }.returns(Result.success(task.copy(assignedToUserId = newAssignee.id))) + assignTaskUC.invoke(task.id, newAssignee.id) + } returns Result.success(task.copy(assignedToUserId = newAssignee.id)) AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() verify(exactly = 1) { assignTaskUC.invoke(task.id, newAssignee.id) } - assertThat(printed).contains("Assigned to ${newAssignee.userName}") + assertThat(printed.last()).contains("Assigned to ${newAssignee.userName}") } @Test @@ -86,7 +95,7 @@ class AssignTaskUITest { fun `error from use case is shown to the user`() { every { reader.read() } returnsMany listOf("1", "2") val boom = IllegalStateException("cant assign") - every { assignTaskUC.invoke(task.id, newAssignee.id) }.returns(Result.failure(boom)) + every { assignTaskUC.invoke(task.id, newAssignee.id) } returns Result.failure(boom) AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() @@ -107,7 +116,7 @@ class AssignTaskUITest { @Test fun `throws and shows InvalidAssigneeException`() { every { reader.read() } returnsMany listOf("1", "2") - every { assignTaskUC.invoke(task.id, newAssignee.id) }.throws(InvalidAssigneeException("nope")) + every { assignTaskUC.invoke(task.id, newAssignee.id) } throws InvalidAssigneeException("nope") AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() @@ -118,18 +127,11 @@ class AssignTaskUITest { @Test fun `on failure with null message shows default assignment failed`() { every { reader.read() } returnsMany listOf("1", "2") - - every { - assignTaskUC.invoke( - task.id, - newAssignee.id - ) - } returns Result.failure(RuntimeException("Assignment failed")) + every { assignTaskUC.invoke(task.id, newAssignee.id) } returns Result.failure(RuntimeException("Assignment failed")) AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() verify(exactly = 1) { assignTaskUC.invoke(task.id, newAssignee.id) } assertThat(printed.last()).isEqualTo("Assignment failed") } - } diff --git a/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt b/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt index 1ad1b58..d1fc1a6 100644 --- a/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt +++ b/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt @@ -1,15 +1,16 @@ -package presentation.task +package com.berlin.presentation.task import com.berlin.data.DummyData import com.berlin.domain.exception.InvalidTaskStateException import com.berlin.domain.exception.TaskNotFoundException import com.berlin.domain.model.State import com.berlin.domain.model.Task +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole import com.berlin.domain.usecase.task.ChangeTaskStateUseCase import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -import com.berlin.presentation.task.ChangeTaskStateUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -20,65 +21,74 @@ class ChangeTaskStateUITest { private lateinit var viewer: Viewer private lateinit var reader: Reader private lateinit var changeUC: ChangeTaskStateUseCase - private lateinit var ui: ChangeTaskStateUI private lateinit var getAllTasks: GetAllTasksUseCase + private lateinit var ui: ChangeTaskStateUI private val printed = mutableListOf() private lateinit var task: Task - private val alice = DummyData.users[0] + private lateinit var alice: User @BeforeEach fun setUp() { - // reset in-memory lists + // reset all in-memory data + DummyData.users.clear() DummyData.tasks.clear() DummyData.states.clear() + // add one user (needed for assignedBy / filter logic) + alice = User("U1", "alice", "pw", UserRole.MATE) + DummyData.users += alice + + // one task in TODO state S1 task = Task( - id = "T1", - projectId = "P1", - title = "Demo", - description = null, - stateId = "S1", - assignedToUserId = alice.id, - createByUserId = alice.id + id = "T1", + projectId = "P1", + title = "Demo", + description = null, + stateId = "S1", + assignedToUserId = alice.id, + createByUserId = alice.id ) - DummyData.tasks += task + + // two possible states for project P1 DummyData.states += State("S1", "TODO", "P1") DummyData.states += State("S2", "DONE", "P1") - viewer = mockk(relaxed = true) { - every { show(capture(printed)) } just Runs - } - reader = mockk() - changeUC = mockk() + // mocks + viewer = mockk(relaxed = true) { every { show(capture(printed)) } just Runs } + reader = mockk() + changeUC = mockk() getAllTasks = mockk() - ui = ChangeTaskStateUI(changeUC, getAllTasks, viewer, reader) + + // stub before UI instantiation every { getAllTasks.invoke() } returns listOf(task) + + ui = ChangeTaskStateUI(changeUC, getAllTasks, viewer, reader) printed.clear() } @Test fun `success moves to chosen state`() { + // pick task #1, then state #1 (TODO) every { reader.read() } returnsMany listOf("1", "1") every { changeUC.invoke("T1", "S1") } returns Result.success(task.copy(stateId = "S1")) ui.run() - assertThat(printed.last()).contains("Task T1 moved to TODO") verify { changeUC.invoke("T1", "S1") } + assertThat(printed.last()).contains("Task T1 moved to TODO") } @Test fun `no states defined prints message and returns`() { - // clear states so possible.isEmpty() triggers - DummyData.states.clear() - every { reader.read() } returns "1" + DummyData.states.clear() // trigger "no states" path + every { reader.read() } returns "1" // choose task ui.run() - assertThat(printed.last()).contains("No states defined for project P1") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("No states defined for project P1") } @Test @@ -87,8 +97,8 @@ class ChangeTaskStateUITest { ui.run() - assertThat(printed.last()).contains("Cancelled.") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("Cancelled.") } @Test @@ -97,8 +107,8 @@ class ChangeTaskStateUITest { ui.run() - assertThat(printed.last()).contains("Invalid selection") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("Invalid selection") } @Test @@ -107,8 +117,8 @@ class ChangeTaskStateUITest { ui.run() - assertThat(printed.last()).contains("Cancelled.") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("Cancelled.") } @Test @@ -117,8 +127,8 @@ class ChangeTaskStateUITest { ui.run() - assertThat(printed.last()).contains("Invalid selection") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("Invalid selection") } @Test diff --git a/src/test/kotlin/presentation/task/CreateTaskUITest.kt b/src/test/kotlin/presentation/task/CreateTaskUITest.kt index 4d3f5c6..dca8bd6 100644 --- a/src/test/kotlin/presentation/task/CreateTaskUITest.kt +++ b/src/test/kotlin/presentation/task/CreateTaskUITest.kt @@ -1,187 +1,164 @@ -//package com.berlin.presentation.task -// -//import com.berlin.data.DummyData -//import com.berlin.domain.exception.InvalidTaskTitle -//import com.berlin.domain.exception.TaskAlreadyExistsException -//import com.berlin.domain.model.Task -//import com.berlin.domain.model.User -//import com.berlin.domain.usecase.task.CreateTaskUseCase -//import com.berlin.presentation.io.Reader -//import com.berlin.presentation.io.Viewer -//import com.google.common.truth.Truth.assertThat -//import io.mockk.* -//import org.junit.jupiter.api.BeforeEach -//import org.junit.jupiter.api.Test -// -//class CreateTaskUITest { -// -// private val printed = mutableListOf() -// private val viewer: Viewer = mockk(relaxed = true) { -// every { show(capture(printed)) } just Runs -// } -// private val reader: Reader = mockk() -// private val createUC: CreateTaskUseCase = mockk() -// private val currentUser: User = DummyData.users.first() -// private lateinit var ui: CreateTaskUI -// -// @BeforeEach -// fun reset() { -// DummyData.tasks.clear() -// printed.clear() -// ui = CreateTaskUI(createUC, currentUser, viewer, reader) -// } -// -// @Test -// fun `creates task and prints success`() { -// every { reader.read() } returnsMany listOf("1", "1", "1", "Feature X", "") -// every { createUC.invoke(any(), any(), any(), any(), any(), any()) } answers { -// Result.success( -// Task( -// id = "T42", -// projectId = firstArg(), -// title = secondArg(), -// description = arg(2), -// stateId = arg(3), -// assignedToUserId = arg(5), -// createByUserId = arg(4) -// ) -// ) -// } -// -// ui.run() -// -// verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } -// assertThat(printed.last()).contains("Task created: id=T42") -// } -// -// @Test -// fun `prints Cancelled when user aborts`() { -// every { reader.read() } returns "X" -// -// ui.run() -// -// verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } -// assertThat(printed.last()).contains("Cancelled.") -// } -// -// @Test -// fun `empty title shows invalid selection message`() { -// // project, state, user selected; then blank title -// every { reader.read() } returnsMany listOf("1", "1", "1", "") -// -// ui.run() -// -// verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } -// assertThat(printed.last()).contains("Invalid selection") -// } -// -// @Test -// fun `failure from use case is printed`() { -// every { reader.read() } returnsMany listOf("1", "1", "1", "Bug fix", "") -// every { -// createUC.invoke( -// any(), any(), any(), any(), any(), any() -// ) -// } returns Result.failure(IllegalStateException("db down")) -// -// ui.run() -// -// verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } -// assertThat(printed.last()).contains("db down") -// } -// -// @Test -// fun `invalid user index prints error message`() { -// // project=1, state=1, then bad user index=99 -// every { reader.read() } returnsMany listOf("1", "1", "99") -// -// ui.run() -// -// verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } -// assertThat(printed.last().lowercase()).contains("invalid selection") -// } -// -// @Test -// fun `invalid state index prints error message`() { -// every { reader.read() } returnsMany listOf("1", "57") -// -// ui.run() -// -// verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } -// assertThat(printed.last().lowercase()).contains("invalid selection") -// } -// -// @Test -// fun `invalid project index prints error message`() { -// every { reader.read() } returns "79" -// -// ui.run() -// -// verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } -// assertThat(printed.last().lowercase()).contains("invalid selection") -// } -// -// @Test -// fun `null description still creates task`() { -// every { reader.read() } returnsMany listOf("1", "1", "1", "Doc title", null) -// every { createUC.invoke(any(), any(), null, any(), any(), any()) } returns Result.success( -// Task( -// id = "T99", -// projectId = "P1", -// title = "Doc title", -// description = null, -// stateId = "S1", -// assignedToUserId = DummyData.users[1].id, -// createByUserId = currentUser.id -// ) -// ) -// -// ui.run() -// -// verify { createUC.invoke(any(), any(), null, any(), any(), any()) } -// assertThat(printed.last()).contains("Task created: id=T99") -// } -// -// @Test -// fun `shows InvalidTaskTitle when use case throws that exception`() { -// every { reader.read() } returnsMany listOf("1", "1", "1", "BadTitle", "") -// -// every { -// createUC.invoke(any(), any(), any(), any(), any(), any()) -// } throws InvalidTaskTitle("title ruled invalid") -// -// ui.run() -// -// assertThat(printed.last()).contains("Invalid task title") -// verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } -// } -// -// @Test -// fun `shows TaskAlreadyExistsException when use case returns failure`() { -// every { reader.read() } returnsMany listOf("1", "1", "1", "MyTask", "") -// every { createUC.invoke(any(), any(), any(), any(), any(), any()) } returns Result.failure( -// TaskAlreadyExistsException("the task already existed") -// ) -// -// ui.run() -// -// assertThat(printed.last()).contains("the task already existed") -// verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } -// } -// -// @Test -// fun `use case throwing TaskAlreadyExistsException is caught and shows existed message`() { -// every { reader.read() } returnsMany listOf("1", "1", "1", "MyTitle", "") -// -// every { -// createUC.invoke(any(), any(), any(), any(), any(), any()) -// } throws TaskAlreadyExistsException("already there") -// -// ui.run() -// -// verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } -// -// assertThat(printed.last()).contains("the task already existed") -// } -// -//} +package com.berlin.presentation.task + +import com.berlin.data.DummyData +import com.berlin.domain.exception.InvalidTaskTitle +import com.berlin.domain.exception.TaskAlreadyExistsException +import com.berlin.domain.model.Task +import com.berlin.domain.model.User +import com.berlin.domain.usecase.task.CreateTaskUseCase +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer +import data.UserCache +import com.google.common.truth.Truth.assertThat +import io.mockk.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class CreateTaskUITest { + + private val printed = mutableListOf() + private val viewer: Viewer = mockk(relaxed = true) { + every { show(capture(printed)) } just Runs + } + private val reader: Reader = mockk() + private val createUC: CreateTaskUseCase = mockk() + private val userCache: UserCache = mockk() + private val currentUser: User = DummyData.users.first() + private lateinit var ui: CreateTaskUI + + @BeforeEach + fun reset() { + DummyData.tasks.clear() + printed.clear() + // Stub the cache to return our test user + every { userCache.currentUser } returns currentUser + ui = CreateTaskUI(createUC, userCache, viewer, reader) + } + + @Test + fun `creates task and prints success`() { + every { reader.read() } returnsMany listOf("1", "1", "1", "Feature X", "") + every { createUC.invoke(any(), any(), any(), any(), any(), any()) } answers { + Result.success( + Task( + id = "T42", + projectId = firstArg(), + title = secondArg(), + description = arg(2), + stateId = arg(3), + assignedToUserId = arg(5), + createByUserId = arg(4) + ) + ) + } + + ui.run() + + verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } + assertThat(printed.last()).contains("Task created: id=T42") + } + + @Test + fun `prints Cancelled when user aborts`() { + every { reader.read() } returns "X" + + ui.run() + + verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } + assertThat(printed.last()).contains("Cancelled.") + } + + @Test + fun `empty title shows invalid selection message`() { + // project, state, user selected; then blank title + every { reader.read() } returnsMany listOf("1", "1", "1", "") + + ui.run() + + verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } + assertThat(printed.last()).contains("Invalid selection") + } + + @Test + fun `failure from use case is printed`() { + every { reader.read() } returnsMany listOf("1", "1", "1", "Bug fix", "") + every { + createUC.invoke(any(), any(), any(), any(), any(), any()) + } returns Result.failure(IllegalStateException("db down")) + + ui.run() + + verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } + assertThat(printed.last()).contains("db down") + } + + @Test + fun `invalid user index prints error message`() { + // project=1, state=1, then bad user index=99 + every { reader.read() } returnsMany listOf("1", "1", "99") + + ui.run() + + verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } + assertThat(printed.last().lowercase()).contains("invalid selection") + } + + @Test + fun `invalid state index prints error message`() { + every { reader.read() } returnsMany listOf("1", "57") + + ui.run() + + verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } + assertThat(printed.last().lowercase()).contains("invalid selection") + } + + @Test + fun `invalid project index prints error message`() { + every { reader.read() } returns "79" + + ui.run() + + verify(exactly = 0) { createUC.invoke(any(), any(), any(), any(), any(), any()) } + assertThat(printed.last().lowercase()).contains("invalid selection") + } + + @Test + fun `shows InvalidTaskTitle when use case throws that exception`() { + every { reader.read() } returnsMany listOf("1", "1", "1", "BadTitle", "") + every { + createUC.invoke(any(), any(), any(), any(), any(), any()) + } throws InvalidTaskTitle("title ruled invalid") + + ui.run() + + assertThat(printed.last()).contains("Invalid task title") + verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } + } + + @Test + fun `shows TaskAlreadyExistsException when use case returns failure`() { + every { reader.read() } returnsMany listOf("1", "1", "1", "MyTask", "") + every { createUC.invoke(any(), any(), any(), any(), any(), any()) } returns Result.failure( + TaskAlreadyExistsException("the task already existed") + ) + + ui.run() + + assertThat(printed.last()).contains("the task already existed") + verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } + } + + @Test + fun `use case throwing TaskAlreadyExistsException is caught and shows existed message`() { + every { reader.read() } returnsMany listOf("1", "1", "1", "MyTitle", "") + every { + createUC.invoke(any(), any(), any(), any(), any(), any()) + } throws TaskAlreadyExistsException("already there") + + ui.run() + + verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } + assertThat(printed.last()).contains("the task already existed") + } +} diff --git a/src/test/kotlin/presentation/task/DeleteTaskUITest.kt b/src/test/kotlin/presentation/task/DeleteTaskUITest.kt index 1befe09..58db663 100644 --- a/src/test/kotlin/presentation/task/DeleteTaskUITest.kt +++ b/src/test/kotlin/presentation/task/DeleteTaskUITest.kt @@ -7,6 +7,7 @@ import com.berlin.domain.usecase.task.DeleteTaskUseCase import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import com.berlin.presentation.task.DeleteTaskUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -22,7 +23,6 @@ class DeleteTaskUITest { private val deleteUC: DeleteTaskUseCase = mockk() private val getAllTasks: GetAllTasksUseCase = mockk() - private lateinit var task: Task private lateinit var ui: DeleteTaskUI @@ -77,6 +77,7 @@ class DeleteTaskUITest { ui.run() verify(exactly = 0) { deleteUC.invoke(any()) } + assertThat(DummyData.tasks).contains(task) assertThat(printed.last()).contains("Cancelled.") } @@ -109,9 +110,10 @@ class DeleteTaskUITest { ui.run() + // task should not have been removed assertThat(DummyData.tasks).contains(task) + // and we show the invalid-id message assertThat(printed.last()).contains("invalid task id") verify(exactly = 1) { deleteUC.invoke(task.id) } } - } diff --git a/src/test/kotlin/presentation/task/UpdateTaskUITest.kt b/src/test/kotlin/presentation/task/UpdateTaskUITest.kt index 576910a..6b9354a 100644 --- a/src/test/kotlin/presentation/task/UpdateTaskUITest.kt +++ b/src/test/kotlin/presentation/task/UpdateTaskUITest.kt @@ -1,5 +1,4 @@ -package presentation.task - +package com.berlin.presentation.task import com.berlin.data.DummyData import com.berlin.domain.exception.InvalidTaskTitle @@ -11,7 +10,6 @@ import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.domain.usecase.task.UpdateTaskUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -import com.berlin.presentation.task.UpdateTaskUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -23,7 +21,6 @@ class UpdateTaskUITest { private lateinit var reader: Reader private lateinit var updateUC: UpdateTaskUseCase private lateinit var getAllTasks: GetAllTasksUseCase - private lateinit var ui: UpdateTaskUI private val printed = mutableListOf() @@ -70,10 +67,10 @@ class UpdateTaskUITest { @Test fun `success when title only changes`() { every { reader.read() } returnsMany listOf( - "1", - "NewTitle", - "", - "X" + "1", // choose task #1 + "NewTitle", // new title + "", // blank → keep old desc + "X" // cancel assignee → keep old ) every { updateUC.invoke("T1", title = "NewTitle", description = null, assignedToUserId = null) @@ -91,8 +88,8 @@ class UpdateTaskUITest { fun `success when description only changes`() { every { reader.read() } returnsMany listOf( "1", - "", - "NewDesc", + "", // keep title + "NewDesc", // new description "X" ) every { @@ -111,9 +108,9 @@ class UpdateTaskUITest { fun `success when assignee only changes`() { every { reader.read() } returnsMany listOf( "1", - "", - "", - "2" + "", // keep title + "", // keep desc + "2" // choose user #2 ) every { updateUC.invoke("T1", title = null, description = null, assignedToUserId = "U2") @@ -130,7 +127,10 @@ class UpdateTaskUITest { @Test fun `success when nothing changes`() { every { reader.read() } returnsMany listOf( - "1", "", "", "X" + "1", + "", + "", + "X" ) every { updateUC.invoke("T1", title = null, description = null, assignedToUserId = null) @@ -222,5 +222,4 @@ class UpdateTaskUITest { assertThat(printed.last()).contains("Task not founc") } - }