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/csv_files/audit.csv b/csv_files/audit.csv new file mode 100644 index 0000000..fa63147 --- /dev/null +++ b/csv_files/audit.csv @@ -0,0 +1,7 @@ +"Audit Id","Timestamp","CreatedBy","Audit Action","Changes Description","Entity Type","Entity Id" +"AUDITDD_52700","1746681952700","user1234","CREATE","","TASK","titlett_52651" +"AUDITDD_16009","1746682016009","user1234","UPDATE","","TASK","titlett_52651" +"AUDITDD_26796","1746682026796","user1234","DELETE","","TASK","titlett_52651" +"AUDITDDDD_29153","1746685829153","user1234","CREATE","","TASK","titlett_29143" +"AUDITDD_39457","1746685839457","user1234","UPDATE","","TASK","titlett_29143""AUDITDD_97145","1746688997145","user1234","CREATE","","PROJECT","taskmangermmmm_97132" +"AUDITDDDD_44756","1746689044756","user1234","UPDATE","","PROJECT","taskmangermmmm_97132" diff --git a/csv_files/project.csv b/csv_files/project.csv new file mode 100644 index 0000000..44c59bf --- /dev/null +++ b/csv_files/project.csv @@ -0,0 +1,2 @@ +"Project Id","Project Name","Description","States","Tasks" +"taskmangermmmm_97132","title updated","the ultimate project","[]","[]" diff --git a/csv_files/task.csv b/csv_files/task.csv index 7082b73..3c93333 100644 --- a/csv_files/task.csv +++ b/csv_files/task.csv @@ -1,2 +1,3 @@ "Task Id","Project Id","Title","Description","State Id","Assigned To User Id","Create By User Id" "dfgsdgfdd_66055","P1","dfgsdgfd","gasdfgfdaf","S1","U1","U1" +"titlett_29143","P1","title","description","S1","U2","user1234" diff --git a/csv_files/user.csv b/csv_files/user.csv new file mode 100644 index 0000000..4debb74 --- /dev/null +++ b/csv_files/user.csv @@ -0,0 +1,2 @@ +"User Id","UserName","Password","User Role" +"ahmadmmmm_70365","ahmad","d2dfac4d62e5470ab0c92d6f425b977d","MATE" diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index fba2b4a..be3a929 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -4,7 +4,6 @@ import com.berlin.di.uiModule import com.berlin.di.useCaseModule import com.berlin.di.* import com.berlin.presentation.MainMenuUI -import com.berlin.presentation.authService.AuthenticateUserUi import org.koin.core.context.startKoin import org.koin.mp.KoinPlatform.getKoin @@ -14,8 +13,10 @@ fun main() { printLogger() modules(dataModule, appModule, useCaseModule, uiModule) } - val mainMenu: MainMenuUI = getKoin().get() mainMenu.run() -} \ No newline at end of file +} + + +// autit -> project [tasks] \ No newline at end of file diff --git a/src/main/kotlin/data/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/UserCache.kt b/src/main/kotlin/data/UserCache.kt index 06841f8..f637491 100644 --- a/src/main/kotlin/data/UserCache.kt +++ b/src/main/kotlin/data/UserCache.kt @@ -2,6 +2,8 @@ package data import com.berlin.domain.model.User -object UserCache { - var currentUser: User? = null +class UserCache( + admin: User +) { + var currentUser: User = admin } \ No newline at end of file diff --git a/src/main/kotlin/data/Audit/AuditRepositoryImpl.kt b/src/main/kotlin/data/audit/AuditRepositoryImpl.kt similarity index 100% rename from src/main/kotlin/data/Audit/AuditRepositoryImpl.kt rename to src/main/kotlin/data/audit/AuditRepositoryImpl.kt diff --git a/src/main/kotlin/data/authentication/AuthenticationRepositoryImpl.kt b/src/main/kotlin/data/authentication/AuthenticationRepositoryImpl.kt index 90b54f5..54ac2a9 100644 --- a/src/main/kotlin/data/authentication/AuthenticationRepositoryImpl.kt +++ b/src/main/kotlin/data/authentication/AuthenticationRepositoryImpl.kt @@ -3,18 +3,14 @@ package com.berlin.data.authentication import com.berlin.data.BaseDataSource import com.berlin.domain.exception.UserNotFoundException import com.berlin.domain.exception.UserNotLoggedInException -import com.berlin.domain.hashPassword.HashingPassword -import com.berlin.domain.hashPassword.MD5Hasher -import com.berlin.domain.helper.IdGenerator -import com.berlin.domain.helper.IdGeneratorImplementation import com.berlin.domain.model.User -import com.berlin.domain.model.UserRole import com.berlin.domain.repository.AuthenticationRepository import data.UserCache import kotlin.Result.Companion.failure class AuthenticationRepositoryImpl( + private val userCache: UserCache, private val userDataSource: BaseDataSource ): AuthenticationRepository { @@ -45,11 +41,11 @@ class AuthenticationRepositoryImpl( override fun getCurrentUser(): Result { - val user = UserCache.currentUser + val user = userCache.currentUser return if (user != null) { Result.success(user) } else { - Result.failure(UserNotLoggedInException("No one logged in")) + failure(UserNotLoggedInException("No one logged in")) } diff --git a/src/main/kotlin/data/csv_data_source/CsvDataSource.kt b/src/main/kotlin/data/csvDataSource/CsvDataSource.kt similarity index 100% rename from src/main/kotlin/data/csv_data_source/CsvDataSource.kt rename to src/main/kotlin/data/csvDataSource/CsvDataSource.kt diff --git a/src/main/kotlin/data/state/StateRepositoryImpl.kt b/src/main/kotlin/data/state/StateRepositoryImpl.kt index c7c1885..ca47270 100644 --- a/src/main/kotlin/data/state/StateRepositoryImpl.kt +++ b/src/main/kotlin/data/state/StateRepositoryImpl.kt @@ -2,54 +2,60 @@ 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.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) + private val taskDataSource: BaseDataSource, +) : StateRepository { + 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 - .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 getStateById(stateId: String): Result = + stateDataSource.getById(stateId) + ?.let { success(it) } + ?: failure(StateNotFoundException(stateId)) + + override fun getAllStates(): List { + return stateDataSource.getAll() } } \ No newline at end of file diff --git a/src/main/kotlin/data/task/TaskRepositoryImpl.kt b/src/main/kotlin/data/task/TaskRepositoryImpl.kt index 54d3835..37729d2 100644 --- a/src/main/kotlin/data/task/TaskRepositoryImpl.kt +++ b/src/main/kotlin/data/task/TaskRepositoryImpl.kt @@ -1,4 +1,4 @@ -package com.berlin.data.memory +package com.berlin.data.task import com.berlin.data.BaseDataSource import com.berlin.domain.exception.InvalidTaskException diff --git a/src/main/kotlin/di/appModule.kt b/src/main/kotlin/di/appModule.kt index 75f61c9..1042001 100644 --- a/src/main/kotlin/di/appModule.kt +++ b/src/main/kotlin/di/appModule.kt @@ -1,14 +1,17 @@ package com.berlin.di -import com.berlin.domain.hashPassword.HashingPassword +import com.berlin.domain.hashPassword.HashingString import com.berlin.domain.hashPassword.MD5Hasher -import com.berlin.domain.helper.IdGeneratorImplementation -import com.berlin.domain.helper.IdGenerator +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.presentation.io.ConsoleReader import com.berlin.presentation.io.ConsoleViewer import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import data.UserCache import org.koin.dsl.module @@ -17,5 +20,12 @@ val appModule = module { single { ConsoleReader() } single { IdGeneratorImplementation() } single { IdGeneratorImplementation() } - single { MD5Hasher() } + single { MD5Hasher() } + + single { + UserCache( + User("user1234", "admin", "1212", UserRole.ADMIN) + ) + } + } diff --git a/src/main/kotlin/di/dataModule.kt b/src/main/kotlin/di/dataModule.kt index baccacb..cb1691c 100644 --- a/src/main/kotlin/di/dataModule.kt +++ b/src/main/kotlin/di/dataModule.kt @@ -1,19 +1,16 @@ package com.berlin.di + import com.berlin.data.Audit.AuditRepositoryImpl import com.berlin.data.BaseDataSource import com.berlin.data.BaseSchema import com.berlin.data.authentication.AuthenticationRepositoryImpl import com.berlin.data.csv_data_source.CsvDataSource -import com.berlin.data.memory.TaskRepositoryImpl +import com.berlin.data.task.TaskRepositoryImpl import com.berlin.data.project.ProjectRepositoryImpl import com.berlin.data.schema.* import com.berlin.data.state.StateRepositoryImpl import com.berlin.domain.model.* -import com.berlin.domain.repository.AuditRepository -import com.berlin.domain.repository.AuthenticationRepository -import com.berlin.domain.repository.ProjectRepository -import com.berlin.domain.repository.StateRepository -import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.repository.* import org.koin.core.qualifier.named import org.koin.dsl.module @@ -22,61 +19,49 @@ val dataModule = module { single>(named("UserSchema")) { UserSchema( - fileName = "user.csv", - header = listOf("User Id", "UserName", "Password", "User Role") + fileName = "user.csv", header = listOf("User Id", "UserName", "Password", "User Role") ) } single>(named("ProjectSchema")) { ProjectSchema( - fileName = "project.csv", - header = listOf("Project Id", "Project Name", "Description", "States", "Tasks") + fileName = "project.csv", header = listOf("Project Id", "Project Name", "Description", "States", "Tasks") ) } single>(named("AuditSchema")) { AuditSchema( - fileName = "audit.csv", - header = listOf( - "Audit Id", - "Timestamp", - "CreatedBy", - "Audit Action", - "Changes Description", - "Entity Type", - "Entity Id" + fileName = "audit.csv", header = listOf( + "Audit Id", "Timestamp", "CreatedBy", "Audit Action", "Changes Description", "Entity Type", "Entity Id" ) ) } single>(named("StateSchema")) { - StateSchema(fileName = "state.csv", - header = listOf("State Id", "Name", "Project Id")) } + StateSchema( + fileName = "state.csv", header = listOf("State Id", "Name", "Project Id") + ) + } single>(named("TaskSchema")) { TaskSchema( - fileName = "task.csv", - header = listOf( - "Task Id", - "Project Id", - "Title", - "Description", - "State Id", - "Assigned To User Id", - "Create By User Id" + fileName = "task.csv", header = listOf( + "Task Id", "Project Id", "Title", "Description", "State Id", "Assigned To User Id", "Create By User Id" ) ) } - single>(named("UserDataSource")){ CsvDataSource("csv_files", get(named("UserSchema"))) } - single>(named("ProjectDataSource")){ CsvDataSource("csv_files", get(named("ProjectSchema"))) } - single>(named("TaskDataSource")){ CsvDataSource("csv_files", get(named("TaskSchema"))) } - single>(named("StateDataSource")){ CsvDataSource("csv_files", get(named("StateSchema"))) } - single>(named("AuditDataSource")){ CsvDataSource("csv_files", get(named("AuditSchema"))) } - + single>(named("UserDataSource")) { CsvDataSource("csv_files", get(named("UserSchema"))) } + single>(named("ProjectDataSource")) { + CsvDataSource( + "csv_files", get(named("ProjectSchema")) + ) + } + single>(named("TaskDataSource")) { CsvDataSource("csv_files", get(named("TaskSchema"))) } + single>(named("StateDataSource")) { CsvDataSource("csv_files", get(named("StateSchema"))) } + single>(named("AuditDataSource")) { CsvDataSource("csv_files", get(named("AuditSchema"))) } - single { ProjectRepositoryImpl(get(named("ProjectDataSource"))) } - single { TaskRepositoryImpl(get(named("TaskDataSource"))) } - single { AuditRepositoryImpl(get(named("AuditDataSource"))) } - single { StateRepositoryImpl(get(named("StateDataSource")),get(named("TaskDataSource"))) } - single { AuthenticationRepositoryImpl(get(named("UserDataSource"))) } - single { AuthenticationRepositoryImpl(get(named("UserDataSource"))) } + single { ProjectRepositoryImpl(get(named("ProjectDataSource"))) } + single { TaskRepositoryImpl(get(named("TaskDataSource"))) } + single { AuditRepositoryImpl(get(named("AuditDataSource"))) } + single { StateRepositoryImpl(get(named("StateDataSource")), get(named("TaskDataSource"))) } + single { AuthenticationRepositoryImpl(get(), get(named("UserDataSource"))) } } \ No newline at end of file diff --git a/src/main/kotlin/di/uiModule.kt b/src/main/kotlin/di/uiModule.kt index 0d14083..9c6e7ed 100644 --- a/src/main/kotlin/di/uiModule.kt +++ b/src/main/kotlin/di/uiModule.kt @@ -1,30 +1,33 @@ -package com.berlin.di + package com.berlin.di -import com.berlin.data.DummyData -import com.berlin.domain.model.User import com.berlin.domain.usecase.authService.GetUserByIDUseCase import com.berlin.presentation.MainMenuUI +import com.berlin.presentation.audit.AuditByProjectUI +import com.berlin.presentation.audit.AuditByTaskUI +import com.berlin.presentation.audit.AuditByUserUI import com.berlin.presentation.authService.* +import com.berlin.presentation.state.* import com.berlin.presentation.project.* import com.berlin.presentation.task.* -import org.koin.core.qualifier.named +import data.UserCache import org.koin.dsl.module val uiModule = module { - single(named("currentUser")) { DummyData.users.first() } +// single(named("currentUser")) { DummyData.users.first() } - single { CreateTaskUI(get(), get(named("currentUser")), get(), get()) } + single { CreateTaskUI(get(), get(), get(), get()) } single { AssignTaskUI(get(), get(), get(), get()) } - single { DeleteTaskUI(get(), get(), get(),get()) } + single { 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 { CreateMateUI(get(),get(),get()) } + single { AuthenticateUserUI(get(),get(),get()) } single { FetchAllUsersUI(get(),get()) } single { GetUserByIDUI(get(),get(),get()) } single { CreateProjectUi(get(),get(),get()) } @@ -32,8 +35,16 @@ val uiModule = module { single { GetAllProjectsUi(get(),get()) } single { GetProjectByIdUi(get(),get(),get()) } single { UpdateProjectUi(get(),get(),get(),get(),get()) } + single { AuditByProjectUI(get(), get(), get(), get()) } + single { AuditByTaskUI(get(), get(), get(), get(), get()) } + single { AuditByUserUI(get(), get(), get()) } + 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( @@ -46,20 +57,30 @@ val uiModule = module { get(), get(), - get(), - get(), + get(), get(), get(), get(), + get(), + get(), + get(), + get(), + get(), + get(), get(), get(), get(), get(), + + get(), + get() ), viewer = get(), - reader = get() + reader = get(), + authUi = get(), + userCache=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..01e6ed3 100644 --- a/src/main/kotlin/di/useCaseModule.kt +++ b/src/main/kotlin/di/useCaseModule.kt @@ -4,30 +4,39 @@ import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase import com.berlin.domain.usecase.auditSystem.GetAuditLogsByProjectIdUseCase import com.berlin.domain.usecase.auditSystem.GetAuditLogsByTaskIdUseCase import com.berlin.domain.usecase.auditSystem.GetAuditLogsByUserIdUseCase -import com.berlin.domain.usecase.authService.CreationOfMateUseCase +import com.berlin.domain.usecase.authService.CreateMateUseCase import com.berlin.domain.usecase.authService.FetchAllUsersUseCase import com.berlin.domain.usecase.authService.GetUserByIDUseCase -import com.berlin.domain.usecase.authService.GettingUsersLoggedInUseCase +import com.berlin.domain.usecase.authService.GetUserLoggedInUseCase import com.berlin.domain.usecase.project.* +import com.berlin.domain.usecase.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 data.UserCache import domain.usecase.authService.AuthenticateUserUseCase import org.koin.dsl.module val useCaseModule = module { - single { CreateTaskUseCase(get(), get()) } - single { AssignTaskUseCase(get()) } - single { DeleteTaskUseCase(get()) } + single { CreateTaskUseCase(get(), get(), get()) } + single { AssignTaskUseCase(get(), get(), get()) } + single { DeleteTaskUseCase(get(), get(), get()) } single { GetTasksByProjectUseCase(get()) } - single { UpdateTaskUseCase(get()) } - single { ChangeTaskStateUseCase(get()) } + single { UpdateTaskUseCase(get(), get(), get()) } + single { ChangeTaskStateUseCase(get(), get(), get()) } single { GetTaskByIdUseCase(get()) } single { GetAllTasksUseCase(get()) } - single { CreateProjectUseCase(get(), get()) } + single { CreateProjectUseCase(get(), get(), get(), get()) } single { GetAllProjectsUseCase(get()) } - single { DeleteProjectUseCase(get()) } + single { DeleteProjectUseCase(get(), get(), get()) } single { GetProjectByIdUseCase(get()) } - single { UpdateProjectUseCase(get()) } + single { UpdateProjectUseCase(get(), get(), get() ) } single { AddAuditLogUseCase(get(), get()) } single { GetAuditLogsByProjectIdUseCase(get()) } @@ -35,8 +44,17 @@ val useCaseModule = module { single { GetAuditLogsByUserIdUseCase(get()) } single { GetUserByIDUseCase(get()) } - single { GettingUsersLoggedInUseCase(get()) } + single { GetUserLoggedInUseCase(get()) } single { FetchAllUsersUseCase(get()) } - single { AuthenticateUserUseCase(get(), get()) } - single { CreationOfMateUseCase(get(), get(), get()) } -} + single { AuthenticateUserUseCase(get(),get(), get()) } + single { CreateMateUseCase(get(), get(), get()) } + + 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/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 85968f8..5c82938 100644 --- a/src/main/kotlin/domain/repository/StateRepository.kt +++ b/src/main/kotlin/domain/repository/StateRepository.kt @@ -5,10 +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/auditSystem/AddAuditLogUseCase.kt b/src/main/kotlin/domain/usecase/auditSystem/AddAuditLogUseCase.kt index a38b114..43094ea 100644 --- a/src/main/kotlin/domain/usecase/auditSystem/AddAuditLogUseCase.kt +++ b/src/main/kotlin/domain/usecase/auditSystem/AddAuditLogUseCase.kt @@ -1,6 +1,6 @@ package com.berlin.domain.usecase.auditSystem -import com.berlin.domain.helper.IdGenerator +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.domain.model.AuditAction import com.berlin.domain.model.AuditLog import com.berlin.domain.model.EntityType @@ -14,16 +14,14 @@ class AddAuditLogUseCase( fun addAuditLog( createdByUserId:String, auditAction: AuditAction, - changesDescription: String, + changesDescription: String? = null, entityType: EntityType, entityId: String, - timestamp: Long = System.currentTimeMillis() ): Result { return try { - val id = idGenerator.generateId("AUDIT") val auditLog = AuditLog( - id = id, - timestamp = timestamp, + id = idGenerator.generateId("AUDIT"), + timestamp = System.currentTimeMillis(), createdByUserId=createdByUserId, auditAction = auditAction, changesDescription = changesDescription, diff --git a/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCase.kt b/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCase.kt index d819a41..00cfe5e 100644 --- a/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCase.kt +++ b/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCase.kt @@ -12,7 +12,7 @@ class GetAuditLogsByTaskIdUseCase( if (!validateTaskId(taskId)) throw IllegalArgumentException("Task ID must not be empty, blank, or purely numeric") - return auditRepository.getAuditLogsByProjectId(taskId) + return auditRepository.getAuditLogsByTaskId(taskId) } diff --git a/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByUserIdUseCase.kt b/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByUserIdUseCase.kt index 0f7af63..478d4ce 100644 --- a/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByUserIdUseCase.kt +++ b/src/main/kotlin/domain/usecase/auditSystem/GetAuditLogsByUserIdUseCase.kt @@ -18,4 +18,4 @@ class GetAuditLogsByUserIdUseCase( private fun validateUserId(projectId: String): Boolean = projectId.isNotBlank() && !(projectId.all { it.isDigit() }) -} +} \ No newline at end of file diff --git a/src/main/kotlin/domain/usecase/authService/AuthenticateUserUseCase.kt b/src/main/kotlin/domain/usecase/authService/AuthenticateUserUseCase.kt index 1bd517f..354a6e3 100644 --- a/src/main/kotlin/domain/usecase/authService/AuthenticateUserUseCase.kt +++ b/src/main/kotlin/domain/usecase/authService/AuthenticateUserUseCase.kt @@ -1,28 +1,29 @@ package domain.usecase.authService import com.berlin.domain.exception.InvalidCredentialsException -import com.berlin.domain.hashPassword.HashingPassword +import com.berlin.domain.hashPassword.HashingString import com.berlin.domain.model.User import com.berlin.domain.repository.AuthenticationRepository import data.UserCache class AuthenticateUserUseCase( + private val userCache: UserCache, private val repository: AuthenticationRepository, - private val hashingPassword: HashingPassword + private val hashingString: HashingString ) { fun login(userName: String, password: String): Result { if (userName.isEmpty() || password.isEmpty()) { return Result.failure(InvalidCredentialsException("No user found")) } - val cachedUser = UserCache.currentUser + val cachedUser = userCache.currentUser if (cachedUser != null && cachedUser.userName == userName) return Result.success(cachedUser) - val hashedPassword=hashingPassword.hashPassword(password) + val hashedPassword=hashingString.hashPassword(password) return repository.login(userName, hashedPassword).fold( onSuccess = { user -> - UserCache.currentUser = user + userCache.currentUser = user Result.success(user) }, onFailure = { exception -> diff --git a/src/main/kotlin/domain/usecase/authService/CreationOfMateUseCase.kt b/src/main/kotlin/domain/usecase/authService/CreateMateUseCase.kt similarity index 70% rename from src/main/kotlin/domain/usecase/authService/CreationOfMateUseCase.kt rename to src/main/kotlin/domain/usecase/authService/CreateMateUseCase.kt index ced1e6e..062f2f9 100644 --- a/src/main/kotlin/domain/usecase/authService/CreationOfMateUseCase.kt +++ b/src/main/kotlin/domain/usecase/authService/CreateMateUseCase.kt @@ -1,27 +1,27 @@ package com.berlin.domain.usecase.authService import com.berlin.domain.exception.InvalidCredentialsException -import com.berlin.domain.hashPassword.HashingPassword -import com.berlin.domain.helper.IdGenerator +import com.berlin.domain.hashPassword.HashingString +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.domain.model.User import com.berlin.domain.model.UserRole import com.berlin.domain.repository.AuthenticationRepository -class CreationOfMateUseCase( +class CreateMateUseCase( private val repository: AuthenticationRepository, private val idGenerator: IdGenerator, - private val hashingPassword: HashingPassword + private val hashingString: HashingString ) { fun createMate(userName: String, password: String): Result { if (userName.isEmpty() || password.isEmpty()) { return Result.failure(InvalidCredentialsException("Username and password must not be empty")) } - if (password.length < MAIN_PASSWORD_LENGHT) { + if (password.length < MAIN_PASSWORD_LENGTH) { return Result.failure(InvalidCredentialsException("Password less than 8 characters")) } - val hashedPassword=hashingPassword.hashPassword(password) + val hashedPassword=hashingString.hashPassword(password) val newUser = User( - id = idGenerator.generateId(password), + id = idGenerator.generateId(userName), userName = userName, password = hashedPassword, role = UserRole.MATE @@ -29,6 +29,6 @@ class CreationOfMateUseCase( return repository.createMate(newUser) } private companion object{ - const val MAIN_PASSWORD_LENGHT = 8 + const val MAIN_PASSWORD_LENGTH = 8 } } diff --git a/src/main/kotlin/domain/usecase/authService/GetUserByIDUseCase.kt b/src/main/kotlin/domain/usecase/authService/GetUserByIDUseCase.kt index b42fcb3..c24e877 100644 --- a/src/main/kotlin/domain/usecase/authService/GetUserByIDUseCase.kt +++ b/src/main/kotlin/domain/usecase/authService/GetUserByIDUseCase.kt @@ -9,8 +9,7 @@ class GetUserByIDUseCase( ) { fun getUserById(id: String): Result { if (!isIDValid(id)) - throw InvalidUserIdException("User ID can't be empty or just digits") - + return Result.failure(InvalidUserIdException("User ID can't be empty or just digits")) return repository.getUserById(id) } diff --git a/src/main/kotlin/domain/usecase/authService/GettingUsersLoggedInUseCase.kt b/src/main/kotlin/domain/usecase/authService/GetUserLoggedInUseCase.kt similarity index 89% rename from src/main/kotlin/domain/usecase/authService/GettingUsersLoggedInUseCase.kt rename to src/main/kotlin/domain/usecase/authService/GetUserLoggedInUseCase.kt index ace5a64..87969a9 100644 --- a/src/main/kotlin/domain/usecase/authService/GettingUsersLoggedInUseCase.kt +++ b/src/main/kotlin/domain/usecase/authService/GetUserLoggedInUseCase.kt @@ -1,7 +1,7 @@ package com.berlin.domain.usecase.authService import com.berlin.domain.model.User import com.berlin.domain.repository.AuthenticationRepository -class GettingUsersLoggedInUseCase( +class GetUserLoggedInUseCase( private val repository: AuthenticationRepository ) { fun getCurrentUser(): Result{ diff --git a/src/main/kotlin/domain/usecase/project/CreateProjectUseCase.kt b/src/main/kotlin/domain/usecase/project/CreateProjectUseCase.kt index 31411a3..6f46590 100644 --- a/src/main/kotlin/domain/usecase/project/CreateProjectUseCase.kt +++ b/src/main/kotlin/domain/usecase/project/CreateProjectUseCase.kt @@ -1,12 +1,18 @@ package com.berlin.domain.usecase.project -import com.berlin.domain.helper.IdGenerator +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.domain.repository.ProjectRepository import com.berlin.domain.model.Project +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class CreateProjectUseCase( private val projectRepository: ProjectRepository, private val idGenerator: IdGenerator, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { fun createNewProject(projectName: String, description: String?, stateId: List?, taskId: List?): Result { @@ -18,7 +24,19 @@ class CreateProjectUseCase( statesId = stateId, tasksId = taskId ) - return projectRepository.createProject(newProject) + + val createdProject = projectRepository.createProject(newProject) + + if (createdProject.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.CREATE, + entityType = EntityType.PROJECT, + entityId = newProject.id, + ) + } + + return createdProject .map { "Creation Successfully" } .recover { "Creation Failed" } } else { diff --git a/src/main/kotlin/domain/usecase/project/DeleteProjectUseCase.kt b/src/main/kotlin/domain/usecase/project/DeleteProjectUseCase.kt index bd0d9fa..768b478 100644 --- a/src/main/kotlin/domain/usecase/project/DeleteProjectUseCase.kt +++ b/src/main/kotlin/domain/usecase/project/DeleteProjectUseCase.kt @@ -1,9 +1,15 @@ package com.berlin.domain.usecase.project +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.repository.ProjectRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class DeleteProjectUseCase ( - private val projectRepository: ProjectRepository + private val projectRepository: ProjectRepository, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { fun deleteProject(projectId: String): Result { @@ -16,7 +22,18 @@ class DeleteProjectUseCase ( ) } - return projectRepository.deleteProject(projectId) + val deletedProject = projectRepository.deleteProject(projectId) + + if (deletedProject.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.DELETE, + entityType = EntityType.PROJECT, + entityId = projectId, + ) + } + + return deletedProject .map { "Deleted Successfully" } .recover { "Deletion Failed" } } diff --git a/src/main/kotlin/domain/usecase/project/UpdateProjectUseCase.kt b/src/main/kotlin/domain/usecase/project/UpdateProjectUseCase.kt index 3c3889b..8d3be27 100644 --- a/src/main/kotlin/domain/usecase/project/UpdateProjectUseCase.kt +++ b/src/main/kotlin/domain/usecase/project/UpdateProjectUseCase.kt @@ -1,16 +1,33 @@ package com.berlin.domain.usecase.project +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.repository.ProjectRepository import com.berlin.domain.model.Project +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class UpdateProjectUseCase ( - private val projectRepository: ProjectRepository + private val projectRepository: ProjectRepository, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { fun updateProject(project: Project): Result { if(!validateProjectName(project.name)) throw Exception("Project Name must not be empty or blank") - return projectRepository.updateProject(project) + val updatedProject = projectRepository.updateProject(project) + + if (updatedProject.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.UPDATE, + entityType = EntityType.PROJECT, + entityId = project.id, + ) + } + + return updatedProject .map { "Updated Successfully" } .recover { "Update Failed" } } diff --git a/src/main/kotlin/domain/usecase/state/CreateStateUseCase.kt b/src/main/kotlin/domain/usecase/state/CreateStateUseCase.kt index 1ed9235..20b871e 100644 --- a/src/main/kotlin/domain/usecase/state/CreateStateUseCase.kt +++ b/src/main/kotlin/domain/usecase/state/CreateStateUseCase.kt @@ -2,9 +2,9 @@ package com.berlin.domain.usecase.state import com.berlin.domain.exception.InvalidStateNameException -import com.berlin.domain.helper.IdGeneratorImplementation import com.berlin.domain.repository.StateRepository import com.berlin.domain.model.State +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation class CreateStateUseCase( private val stateRepository: StateRepository, 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 a084bb9..4acc5ce 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 @@ -13,16 +11,15 @@ 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") } else { - 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/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/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/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/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/domain/usecase/task/AssignTaskUseCase.kt b/src/main/kotlin/domain/usecase/task/AssignTaskUseCase.kt index 180dd0b..f921635 100644 --- a/src/main/kotlin/domain/usecase/task/AssignTaskUseCase.kt +++ b/src/main/kotlin/domain/usecase/task/AssignTaskUseCase.kt @@ -1,11 +1,18 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidAssigneeException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task +import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class AssignTaskUseCase( - private val taskRepository: TaskRepository + private val taskRepository: TaskRepository, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { operator fun invoke(taskId: String, newAssigneeId: String): Result { @@ -21,7 +28,19 @@ class AssignTaskUseCase( } val updated = original.copy(assignedToUserId = newAssigneeId) - return taskRepository.update(updated) + + val updatedTask = taskRepository.update(updated) + + if (updatedTask.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.UPDATE, + entityType = EntityType.TASK, + entityId = updated.id, + ) + } + + return updatedTask } private fun validateAssignee(id: String): Boolean = diff --git a/src/main/kotlin/domain/usecase/task/ChangeTaskStateUseCase.kt b/src/main/kotlin/domain/usecase/task/ChangeTaskStateUseCase.kt index 5b073e9..60cbf6a 100644 --- a/src/main/kotlin/domain/usecase/task/ChangeTaskStateUseCase.kt +++ b/src/main/kotlin/domain/usecase/task/ChangeTaskStateUseCase.kt @@ -1,11 +1,18 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskStateException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task +import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class ChangeTaskStateUseCase( - private val taskRepository: TaskRepository + private val taskRepository: TaskRepository, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { operator fun invoke(taskId: String, newStateId: String): Result { @@ -19,7 +26,18 @@ class ChangeTaskStateUseCase( } val updated = original.copy(stateId = newStateId) - return taskRepository.update(updated) + val updatedTask = taskRepository.update(updated) + + if (updatedTask.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.UPDATE, + entityType = EntityType.TASK, + entityId = updated.id, + ) + } + + return updatedTask } private fun validateStateId(stateId: String): Boolean = diff --git a/src/main/kotlin/domain/usecase/task/CreateTaskUseCase.kt b/src/main/kotlin/domain/usecase/task/CreateTaskUseCase.kt index 44f061b..aca6e57 100644 --- a/src/main/kotlin/domain/usecase/task/CreateTaskUseCase.kt +++ b/src/main/kotlin/domain/usecase/task/CreateTaskUseCase.kt @@ -2,13 +2,18 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskTitle import com.berlin.domain.exception.TaskAlreadyExistsException -import com.berlin.domain.helper.IdGeneratorImplementation +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.AuditLog +import com.berlin.domain.model.EntityType +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.berlin.domain.model.Task import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase class CreateTaskUseCase( private val taskRepository: TaskRepository, - private val defaultIdGenerator: IdGeneratorImplementation + private val defaultIdGenerator: IdGeneratorImplementation, + private val addAuditLogUseCase: AddAuditLogUseCase ) { operator fun invoke( projectId: String, @@ -31,7 +36,19 @@ class CreateTaskUseCase( if (!validateUniqueTask(newTask.id)) { throw TaskAlreadyExistsException("Task with id= ${newTask.id} and title = ${newTask.title} already exists") } - return taskRepository.create(newTask) + + val createdTask = taskRepository.create(newTask) + + if (createdTask.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = createByUserId, + auditAction = AuditAction.CREATE, + entityType = EntityType.TASK, + entityId = newTask.id, + ) + } + + return createdTask } else { throw InvalidTaskTitle("task title must be not empty or plank") } diff --git a/src/main/kotlin/domain/usecase/task/DeleteTaskUseCase.kt b/src/main/kotlin/domain/usecase/task/DeleteTaskUseCase.kt index bd99392..d38462e 100644 --- a/src/main/kotlin/domain/usecase/task/DeleteTaskUseCase.kt +++ b/src/main/kotlin/domain/usecase/task/DeleteTaskUseCase.kt @@ -1,10 +1,17 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.TaskNotFoundException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType +import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class DeleteTaskUseCase( private val taskRepository: TaskRepository, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { operator fun invoke(taskId: String): Result { if (!validateTaskId(taskId)) throw Exception("Project ID must not be empty or blank") @@ -14,7 +21,19 @@ class DeleteTaskUseCase( TaskNotFoundException("task with ID $taskId does not exist") ) } - return taskRepository.delete(taskId) + + val deleteTask = taskRepository.delete(taskId) + + if (deleteTask.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.DELETE, + entityType = EntityType.TASK, + entityId = taskId, + ) + } + + return deleteTask } private fun validateTaskId(taskId: String): Boolean = diff --git a/src/main/kotlin/domain/usecase/task/UpdateTaskUseCase.kt b/src/main/kotlin/domain/usecase/task/UpdateTaskUseCase.kt index a5e7d75..4b278ed 100644 --- a/src/main/kotlin/domain/usecase/task/UpdateTaskUseCase.kt +++ b/src/main/kotlin/domain/usecase/task/UpdateTaskUseCase.kt @@ -1,11 +1,18 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskTitle +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task +import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import data.UserCache class UpdateTaskUseCase( private val taskRepository: TaskRepository, + private val addAuditLogUseCase: AddAuditLogUseCase, + private val cashedUser: UserCache ) { operator fun invoke( @@ -24,11 +31,22 @@ class UpdateTaskUseCase( description = description ?: original.description, assignedToUserId = assignedToUserId ?: original.assignedToUserId ) - if (!validateTaskTitle(updated.title.trim())) { + + if (!validateTaskTitle(updated.title.trim())) throw InvalidTaskTitle("task title must be not empty or plank") - } else { - return taskRepository.update(updated) + + val updatedTask = taskRepository.create(updated) + + if (updatedTask.isSuccess) { + addAuditLogUseCase.addAuditLog( + createdByUserId = cashedUser.currentUser.id, + auditAction = AuditAction.UPDATE, + entityType = EntityType.TASK, + entityId = updated.id, + ) } + + return updatedTask } private fun validateTaskTitle(title: String): Boolean { diff --git a/src/main/kotlin/domain/helper/IdGenerator.kt b/src/main/kotlin/domain/usecase/utils/IDGenerator/IdGenerator.kt similarity index 81% rename from src/main/kotlin/domain/helper/IdGenerator.kt rename to src/main/kotlin/domain/usecase/utils/IDGenerator/IdGenerator.kt index 621a2c9..abbfe7b 100644 --- a/src/main/kotlin/domain/helper/IdGenerator.kt +++ b/src/main/kotlin/domain/usecase/utils/IDGenerator/IdGenerator.kt @@ -1,4 +1,4 @@ -package com.berlin.domain.helper +package com.berlin.domain.usecase.utils.IDGenerator import kotlin.random.Random diff --git a/src/main/kotlin/domain/helper/IdGeneratorImplementation.kt b/src/main/kotlin/domain/usecase/utils/IDGenerator/IdGeneratorImplementation.kt similarity index 89% rename from src/main/kotlin/domain/helper/IdGeneratorImplementation.kt rename to src/main/kotlin/domain/usecase/utils/IDGenerator/IdGeneratorImplementation.kt index 616e715..a34214f 100644 --- a/src/main/kotlin/domain/helper/IdGeneratorImplementation.kt +++ b/src/main/kotlin/domain/usecase/utils/IDGenerator/IdGeneratorImplementation.kt @@ -1,4 +1,4 @@ -package com.berlin.domain.helper +package com.berlin.domain.usecase.utils.IDGenerator class IdGeneratorImplementation : IdGenerator { override fun generateId( diff --git a/src/main/kotlin/domain/hashPassword/HashingPassword.kt b/src/main/kotlin/domain/usecase/utils/hashAlgorithm/HashingString.kt similarity index 75% rename from src/main/kotlin/domain/hashPassword/HashingPassword.kt rename to src/main/kotlin/domain/usecase/utils/hashAlgorithm/HashingString.kt index 9410656..dfbf5c8 100644 --- a/src/main/kotlin/domain/hashPassword/HashingPassword.kt +++ b/src/main/kotlin/domain/usecase/utils/hashAlgorithm/HashingString.kt @@ -1,5 +1,5 @@ package com.berlin.domain.hashPassword -interface HashingPassword { +interface HashingString { fun hashPassword(password: String):String } \ No newline at end of file diff --git a/src/main/kotlin/domain/hashPassword/MD5Hasher.kt b/src/main/kotlin/domain/usecase/utils/hashAlgorithm/MD5Hasher.kt similarity index 91% rename from src/main/kotlin/domain/hashPassword/MD5Hasher.kt rename to src/main/kotlin/domain/usecase/utils/hashAlgorithm/MD5Hasher.kt index 9ebe3a2..bcb922b 100644 --- a/src/main/kotlin/domain/hashPassword/MD5Hasher.kt +++ b/src/main/kotlin/domain/usecase/utils/hashAlgorithm/MD5Hasher.kt @@ -2,7 +2,7 @@ package com.berlin.domain.hashPassword import java.security.MessageDigest -class MD5Hasher : HashingPassword { +class MD5Hasher : HashingString { override fun hashPassword(password: String): String { return encode(password) } diff --git a/src/main/kotlin/model/Project.kt b/src/main/kotlin/model/Project.kt deleted file mode 100644 index 8b77e24..0000000 --- a/src/main/kotlin/model/Project.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.berlin.model - -data class Project( - val id:String, - val name:String, - val description:String?, - val statesId:List, - val tasksId:List -) diff --git a/src/main/kotlin/presentation/MainMenuUI.kt b/src/main/kotlin/presentation/MainMenuUI.kt index f74d7c8..8aa22f4 100644 --- a/src/main/kotlin/presentation/MainMenuUI.kt +++ b/src/main/kotlin/presentation/MainMenuUI.kt @@ -1,23 +1,40 @@ package com.berlin.presentation +import com.berlin.domain.model.UserRole +import com.berlin.presentation.authService.AuthenticateUserUI import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import data.UserCache class MainMenuUI( private val runners: List, - private val viewer : Viewer, - private val reader : Reader + private val viewer: Viewer, + private val reader: Reader, + private val authUi: AuthenticateUserUI, + private val userCache: UserCache, ) : UiRunner { - override val id = 0 + override val id = 0 override val label = "Main menu" override fun run() { + viewer.show("===Welcome to our PlanMate===") + authUi.run() + val currentUser = userCache.currentUser + if (currentUser == null) { + viewer.show("Login failed.") + return + } + + viewer.show("===${currentUser.role} Board===") + + val filteringRunners = filterRunners(currentUser.role) + while (true) { - showMenu() + showMenu(filteringRunners) when (val input = reader.read()?.trim()) { - null, "", "X", "x" -> return - else -> runners + null, "", "x", "X" -> return + else -> filteringRunners .firstOrNull { it.id == input.toIntOrNull() } ?.run() ?: viewer.show("Invalid choice: $input") @@ -25,15 +42,22 @@ class MainMenuUI( } } - private fun showMenu() { - viewer.show("=== Task Manager ===") + private fun filterRunners(userRole: UserRole): List = + when (userRole) { + UserRole.ADMIN -> runners.filter { it.id in adminPermissionFilterIds } + UserRole.MATE -> runners.filter { it.id in matePermissionFilterIds } + } + + private fun showMenu(runners: List) { runners.sortedBy { it.id } .forEach { viewer.show("${it.id} – ${it.label}") } viewer.show("X – Exit") viewer.show("Select an option:") } - private fun showOptions(){ - viewer.show("") + + private companion object { + val adminPermissionFilterIds = listOf(1, 2, 3, 4, 5, 6, 7, 30,50, 100, 300, 500, 900, 11, 12, 13, 14, 15, 24390823,1000,2000,3000,200000, 2349, 24234) + val matePermissionFilterIds = listOf(1, 2, 3, 4, 5, 6, 7) } } \ No newline at end of file diff --git a/src/main/kotlin/presentation/audit/AuditByProjectUI.kt b/src/main/kotlin/presentation/audit/AuditByProjectUI.kt index 6f355d3..1f7d0aa 100644 --- a/src/main/kotlin/presentation/audit/AuditByProjectUI.kt +++ b/src/main/kotlin/presentation/audit/AuditByProjectUI.kt @@ -1,11 +1,11 @@ package com.berlin.presentation.audit -import com.berlin.data.DummyData import com.berlin.domain.exception.InputCancelledException import com.berlin.domain.exception.InvalidSelectionException import com.berlin.domain.model.AuditLog import com.berlin.domain.model.Project import com.berlin.domain.usecase.auditSystem.GetAuditLogsByProjectIdUseCase +import com.berlin.domain.usecase.project.GetAllProjectsUseCase import com.berlin.presentation.UiRunner import com.berlin.presentation.helper.choose import com.berlin.presentation.io.Reader @@ -13,11 +13,12 @@ import com.berlin.presentation.io.Viewer class AuditByProjectUI( private val getAuditLogsByProjectIdUseCase: GetAuditLogsByProjectIdUseCase, + private val getAllProjectsUseCase: GetAllProjectsUseCase, private val viewer: Viewer, private val reader: Reader ) : UiRunner { - override val id: Int = 1 + override val id: Int = 24390823 override val label: String = "Show audit by project" override fun run() { @@ -49,13 +50,14 @@ class AuditByProjectUI( Changes: ${log.changesDescription ?: "null"} """.trimIndent() ) + viewer.show("") } } private fun selectProject(): Project { return choose( title = "Choose a project", - elements = DummyData.projects, + elements = getAllProjectsUseCase.getAllProjects(), labelOf = { project -> project.name }, viewer = viewer, reader = reader diff --git a/src/main/kotlin/presentation/audit/AuditByTaskUI.kt b/src/main/kotlin/presentation/audit/AuditByTaskUI.kt index 5b9816a..132205d 100644 --- a/src/main/kotlin/presentation/audit/AuditByTaskUI.kt +++ b/src/main/kotlin/presentation/audit/AuditByTaskUI.kt @@ -7,6 +7,8 @@ import com.berlin.domain.model.AuditLog import com.berlin.domain.model.Project import com.berlin.domain.model.Task import com.berlin.domain.usecase.auditSystem.GetAuditLogsByTaskIdUseCase +import com.berlin.domain.usecase.project.GetAllProjectsUseCase +import com.berlin.domain.usecase.task.GetTasksByProjectUseCase import com.berlin.presentation.UiRunner import com.berlin.presentation.helper.choose import com.berlin.presentation.io.Reader @@ -15,11 +17,13 @@ import com.berlin.presentation.io.Viewer class AuditByTaskUI( private val viewer: Viewer, private val reader: Reader, - private val getAuditLogsByTaskIdUseCase: GetAuditLogsByTaskIdUseCase + private val getAuditLogsByTaskIdUseCase: GetAuditLogsByTaskIdUseCase, + private val getTasksByProjectUseCase: GetTasksByProjectUseCase, + private val getAllProjectsUseCase: GetAllProjectsUseCase ) : UiRunner { - override val id = 2 + override val id = 2349 override val label = "View Audit Logs by Task" override fun run() { @@ -57,13 +61,14 @@ class AuditByTaskUI( Changes: ${log.changesDescription ?: "null"} """.trimIndent() ) + viewer.show("") } } private fun selectProject () : Project{ return choose( title = "Choose a project", - elements = DummyData.projects, + elements = getAllProjectsUseCase.getAllProjects(), labelOf = { project -> project.name }, viewer = viewer, reader = reader @@ -73,7 +78,7 @@ class AuditByTaskUI( private fun selectTask (selectedProject : Project) : Task { return choose( title = "Choose a task", - elements = DummyData.initialDemoTasks.filter { it.projectId == selectedProject.id }, + elements = getTasksByProjectUseCase( selectedProject.id).getOrNull() ?: emptyList() , labelOf = { task -> task.title }, viewer = viewer, reader = reader diff --git a/src/main/kotlin/presentation/audit/AuditByUserUI.kt b/src/main/kotlin/presentation/audit/AuditByUserUI.kt index c6f0d2e..595540e 100644 --- a/src/main/kotlin/presentation/audit/AuditByUserUI.kt +++ b/src/main/kotlin/presentation/audit/AuditByUserUI.kt @@ -17,7 +17,7 @@ class AuditByUserUI( private val reader: Reader ) : UiRunner { - override val id: Int = 3 + override val id: Int = 24234 override val label: String = "Show audit by user" override fun run() { @@ -61,6 +61,7 @@ class AuditByUserUI( Changes: ${log.changesDescription ?: "null"} """.trimIndent() ) + viewer.show("") } } } diff --git a/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt b/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt index ac3f611..2804894 100644 --- a/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt +++ b/src/main/kotlin/presentation/authService/AuthenticateUserUI.kt @@ -7,19 +7,19 @@ import com.berlin.presentation.io.Viewer import data.UserCache import domain.usecase.authService.AuthenticateUserUseCase -class AuthenticateUserUi( +class AuthenticateUserUI( private val authenticateUser: AuthenticateUserUseCase, private val viewer: Viewer, private val reader: Reader ) : UiRunner { - override val id: Int = 1 + override val id: Int = 4000 override val label: String = "Log in" override fun run() { authenticateLoop() } - private fun validateUser(): Result { + fun validateUser(): Result { viewer.show("Enter your user name: ") val userName = reader.read()?.trim().orEmpty() viewer.show("Enter your password: ") @@ -31,7 +31,6 @@ class AuthenticateUserUi( validateUser().fold( onSuccess = { viewer.show("Welcome ${it.userName}") - UserCache.currentUser = it }, onFailure = { viewer.show("Try again") diff --git a/src/main/kotlin/presentation/authService/CreateMateUI.kt b/src/main/kotlin/presentation/authService/CreateMateUI.kt index 44fec38..a09e827 100644 --- a/src/main/kotlin/presentation/authService/CreateMateUI.kt +++ b/src/main/kotlin/presentation/authService/CreateMateUI.kt @@ -1,13 +1,13 @@ package com.berlin.presentation.authService import com.berlin.domain.model.User -import com.berlin.domain.usecase.authService.CreationOfMateUseCase +import com.berlin.domain.usecase.authService.CreateMateUseCase import com.berlin.presentation.UiRunner import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -class CreationOfMateUi( - private val creationOfMateUseCase: CreationOfMateUseCase, +class CreateMateUI( + private val createMateUseCase: CreateMateUseCase, private val viewer: Viewer, private val reader: Reader, ): UiRunner { @@ -17,22 +17,21 @@ class CreationOfMateUi( handleMateCreation() } private fun createMate(): Result{ - viewer.show("Enter user name: ") + viewer.show("Enter user name or x to exit: ") val userName = reader.read()?.trim().orEmpty() viewer.show("Enter user password: ") val userPassword = reader.read()?.trim().orEmpty() - return creationOfMateUseCase.createMate(userName, userPassword) + return createMateUseCase.createMate(userName, userPassword) } private fun handleMateCreation(attempt: Int = 0, maxAttempts: Int = 3) { createMate().onSuccess { viewer.show("New mate is successfully created!") }.onFailure { - viewer.show("something wrong please try again!") + viewer.show(it.message ?: "some thing went wrong please try again!") if (attempt < maxAttempts) { handleMateCreation(attempt + 1, maxAttempts) } } } - } \ No newline at end of file diff --git a/src/main/kotlin/presentation/authService/GetUserByIDUI.kt b/src/main/kotlin/presentation/authService/GetUserByIDUI.kt index f2ac9d6..37a3af7 100644 --- a/src/main/kotlin/presentation/authService/GetUserByIDUI.kt +++ b/src/main/kotlin/presentation/authService/GetUserByIDUI.kt @@ -1,7 +1,5 @@ package com.berlin.presentation.authService -import com.berlin.domain.exception.InvalidUserIdException -import com.berlin.domain.exception.UserNotFoundException import com.berlin.domain.model.User import com.berlin.domain.usecase.authService.GetUserByIDUseCase import com.berlin.presentation.UiRunner @@ -19,24 +17,10 @@ class GetUserByIDUI( override fun run() { viewer.show("Enter the user id: ") val id = reader.read()?.trim().orEmpty() - try { - //val id = reader.read()?.trim().orEmpty() - val user = getUserByIDUseCase.getUserById(id) - user.fold( - onSuccess = { showUserInfo(it) }, - - onFailure = { ex -> - when (ex) { - is UserNotFoundException -> - viewer.show("No user found for this ID") - - else -> viewer.show("error: $ex") - } - } - ) - } catch (e: InvalidUserIdException) { - viewer.show("Invalid ID") - } + getUserByIDUseCase.getUserById(id).fold( + onSuccess = { showUserInfo(it) }, + onFailure = { viewer.show(it.message ?: "invalid user id") } + ) } private fun showUserInfo(user: User) { diff --git a/src/main/kotlin/presentation/authService/GettingUsersLoggedInUI.kt b/src/main/kotlin/presentation/authService/GettingUsersLoggedInUI.kt index ef9bdd7..d0a3194 100644 --- a/src/main/kotlin/presentation/authService/GettingUsersLoggedInUI.kt +++ b/src/main/kotlin/presentation/authService/GettingUsersLoggedInUI.kt @@ -1,12 +1,12 @@ package com.berlin.presentation.authService import com.berlin.domain.model.User -import com.berlin.domain.usecase.authService.GettingUsersLoggedInUseCase +import com.berlin.domain.usecase.authService.GetUserLoggedInUseCase import com.berlin.presentation.UiRunner import com.berlin.presentation.io.Viewer class GettingUsersLoggedInUI( - private val getUserLoggedIn: GettingUsersLoggedInUseCase, + private val getUserLoggedIn: GetUserLoggedInUseCase, private val viewer: Viewer, ) : UiRunner { override val id: Int = 100 diff --git a/src/main/kotlin/presentation/project/CreateProjectUi.kt b/src/main/kotlin/presentation/project/CreateProjectUi.kt index b546e4f..6aac1b4 100644 --- a/src/main/kotlin/presentation/project/CreateProjectUi.kt +++ b/src/main/kotlin/presentation/project/CreateProjectUi.kt @@ -1,49 +1,76 @@ package com.berlin.presentation.project -import com.berlin.domain.exception.InputCancelledException -import com.berlin.domain.exception.InvalidSelectionException import com.berlin.domain.usecase.project.CreateProjectUseCase import com.berlin.presentation.UiRunner import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer class CreateProjectUi( - private val createProject: CreateProjectUseCase, + private val createProjectUseCase: CreateProjectUseCase, private val viewer: Viewer, private val reader: Reader ) : UiRunner { - - override val id: Int = 11 + override val id: Int = 1 override val label: String = "Create New Project" - override fun run() { - try { - val (name, description) = askProjectTitleAndDescription() - - createProject.createNewProject( - projectName = name, - description = description, - stateId = null, - taskId = null - ).onSuccess { viewer.show("Project created successfully") } - .onFailure { viewer.show(it.message ?: "Creation failed") } - - } catch (e: InputCancelledException) { - viewer.show("Project creation cancelled.") - } catch (e: Exception) { - viewer.show("Error: ${e.message}") - } + displayHeader() + + val projectName = getProjectName() + + val projectDescription = getProjectDescription() + + createProject(projectName, projectDescription) + } + + private fun displayHeader() { + viewer.show("=== Create New Project ===\n") + viewer.show("================================================================\n\n") + viewer.show("Enter project details:\n") } - private fun askProjectTitleAndDescription(): Pair { - viewer.show("Enter project name:") - val name = reader.read()?.trim().orEmpty() - if (name.isEmpty()) throw InvalidSelectionException("Project name cannot be empty") + private fun getProjectName(): String { + viewer.show("Project Title:") - viewer.show("Enter project description (optional):") - return name to reader.read()?.trim() + while (true) { + reader.read()?.let { input -> + if (input.isNotEmpty()) { + return input + } + } + viewer.show("Please enter a valid project name:") + } + } + + private fun getProjectDescription(): String? { + viewer.show("Do you want to write a description? (yes/no)") + val option = reader.read()?.lowercase() + return if (option == "yes") { + viewer.show("Enter project description:") + reader.read() + } else { + null + } } + private fun createProject(projectName: String, projectDescription: String?) { + viewer.show("Creating project...\n") + + val creationResult = createProjectUseCase.createNewProject( + projectName, + projectDescription, + null, + null + ) + + creationResult.fold( + onSuccess = { message -> + viewer.show(message) + }, + onFailure = { error -> + viewer.show("Error: ${error.message ?: "Project creation failed!"}\n") + } + ) + } } \ No newline at end of file diff --git a/src/main/kotlin/presentation/state/CreatestateUi.kt b/src/main/kotlin/presentation/state/CreatestateUi.kt new file mode 100644 index 0000000..136cecc --- /dev/null +++ b/src/main/kotlin/presentation/state/CreatestateUi.kt @@ -0,0 +1,65 @@ +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 +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() { + + try { + val project = selectProject() + viewer.show("-- Enter a state in project ${project.name} --") + addStateName(project) + } catch (_: InputCancelledException) { + viewer.show("Cancelled!") + } + + + } + + 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..aaa0888 --- /dev/null +++ b/src/main/kotlin/presentation/state/DeleteStateUi.kt @@ -0,0 +1,53 @@ +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("Cancelled.") + + 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/main/kotlin/presentation/state/GetAllstatesByProjectIdUi.kt b/src/main/kotlin/presentation/state/GetAllstatesByProjectIdUi.kt new file mode 100644 index 0000000..952ea86 --- /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..d5cdbce --- /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 state 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..8be0482 --- /dev/null +++ b/src/main/kotlin/presentation/state/updateStateUi.kt @@ -0,0 +1,48 @@ +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 +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 (_: InputCancelledException) { + viewer.show("Cancelled!") + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/presentation/task/CreateTaskUI.kt b/src/main/kotlin/presentation/task/CreateTaskUI.kt index 73a5d15..a6bd26e 100644 --- a/src/main/kotlin/presentation/task/CreateTaskUI.kt +++ b/src/main/kotlin/presentation/task/CreateTaskUI.kt @@ -12,10 +12,11 @@ import com.berlin.presentation.UiRunner import com.berlin.presentation.helper.choose import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import data.UserCache class CreateTaskUI( private val createTask: CreateTaskUseCase, - private val currentUser: User, + private val cashedUser: UserCache, private val viewer: Viewer, private val reader: Reader, ) : UiRunner { @@ -31,7 +32,7 @@ class CreateTaskUI( val (title, desc) = askTitleAndDescription() createTask( - project.id, title, desc, state.id, currentUser.id, assignee.id + project.id, title, desc, state.id, cashedUser.currentUser.id, assignee.id ).onSuccess { viewer.show("Task created: id=${it.id}") } .onFailure { viewer.show(it.message ?: "Creation failed") } diff --git a/src/test/kotlin/MainKtTest.kt b/src/test/kotlin/MainKtTest.kt index 12c306b..5324d0f 100644 --- a/src/test/kotlin/MainKtTest.kt +++ b/src/test/kotlin/MainKtTest.kt @@ -1,29 +1,43 @@ package com.berlin -import com.google.common.truth.Truth.assertThat +import com.berlin.data.DummyData +import com.berlin.presentation.MainMenuUI +import com.berlin.presentation.UiRunner +import com.berlin.presentation.authService.AuthenticateUserUI +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer +import data.UserCache import io.mockk.* import org.junit.jupiter.api.Test -import org.koin.core.context.stopKoin import java.io.ByteArrayOutputStream import java.io.PrintStream + class MainTest { @Test fun `main prints banner`() { - mockkStatic("kotlin.io.ConsoleKt") - every { readLine() } returns "X" - + //when + val mockViewer = mockk(relaxed = true) + val mockReader = mockk() + every { mockReader.read() } returns "X" + val mockAuthUi = mockk { every { run() } just Runs } + val mockUserCache = mockk() + every { mockUserCache.currentUser } returns DummyData.users.first() + val dummyRunners = emptyList() + val mainMenuUI = MainMenuUI( + runners = dummyRunners, + viewer = mockViewer, + reader = mockReader, + authUi = mockAuthUi, + userCache = mockUserCache + ) val originalOut = System.out val buffer = ByteArrayOutputStream() System.setOut(PrintStream(buffer)) - main() - - val output = buffer.toString() - assertThat(output).contains("=== Task Manager") - - verify(exactly = 1) { readLine() } + mainMenuUI.run() System.setOut(originalOut) + verify { mockAuthUi.run() } } } diff --git a/src/main/kotlin/data/AuthDummyData.kt b/src/test/kotlin/data/AuthDummyData.kt similarity index 100% rename from src/main/kotlin/data/AuthDummyData.kt rename to src/test/kotlin/data/AuthDummyData.kt diff --git a/src/test/kotlin/data/Audit/AuditRepositoryImplTest.kt b/src/test/kotlin/data/audit/AuditRepositoryImplTest.kt similarity index 99% rename from src/test/kotlin/data/Audit/AuditRepositoryImplTest.kt rename to src/test/kotlin/data/audit/AuditRepositoryImplTest.kt index 6a190d7..860090b 100644 --- a/src/test/kotlin/data/Audit/AuditRepositoryImplTest.kt +++ b/src/test/kotlin/data/audit/AuditRepositoryImplTest.kt @@ -1,4 +1,4 @@ -package data.Audit +package data.audit import com.berlin.data.Audit.AuditRepositoryImpl import com.berlin.data.csv_data_source.CsvDataSource diff --git a/src/test/kotlin/data/csv_data_source/CsvDataSourceTest.kt b/src/test/kotlin/data/csvDataSource/CsvDataSourceTest.kt similarity index 100% rename from src/test/kotlin/data/csv_data_source/CsvDataSourceTest.kt rename to src/test/kotlin/data/csvDataSource/CsvDataSourceTest.kt diff --git a/src/test/kotlin/data/state/StateRepositoryImplTest.kt b/src/test/kotlin/data/state/StateRepositoryImplTest.kt index 2f57b80..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 @@ -73,7 +72,7 @@ class StateRepositoryImplTest { // When val result = repository.getStateById(validState.id) // Then - assertThat(result).isEqualTo(validState) + assertThat(result.getOrNull()).isEqualTo(validState) } @Test @@ -83,7 +82,7 @@ class StateRepositoryImplTest { // When val result = repository.getStateById(validState.id) // Then - assertThat(result).isNull() + assertThat(result.getOrNull()).isNull() } // endregion @@ -95,7 +94,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 +105,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/data/task/AuthenticationRepositoryInMemoryTest.kt b/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt index b212ea7..2375f98 100644 --- a/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt +++ b/src/test/kotlin/data/task/AuthenticationRepositoryInMemoryTest.kt @@ -1,44 +1,118 @@ -package com.berlin.data.memory +package com.berlin.data.authentication import com.berlin.data.AuthDummyData -import com.berlin.data.authentication.AuthenticationRepositoryImpl -import com.berlin.domain.hashPassword.HashingPassword -import com.berlin.domain.hashPassword.MD5Hasher +import com.berlin.domain.exception.UserNotFoundException import com.berlin.domain.helper.AuthServiceTestData -import com.google.common.truth.Truth.assertThat +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole +import data.UserCache import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import com.google.common.truth.Truth.assertThat class AuthenticationRepositoryInMemoryTest { - private lateinit var inMemoryAuthRepositoryImpl: AuthenticationRepositoryImpl - private lateinit var hashingPassword: HashingPassword + + private val cachedUser = User("user1234", "admin", "1212", UserRole.ADMIN) + + private lateinit var repoWithCache: AuthenticationRepositoryImpl + private lateinit var repoWithoutCache: AuthenticationRepositoryImpl @BeforeEach fun setup() { - hashingPassword = MD5Hasher() AuthDummyData.users.clear() - inMemoryAuthRepositoryImpl =AuthenticationRepositoryImpl(AuthDummyData) + + repoWithCache = AuthenticationRepositoryImpl( + userCache = UserCache(cachedUser), + userDataSource = AuthDummyData + ) + + repoWithoutCache = AuthenticationRepositoryImpl( + userCache = UserCache(cachedUser), + userDataSource = AuthDummyData + ) + } + + @Test + fun `login succeeds with exactly matching credentials`() { + val expected = AuthServiceTestData.expectedUser + repoWithoutCache.createMate(expected) + + val result = repoWithoutCache.login(expected.userName, expected.password) + + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).isEqualTo(expected) + } + + @Test + fun `login fails with wrong credentials`() { + repoWithoutCache.createMate(AuthServiceTestData.expectedUser) + + val result = repoWithoutCache.login("wrongUser", "wrongPass") + + assertThat(result.isFailure).isTrue() } + @Test + fun `createMate persists and returns the new user`() { + val newUser = User("id-foo", "bar", "baz", UserRole.MATE) + + val result = repoWithoutCache.createMate(newUser) + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).isEqualTo(newUser) + assertThat(AuthDummyData.users).contains(newUser) + } @Test - fun `login should return success when provided with valid credentials`() { - val username = "Fatma" - val password = "hashed_securePassword" - inMemoryAuthRepositoryImpl.createMate(AuthServiceTestData.excepctedUser) - val result = inMemoryAuthRepositoryImpl.login(username, password) + fun `getUserById returns user when exists`() { + val u = AuthServiceTestData.expectedUser + repoWithoutCache.createMate(u) + + val result = repoWithoutCache.getUserById(u.id) + assertThat(result.isSuccess).isTrue() - val user = result.getOrNull() - assertThat(user?.userName).isEqualTo(username) + assertThat(result.getOrNull()).isEqualTo(u) } @Test - fun `login should return failure when provided with invalid credentials`() { - inMemoryAuthRepositoryImpl.createMate(AuthServiceTestData.user) - val result = inMemoryAuthRepositoryImpl.login("wrong", "pass") + fun `getUserById fails when user does not exist`() { + val missingId = "does-not-exist" + + val result = repoWithoutCache.getUserById(missingId) + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()) + .isInstanceOf(UserNotFoundException::class.java) + assertThat(result.exceptionOrNull()?.message) + .isEqualTo(missingId) + } + + @Test + fun `getAllUsers returns empty list when none exist`() { + val result = repoWithoutCache.getAllUsers() + + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).isEmpty() + } + + @Test + fun `getAllUsers returns all users when some exist`() { + val u1 = AuthServiceTestData.expectedUser + val u2 = User("id2", "alice", "pw", UserRole.MATE) + repoWithoutCache.createMate(u1) + repoWithoutCache.createMate(u2) + + val result = repoWithoutCache.getAllUsers() + + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).containsExactly(u1, u2) } + @Test + fun `getCurrentUser returns the cached user when present`() { + val result = repoWithCache.getCurrentUser() + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).isEqualTo(cachedUser) + } } diff --git a/src/test/kotlin/data/task/TaskRepositoryImplTest.kt b/src/test/kotlin/data/task/TaskRepositoryImplTest.kt index eb422ed..12c1259 100644 --- a/src/test/kotlin/data/task/TaskRepositoryImplTest.kt +++ b/src/test/kotlin/data/task/TaskRepositoryImplTest.kt @@ -1,4 +1,4 @@ -package com.berlin.data.memory +package com.berlin.data.task import com.berlin.data.BaseDataSource import com.berlin.domain.exception.InvalidTaskException @@ -147,4 +147,17 @@ class TaskRepositoryImplTest { assertThat(result.exceptionOrNull()).isInstanceOf(InvalidTaskException::class.java) } + @Test + fun `writeAll on data source adds all tasks and is reflected in repo`() { + DummyData.tasks.clear() + + val t1 = task("bulk1") + val t2 = task("bulk2") + + val wrote = DummyData.writeAll(listOf(t1, t2)) + assertThat(wrote).isTrue() + + val all = repo.getAllTasks() + assertThat(all).containsExactly(t1, t2) + } } diff --git a/src/test/kotlin/domain/fakeData/FakeHashingPassword.kt b/src/test/kotlin/domain/fakeData/FakeHashingString.kt similarity index 53% rename from src/test/kotlin/domain/fakeData/FakeHashingPassword.kt rename to src/test/kotlin/domain/fakeData/FakeHashingString.kt index 8b15c1d..aa26c97 100644 --- a/src/test/kotlin/domain/fakeData/FakeHashingPassword.kt +++ b/src/test/kotlin/domain/fakeData/FakeHashingString.kt @@ -1,8 +1,8 @@ package com.berlin.domain.fakeData -import com.berlin.domain.hashPassword.HashingPassword +import com.berlin.domain.hashPassword.HashingString -class FakeHashingPassword : HashingPassword { +class FakeHashingString : HashingString { override fun hashPassword(password: String): String = "$password-hashed" } diff --git a/src/test/kotlin/logic/generateIdHelper/DefaultIdGeneratorTest.kt b/src/test/kotlin/domain/generateIdHelper/DefaultIdGeneratorTest.kt similarity index 94% rename from src/test/kotlin/logic/generateIdHelper/DefaultIdGeneratorTest.kt rename to src/test/kotlin/domain/generateIdHelper/DefaultIdGeneratorTest.kt index 46dec57..6340f19 100644 --- a/src/test/kotlin/logic/generateIdHelper/DefaultIdGeneratorTest.kt +++ b/src/test/kotlin/domain/generateIdHelper/DefaultIdGeneratorTest.kt @@ -1,6 +1,6 @@ -package com.berlin.logic.generateIdHelper +package com.berlin.domain.generateIdHelper -import com.berlin.domain.helper.IdGeneratorImplementation +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.google.common.truth.Truth.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows diff --git a/src/test/kotlin/domain/hashPassword/MD5HashAlgorithmTest.kt b/src/test/kotlin/domain/hashAlgorithm/MD5HashAlgorithmTest.kt similarity index 94% rename from src/test/kotlin/domain/hashPassword/MD5HashAlgorithmTest.kt rename to src/test/kotlin/domain/hashAlgorithm/MD5HashAlgorithmTest.kt index 3dd9363..e26b8f4 100644 --- a/src/test/kotlin/domain/hashPassword/MD5HashAlgorithmTest.kt +++ b/src/test/kotlin/domain/hashAlgorithm/MD5HashAlgorithmTest.kt @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test class MD5HashAlgorithmTest { - private lateinit var mD5HashAlgorithm: HashingPassword + private lateinit var mD5HashAlgorithm: HashingString @BeforeEach fun setup() { diff --git a/src/test/kotlin/domain/helper/AuthServiceTestData.kt b/src/test/kotlin/domain/helper/AuthServiceTestData.kt index aae81d1..8b06899 100644 --- a/src/test/kotlin/domain/helper/AuthServiceTestData.kt +++ b/src/test/kotlin/domain/helper/AuthServiceTestData.kt @@ -7,14 +7,14 @@ import com.berlin.domain.model.User object AuthServiceTestData { val inValidUserName = "Ahmed" val inValidUserPassword = "00000000" - val passwordLessThanEight = "hashd_s" val userName = "Fatma" val userNameIsEmpty = "" val userPassword = "hashed_securePassword" val userPasswordIsEmpty = "" val idNotExist = "u6" + val idWithSpacesExist=" u13" val idExist = "u13" - val excepctedUser = userDummyData( + val expectedUser = userDummyData( userName = userName, password = userPassword, role = UserRole.MATE @@ -22,31 +22,13 @@ object AuthServiceTestData { val user = userDummyData( userName = "Fatma", password = "hashed_securePassword" ) - val userIsEmpty = userDummyData("","","") val testForUserName = "Fatma" val testForUserPassword = "1234567899" val adminIsFirstUser = userDummyData("u55", "Menna", "12345678") val existingUser = userDummyData("u13", "Menna", "12345678") - const val testUserName = "testUser" - const val testUserPassword = "testPassword" - const val invalidInput = "" - - val expectedUser = User( - id = "id_testPassword", - userName = testForUserName, - password = "hashed_testPassword", - role = UserRole.MATE - ) - - val emptyUser = User( - id = "id_", - userName = "", - password = "hashed_", - role = UserRole.MATE - ) - - -} + val EMPTY_USER = User("","","",UserRole.ADMIN) + val CACHEUSER =User("user1234", "admin", "1212", UserRole.ADMIN) +} \ No newline at end of file diff --git a/src/test/kotlin/domain/helper/DefaultIdGeneratorTest.kt b/src/test/kotlin/domain/helper/DefaultIdGeneratorTest.kt index 06b2fe3..383c693 100644 --- a/src/test/kotlin/domain/helper/DefaultIdGeneratorTest.kt +++ b/src/test/kotlin/domain/helper/DefaultIdGeneratorTest.kt @@ -1,7 +1,7 @@ package domain.helper -import com.berlin.domain.helper.IdGeneratorImplementation +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.google.common.truth.Truth.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows diff --git a/src/test/kotlin/domain/usecase/auditSystem/AddAuditLogUseCaseTest.kt b/src/test/kotlin/domain/usecase/auditSystem/AddAuditLogUseCaseTest.kt index a24379f..1bc5127 100644 --- a/src/test/kotlin/domain/usecase/auditSystem/AddAuditLogUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/auditSystem/AddAuditLogUseCaseTest.kt @@ -1,6 +1,6 @@ package com.berlin.domain.usecase.auditSystem -import com.berlin.domain.helper.IdGenerator +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator import com.berlin.domain.model.* import com.berlin.domain.repository.AuditRepository import com.berlin.helper.generateAuditLog diff --git a/src/test/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCaseTest.kt b/src/test/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCaseTest.kt index 4c9a9bb..01973be 100644 --- a/src/test/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/auditSystem/GetAuditLogsByTaskIdUseCaseTest.kt @@ -29,15 +29,13 @@ class GetAuditLogsByTaskIdUseCaseTest { val logs = listOf( generateAuditLog(id = "A3", entityId = taskId, entityType = EntityType.TASK) ) - every { auditRepository.getAuditLogsByProjectId(taskId) } returns logs + every { auditRepository.getAuditLogsByTaskId(taskId) } returns logs //When val result = getAuditLogsByTaskIdUseCase.getAuditLogsByTaskId(taskId) //That assertThat(result).isEqualTo(logs) - verify(exactly = 1) { auditRepository.getAuditLogsByProjectId(taskId) } - } diff --git a/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt b/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt index ec47ea8..3ac4abb 100644 --- a/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/authService/AuthenticateUserUseCaseTest.kt @@ -1,36 +1,43 @@ -package domain.logic.usecase.authService - +package com.berlin.domain.usecase.authService import com.berlin.domain.exception.InvalidCredentialsException -import com.berlin.domain.hashPassword.HashingPassword +import com.berlin.domain.hashPassword.HashingString import com.berlin.domain.repository.AuthenticationRepository -import com.berlin.domain.fakeData.FakeHashingPassword +import com.berlin.domain.fakeData.FakeHashingString import com.berlin.domain.helper.AuthServiceTestData +import com.berlin.domain.helper.AuthServiceTestData.EMPTY_USER +import com.berlin.domain.helper.AuthServiceTestData.userName +import com.berlin.domain.helper.AuthServiceTestData.userPassword +import com.berlin.domain.helper.AuthServiceTestData.CACHEUSER import com.google.common.truth.Truth.assertThat import data.UserCache import domain.usecase.authService.AuthenticateUserUseCase import io.mockk.every import io.mockk.mockk +import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import kotlin.test.assertTrue class AuthenticateUserUseCaseTest { private lateinit var authRepository: AuthenticationRepository - private lateinit var hashingPassword: HashingPassword + private lateinit var hashingString: HashingString private lateinit var authenticateUserUseCase: AuthenticateUserUseCase + private var cashedUser = CACHEUSER + private lateinit var userCache: UserCache @BeforeEach fun setup() { authRepository = mockk() - hashingPassword = FakeHashingPassword() - authenticateUserUseCase = AuthenticateUserUseCase(authRepository, hashingPassword) + hashingString = FakeHashingString() + userCache= UserCache(cashedUser) + authenticateUserUseCase = AuthenticateUserUseCase(userCache,authRepository, hashingString) } - @Test fun `login returns user successfully when valid credentials are provided`() { // Given val validUser = AuthServiceTestData.user - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.userPassword) + val hashedPassword = hashingString.hashPassword(AuthServiceTestData.userPassword) every { authRepository.getAllUsers() } returns Result.success(listOf(validUser)) every { authRepository.login(AuthServiceTestData.userName, hashedPassword) } returns Result.success(validUser) @@ -45,7 +52,7 @@ class AuthenticateUserUseCaseTest { @Test fun `login fails when user is not found in repository`() { // Given - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.inValidUserPassword) + val hashedPassword = hashingString.hashPassword(AuthServiceTestData.inValidUserPassword) every { authRepository.login(AuthServiceTestData.inValidUserName, hashedPassword) } returns Result.failure( InvalidCredentialsException("No found data") ) @@ -59,7 +66,7 @@ class AuthenticateUserUseCaseTest { @Test fun `login fails when username is empty`() { // Given - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.userPassword) + val hashedPassword = hashingString.hashPassword(AuthServiceTestData.userPassword) every { authRepository.getAllUsers() } returns Result.success(listOf()) every { authRepository.login(AuthServiceTestData.userNameIsEmpty, hashedPassword) } returns Result.failure( InvalidCredentialsException("No user found") @@ -73,7 +80,7 @@ class AuthenticateUserUseCaseTest { @Test fun `login fails when password is empty`() { // Given - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.userPassword) + val hashedPassword = hashingString.hashPassword(AuthServiceTestData.userPassword) every { authRepository.getAllUsers() } returns Result.success(listOf()) every { authRepository.login(AuthServiceTestData.userNameIsEmpty, hashedPassword) } returns Result.failure( InvalidCredentialsException("No user found") @@ -91,8 +98,8 @@ class AuthenticateUserUseCaseTest { fun `login returns cached user when user is already authenticated`() { // Given val cachedUser = AuthServiceTestData.user - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.userPassword) - UserCache.currentUser = cachedUser + val hashedPassword = hashingString.hashPassword(AuthServiceTestData.userPassword) + userCache.currentUser = cachedUser every { authRepository.getAllUsers() } returns Result.success(listOf(cachedUser)) every { authRepository.login(AuthServiceTestData.userName, hashedPassword) } returns Result.failure( @@ -114,16 +121,16 @@ class AuthenticateUserUseCaseTest { val rawPassword = "123456" val hashedPassword = "hashed_123456" - val mockedHashing = mockk() + val mockedHashing = mockk() every { mockedHashing.hashPassword(rawPassword) } returns hashedPassword val mockedRepo = mockk() - authenticateUserUseCase = AuthenticateUserUseCase(mockedRepo, mockedHashing) + authenticateUserUseCase = AuthenticateUserUseCase(userCache,mockedRepo, mockedHashing) every { mockedRepo.getAllUsers() } returns Result.success(listOf(AuthServiceTestData.user)) every { mockedRepo.login(userName, hashedPassword) } returns Result.failure(InvalidCredentialsException("Invalid")) - UserCache.currentUser = null + userCache.currentUser = cashedUser // When val result = authenticateUserUseCase.login(userName, rawPassword) @@ -139,7 +146,7 @@ class AuthenticateUserUseCaseTest { fun `login returns cached user even if password is wrong`() { // Given val cachedUser = AuthServiceTestData.user - UserCache.currentUser = cachedUser + userCache.currentUser = cachedUser every { authRepository.getAllUsers() } returns Result.success(listOf(cachedUser)) // When @@ -154,10 +161,10 @@ class AuthenticateUserUseCaseTest { fun `login fails with unknown exception from repository`() { // Given val user = AuthServiceTestData.user - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.userPassword) + val hashedPassword = hashingString.hashPassword(AuthServiceTestData.userPassword) every { authRepository.getAllUsers() } returns Result.success(listOf(user)) every { authRepository.login(user.userName, hashedPassword) } returns Result.failure(RuntimeException("Unexpected error")) - UserCache.currentUser = null + userCache.currentUser = cashedUser // When val result = authenticateUserUseCase.login(user.userName, AuthServiceTestData.userPassword) @@ -168,35 +175,15 @@ class AuthenticateUserUseCaseTest { assertThat(result.exceptionOrNull()?.message).isEqualTo("Unexpected error") } - @Test - fun `UserCache remains null if login fails`() { - // Given - val user = AuthServiceTestData.user - val hashedPassword = hashingPassword.hashPassword(AuthServiceTestData.userPassword) - every { authRepository.getAllUsers() } returns Result.success(listOf(user)) - every { authRepository.login(user.userName, hashedPassword) } returns Result.failure( - InvalidCredentialsException("Wrong credentials") - ) - UserCache.currentUser = null - - // When - val result = authenticateUserUseCase.login(user.userName, AuthServiceTestData.userPassword) - - // Then - assertThat(result.isFailure).isTrue() - assertThat(UserCache.currentUser).isNull() - } - - @Test fun `login ignores cache if username is different and proceeds with login`() { // Given val cachedUser = AuthServiceTestData.user.copy(userName = "otherUser") val expectedUser = AuthServiceTestData.user val password = AuthServiceTestData.userPassword - val hashedPassword = hashingPassword.hashPassword(password) + val hashedPassword = hashingString.hashPassword(password) - UserCache.currentUser = cachedUser + userCache.currentUser = cachedUser every { authRepository.getAllUsers() } returns Result.success(listOf(expectedUser)) every { authRepository.login(expectedUser.userName, hashedPassword) } returns Result.success(expectedUser) @@ -206,7 +193,7 @@ class AuthenticateUserUseCaseTest { // Then assertThat(result.isSuccess).isTrue() assertThat(result.getOrNull()).isEqualTo(expectedUser) - assertThat(UserCache.currentUser).isEqualTo(expectedUser) + assertThat(userCache.currentUser).isEqualTo(expectedUser) } @Test @@ -214,9 +201,9 @@ class AuthenticateUserUseCaseTest { // Given val user = AuthServiceTestData.user val wrongPassword = "wrongPassword" - val hashedWrongPassword = hashingPassword.hashPassword(wrongPassword) + val hashedWrongPassword = hashingString.hashPassword(wrongPassword) - UserCache.currentUser = null + userCache.currentUser = cashedUser every { authRepository.getAllUsers() } returns Result.success(listOf(user)) every { authRepository.login(user.userName, hashedWrongPassword) } returns Result.failure( InvalidCredentialsException("Wrong password") @@ -229,4 +216,38 @@ class AuthenticateUserUseCaseTest { assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isInstanceOf(InvalidCredentialsException::class.java) } + @Test + fun `userCashing returns null when user enter invalid data`(){ + //Given + val emptyUser =EMPTY_USER + userCache.currentUser = emptyUser + val hashPassword = hashingString.hashPassword(userPassword) + every { authRepository.getAllUsers() } returns Result.success(listOf(emptyUser)) + every { authRepository.login(any() , any()) } returns Result.failure( + InvalidCredentialsException("No user found") + ) + //When + val result = authenticateUserUseCase.login(userName, hashPassword) + //Then + assertTrue(result.isFailure) + assertThat(result.exceptionOrNull() is InvalidCredentialsException) + } + @Test + fun `login should return failure when repository returns failure`() { + val userCache = UserCache(EMPTY_USER) + val repository = mockk() + val hashingString = FakeHashingString() + + val useCase = AuthenticateUserUseCase(userCache, repository, hashingString) + + val userName = "admin" + val password = "wrongPass" + val expectedException = InvalidCredentialsException("Invalid login") + + every { repository.login(userName, any()) } returns Result.failure(expectedException) + val result = useCase.login(userName, password) + + assertTrue(result.isFailure) + Assertions.assertEquals(expectedException, result.exceptionOrNull()) + } } diff --git a/src/test/kotlin/domain/usecase/authService/CreateMateUseCaseTest.kt b/src/test/kotlin/domain/usecase/authService/CreateMateUseCaseTest.kt new file mode 100644 index 0000000..cd74aba --- /dev/null +++ b/src/test/kotlin/domain/usecase/authService/CreateMateUseCaseTest.kt @@ -0,0 +1,112 @@ +package com.berlin.domain.usecase.authService + +import com.berlin.domain.exception.InvalidCredentialsException +import com.berlin.domain.hashPassword.HashingString +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole +import com.berlin.domain.repository.AuthenticationRepository +import com.berlin.domain.usecase.authService.CreateMateUseCase +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator +import com.google.common.truth.Truth.assertThat +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class CreateMateUseCaseTest { + + private lateinit var authRepository: AuthenticationRepository + private val idGenerator: IdGenerator = mockk() + private lateinit var hashingString: HashingString + private lateinit var createMateUseCase: CreateMateUseCase + + @BeforeEach + fun setup() { + authRepository = mockk() + hashingString = mockk() + createMateUseCase = CreateMateUseCase(authRepository, idGenerator, hashingString) + } + + @Test + fun `createMate fails when username is empty`() { + val result = createMateUseCase.createMate("", "validPassword") + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()) + .isInstanceOf(InvalidCredentialsException::class.java) + assertThat(result.exceptionOrNull()?.message) + .isEqualTo("Username and password must not be empty") + } + + @Test + fun `createMate fails when password is empty`() { + val result = createMateUseCase.createMate("validUser", "") + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()) + .isInstanceOf(InvalidCredentialsException::class.java) + assertThat(result.exceptionOrNull()?.message) + .isEqualTo("Username and password must not be empty") + } + + @Test + fun `createMate fails when password length is less than 8 characters`() { + val result = createMateUseCase.createMate("validUser", "short7") + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()) + .isInstanceOf(InvalidCredentialsException::class.java) + assertThat(result.exceptionOrNull()?.message) + .isEqualTo("Password less than 8 characters") + } + + // —— NEW TESTS BELOW —— + + @Test + fun `createMate succeeds when credentials are valid`() { + // Arrange + val username = "validUser" + val password = "longEnough" + val fakeHash = "hashed_longEnough" + val fakeId = "generated-id-123" + val expected = User(fakeId, username, fakeHash, UserRole.MATE) + + every { hashingString.hashPassword(password) } returns fakeHash + every { idGenerator.generateId(username, any(), any()) } returns fakeId + every { authRepository.createMate(expected) } returns Result.success(expected) + + // Act + val result = createMateUseCase.createMate(username, password) + + // Assert + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).isEqualTo(expected) + verify { + hashingString.hashPassword(password) + idGenerator.generateId(username, any(), any()) + authRepository.createMate(expected) + } + } + + @Test + fun `createMate propagates repository failure`() { + // Arrange + val username = "validUser" + val password = "longEnough" + val fakeHash = "hashed_longEnough" + val fakeId = "generated-id-456" + val expected = User(fakeId, username, fakeHash, UserRole.MATE) + val repoEx = RuntimeException("already exists") + + every { hashingString.hashPassword(password) } returns fakeHash + every { idGenerator.generateId(username, any(), any()) } returns fakeId + every { authRepository.createMate(expected) } returns Result.failure(repoEx) + + // Act + val result = createMateUseCase.createMate(username, password) + + // Assert + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()).isEqualTo(repoEx) + verify { authRepository.createMate(expected) } + } + +} diff --git a/src/test/kotlin/domain/usecase/authService/CreationOfMateUseCaseTest.kt b/src/test/kotlin/domain/usecase/authService/CreationOfMateUseCaseTest.kt deleted file mode 100644 index 709adec..0000000 --- a/src/test/kotlin/domain/usecase/authService/CreationOfMateUseCaseTest.kt +++ /dev/null @@ -1,76 +0,0 @@ -package domain.logic.usecase.authService - -import com.berlin.domain.hashPassword.HashingPassword -import com.berlin.domain.repository.AuthenticationRepository -import com.berlin.domain.usecase.authService.CreationOfMateUseCase -import com.berlin.domain.fakeData.FakeHashingPassword -import com.berlin.domain.helper.AuthServiceTestData -import com.berlin.domain.helper.IdGenerator -import com.berlin.domain.model.User -import com.berlin.domain.model.UserRole -import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -class CreationOfMateUseCaseTest { - - private lateinit var authRepository: AuthenticationRepository - private lateinit var idGenerator: IdGenerator - private lateinit var hashingPassword: HashingPassword - private lateinit var createMateUseCase: CreationOfMateUseCase - - @BeforeEach - fun setup() { - authRepository = mockk() - hashingPassword = mockk() - idGenerator = mockk() - createMateUseCase = CreationOfMateUseCase(authRepository, idGenerator, hashingPassword) - } - - @Test - fun `createMate fails when username is empty`() { - // Given - val emptyUsername = AuthServiceTestData.userNameIsEmpty - val password = AuthServiceTestData.userPassword - - // When - val result = createMateUseCase.createMate(emptyUsername, password) - - // Then - assertThat(result.isFailure).isTrue() - assertThat(result.exceptionOrNull()?.message).isEqualTo("Username and password must not be empty") - } - - @Test - fun `createMate fails when password is empty`() { - // Given - val username = AuthServiceTestData.userName - val emptyPassword = AuthServiceTestData.userPasswordIsEmpty - - // When - val result = createMateUseCase.createMate(username, emptyPassword) - - // Then - assertThat(result.isFailure).isTrue() - assertThat(result.exceptionOrNull()?.message).isEqualTo("Username and password must not be empty") - } - - @Test - fun `createMate fails when password length is less than 8 characters`() { - // Given - val username = AuthServiceTestData.userName - val shortPassword = AuthServiceTestData.passwordLessThanEight - - // When - val result = createMateUseCase.createMate(username, shortPassword) - - // Then - assertThat(result.isFailure).isTrue() - assertThat(result.exceptionOrNull()?.message).isEqualTo("Password less than 8 characters") - } - -} - - diff --git a/src/test/kotlin/domain/usecase/authService/GetUserByIDUseCaseTest.kt b/src/test/kotlin/domain/usecase/authService/GetUserByIDUseCaseTest.kt index 3687fa8..619257a 100644 --- a/src/test/kotlin/domain/usecase/authService/GetUserByIDUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/authService/GetUserByIDUseCaseTest.kt @@ -1,15 +1,16 @@ package com.berlin.domain.usecase.authService import com.berlin.domain.exception.InvalidUserIdException -import com.berlin.domain.repository.AuthenticationRepository import com.berlin.domain.helper.AuthServiceTestData +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole +import com.berlin.domain.repository.AuthenticationRepository import com.google.common.truth.Truth.assertThat import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows class GetUserByIDUseCaseTest { private lateinit var repository: AuthenticationRepository @@ -23,11 +24,14 @@ class GetUserByIDUseCaseTest { @Test - fun `getUserById throws InvalidUserIdException when ID is empty`() { + fun `getUserById return InvalidUserIdException when ID is empty`() { + val existingId = "" + every { repository.getUserById(existingId) + } returns Result.failure(InvalidUserIdException("User ID can't be empty or just digits")) + val result = getUserByIDUseCase.getUserById(existingId) // When & Then - assertThrows { - getUserByIDUseCase.getUserById("") - } + assertThat(result.isFailure) + } @Test @@ -46,9 +50,11 @@ class GetUserByIDUseCaseTest { @Test fun `throws Invalid User Id Exception when id is blank`() { - assertThrows { - getUserByIDUseCase.getUserById(" ") - } + val existingId = "51" + every { repository.getUserById(existingId) + } returns Result.failure(InvalidUserIdException("User ID can't be empty or just digits")) + verify(exactly = 0) { repository.getUserById(any()) } } + } \ No newline at end of file diff --git a/src/test/kotlin/domain/usecase/authService/GettingUsersLoggedInUseCaseTest.kt b/src/test/kotlin/domain/usecase/authService/GetUserLoggedInUseCaseTest.kt similarity index 81% rename from src/test/kotlin/domain/usecase/authService/GettingUsersLoggedInUseCaseTest.kt rename to src/test/kotlin/domain/usecase/authService/GetUserLoggedInUseCaseTest.kt index 8e18f17..25befa6 100644 --- a/src/test/kotlin/domain/usecase/authService/GettingUsersLoggedInUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/authService/GetUserLoggedInUseCaseTest.kt @@ -8,14 +8,14 @@ import io.mockk.mockk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class GettingUsersLoggedInUseCaseTest { +class GetUserLoggedInUseCaseTest { private lateinit var repository: AuthenticationRepository - private lateinit var gettingUsersLoggedInUseCase: GettingUsersLoggedInUseCase + private lateinit var gettingUsersLoggedInUseCase: GetUserLoggedInUseCase @BeforeEach fun setup() { repository = mockk() - gettingUsersLoggedInUseCase = GettingUsersLoggedInUseCase(repository) + gettingUsersLoggedInUseCase = GetUserLoggedInUseCase(repository) } diff --git a/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt b/src/test/kotlin/domain/usecase/project/CreateProjectUseCaseTest.kt similarity index 52% rename from src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt rename to src/test/kotlin/domain/usecase/project/CreateProjectUseCaseTest.kt index c3ccc6e..57a2df1 100644 --- a/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/project/CreateProjectUseCaseTest.kt @@ -1,12 +1,14 @@ -package logic.usecase.project +package com.berlin.domain.usecase.project -import com.berlin.domain.helper.IdGenerator -import com.berlin.helper.projectHelper import com.berlin.domain.repository.ProjectRepository -import com.berlin.domain.usecase.project.CreateProjectUseCase +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import com.berlin.domain.usecase.utils.IDGenerator.IdGenerator +import com.berlin.helper.projectHelper import com.google.common.truth.Truth.assertThat +import data.UserCache import io.mockk.every import io.mockk.mockk +import io.mockk.verify import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest @@ -17,30 +19,34 @@ class CreateProjectUseCaseTest { private lateinit var createProjectUseCase: CreateProjectUseCase private val projectRepository: ProjectRepository = mockk(relaxed = true) - + private val addAuditLogUseCase: AddAuditLogUseCase = mockk(relaxed = true) + private val cashedUser: UserCache = mockk(relaxed = true) @BeforeEach fun setup() { val idGenerator: IdGenerator = mockk(relaxed = true) - createProjectUseCase = CreateProjectUseCase(projectRepository, idGenerator) + createProjectUseCase = CreateProjectUseCase(projectRepository, idGenerator, addAuditLogUseCase, cashedUser) } @Test - fun `createNewProject should return success when project created successfully`() { + fun `createNewProject should log audit when project is created successfully`() { // Given val validProject = projectHelper() every { projectRepository.createProject(any()) } returns Result.success("Creation Successfully") + every { cashedUser.currentUser.id } returns "user_123" // When val result = createProjectUseCase.createNewProject( - validProject.name, - validProject.description, - validProject.statesId, - validProject.tasksId + validProject.name, validProject.description, validProject.statesId, validProject.tasksId ) // Then assertThat(result).isEqualTo(Result.success("Creation Successfully")) + verify(exactly = 1) { + addAuditLogUseCase.addAuditLog( + createdByUserId = "user_123", auditAction = any(), entityType = any(), entityId = any() + ) + } } @Test @@ -48,35 +54,26 @@ class CreateProjectUseCaseTest { // Given val validProject = projectHelper() every { projectRepository.createProject(any()) } returns Result.failure(Exception()) - // When val result = createProjectUseCase.createNewProject( - validProject.name, - validProject.description, - validProject.statesId, - validProject.tasksId + validProject.name, validProject.description, validProject.statesId, validProject.tasksId ) - // Then - result.onFailure { exception -> - assertThat(exception.message).isEqualTo("Creation Failed") - } + result.onFailure { exception -> assertThat(exception.message).isEqualTo("Creation Failed") } } @ParameterizedTest @ValueSource(strings = ["", " ", "123"]) - fun `validateProjectName should throw exception when project name is invalid`( - invalidName: String - ) { - // When && Then - assertThrows { - createProjectUseCase.createNewProject( - invalidName, - null, - null, - null - ) + fun `validateProjectName should throw exception when project name is invalid`(invalidName: String) { + // When & Then + val exception = assertThrows { + createProjectUseCase.createNewProject(invalidName, null, null, null) } + assertThat(exception.message).isEqualTo("Project Name must not be empty or blank") + + // Ensure no repository or audit log calls were made + verify(exactly = 0) { projectRepository.createProject(any()) } + } } \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt b/src/test/kotlin/domain/usecase/project/DeleteProjectUseCaseTest.kt similarity index 77% rename from src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt rename to src/test/kotlin/domain/usecase/project/DeleteProjectUseCaseTest.kt index 0d41784..e14edf2 100644 --- a/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/project/DeleteProjectUseCaseTest.kt @@ -1,10 +1,12 @@ -package logic.usecase.project; +package com.berlin.domain.usecase.project; import com.berlin.domain.repository.ProjectRepository -import com.berlin.domain.usecase.project.DeleteProjectUseCase +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase import com.google.common.truth.Truth.assertThat +import data.UserCache import io.mockk.every import io.mockk.mockk +import io.mockk.verify import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest @@ -15,22 +17,32 @@ class DeleteProjectUseCaseTest { private lateinit var deleteProjectUseCase: DeleteProjectUseCase private val projectRepository: ProjectRepository = mockk(relaxed = true) + private val addAuditLogUseCase: AddAuditLogUseCase = mockk(relaxed = true) + private val cashedUser: UserCache = mockk(relaxed = true) @BeforeEach fun setup() { - deleteProjectUseCase = DeleteProjectUseCase(projectRepository) + deleteProjectUseCase = DeleteProjectUseCase(projectRepository, addAuditLogUseCase, + cashedUser) } @Test fun `should return success when project deleted successfully`() { // Given every { projectRepository.deleteProject(any()) } returns Result.success("") + every { cashedUser.currentUser.id } returns "user_123" // When val result = deleteProjectUseCase.deleteProject("project_1") // Then assertThat(result).isEqualTo(Result.success("Deleted Successfully")) + verify(exactly = 1) { addAuditLogUseCase.addAuditLog( + createdByUserId = "user_123", + auditAction = any(), + entityType = any(), + entityId = any() + ) } } @Test diff --git a/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt b/src/test/kotlin/domain/usecase/project/GetAllProjectsUseCaseTest.kt similarity index 97% rename from src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt rename to src/test/kotlin/domain/usecase/project/GetAllProjectsUseCaseTest.kt index 24d855f..b4baec7 100644 --- a/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/project/GetAllProjectsUseCaseTest.kt @@ -1,4 +1,4 @@ -package com.berlin.logic.usecase.project +package com.berlin.domain.usecase.project import com.berlin.helper.projectHelper import com.berlin.domain.repository.ProjectRepository diff --git a/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt b/src/test/kotlin/domain/usecase/project/GetProjectByIdUseCaseTest.kt similarity index 97% rename from src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt rename to src/test/kotlin/domain/usecase/project/GetProjectByIdUseCaseTest.kt index c1a0b02..a132594 100644 --- a/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/project/GetProjectByIdUseCaseTest.kt @@ -1,4 +1,4 @@ -package logic.usecase.project; +package com.berlin.domain.usecase.project; import com.berlin.helper.projectHelper import com.berlin.domain.repository.ProjectRepository diff --git a/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt b/src/test/kotlin/domain/usecase/project/UpdateProjectUseCaseTest.kt similarity index 76% rename from src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt rename to src/test/kotlin/domain/usecase/project/UpdateProjectUseCaseTest.kt index 039c1f0..c60989b 100644 --- a/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/project/UpdateProjectUseCaseTest.kt @@ -1,11 +1,13 @@ -package logic.usecase.project; +package com.berlin.domain.usecase.project; import com.berlin.helper.projectHelper import com.berlin.domain.repository.ProjectRepository -import com.berlin.domain.usecase.project.UpdateProjectUseCase +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase import com.google.common.truth.Truth.assertThat +import data.UserCache import io.mockk.every import io.mockk.mockk +import io.mockk.verify import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest @@ -16,10 +18,13 @@ class UpdateProjectUseCaseTest { private lateinit var updateProjectUseCase: UpdateProjectUseCase private val projectRepository: ProjectRepository = mockk(relaxed = true) + private val addAuditLogUseCase: AddAuditLogUseCase = mockk(relaxed = true) + private val cashedUser: UserCache = mockk(relaxed = true) @BeforeEach fun setup() { - updateProjectUseCase = UpdateProjectUseCase(projectRepository) + updateProjectUseCase = UpdateProjectUseCase(projectRepository, + addAuditLogUseCase, cashedUser) } @Test @@ -27,12 +32,18 @@ class UpdateProjectUseCaseTest { // Given val project = projectHelper() every { projectRepository.updateProject(project) } returns Result.success("Updated Successfully") + every { cashedUser.currentUser.id } returns "user_123" // When val result = updateProjectUseCase.updateProject(project) // Then assertThat(result).isEqualTo(Result.success("Updated Successfully")) + verify(exactly = 1) { + addAuditLogUseCase.addAuditLog( + createdByUserId = "user_123", auditAction = any(), entityType = any(), entityId = any() + ) + } } @Test diff --git a/src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/CreateStateUseCaseTest.kt similarity index 95% rename from src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/CreateStateUseCaseTest.kt index f244117..558209f 100644 --- a/src/test/kotlin/logic/usecase/state/CreateStateUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/state/CreateStateUseCaseTest.kt @@ -1,9 +1,9 @@ -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.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.google.common.truth.Truth.assertThat import io.mockk.every import io.mockk.mockk diff --git a/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/DeleteStateUseCaseTest.kt similarity index 86% rename from src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/DeleteStateUseCaseTest.kt index 17a248f..a67fade 100644 --- a/src/test/kotlin/logic/usecase/state/DeleteStateUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/state/DeleteStateUseCaseTest.kt @@ -1,7 +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 @@ -51,14 +51,15 @@ class DeleteStateUseCaseTest { @Test fun `should throw exception when state does not exist`() { // Given - every { stateRepository.getStateById(any()) } returns null + 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/domain/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt similarity index 76% rename from src/test/kotlin/logic/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/GetAllStatesByProjectIdUseCaseTest.kt index 012ea69..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 @@ -35,24 +34,22 @@ 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") // 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 null + 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 +58,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/GetStateByIdUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/GetStateByIdUseCaseTest.kt similarity index 69% rename from src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/GetStateByIdUseCaseTest.kt index 522f573..9f5d3ab 100644 --- a/src/test/kotlin/logic/usecase/state/GetStateByIdUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/state/GetStateByIdUseCaseTest.kt @@ -1,6 +1,6 @@ -package com.berlin.logic.usecase.state +package com.berlin.domain.usecase.state -import com.berlin.domain.usecase.state.GetStateByIdUseCase +import com.berlin.domain.exception.StateNotFoundException import com.berlin.domain.model.State import com.berlin.domain.repository.StateRepository import com.google.common.truth.Truth.assertThat @@ -15,10 +15,11 @@ 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 +27,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/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 85% rename from src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/GetTasksByStateIdUseCaseTest.kt index 5a1acc6..c5ca912 100644 --- a/src/test/kotlin/logic/usecase/state/GetTasksByStateIdUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/state/GetTasksByStateIdUseCaseTest.kt @@ -1,6 +1,7 @@ -package com.berlin.logic.usecase.state +package com.berlin.domain.usecase.state -import com.berlin.domain.usecase.state.GetTasksByStateIdUseCase +import com.berlin.domain.exception.InvalidStateIdException +import com.berlin.domain.exception.StateNotFoundException import com.berlin.domain.model.Task import com.berlin.domain.repository.StateRepository import com.google.common.truth.Truth.assertThat @@ -60,10 +61,12 @@ 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 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") } diff --git a/src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt b/src/test/kotlin/domain/usecase/state/UpdateStateUseCaseTest.kt similarity index 85% rename from src/test/kotlin/logic/usecase/state/UpdateStateUseCaseTest.kt rename to src/test/kotlin/domain/usecase/state/UpdateStateUseCaseTest.kt index 3a1d9a6..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 @@ -29,7 +28,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 +41,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 +61,7 @@ class UpdateStateUseCaseTest { // When && Then assertThrows { - updateStateUseCase.updateState(input) + updateStateUseCase.updateState(input.id,input.name,input.projectId) } } diff --git a/src/test/kotlin/domain/usecase/task/AssignTaskUseCaseTest.kt b/src/test/kotlin/domain/usecase/task/AssignTaskUseCaseTest.kt index 447cfc9..e997fb5 100644 --- a/src/test/kotlin/domain/usecase/task/AssignTaskUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/task/AssignTaskUseCaseTest.kt @@ -2,14 +2,16 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidAssigneeException import com.berlin.domain.exception.TaskNotFoundException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task import com.berlin.domain.model.User import com.berlin.domain.model.UserRole import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify +import data.UserCache +import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -17,10 +19,12 @@ import org.junit.jupiter.api.assertThrows class AssignTaskUseCaseTest { private lateinit var taskRepository: TaskRepository + private lateinit var addAuditLogUseCase: AddAuditLogUseCase + private lateinit var userCache: UserCache private lateinit var useCase: AssignTaskUseCase - private val creator = User("U0", "alice", "pw", UserRole.ADMIN) - private val oldAssignee = User("U1", "john", "pw", UserRole.MATE) + private val creator = User("U0", "alice", "pw", UserRole.ADMIN) + private val oldAssignee = User("U1", "john", "pw", UserRole.MATE) private val anotherAssignee = User("U2", "bob", "pw", UserRole.MATE) private val stored = Task( @@ -36,35 +40,40 @@ class AssignTaskUseCaseTest { @BeforeEach fun setUp() { taskRepository = mockk() - useCase = AssignTaskUseCase(taskRepository) - } + addAuditLogUseCase = mockk() + userCache = mockk() + every { userCache.currentUser } returns creator + useCase = AssignTaskUseCase(taskRepository, addAuditLogUseCase, userCache) + } @Test fun `result is success when assignee changes`() { stubHappyPath() val result = useCase("1", anotherAssignee.id) assertThat(result.isSuccess).isTrue() + verifyAudit("1") } @Test fun `repository update is called with new assignee`() { stubHappyPath() useCase("1", anotherAssignee.id) - - verify(exactly = 1) { + verify { taskRepository.update( - match { it.id == "1" && it.assignedToUserId == anotherAssignee.id }) + match { it.id == "1" && it.assignedToUserId == anotherAssignee.id } + ) } } - @Test fun `result is failure when task is not found`() { every { taskRepository.findById("1") } returns Result.failure(TaskNotFoundException("1")) val result = useCase("1", anotherAssignee.id) + assertThat(result.isFailure).isTrue() + verify(exactly = 0) { taskRepository.update(any()) } } @Test @@ -73,6 +82,7 @@ class AssignTaskUseCaseTest { every { taskRepository.update(any()) } returns Result.failure(IllegalStateException("boom")) val result = useCase("1", anotherAssignee.id) + assertThat(result.isFailure).isTrue() } @@ -90,5 +100,26 @@ class AssignTaskUseCaseTest { private fun stubHappyPath() { every { taskRepository.findById("1") } returns Result.success(stored) every { taskRepository.update(any()) } answers { Result.success(firstArg()) } + every { + addAuditLogUseCase.addAuditLog( + createdByUserId = creator.id, + auditAction = AuditAction.UPDATE, + changesDescription = null, + entityType = EntityType.TASK, + entityId = "1" + ) + } returns Result.success("audit-id-1") + } + + private fun verifyAudit(taskId: String) { + verify { + addAuditLogUseCase.addAuditLog( + createdByUserId = creator.id, + auditAction = AuditAction.UPDATE, + changesDescription = null, + entityType = EntityType.TASK, + entityId = taskId + ) + } } } diff --git a/src/test/kotlin/domain/usecase/task/ChangeTaskStateUseCaseTest.kt b/src/test/kotlin/domain/usecase/task/ChangeTaskStateUseCaseTest.kt index c8d736e..e45bf19 100644 --- a/src/test/kotlin/domain/usecase/task/ChangeTaskStateUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/task/ChangeTaskStateUseCaseTest.kt @@ -2,13 +2,15 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskStateException import com.berlin.domain.exception.TaskNotFoundException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify +import data.UserCache +import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -16,6 +18,8 @@ import org.junit.jupiter.api.assertThrows class ChangeTaskStateUseCaseTest { private lateinit var taskRepository: TaskRepository + private lateinit var addAuditLogUseCase: AddAuditLogUseCase + private lateinit var userCache: UserCache private lateinit var useCase: ChangeTaskStateUseCase private val creator = mockk(relaxed = true) @@ -34,7 +38,22 @@ class ChangeTaskStateUseCaseTest { @BeforeEach fun setUp() { taskRepository = mockk() - useCase = ChangeTaskStateUseCase(taskRepository) + addAuditLogUseCase = mockk() + userCache = mockk() + every { userCache.currentUser } returns creator + + // Needed to avoid MockKException during audit logging + every { + addAuditLogUseCase.addAuditLog( + createdByUserId = any(), + auditAction = any(), + changesDescription = any(), + entityType = any(), + entityId = any() + ) + } returns Result.success("audit-log-id") + + useCase = ChangeTaskStateUseCase(taskRepository, addAuditLogUseCase, userCache) } @Test @@ -45,9 +64,10 @@ class ChangeTaskStateUseCaseTest { val result = useCase("1", "DONE") assertThat(result.isSuccess).isTrue() - verify(exactly = 1) { + verify { taskRepository.update(match { it.id == "1" && it.stateId == "DONE" }) } + verifyAudit("1") } @Test @@ -78,7 +98,6 @@ class ChangeTaskStateUseCaseTest { assertThrows { useCase("1", " ") } - // no update should be attempted after validation fails verify(exactly = 0) { taskRepository.update(any()) } } @@ -91,4 +110,16 @@ class ChangeTaskStateUseCaseTest { } verify(exactly = 0) { taskRepository.update(any()) } } + + private fun verifyAudit(taskId: String) { + verify { + addAuditLogUseCase.addAuditLog( + createdByUserId = creator.id, + auditAction = AuditAction.UPDATE, + changesDescription = null, + entityType = EntityType.TASK, + entityId = taskId + ) + } + } } diff --git a/src/test/kotlin/domain/usecase/task/CreateTaskUseCaseTest.kt b/src/test/kotlin/domain/usecase/task/CreateTaskUseCaseTest.kt index bbfe069..99f6e4f 100644 --- a/src/test/kotlin/domain/usecase/task/CreateTaskUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/task/CreateTaskUseCaseTest.kt @@ -2,14 +2,14 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskTitle import com.berlin.domain.exception.TaskAlreadyExistsException -import com.berlin.domain.helper.IdGeneratorImplementation +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase +import com.berlin.domain.usecase.utils.IDGenerator.IdGeneratorImplementation import com.google.common.truth.Truth.assertThat -import io.mockk.Called -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify +import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -18,6 +18,7 @@ class CreateTaskUseCaseTest { private lateinit var taskRepository: TaskRepository private lateinit var idGenerator: IdGeneratorImplementation + private lateinit var addAuditLogUseCase: AddAuditLogUseCase private lateinit var useCase: CreateTaskUseCase private val projectId = "P1" @@ -30,7 +31,8 @@ class CreateTaskUseCaseTest { fun setUp() { taskRepository = mockk() idGenerator = mockk() - useCase = CreateTaskUseCase(taskRepository, idGenerator) + addAuditLogUseCase = mockk(relaxUnitFun = true) + useCase = CreateTaskUseCase(taskRepository, idGenerator, addAuditLogUseCase) } @Test @@ -39,30 +41,45 @@ class CreateTaskUseCaseTest { val trimmed = rawTitle.trim() val generated = "T123" - every { idGenerator.generateId(eq(trimmed), any(), any()) } returns generated - // NOW: unique => no existing tasks + every { idGenerator.generateId(trimmed, any(), any()) } returns generated every { taskRepository.getAllTasks() } returns emptyList() - every { taskRepository.create(any()) }.answers { Result.success(firstArg()) } + every { taskRepository.create(any()) } answers { Result.success(firstArg()) } + + every { + addAuditLogUseCase.addAuditLog( + createByUserId, + AuditAction.CREATE, + null, + EntityType.TASK, + generated + ) + } returns Result.success("log-id-123") - val result = useCase( - projectId, rawTitle, description, stateId, createByUserId, assignedToUserId - ) + val result = useCase(projectId, rawTitle, description, stateId, createByUserId, assignedToUserId) assertThat(result.isSuccess).isTrue() - verify { idGenerator.generateId(eq(trimmed), any(), any()) } + verify { idGenerator.generateId(trimmed, any(), any()) } verify { taskRepository.create(match { it.id == generated && it.title == trimmed }) } + verify { + addAuditLogUseCase.addAuditLog( + createByUserId, + AuditAction.CREATE, + null, + EntityType.TASK, + generated + ) + } } + @Test fun `throws InvalidTaskTitle for blank title`() { assertThrows { - useCase( - projectId, " ", description, stateId, createByUserId, assignedToUserId - ) + useCase(projectId, " ", description, stateId, createByUserId, assignedToUserId) } verify { idGenerator wasNot Called } verify { taskRepository wasNot Called } @@ -71,9 +88,7 @@ class CreateTaskUseCaseTest { @Test fun `throws InvalidTaskTitle for numeric-only title`() { assertThrows { - useCase( - projectId, "12345", description, stateId, createByUserId, assignedToUserId - ) + useCase(projectId, "12345", description, stateId, createByUserId, assignedToUserId) } verify { idGenerator wasNot Called } verify { taskRepository wasNot Called } @@ -84,36 +99,40 @@ class CreateTaskUseCaseTest { val title = "Unique" val generated = "T999" - every { idGenerator.generateId(eq(title), any(), any()) } returns generated - // NOW: not unique => that ID is already in getAllTasks() + every { idGenerator.generateId(title, any(), any()) } returns generated every { taskRepository.getAllTasks() } returns listOf( Task(generated, projectId, title, description, stateId, assignedToUserId, createByUserId) ) assertThrows { - useCase( - projectId, title, description, stateId, createByUserId, assignedToUserId - ) + useCase(projectId, title, description, stateId, createByUserId, assignedToUserId) } - verify { idGenerator.generateId(eq(title), any(), any()) } + verify { idGenerator.generateId(title, any(), any()) } verify(exactly = 0) { taskRepository.create(any()) } } @Test fun `result is failure when repository create fails`() { val title = "Valid" + val trimmed = title.trim() val generated = "T500" - every { idGenerator.generateId(eq(title), any(), any()) } returns generated - // unique so we proceed to create + + every { idGenerator.generateId(trimmed, any(), any()) } returns generated every { taskRepository.getAllTasks() } returns emptyList() every { taskRepository.create(any()) } returns Result.failure(IllegalStateException("boom")) - val result = useCase( - projectId, title, description, stateId, createByUserId, assignedToUserId - ) + val result = useCase(projectId, trimmed, description, stateId, createByUserId, assignedToUserId) assertThat(result.isFailure).isTrue() - verify { taskRepository.create(match { it.id == generated }) } + verify { + taskRepository.create(match { + it.id == generated && + it.projectId == projectId && + it.title == trimmed && + it.assignedToUserId == assignedToUserId && + it.createByUserId == createByUserId + }) + } } } diff --git a/src/test/kotlin/domain/usecase/task/DeleteTaskUseCaseTest.kt b/src/test/kotlin/domain/usecase/task/DeleteTaskUseCaseTest.kt index 292d74c..616b822 100644 --- a/src/test/kotlin/domain/usecase/task/DeleteTaskUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/task/DeleteTaskUseCaseTest.kt @@ -1,12 +1,15 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.TaskNotFoundException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task +import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify +import data.UserCache +import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -14,8 +17,12 @@ import org.junit.jupiter.api.assertThrows class DeleteTaskUseCaseTest { private lateinit var taskRepository: TaskRepository + private lateinit var addAuditLogUseCase: AddAuditLogUseCase + private lateinit var userCache: UserCache private lateinit var deleteTaskUseCase: DeleteTaskUseCase + private lateinit var currentUser: User + private val stored = Task( id = "T1", projectId = "P1", @@ -29,7 +36,18 @@ class DeleteTaskUseCaseTest { @BeforeEach fun setUp() { taskRepository = mockk() - deleteTaskUseCase = DeleteTaskUseCase(taskRepository) + addAuditLogUseCase = mockk() + userCache = mockk() + + currentUser = mockk(relaxed = true) + every { currentUser.id } returns "U1" + every { userCache.currentUser } returns currentUser + + every { + addAuditLogUseCase.addAuditLog("U1", AuditAction.DELETE, null, EntityType.TASK, "T1") + } returns Result.success("audit-log-id") + + deleteTaskUseCase = DeleteTaskUseCase(taskRepository, addAuditLogUseCase, userCache) } @Test @@ -40,7 +58,16 @@ class DeleteTaskUseCaseTest { val result = deleteTaskUseCase("T1") assertThat(result.isSuccess).isTrue() - verify(exactly = 1) { taskRepository.delete("T1") } + verify { taskRepository.delete("T1") } + verify { + addAuditLogUseCase.addAuditLog( + createdByUserId = "U1", + auditAction = AuditAction.DELETE, + changesDescription = null, + entityType = EntityType.TASK, + entityId = "T1" + ) + } } @Test @@ -63,7 +90,7 @@ class DeleteTaskUseCaseTest { assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isInstanceOf(IllegalStateException::class.java) - verify(exactly = 1) { taskRepository.delete("T1") } + verify { taskRepository.delete("T1") } } @Test diff --git a/src/test/kotlin/domain/usecase/task/GetAllTasksUseCaseTest.kt b/src/test/kotlin/domain/usecase/task/GetAllTasksUseCaseTest.kt index 3dde75b..c29c3d9 100644 --- a/src/test/kotlin/domain/usecase/task/GetAllTasksUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/task/GetAllTasksUseCaseTest.kt @@ -1,8 +1,7 @@ -package domain.usecase.task +package com.berlin.domain.usecase.task import com.berlin.domain.model.Task import com.berlin.domain.repository.TaskRepository -import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.google.common.truth.Truth.assertThat import io.mockk.every import io.mockk.mockk diff --git a/src/test/kotlin/domain/usecase/task/UpdateTaskUseCaseTest.kt b/src/test/kotlin/domain/usecase/task/UpdateTaskUseCaseTest.kt index 50b0e04..e5e00cd 100644 --- a/src/test/kotlin/domain/usecase/task/UpdateTaskUseCaseTest.kt +++ b/src/test/kotlin/domain/usecase/task/UpdateTaskUseCaseTest.kt @@ -2,13 +2,15 @@ package com.berlin.domain.usecase.task import com.berlin.domain.exception.InvalidTaskTitle import com.berlin.domain.exception.TaskNotFoundException +import com.berlin.domain.model.AuditAction +import com.berlin.domain.model.EntityType import com.berlin.domain.model.Task import com.berlin.domain.model.User import com.berlin.domain.repository.TaskRepository +import com.berlin.domain.usecase.auditSystem.AddAuditLogUseCase import com.google.common.truth.Truth.assertThat -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify +import data.UserCache +import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -16,9 +18,11 @@ import org.junit.jupiter.api.assertThrows class UpdateTaskUseCaseTest { private lateinit var taskRepository: TaskRepository + private lateinit var addAuditLogUseCase: AddAuditLogUseCase + private lateinit var userCache: UserCache private lateinit var useCase: UpdateTaskUseCase - private val creator = mockk(relaxed = true) + private lateinit var creator: User private val assignee = mockk(relaxed = true) private val stored = Task( @@ -27,14 +31,26 @@ class UpdateTaskUseCaseTest { title = "Old title", description = "Old description", stateId = "TODO", - assignedToUserId = assignee.id, - createByUserId = creator.id + assignedToUserId = "U2", + createByUserId = "U1" ) @BeforeEach fun setUp() { taskRepository = mockk() - useCase = UpdateTaskUseCase(taskRepository) + addAuditLogUseCase = mockk() + userCache = mockk() + + creator = mockk(relaxed = true) + every { creator.id } returns "U1" + every { userCache.currentUser } returns creator + + // Stub audit log call to prevent MockKException + every { + addAuditLogUseCase.addAuditLog("U1", AuditAction.UPDATE, null, EntityType.TASK, "1") + } returns Result.success("log-id") + + useCase = UpdateTaskUseCase(taskRepository, addAuditLogUseCase, userCache) } @Test @@ -42,6 +58,7 @@ class UpdateTaskUseCaseTest { primeRepoToSucceed() val result = useCase("1", title = "New title") assertThat(result.isSuccess).isTrue() + verifyAudit() } @Test @@ -49,14 +66,15 @@ class UpdateTaskUseCaseTest { primeRepoToSucceed() val result = useCase("1", description = "New description") assertThat(result.isSuccess).isTrue() + verifyAudit() } @Test fun `success when only assignee changes`() { primeRepoToSucceed() - val newUser = mockk(relaxed = true) - val result = useCase("1", assignedToUserId = newUser.id) + val result = useCase("1", assignedToUserId = "NEW_USER") assertThat(result.isSuccess).isTrue() + verifyAudit() } @Test @@ -64,9 +82,9 @@ class UpdateTaskUseCaseTest { primeRepoToSucceed() val result = useCase("1") assertThat(result.isSuccess).isTrue() + verifyAudit() } - @Test fun `failure when task is not found`() { every { taskRepository.findById("1") } returns Result.failure(TaskNotFoundException("1")) @@ -75,39 +93,45 @@ class UpdateTaskUseCaseTest { } @Test - fun `failure when repository update returns unexpected error`() { + fun `failure when repository create returns unexpected error`() { every { taskRepository.findById("1") } returns Result.success(stored) - every { taskRepository.update(any()) } returns Result.failure(IllegalStateException("boom")) + every { taskRepository.create(any()) } returns Result.failure(IllegalStateException("boom")) val result = useCase("1", title = "New title") assertThat(result.isFailure).isTrue() } - @Test fun `throws InvalidTaskTitle when new title is blank`() { primeRepoToSucceed() - assertThrows { useCase("1", title = " ") } - - verify(exactly = 0) { taskRepository.update(any()) } + verify(exactly = 0) { taskRepository.create(any()) } } @Test fun `throws InvalidTaskTitle when new title is numeric-only`() { primeRepoToSucceed() - assertThrows { useCase("1", title = "123456") } - - verify(exactly = 0) { taskRepository.update(any()) } + verify(exactly = 0) { taskRepository.create(any()) } } - private fun primeRepoToSucceed() { every { taskRepository.findById("1") } returns Result.success(stored) - every { taskRepository.update(any()) } answers { Result.success(firstArg()) } + every { taskRepository.create(any()) } answers { Result.success(firstArg()) } + } + + private fun verifyAudit() { + verify { + addAuditLogUseCase.addAuditLog( + createdByUserId = "U1", + auditAction = AuditAction.UPDATE, + changesDescription = null, + entityType = EntityType.TASK, + entityId = "1" + ) + } } -} +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/MainMenuUITest.kt b/src/test/kotlin/presentation/MainMenuUITest.kt index 97af84f..8dbd697 100644 --- a/src/test/kotlin/presentation/MainMenuUITest.kt +++ b/src/test/kotlin/presentation/MainMenuUITest.kt @@ -1,126 +1,166 @@ -package presentation - -import com.berlin.presentation.MainMenuUI -import com.berlin.presentation.UiRunner -import com.berlin.presentation.io.Reader -import com.berlin.presentation.io.Viewer -import com.google.common.truth.Truth.assertThat -import io.mockk.Runs -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.fail - -class MainMenuUITest { - - private lateinit var viewer: Viewer - private lateinit var reader: Reader - private lateinit var menu: MainMenuUI - - private val printed = mutableListOf() - - @BeforeEach - fun setUp() { - viewer = mockk(relaxed = true) { - every { show(capture(printed)) } just Runs - } - reader = mockk() - } - - @Test - fun `exit immediately on blank input without running any runner`() { - every { reader.read() } returns "" - menu = MainMenuUI(emptyList(), viewer, reader) - - menu.run() - - // should have printed the menu once, then returned - assert(printed.first().contains("=== Task Manager ===")) - } - - @Test - fun `runs matching runner then exits on X`() { - val r0 = object : UiRunner { - override val id = 0 - override val label = "zero" - var invoked = 0 - override fun run() { - invoked++ - } - } - val r1 = object : UiRunner { - override val id = 1 - override val label = "one" - var invoked = 0 - override fun run() { - invoked++ - } - } - - every { reader.read() } returnsMany listOf("1", "X") - menu = MainMenuUI(listOf(r0, r1), viewer, reader) - - menu.run() - - assert(r1.invoked == 1) - assert(r0.invoked == 0) - - val banners = printed.filter { it.contains("=== Task Manager ===") } - assert(banners.size == 2) - } - - @Test - fun `invalid choice prints error then exits`() { - val dummy = object : UiRunner { - override val id = 5 - override val label = "five" - override fun run() = fail("should not run") - } - - every { reader.read() } returnsMany listOf("99", "") - menu = MainMenuUI(listOf(dummy), viewer, reader) - - menu.run() - - assert(printed.any { it.contains("Invalid choice: 99") }) - } - - @Test - fun `trimmed lowercase x also exits`() { - every { reader.read() } returnsMany listOf(" x ") - menu = MainMenuUI(emptyList(), viewer, reader) - - menu.run() - - assert(printed.size >= 1) - } - - @Test - fun `exit on null input`() { - every { reader.read() } returns null - - val dummy = object : UiRunner { - override val id = 1 - override val label = "one" - var ran = false - override fun run() { ran = true } - } - menu = MainMenuUI(listOf(dummy), viewer, reader) - - menu.run() - - assertThat(dummy.ran).isFalse() - assertThat(printed.first()).contains("=== Task Manager ===") - } - - @Test - fun `menu has correct id and label`() { - every { reader.read() } returns "" - menu = MainMenuUI(emptyList(), viewer, reader) - - assertThat(menu.id).isEqualTo(0) - assertThat(menu.label).isEqualTo("Main menu") - } -} \ No newline at end of file +//package presentation +// +//import com.berlin.domain.model.User +//import com.berlin.domain.model.UserRole +//import com.berlin.presentation.MainMenuUI +//import com.berlin.presentation.UiRunner +//import com.berlin.presentation.authService.AuthenticateUserUi +//import com.berlin.presentation.io.Reader +//import com.berlin.presentation.io.Viewer +//import com.google.common.truth.Truth.assertThat +//import data.UserCache +//import io.mockk.* +//import org.junit.jupiter.api.BeforeEach +//import org.junit.jupiter.api.Test +//import org.junit.jupiter.api.fail +// +//class MainMenuUITest { +// +// private lateinit var viewer: Viewer +// private lateinit var reader: Reader +// private lateinit var menu: MainMenuUI +// private lateinit var authUi: AuthenticateUserUi +// private lateinit var userCache: UserCache +// +// private val printed = mutableListOf() +// +// @BeforeEach +// fun setUp() { +// viewer = mockk(relaxed = true) { +// every { show(capture(printed)) } just Runs +// } +// reader = mockk() +// authUi = mockk() +// userCache = UserCache() +// userCache.currentUser = User("Y1", "menna", "12345678", UserRole.ADMIN) +// } +// +// +// @Test +// fun `run should return user role when successfully logged in`() { +// //given +// every { viewer.show("") } +// every { authUi.run() } just Runs +// every { viewer.show("") } +// every { reader.read() } returns "X" +// menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) +// +// //when +// menu.run() +// +// //Then +// assert(printed.contains("===ADMIN Board===")) +// } +// +// @Test +// fun `run should exit immediately on blank input without running any runner `() { +// //Given +// every { viewer.show("") } +// every { authUi.run() } just Runs +// every { viewer.show("") } +// every { reader.read() } returns "X" +// menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) +// +// //when +// menu.run() +// +// //Then +// assert(printed.first().contains("===Welcome to our PlanMate===")) +// +// +// } +// +// @Test +// fun `runs matching runner then exits on X`() { +// every { viewer.show("") } +// every { authUi.run() } just Runs +// every { viewer.show("") } +// val r0 = object : UiRunner { +// override val id = 0 +// override val label = "zero" +// var invoked = 0 +// override fun run() { +// invoked++ +// } +// } +// val r1 = object : UiRunner { +// override val id = 1 +// override val label = "one" +// var invoked = 0 +// override fun run() { +// invoked++ +// } +// } +// +// every { reader.read() } returnsMany listOf("1", "X") +// menu = MainMenuUI(listOf(r0, r1), viewer, reader, authUi, userCache) +// +// menu.run() +// +// assert(r1.invoked == 1) +// assert(r0.invoked == 0) +// +// } +// +// @Test +// fun `invalid choice prints error then exits`() { +// every { viewer.show("") } +// every { authUi.run() } just Runs +// every { viewer.show("") } +// val dummy = object : UiRunner { +// override val id = 5 +// override val label = "five" +// override fun run() = fail("should not run") +// } +// +// every { reader.read() } returnsMany listOf("99", "") +// menu = MainMenuUI(listOf(dummy), viewer, reader, authUi, userCache) +// +// menu.run() +// +// assert(printed.any { it.contains("Invalid choice: 99") }) +// } +// +// @Test +// fun `trimmed lowercase x also exits`() { +// every { viewer.show("") } +// every { authUi.run() } just Runs +// every { viewer.show("") } +// every { reader.read() } returnsMany listOf(" x ") +// menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) +// +// menu.run() +// +// assert(printed.size >= 1) +// } +// +// @Test +// fun `exit on null input`() { +// every { viewer.show("") } +// every { authUi.run() } just Runs +// every { viewer.show("") } +// every { reader.read() } returns null +// +// val dummy = object : UiRunner { +// override val id = 1 +// override val label = "one" +// var ran = false +// override fun run() { +// ran = true +// } +// } +// menu = MainMenuUI(listOf(dummy), viewer, reader, authUi, userCache) +// +// menu.run() +// +// assertThat(dummy.ran).isFalse() } +// +// @Test +// fun `menu has correct id and label`() { +// every { reader.read() } returns "" +// menu = MainMenuUI(emptyList(), viewer, reader, authUi, userCache) +// +// assertThat(menu.id).isEqualTo(0) +// assertThat(menu.label).isEqualTo("Main menu") +// } +//} diff --git a/src/test/kotlin/presentation/audit/AuditByProjectUITest.kt b/src/test/kotlin/presentation/audit/AuditByProjectUITest.kt index 6f4a044..572455c 100644 --- a/src/test/kotlin/presentation/audit/AuditByProjectUITest.kt +++ b/src/test/kotlin/presentation/audit/AuditByProjectUITest.kt @@ -6,6 +6,8 @@ import com.berlin.domain.model.AuditLog import com.berlin.domain.model.EntityType import com.berlin.domain.model.Project import com.berlin.domain.usecase.auditSystem.GetAuditLogsByProjectIdUseCase +import com.berlin.domain.usecase.project.GetAllProjectsUseCase +import com.berlin.domain.usecase.task.GetTasksByProjectUseCase import com.berlin.presentation.audit.AuditByProjectUI import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer @@ -18,6 +20,7 @@ import kotlin.test.Test class AuditByProjectUITest { private lateinit var getAuditLogsByProjectIdUseCase: GetAuditLogsByProjectIdUseCase + private lateinit var getAllProjectsUseCase: GetAllProjectsUseCase private lateinit var viewer: Viewer private lateinit var reader: Reader private lateinit var ui: AuditByProjectUI @@ -38,9 +41,10 @@ class AuditByProjectUITest { @BeforeEach fun setup() { getAuditLogsByProjectIdUseCase = mockk() + getAllProjectsUseCase = mockk() viewer = mockk(relaxed = true) reader = mockk() - ui = AuditByProjectUI(getAuditLogsByProjectIdUseCase, viewer, reader) + ui = AuditByProjectUI(getAuditLogsByProjectIdUseCase, getAllProjectsUseCase, viewer, reader) DummyData.projects.clear() DummyData.projects.add(sampleProject) @@ -50,7 +54,7 @@ class AuditByProjectUITest { fun `displays audit logs for selected project`() { every { reader.read() } returns "1" every { getAuditLogsByProjectIdUseCase.getAuditLogsByProjectId("P1") } returns sampleLogs - + every { getAllProjectsUseCase.getAllProjects() } returns listOf(sampleProject) ui.run() verify { @@ -64,7 +68,7 @@ class AuditByProjectUITest { fun `displays message when no logs exist for project`() { every { reader.read() } returns "1" every { getAuditLogsByProjectIdUseCase.getAuditLogsByProjectId("P1") } returns emptyList() - + every { getAllProjectsUseCase.getAllProjects() } returns listOf(sampleProject) ui.run() verify { @@ -75,7 +79,7 @@ class AuditByProjectUITest { @Test fun `displays cancelled message when input is x`() { every { reader.read() } returns "x" - + every { getAllProjectsUseCase.getAllProjects() } returns listOf(sampleProject) ui.run() verify { @@ -96,6 +100,7 @@ class AuditByProjectUITest { entityId = "P1" ) ) + every { getAllProjectsUseCase.getAllProjects() } returns listOf(sampleProject) ui.run() @@ -107,6 +112,7 @@ class AuditByProjectUITest { @Test fun `displays invalid selection for non-number input`() { every { reader.read() } returns "not-a-number" + every { getAllProjectsUseCase.getAllProjects() } returns listOf(sampleProject) ui.run() diff --git a/src/test/kotlin/presentation/audit/AuditByTaskUITest.kt b/src/test/kotlin/presentation/audit/AuditByTaskUITest.kt index 1e9c482..051f5c8 100644 --- a/src/test/kotlin/presentation/audit/AuditByTaskUITest.kt +++ b/src/test/kotlin/presentation/audit/AuditByTaskUITest.kt @@ -1,9 +1,13 @@ package presentation.audit import com.berlin.data.DummyData +import com.berlin.domain.exception.InvalidSelectionException import com.berlin.domain.model.* import com.berlin.domain.usecase.auditSystem.GetAuditLogsByTaskIdUseCase +import com.berlin.domain.usecase.project.GetAllProjectsUseCase +import com.berlin.domain.usecase.task.GetTasksByProjectUseCase import com.berlin.presentation.audit.AuditByTaskUI +import com.berlin.presentation.helper.choose import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer import io.mockk.every @@ -11,11 +15,14 @@ import io.mockk.mockk import io.mockk.verify import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows class AuditByTaskUITest { private val viewer = mockk(relaxed = true) private val reader = mockk() + private lateinit var getAllProjectsUseCase: GetAllProjectsUseCase + private lateinit var getTasksByProjectUseCase: GetTasksByProjectUseCase private val getAuditLogsByTaskIdUseCase = mockk() private lateinit var ui: AuditByTaskUI @@ -23,7 +30,16 @@ class AuditByTaskUITest { @BeforeEach fun setup() { - ui = AuditByTaskUI(viewer, reader, getAuditLogsByTaskIdUseCase) + getAllProjectsUseCase = mockk() + getTasksByProjectUseCase = mockk() + ui = AuditByTaskUI(viewer, reader, getAuditLogsByTaskIdUseCase, getTasksByProjectUseCase, getAllProjectsUseCase) + + every { getAllProjectsUseCase.getAllProjects() }returns listOf( + Project("P1", "Project 1", null, emptyList(), emptyList()) + ) + every { getTasksByProjectUseCase.invoke("P1") }returns Result.success(listOf( + Task("T1", "P1", "Task 1", null, "S1", "U2", "U1") + )) DummyData.projects.clear() DummyData.projects.addAll( @@ -97,7 +113,6 @@ class AuditByTaskUITest { fun `should display message when no logs are found`() { every { reader.read() } returnsMany listOf("1", "1") every { getAuditLogsByTaskIdUseCase.getAuditLogsByTaskId("T1") } returns emptyList() - ui.run() verify { viewer.show("No audit logs found for this task.") } @@ -106,7 +121,6 @@ class AuditByTaskUITest { @Test fun `should handle InputCancelledException gracefully`() { every { reader.read() } returns "x" - ui.run() verify { viewer.show("Cancelled.") } @@ -120,4 +134,7 @@ class AuditByTaskUITest { verify { viewer.show("Invalid selection") } } + + + } \ No newline at end of file diff --git a/src/test/kotlin/presentation/authService/AuthenticateUserUITest.kt b/src/test/kotlin/presentation/authService/AuthenticateUserUITest.kt new file mode 100644 index 0000000..3a80e39 --- /dev/null +++ b/src/test/kotlin/presentation/authService/AuthenticateUserUITest.kt @@ -0,0 +1,200 @@ +package presentation.authService + +import com.berlin.domain.exception.InvalidCredentialsException +import com.berlin.domain.fakeData.FakeHashingString +import com.berlin.domain.hashPassword.HashingString +import com.berlin.domain.helper.AuthServiceTestData.CACHEUSER +import com.berlin.domain.helper.AuthServiceTestData.EMPTY_USER +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole +import com.berlin.domain.repository.AuthenticationRepository +import com.berlin.presentation.authService.AuthenticateUserUI +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer +import com.google.common.truth.Truth.assertThat +import data.UserCache +import domain.usecase.authService.AuthenticateUserUseCase +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.verify +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertTrue +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + + +class AuthenticateUserUITest { + private lateinit var userCache: UserCache + private lateinit var authenticationRepository: AuthenticationRepository + private lateinit var hashPassword: HashingString + private lateinit var authenticateUserUseCase: AuthenticateUserUseCase + private lateinit var authenticateUserUi: AuthenticateUserUI + private lateinit var viewer: Viewer + private lateinit var reader: Reader + private val testUser = User("id123", "admin", "hashed123", UserRole.ADMIN) + private val userName = "admin" + private val wrongPassword = "wrong" + + @BeforeEach + fun setup() { + userCache = UserCache(CACHEUSER) + authenticationRepository = mockk(relaxed = true) + hashPassword = FakeHashingString() + viewer = mockk(relaxed = true) + reader = mockk() + authenticateUserUseCase = + AuthenticateUserUseCase(userCache, authenticationRepository, hashPassword) + authenticateUserUi = AuthenticateUserUI(authenticateUserUseCase, viewer, reader) + } + + @Test + fun `run should return success when user enter valid data `() { + val hashingString = hashPassword.hashPassword("1212") + every { reader.read() } returnsMany listOf("admin", "1212") + every { viewer.show(any()) } just Runs + every { authenticationRepository.login("admin", hashingString) } + val result = authenticateUserUseCase.login("admin", "1212") + userCache.currentUser = CACHEUSER + assertThat(result) + + } + + @Test + fun `run should return failed when enter invalid data `() { + every { reader.read() } returnsMany listOf("ahmed", "5684") + every { + authenticationRepository.login(any(), any()) + } returns Result.failure(InvalidCredentialsException("No user found")) + + val result = authenticateUserUseCase.login("ahmed", "5684") + + assertThat(result) + } + + @Test + fun `login succeeds on first attempt`() { + val expectedUser = User("user1234", "admin", "1212", UserRole.ADMIN) + + every { reader.read() } returnsMany listOf("admin", "1212") + every { authenticationRepository.login("admin", "1212") } returns Result.success( + expectedUser + ) + every { viewer.show(any()) } just Runs + + authenticateUserUi.run() + + assertEquals(expectedUser, userCache.currentUser) + verify { viewer.show("Welcome admin") } + + } + + @Test + fun `run should login successfully on first attempt`() { + val testUser = User("user1234", "admin", "1212", UserRole.ADMIN) + + every { reader.read() } returnsMany listOf("admin", "1212") + every { authenticationRepository.login("admin", "1212") } returns Result.success(testUser) + every { viewer.show(any()) } just Runs + + authenticateUserUi.run() + + Assertions.assertEquals(testUser, userCache.currentUser) + verify { viewer.show("Welcome admin") } + } + + @Test + fun `should login successfully on first attempt`() { + every { reader.read() } returnsMany listOf("admin", "1234") + every { authenticationRepository.login("admin", "1234") } returns Result.success(testUser) + + authenticateUserUi.run() + + verify { viewer.show("Enter your user name: ") } + verify { viewer.show("Enter your password: ") } + verify { viewer.show("Welcome admin") } + } + + @Test + fun `should stop retrying if login succeeds before max attempts`() { + val userCache = UserCache(EMPTY_USER) + val hashing = mockk() + + val authenticateUserUseCase = + AuthenticateUserUseCase(userCache, authenticationRepository, hashing) + val ui = AuthenticateUserUI(authenticateUserUseCase, viewer, reader) + + // simulate inputs + every { reader.read() } returnsMany listOf( + userName, wrongPassword, userName, wrongPassword, userName, "correct" + ) + + every { hashing.hashPassword(wrongPassword) } returns "hashed_wrong" + every { hashing.hashPassword("correct") } returns "hashed_correct" + + every { authenticationRepository.login(userName, "hashed_wrong") } returnsMany listOf( + Result.failure(Exception("Invalid")), Result.failure(Exception("Invalid")) + ) + every { authenticationRepository.login(userName, "hashed_correct") } returns Result.success( + testUser + ) + + ui.run() + + verify { viewer.show("Welcome $userName") } + } + + @Test + fun `should show Try again two times then stop on third failure`() { + every { reader.read() } returnsMany listOf( + "admin", "wrong", "admin", "wrong", "admin", "wrong" + ) + every { authenticationRepository.login("admin", "wrong") } returnsMany listOf( + Result.failure(Exception("fail1")), + Result.failure(Exception("fail2")), + Result.failure(Exception("fail3")) + ) + every { viewer.show(any()) } just Runs + + val useCase = AuthenticateUserUseCase(userCache, authenticationRepository, hashPassword) + val ui = AuthenticateUserUI(useCase, viewer, reader) + + ui.run() + + verify(exactly = 0) { viewer.show("Try again") } + } + + @Test + fun `should handle null password`() { + every { reader.read() } returnsMany listOf("admin", null) + every { + authenticationRepository.login( + "admin", + "" + ) + } returns Result.failure(Exception("Invalid")) + + val result = authenticateUserUi.validateUser() + + assertTrue(result.isFailure) + } + + @Test + fun `should handle null username and password`() { + every { reader.read() } returnsMany listOf(null, null) + every { + authenticationRepository.login( + "", + "" + ) + } returns Result.failure(Exception("Invalid")) + + val result = authenticateUserUi.validateUser() + + assertTrue(result.isFailure) + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/authService/CreateMateUITest.kt b/src/test/kotlin/presentation/authService/CreateMateUITest.kt index 84acd37..3c720f2 100644 --- a/src/test/kotlin/presentation/authService/CreateMateUITest.kt +++ b/src/test/kotlin/presentation/authService/CreateMateUITest.kt @@ -2,7 +2,7 @@ package com.berlin.presentation.authService import com.berlin.domain.exception.InvalidAssigneeException import com.berlin.domain.helper.AuthServiceTestData -import com.berlin.domain.usecase.authService.CreationOfMateUseCase +import com.berlin.domain.usecase.authService.CreateMateUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer import io.mockk.every @@ -12,10 +12,10 @@ import io.mockk.verifySequence import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class CreationOfMateUiTest { +class CreateMateUITest { - private lateinit var creationOfMateUseCase: CreationOfMateUseCase - private lateinit var creationOfMateUi: CreationOfMateUi + private lateinit var createMateUseCase: CreateMateUseCase + private lateinit var creationOfMateUi: CreateMateUI private lateinit var viewer: Viewer private lateinit var reader: Reader @@ -23,16 +23,16 @@ class CreationOfMateUiTest { @BeforeEach fun setup() { - creationOfMateUseCase = mockk() + createMateUseCase = mockk() viewer = mockk(relaxed = true) reader = mockk() - creationOfMateUi = CreationOfMateUi(creationOfMateUseCase, viewer, reader) + creationOfMateUi = CreateMateUI(createMateUseCase, viewer, reader) } @Test fun `run should show success message when user creation succeeds`() { every { reader.read() } returnsMany listOf(AuthServiceTestData.testForUserName, AuthServiceTestData.testForUserPassword) - every { creationOfMateUseCase.createMate(AuthServiceTestData.testForUserName, AuthServiceTestData.testForUserPassword) } returns Result.success(AuthServiceTestData.user) + every { createMateUseCase.createMate(AuthServiceTestData.testForUserName, AuthServiceTestData.testForUserPassword) } returns Result.success(AuthServiceTestData.user) creationOfMateUi.run() @@ -42,17 +42,17 @@ class CreationOfMateUiTest { @Test fun `run should retry once after failure and succeed second time`() { every { reader.read() } returnsMany listOf("test1", "123", "test2", "456") - every { creationOfMateUseCase.createMate("test1", "123") } returns Result.failure(InvalidAssigneeException("fail")) - every { creationOfMateUseCase.createMate("test2", "456") } returns Result.success(AuthServiceTestData.excepctedUser) + every { createMateUseCase.createMate("test1", "123") } returns Result.failure(InvalidAssigneeException("fail")) + every { createMateUseCase.createMate("test2", "456") } returns Result.success(AuthServiceTestData.expectedUser) creationOfMateUi.run() verifySequence { - viewer.show("Enter user name: ") + viewer.show("Enter user name or x to exit: ") viewer.show("Enter user password: ") - viewer.show("something wrong please try again!") + viewer.show("fail") - viewer.show("Enter user name: ") + viewer.show("Enter user name or x to exit: ") viewer.show("Enter user password: ") viewer.show("New mate is successfully created!") } @@ -62,12 +62,14 @@ class CreationOfMateUiTest { @Test fun `run should treat null inputs as empty strings`() { every { reader.read() } returnsMany listOf(null, null, "name", "pass") - every { creationOfMateUseCase.createMate("", "") } returns Result.failure(InvalidAssigneeException("empty")) - every { creationOfMateUseCase.createMate("name", "pass") } returns Result.success(AuthServiceTestData.excepctedUser) + every { createMateUseCase.createMate("", "") } returns Result.failure(InvalidAssigneeException("empty")) + every { createMateUseCase.createMate("name", "pass") } returns Result.success(AuthServiceTestData.expectedUser) creationOfMateUi.run() - verify { viewer.show("something wrong please try again!") } + verify { viewer.show("empty") } // matching the actual error message verify { viewer.show("New mate is successfully created!") } } + + } diff --git a/src/test/kotlin/presentation/authService/FetchAllUsersUITest.kt b/src/test/kotlin/presentation/authService/FetchAllUsersUITest.kt index 151b86e..2c7912e 100644 --- a/src/test/kotlin/presentation/authService/FetchAllUsersUITest.kt +++ b/src/test/kotlin/presentation/authService/FetchAllUsersUITest.kt @@ -10,7 +10,7 @@ import io.mockk.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class FetchAllUsersUseCaseTest { +class FetchAllUsersUITest { private lateinit var viewer: Viewer private lateinit var useCase: FetchAllUsersUseCase private lateinit var ui: FetchAllUsersUI @@ -29,10 +29,6 @@ class FetchAllUsersUseCaseTest { @Test fun `should print all users when users are available`() { // Given - val users = listOf( - User(id = "1", userName = "Menna", password = "12345678j", role =UserRole.ADMIN), - User(id = "2", userName = "Sarah", password = "1234567890", role = UserRole.MATE) - ) every { useCase.getAllUsers() } returns Result.success(users) // When @@ -47,9 +43,11 @@ class FetchAllUsersUseCaseTest { "ID: 2", "Name: Sarah", "role: MATE", - "=====================") + "=====================" + ) } + @Test fun `should print message when no users are found`() { // Given @@ -63,4 +61,30 @@ class FetchAllUsersUseCaseTest { "No users found." ) } + + @Test + fun `should return users when there is users`() { + every { useCase.getAllUsers() } returns Result.success(users) + + ui.run() + + verifySequence { + viewer.show("ID: ${users[0].id}") + viewer.show("Name: ${users[0].userName}") + viewer.show("role: ${users[0].role}") + viewer.show("=====================") + viewer.show("ID: ${users[1].id}") + viewer.show("Name: ${users[1].userName}") + viewer.show("role: ${users[1].role}") + viewer.show("=====================") + } + } + + private companion object { + val users = listOf( + User(id = "1", userName = "Menna", password = "12345678j", role = UserRole.ADMIN), + User(id = "2", userName = "Sarah", password = "1234567890", role = UserRole.MATE) + ) + } + } \ No newline at end of file diff --git a/src/test/kotlin/presentation/authService/GetUserByIDUITest.kt b/src/test/kotlin/presentation/authService/GetUserByIDUITest.kt index 417a43c..6933c67 100644 --- a/src/test/kotlin/presentation/authService/GetUserByIDUITest.kt +++ b/src/test/kotlin/presentation/authService/GetUserByIDUITest.kt @@ -1,5 +1,6 @@ package presentation.authService +import com.berlin.domain.exception.InvalidUserIdException import com.berlin.domain.helper.AuthServiceTestData import com.berlin.domain.usecase.authService.GetUserByIDUseCase import com.berlin.presentation.authService.GetUserByIDUI @@ -44,6 +45,74 @@ class GetUserByIDUITest { verify { useCase.getUserById(id) } assertThat(printed).contains("Enter the user id: ") } + @Test + fun `should call use case when correct user ID with regardless to spaces in begin of id`() { + // Given + val id = AuthServiceTestData.idWithSpacesExist + every { reader.read() } returns id.trim() + every { useCase.getUserById(id.trim()) } returns Result.success(AuthServiceTestData.existingUser) + + // When + ui.run() + + // Then + verify { useCase.getUserById(id.trim()) } + assertThat(printed).contains("Enter the user id: ") + + } + + @Test + fun `should throw exception when id not exists`() { + val id = AuthServiceTestData.idNotExist + every { reader.read() } returns id + every { useCase.getUserById(id) } returns Result.failure(InvalidUserIdException("User ID can't be empty or just digits")) + + // When + ui.run() + + // Then + assertThat(useCase.getUserById(id).isFailure).isTrue() + } + @Test + fun `getUserById should return User ID can't be blank `(){ + //Given + every { reader.read() } returns " " + every { useCase.getUserById("") } returns Result.failure(InvalidUserIdException("User ID can't be empty or just digits")) + + //When + ui.run() + + //Then + assertThat(printed.last()).isEqualTo("User ID can't be empty or just digits") + + } + + @Test + fun `getUserById should return User ID can't be empty `(){ + //Given + every { reader.read() } returns "" + every { useCase.getUserById("") } returns Result.failure(InvalidUserIdException("User ID can't be empty or just digits")) + + //When + ui.run() + + //Then + assertThat(printed.last()).isEqualTo("User ID can't be empty or just digits") + + } + @Test + fun `getUserById should return error when there is un expected error `(){ + //Given + every { reader.read() } returns "" + every { useCase.getUserById("") } returns Result.failure(Exception()) + + //When + ui.run() + + //Then + assertThat(printed.last()).isEqualTo("invalid user id") + + } -} \ No newline at end of file +} diff --git a/src/test/kotlin/presentation/authService/GettingUsersLoggedInUITest.kt b/src/test/kotlin/presentation/authService/GettingUsersLoggedInUITest.kt new file mode 100644 index 0000000..cf7cb3c --- /dev/null +++ b/src/test/kotlin/presentation/authService/GettingUsersLoggedInUITest.kt @@ -0,0 +1,53 @@ +package presentation.authService + +import com.berlin.domain.exception.UserNotLoggedInException +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole +import com.berlin.domain.usecase.authService.GetUserLoggedInUseCase +import com.berlin.presentation.authService.GettingUsersLoggedInUI +import com.berlin.presentation.io.Reader +import com.berlin.presentation.io.Viewer +import io.mockk.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class GettingUsersLoggedInUITest { + private lateinit var getUserLoggedIn: GetUserLoggedInUseCase + private lateinit var gettingUsersLoggedInUI: GettingUsersLoggedInUI + private lateinit var viewer: Viewer + private lateinit var reader: Reader + + + @BeforeEach + fun setup() { + getUserLoggedIn = mockk() + viewer = mockk(relaxed = true) + reader = mockk() + gettingUsersLoggedInUI = GettingUsersLoggedInUI(getUserLoggedIn, viewer) + } + + @Test + fun `gettingUserLoggedIn should return current user when some one logged in`() { + every { getUserLoggedIn.getCurrentUser() } returns Result.success(cachedUser) + + gettingUsersLoggedInUI.run() + + verifySequence { + viewer.show("ID: ${cachedUser.id}") + + viewer.show(" Name: ${cachedUser.userName}") + viewer.show(" role: ${cachedUser.role}") + } + } + + @Test + fun `gettinguserLoggedIn should return error message when no one logged in`() { + every { getUserLoggedIn.getCurrentUser() } returns Result.failure(UserNotLoggedInException("No one logged in")) + gettingUsersLoggedInUI.run() + verify { viewer.show("no user logged in,please log in") } + } + + private companion object { + val cachedUser = User(id = "1", userName = "Menna", password = "12345678j", role = UserRole.ADMIN) + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/project/CreateProjectUiTest.kt b/src/test/kotlin/presentation/project/CreateProjectUiTest.kt index 684a19d..5549aef 100644 --- a/src/test/kotlin/presentation/project/CreateProjectUiTest.kt +++ b/src/test/kotlin/presentation/project/CreateProjectUiTest.kt @@ -1,14 +1,10 @@ -package presentation.project +package com.berlin.presentation.project -import com.berlin.domain.exception.InputCancelledException import com.berlin.domain.usecase.project.CreateProjectUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -import com.berlin.presentation.project.CreateProjectUi import io.mockk.* import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource import kotlin.test.Test class CreateProjectUiTest { @@ -20,9 +16,9 @@ class CreateProjectUiTest { @BeforeEach fun setup() { - createProjectUseCase = mockk() + createProjectUseCase = mockk(relaxed = true) viewer = mockk(relaxed = true) - reader = mockk() + reader = mockk(relaxed = true) ui = CreateProjectUi(createProjectUseCase, viewer, reader) } @@ -32,143 +28,82 @@ class CreateProjectUiTest { val name = "TestProject" val description = "Test Description" - every { reader.read() } returns name andThen description - every { - createProjectUseCase.createNewProject(name, description, null, null) - } returns Result.success("Creation Successfully") + every { reader.read() } returns name andThen "yes" andThen description + every { createProjectUseCase.createNewProject(name, description, + null, null) }returns Result.success("Creation Successfully") // When ui.run() // Then - verifySequence { - viewer.show("Enter project name:") - reader.read() - viewer.show("Enter project description (optional):") - reader.read() - createProjectUseCase.createNewProject(name, description, null, null) - viewer.show("Project created successfully") - } - } - - @Test - fun `run should show error when project name is empty`() { - // Given - every { reader.read() } returns " " - - // When - ui.run() - - // Then - verify { - viewer.show("Enter project name:") - reader.read() - viewer.show("Error: Project name cannot be empty") - } + verify { viewer.show("Creation Successfully") } } @Test fun `run should show failure message when project creation fails`() { // Given - val name = "ValidName" - val description = "Something" + val name = "ValidProject" + val description = "Test Description" - every { reader.read() } returns name andThen description - every { - createProjectUseCase.createNewProject(name, description, null, null) - } returns Result.failure(Exception("Failed")) + every { reader.read() } returns name andThen "yes" andThen description + every { createProjectUseCase.createNewProject(name, description, null, null) }returns Result.failure(Exception("Creation Failed")) // When ui.run() // Then verifySequence { - viewer.show("Enter project name:") + viewer.show("=== Create New Project ===\n") + viewer.show("================================================================\n\n") + viewer.show("Enter project details:\n") + viewer.show("Project Title:") reader.read() - viewer.show("Enter project description (optional):") + viewer.show("Do you want to write a description? (yes/no)") reader.read() - createProjectUseCase.createNewProject(name, description, null, null) - viewer.show("Failed") - } - } - - @Test - fun `run should show cancelled message when input is cancelled`() { - // Given - every { reader.read() } throws InputCancelledException("User cancelled") - - // When - ui.run() - - // Then - verify { - viewer.show("Enter project name:") - viewer.show("Project creation cancelled.") + viewer.show("Enter project description:") + reader.read() + viewer.show("Creating project...\n") + createProjectUseCase.createNewProject(name, description, + null, null) + viewer.show("Error: Creation Failed\n") } } @Test - fun `run should show default failure message when exception has no message`() { - // Given - val name = "ValidProject" - val description = "Something" - - every { reader.read() } returns name andThen description - every { - createProjectUseCase.createNewProject(name, description, null, null) - } returns Result.failure(Exception()) - - // When + fun `run should view to user again when input null project name`(){ + //given + every { reader.read() } returns null andThen "ggg" andThen "sfd" + every { createProjectUseCase.createNewProject(any(),any(),any(),any()) }returns Result.success("j") + //when ui.run() - - // Then - verifySequence { - viewer.show("Enter project name:") - reader.read() - viewer.show("Enter project description (optional):") - reader.read() - createProjectUseCase.createNewProject(name, description, null, null) - viewer.show("Creation failed") - } + //then + verify { viewer.show("Please enter a valid project name:") } } @Test - fun `run should show error when name is null and becomes empty`() { - // Given - every { reader.read() } returns null - - // When + fun `run should view to user again when input blank project name`(){ + //given + every { reader.read() } returns "" andThen "ggg" andThen "sfd" + every { createProjectUseCase.createNewProject(any(),any(),any(),any()) }returns Result.success("j") + //when ui.run() - - // Then - verify { - viewer.show("Enter project name:") - viewer.show("Error: Project name cannot be empty") - } + //then + verify { viewer.show("Please enter a valid project name:") } } @Test fun `run should create project when description is null`() { // Given val name = "MyProject" - val nullDescription: String? = null - every { reader.read() } returns name andThen nullDescription - every { - createProjectUseCase.createNewProject(name, null, null, null) - } returns Result.success("Creation Successfully") + every { reader.read() } returns name andThen "no" + every { createProjectUseCase.createNewProject(name, null, + null, null) }returns Result.success("Creation Successfully") // When ui.run() // Then - verifySequence { - viewer.show("Enter project name:") - reader.read() - viewer.show("Enter project description (optional):") - reader.read() - createProjectUseCase.createNewProject(name, null, null, null) - viewer.show("Project created successfully") - } + verify { viewer.show("Creation Successfully") } } -} +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/project/DeleteProjectUiTest.kt b/src/test/kotlin/presentation/project/DeleteProjectUiTest.kt index 9548ea9..52bfe05 100644 --- a/src/test/kotlin/presentation/project/DeleteProjectUiTest.kt +++ b/src/test/kotlin/presentation/project/DeleteProjectUiTest.kt @@ -1,4 +1,4 @@ -package presentation.project +package com.berlin.presentation.project import com.berlin.domain.exception.InvalidProjectIdException import com.berlin.domain.model.Project diff --git a/src/test/kotlin/presentation/project/GetAllProjectsUiTest.kt b/src/test/kotlin/presentation/project/GetAllProjectsUiTest.kt index 4751863..a953629 100644 --- a/src/test/kotlin/presentation/project/GetAllProjectsUiTest.kt +++ b/src/test/kotlin/presentation/project/GetAllProjectsUiTest.kt @@ -1,4 +1,4 @@ -package presentation.project +package com.berlin.presentation.project import com.berlin.domain.usecase.project.GetAllProjectsUseCase import com.berlin.helper.projectHelper diff --git a/src/test/kotlin/presentation/project/GetProjectByIdUiTest.kt b/src/test/kotlin/presentation/project/GetProjectByIdUiTest.kt index 62669fd..c37fe4e 100644 --- a/src/test/kotlin/presentation/project/GetProjectByIdUiTest.kt +++ b/src/test/kotlin/presentation/project/GetProjectByIdUiTest.kt @@ -1,4 +1,4 @@ -package presentation.project +package com.berlin.presentation.project import com.berlin.domain.exception.InvalidProjectIdException import com.berlin.domain.exception.ProjectNotFoundException diff --git a/src/test/kotlin/presentation/project/UpdateProjectUiTest.kt b/src/test/kotlin/presentation/project/UpdateProjectUiTest.kt index 0046750..eefddc4 100644 --- a/src/test/kotlin/presentation/project/UpdateProjectUiTest.kt +++ b/src/test/kotlin/presentation/project/UpdateProjectUiTest.kt @@ -1,4 +1,4 @@ -package presentation.project +package com.berlin.presentation.project import com.berlin.domain.model.Project import com.berlin.domain.usecase.project.GetAllProjectsUseCase @@ -7,13 +7,11 @@ import com.berlin.domain.usecase.project.UpdateProjectUseCase import com.berlin.helper.projectHelper import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -import com.berlin.presentation.project.UpdateProjectUi import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.verify import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.assertThrows import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/test/kotlin/presentation/state/CreateStateUiTest.kt b/src/test/kotlin/presentation/state/CreateStateUiTest.kt new file mode 100644 index 0000000..5d0de79 --- /dev/null +++ b/src/test/kotlin/presentation/state/CreateStateUiTest.kt @@ -0,0 +1,117 @@ +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 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) + + + 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() } + } +} diff --git a/src/test/kotlin/presentation/state/DeleteStateUiTest.kt b/src/test/kotlin/presentation/state/DeleteStateUiTest.kt new file mode 100644 index 0000000..6aca342 --- /dev/null +++ b/src/test/kotlin/presentation/state/DeleteStateUiTest.kt @@ -0,0 +1,130 @@ +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.google.common.truth.Truth.assertThat +import io.mockk.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + + + +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() + + //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..60ba5dc --- /dev/null +++ b/src/test/kotlin/presentation/state/GetAllStatesByProjectIdUiTest.kt @@ -0,0 +1,133 @@ +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 + + + @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 new file mode 100644 index 0000000..84d2586 --- /dev/null +++ b/src/test/kotlin/presentation/state/GetStateByIdUiTest.kt @@ -0,0 +1,79 @@ +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 com.google.common.truth.Truth.assertThat +import io.mockk.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class GetStateByIdUiTest { + + private lateinit var viewer: Viewer + 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) { + every { show(capture(printed)) } just Runs + } + reader = mockk() + getStateById = mockk() + ui = GetStateByIdUi(getStateById, viewer, reader) + 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(stateExists) + 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 stateIdNotExist + every { getStateById.getStateById(stateIdNotExist) } returns Result.failure(StateNotFoundException("State with ID Q999 not found")) + + // When + ui.run() + + // Then + assertThat(printed.last()).isEqualTo("No state found with ID “Q999”") + } + + @Test + fun `lookup failed fallback when exception message null`() { + //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") + } + +} \ 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..de588fc --- /dev/null +++ b/src/test/kotlin/presentation/state/UpdateStateUiTest.kt @@ -0,0 +1,96 @@ +package presentation.state + +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 +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 + +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) + } + + + @Test + fun `run should return successfully updated when every thing is correct`() { + //Given + every { getAllStates() } returns states + every { reader.read() } returnsMany listOf("1", "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( + state.id, + successfullyStateNewName, + state.projectId + ) + } + verify { viewer.show("Updated Successfully") } + } + + @Test + fun `run should return update failed when update fails`() { + //Given + every { getAllStates() } returns states + every { reader.read() } returnsMany listOf("1", "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 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") + + //When + updateStateUi.run() + + //Then + 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 diff --git a/src/test/kotlin/presentation/task/AssignTaskUITest.kt b/src/test/kotlin/presentation/task/AssignTaskUITest.kt index 035baf3..3000713 100644 --- a/src/test/kotlin/presentation/task/AssignTaskUITest.kt +++ b/src/test/kotlin/presentation/task/AssignTaskUITest.kt @@ -2,12 +2,16 @@ package com.berlin.presentation.task import com.berlin.data.DummyData import com.berlin.domain.exception.InvalidAssigneeException +import com.berlin.domain.exception.InputCancelledException +import com.berlin.domain.exception.InvalidSelectionException import com.berlin.domain.model.Task import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole import com.berlin.domain.usecase.task.AssignTaskUseCase import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import com.berlin.presentation.task.AssignTaskUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -19,7 +23,6 @@ class AssignTaskUITest { private val viewer: Viewer = mockk(relaxed = true) { every { show(capture(printed)) } just Runs } - private val reader: Reader = mockk() private val assignTaskUC: AssignTaskUseCase = mockk() private val getAllTasks: GetAllTasksUseCase = mockk() @@ -29,37 +32,43 @@ class AssignTaskUITest { @BeforeEach fun setUp() { + // reset in-memory data DummyData.tasks.clear() + DummyData.users.clear() + + // populate two users so choose(...) can pick index "1" and "2" + DummyData.users += User("U1", "alice", "pw", UserRole.MATE) + DummyData.users += User("U2", "bob", "pw", UserRole.MATE) newAssignee = DummyData.users[1] + + // create one task assigned initially to alice task = Task( - id = "1", - projectId = "P1", - title = "Demo", - description = null, - stateId = "TODO", - assignedToUserId = DummyData.users[0].id, - createByUserId = DummyData.users[0].id + id = "1", + projectId = "P1", + title = "Demo", + description = null, + stateId = "TODO", + assignedToUserId = DummyData.users[0].id, + createByUserId = DummyData.users[0].id ) DummyData.tasks += task every { getAllTasks.invoke() } returns listOf(task) + printed.clear() } @Test fun `repository update is called with new assignee`() { every { reader.read() } returnsMany listOf("1", "2") every { - assignTaskUC.invoke( - task.id, - newAssignee.id - ) - }.returns(Result.success(task.copy(assignedToUserId = newAssignee.id))) + assignTaskUC.invoke(task.id, newAssignee.id) + } returns Result.success(task.copy(assignedToUserId = newAssignee.id)) AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() verify(exactly = 1) { assignTaskUC.invoke(task.id, newAssignee.id) } - assertThat(printed).contains("Assigned to ${newAssignee.userName}") + assertThat(printed.last()).contains("Assigned to ${newAssignee.userName}") } @Test @@ -86,7 +95,7 @@ class AssignTaskUITest { fun `error from use case is shown to the user`() { every { reader.read() } returnsMany listOf("1", "2") val boom = IllegalStateException("cant assign") - every { assignTaskUC.invoke(task.id, newAssignee.id) }.returns(Result.failure(boom)) + every { assignTaskUC.invoke(task.id, newAssignee.id) } returns Result.failure(boom) AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() @@ -107,7 +116,7 @@ class AssignTaskUITest { @Test fun `throws and shows InvalidAssigneeException`() { every { reader.read() } returnsMany listOf("1", "2") - every { assignTaskUC.invoke(task.id, newAssignee.id) }.throws(InvalidAssigneeException("nope")) + every { assignTaskUC.invoke(task.id, newAssignee.id) } throws InvalidAssigneeException("nope") AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() @@ -118,18 +127,11 @@ class AssignTaskUITest { @Test fun `on failure with null message shows default assignment failed`() { every { reader.read() } returnsMany listOf("1", "2") - - every { - assignTaskUC.invoke( - task.id, - newAssignee.id - ) - } returns Result.failure(RuntimeException("Assignment failed")) + every { assignTaskUC.invoke(task.id, newAssignee.id) } returns Result.failure(RuntimeException("Assignment failed")) AssignTaskUI(assignTaskUC, getAllTasks, viewer, reader).run() verify(exactly = 1) { assignTaskUC.invoke(task.id, newAssignee.id) } assertThat(printed.last()).isEqualTo("Assignment failed") } - } diff --git a/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt b/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt index 1ad1b58..d1fc1a6 100644 --- a/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt +++ b/src/test/kotlin/presentation/task/ChangeTaskStateUITest.kt @@ -1,15 +1,16 @@ -package presentation.task +package com.berlin.presentation.task import com.berlin.data.DummyData import com.berlin.domain.exception.InvalidTaskStateException import com.berlin.domain.exception.TaskNotFoundException import com.berlin.domain.model.State import com.berlin.domain.model.Task +import com.berlin.domain.model.User +import com.berlin.domain.model.UserRole import com.berlin.domain.usecase.task.ChangeTaskStateUseCase import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -import com.berlin.presentation.task.ChangeTaskStateUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -20,65 +21,74 @@ class ChangeTaskStateUITest { private lateinit var viewer: Viewer private lateinit var reader: Reader private lateinit var changeUC: ChangeTaskStateUseCase - private lateinit var ui: ChangeTaskStateUI private lateinit var getAllTasks: GetAllTasksUseCase + private lateinit var ui: ChangeTaskStateUI private val printed = mutableListOf() private lateinit var task: Task - private val alice = DummyData.users[0] + private lateinit var alice: User @BeforeEach fun setUp() { - // reset in-memory lists + // reset all in-memory data + DummyData.users.clear() DummyData.tasks.clear() DummyData.states.clear() + // add one user (needed for assignedBy / filter logic) + alice = User("U1", "alice", "pw", UserRole.MATE) + DummyData.users += alice + + // one task in TODO state S1 task = Task( - id = "T1", - projectId = "P1", - title = "Demo", - description = null, - stateId = "S1", - assignedToUserId = alice.id, - createByUserId = alice.id + id = "T1", + projectId = "P1", + title = "Demo", + description = null, + stateId = "S1", + assignedToUserId = alice.id, + createByUserId = alice.id ) - DummyData.tasks += task + + // two possible states for project P1 DummyData.states += State("S1", "TODO", "P1") DummyData.states += State("S2", "DONE", "P1") - viewer = mockk(relaxed = true) { - every { show(capture(printed)) } just Runs - } - reader = mockk() - changeUC = mockk() + // mocks + viewer = mockk(relaxed = true) { every { show(capture(printed)) } just Runs } + reader = mockk() + changeUC = mockk() getAllTasks = mockk() - ui = ChangeTaskStateUI(changeUC, getAllTasks, viewer, reader) + + // stub before UI instantiation every { getAllTasks.invoke() } returns listOf(task) + + ui = ChangeTaskStateUI(changeUC, getAllTasks, viewer, reader) printed.clear() } @Test fun `success moves to chosen state`() { + // pick task #1, then state #1 (TODO) every { reader.read() } returnsMany listOf("1", "1") every { changeUC.invoke("T1", "S1") } returns Result.success(task.copy(stateId = "S1")) ui.run() - assertThat(printed.last()).contains("Task T1 moved to TODO") verify { changeUC.invoke("T1", "S1") } + assertThat(printed.last()).contains("Task T1 moved to TODO") } @Test fun `no states defined prints message and returns`() { - // clear states so possible.isEmpty() triggers - DummyData.states.clear() - every { reader.read() } returns "1" + DummyData.states.clear() // trigger "no states" path + every { reader.read() } returns "1" // choose task ui.run() - assertThat(printed.last()).contains("No states defined for project P1") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("No states defined for project P1") } @Test @@ -87,8 +97,8 @@ class ChangeTaskStateUITest { ui.run() - assertThat(printed.last()).contains("Cancelled.") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("Cancelled.") } @Test @@ -97,8 +107,8 @@ class ChangeTaskStateUITest { ui.run() - assertThat(printed.last()).contains("Invalid selection") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("Invalid selection") } @Test @@ -107,8 +117,8 @@ class ChangeTaskStateUITest { ui.run() - assertThat(printed.last()).contains("Cancelled.") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("Cancelled.") } @Test @@ -117,8 +127,8 @@ class ChangeTaskStateUITest { ui.run() - assertThat(printed.last()).contains("Invalid selection") verify { changeUC wasNot Called } + assertThat(printed.last()).contains("Invalid selection") } @Test diff --git a/src/test/kotlin/presentation/task/CreateTaskUITest.kt b/src/test/kotlin/presentation/task/CreateTaskUITest.kt index cb8ef92..dca8bd6 100644 --- a/src/test/kotlin/presentation/task/CreateTaskUITest.kt +++ b/src/test/kotlin/presentation/task/CreateTaskUITest.kt @@ -8,6 +8,7 @@ import com.berlin.domain.model.User import com.berlin.domain.usecase.task.CreateTaskUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import data.UserCache import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -21,6 +22,7 @@ class CreateTaskUITest { } private val reader: Reader = mockk() private val createUC: CreateTaskUseCase = mockk() + private val userCache: UserCache = mockk() private val currentUser: User = DummyData.users.first() private lateinit var ui: CreateTaskUI @@ -28,7 +30,9 @@ class CreateTaskUITest { fun reset() { DummyData.tasks.clear() printed.clear() - ui = CreateTaskUI(createUC, currentUser, viewer, reader) + // Stub the cache to return our test user + every { userCache.currentUser } returns currentUser + ui = CreateTaskUI(createUC, userCache, viewer, reader) } @Test @@ -79,9 +83,7 @@ class CreateTaskUITest { fun `failure from use case is printed`() { every { reader.read() } returnsMany listOf("1", "1", "1", "Bug fix", "") every { - createUC.invoke( - any(), any(), any(), any(), any(), any() - ) + createUC.invoke(any(), any(), any(), any(), any(), any()) } returns Result.failure(IllegalStateException("db down")) ui.run() @@ -121,31 +123,9 @@ class CreateTaskUITest { assertThat(printed.last().lowercase()).contains("invalid selection") } - @Test - fun `null description still creates task`() { - every { reader.read() } returnsMany listOf("1", "1", "1", "Doc title", null) - every { createUC.invoke(any(), any(), null, any(), any(), any()) } returns Result.success( - Task( - id = "T99", - projectId = "P1", - title = "Doc title", - description = null, - stateId = "S1", - assignedToUserId = DummyData.users[1].id, - createByUserId = currentUser.id - ) - ) - - ui.run() - - verify { createUC.invoke(any(), any(), null, any(), any(), any()) } - assertThat(printed.last()).contains("Task created: id=T99") - } - @Test fun `shows InvalidTaskTitle when use case throws that exception`() { every { reader.read() } returnsMany listOf("1", "1", "1", "BadTitle", "") - every { createUC.invoke(any(), any(), any(), any(), any(), any()) } throws InvalidTaskTitle("title ruled invalid") @@ -172,7 +152,6 @@ class CreateTaskUITest { @Test fun `use case throwing TaskAlreadyExistsException is caught and shows existed message`() { every { reader.read() } returnsMany listOf("1", "1", "1", "MyTitle", "") - every { createUC.invoke(any(), any(), any(), any(), any(), any()) } throws TaskAlreadyExistsException("already there") @@ -180,8 +159,6 @@ class CreateTaskUITest { ui.run() verify(exactly = 1) { createUC.invoke(any(), any(), any(), any(), any(), any()) } - assertThat(printed.last()).contains("the task already existed") } - } diff --git a/src/test/kotlin/presentation/task/DeleteTaskUITest.kt b/src/test/kotlin/presentation/task/DeleteTaskUITest.kt index 1befe09..58db663 100644 --- a/src/test/kotlin/presentation/task/DeleteTaskUITest.kt +++ b/src/test/kotlin/presentation/task/DeleteTaskUITest.kt @@ -7,6 +7,7 @@ import com.berlin.domain.usecase.task.DeleteTaskUseCase import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer +import com.berlin.presentation.task.DeleteTaskUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -22,7 +23,6 @@ class DeleteTaskUITest { private val deleteUC: DeleteTaskUseCase = mockk() private val getAllTasks: GetAllTasksUseCase = mockk() - private lateinit var task: Task private lateinit var ui: DeleteTaskUI @@ -77,6 +77,7 @@ class DeleteTaskUITest { ui.run() verify(exactly = 0) { deleteUC.invoke(any()) } + assertThat(DummyData.tasks).contains(task) assertThat(printed.last()).contains("Cancelled.") } @@ -109,9 +110,10 @@ class DeleteTaskUITest { ui.run() + // task should not have been removed assertThat(DummyData.tasks).contains(task) + // and we show the invalid-id message assertThat(printed.last()).contains("invalid task id") verify(exactly = 1) { deleteUC.invoke(task.id) } } - } diff --git a/src/test/kotlin/presentation/task/UpdateTaskUITest.kt b/src/test/kotlin/presentation/task/UpdateTaskUITest.kt index 576910a..6b9354a 100644 --- a/src/test/kotlin/presentation/task/UpdateTaskUITest.kt +++ b/src/test/kotlin/presentation/task/UpdateTaskUITest.kt @@ -1,5 +1,4 @@ -package presentation.task - +package com.berlin.presentation.task import com.berlin.data.DummyData import com.berlin.domain.exception.InvalidTaskTitle @@ -11,7 +10,6 @@ import com.berlin.domain.usecase.task.GetAllTasksUseCase import com.berlin.domain.usecase.task.UpdateTaskUseCase import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer -import com.berlin.presentation.task.UpdateTaskUI import com.google.common.truth.Truth.assertThat import io.mockk.* import org.junit.jupiter.api.BeforeEach @@ -23,7 +21,6 @@ class UpdateTaskUITest { private lateinit var reader: Reader private lateinit var updateUC: UpdateTaskUseCase private lateinit var getAllTasks: GetAllTasksUseCase - private lateinit var ui: UpdateTaskUI private val printed = mutableListOf() @@ -70,10 +67,10 @@ class UpdateTaskUITest { @Test fun `success when title only changes`() { every { reader.read() } returnsMany listOf( - "1", - "NewTitle", - "", - "X" + "1", // choose task #1 + "NewTitle", // new title + "", // blank → keep old desc + "X" // cancel assignee → keep old ) every { updateUC.invoke("T1", title = "NewTitle", description = null, assignedToUserId = null) @@ -91,8 +88,8 @@ class UpdateTaskUITest { fun `success when description only changes`() { every { reader.read() } returnsMany listOf( "1", - "", - "NewDesc", + "", // keep title + "NewDesc", // new description "X" ) every { @@ -111,9 +108,9 @@ class UpdateTaskUITest { fun `success when assignee only changes`() { every { reader.read() } returnsMany listOf( "1", - "", - "", - "2" + "", // keep title + "", // keep desc + "2" // choose user #2 ) every { updateUC.invoke("T1", title = null, description = null, assignedToUserId = "U2") @@ -130,7 +127,10 @@ class UpdateTaskUITest { @Test fun `success when nothing changes`() { every { reader.read() } returnsMany listOf( - "1", "", "", "X" + "1", + "", + "", + "X" ) every { updateUC.invoke("T1", title = null, description = null, assignedToUserId = null) @@ -222,5 +222,4 @@ class UpdateTaskUITest { assertThat(printed.last()).contains("Task not founc") } - }