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..07519a5 --- /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..673ce40 --- /dev/null +++ b/src/main/kotlin/logic/usecase/project/CreateProjectUseCase.kt @@ -0,0 +1,31 @@ +package com.berlin.logic.usecase.project + +import com.berlin.logic.generateIdHelper.IdGenerator +import com.berlin.logic.repositories.ProjectRepository +import com.berlin.model.Project + +class CreateProjectUseCase( + private val projectRepository: ProjectRepository, + private val idGenerator: IdGenerator, + ) { + fun createNewProject(projectName: String, description: String?, stateId: List?, taskId: List?): + Result { + if (validateProjectName(projectName)) { + val newProject = Project( + id = idGenerator.generateId(projectName), + name = projectName, + description = description, + 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..736b3f4 --- /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() + ?: 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/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/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.") } } } diff --git a/src/test/kotlin/helper/projectHelper.kt b/src/test/kotlin/helper/projectHelper.kt index 4636dc7..9bbd927 100644 --- a/src/test/kotlin/helper/projectHelper.kt +++ b/src/test/kotlin/helper/projectHelper.kt @@ -1,6 +1,6 @@ package com.berlin.helper -import com.berlin.domain.model.Project +import com.berlin.model.Project fun projectHelper( id: String = "123", 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..ed29dae --- /dev/null +++ b/src/test/kotlin/logic/usecase/project/CreateProjectUseCaseTest.kt @@ -0,0 +1,82 @@ +package logic.usecase.project; + +import com.berlin.helper.projectHelper +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 +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import kotlin.test.Test + +class CreateProjectUseCaseTest { + + private lateinit var createProjectUseCase: CreateProjectUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + + + @BeforeEach + fun setup() { + val idGenerator: IdGenerator = mockk(relaxed = true) + 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("Creation Successfully") + + // When + val result = createProjectUseCase.createNewProject( + validProject.name, + validProject.description, + validProject.statesId, + validProject.tasksId + ) + + // Then + assertThat(result).isEqualTo(Result.success("Creation 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.description, + validProject.statesId, + validProject.tasksId + ) + + // Then + 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 + ) + } + } + +} \ 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..524eff1 --- /dev/null +++ b/src/test/kotlin/logic/usecase/project/DeleteProjectUseCaseTest.kt @@ -0,0 +1,74 @@ +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 org.junit.jupiter.params.provider.ValueSource +import kotlin.test.Test + +class DeleteProjectUseCaseTest { + + private lateinit var deleteProjectUseCase: DeleteProjectUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + + @BeforeEach + fun setup() { + deleteProjectUseCase = DeleteProjectUseCase(projectRepository) + } + + @Test + fun `should return success when project deleted successfully`() { + // Given + every { projectRepository.deleteProject(any()) } returns Result.success("") + + // When + val result = deleteProjectUseCase.deleteProject("project_1") + + // Then + assertThat(result).isEqualTo(Result.success("Deleted Successfully")) + } + + @Test + fun `should return failure when project deletion fails`() { + // Given + every { projectRepository.deleteProject("P1") } returns Result.failure(Exception()) + + // When + val result = deleteProjectUseCase.deleteProject("P1") + + // Then + result.onFailure { exception -> + assertThat(exception.message).isEqualTo("Deletion Failed") + } + } + + @Test + fun `should throw exception when project id does not exists`() { + // Given + every { projectRepository.getProjectById(any()) } returns null + + // When + val result = deleteProjectUseCase.deleteProject("P2") + + // Then + result.onFailure { exception -> + assertThat(exception.message).isEqualTo("Project with ID P2 does not exist") + } + } + + @ParameterizedTest + @ValueSource(strings = ["", " ", "123"]) + fun `should throw exception when project ID is invalid`(projectId: String) { + // When && Then + assertThrows { + deleteProjectUseCase.deleteProject(projectId) + } + } + +} \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt new file mode 100644 index 0000000..ac91456 --- /dev/null +++ b/src/test/kotlin/logic/usecase/project/GetAllProjectsUseCaseTest.kt @@ -0,0 +1,48 @@ +package com.berlin.logic.usecase.project + +import com.berlin.helper.projectHelper +import com.berlin.logic.repositories.ProjectRepository +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 + +class GetAllProjectsUseCaseTest { + + private lateinit var getAllProjectsUseCase: GetAllProjectsUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + + @BeforeEach + fun setup() { + getAllProjectsUseCase = GetAllProjectsUseCase(projectRepository) + } + + @Test + fun `should return list of projects when projects exist`() { + // Given + val expectedProjects = listOf( + projectHelper(), + projectHelper() + ) + every { projectRepository.getAllProjects() } returns expectedProjects + + // When + val result = getAllProjectsUseCase.getAllProjects() + + // Then + assertThat(result).isEqualTo(expectedProjects) + } + + @Test + fun `should throw exception when no projects are found`() { + // Given + every { projectRepository.getAllProjects() } returns null + + // When & Then + val exception = assertThrows { getAllProjectsUseCase.getAllProjects() } + assertThat(exception.message).isEqualTo("No projects found") + } +} diff --git a/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt new file mode 100644 index 0000000..202b0b2 --- /dev/null +++ b/src/test/kotlin/logic/usecase/project/GetProjectByIdUseCaseTest.kt @@ -0,0 +1,60 @@ +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 org.junit.jupiter.params.provider.ValueSource +import kotlin.test.Test + +class GetProjectByIdUseCaseTest { + + private lateinit var getProjectByIdUseCase: GetProjectByIdUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + + @BeforeEach + fun setup() { + getProjectByIdUseCase = GetProjectByIdUseCase(projectRepository) + } + + @Test + fun `should return project when valid project id exists`() { + // Given + val expectedProject = projectHelper() + every { projectRepository.getProjectById("P1") } returns expectedProject + + // When + val result = getProjectByIdUseCase.getProjectById("P1") + + // Then + assertThat(result).isEqualTo(expectedProject) + } + + @Test + fun `should throw exception when project id does not exist`() { + // Given + val input = "P2" + every { projectRepository.getProjectById(any()) } returns null + + // When + val exception = assertThrows { getProjectByIdUseCase.getProjectById("P2") } + + // Then + assertThat(exception.message).isEqualTo("Project with ID $input does not exist") + } + + @ParameterizedTest + @ValueSource(strings = ["", " ", "123"]) + fun `should throw exception when project id is invalid`(projectId: String) { + // When && Then + assertThrows { + getProjectByIdUseCase.getProjectById(projectId) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt b/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt new file mode 100644 index 0000000..23dfe1e --- /dev/null +++ b/src/test/kotlin/logic/usecase/project/UpdateProjectUseCaseTest.kt @@ -0,0 +1,69 @@ +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 org.junit.jupiter.params.provider.ValueSource +import kotlin.test.Test + +class UpdateProjectUseCaseTest { + + private lateinit var updateProjectUseCase: UpdateProjectUseCase + private val projectRepository: ProjectRepository = mockk(relaxed = true) + + @BeforeEach + fun setup() { + updateProjectUseCase = UpdateProjectUseCase(projectRepository) + } + + @Test + fun `should return success when project update succeeds`() { + // Given + val project = projectHelper() + every { projectRepository.updateProject(project) } returns Result.success("Updated Successfully") + + // When + val result = updateProjectUseCase.updateProject(project) + + // Then + assertThat(result).isEqualTo(Result.success("Updated Successfully")) + } + + @Test + fun `should return failure when project update fails`() { + // Given + val project = projectHelper() + every { projectRepository.updateProject(project) } returns Result.failure(Exception()) + + // When + val result = updateProjectUseCase.updateProject(project) + + // Then + result.onFailure { exception -> + assertThat(exception.message).isEqualTo("Update Failed") + } + } + + @ParameterizedTest + @ValueSource(strings = ["", " ", "123"]) + fun `should throw exception when project ID is invalid`( + invalidName: String + ) { + // When && Then + assertThrows { + updateProjectUseCase.updateProject( + projectHelper( + name = invalidName + ) + ) + } + } +} \ No newline at end of file