From c1c01c9edc70876d34a58a6b6242b055e3107ca6 Mon Sep 17 00:00:00 2001 From: Abdulrahman Ragab Date: Wed, 30 Apr 2025 23:33:32 +0300 Subject: [PATCH 1/4] implement project usecases and its unit tests --- .../generateIdHelper/DefaultIdGenerator.kt | 14 +++ .../logic/generateIdHelper/IdGenerator.kt | 11 ++ .../logic/repositories/ProjectRepository.kt | 11 ++ .../usecase/project/CreateProjectUseCase.kt | 30 +++++ .../usecase/project/DeleteProjectUseCase.kt | 29 +++++ .../usecase/project/GetAllProjectsUseCase.kt | 13 +++ .../usecase/project/GetProjectByIdUseCase.kt | 21 ++++ .../usecase/project/UpdateProjectUseCase.kt | 21 ++++ src/test/kotlin/helper/projectHelper.kt | 6 +- .../project/CreateProjectUseCaseTest.kt | 102 +++++++++++++++++ .../project/DeleteProjectUseCaseTest.kt | 104 ++++++++++++++++++ .../project/GetAllProjectsUseCaseTest.kt | 48 ++++++++ .../project/GetProjectByIdUseCaseTest.kt | 78 +++++++++++++ .../project/UpdateProjectUseCaseTest.kt | 92 ++++++++++++++++ 14 files changed, 577 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/logic/generateIdHelper/DefaultIdGenerator.kt create mode 100644 src/main/kotlin/logic/generateIdHelper/IdGenerator.kt create mode 100644 src/main/kotlin/logic/repositories/ProjectRepository.kt create mode 100644 src/main/kotlin/logic/usecase/project/CreateProjectUseCase.kt create mode 100644 src/main/kotlin/logic/usecase/project/DeleteProjectUseCase.kt create mode 100644 src/main/kotlin/logic/usecase/project/GetAllProjectsUseCase.kt create mode 100644 src/main/kotlin/logic/usecase/project/GetProjectByIdUseCase.kt create mode 100644 src/main/kotlin/logic/usecase/project/UpdateProjectUseCase.kt create mode 100644 src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt create mode 100644 src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt create mode 100644 src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt create mode 100644 src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt create mode 100644 src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt diff --git a/src/main/kotlin/logic/generateIdHelper/DefaultIdGenerator.kt b/src/main/kotlin/logic/generateIdHelper/DefaultIdGenerator.kt new file mode 100644 index 0000000..35e3f69 --- /dev/null +++ b/src/main/kotlin/logic/generateIdHelper/DefaultIdGenerator.kt @@ -0,0 +1,14 @@ +package com.berlin.logic.generateIdHelper + +class DefaultIdGenerator : IdGenerator { + override fun generateId( + input: String, padChar: Char, padCharLength: Int): String { + input.trim() + .replace(" ", "") + .ifEmpty { throw IllegalArgumentException("String must not be empty") } + .padEnd(padCharLength, padChar).let { paddedNumber -> + "${paddedNumber}_${System.currentTimeMillis() % 100000}" + } + return input + } +} \ No newline at end of file diff --git a/src/main/kotlin/logic/generateIdHelper/IdGenerator.kt b/src/main/kotlin/logic/generateIdHelper/IdGenerator.kt new file mode 100644 index 0000000..9b736c1 --- /dev/null +++ b/src/main/kotlin/logic/generateIdHelper/IdGenerator.kt @@ -0,0 +1,11 @@ +package com.berlin.logic.generateIdHelper + +import kotlin.random.Random + +interface IdGenerator { + fun generateId( + input: String, + padChar: Char = input[input.length / 2], + padCharLength: Int = input.length + Random.nextInt(1, 5) + ): String +} \ No newline at end of file diff --git a/src/main/kotlin/logic/repositories/ProjectRepository.kt b/src/main/kotlin/logic/repositories/ProjectRepository.kt new file mode 100644 index 0000000..ad99f4e --- /dev/null +++ b/src/main/kotlin/logic/repositories/ProjectRepository.kt @@ -0,0 +1,11 @@ +package com.berlin.logic.repositories + +import com.berlin.model.Project + +interface ProjectRepository { + fun createProject(project:Project): Result + fun getProjectById(projectId:String):Project? + fun getAllProjects():List + fun updateProject(project: Project):Result + fun deleteProject(projectId: String):Result +} \ No newline at end of file diff --git a/src/main/kotlin/logic/usecase/project/CreateProjectUseCase.kt b/src/main/kotlin/logic/usecase/project/CreateProjectUseCase.kt new file mode 100644 index 0000000..2b2b17d --- /dev/null +++ b/src/main/kotlin/logic/usecase/project/CreateProjectUseCase.kt @@ -0,0 +1,30 @@ +package com.berlin.logic.usecase.project + +import com.berlin.logic.generateIdHelper.DefaultIdGenerator +import com.berlin.logic.repositories.ProjectRepository +import com.berlin.model.Project + +class CreateProjectUseCase( + private val projectRepository: ProjectRepository, + private val defaultIdGenerator: DefaultIdGenerator, + ) { + fun createNewProject(projectName: String, stateId: List, taskId: List): Result { + if (validateProjectName(projectName)) { + val newProject = Project( + id = defaultIdGenerator.generateId(projectName), + name = projectName, + description = null, + statesId = stateId, + tasksId = taskId + ) + return projectRepository.createProject(newProject) + .map { "Creation Successfully" } + .recover { "Creation Failed" } + } else { + throw Exception("Project Name must not be empty or blank") + } + } + + private fun validateProjectName(projectName: String): Boolean = + projectName.isNotBlank() && !(projectName.all { it.isDigit() }) +} \ No newline at end of file diff --git a/src/main/kotlin/logic/usecase/project/DeleteProjectUseCase.kt b/src/main/kotlin/logic/usecase/project/DeleteProjectUseCase.kt new file mode 100644 index 0000000..ca87e77 --- /dev/null +++ b/src/main/kotlin/logic/usecase/project/DeleteProjectUseCase.kt @@ -0,0 +1,29 @@ +package com.berlin.logic.usecase.project + +import com.berlin.logic.repositories.ProjectRepository + +class DeleteProjectUseCase ( + private val projectRepository: ProjectRepository +) { + fun deleteProject(projectId: String): Result { + + if(!validateProjectId(projectId)) + throw Exception("Project ID must not be empty or blank") + + if (!checkProjectExists(projectId)) { + return Result.failure( + Exception("Project with ID $projectId does not exist") + ) + } + + return projectRepository.deleteProject(projectId) + .map { "Deleted Successfully" } + .recover { "Deletion Failed" } + } + + private fun validateProjectId(projectId: String): Boolean = + projectId.isNotBlank() && !(projectId.all { it.isDigit() }) + + private fun checkProjectExists(projectId: String): Boolean = + projectRepository.getProjectById(projectId) != null +} \ No newline at end of file diff --git a/src/main/kotlin/logic/usecase/project/GetAllProjectsUseCase.kt b/src/main/kotlin/logic/usecase/project/GetAllProjectsUseCase.kt new file mode 100644 index 0000000..0c75e8f --- /dev/null +++ b/src/main/kotlin/logic/usecase/project/GetAllProjectsUseCase.kt @@ -0,0 +1,13 @@ +package com.berlin.logic.usecase.project + +import com.berlin.logic.repositories.ProjectRepository +import com.berlin.model.Project + +class GetAllProjectsUseCase( + private val projectRepository: ProjectRepository +) { + fun getAllProjects(): List? { + return projectRepository.getAllProjects().takeIf { it.isNotEmpty() } + ?: throw Exception("No projects found") + } +} \ No newline at end of file diff --git a/src/main/kotlin/logic/usecase/project/GetProjectByIdUseCase.kt b/src/main/kotlin/logic/usecase/project/GetProjectByIdUseCase.kt new file mode 100644 index 0000000..b4774d8 --- /dev/null +++ b/src/main/kotlin/logic/usecase/project/GetProjectByIdUseCase.kt @@ -0,0 +1,21 @@ +package com.berlin.logic.usecase.project + +import com.berlin.logic.repositories.ProjectRepository +import com.berlin.model.Project + +class GetProjectByIdUseCase ( + private val projectRepository: ProjectRepository +) { + + fun getProjectById(projectId: String): Project { + if(!validateProjectId(projectId)) + throw Exception("Project ID must not be empty or blank") + + return projectRepository.getProjectById(projectId) + ?: throw Exception("Project with ID $projectId does not exist") + } + + private fun validateProjectId(projectId: String): Boolean = + projectId.isNotBlank() && !(projectId.all { it.isDigit() }) + +} \ No newline at end of file diff --git a/src/main/kotlin/logic/usecase/project/UpdateProjectUseCase.kt b/src/main/kotlin/logic/usecase/project/UpdateProjectUseCase.kt new file mode 100644 index 0000000..de4037f --- /dev/null +++ b/src/main/kotlin/logic/usecase/project/UpdateProjectUseCase.kt @@ -0,0 +1,21 @@ +package com.berlin.logic.usecase.project + +import com.berlin.logic.repositories.ProjectRepository +import com.berlin.model.Project + +class UpdateProjectUseCase ( + private val projectRepository: ProjectRepository +) { + fun updateProject(project: Project): Result { + if(!validateProjectName(project.name)) + throw Exception("Project Name must not be empty or blank") + + return projectRepository.updateProject(project) + .map { "Updated Successfully" } + .recover { "Update Failed" } + } + + + private fun validateProjectName(projectName: String): Boolean = + projectName.isNotBlank() && !(projectName.all { it.isDigit() }) +} \ No newline at end of file diff --git a/src/test/kotlin/helper/projectHelper.kt b/src/test/kotlin/helper/projectHelper.kt index 1abf420..35512c8 100644 --- a/src/test/kotlin/helper/projectHelper.kt +++ b/src/test/kotlin/helper/projectHelper.kt @@ -1,13 +1,13 @@ package com.berlin.helper -import com.berlin.domain.model.Project +import com.berlin.model.Project fun projectHelper( id: String = "123", name: String = "TODO", description:String? = null, - statesId:List? = listOf("1","2"), - tasksId:List? = listOf("3","4") + statesId:List = listOf("1","2"), + tasksId:List = listOf("3","4") ): Project { return Project( id = id, diff --git a/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt new file mode 100644 index 0000000..d138bf5 --- /dev/null +++ b/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt @@ -0,0 +1,102 @@ +package logic.usecase.project; + +import com.berlin.helper.projectHelper +import com.berlin.logic.generateIdHelper.DefaultIdGenerator +import com.berlin.logic.repositories.ProjectRepository +import com.berlin.logic.usecase.project.CreateProjectUseCase +import com.google.common.truth.Truth.assertThat +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import kotlin.test.Test + +class CreateProjectUseCaseTest { + + private lateinit var createProjectUseCase: CreateProjectUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + + + @BeforeEach + fun setup() { + val idGenerator: DefaultIdGenerator = mockk() + createProjectUseCase = CreateProjectUseCase(projectRepository, idGenerator) + } + + @Test + fun `createNewProject should return success when project created successfully`() { + // Given + val validProject = projectHelper() + every { projectRepository.createProject(any()) } returns Result.success("") + + // When + val result = createProjectUseCase.createNewProject( + validProject.name, + validProject.statesId, + validProject.tasksId + ) + + // Then + assertThat(result).isEqualTo( + Result.success("Project created successfully") + ) + } + + @Test + fun `createNewProject should return failure when project creation fails`() { + // Given + val validProject = projectHelper() + every { projectRepository.createProject(any()) } returns Result.failure(Exception()) + + // When + val result = createProjectUseCase.createNewProject( + validProject.name, + validProject.statesId, + validProject.tasksId + ) + + // Then + assertThat(result).isEqualTo(Exception("Project creation failed")) + } + + @ParameterizedTest + @CsvSource( + "", + " ", + "123" + ) + fun `validateProjectName should throw exception when project name is invalid`( + invalidName: String + ) { + // Given + val projectInput = projectHelper(name = invalidName) + + // When && Then + assertThrows { + createProjectUseCase.createNewProject( + projectInput.name, + projectInput.statesId, + projectInput.tasksId + ) + } + } + + @Test + fun `validateProjectName should return true when project name is valid`() { + // Given + val projectInput = projectHelper() + + // When + val result = createProjectUseCase.createNewProject( + projectInput.name, + projectInput.statesId, + projectInput.tasksId + ) + + // Then + assertThat(result).isEqualTo(true) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt new file mode 100644 index 0000000..43251ca --- /dev/null +++ b/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt @@ -0,0 +1,104 @@ +package logic.usecase.project; + +import com.berlin.logic.repositories.ProjectRepository +import com.berlin.logic.usecase.project.DeleteProjectUseCase +import com.google.common.truth.Truth.assertThat +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import kotlin.test.Test + +class DeleteProjectUseCaseTest { + + private lateinit var deleteProjectUseCase: DeleteProjectUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + + @BeforeEach + fun setup() { + deleteProjectUseCase = DeleteProjectUseCase(projectRepository) + } + + @Test + fun `should return success when project deleted successfully`() { + // Given + every { projectRepository.deleteProject(any()) } returns Result.success("") + + // When + val result = deleteProjectUseCase.deleteProject("project_1") + + // Then + assertThat(result).isEqualTo("Deleted Successfully") + } + + @Test + fun `should return failure when project deletion fails`() { + // Given + every { projectRepository.deleteProject(any()) } returns Result.failure(Exception()) + + // When + val result = deleteProjectUseCase.deleteProject("1") + + // Then + assertThat(result).isEqualTo(Exception("Deletion Failed")) + } + + @Test + fun `should return null value when project does not exist`() { + // Given + val input = "project_1" + every { projectRepository.getProjectById(any()) } returns null + + // When + val exception = assertThrows { deleteProjectUseCase.deleteProject("P2") } + + // Then + assertThat(exception.message).isEqualTo( + "Project with ID $input does not exist" + ) + } + + @Test + fun `should return true when project exists`() { + // Given + every { projectRepository.getProjectById(any()) } returns mockk() + + // When + val result = deleteProjectUseCase.deleteProject("P2") + + // Then + assertThat(result).isEqualTo(true) + } + + + @ParameterizedTest + @CsvSource( + "", + " ", + "123" + ) + fun `should throw exception when project ID is invalid`(projectId: String) { + // Given + val input = projectId + + // When && Then + assertThrows { + deleteProjectUseCase.deleteProject(input) + } + } + + @Test + fun `should return true when project id is valid`() { + // Given + val projectInput = "project_1" + + // When + val result = deleteProjectUseCase.deleteProject(projectInput) + + // Then + assertThat(result).isEqualTo(true) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt new file mode 100644 index 0000000..754ffb1 --- /dev/null +++ b/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt @@ -0,0 +1,48 @@ +package logic.usecase.project; + +import com.berlin.helper.projectHelper +import com.berlin.logic.repositories.ProjectRepository +import com.berlin.logic.usecase.project.GetAllProjectsUseCase; +import com.google.common.truth.Truth.assertThat +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.assertThrows +import kotlin.test.Test + +class GetAllProjectsUseCaseTest { + + private lateinit var getAllProjectsUseCase: GetAllProjectsUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + + @BeforeEach + fun setup() { + getAllProjectsUseCase = GetAllProjectsUseCase(projectRepository) + } + + @Test + fun `should return list of projects when valid projects exists`() { + // Given + val expectedProjectsList = listOf(projectHelper(),projectHelper(),projectHelper()) + every { projectRepository.getAllProjects() } returns expectedProjectsList + + // When + val result = getAllProjectsUseCase.getAllProjects() + + // Then + assertThat(result).isEqualTo(expectedProjectsList) + } + + @Test + fun `should throw exception when there is no project exist`() { + // Given + every { projectRepository.getAllProjects() } returns emptyList() + + // When + val exception = assertThrows { getAllProjectsUseCase.getAllProjects() } + + // Then + assertThat(exception.message).isEqualTo("No projects found") + } + +} \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt new file mode 100644 index 0000000..3e9076e --- /dev/null +++ b/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt @@ -0,0 +1,78 @@ +package logic.usecase.project; + +import com.berlin.helper.projectHelper +import com.berlin.logic.repositories.ProjectRepository +import com.berlin.logic.usecase.project.GetProjectByIdUseCase +import com.google.common.truth.Truth.assertThat +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import kotlin.test.Test + +class GetProjectByIdUseCaseTest { + + private lateinit var getProjectByIdUseCase: GetProjectByIdUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + + @BeforeEach + fun setup() { + getProjectByIdUseCase = GetProjectByIdUseCase(projectRepository) + } + + @Test + fun `should return project when valid project id exists`() { + // Given + val expectedProject = projectHelper() + every { projectRepository.getProjectById("P1") } returns expectedProject + + // When + val result = getProjectByIdUseCase.getProjectById("P1") + + // Then + assertThat(result).isEqualTo(expectedProject) + } + + @Test + fun `should throw exception when project id does not exist`() { + // Given + val input = "P2" + every { projectRepository.getProjectById(any()) } returns null + + // When + val exception = assertThrows { getProjectByIdUseCase.getProjectById("P2") } + + // Then + assertThat(exception.message).isEqualTo("Project with ID $input does not exist") + } + + @ParameterizedTest + @CsvSource( + "", + " ", + "123" + ) + fun `should throw exception when project id is invalid`(projectId: String) { + // Given + val input = projectId + + // When && Then + assertThrows { + getProjectByIdUseCase.getProjectById(input) + } + } + + @Test + fun `should return true when project id is valid`() { + // Given + val projectInput = "project_1" + + // When + val result = getProjectByIdUseCase.getProjectById(projectInput) + + // Then + assertThat(result).isEqualTo(true) + } +} \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt new file mode 100644 index 0000000..eadad7a --- /dev/null +++ b/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt @@ -0,0 +1,92 @@ +package logic.usecase.project; + +import com.berlin.helper.projectHelper +import com.berlin.logic.repositories.ProjectRepository +import com.berlin.logic.usecase.project.UpdateProjectUseCase +import com.berlin.model.Project +import com.google.common.truth.Truth.assertThat +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import kotlin.test.Test + +class UpdateProjectUseCaseTest { + + private lateinit var updateProjectUseCase: UpdateProjectUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + + @BeforeEach + fun setup() { + updateProjectUseCase = UpdateProjectUseCase(projectRepository) + } + + @Test + fun `should return success when project update succeeds`() { + // Given + val project = projectHelper() + every { projectRepository.updateProject(project) } returns Result.success("Updated Successfully") + + // When + val result = updateProjectUseCase.updateProject(project) + + // Then + assertThat(result).isEqualTo(Result.success("Updated Successfully")) + } + + @Test + fun `should return failure when project update fails`() { + // Given + val project = projectHelper() + every { projectRepository.updateProject(project) } returns Result.failure(Exception()) + + // When + val result = updateProjectUseCase.updateProject(project) + + // Then + assertThat(result).isEqualTo(Exception("Update Failed")) + } + + @ParameterizedTest + @CsvSource( + ", name, null, [s1,s2,s3] , [t1,t2,t3]", + "17, name, null, [s1,s2,s3] , [t1,t2,t3]", + "-, name, null, [s1,s2,s3] , [t1,t2,t3]" + ) + fun `should throw exception when project ID is invalid`( + id: String, + projectName: String, + description: String, + statesId: List, + tasksId: List + ) { + // Given + val input = Project( + id, + projectName, + description, + statesId, + tasksId + ) + + // When && Then + assertThrows { + updateProjectUseCase.updateProject(input) + } + } + + @Test + fun `should return true when project name is valid`() { + // Given + val projectInput = projectHelper() + + // When + val result = updateProjectUseCase.updateProject(projectInput) + + // Then + assertThat(result).isEqualTo(true) + } + +} \ No newline at end of file From 262dee9493e309eca79f6ae2c1689b037001233a Mon Sep 17 00:00:00 2001 From: Ahmed Hosny Date: Thu, 1 May 2025 07:15:17 +0300 Subject: [PATCH 2/4] update usecase and unit test --- .../logic/repositories/ProjectRepository.kt | 8 +-- .../usecase/project/CreateProjectUseCase.kt | 6 +- .../usecase/project/GetAllProjectsUseCase.kt | 4 +- src/main/kotlin/model/Project.kt | 9 +++ src/test/kotlin/helper/projectHelper.kt | 4 +- .../project/CreateProjectUseCaseTest.kt | 51 +++++----------- .../project/DeleteProjectUseCaseTest.kt | 58 +++++-------------- .../project/GetAllProjectsUseCaseTest.kt | 28 ++++----- .../project/GetProjectByIdUseCaseTest.kt | 26 ++------- .../project/UpdateProjectUseCaseTest.kt | 47 ++++----------- 10 files changed, 81 insertions(+), 160 deletions(-) create mode 100644 src/main/kotlin/model/Project.kt diff --git a/src/main/kotlin/logic/repositories/ProjectRepository.kt b/src/main/kotlin/logic/repositories/ProjectRepository.kt index ad99f4e..07519a5 100644 --- a/src/main/kotlin/logic/repositories/ProjectRepository.kt +++ b/src/main/kotlin/logic/repositories/ProjectRepository.kt @@ -4,8 +4,8 @@ import com.berlin.model.Project interface ProjectRepository { fun createProject(project:Project): Result - fun getProjectById(projectId:String):Project? - fun getAllProjects():List - fun updateProject(project: Project):Result - fun deleteProject(projectId: String):Result + fun getProjectById(projectId:String): Project? + fun getAllProjects(): List? + fun updateProject(project: Project): Result + fun deleteProject(projectId: String): Result } \ No newline at end of file diff --git a/src/main/kotlin/logic/usecase/project/CreateProjectUseCase.kt b/src/main/kotlin/logic/usecase/project/CreateProjectUseCase.kt index 2b2b17d..2c7fdf4 100644 --- a/src/main/kotlin/logic/usecase/project/CreateProjectUseCase.kt +++ b/src/main/kotlin/logic/usecase/project/CreateProjectUseCase.kt @@ -3,17 +3,19 @@ package com.berlin.logic.usecase.project import com.berlin.logic.generateIdHelper.DefaultIdGenerator import com.berlin.logic.repositories.ProjectRepository import com.berlin.model.Project +import jdk.jfr.Description class CreateProjectUseCase( private val projectRepository: ProjectRepository, private val defaultIdGenerator: DefaultIdGenerator, ) { - fun createNewProject(projectName: String, stateId: List, taskId: List): Result { + fun createNewProject(projectName: String, description: String?, stateId: List?, taskId: List?): + Result { if (validateProjectName(projectName)) { val newProject = Project( id = defaultIdGenerator.generateId(projectName), name = projectName, - description = null, + description = description, statesId = stateId, tasksId = taskId ) diff --git a/src/main/kotlin/logic/usecase/project/GetAllProjectsUseCase.kt b/src/main/kotlin/logic/usecase/project/GetAllProjectsUseCase.kt index 0c75e8f..736b3f4 100644 --- a/src/main/kotlin/logic/usecase/project/GetAllProjectsUseCase.kt +++ b/src/main/kotlin/logic/usecase/project/GetAllProjectsUseCase.kt @@ -6,8 +6,8 @@ import com.berlin.model.Project class GetAllProjectsUseCase( private val projectRepository: ProjectRepository ) { - fun getAllProjects(): List? { - return projectRepository.getAllProjects().takeIf { it.isNotEmpty() } + fun getAllProjects(): List { + return projectRepository.getAllProjects() ?: throw Exception("No projects found") } } \ No newline at end of file diff --git a/src/main/kotlin/model/Project.kt b/src/main/kotlin/model/Project.kt new file mode 100644 index 0000000..e459a0c --- /dev/null +++ b/src/main/kotlin/model/Project.kt @@ -0,0 +1,9 @@ +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/test/kotlin/helper/projectHelper.kt b/src/test/kotlin/helper/projectHelper.kt index 35512c8..92fa58d 100644 --- a/src/test/kotlin/helper/projectHelper.kt +++ b/src/test/kotlin/helper/projectHelper.kt @@ -6,8 +6,8 @@ fun projectHelper( id: String = "123", name: String = "TODO", description:String? = null, - statesId:List = listOf("1","2"), - tasksId:List = listOf("3","4") + statesId:List? = listOf("1","2"), + tasksId:List? = listOf("3","4") ): Project { return Project( id = id, diff --git a/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt index d138bf5..fdbac20 100644 --- a/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt @@ -11,6 +11,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource +import org.junit.jupiter.params.provider.ValueSource import kotlin.test.Test class CreateProjectUseCaseTest { @@ -21,7 +22,7 @@ class CreateProjectUseCaseTest { @BeforeEach fun setup() { - val idGenerator: DefaultIdGenerator = mockk() + val idGenerator: DefaultIdGenerator = mockk(relaxed = true) createProjectUseCase = CreateProjectUseCase(projectRepository, idGenerator) } @@ -29,19 +30,18 @@ class CreateProjectUseCaseTest { fun `createNewProject should return success when project created successfully`() { // Given val validProject = projectHelper() - every { projectRepository.createProject(any()) } returns Result.success("") + every { projectRepository.createProject(any()) } returns Result.success("Creation Successfully") // When val result = createProjectUseCase.createNewProject( validProject.name, + validProject.description, validProject.statesId, validProject.tasksId ) // Then - assertThat(result).isEqualTo( - Result.success("Project created successfully") - ) + assertThat(result).isEqualTo(Result.success("Creation Successfully")) } @Test @@ -53,50 +53,31 @@ class CreateProjectUseCaseTest { // When val result = createProjectUseCase.createNewProject( validProject.name, + validProject.description, validProject.statesId, validProject.tasksId ) // Then - assertThat(result).isEqualTo(Exception("Project creation failed")) + result.onFailure { exception -> + assertThat(exception.message).isEqualTo("Creation Failed") + } } + @ParameterizedTest - @CsvSource( - "", - " ", - "123" - ) + @ValueSource(strings = ["", " ", "123"]) fun `validateProjectName should throw exception when project name is invalid`( invalidName: String ) { - // Given - val projectInput = projectHelper(name = invalidName) - // When && Then - assertThrows { - createProjectUseCase.createNewProject( - projectInput.name, - projectInput.statesId, - projectInput.tasksId + assertThrows { + createProjectUseCase.createNewProject(invalidName, + null, + null, + null ) } } - @Test - fun `validateProjectName should return true when project name is valid`() { - // Given - val projectInput = projectHelper() - - // When - val result = createProjectUseCase.createNewProject( - projectInput.name, - projectInput.statesId, - projectInput.tasksId - ) - - // Then - assertThat(result).isEqualTo(true) - } - } \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt index 43251ca..524eff1 100644 --- a/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt @@ -9,6 +9,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource +import org.junit.jupiter.params.provider.ValueSource import kotlin.test.Test class DeleteProjectUseCaseTest { @@ -30,75 +31,44 @@ class DeleteProjectUseCaseTest { val result = deleteProjectUseCase.deleteProject("project_1") // Then - assertThat(result).isEqualTo("Deleted Successfully") + assertThat(result).isEqualTo(Result.success("Deleted Successfully")) } @Test fun `should return failure when project deletion fails`() { // Given - every { projectRepository.deleteProject(any()) } returns Result.failure(Exception()) + every { projectRepository.deleteProject("P1") } returns Result.failure(Exception()) // When - val result = deleteProjectUseCase.deleteProject("1") + val result = deleteProjectUseCase.deleteProject("P1") // Then - assertThat(result).isEqualTo(Exception("Deletion Failed")) + result.onFailure { exception -> + assertThat(exception.message).isEqualTo("Deletion Failed") + } } @Test - fun `should return null value when project does not exist`() { + fun `should throw exception when project id does not exists`() { // Given - val input = "project_1" every { projectRepository.getProjectById(any()) } returns null - // When - val exception = assertThrows { deleteProjectUseCase.deleteProject("P2") } - - // Then - assertThat(exception.message).isEqualTo( - "Project with ID $input does not exist" - ) - } - - @Test - fun `should return true when project exists`() { - // Given - every { projectRepository.getProjectById(any()) } returns mockk() - // When val result = deleteProjectUseCase.deleteProject("P2") // Then - assertThat(result).isEqualTo(true) + result.onFailure { exception -> + assertThat(exception.message).isEqualTo("Project with ID P2 does not exist") + } } - @ParameterizedTest - @CsvSource( - "", - " ", - "123" - ) + @ValueSource(strings = ["", " ", "123"]) fun `should throw exception when project ID is invalid`(projectId: String) { - // Given - val input = projectId - // When && Then - assertThrows { - deleteProjectUseCase.deleteProject(input) + assertThrows { + deleteProjectUseCase.deleteProject(projectId) } } - @Test - fun `should return true when project id is valid`() { - // Given - val projectInput = "project_1" - - // When - val result = deleteProjectUseCase.deleteProject(projectInput) - - // Then - assertThat(result).isEqualTo(true) - } - } \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt index 754ffb1..ac91456 100644 --- a/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt @@ -1,14 +1,14 @@ -package logic.usecase.project; +package com.berlin.logic.usecase.project import com.berlin.helper.projectHelper import com.berlin.logic.repositories.ProjectRepository -import com.berlin.logic.usecase.project.GetAllProjectsUseCase; +import com.berlin.model.Project import com.google.common.truth.Truth.assertThat import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import kotlin.test.Test class GetAllProjectsUseCaseTest { @@ -21,28 +21,28 @@ class GetAllProjectsUseCaseTest { } @Test - fun `should return list of projects when valid projects exists`() { + fun `should return list of projects when projects exist`() { // Given - val expectedProjectsList = listOf(projectHelper(),projectHelper(),projectHelper()) - every { projectRepository.getAllProjects() } returns expectedProjectsList + val expectedProjects = listOf( + projectHelper(), + projectHelper() + ) + every { projectRepository.getAllProjects() } returns expectedProjects // When val result = getAllProjectsUseCase.getAllProjects() // Then - assertThat(result).isEqualTo(expectedProjectsList) + assertThat(result).isEqualTo(expectedProjects) } @Test - fun `should throw exception when there is no project exist`() { + fun `should throw exception when no projects are found`() { // Given - every { projectRepository.getAllProjects() } returns emptyList() + every { projectRepository.getAllProjects() } returns null - // When + // When & Then val exception = assertThrows { getAllProjectsUseCase.getAllProjects() } - - // Then assertThat(exception.message).isEqualTo("No projects found") } - -} \ No newline at end of file +} diff --git a/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt index 3e9076e..202b0b2 100644 --- a/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt @@ -10,6 +10,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource +import org.junit.jupiter.params.provider.ValueSource import kotlin.test.Test class GetProjectByIdUseCaseTest { @@ -49,30 +50,11 @@ class GetProjectByIdUseCaseTest { } @ParameterizedTest - @CsvSource( - "", - " ", - "123" - ) + @ValueSource(strings = ["", " ", "123"]) fun `should throw exception when project id is invalid`(projectId: String) { - // Given - val input = projectId - // When && Then - assertThrows { - getProjectByIdUseCase.getProjectById(input) + assertThrows { + getProjectByIdUseCase.getProjectById(projectId) } } - - @Test - fun `should return true when project id is valid`() { - // Given - val projectInput = "project_1" - - // When - val result = getProjectByIdUseCase.getProjectById(projectInput) - - // Then - assertThat(result).isEqualTo(true) - } } \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt index eadad7a..23dfe1e 100644 --- a/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt @@ -11,6 +11,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource +import org.junit.jupiter.params.provider.ValueSource import kotlin.test.Test class UpdateProjectUseCaseTest { @@ -46,47 +47,23 @@ class UpdateProjectUseCaseTest { val result = updateProjectUseCase.updateProject(project) // Then - assertThat(result).isEqualTo(Exception("Update Failed")) + result.onFailure { exception -> + assertThat(exception.message).isEqualTo("Update Failed") + } } @ParameterizedTest - @CsvSource( - ", name, null, [s1,s2,s3] , [t1,t2,t3]", - "17, name, null, [s1,s2,s3] , [t1,t2,t3]", - "-, name, null, [s1,s2,s3] , [t1,t2,t3]" - ) + @ValueSource(strings = ["", " ", "123"]) fun `should throw exception when project ID is invalid`( - id: String, - projectName: String, - description: String, - statesId: List, - tasksId: List + invalidName: String ) { - // Given - val input = Project( - id, - projectName, - description, - statesId, - tasksId - ) - // When && Then - assertThrows { - updateProjectUseCase.updateProject(input) + assertThrows { + updateProjectUseCase.updateProject( + projectHelper( + name = invalidName + ) + ) } } - - @Test - fun `should return true when project name is valid`() { - // Given - val projectInput = projectHelper() - - // When - val result = updateProjectUseCase.updateProject(projectInput) - - // Then - assertThat(result).isEqualTo(true) - } - } \ No newline at end of file From 1a80421cb92a743a60471b2570089b919dbe7495 Mon Sep 17 00:00:00 2001 From: Ahmed Hosny Date: Thu, 1 May 2025 11:52:46 +0300 Subject: [PATCH 3/4] change naming of id generator class, and these parameters solve a problem in creation state (change depend class on implementation to abstraction) --- .../kotlin/logic/usecase/project/CreateProjectUseCase.kt | 7 +++---- .../logic/usecase/project/CreateProjectUseCaseTest.kt | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/logic/usecase/project/CreateProjectUseCase.kt b/src/main/kotlin/logic/usecase/project/CreateProjectUseCase.kt index 2c7fdf4..673ce40 100644 --- a/src/main/kotlin/logic/usecase/project/CreateProjectUseCase.kt +++ b/src/main/kotlin/logic/usecase/project/CreateProjectUseCase.kt @@ -1,19 +1,18 @@ package com.berlin.logic.usecase.project -import com.berlin.logic.generateIdHelper.DefaultIdGenerator +import com.berlin.logic.generateIdHelper.IdGenerator import com.berlin.logic.repositories.ProjectRepository import com.berlin.model.Project -import jdk.jfr.Description class CreateProjectUseCase( private val projectRepository: ProjectRepository, - private val defaultIdGenerator: DefaultIdGenerator, + private val idGenerator: IdGenerator, ) { fun createNewProject(projectName: String, description: String?, stateId: List?, taskId: List?): Result { if (validateProjectName(projectName)) { val newProject = Project( - id = defaultIdGenerator.generateId(projectName), + id = idGenerator.generateId(projectName), name = projectName, description = description, statesId = stateId, diff --git a/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt index fdbac20..ed29dae 100644 --- a/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt +++ b/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt @@ -1,7 +1,7 @@ package logic.usecase.project; import com.berlin.helper.projectHelper -import com.berlin.logic.generateIdHelper.DefaultIdGenerator +import com.berlin.logic.generateIdHelper.IdGenerator import com.berlin.logic.repositories.ProjectRepository import com.berlin.logic.usecase.project.CreateProjectUseCase import com.google.common.truth.Truth.assertThat @@ -10,7 +10,6 @@ import io.mockk.mockk import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.CsvSource import org.junit.jupiter.params.provider.ValueSource import kotlin.test.Test @@ -22,7 +21,7 @@ class CreateProjectUseCaseTest { @BeforeEach fun setup() { - val idGenerator: DefaultIdGenerator = mockk(relaxed = true) + val idGenerator: IdGenerator = mockk(relaxed = true) createProjectUseCase = CreateProjectUseCase(projectRepository, idGenerator) } From 645243420ba24265c675dfa63ac8a37bf4d30278 Mon Sep 17 00:00:00 2001 From: Ahmed Hosny Date: Sun, 18 May 2025 15:05:38 +0300 Subject: [PATCH 4/4] refactor userCache currentPermission as lazy --- src/main/kotlin/presentation/CategoryUI.kt | 24 +++++++++------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/presentation/CategoryUI.kt b/src/main/kotlin/presentation/CategoryUI.kt index 6d588de..a2384c0 100644 --- a/src/main/kotlin/presentation/CategoryUI.kt +++ b/src/main/kotlin/presentation/CategoryUI.kt @@ -3,7 +3,6 @@ package com.berlin.presentation import com.berlin.presentation.io.Reader import com.berlin.presentation.io.Viewer import data.UserCache -import java.util.* class CategoryUI( override val id: Int, @@ -12,30 +11,27 @@ class CategoryUI( private val viewer: Viewer, private val reader: Reader, private val userCache: UserCache +) : UiRunner { -): UiRunner { + private val allowedActions: List by lazy { + children.filter { it.isAllowed(userCache.currentPermission) } + } override fun run() { - val permission = userCache.currentPermission - val allowed = children.filter { it.isAllowed(permission) } - - if (allowed.isEmpty()) { + if (allowedActions.isEmpty()) { viewer.show("No available actions in $label.") return } while (true) { viewer.show("=== $label ===") - allowed.sortedBy { it.id } - .forEach { viewer.show("${it.id} – ${it.label}") } + allowedActions.sortedBy { it.id }.forEach { viewer.show("${it.id} – ${it.label}") } viewer.show("X – Back") - when (val input = reader.read()?.trim()?.uppercase(Locale.getDefault())) { - null,"X" -> return - else -> allowed - .firstOrNull { it.id == input.toIntOrNull() } - ?.run() - ?: viewer.show("Invalid choice") + when (val input = reader.read()?.trim()?.uppercase()) { + null, "X" -> return + else -> allowedActions.firstOrNull { it.id == input.toIntOrNull() } + ?.run() ?: viewer.show("Invalid option: $input. Please select a valid action.") } } }