From c864908e773fe86b330bdf4832c403c5afaf0a21 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Wed, 7 May 2025 00:35:38 +0300 Subject: [PATCH 01/12] 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 02/12] 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 7cfc53acf115443ac68415c32226bcbb46b5ca7e Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Thu, 8 May 2025 13:27:27 +0300 Subject: [PATCH 03/12] 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 04/12] 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 05/12] 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 06/12] 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 07/12] 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 08/12] 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 3c2dadaa0267a42377256e47b347b451dbdcd982 Mon Sep 17 00:00:00 2001 From: Menna Srour Date: Fri, 9 May 2025 07:07:52 +0300 Subject: [PATCH 09/12] 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 10/12] 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 0340e471e357836df74ef3de1bbeeb2c193b72b7 Mon Sep 17 00:00:00 2001 From: Abdulrahman Ragab Date: Fri, 9 May 2025 09:05:02 +0300 Subject: [PATCH 11/12] 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 12/12] 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