diff --git a/src/test/kotlin/presentation/auth/LoginUserUITest.kt b/src/test/kotlin/presentation/auth/LoginUserUITest.kt new file mode 100644 index 0000000..77211c7 --- /dev/null +++ b/src/test/kotlin/presentation/auth/LoginUserUITest.kt @@ -0,0 +1,142 @@ +package presentation.auth + +import domain.usecases.auth.LoginUseCase +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.coVerifySequence +import io.mockk.just +import io.mockk.mockk +import io.mockk.runs +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO + +class LoginUserUITest { + private lateinit var loginUseCase: LoginUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var loginUserUI: LoginUserUI + + @BeforeEach + fun setUp() { + loginUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + loginUserUI = LoginUserUI(loginUseCase, consoleIO) + } + + @Test + fun `should login successfully on first attempt`() = runTest { + // Given + val username = "testUser" + val password = "testPassword" + + coEvery { consoleIO.read() } returnsMany listOf(username, password) + coEvery { loginUseCase(username, password) } returns mockk() + coEvery { consoleIO.write(any()) } just runs + + // When + loginUserUI() + + // Then + coVerifySequence { + consoleIO.write("šŸ” Login") + consoleIO.write("Enter username: ") + consoleIO.read() + consoleIO.write("Enter password: šŸ”‘") + consoleIO.read() + loginUseCase(username, password) + consoleIO.write("āœ… Logged in successfully! Welcome back, $username.") + } + } + + @Test + fun `should retry login after failure and then succeed`() = runTest { + // Given + val failedUsername = "invalidUser" + val failedPassword = "invalidPass" + val successUsername = "validUser" + val successPassword = "validPass" + val errorMessage = "Invalid credentials" + + coEvery { consoleIO.read() } returnsMany listOf( + failedUsername, failedPassword, + successUsername, successPassword + ) + coEvery { loginUseCase(failedUsername, failedPassword) } throws Exception(errorMessage) + coEvery { loginUseCase(successUsername, successPassword) } returns mockk() + coEvery { consoleIO.write(any()) } just runs + + // When + loginUserUI() + + // Then + coVerifySequence { + consoleIO.write("šŸ” Login") + consoleIO.write("Enter username: ") + consoleIO.read() + consoleIO.write("Enter password: šŸ”‘") + consoleIO.read() + loginUseCase(failedUsername, failedPassword) + consoleIO.write("Login failed. $errorMessage šŸ˜ž") + consoleIO.write("Enter username: ") + consoleIO.read() + consoleIO.write("Enter password: šŸ”‘") + consoleIO.read() + loginUseCase(successUsername, successPassword) + consoleIO.write("āœ… Logged in successfully! Welcome back, $successUsername.") + } + + coVerify { + loginUseCase(failedUsername, failedPassword) + loginUseCase(successUsername, successPassword) + consoleIO.write("Login failed. $errorMessage šŸ˜ž") + consoleIO.write("āœ… Logged in successfully! Welcome back, $successUsername.") + } + } + + @Test + fun `should handle multiple login failures before success`() = runTest { + // Given + val inputs = listOf( + "user1", "pass1", + "user2", "pass2", + "user3", "pass3" + ) + val errors = listOf("Invalid username", "Password incorrect") + + coEvery { consoleIO.read() } returnsMany inputs + coEvery { loginUseCase("user1", "pass1") } throws Exception(errors[0]) + coEvery { loginUseCase("user2", "pass2") } throws Exception(errors[1]) + coEvery { loginUseCase("user3", "pass3") } returns mockk() + coEvery { consoleIO.write(any()) } just runs + + // When + loginUserUI() + + // Then + coVerifySequence { + consoleIO.write("šŸ” Login") + + consoleIO.write("Enter username: ") + consoleIO.read() + consoleIO.write("Enter password: šŸ”‘") + consoleIO.read() + loginUseCase("user1", "pass1") + consoleIO.write("Login failed. ${errors[0]} šŸ˜ž") + + consoleIO.write("Enter username: ") + consoleIO.read() + consoleIO.write("Enter password: šŸ”‘") + consoleIO.read() + loginUseCase("user2", "pass2") + consoleIO.write("Login failed. ${errors[1]} šŸ˜ž") + + consoleIO.write("Enter username: ") + consoleIO.read() + consoleIO.write("Enter password: šŸ”‘") + consoleIO.read() + loginUseCase("user3", "pass3") + consoleIO.write("āœ… Logged in successfully! Welcome back, user3.") + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/auth/RegisterAdminUITest.kt b/src/test/kotlin/presentation/auth/RegisterAdminUITest.kt index 6af7866..9a9def6 100644 --- a/src/test/kotlin/presentation/auth/RegisterAdminUITest.kt +++ b/src/test/kotlin/presentation/auth/RegisterAdminUITest.kt @@ -1,133 +1,173 @@ package presentation.auth +import domain.models.User import domain.models.User.UserRole import domain.usecases.auth.RegisterUseCase -import fake.FakeConsoleIO -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.coVerifySequence -import io.mockk.mockk +import io.mockk.* import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import java.util.* +import presentation.io.ConsoleIO class RegisterAdminUITest { private lateinit var registerUseCase: RegisterUseCase - private lateinit var consoleIO: FakeConsoleIO + private lateinit var consoleIO: ConsoleIO private lateinit var registerAdminUI: RegisterAdminUI @BeforeEach fun setUp() { registerUseCase = mockk(relaxed = true) - consoleIO = FakeConsoleIO(LinkedList(listOf("test admin", "test password"))) + consoleIO = mockk(relaxed = true) registerAdminUI = RegisterAdminUI(registerUseCase, consoleIO) } @Test fun `should register admin successfully on first attempt`() = runTest { // Given - coEvery { registerUseCase("test admin", "test password", UserRole.ADMIN) } returns mockk( - relaxed = true - ) + val username = "adminUser" + val password = "P@ssw0rd123" + val mockUser = mockk(relaxed = true) + + coEvery { consoleIO.read() } returnsMany listOf(username, password) + coEvery { registerUseCase(username, password, UserRole.ADMIN) } returns mockUser + coEvery { consoleIO.write(any()) } just runs + // When - registerAdminUI.invoke() + registerAdminUI() // Then coVerifySequence { - registerUseCase("test admin", "test password", UserRole.ADMIN) - } - - val expectedOutputs = listOf( - "šŸ›”ļø Register Admin", - "Enter admin username: ", - "Enter password: šŸ”‘", - "Admin registered successfully! šŸŽ‰", - "āœ… Welcome, test admin Let's begin by adding your first project." - ) - - expectedOutputs.forEachIndexed { index, message -> - assert(consoleIO.outputs[index] == message) { - "Expected '${expectedOutputs[index]}' but got '${consoleIO.outputs[index]}'" - } + consoleIO.write("šŸ›”ļø Register Admin") + consoleIO.write("Enter admin username: ") + consoleIO.read() + consoleIO.write(any()) // Password requirements + consoleIO.write("Enter password: šŸ”‘") + consoleIO.read() + registerUseCase(username, password, UserRole.ADMIN) + consoleIO.write("Admin registered successfully! šŸŽ‰") + consoleIO.write("āœ… Welcome, $username Let's begin by adding your first project.") } } @Test fun `should retry registration after failure`() = runTest { // Given - consoleIO = FakeConsoleIO(LinkedList(listOf("existing", "pass1", "new admin", "pass2"))) - registerAdminUI = RegisterAdminUI(registerUseCase, consoleIO) + val failedUsername = "existingAdmin" + val failedPassword = "WeakPw1!" + val successUsername = "newAdmin" + val successPassword = "Str0ng#Pass2" + val mockUser = mockk(relaxed = true) + val errorMessage = "Username already exists" + + coEvery { consoleIO.read() } returnsMany listOf( + failedUsername, + failedPassword, + successUsername, + successPassword + ) + coEvery { registerUseCase(failedUsername, failedPassword, UserRole.ADMIN) } throws Exception(errorMessage) + coEvery { registerUseCase(successUsername, successPassword, UserRole.ADMIN) } returns mockUser + coEvery { consoleIO.write(any()) } just runs - coEvery { registerUseCase("existing", "pass1", UserRole.ADMIN) } throws Exception("Username already exists") - coEvery { - registerUseCase( - "new admin", - "pass2", - UserRole.ADMIN - ) - } returns mockk(relaxed = true) // When - registerAdminUI.invoke() + registerAdminUI() // Then coVerifySequence { - registerUseCase("existing", "pass1", UserRole.ADMIN) - registerUseCase("new admin", "pass2", UserRole.ADMIN) + consoleIO.write("šŸ›”ļø Register Admin") + consoleIO.write("Enter admin username: ") + consoleIO.read() + consoleIO.write(any()) // Password requirements + consoleIO.write("Enter password: šŸ”‘") + consoleIO.read() + registerUseCase(failedUsername, failedPassword, UserRole.ADMIN) + consoleIO.write("Registration failed. $errorMessage āŒ") + consoleIO.write("Enter admin username: ") + consoleIO.read() + consoleIO.write(any()) // Password requirements + consoleIO.write("Enter password: šŸ”‘") + consoleIO.read() + registerUseCase(successUsername, successPassword, UserRole.ADMIN) + consoleIO.write("Admin registered successfully! šŸŽ‰") + consoleIO.write("āœ… Welcome, $successUsername Let's begin by adding your first project.") } - val expectedOutputs = listOf( - "šŸ›”ļø Register Admin", - "Enter admin username: ", - "Enter password: šŸ”‘", - "Registration failed. Username already exists āŒ", - "Enter admin username: ", - "Enter password: šŸ”‘", - "Admin registered successfully! šŸŽ‰", - "āœ… Welcome, new admin Let's begin by adding your first project." - ) - - expectedOutputs.forEachIndexed { index, message -> - assert(consoleIO.outputs[index] == message) + coVerify { + registerUseCase(failedUsername, failedPassword, UserRole.ADMIN) + registerUseCase(successUsername, successPassword, UserRole.ADMIN) + consoleIO.write("Registration failed. $errorMessage āŒ") + consoleIO.write("Admin registered successfully! šŸŽ‰") + consoleIO.write("āœ… Welcome, $successUsername Let's begin by adding your first project.") } } @Test fun `should handle multiple registration failures before success`() = runTest { // Given - consoleIO = FakeConsoleIO( - LinkedList( - listOf( - "user1", "weak", - "user2", "short", - "user3", "valid123" - ) - ) + val inputs = listOf( + "admin1", "weak1", + "admin2", "short2", + "admin3", "Valid@Pw3" ) - registerAdminUI = RegisterAdminUI(registerUseCase, consoleIO) + val errors = listOf("Password too weak", "Password too short") + val mockUser = mockk(relaxed = true) + + coEvery { consoleIO.read() } returnsMany inputs + coEvery { registerUseCase("admin1", "weak1", UserRole.ADMIN) } throws Exception(errors[0]) + coEvery { registerUseCase("admin2", "short2", UserRole.ADMIN) } throws Exception(errors[1]) + coEvery { registerUseCase("admin3", "Valid@Pw3", UserRole.ADMIN) } returns mockUser + coEvery { consoleIO.write(any()) } just runs - coEvery { registerUseCase("user1", "weak", UserRole.ADMIN) } throws Exception("Password too weak") - coEvery { registerUseCase("user2", "short", UserRole.ADMIN) } throws Exception("Password too short") - coEvery { - registerUseCase( - "user3", - "valid123", - UserRole.ADMIN - ) - } returns mockk(relaxed = true) // When - registerAdminUI.invoke() + registerAdminUI() // Then - coVerify { - registerUseCase("user1", "weak", UserRole.ADMIN) - registerUseCase("user2", "short", UserRole.ADMIN) - registerUseCase("user3", "valid123", UserRole.ADMIN) + // Then + coVerifySequence { + consoleIO.write("šŸ›”ļø Register Admin") + consoleIO.write("Enter admin username: ") + consoleIO.read() + // Match the actual multi-line password requirements format + consoleIO.write( + """# At least one lowercase letter +# At least one uppercase letter +# At least one digit +# At least one special character +# Minimum length of 8 characters""" + ) + consoleIO.write("Enter password: šŸ”‘") + consoleIO.read() + registerUseCase("admin1", "weak1", UserRole.ADMIN) + consoleIO.write("Registration failed. ${errors[0]} āŒ") + + consoleIO.write("Enter admin username: ") + consoleIO.read() + consoleIO.write( + """# At least one lowercase letter +# At least one uppercase letter +# At least one digit +# At least one special character +# Minimum length of 8 characters""" + ) + consoleIO.write("Enter password: šŸ”‘") + consoleIO.read() + registerUseCase("admin2", "short2", UserRole.ADMIN) + consoleIO.write("Registration failed. ${errors[1]} āŒ") + + consoleIO.write("Enter admin username: ") + consoleIO.read() + consoleIO.write( + """# At least one lowercase letter +# At least one uppercase letter +# At least one digit +# At least one special character +# Minimum length of 8 characters""" + ) + consoleIO.write("Enter password: šŸ”‘") + consoleIO.read() + registerUseCase("admin3", "Valid@Pw3", UserRole.ADMIN) + consoleIO.write("Admin registered successfully! šŸŽ‰") + consoleIO.write("āœ… Welcome, admin3 Let's begin by adding your first project.") } - - assert(consoleIO.outputs.contains("Registration failed. Password too weak āŒ")) - assert(consoleIO.outputs.contains("Registration failed. Password too short āŒ")) - assert(consoleIO.outputs.contains("Admin registered successfully! šŸŽ‰")) - assert(consoleIO.outputs.contains("āœ… Welcome, user3 Let's begin by adding your first project.")) } -} +} \ 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 a27f25b..8887fcd 100644 --- a/src/test/kotlin/presentation/project/CreateProjectUITest.kt +++ b/src/test/kotlin/presentation/project/CreateProjectUITest.kt @@ -5,115 +5,135 @@ import data.session_manager.SessionManager import domain.models.User.UserRole import domain.usecases.project.CreateProjectUseCase import io.mockk.* -import io.mockk.impl.annotations.MockK -import io.mockk.junit5.MockKExtension import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith import presentation.io.ConsoleIO import presentation.state.CreateTaskStateUI import java.util.* -@ExtendWith(MockKExtension::class) class CreateProjectUITest { - - @MockK private lateinit var createProjectUseCase: CreateProjectUseCase - - @MockK private lateinit var consoleIO: ConsoleIO - - @MockK private lateinit var createTaskStateUI: CreateTaskStateUI - private lateinit var createProjectUI: CreateProjectUI + private val projectId = UUID.randomUUID() + private val projectName = "Test Project" @BeforeEach fun setUp() { - MockKAnnotations.init(this) + createProjectUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + createTaskStateUI = mockk(relaxed = true) createProjectUI = CreateProjectUI(createProjectUseCase, createTaskStateUI, consoleIO) mockkObject(SessionManager) + + every { consoleIO.read() } returnsMany listOf(projectName, "no") + coEvery { createProjectUseCase(projectName) } returns projectId } @Test - fun `invoke should create project successfully for admin user`() = runTest { - val projectName = "Test Project" - val projectId = UUID.randomUUID() + fun `should create project successfully when user is admin`() = runTest { + // Given val adminUser = mockk() - coEvery { adminUser.role } returns UserRole.ADMIN coEvery { SessionManager.currentUser } returns adminUser - coEvery { consoleIO.read() } returns projectName - coEvery { consoleIO.write(any()) } just Runs - coEvery { createProjectUseCase(projectName) } returns projectId + // When createProjectUI.invoke() - coVerify { consoleIO.write("Enter project name:") } - coVerify { consoleIO.read() } - coVerify { createProjectUseCase(projectName) } - coVerify { consoleIO.write("Project $projectId created successfully!") } + // Then + coVerifySequence { + consoleIO.write("Enter project name:") + consoleIO.read() + createProjectUseCase(projectName) + consoleIO.write("Project named $projectName with id $projectId created successfully!") + consoleIO.write("Would you like to add a state to the project now? (yes/no):") + consoleIO.read() + consoleIO.write("āš ļø Warning: You won't be able to add tasks to this project unless you add at least one state.") + } } @Test - fun `invoke should create project successfully for regular user`() = runTest { - val projectName = "Test Project" - val projectId = UUID.randomUUID() + fun `should create project successfully when user is not admin`() = runTest { + // Given val regularUser = mockk() - coEvery { regularUser.role } returns UserRole.MATE coEvery { SessionManager.currentUser } returns regularUser - coEvery { consoleIO.read() } returns projectName - coEvery { consoleIO.write(any()) } just Runs - coEvery { createProjectUseCase(projectName) } returns projectId + // When createProjectUI.invoke() - // Assert - coVerify { consoleIO.write("Enter project name:") } - coVerify { consoleIO.read() } - coVerify { createProjectUseCase(projectName) } - coVerify { consoleIO.write("Project $projectId created successfully!") } + // Then + coVerifySequence { + consoleIO.write("Enter project name:") + consoleIO.read() + createProjectUseCase(projectName) + consoleIO.write("Project named $projectName with id $projectId created successfully!") + consoleIO.write("Would you like to add a state to the project now? (yes/no):") + consoleIO.read() + consoleIO.write("āš ļø Warning: You won't be able to add tasks to this project unless you add at least one state.") + } } @Test - fun `invoke should handle error when creating project fails`() = runTest { - // Arrange - val projectName = "Test Project" + fun `should handle exception when creating project`() = runTest { + // Given val errorMessage = "Invalid project name" - val user = mockk() - - coEvery { user.role } returns UserRole.MATE - coEvery { SessionManager.currentUser } returns user - coEvery { consoleIO.read() } returns projectName - coEvery { consoleIO.write(any()) } just Runs + coEvery { SessionManager.currentUser } returns null coEvery { createProjectUseCase(projectName) } throws IllegalArgumentException(errorMessage) + // When createProjectUI.invoke() - coVerify { consoleIO.write("Enter project name:") } - coVerify { consoleIO.read() } - coVerify { createProjectUseCase(projectName) } - coVerify { consoleIO.write("Error creating project: $errorMessage") } + // Then + coVerifySequence { + consoleIO.write("Enter project name:") + consoleIO.read() + createProjectUseCase(projectName) + consoleIO.write("Error creating project: $errorMessage") + } } @Test - fun `invoke should handle null user in SessionManager`() = runTest { - val projectName = "Test Project" - val projectId = UUID.randomUUID() + fun `should handle null user in session`() = runTest { + // Given + coEvery { SessionManager.currentUser } returns null + + // When + createProjectUI.invoke() + // Then + coVerifySequence { + consoleIO.write("Enter project name:") + consoleIO.read() + createProjectUseCase(projectName) + consoleIO.write("Project named $projectName with id $projectId created successfully!") + consoleIO.write("Would you like to add a state to the project now? (yes/no):") + consoleIO.read() + consoleIO.write("āš ļø Warning: You won't be able to add tasks to this project unless you add at least one state.") + } + } + + @Test + fun `should create task state when user selects yes`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf(projectName, "yes") coEvery { SessionManager.currentUser } returns null - coEvery { consoleIO.read() } returns projectName - coEvery { consoleIO.write(any()) } just Runs - coEvery { createProjectUseCase(projectName) } returns projectId + // When createProjectUI.invoke() - coVerify { consoleIO.write("Enter project name:") } - coVerify { consoleIO.read() } - coVerify { createProjectUseCase(projectName) } - coVerify { consoleIO.write("Project $projectId created successfully!") } + // Then + coVerifySequence { + consoleIO.write("Enter project name:") + consoleIO.read() + createProjectUseCase(projectName) + consoleIO.write("Project named $projectName with id $projectId created successfully!") + consoleIO.write("Would you like to add a state to the project now? (yes/no):") + consoleIO.read() + createTaskStateUI(projectId) + } } -} +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/project/GetAllProjectsUITest.kt b/src/test/kotlin/presentation/project/GetAllProjectsUITest.kt index a8d5c09..d74e5b2 100644 --- a/src/test/kotlin/presentation/project/GetAllProjectsUITest.kt +++ b/src/test/kotlin/presentation/project/GetAllProjectsUITest.kt @@ -1,10 +1,11 @@ package presentation.project +import data.session_manager.LoggedInUser +import data.session_manager.SessionManager import domain.models.Project +import domain.models.User.UserRole import domain.usecases.project.GetAllProjectsUseCase -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.mockk +import io.mockk.* import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -15,21 +16,46 @@ class GetAllProjectsUITest { private lateinit var getAllProjectsUseCase: GetAllProjectsUseCase private lateinit var consoleIO: ConsoleIO private lateinit var getAllProjectsUI: GetAllProjectsUI + private val project1 = Project(id = UUID.fromString("00000000-0000-0000-0000-000000000001"), name = "Project 1") + private val project2 = Project(id = UUID.fromString("00000000-0000-0000-0000-000000000002"), name = "Project 2") + private val projectsList = listOf(project1, project2) @BeforeEach fun setUp() { getAllProjectsUseCase = mockk(relaxed = true) consoleIO = mockk(relaxed = true) getAllProjectsUI = GetAllProjectsUI(getAllProjectsUseCase, consoleIO) + + mockkObject(SessionManager) + } + + @Test + fun `should display projects when user is admin`() = runTest { + // Given + val adminUser = mockk() + coEvery { adminUser.role } returns UserRole.ADMIN + coEvery { SessionManager.currentUser } returns adminUser + coEvery { getAllProjectsUseCase() } returns projectsList + + // When + getAllProjectsUI.invoke() + + // Then + coVerify { + getAllProjectsUseCase() + consoleIO.write("Projects:") + consoleIO.write(match { it.contains("Project ID : ${project1.id}") && it.contains("Name : ${project1.name}") }) + consoleIO.write(match { it.contains("Project ID : ${project2.id}") && it.contains("Name : ${project2.name}") }) + } } @Test - fun `should display projects when projects are available`() = runTest { + fun `should display projects when user is not admin`() = runTest { // Given - val project1 = createProject(id = "00000000-0000-0000-0000-000000000001", name = "Project 1") - val project2 = createProject(id = "00000000-0000-0000-0000-000000000002", name = "Project 2") - val projects = listOf(project1, project2) - coEvery { getAllProjectsUseCase() } returns projects + val regularUser = mockk() + coEvery { regularUser.role } returns UserRole.MATE + coEvery { SessionManager.currentUser } returns regularUser + coEvery { getAllProjectsUseCase() } returns projectsList // When getAllProjectsUI.invoke() @@ -38,14 +64,15 @@ class GetAllProjectsUITest { coVerify { getAllProjectsUseCase() consoleIO.write("Projects:") - consoleIO.write(match { it.contains("Project ID: ${project1.id}") && it.contains("Name: ${project1.name}") }) - consoleIO.write(match { it.contains("Project ID: ${project2.id}") && it.contains("Name: ${project2.name}") }) + consoleIO.write(match { it.contains("Project ID : ${project1.id}") && it.contains("Name : ${project1.name}") }) + consoleIO.write(match { it.contains("Project ID : ${project2.id}") && it.contains("Name : ${project2.name}") }) } } @Test fun `should display message when no projects are found`() = runTest { // Given + coEvery { SessionManager.currentUser } returns null coEvery { getAllProjectsUseCase() } returns emptyList() // When @@ -53,14 +80,16 @@ class GetAllProjectsUITest { // Then coVerify(exactly = 1) { + getAllProjectsUseCase() consoleIO.write("No projects found.") } } @Test - fun `should display error message when exception is thrown`() = runTest { + fun `should handle exception when retrieving projects`() = runTest { // Given val errorMessage = "Database connection failed" + coEvery { SessionManager.currentUser } returns null coEvery { getAllProjectsUseCase() } throws RuntimeException(errorMessage) // When @@ -68,9 +97,26 @@ class GetAllProjectsUITest { // Then coVerify(exactly = 1) { + getAllProjectsUseCase() consoleIO.write("Error retrieving projects: $errorMessage") } } - private fun createProject(id: String, name: String) = Project(id = UUID.fromString(id), name = name) -} + @Test + fun `should handle null user in session`() = runTest { + // Given + coEvery { SessionManager.currentUser } returns null + coEvery { getAllProjectsUseCase() } returns projectsList + + // When + getAllProjectsUI.invoke() + + // Then + coVerify { + getAllProjectsUseCase() + consoleIO.write("Projects:") + consoleIO.write(match { it.contains("Project ID : ${project1.id}") && it.contains("Name : ${project1.name}") }) + consoleIO.write(match { it.contains("Project ID : ${project2.id}") && it.contains("Name : ${project2.name}") }) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/project/ProjectsUITest.kt b/src/test/kotlin/presentation/project/ProjectsUITest.kt index 4cc8719..ab2aecf 100644 --- a/src/test/kotlin/presentation/project/ProjectsUITest.kt +++ b/src/test/kotlin/presentation/project/ProjectsUITest.kt @@ -1,5 +1,6 @@ package presentation.project +import domain.models.Project import domain.usecases.project.GetAllProjectsUseCase import io.mockk.coEvery import io.mockk.coVerify @@ -33,30 +34,54 @@ class ProjectsUITest { updateProjectNameUI, deleteProjectUI, getAllProjectsUseCase, - consoleIO, + consoleIO ) } @Test - fun `should display menu and call getAllProjectsUI`() = runTest { + fun `should redirect to create project when no projects exist`() = runTest { // Given - coEvery { consoleIO.read() } returns "4" + coEvery { getAllProjectsUseCase() } returns emptyList() // When projectsUI.invoke() // Then coVerifySequence { + getAllProjectsUseCase() + consoleIO.write("No projects found. You must create a project first.") + createProjectUI.invoke() + } + + coVerify(exactly = 0) { getAllProjectsUI.invoke() - consoleIO.write(any()) consoleIO.read() - consoleIO.write("Going back to the main menu...") } } @Test - fun `should call createProjectUI when option 1 is selected`() = runTest { + fun `should handle exception when checking if projects exist`() = runTest { // Given + val errorMessage = "Database connection failed" + coEvery { getAllProjectsUseCase() } throws RuntimeException(errorMessage) + + // When + projectsUI.invoke() + + // Then + coVerifySequence { + getAllProjectsUseCase() + consoleIO.write("Error checking projects: $errorMessage") + consoleIO.write("No projects found. You must create a project first.") + createProjectUI.invoke() + } + } + + @Test + fun `should display menu and create project when option 1 is selected`() = runTest { + // Given + val mockProjects = listOf(mockk()) + coEvery { getAllProjectsUseCase() } returns mockProjects coEvery { consoleIO.read() } returns "1" // When @@ -64,16 +89,19 @@ class ProjectsUITest { // Then coVerifySequence { + getAllProjectsUseCase() getAllProjectsUI.invoke() - consoleIO.write(any()) + consoleIO.write(any()) consoleIO.read() createProjectUI.invoke() } } @Test - fun `should call updateProjectUI when option 2 is selected`() = runTest { + fun `should display menu and update project when option 2 is selected`() = runTest { // Given + val mockProjects = listOf(mockk()) + coEvery { getAllProjectsUseCase() } returns mockProjects coEvery { consoleIO.read() } returns "2" // When @@ -81,15 +109,19 @@ class ProjectsUITest { // Then coVerifySequence { - consoleIO.write(any()) + getAllProjectsUseCase() + getAllProjectsUI.invoke() + consoleIO.write(any()) consoleIO.read() updateProjectNameUI.invoke() } } @Test - fun `should call deleteProjectUI when option 3 is selected`() = runTest { + fun `should display menu and delete project when option 3 is selected`() = runTest { // Given + val mockProjects = listOf(mockk()) + coEvery { getAllProjectsUseCase() } returns mockProjects coEvery { consoleIO.read() } returns "3" // When @@ -97,16 +129,19 @@ class ProjectsUITest { // Then coVerifySequence { + getAllProjectsUseCase() getAllProjectsUI.invoke() - consoleIO.write(any()) + consoleIO.write(any()) consoleIO.read() deleteProjectUI.invoke() } } @Test - fun `should return to main menu when option 4 is selected`() = runTest { + fun `should display menu and return to main menu when option 4 is selected`() = runTest { // Given + val mockProjects = listOf(mockk()) + coEvery { getAllProjectsUseCase() } returns mockProjects coEvery { consoleIO.read() } returns "4" // When @@ -114,8 +149,9 @@ class ProjectsUITest { // Then coVerifySequence { + getAllProjectsUseCase() getAllProjectsUI.invoke() - consoleIO.write(any()) + consoleIO.write(any()) consoleIO.read() consoleIO.write("Going back to the main menu...") } @@ -128,8 +164,10 @@ class ProjectsUITest { } @Test - fun `should display error message when invalid option is entered`() = runTest { + fun `should display error message when invalid input is entered`() = runTest { // Given + val mockProjects = listOf(mockk()) + coEvery { getAllProjectsUseCase() } returns mockProjects coEvery { consoleIO.read() } returns "invalid" // When @@ -137,8 +175,9 @@ class ProjectsUITest { // Then coVerifySequence { + getAllProjectsUseCase() getAllProjectsUI.invoke() - consoleIO.write(any()) + consoleIO.write(any()) consoleIO.read() consoleIO.write("\nInvalid input. Please enter a number between 1 and 4.") } @@ -147,6 +186,8 @@ class ProjectsUITest { @Test fun `should display error message when out of range option is entered`() = runTest { // Given + val mockProjects = listOf(mockk()) + coEvery { getAllProjectsUseCase() } returns mockProjects coEvery { consoleIO.read() } returns "5" // When @@ -154,10 +195,11 @@ class ProjectsUITest { // Then coVerifySequence { + getAllProjectsUseCase() getAllProjectsUI.invoke() - consoleIO.write(any()) + consoleIO.write(any()) consoleIO.read() consoleIO.write("\nInvalid input. Please enter a number between 1 and 4.") } } -} +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/project/UpdateProjectNameUITest.kt b/src/test/kotlin/presentation/project/UpdateProjectNameUITest.kt index 71dae0e..27b43ee 100644 --- a/src/test/kotlin/presentation/project/UpdateProjectNameUITest.kt +++ b/src/test/kotlin/presentation/project/UpdateProjectNameUITest.kt @@ -16,6 +16,9 @@ class UpdateProjectNameUITest { private lateinit var updateProjectUseCase: UpdateProjectUseCase private lateinit var consoleIO: ConsoleIO private lateinit var updateProjectNameUI: UpdateProjectNameUI + private val projectId = UUID.randomUUID() + private val projectIdString = projectId.toString() + private val newProjectName = "Updated Project Name" @BeforeEach fun setUp() { @@ -23,147 +26,124 @@ class UpdateProjectNameUITest { consoleIO = mockk(relaxed = true) updateProjectNameUI = UpdateProjectNameUI(updateProjectUseCase, consoleIO) - mockkObject(SessionManager) - mockkStatic("data.mongodb_data.mappers.MapperKt") - SessionManager.currentUser = null + mockkStatic(String::toUUID) + every { projectIdString.toUUID() } returns projectId } @Test - fun `should update project name successfully when valid inputs and regular user`() = runTest { - val projectId = UUID.randomUUID() - val projectName = "Updated Project Name" - SessionManager.currentUser = LoggedInUser(UUID.randomUUID(), "user", UserRole.MATE, listOf()) + fun `should update project name successfully when user is admin`() = runTest { + // Given + val adminUser = mockk() + coEvery { adminUser.role } returns UserRole.ADMIN + mockkObject(SessionManager) + coEvery { SessionManager.currentUser } returns adminUser - every { consoleIO.read() } returnsMany listOf(projectId.toString(), projectName) - every { any().toUUID() } returns projectId - coEvery { updateProjectUseCase(projectId, projectName) } returns true + every { consoleIO.read() } returnsMany listOf(projectIdString, newProjectName) - updateProjectNameUI() + // When + updateProjectNameUI.invoke() + // Then coVerifySequence { consoleIO.write("Enter the project ID to update:") consoleIO.read() consoleIO.write("Please Enter new project name: ") consoleIO.read() - updateProjectUseCase(projectId, projectName) + updateProjectUseCase(projectId, newProjectName) consoleIO.write("āœ… Project updated successfully.") } } @Test - fun `should update project name successfully when valid inputs and admin user`() = runTest { - val projectId = UUID.randomUUID() - val projectName = "Updated Project Name" - SessionManager.currentUser = LoggedInUser(UUID.randomUUID(), "admin", UserRole.ADMIN, listOf()) + fun `should update project name successfully when user is not admin`() = runTest { + // Given + val regularUser = mockk() + coEvery { regularUser.role } returns UserRole.MATE + mockkObject(SessionManager) + coEvery { SessionManager.currentUser } returns regularUser - every { consoleIO.read() } returnsMany listOf(projectId.toString(), projectName) - every { any().toUUID() } returns projectId - coEvery { updateProjectUseCase(projectId, projectName) } returns true + every { consoleIO.read() } returnsMany listOf(projectIdString, newProjectName) - updateProjectNameUI() + // When + updateProjectNameUI.invoke() + // Then coVerifySequence { consoleIO.write("Enter the project ID to update:") consoleIO.read() consoleIO.write("Please Enter new project name: ") consoleIO.read() - updateProjectUseCase(projectId, projectName) + updateProjectUseCase(projectId, newProjectName) consoleIO.write("āœ… Project updated successfully.") } } @Test - fun `should handle error when update project name fails`() = runTest { - val projectId = UUID.randomUUID() - val projectName = "Updated Project Name" + fun `should handle exception when updating project name`() = runTest { + // Given val errorMessage = "Project not found" - SessionManager.currentUser = LoggedInUser(UUID.randomUUID(), "user", UserRole.MATE, listOf()) + mockkObject(SessionManager) + coEvery { SessionManager.currentUser } returns null - every { consoleIO.read() } returnsMany listOf(projectId.toString(), projectName) - every { any().toUUID() } returns projectId - coEvery { updateProjectUseCase(projectId, projectName) } throws IllegalArgumentException(errorMessage) + every { consoleIO.read() } returnsMany listOf(projectIdString, newProjectName) + coEvery { updateProjectUseCase(projectId, newProjectName) } throws RuntimeException(errorMessage) - updateProjectNameUI() + // When + updateProjectNameUI.invoke() + // Then coVerifySequence { consoleIO.write("Enter the project ID to update:") consoleIO.read() consoleIO.write("Please Enter new project name: ") consoleIO.read() - updateProjectUseCase(projectId, projectName) + updateProjectUseCase(projectId, newProjectName) consoleIO.write("āŒ Failed to update project: $errorMessage") } } @Test fun `should handle invalid project ID`() = runTest { - val invalidId = "not-a-uuid" - val validProjectId = UUID.randomUUID() - val projectName = "Updated Project Name" - SessionManager.currentUser = LoggedInUser(UUID.randomUUID(), "user", UserRole.MATE, listOf()) + // Given + val invalidIdString = "invalid-uuid" - every { consoleIO.read() } returnsMany listOf(invalidId, validProjectId.toString(), projectName) - every { invalidId.toUUID() } throws IllegalArgumentException("Invalid UUID") - every { validProjectId.toString().toUUID() } returns validProjectId - coEvery { updateProjectUseCase(validProjectId, projectName) } returns true + every { consoleIO.read() } returns invalidIdString + every { invalidIdString.toUUID() } throws IllegalArgumentException("Invalid UUID format") - updateProjectNameUI() + // When + updateProjectNameUI.invoke() + // Then coVerifySequence { consoleIO.write("Enter the project ID to update:") consoleIO.read() consoleIO.write("āŒ please enter correct ID ") - consoleIO.write("Enter the project ID to update:") - consoleIO.read() - consoleIO.write("Please Enter new project name: ") - consoleIO.read() - updateProjectUseCase(validProjectId, projectName) - consoleIO.write("āœ… Project updated successfully.") } - } - - @Test - fun `should handle null user in session`() = runTest { - val projectId = UUID.randomUUID() - val projectName = "Updated Project Name" - SessionManager.currentUser = null - - every { consoleIO.read() } returnsMany listOf(projectId.toString(), projectName) - every { any().toUUID() } returns projectId - coEvery { updateProjectUseCase(projectId, projectName) } returns true - - updateProjectNameUI() - coVerifySequence { - consoleIO.write("Enter the project ID to update:") - consoleIO.read() + verify(exactly = 0) { consoleIO.write("Please Enter new project name: ") - consoleIO.read() - updateProjectUseCase(projectId, projectName) - consoleIO.write("āœ… Project updated successfully.") } } @Test - fun `should handle empty project name`() = runTest { - val projectId = UUID.randomUUID() - val emptyName = "" - val errorMessage = "Project name cannot be empty" - SessionManager.currentUser = LoggedInUser(UUID.randomUUID(), "user", UserRole.MATE, listOf()) + fun `should handle null user in session`() = runTest { + // Given + mockkObject(SessionManager) + coEvery { SessionManager.currentUser } returns null - every { consoleIO.read() } returnsMany listOf(projectId.toString(), emptyName) - every { any().toUUID() } returns projectId - coEvery { updateProjectUseCase(projectId, emptyName) } throws IllegalArgumentException(errorMessage) + every { consoleIO.read() } returnsMany listOf(projectIdString, newProjectName) - updateProjectNameUI() + // When + updateProjectNameUI.invoke() + // Then coVerifySequence { consoleIO.write("Enter the project ID to update:") consoleIO.read() consoleIO.write("Please Enter new project name: ") consoleIO.read() - updateProjectUseCase(projectId, emptyName) - consoleIO.write("āŒ Failed to update project: $errorMessage") + updateProjectUseCase(projectId, newProjectName) + consoleIO.write("āœ… Project updated successfully.") } } -} +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/state/CreateTaskStateUITest.kt b/src/test/kotlin/presentation/state/CreateTaskStateUITest.kt new file mode 100644 index 0000000..c964748 --- /dev/null +++ b/src/test/kotlin/presentation/state/CreateTaskStateUITest.kt @@ -0,0 +1,131 @@ +package presentation.state + +import domain.models.TaskState +import domain.usecases.task_state.AddTaskStateUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class CreateTaskStateUITest { + private lateinit var addTaskStateUseCase: AddTaskStateUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var createTaskStateUI: CreateTaskStateUI + private val projectId = UUID.randomUUID() + private val stateId = UUID.randomUUID() + private val stateName = "To Do" + + @BeforeEach + fun setUp() { + addTaskStateUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + createTaskStateUI = CreateTaskStateUI(addTaskStateUseCase, consoleIO) + + every { consoleIO.read() } returns stateName + + mockkStatic(UUID::class) + every { UUID.randomUUID() } returns stateId + } + + @Test + fun `should create task state successfully`() = runTest { + // Given + coEvery { addTaskStateUseCase(any()) } returns true + + // When + createTaskStateUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the state name:") + consoleIO.read() + addTaskStateUseCase(TaskState( + id = stateId, + name = stateName, + projectId = projectId + )) + consoleIO.write("āœ… State created successfully.") + } + } + + @Test + fun `should handle empty state name`() = runTest { + // Given + val emptyStateName = "" + every { consoleIO.read() } returns emptyStateName + + // When + createTaskStateUI.invoke(projectId) + + // Then + coVerify { + consoleIO.write("Please enter the state name:") + consoleIO.read() + addTaskStateUseCase(TaskState( + id = stateId, + name = emptyStateName, + projectId = projectId + )) + } + } + + @Test + fun `should handle exception when adding state`() = runTest { + // Given + val errorMessage = "Failed to add state" + coEvery { addTaskStateUseCase(any()) } throws RuntimeException(errorMessage) + + // When + createTaskStateUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the state name:") + consoleIO.read() + addTaskStateUseCase(any()) + consoleIO.write("āŒ Failed to create state: $errorMessage") + } + } + + @Test + fun `should create task state with correct project ID`() = runTest { + // Given + val differentProjectId = UUID.randomUUID() + val capturedState = slot() + coEvery { addTaskStateUseCase(capture(capturedState)) } returns true + + // When + createTaskStateUI.invoke(differentProjectId) + + // Then + coVerify { + addTaskStateUseCase(any()) + } + assert(capturedState.captured.projectId == differentProjectId) + assert(capturedState.captured.name == stateName) + } + + @Test + fun `should create task state with long name`() = runTest { + // Given + val longName = "A".repeat(100) + every { consoleIO.read() } returns longName + + // When + createTaskStateUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the state name:") + consoleIO.read() + addTaskStateUseCase(TaskState( + id = stateId, + name = longName, + projectId = projectId + )) + consoleIO.write("āœ… State created successfully.") + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/state/DeleteTaskStateUITest.kt b/src/test/kotlin/presentation/state/DeleteTaskStateUITest.kt new file mode 100644 index 0000000..fdf75e8 --- /dev/null +++ b/src/test/kotlin/presentation/state/DeleteTaskStateUITest.kt @@ -0,0 +1,115 @@ +package presentation.state + +import domain.usecases.task_state.DeleteTaskStateUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class DeleteTaskStateUITest { + private lateinit var deleteTaskStateUseCase: DeleteTaskStateUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var deleteTaskStateUI: DeleteTaskStateUI + private val projectId = UUID.randomUUID() + private val stateId = UUID.randomUUID() + + @BeforeEach + fun setUp() { + deleteTaskStateUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + deleteTaskStateUI = DeleteTaskStateUI(deleteTaskStateUseCase, consoleIO) + + every { consoleIO.readUUID() } returns stateId + } + + @Test + fun `should delete task state successfully`() = runTest { + // Given + coEvery { deleteTaskStateUseCase(any(), any()) } returns true + + // When + deleteTaskStateUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the state ID:") + consoleIO.readUUID() + deleteTaskStateUseCase(stateId, projectId) + consoleIO.write("āœ… State deleted successfully.") + } + } + + @Test + fun `should handle invalid state ID`() = runTest { + // Given + every { consoleIO.readUUID() } returns null + + // When + deleteTaskStateUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the state ID:") + consoleIO.readUUID() + consoleIO.write("āŒ Invalid state ID.") + } + + coVerify(exactly = 0) { + deleteTaskStateUseCase(any(), any()) + } + } + + @Test + fun `should handle exception when deleting state`() = runTest { + // Given + val errorMessage = "Failed to delete state" + coEvery { deleteTaskStateUseCase(any(), any()) } throws RuntimeException(errorMessage) + + // When + deleteTaskStateUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the state ID:") + consoleIO.readUUID() + deleteTaskStateUseCase(stateId, projectId) + consoleIO.write("āŒ Failed to delete state: $errorMessage") + } + } + + @Test + fun `should delete task state with correct project ID`() = runTest { + // Given + val differentProjectId = UUID.randomUUID() + val capturedStateId = slot() + val capturedProjectId = slot() + coEvery { deleteTaskStateUseCase(capture(capturedStateId), capture(capturedProjectId)) } returns true + + // When + deleteTaskStateUI.invoke(differentProjectId) + + // Then + coVerify { + deleteTaskStateUseCase(any(), any()) + } + assert(capturedStateId.captured == stateId) + assert(capturedProjectId.captured == differentProjectId) + } + + @Test + fun `should pass correct parameters to use case`() = runTest { + // Given + val differentStateId = UUID.randomUUID() + every { consoleIO.readUUID() } returns differentStateId + + // When + deleteTaskStateUI.invoke(projectId) + + // Then + coVerify { + deleteTaskStateUseCase(differentStateId, projectId) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/state/EditTaskStateUITest.kt b/src/test/kotlin/presentation/state/EditTaskStateUITest.kt new file mode 100644 index 0000000..b85952a --- /dev/null +++ b/src/test/kotlin/presentation/state/EditTaskStateUITest.kt @@ -0,0 +1,168 @@ +package presentation.state + +import domain.models.TaskState +import domain.usecases.task_state.EditTaskStateUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class EditTaskStateUITest { + private lateinit var editTaskStateUseCase: EditTaskStateUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var editTaskStateUI: EditTaskStateUI + private val projectId = UUID.randomUUID() + private val stateId = UUID.randomUUID() + private val stateName = "In Progress" + + @BeforeEach + fun setUp() { + editTaskStateUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + editTaskStateUI = EditTaskStateUI(editTaskStateUseCase, consoleIO) + + every { consoleIO.readUUID() } returns stateId + every { consoleIO.read() } returns stateName + } + + @Test + fun `should edit task state successfully`() = runTest { + // Given + coEvery { editTaskStateUseCase(any()) } returns TaskState(id = stateId, name = stateName, projectId = projectId) + // When + editTaskStateUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the state ID:") + consoleIO.readUUID() + consoleIO.write("Please enter the new state name:") + consoleIO.read() + editTaskStateUseCase( + TaskState( + id = stateId, + name = stateName, + projectId = projectId + ) + ) + consoleIO.write("āœ… State updated successfully.") + } + } + + @Test + fun `should handle invalid state ID`() = runTest { + // Given + every { consoleIO.readUUID() } returns null + + // When + editTaskStateUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the state ID:") + consoleIO.readUUID() + consoleIO.write("Please enter the new state name:") + consoleIO.read() + consoleIO.write("āŒ Invalid state ID.") + } + + coVerify(exactly = 0) { + editTaskStateUseCase(any()) + } + } + + @Test + fun `should handle empty state name`() = runTest { + // Given + val emptyStateName = "" + every { consoleIO.read() } returns emptyStateName + + // When + editTaskStateUI.invoke(projectId) + + // Then + coVerify { + consoleIO.write("Please enter the state ID:") + consoleIO.readUUID() + consoleIO.write("Please enter the new state name:") + consoleIO.read() + editTaskStateUseCase( + TaskState( + id = stateId, + name = emptyStateName, + projectId = projectId + ) + ) + } + } + + @Test + fun `should handle exception when editing state`() = runTest { + // Given + val errorMessage = "Failed to edit state" + coEvery { editTaskStateUseCase(any()) } throws RuntimeException(errorMessage) + + // When + editTaskStateUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the state ID:") + consoleIO.readUUID() + consoleIO.write("Please enter the new state name:") + consoleIO.read() + editTaskStateUseCase(any()) + consoleIO.write("āŒ Failed to update state: $errorMessage") + } + } + + @Test + fun `should edit task state with correct project ID`() = runTest { + // Given + val differentProjectId = UUID.randomUUID() + val capturedState = slot() + coEvery { editTaskStateUseCase(capture(capturedState)) } returns TaskState( + id = stateId, + name = stateName, + projectId = differentProjectId + ) + // When + editTaskStateUI.invoke(differentProjectId) + + // Then + coVerify { + editTaskStateUseCase(any()) + } + assert(capturedState.captured.projectId == differentProjectId) + assert(capturedState.captured.id == stateId) + assert(capturedState.captured.name == stateName) + } + + @Test + fun `should edit task state with long name`() = runTest { + // Given + val longName = "A".repeat(100) + every { consoleIO.read() } returns longName + + // When + editTaskStateUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the state ID:") + consoleIO.readUUID() + consoleIO.write("Please enter the new state name:") + consoleIO.read() + editTaskStateUseCase( + TaskState( + id = stateId, + name = longName, + projectId = projectId + ) + ) + consoleIO.write("āœ… State updated successfully.") + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/state/GetAllTaskStatesUITest.kt b/src/test/kotlin/presentation/state/GetAllTaskStatesUITest.kt new file mode 100644 index 0000000..2b59d10 --- /dev/null +++ b/src/test/kotlin/presentation/state/GetAllTaskStatesUITest.kt @@ -0,0 +1,165 @@ +package presentation.state + +import domain.models.TaskState +import domain.usecases.task_state.GetTaskStatesByProjectIdUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class GetAllTaskStatesUITest { + private lateinit var getTaskStatesByProjectIdUseCase: GetTaskStatesByProjectIdUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var getAllTaskStatesUI: GetAllTaskStatesUI + private val projectId = UUID.randomUUID() + + @BeforeEach + fun setUp() { + getTaskStatesByProjectIdUseCase = mockk() + consoleIO = mockk(relaxed = true) + getAllTaskStatesUI = GetAllTaskStatesUI(getTaskStatesByProjectIdUseCase, consoleIO) + } + + @Test + fun `should display all states when states exist`() = runTest { + // Given + val state1 = TaskState( + id = UUID.randomUUID(), + name = "To Do", + projectId = projectId + ) + val state2 = TaskState( + id = UUID.randomUUID(), + name = "In Progress", + projectId = projectId + ) + val states = listOf(state1, state2) + coEvery { getTaskStatesByProjectIdUseCase(projectId) } returns states + + // When + getAllTaskStatesUI.invoke(projectId) + + // Then + coVerify { + getTaskStatesByProjectIdUseCase(projectId) + consoleIO.write( + """ + ------------------------------------------------------- + | State Name: ${state1.name} + | State ID: ${state1.id} + ------------------------------------------------------- + """.trimIndent() + ) + consoleIO.write( + """ + ------------------------------------------------------- + | State Name: ${state2.name} + | State ID: ${state2.id} + ------------------------------------------------------- + """.trimIndent() + ) + } + } + + @Test + fun `should display message when no states exist`() = runTest { + // Given + val emptyList = emptyList() + coEvery { getTaskStatesByProjectIdUseCase(projectId) } returns emptyList + + // When + getAllTaskStatesUI.invoke(projectId) + + // Then + coVerifySequence { + getTaskStatesByProjectIdUseCase(projectId) + consoleIO.write("ā„¹ļø No states found for this project.") + } + } + + @Test + fun `should handle exception when fetching states`() = runTest { + // Given + val errorMessage = "Failed to fetch states" + coEvery { getTaskStatesByProjectIdUseCase(projectId) } throws RuntimeException(errorMessage) + + // When + getAllTaskStatesUI.invoke(projectId) + + // Then + coVerifySequence { + getTaskStatesByProjectIdUseCase(projectId) + consoleIO.write("āŒ Failed to fetch states: $errorMessage") + } + } + + @Test + fun `should fetch states with correct project ID`() = runTest { + // Given + val differentProjectId = UUID.randomUUID() + coEvery { getTaskStatesByProjectIdUseCase(differentProjectId) } returns emptyList() + + // When + getAllTaskStatesUI.invoke(differentProjectId) + + // Then + coVerify { + getTaskStatesByProjectIdUseCase(differentProjectId) + } + } + + @Test + fun `should display states in the correct order`() = runTest { + // Given + val state1 = TaskState( + id = UUID.randomUUID(), + name = "To Do", + projectId = projectId + ) + val state2 = TaskState( + id = UUID.randomUUID(), + name = "In Progress", + projectId = projectId + ) + val state3 = TaskState( + id = UUID.randomUUID(), + name = "Done", + projectId = projectId + ) + val states = listOf(state1, state2, state3) + coEvery { getTaskStatesByProjectIdUseCase(projectId) } returns states + + // When + getAllTaskStatesUI.invoke(projectId) + + // Then + verifySequence { + consoleIO.write( + """ + ------------------------------------------------------- + | State Name: ${state1.name} + | State ID: ${state1.id} + ------------------------------------------------------- + """.trimIndent() + ) + consoleIO.write( + """ + ------------------------------------------------------- + | State Name: ${state2.name} + | State ID: ${state2.id} + ------------------------------------------------------- + """.trimIndent() + ) + consoleIO.write( + """ + ------------------------------------------------------- + | State Name: ${state3.name} + | State ID: ${state3.id} + ------------------------------------------------------- + """.trimIndent() + ) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/state/TaskStateUITest.kt b/src/test/kotlin/presentation/state/TaskStateUITest.kt new file mode 100644 index 0000000..522bee1 --- /dev/null +++ b/src/test/kotlin/presentation/state/TaskStateUITest.kt @@ -0,0 +1,138 @@ +package presentation.state + +import domain.models.Project +import domain.usecases.project.GetProjectByIdUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import presentation.project.GetAllProjectsUI +import java.util.* + +class TaskStateUITest { + private lateinit var getProjectByIdUseCase: GetProjectByIdUseCase + private lateinit var createTaskStateUI: CreateTaskStateUI + private lateinit var getAllTaskStatesUI: GetAllTaskStatesUI + private lateinit var editTaskStateUI: EditTaskStateUI + private lateinit var deleteTaskStateUI: DeleteTaskStateUI + private lateinit var getAllProjectsUI: GetAllProjectsUI + private lateinit var consoleIO: ConsoleIO + private lateinit var taskStateUI: TaskStateUI + + private val projectId = UUID.randomUUID() + private val validProjectIdString = projectId.toString() + private val invalidProjectIdString = "invalid-uuid" + + @BeforeEach + fun setUp() { + getProjectByIdUseCase = mockk() + createTaskStateUI = mockk(relaxed = true) + getAllTaskStatesUI = mockk(relaxed = true) + editTaskStateUI = mockk(relaxed = true) + deleteTaskStateUI = mockk(relaxed = true) + getAllProjectsUI = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + + taskStateUI = TaskStateUI( + getProjectByIdUseCase, + createTaskStateUI, + getAllTaskStatesUI, + editTaskStateUI, + deleteTaskStateUI, + getAllProjectsUI, + consoleIO + ) + + // Mock the extension function without using mockkStatic + mockkStatic(UUID::class) + every { UUID.fromString(validProjectIdString) } returns projectId + every { UUID.fromString(invalidProjectIdString) } throws IllegalArgumentException("Invalid UUID") + } + + @Test + fun `should handle non-existent project`() = runTest { + // Given + every { consoleIO.read() } returns validProjectIdString + coEvery { getProjectByIdUseCase(projectId) } throws RuntimeException("Project not found") + + // When + taskStateUI.invoke() + + // Then + coVerifySequence { + getAllProjectsUI() + consoleIO.write("Enter the project ID:") + consoleIO.read() + getProjectByIdUseCase(projectId) + consoleIO.write("āŒ No project exists. Add a new project to manage tasks. Please try again") + } + } + + @Test + fun `should navigate to create state`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf(validProjectIdString, "1") + coEvery { getProjectByIdUseCase(projectId) } returns mockk() + + // When + taskStateUI.invoke() + + // Then + coVerifySequence { + getAllProjectsUI() + consoleIO.write("Enter the project ID:") + consoleIO.read() + getProjectByIdUseCase(projectId) + getAllTaskStatesUI(projectId) + consoleIO.write(any()) // Menu options + consoleIO.read() + createTaskStateUI(projectId) + } + } + + @Test + fun `should navigate to edit state`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf(validProjectIdString, "2") + coEvery { getProjectByIdUseCase(projectId) } returns mockk() + + // When + taskStateUI.invoke() + + // Then + coVerify { + editTaskStateUI(projectId) + } + } + + @Test + fun `should navigate to delete state`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf(validProjectIdString, "3") + coEvery { getProjectByIdUseCase(projectId) } returns mockk() + + // When + taskStateUI.invoke() + + // Then + coVerify { + deleteTaskStateUI(projectId) + } + } + + @Test + fun `should navigate back`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf(validProjectIdString, "4") + coEvery { getProjectByIdUseCase(projectId) } returns mockk() + + // When + taskStateUI.invoke() + + // Then + verify { + consoleIO.write("Navigating back...") + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/sub_task/CalculateSubTaskPercentageUITest.kt b/src/test/kotlin/presentation/sub_task/CalculateSubTaskPercentageUITest.kt new file mode 100644 index 0000000..5494bd1 --- /dev/null +++ b/src/test/kotlin/presentation/sub_task/CalculateSubTaskPercentageUITest.kt @@ -0,0 +1,126 @@ +package presentation.sub_task + +import domain.models.Task +import domain.usecases.task.GetTaskByIdUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class CalculateSubTaskPercentageUITest { + private lateinit var getTaskByIdUseCase: GetTaskByIdUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var calculateSubTaskPercentageUI: CalculateSubTaskPercentageUI + private val taskId = UUID.randomUUID() + private val completionPercentage = 75.0 + + @BeforeEach + fun setUp() { + getTaskByIdUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + calculateSubTaskPercentageUI = CalculateSubTaskPercentageUI(getTaskByIdUseCase, consoleIO) + } + + @Test + fun `should calculate subtask percentage successfully`() = runTest { + // Given + val task = mockk() + every { consoleIO.readUUID() } returns taskId + coEvery { getTaskByIdUseCase(taskId) } returns task + every { task.calculateSubTaskCompletionPercentage() } returns completionPercentage + + // When + calculateSubTaskPercentageUI.invoke() + + // Then + coVerifySequence { + consoleIO.write("Please enter Task ID:") + consoleIO.readUUID() + getTaskByIdUseCase(taskId) + task.calculateSubTaskCompletionPercentage() + consoleIO.write("Task completed percentage: $completionPercentage") + } + } + + @Test + fun `should handle invalid task ID input`() = runTest { + // Given + every { consoleIO.readUUID() } returns null + + // When + calculateSubTaskPercentageUI.invoke() + + // Then + verifySequence { + consoleIO.write("Please enter Task ID:") + consoleIO.readUUID() + consoleIO.write("āŒ Invalid Task ID.") + } + + coVerify(exactly = 0) { + getTaskByIdUseCase(any()) + } + } + + @Test + fun `should handle task not found`() = runTest { + // Given + every { consoleIO.readUUID() } returns taskId + coEvery { getTaskByIdUseCase(taskId) } throws NoSuchElementException("Task not found") + // When + calculateSubTaskPercentageUI.invoke() + + // Then + coVerifySequence { + consoleIO.write("Please enter Task ID:") + consoleIO.readUUID() + getTaskByIdUseCase(taskId) + consoleIO.write("āŒ Failed to retrieve task: Task not found") + consoleIO.write("āŒ Task not found.") + } + + } + + @Test + fun `should handle exception when retrieving task`() = runTest { + // Given + val errorMessage = "Database connection failed" + every { consoleIO.readUUID() } returns taskId + coEvery { getTaskByIdUseCase(taskId) } throws RuntimeException(errorMessage) + + // When + calculateSubTaskPercentageUI.invoke() + + // Then + coVerifySequence { + consoleIO.write("Please enter Task ID:") + consoleIO.readUUID() + getTaskByIdUseCase(taskId) + consoleIO.write("āŒ Failed to retrieve task: $errorMessage") + consoleIO.write("āŒ Task not found.") + } + } + + @Test + fun `should display zero percent when task has no subtasks`() = runTest { + // Given + val task = mockk() + every { consoleIO.readUUID() } returns taskId + coEvery { getTaskByIdUseCase(taskId) } returns task + every { task.calculateSubTaskCompletionPercentage() } returns 0.0 + + // When + calculateSubTaskPercentageUI.invoke() + + // Then + coVerifySequence { + consoleIO.write("Please enter Task ID:") + consoleIO.readUUID() + getTaskByIdUseCase(taskId) + task.calculateSubTaskCompletionPercentage() + consoleIO.write("Task completed percentage: 0.0") + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/sub_task/CreateSubTaskUITest.kt b/src/test/kotlin/presentation/sub_task/CreateSubTaskUITest.kt new file mode 100644 index 0000000..a013a43 --- /dev/null +++ b/src/test/kotlin/presentation/sub_task/CreateSubTaskUITest.kt @@ -0,0 +1,183 @@ +package presentation.sub_task + +import domain.models.SubTask +import domain.models.Task +import domain.usecases.sub_task.CreateSubTaskUseCase +import domain.usecases.task.EditTaskUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class CreateSubTaskUITest { + private lateinit var createSubTaskUseCase: CreateSubTaskUseCase + private lateinit var editTaskUseCase: EditTaskUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var createSubTaskUI: CreateSubTaskUI + + private val taskId = UUID.randomUUID() + private val subTaskId = UUID.randomUUID() + private val taskTitle = "Main Task" + private val subTaskTitle = "Sub Task Title" + private val subTaskDescription = "Sub Task Description" + private val task = Task( + id = taskId, + title = taskTitle, + description = "Task Description", + projectId = UUID.randomUUID(), + stateId = UUID.randomUUID(), + subTasks = emptyList() + ) + + @BeforeEach + fun setUp() { + createSubTaskUseCase = mockk(relaxed = true) + editTaskUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + createSubTaskUI = CreateSubTaskUI(createSubTaskUseCase, editTaskUseCase, consoleIO) + + every { consoleIO.read() } returnsMany listOf(subTaskTitle, subTaskDescription) + + mockkStatic(UUID::class) + every { UUID.randomUUID() } returns subTaskId + } + + @Test + fun `should create sub task successfully`() = runTest { + // Given + coEvery { createSubTaskUseCase(any()) } returns true + coEvery { editTaskUseCase(any()) } returns true + + // When + createSubTaskUI.invoke(task) + + // Then + val expectedSubTask = SubTask( + id = subTaskId, + title = subTaskTitle, + description = subTaskDescription, + isCompleted = false, + parentTaskId = taskId + ) + + val expectedUpdatedTask = task.copy( + subTasks = task.subTasks + expectedSubTask + ) + + coVerifySequence { + consoleIO.write("Please enter sub task title:") + consoleIO.read() + consoleIO.write("Please enter sub task description:") + consoleIO.read() + createSubTaskUseCase(expectedSubTask) + consoleIO.write("āœ… sub task added successfully.") + editTaskUseCase(expectedUpdatedTask) + consoleIO.write("āœ… Task updated successfully.") + } + } + + @Test + fun `should handle empty sub task title and description`() = runTest { + // Given + val emptyTitle = "" + val emptyDescription = "" + every { consoleIO.read() } returnsMany listOf(emptyTitle, emptyDescription) + + // When + createSubTaskUI.invoke(task) + + // Then + val expectedSubTask = SubTask( + id = subTaskId, + title = emptyTitle, + description = emptyDescription, + isCompleted = false, + parentTaskId = taskId + ) + + coVerify { + consoleIO.write("Please enter sub task title:") + consoleIO.read() + consoleIO.write("Please enter sub task description:") + consoleIO.read() + createSubTaskUseCase(expectedSubTask) + } + } + + @Test + fun `should handle exception when creating sub task`() = runTest { + // Given + val errorMessage = "Failed to create sub task" + coEvery { createSubTaskUseCase(any()) } throws RuntimeException(errorMessage) + + // When + createSubTaskUI.invoke(task) + + // Then + coVerifySequence { + consoleIO.write("Please enter sub task title:") + consoleIO.read() + consoleIO.write("Please enter sub task description:") + consoleIO.read() + createSubTaskUseCase(any()) + consoleIO.write("āŒ failed to add sub task: $errorMessage") + } + } + + @Test + fun `should handle exception when updating task`() = runTest { + // Given + val errorMessage = "Failed to update task" + coEvery { createSubTaskUseCase(any()) } returns true + coEvery { editTaskUseCase(any()) } throws RuntimeException(errorMessage) + + // When + createSubTaskUI.invoke(task) + + // Then + coVerifySequence { + consoleIO.write("Please enter sub task title:") + consoleIO.read() + consoleIO.write("Please enter sub task description:") + consoleIO.read() + createSubTaskUseCase(any()) + consoleIO.write("āœ… sub task added successfully.") + editTaskUseCase(any()) + consoleIO.write("āŒ Failed to update task: $errorMessage") + } + } + + @Test + fun `should add sub task to existing task with sub tasks`() = runTest { + // Given + val existingSubTask = SubTask( + id = UUID.randomUUID(), + title = "Existing SubTask", + description = "Description", + isCompleted = true, + parentTaskId = taskId + ) + + val taskWithSubTasks = task.copy(subTasks = listOf(existingSubTask)) + val capturedTask = slot() + + coEvery { createSubTaskUseCase(any()) } returns true + coEvery { editTaskUseCase(capture(capturedTask)) } returns true + + // When + createSubTaskUI.invoke(taskWithSubTasks) + + // Then + coVerify { + createSubTaskUseCase(any()) + editTaskUseCase(any()) + } + + assert(capturedTask.captured.subTasks.size == 2) + assert(capturedTask.captured.subTasks[0] == existingSubTask) + assert(capturedTask.captured.subTasks[1].title == subTaskTitle) + assert(capturedTask.captured.subTasks[1].description == subTaskDescription) + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/sub_task/DeleteSubTaskUITest.kt b/src/test/kotlin/presentation/sub_task/DeleteSubTaskUITest.kt new file mode 100644 index 0000000..7ce0dc6 --- /dev/null +++ b/src/test/kotlin/presentation/sub_task/DeleteSubTaskUITest.kt @@ -0,0 +1,157 @@ +package presentation.sub_task + +import domain.models.SubTask +import domain.models.Task +import domain.usecases.sub_task.DeleteSubTaskUseCase +import domain.usecases.task.EditTaskUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class DeleteSubTaskUITest { + private lateinit var deleteSubTaskUseCase: DeleteSubTaskUseCase + private lateinit var editTaskUseCase: EditTaskUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var deleteSubTaskUI: DeleteSubTaskUI + + private val taskId = UUID.randomUUID() + private val subTaskId1 = UUID.randomUUID() + private val subTaskId2 = UUID.randomUUID() + + private val subTask1 = SubTask(id = subTaskId1, title = "SubTask 1", description = "", isCompleted = false, parentTaskId = taskId) + private val subTask2 = SubTask(id = subTaskId2, title = "SubTask 2", description = "", isCompleted = true, parentTaskId = taskId) + private lateinit var task: Task + + @BeforeEach + fun setUp() { + deleteSubTaskUseCase = mockk(relaxed = true) + editTaskUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + deleteSubTaskUI = DeleteSubTaskUI(deleteSubTaskUseCase, editTaskUseCase, consoleIO) + + task = Task( + id = taskId, + title = "Test Task", + description = "Description", + projectId = UUID.randomUUID(), + stateId = UUID.randomUUID(), + subTasks = listOf(subTask1, subTask2) + ) + + every { consoleIO.readUUID() } returns subTaskId1 + } + + @Test + fun `should delete sub-task successfully`() = runTest { + // Given + coEvery { deleteSubTaskUseCase(any()) } returns true + coEvery { editTaskUseCase(any()) } returns true + + // When + deleteSubTaskUI.invoke(task) + + // Then + val expectedUpdatedTask = task.copy( + subTasks = listOf(subTask2) + ) + + coVerifySequence { + consoleIO.write(any()) + consoleIO.write("Enter sub-task ID:") + consoleIO.readUUID() + deleteSubTaskUseCase(subTaskId1) + consoleIO.write("āœ… Sub Task deleted successfully.") + editTaskUseCase(expectedUpdatedTask) + consoleIO.write("āœ… Task updated successfully.") + } + } + + @Test + fun `should notify when there are no sub-tasks to edit`() = runTest { + // Given + val emptyTask = task.copy(subTasks = emptyList()) + + // When + deleteSubTaskUI.invoke(emptyTask) + + // Then + verify(exactly = 1) { + consoleIO.write("āš ļø No sub-tasks available to edit.") + } + coVerify(exactly = 0) { + deleteSubTaskUseCase(any()) + editTaskUseCase(any()) + } + } + + @Test + fun `should continue prompting until a valid sub-task ID is entered`() = runTest { + // Given + val invalidId = UUID.randomUUID() + every { consoleIO.readUUID() } returnsMany listOf(invalidId, invalidId, subTaskId1) + coEvery { deleteSubTaskUseCase(any()) } returns true + coEvery { editTaskUseCase(any()) } returns true + + // When + deleteSubTaskUI.invoke(task) + + // Then + verifySequence { + consoleIO.write(any()) // Initial instructions + consoleIO.write("Enter sub-task ID:") + consoleIO.readUUID() + consoleIO.write("āŒ Invalid sub-task ID. Please try again.") + consoleIO.write("Enter sub-task ID:") + consoleIO.readUUID() + consoleIO.write("āŒ Invalid sub-task ID. Please try again.") + consoleIO.write("Enter sub-task ID:") + consoleIO.readUUID() + consoleIO.write("āœ… Sub Task deleted successfully.") + consoleIO.write("āœ… Task updated successfully.") + } + coVerify { + deleteSubTaskUseCase(subTaskId1) + } + } + + @Test + fun `should handle exception when deleting sub-task`() = runTest { + // Given + val errorMessage = "Failed to delete sub-task" + coEvery { deleteSubTaskUseCase(any()) } throws RuntimeException(errorMessage) + + // When + deleteSubTaskUI.invoke(task) + + // Then + coVerify { + deleteSubTaskUseCase(subTaskId1) + consoleIO.write("āŒ Failed to delete Sub task: $errorMessage") + } + coVerify(exactly = 0) { + editTaskUseCase(any()) + } + } + + @Test + fun `should handle exception when updating task`() = runTest { + // Given + val errorMessage = "Failed to update task" + coEvery { deleteSubTaskUseCase(any()) } returns true + coEvery { editTaskUseCase(any()) } throws RuntimeException(errorMessage) + + // When + deleteSubTaskUI.invoke(task) + + // Then + coVerify { + deleteSubTaskUseCase(subTaskId1) + consoleIO.write("āœ… Sub Task deleted successfully.") + editTaskUseCase(any()) + consoleIO.write("āŒ Failed to update task: $errorMessage") + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/sub_task/EditSubTaskUITest.kt b/src/test/kotlin/presentation/sub_task/EditSubTaskUITest.kt new file mode 100644 index 0000000..e286e76 --- /dev/null +++ b/src/test/kotlin/presentation/sub_task/EditSubTaskUITest.kt @@ -0,0 +1,234 @@ +package presentation.sub_task + +import domain.models.SubTask +import domain.models.Task +import domain.usecases.sub_task.UpdateSubTaskUseCase +import domain.usecases.task.EditTaskUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class EditSubTaskUITest { + private lateinit var updateSubTaskUseCase: UpdateSubTaskUseCase + private lateinit var editTaskUseCase: EditTaskUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var editSubTaskUI: EditSubTaskUI + + private val taskId = UUID.randomUUID() + private val subTaskId = UUID.randomUUID() + private val subTaskTitle = "Test SubTask" + private val subTaskDescription = "Test Description" + private val subTaskIsCompleted = false + + private val newSubTaskTitle = "Updated SubTask" + private val newSubTaskDescription = "Updated Description" + private val newSubTaskIsCompleted = true + + private lateinit var subTask: SubTask + private lateinit var task: Task + + @BeforeEach + fun setUp() { + updateSubTaskUseCase = mockk(relaxed = true) + editTaskUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + editSubTaskUI = EditSubTaskUI(updateSubTaskUseCase, editTaskUseCase, consoleIO) + + subTask = SubTask( + id = subTaskId, + title = subTaskTitle, + description = subTaskDescription, + isCompleted = subTaskIsCompleted, + parentTaskId = taskId + ) + + task = Task( + id = taskId, + title = "Task Title", + description = "Task Description", + projectId = UUID.randomUUID(), + stateId = UUID.randomUUID(), + subTasks = listOf(subTask) + ) + } + + @Test + fun `should show warning when no sub-tasks available`() = runTest { + // Given + val emptyTask = task.copy(subTasks = emptyList()) + + // When + editSubTaskUI.invoke(emptyTask) + + // Then + verify { + consoleIO.write("āš ļø No sub-tasks available to edit.") + } + } + + @Test + fun `should edit sub-task successfully`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf( + subTaskId.toString(), + newSubTaskTitle, + newSubTaskDescription, + newSubTaskIsCompleted.toString(), + "yes" + ) + every { consoleIO.readUUID() } returns subTaskId + + coEvery { updateSubTaskUseCase.invoke(ofType(SubTask::class)) } returns true + coEvery { editTaskUseCase.invoke(ofType(Task::class)) } returns true + + // When + editSubTaskUI.invoke(task) + + // Then + coVerify { + updateSubTaskUseCase.invoke(ofType(SubTask::class)) + editTaskUseCase.invoke(ofType(Task::class)) + } + + verify { + consoleIO.write(match { it.contains("Available Sub-Tasks for Task") }) + consoleIO.write("Enter sub-task ID:") + consoleIO.write("Enter new title [Test SubTask]: (press Enter to keep current value)") + consoleIO.write("Enter new description [Test Description]: (press Enter to keep current value)") + consoleIO.write("Is the task completed? (true/false) [false]: (press Enter to keep current value)") + consoleIO.write("Do you want to update the sub-task? (yes/no):") + consoleIO.write("āœ… Sub-task updated successfully.") + consoleIO.write("āœ… Task updated successfully.") + } + } + + @Test + fun `should handle invalid sub-task ID`() = runTest { + // Given + val invalidId = UUID.randomUUID() + val validId = subTaskId + + every { consoleIO.readUUID() } returnsMany listOf(invalidId, validId) + every { consoleIO.read() } returnsMany listOf( + invalidId.toString(), + validId.toString(), + newSubTaskTitle, + newSubTaskDescription, + newSubTaskIsCompleted.toString(), + "yes" + ) + + // When + editSubTaskUI.invoke(task) + + // Then + verify { + consoleIO.write("āŒ Invalid sub-task ID. Please try again.") + } + } + + @Test + fun `should handle invalid boolean input`() = runTest { + // Given + every { consoleIO.readUUID() } returns subTaskId + every { consoleIO.read() } returnsMany listOf( + subTaskId.toString(), + newSubTaskTitle, + newSubTaskDescription, + "invalid", // Invalid boolean + "true", // Correct value + "yes" + ) + + // When + editSubTaskUI.invoke(task) + + // Then + verify { + consoleIO.write("āŒ Please enter 'true' or 'false'.") + } + } + + @Test + fun `should cancel update when user declines`() = runTest { + // Given + every { consoleIO.readUUID() } returns subTaskId + every { consoleIO.read() } returnsMany listOf( + subTaskId.toString(), + newSubTaskTitle, + newSubTaskDescription, + newSubTaskIsCompleted.toString(), + "no" // User declines update + ) + + // When + editSubTaskUI.invoke(task) + + // Then + verify { + consoleIO.write("āŒ Update cancelled.") + } + + coVerify(exactly = 0) { + updateSubTaskUseCase.invoke(ofType(SubTask::class)) + editTaskUseCase.invoke(ofType(Task::class)) + } + } + + @Test + fun `should handle exception when updating sub-task`() = runTest { + // Given + every { consoleIO.readUUID() } returns subTaskId + every { consoleIO.read() } returnsMany listOf( + subTaskId.toString(), + newSubTaskTitle, + newSubTaskDescription, + newSubTaskIsCompleted.toString(), + "yes" + ) + + val errorMessage = "Failed to update sub-task" + coEvery { updateSubTaskUseCase.invoke(ofType(SubTask::class)) } throws RuntimeException(errorMessage) + + // When + editSubTaskUI.invoke(task) + + // Then + verify { + consoleIO.write("āŒ Failed to update sub-task: $errorMessage") + } + + coVerify(exactly = 0) { + editTaskUseCase.invoke(ofType(Task::class)) + } + } + + @Test + fun `should handle exception when updating task`() = runTest { + // Given + every { consoleIO.readUUID() } returns subTaskId + every { consoleIO.read() } returnsMany listOf( + subTaskId.toString(), + newSubTaskTitle, + newSubTaskDescription, + newSubTaskIsCompleted.toString(), + "yes" + ) + + val errorMessage = "Failed to update task" + coEvery { updateSubTaskUseCase.invoke(ofType(SubTask::class)) } returns true + coEvery { editTaskUseCase.invoke(ofType(Task::class)) } throws RuntimeException(errorMessage) + + // When + editSubTaskUI.invoke(task) + + // Then + verify { + consoleIO.write("āœ… Sub-task updated successfully.") + consoleIO.write("āŒ Failed to update task: $errorMessage") + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/sub_task/GetSubTasksByTaskIdUITest.kt b/src/test/kotlin/presentation/sub_task/GetSubTasksByTaskIdUITest.kt new file mode 100644 index 0000000..803b1af --- /dev/null +++ b/src/test/kotlin/presentation/sub_task/GetSubTasksByTaskIdUITest.kt @@ -0,0 +1,143 @@ +package presentation.sub_task + +import domain.models.SubTask +import domain.usecases.sub_task.GetSubTasksByTaskIdUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class GetSubTasksByTaskIdUITest { + private lateinit var getSubTasksByTaskIdUseCase: GetSubTasksByTaskIdUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var getSubTasksByTaskIdUI: GetSubTasksByTaskIdUI + private val taskId = UUID.randomUUID() + private val subTaskId = UUID.randomUUID() + + @BeforeEach + fun setUp() { + getSubTasksByTaskIdUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + getSubTasksByTaskIdUI = GetSubTasksByTaskIdUI(getSubTasksByTaskIdUseCase, consoleIO) + + every { consoleIO.readUUID() } returns taskId + } + + @Test + fun `should fetch and display sub tasks successfully`() = runTest { + // Given + val subTasks = listOf( + SubTask( + id = subTaskId, + title = "Test SubTask", + description = "Test Description", + isCompleted = false, + parentTaskId = taskId + ) + ) + coEvery { getSubTasksByTaskIdUseCase(taskId) } returns subTasks + + // When + getSubTasksByTaskIdUI.invoke() + + // Then + coVerifySequence { + consoleIO.write("Please enter task id:") + consoleIO.readUUID() + getSubTasksByTaskIdUseCase(taskId) + consoleIO.write(match { it.contains("Sub Task Name: Test SubTask") && + it.contains("Sub Description: Test Description") && + it.contains("Sub Task ID: $subTaskId") && + it.contains("Sub Task State ID: false") && + it.contains("Task ID: $taskId") }) + } + } + + @Test + fun `should display message when no sub tasks are found`() = runTest { + // Given + coEvery { getSubTasksByTaskIdUseCase(taskId) } returns emptyList() + + // When + getSubTasksByTaskIdUI.invoke() + + // Then + coVerifySequence { + consoleIO.write("Please enter task id:") + consoleIO.readUUID() + getSubTasksByTaskIdUseCase(taskId) + consoleIO.write("ā„¹ļø No sub tasks found for this Task.") + } + } + + @Test + fun `should handle invalid task id input`() = runTest { + // Given + every { consoleIO.readUUID() } returns null + + // When + getSubTasksByTaskIdUI.invoke() + + // Then + coVerifySequence { + consoleIO.write("Please enter task id:") + consoleIO.readUUID() + consoleIO.write("āŒ Invalid task sub task.") + } + coVerify(exactly = 0) { getSubTasksByTaskIdUseCase(any()) } + } + + @Test + fun `should handle exception when fetching sub tasks`() = runTest { + // Given + val errorMessage = "Failed to fetch sub tasks" + coEvery { getSubTasksByTaskIdUseCase(taskId) } throws RuntimeException(errorMessage) + + // When + getSubTasksByTaskIdUI.invoke() + + // Then + coVerifySequence { + consoleIO.write("Please enter task id:") + consoleIO.readUUID() + getSubTasksByTaskIdUseCase(taskId) + consoleIO.write("āŒ Failed to fetch sub tasks: $errorMessage") + } + } + + @Test + fun `should display multiple sub tasks correctly`() = runTest { + // Given + val subTasks = listOf( + SubTask( + id = UUID.randomUUID(), + title = "First SubTask", + description = "First Description", + isCompleted = false, + parentTaskId = taskId + ), + SubTask( + id = UUID.randomUUID(), + title = "Second SubTask", + description = "Second Description", + isCompleted = true, + parentTaskId = taskId + ) + ) + coEvery { getSubTasksByTaskIdUseCase(taskId) } returns subTasks + + // When + getSubTasksByTaskIdUI.invoke() + + // Then + coVerify { + consoleIO.write("Please enter task id:") + consoleIO.readUUID() + getSubTasksByTaskIdUseCase(taskId) + consoleIO.write(match { it.contains("Sub Task Name: First SubTask") }) + consoleIO.write(match { it.contains("Sub Task Name: Second SubTask") }) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/sub_task/SubTaskUITest.kt b/src/test/kotlin/presentation/sub_task/SubTaskUITest.kt new file mode 100644 index 0000000..c9b205f --- /dev/null +++ b/src/test/kotlin/presentation/sub_task/SubTaskUITest.kt @@ -0,0 +1,198 @@ +package presentation.sub_task + +import domain.models.Task +import domain.models.SubTask +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class SubTaskUITest { + private lateinit var createSubTaskUI: CreateSubTaskUI + private lateinit var deleteSubTaskUI: DeleteSubTaskUI + private lateinit var editSubTaskUI: EditSubTaskUI + private lateinit var getSubTasksByTaskIdUI: GetSubTasksByTaskIdUI + private lateinit var consoleIO: ConsoleIO + private lateinit var subTaskUI: SubTaskUI + private lateinit var task: Task + + @BeforeEach + fun setUp() { + createSubTaskUI = mockk(relaxed = true) + deleteSubTaskUI = mockk(relaxed = true) + editSubTaskUI = mockk(relaxed = true) + getSubTasksByTaskIdUI = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + + subTaskUI = SubTaskUI( + createSubTaskUI, + deleteSubTaskUI, + editSubTaskUI, + getSubTasksByTaskIdUI, + consoleIO + ) + + task = Task( + id = UUID.randomUUID(), + title = "Test Task", + description = "Test Description", + projectId = UUID.randomUUID(), + stateId = UUID.randomUUID(), + subTasks = listOf( + SubTask( + id = UUID.randomUUID(), + title = "Test SubTask", + description = "Test SubTask Description", + isCompleted = false, + parentTaskId = UUID.randomUUID() + ) + ) + ) + } + + @Test + fun `should display task details correctly`() = runTest { + // Given + every { consoleIO.read() } returns "5" + + // When + subTaskUI.invoke(task) + + // Then + verify { + consoleIO.write(match { it.contains("Task Details") && it.contains(task.title) }) + consoleIO.write(match { it.contains("Select an operation:") }) + } + } + + @Test + fun `should navigate to create sub task when option 1 is selected`() = runTest { + // Given + every { consoleIO.read() } returns "1" + + // When + subTaskUI.invoke(task) + + // Then + coVerify { + createSubTaskUI.invoke(task) + } + } + + @Test + fun `should navigate to edit sub task when option 2 is selected`() = runTest { + // Given + every { consoleIO.read() } returns "2" + + // When + subTaskUI.invoke(task) + + // Then + coVerify { + editSubTaskUI.invoke(task) + } + } + + @Test + fun `should navigate to delete sub task when option 3 is selected`() = runTest { + // Given + every { consoleIO.read() } returns "3" + + // When + subTaskUI.invoke(task) + + // Then + coVerify { + deleteSubTaskUI.invoke(task) + } + } + + @Test + fun `should navigate to get sub tasks when option 4 is selected`() = runTest { + // Given + every { consoleIO.read() } returns "4" + + // When + subTaskUI.invoke(task) + + // Then + coVerify { + getSubTasksByTaskIdUI.invoke() + } + } + + @Test + fun `should navigate back when option 5 is selected`() = runTest { + // Given + every { consoleIO.read() } returns "5" + + // When + subTaskUI.invoke(task) + + // Then + verify { + consoleIO.write("Navigating back...") + } + } + + @Test + fun `should show error message and retry when invalid option is selected`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf("invalid", "5") + + // When + subTaskUI.invoke(task) + + // Then + verifySequence { + consoleIO.write(any()) + consoleIO.write(any()) + consoleIO.read() + consoleIO.write("āŒ Invalid option.") + consoleIO.write(any()) + consoleIO.write(any()) + consoleIO.read() + consoleIO.write("Navigating back...") + } + } + + @Test + fun `should display completed and pending sub-tasks correctly`() = runTest { + // Given + val completedSubTask = SubTask( + id = UUID.randomUUID(), + title = "Completed SubTask", + description = "Completed SubTask Description", + isCompleted = true, + parentTaskId = task.id + ) + val pendingSubTask = SubTask( + id = UUID.randomUUID(), + title = "Pending SubTask", + description = "Pending SubTask Description", + isCompleted = false, + parentTaskId = task.id + ) + + val taskWithMixedSubTasks = task.copy( + subTasks = listOf(completedSubTask, pendingSubTask) + ) + + every { consoleIO.read() } returns "5" + + // When + subTaskUI.invoke(taskWithMixedSubTasks) + + // Then + verify { + consoleIO.write(match { + it.contains("Completed") && + it.contains("Pending") && + it.contains(completedSubTask.title) && + it.contains(pendingSubTask.title) + }) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/task/CreateTaskUITest.kt b/src/test/kotlin/presentation/task/CreateTaskUITest.kt new file mode 100644 index 0000000..bf9e888 --- /dev/null +++ b/src/test/kotlin/presentation/task/CreateTaskUITest.kt @@ -0,0 +1,198 @@ +package presentation.task + +import domain.models.Task +import domain.models.TaskState +import domain.usecases.task.AddTaskUseCase +import domain.usecases.task_state.GetTaskStatesByProjectIdUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class CreateTaskUITest { + private lateinit var addTaskUseCase: AddTaskUseCase + private lateinit var getTaskStatesByProjectIdUseCase: GetTaskStatesByProjectIdUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var createTaskUI: CreateTaskUI + + private val projectId = UUID.randomUUID() + private val stateId = UUID.randomUUID() + private val taskName = "Implement Feature" + private val taskDescription = "Implement the new feature" + + @BeforeEach + fun setUp() { + addTaskUseCase = mockk(relaxed = true) + getTaskStatesByProjectIdUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + createTaskUI = CreateTaskUI(addTaskUseCase, getTaskStatesByProjectIdUseCase, consoleIO) + + every { consoleIO.read() } returnsMany listOf(taskName, taskDescription, stateId.toString()) + + coEvery { getTaskStatesByProjectIdUseCase(any()) } returns listOf( + TaskState(id = stateId, name = "To Do", projectId = projectId) + ) + } + + @Test + fun `should create task successfully`() = runTest { + // Given + coEvery { addTaskUseCase(any()) } returns true + + // When + createTaskUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the task name:") + consoleIO.read() // Task name + consoleIO.write("Please enter the task description:") + consoleIO.read() // Task description + getTaskStatesByProjectIdUseCase(projectId) + consoleIO.write("Available task states:") + consoleIO.write("State ID: $stateId, State Name: To Do") + consoleIO.write("Please enter the state ID:") + consoleIO.read() // State ID + addTaskUseCase(any()) + consoleIO.write("āœ… Task created successfully.") + } + + val taskSlot = slot() + coVerify { addTaskUseCase(capture(taskSlot)) } + + with(taskSlot.captured) { + assert(title == taskName) + assert(description == taskDescription) + assert(projectId == this@CreateTaskUITest.projectId) + assert(stateId == this@CreateTaskUITest.stateId) + assert(subTasks.isEmpty()) + } + } + + @Test + fun `should handle empty states list`() = runTest { + // Given + coEvery { getTaskStatesByProjectIdUseCase(any()) } returns emptyList() + + // When + createTaskUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the task name:") + consoleIO.read() + consoleIO.write("Please enter the task description:") + consoleIO.read() + getTaskStatesByProjectIdUseCase(projectId) + consoleIO.write("ā„¹ļø No task states found for this project.") + } + + coVerify(exactly = 0) { + addTaskUseCase(any()) + } + } + + @Test + fun `should handle exception when fetching states`() = runTest { + // Given + val errorMessage = "Failed to fetch states" + coEvery { getTaskStatesByProjectIdUseCase(any()) } throws RuntimeException(errorMessage) + + // When + createTaskUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the task name:") + consoleIO.read() + consoleIO.write("Please enter the task description:") + consoleIO.read() + getTaskStatesByProjectIdUseCase(projectId) + consoleIO.write("āŒ Failed to fetch task states: $errorMessage") + } + + coVerify(exactly = 0) { + addTaskUseCase(any()) + } + } + + @Test + fun `should handle exception when adding task`() = runTest { + // Given + val errorMessage = "Failed to add task" + coEvery { addTaskUseCase(any()) } throws RuntimeException(errorMessage) + + // When + createTaskUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the task name:") + consoleIO.read() + consoleIO.write("Please enter the task description:") + consoleIO.read() + getTaskStatesByProjectIdUseCase(projectId) + consoleIO.write("Available task states:") + consoleIO.write("State ID: $stateId, State Name: To Do") + consoleIO.write("Please enter the state ID:") + consoleIO.read() + addTaskUseCase(any()) + consoleIO.write("āŒ Failed to create task: $errorMessage") + } + } + + @Test + fun `should create task with correct project ID`() = runTest { + // Given + val differentProjectId = UUID.randomUUID() + val capturedTask = slot() + coEvery { addTaskUseCase(capture(capturedTask)) } returns true + + // When + createTaskUI.invoke(differentProjectId) + + // Then + coVerify { + getTaskStatesByProjectIdUseCase(differentProjectId) + addTaskUseCase(any()) + } + assert(capturedTask.captured.projectId == differentProjectId) + assert(capturedTask.captured.title == taskName) + assert(capturedTask.captured.description == taskDescription) + } + + @Test + fun `should handle invalid state ID input`() = runTest { + // Given + val invalidStateId = "invalid-uuid" + every { consoleIO.read() } returnsMany listOf(taskName, taskDescription, invalidStateId) + + try { + // When + createTaskUI.invoke(projectId) + + assert(false) { "Expected exception was not thrown" } + } catch (e: IllegalArgumentException) { + // Then + assert(e.message?.contains("invalid-uuid") == true) + } + + coVerify { + consoleIO.write("Please enter the task name:") + consoleIO.read() + consoleIO.write("Please enter the task description:") + consoleIO.read() + getTaskStatesByProjectIdUseCase(projectId) + consoleIO.write("Available task states:") + consoleIO.write("State ID: $stateId, State Name: To Do") + consoleIO.write("Please enter the state ID:") + consoleIO.read() + } + + coVerify(exactly = 0) { + addTaskUseCase(any()) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/task/DeleteTaskUITest.kt b/src/test/kotlin/presentation/task/DeleteTaskUITest.kt new file mode 100644 index 0000000..5203c86 --- /dev/null +++ b/src/test/kotlin/presentation/task/DeleteTaskUITest.kt @@ -0,0 +1,115 @@ +package presentation.task + +import domain.usecases.task.DeleteTaskUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class DeleteTaskUITest { + private lateinit var deleteTaskUseCase: DeleteTaskUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var deleteTaskUI: DeleteTaskUI + private val taskId = UUID.randomUUID() + + @BeforeEach + fun setUp() { + deleteTaskUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + deleteTaskUI = DeleteTaskUI(deleteTaskUseCase, consoleIO) + + every { consoleIO.readUUID() } returns taskId + } + + @Test + fun `should delete task successfully`() = runTest { + // Given + coEvery { deleteTaskUseCase(any()) } returns true + + // When + deleteTaskUI.invoke() + + // Then + coVerifySequence { + consoleIO.write("Please enter task ID:") + consoleIO.readUUID() + deleteTaskUseCase(taskId) + consoleIO.write("āœ… Task deleted successfully.") + } + } + + @Test + fun `should handle invalid task ID`() = runTest { + // Given + every { consoleIO.readUUID() } returns null + + // When + deleteTaskUI.invoke() + + // Then + coVerifySequence { + consoleIO.write("Please enter task ID:") + consoleIO.readUUID() + consoleIO.write("āŒ Invalid task ID.") + } + + coVerify(exactly = 0) { + deleteTaskUseCase(any()) + } + } + + @Test + fun `should handle exception when deleting task`() = runTest { + // Given + val errorMessage = "Task not found" + coEvery { deleteTaskUseCase(any()) } throws RuntimeException(errorMessage) + + // When + deleteTaskUI.invoke() + + // Then + coVerifySequence { + consoleIO.write("Please enter task ID:") + consoleIO.readUUID() + deleteTaskUseCase(taskId) + consoleIO.write("āŒ Failed to delete task: $errorMessage") + } + } + + @Test + fun `should call delete task use case with correct task ID`() = runTest { + // Given + val differentTaskId = UUID.randomUUID() + every { consoleIO.readUUID() } returns differentTaskId + val capturedTaskId = slot() + coEvery { deleteTaskUseCase(capture(capturedTaskId)) } returns true + + // When + deleteTaskUI.invoke() + + // Then + coVerify { + deleteTaskUseCase(any()) + } + assert(capturedTaskId.captured == differentTaskId) + } + + @Test + fun `should handle null response from delete task use case`() = runTest { + // Given + coEvery { deleteTaskUseCase(any()) } returns true + + // When + deleteTaskUI.invoke() + + // Then + coVerifySequence { + consoleIO.write("Please enter task ID:") + consoleIO.readUUID() + deleteTaskUseCase(taskId) + consoleIO.write("āœ… Task deleted successfully.") + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/task/EditTaskUITest.kt b/src/test/kotlin/presentation/task/EditTaskUITest.kt new file mode 100644 index 0000000..ede08e9 --- /dev/null +++ b/src/test/kotlin/presentation/task/EditTaskUITest.kt @@ -0,0 +1,221 @@ +package presentation.task + +import domain.models.Task +import domain.usecases.task.EditTaskUseCase +import domain.usecases.task.GetTaskByIdUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import presentation.sub_task.SubTaskUI +import java.util.* + +class EditTaskUITest { + private lateinit var getTaskByIdUseCase: GetTaskByIdUseCase + private lateinit var editTaskUseCase: EditTaskUseCase + private lateinit var subTaskUI: SubTaskUI + private lateinit var consoleIO: ConsoleIO + private lateinit var editTaskUI: EditTaskUI + + private val projectId = UUID.randomUUID() + private val taskId = UUID.randomUUID() + private val stateId = UUID.randomUUID() + private val task = Task( + id = taskId, + title = "Original Task", + description = "Original Description", + projectId = projectId, + stateId = UUID.randomUUID(), + subTasks = emptyList() + ) + + @BeforeEach + fun setUp() { + getTaskByIdUseCase = mockk() + editTaskUseCase = mockk(relaxed = true) + subTaskUI = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + editTaskUI = EditTaskUI(getTaskByIdUseCase, editTaskUseCase, subTaskUI, consoleIO) + + every { consoleIO.read() } returns "1" + every { consoleIO.readUUID() } returns taskId + coEvery { getTaskByIdUseCase(any()) } returns task + } + + @Test + fun `should edit task details successfully`() = runTest { + // Given + val newTaskName = "New Task Name" + val newTaskDescription = "New Task Description" + + every { consoleIO.read() } returnsMany listOf("1", newTaskName, newTaskDescription) + every { consoleIO.readUUID() } returnsMany listOf(taskId, stateId) + + // When + editTaskUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the task ID:") + consoleIO.readUUID() + getTaskByIdUseCase(taskId) + consoleIO.write("What do you want to edit?\n1. Task details\n2. Subtasks\n3. Back") + consoleIO.read() + consoleIO.write("Please enter the state ID:") + consoleIO.readUUID() + consoleIO.write("Please enter the new task name:") + consoleIO.read() + consoleIO.write("Please enter the new task description:") + consoleIO.read() + editTaskUseCase(withArg { + assert(it.id == taskId) + assert(it.title == newTaskName) + assert(it.description == newTaskDescription) + assert(it.stateId == stateId) + assert(it.projectId == projectId) + }) + consoleIO.write("āœ… Task updated successfully.") + } + } + + @Test + fun `should handle invalid task ID input`() = runTest { + // Given + every { consoleIO.readUUID() } returns null + + // When + editTaskUI.invoke(projectId) + + // Then + verifySequence { + consoleIO.write("Please enter the task ID:") + consoleIO.readUUID() + consoleIO.write("āŒ Invalid task ID.") + } + + coVerify(exactly = 0) { getTaskByIdUseCase(any()) } + } + + @Test + fun `should handle task not found`() = runTest { + // Given + coEvery { getTaskByIdUseCase(any()) } throws RuntimeException("Task not found") + + // When + editTaskUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the task ID:") + consoleIO.readUUID() + getTaskByIdUseCase(taskId) + consoleIO.write("āŒ Failed to retrieve task: Task not found") + consoleIO.write("āŒ Task not found.") + } + } + + @Test + fun `should navigate to subtasks menu`() = runTest { + // Given + every { consoleIO.read() } returns "2" + + // When + editTaskUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the task ID:") + consoleIO.readUUID() + getTaskByIdUseCase(taskId) + consoleIO.write("What do you want to edit?\n1. Task details\n2. Subtasks\n3. Back") + consoleIO.read() + subTaskUI(task) + } + } + + @Test + fun `should navigate back when option 3 is selected`() = runTest { + // Given + every { consoleIO.read() } returns "3" + + // When + editTaskUI.invoke(projectId) + + // Then + verifySequence { + consoleIO.write("Please enter the task ID:") + consoleIO.readUUID() + consoleIO.write("What do you want to edit?\n1. Task details\n2. Subtasks\n3. Back") + consoleIO.read() + consoleIO.write("Navigating back...") + } + } + + @Test + fun `should handle invalid menu option`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf("invalid", "3") + + // When + editTaskUI.invoke(projectId) + + // Then + coVerifySequence { + consoleIO.write("Please enter the task ID:") + consoleIO.readUUID() + getTaskByIdUseCase(taskId) + consoleIO.write("What do you want to edit?\n1. Task details\n2. Subtasks\n3. Back") + consoleIO.read() + consoleIO.write("āŒ Invalid choice.") + consoleIO.write("Please enter the task ID:") + consoleIO.readUUID() + getTaskByIdUseCase(taskId) + consoleIO.write("What do you want to edit?\n1. Task details\n2. Subtasks\n3. Back") + consoleIO.read() + consoleIO.write("Navigating back...") + } + } + + @Test + fun `should handle invalid state ID when editing task`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf("1", "New Name", "New Description") + every { consoleIO.readUUID() } returnsMany listOf(taskId, null) + + // When + editTaskUI.invoke(projectId) + + // Then + verifySequence { + consoleIO.write("Please enter the task ID:") + consoleIO.readUUID() + consoleIO.write("What do you want to edit?\n1. Task details\n2. Subtasks\n3. Back") + consoleIO.read() + consoleIO.write("Please enter the state ID:") + consoleIO.readUUID() + consoleIO.write("Please enter the new task name:") + consoleIO.read() + consoleIO.write("Please enter the new task description:") + consoleIO.read() + consoleIO.write("āŒ Invalid task or state ID.") + } + } + + @Test + fun `should handle exception when updating task`() = runTest { + // Given + val errorMessage = "Failed to update task" + every { consoleIO.read() } returnsMany listOf("1", "New Name", "New Description") + every { consoleIO.readUUID() } returnsMany listOf(taskId, stateId) + coEvery { editTaskUseCase(any()) } throws RuntimeException(errorMessage) + + // When + editTaskUI.invoke(projectId) + + // Then + coVerify { + consoleIO.write("āŒ Failed to update task: $errorMessage") + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/task/GetAllTasksUITest.kt b/src/test/kotlin/presentation/task/GetAllTasksUITest.kt new file mode 100644 index 0000000..4b5b8ea --- /dev/null +++ b/src/test/kotlin/presentation/task/GetAllTasksUITest.kt @@ -0,0 +1,177 @@ +package presentation.task + +import domain.models.SubTask +import domain.models.Task +import domain.usecases.task.GetTaskByProjectIdUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class GetAllTasksUITest { + private lateinit var getTaskByProjectIdUseCase: GetTaskByProjectIdUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var getAllTasksUI: GetAllTasksUI + private val projectId = UUID.randomUUID() + private val taskId = UUID.randomUUID() + private val stateId = UUID.randomUUID() + private val subTaskId = UUID.randomUUID() + + @BeforeEach + fun setUp() { + getTaskByProjectIdUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + getAllTasksUI = GetAllTasksUI(getTaskByProjectIdUseCase, consoleIO) + } + + @Test + fun `should display tasks when they exist`() = runTest { + // Given + val subTask = SubTask( + id = subTaskId, + title = "Subtask 1", + description = "Subtask description", + isCompleted = false, + parentTaskId = taskId + ) + + val task = Task( + id = taskId, + title = "Sample Task", + description = "Task description", + projectId = projectId, + stateId = stateId, + subTasks = listOf(subTask) + ) + + coEvery { getTaskByProjectIdUseCase(projectId) } returns listOf(task) + + // When + getAllTasksUI.invoke(projectId) + + // Then + coVerify { + getTaskByProjectIdUseCase(projectId) + consoleIO.write(match { + it.contains("Sample Task") && + it.contains("Task description") && + it.contains(taskId.toString()) && + it.contains(stateId.toString()) && + it.contains("Subtask 1") && + it.contains("Pending") + }) + } + } + + @Test + fun `should display message when no tasks exist`() = runTest { + // Given + coEvery { getTaskByProjectIdUseCase(projectId) } returns emptyList() + + // When + getAllTasksUI.invoke(projectId) + + // Then + coVerify { + getTaskByProjectIdUseCase(projectId) + consoleIO.write("ā„¹ļø No tasks found for this project.") + } + } + + @Test + fun `should handle exception when fetching tasks`() = runTest { + // Given + val errorMessage = "Failed to fetch tasks" + coEvery { getTaskByProjectIdUseCase(projectId) } throws RuntimeException(errorMessage) + + // When + getAllTasksUI.invoke(projectId) + + // Then + coVerify { + getTaskByProjectIdUseCase(projectId) + consoleIO.write("āŒ Failed to fetch tasks: $errorMessage") + } + } + + @Test + fun `should display multiple tasks correctly`() = runTest { + // Given + val task1 = Task( + id = UUID.randomUUID(), + title = "Task 1", + description = "First task", + projectId = projectId, + stateId = stateId, + subTasks = emptyList() + ) + + val task2 = Task( + id = UUID.randomUUID(), + title = "Task 2", + description = "Second task", + projectId = projectId, + stateId = stateId, + subTasks = emptyList() + ) + + coEvery { getTaskByProjectIdUseCase(projectId) } returns listOf(task1, task2) + + // When + getAllTasksUI.invoke(projectId) + + // Then + coVerify { + getTaskByProjectIdUseCase(projectId) + consoleIO.write(match { it.contains("Task 1") && it.contains("First task") }) + consoleIO.write(match { it.contains("Task 2") && it.contains("Second task") }) + } + } + + @Test + fun `should display completed subtasks correctly`() = runTest { + // Given + val completedSubTask = SubTask( + id = UUID.randomUUID(), + title = "Completed Subtask", + description = "Completed subtask description", + isCompleted = true, + parentTaskId = taskId + ) + + val pendingSubTask = SubTask( + id = UUID.randomUUID(), + title = "Pending Subtask", + description = "Pending subtask description", + isCompleted = false, + parentTaskId = taskId + ) + + val task = Task( + id = taskId, + title = "Task with subtasks", + description = "Description", + projectId = projectId, + stateId = stateId, + subTasks = listOf(completedSubTask, pendingSubTask) + ) + + coEvery { getTaskByProjectIdUseCase(projectId) } returns listOf(task) + + // When + getAllTasksUI.invoke(projectId) + + // Then + coVerify { + getTaskByProjectIdUseCase(projectId) + consoleIO.write(match { + it.contains("Completed Subtask") && + it.contains("Pending Subtask") && + it.contains("Completed") && + it.contains("Pending") + }) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/task/TasksUITest.kt b/src/test/kotlin/presentation/task/TasksUITest.kt new file mode 100644 index 0000000..ce97ca5 --- /dev/null +++ b/src/test/kotlin/presentation/task/TasksUITest.kt @@ -0,0 +1,254 @@ +package presentation.task + +import domain.models.Project +import domain.models.TaskState +import domain.usecases.project.GetProjectByIdUseCase +import domain.usecases.task_state.GetTaskStatesByProjectIdUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import presentation.project.GetAllProjectsUI +import presentation.sub_task.CalculateSubTaskPercentageUI +import java.util.* + +class TasksUITest { + private lateinit var getProjectByIdUseCase: GetProjectByIdUseCase + private lateinit var getTaskStatesByProjectIdUseCase: GetTaskStatesByProjectIdUseCase + private lateinit var deleteTaskUI: DeleteTaskUI + private lateinit var createTaskUI: CreateTaskUI + private lateinit var getAllTasksUI: GetAllTasksUI + private lateinit var editTaskUI: EditTaskUI + private lateinit var calculateSubTaskPercentageUI: CalculateSubTaskPercentageUI + private lateinit var getAllProjectsUI: GetAllProjectsUI + private lateinit var consoleIO: ConsoleIO + private lateinit var tasksUI: TasksUI + + private val projectId = UUID.randomUUID() + private val validProjectIdString = projectId.toString() + private val invalidProjectIdString = "invalid-uuid" + + @BeforeEach + fun setUp() { + getProjectByIdUseCase = mockk() + getTaskStatesByProjectIdUseCase = mockk() + deleteTaskUI = mockk(relaxed = true) + createTaskUI = mockk(relaxed = true) + getAllTasksUI = mockk(relaxed = true) + editTaskUI = mockk(relaxed = true) + calculateSubTaskPercentageUI = mockk(relaxed = true) + getAllProjectsUI = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + + tasksUI = TasksUI( + getProjectByIdUseCase, + getTaskStatesByProjectIdUseCase, + deleteTaskUI, + createTaskUI, + getAllTasksUI, + editTaskUI, + calculateSubTaskPercentageUI, + getAllProjectsUI, + consoleIO + ) + + // Mock the extension function without using mockkStatic + mockkStatic(UUID::class) + every { UUID.fromString(validProjectIdString) } returns projectId + every { UUID.fromString(invalidProjectIdString) } throws IllegalArgumentException("Invalid UUID") + } + + @Test + fun `should show all projects and handle invalid project ID input`() = runTest { + // Given + every { consoleIO.read() } returns invalidProjectIdString + + // When + tasksUI.invoke() + + // Then + coVerifySequence { + getAllProjectsUI() + consoleIO.write("Enter the project ID:") + consoleIO.read() + consoleIO.write("āŒ Invalid project ID format. Please enter a valid UUID.") + } + } + + @Test + fun `should handle non-existent project`() = runTest { + // Given + every { consoleIO.read() } returns validProjectIdString + coEvery { getProjectByIdUseCase(projectId) } throws RuntimeException("Project not found") + + // When + tasksUI.invoke() + + // Then + coVerifySequence { + getAllProjectsUI() + consoleIO.write("Enter the project ID:") + consoleIO.read() + getProjectByIdUseCase(projectId) + consoleIO.write("āŒ No project exists. Add a new project to manage tasks. Please try again.") + } + } + + @Test + fun `should handle project with no states`() = runTest { + // Given + every { consoleIO.read() } returns validProjectIdString + coEvery { getProjectByIdUseCase(projectId) } returns mockk() + coEvery { getTaskStatesByProjectIdUseCase(projectId) } returns emptyList() + + // When + tasksUI.invoke() + + // Then + coVerifySequence { + getAllProjectsUI() + consoleIO.write("Enter the project ID:") + consoleIO.read() + getProjectByIdUseCase(projectId) + getTaskStatesByProjectIdUseCase(projectId) + consoleIO.write("āŒ No states exist for this project. Please add at least one state before managing tasks.") + } + } + + @Test + fun `should handle error checking states`() = runTest { + // Given + every { consoleIO.read() } returns validProjectIdString + coEvery { getProjectByIdUseCase(projectId) } returns mockk() + coEvery { getTaskStatesByProjectIdUseCase(projectId) } throws RuntimeException("Error fetching states") + + // When + tasksUI.invoke() + + // Then + coVerify { + consoleIO.write("āŒ Error checking states: Error fetching states") + } + } + + @Test + fun `should navigate to create task`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf(validProjectIdString, "1") + coEvery { getProjectByIdUseCase(projectId) } returns mockk() + coEvery { getTaskStatesByProjectIdUseCase(projectId) } returns listOf(mockk()) + + // When + tasksUI.invoke() + + // Then + coVerifySequence { + getAllProjectsUI() + consoleIO.write("Enter the project ID:") + consoleIO.read() + getProjectByIdUseCase(projectId) + getTaskStatesByProjectIdUseCase(projectId) + getAllTasksUI(projectId) + consoleIO.write(any()) // Menu options + consoleIO.read() + createTaskUI(projectId) + } + } + + @Test + fun `should navigate to edit task`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf(validProjectIdString, "2") + coEvery { getProjectByIdUseCase(projectId) } returns mockk() + coEvery { getTaskStatesByProjectIdUseCase(projectId) } returns listOf(mockk()) + + // When + tasksUI.invoke() + + // Then + coVerify { + editTaskUI(projectId) + } + } + + @Test + fun `should navigate to delete task`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf(validProjectIdString, "3") + coEvery { getProjectByIdUseCase(projectId) } returns mockk() + coEvery { getTaskStatesByProjectIdUseCase(projectId) } returns listOf(mockk()) + + // When + tasksUI.invoke() + + // Then + coVerify { + deleteTaskUI() + } + } + + @Test + fun `should navigate to calculate task percentage`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf(validProjectIdString, "4") + coEvery { getProjectByIdUseCase(projectId) } returns mockk() + coEvery { getTaskStatesByProjectIdUseCase(projectId) } returns listOf(mockk()) + + // When + tasksUI.invoke() + + // Then + coVerify { + calculateSubTaskPercentageUI() + } + } + + @Test + fun `should navigate back`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf(validProjectIdString, "5") + coEvery { getProjectByIdUseCase(projectId) } returns mockk() + coEvery { getTaskStatesByProjectIdUseCase(projectId) } returns listOf(mockk()) + + // When + tasksUI.invoke() + + // Then + verify { + consoleIO.write("Navigating back...") + } + } + + @Test + fun `should handle invalid menu option`() = runTest { + // Given + every { consoleIO.read() } returnsMany listOf(validProjectIdString, "invalid", "invalid-uuid") + coEvery { getProjectByIdUseCase(projectId) } returns mockk() + coEvery { getTaskStatesByProjectIdUseCase(projectId) } returns listOf(mockk()) + + // When + tasksUI.invoke() + + // Then + coVerifySequence { + getAllProjectsUI() + consoleIO.write("Enter the project ID:") + consoleIO.read() + getProjectByIdUseCase(projectId) + getTaskStatesByProjectIdUseCase(projectId) + getAllTasksUI(projectId) + consoleIO.write(any()) // Menu options + consoleIO.read() + consoleIO.write("āŒ Invalid option.") + getAllProjectsUI() + consoleIO.write("Enter the project ID:") + consoleIO.read() + consoleIO.write("āŒ Invalid project ID format. Please enter a valid UUID.") + } + + coVerify { + tasksUI.invoke() + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/user/AssignProjectToUserUITest.kt b/src/test/kotlin/presentation/user/AssignProjectToUserUITest.kt index eac295a..8741e30 100644 --- a/src/test/kotlin/presentation/user/AssignProjectToUserUITest.kt +++ b/src/test/kotlin/presentation/user/AssignProjectToUserUITest.kt @@ -14,40 +14,35 @@ import java.util.* class AssignProjectToUserUITest { private lateinit var assignProjectToUserUseCase: AssignProjectToUserUseCase - private lateinit var sessionManager: SessionManager private lateinit var consoleIO: ConsoleIO private lateinit var getAllProjectsUI: GetAllProjectsUI private lateinit var assignProjectToUserUI: AssignProjectToUserUI - private val currentUserRole = UserRole.ADMIN - private val validProjectIdString = "123e4567-e89b-12d3-a456-426614174000" - private val validProjectId = UUID.fromString(validProjectIdString) - private val validUserIdString = "223e4567-e89b-12d3-a456-426614174001" - private val validUserId = UUID.fromString(validUserIdString) + + private val validProjectId = UUID.randomUUID() + private val validProjectIdString = validProjectId.toString() + private val validUserId = UUID.randomUUID() + private val validUserIdString = validUserId.toString() @BeforeEach fun setUp() { assignProjectToUserUseCase = mockk(relaxed = true) - sessionManager = mockk(relaxed = true) consoleIO = mockk(relaxed = true) getAllProjectsUI = mockk(relaxed = true) + assignProjectToUserUI = AssignProjectToUserUI(assignProjectToUserUseCase, consoleIO, getAllProjectsUI) - mockkStatic("data.mongodb_data.mappers.MapperKt") - - every { sessionManager.getCurrentUserRole() } returns currentUserRole - - assignProjectToUserUI = AssignProjectToUserUI( - assignProjectToUserUseCase, - consoleIO, - getAllProjectsUI - ) + mockkStatic(String::toUUID) + mockkObject(SessionManager) } @Test - fun `should assign project to user successfully with valid UUIDs`() = runTest { + fun `should assign project to user successfully when user is admin`() = runTest { // Given + coEvery { SessionManager.getCurrentUserRole() } returns UserRole.ADMIN coEvery { consoleIO.read() } returnsMany listOf(validProjectIdString, validUserIdString) - coEvery { validProjectIdString.toUUID() } returns validProjectId - coEvery { validUserIdString.toUUID() } returns validUserId + + every { validProjectIdString.toUUID() } returns validProjectId + every { validUserIdString.toUUID() } returns validUserId + coEvery { assignProjectToUserUseCase(UserRole.ADMIN, validProjectId, validUserId) } returns true // When @@ -55,117 +50,154 @@ class AssignProjectToUserUITest { // Then coVerifySequence { + SessionManager.getCurrentUserRole() getAllProjectsUI.invoke() consoleIO.write("\n=== Assign Project to User ===") consoleIO.write("Enter Project ID:") consoleIO.read() consoleIO.write("Enter User ID:") consoleIO.read() - sessionManager.getCurrentUserRole() assignProjectToUserUseCase(UserRole.ADMIN, validProjectId, validUserId) consoleIO.write("User successfully assigned to the project.") } } @Test - fun `should handle failed assignment`() = runTest { + fun `should handle invalid project ID format`() = runTest { // Given - val errorMessage = "Permission denied" - coEvery { consoleIO.read() } returnsMany listOf(validProjectIdString, validUserIdString) - coEvery { validProjectIdString.toUUID() } returns validProjectId - coEvery { validUserIdString.toUUID() } returns validUserId - coEvery { assignProjectToUserUseCase(UserRole.ADMIN, validProjectId, validUserId) } throws - IllegalStateException(errorMessage) + val invalidProjectId = "not-a-uuid" + coEvery { SessionManager.getCurrentUserRole() } returns UserRole.ADMIN + coEvery { consoleIO.read() } returns invalidProjectId + every { invalidProjectId.toUUID() } throws IllegalArgumentException("Invalid UUID format") // When assignProjectToUserUI.invoke() // Then coVerifySequence { + SessionManager.getCurrentUserRole() getAllProjectsUI.invoke() consoleIO.write("\n=== Assign Project to User ===") consoleIO.write("Enter Project ID:") consoleIO.read() - consoleIO.write("Enter User ID:") - consoleIO.read() - sessionManager.getCurrentUserRole() - assignProjectToUserUseCase(UserRole.ADMIN, validProjectId, validUserId) - consoleIO.write("Failed to assign user. $errorMessage") + consoleIO.write("Invalid Project ID format. Please enter a valid UUID.") + } + + coVerify(exactly = 0) { + assignProjectToUserUseCase(any(), any(), any()) } } @Test - fun `should handle invalid project ID format`() = runTest { + fun `should handle invalid user ID format`() = runTest { // Given - val invalidProjectId = "not-a-uuid" - coEvery { consoleIO.read() } returns invalidProjectId - coEvery { invalidProjectId.toUUID() } throws IllegalArgumentException("Invalid UUID format") + val invalidUserId = "not-a-uuid" + coEvery { SessionManager.getCurrentUserRole() } returns UserRole.ADMIN + coEvery { consoleIO.read() } returnsMany listOf(validProjectIdString, invalidUserId) + + every { validProjectIdString.toUUID() } returns validProjectId + every { invalidUserId.toUUID() } throws IllegalArgumentException("Invalid UUID format") // When assignProjectToUserUI.invoke() // Then coVerifySequence { + SessionManager.getCurrentUserRole() getAllProjectsUI.invoke() consoleIO.write("\n=== Assign Project to User ===") consoleIO.write("Enter Project ID:") consoleIO.read() - consoleIO.write("Invalid Project ID format. Please enter a valid UUID.") - } - - coVerify(exactly = 0) { - assignProjectToUserUseCase(any(), any(), any()) + consoleIO.write("Enter User ID:") + consoleIO.read() + consoleIO.write("Failed to assign user. Invalid UUID format.") } } @Test - fun `should handle invalid user ID format`() = runTest { + fun `should deny access when user is not admin`() = runTest { // Given - val invalidUserId = "not-a-uuid" - coEvery { consoleIO.read() } returnsMany listOf(validProjectIdString, invalidUserId) - coEvery { validProjectIdString.toUUID() } returns validProjectId - coEvery { invalidUserId.toUUID() } throws IllegalArgumentException("Invalid UUID format") + coEvery { SessionManager.getCurrentUserRole() } returns UserRole.MATE + coEvery { consoleIO.read() } returnsMany listOf(validProjectIdString, validUserIdString) + + every { validProjectIdString.toUUID() } returns validProjectId + every { validUserIdString.toUUID() } returns validUserId + + coEvery { assignProjectToUserUseCase(UserRole.MATE, validProjectId, validUserId) } throws + IllegalStateException("Only admins can assign users to projects.") // When assignProjectToUserUI.invoke() // Then coVerifySequence { + SessionManager.getCurrentUserRole() getAllProjectsUI.invoke() consoleIO.write("\n=== Assign Project to User ===") consoleIO.write("Enter Project ID:") consoleIO.read() consoleIO.write("Enter User ID:") consoleIO.read() - consoleIO.write("Failed to assign user. Invalid UUID format.") + assignProjectToUserUseCase(UserRole.MATE, validProjectId, validUserId) + consoleIO.write("Failed to assign user. Only admins can assign users to projects.") } } @Test - fun `should deny access when user is not admin`() = runTest { + fun `should handle project not found exception`() = runTest { // Given + coEvery { SessionManager.getCurrentUserRole() } returns UserRole.ADMIN coEvery { consoleIO.read() } returnsMany listOf(validProjectIdString, validUserIdString) - coEvery { validProjectIdString.toUUID() } returns validProjectId - coEvery { validUserIdString.toUUID() } returns validUserId - coEvery { sessionManager.getCurrentUserRole() } returns UserRole.MATE + + every { validProjectIdString.toUUID() } returns validProjectId + every { validUserIdString.toUUID() } returns validUserId + + coEvery { assignProjectToUserUseCase(UserRole.ADMIN, validProjectId, validUserId) } throws + IllegalArgumentException("Project not found") // When assignProjectToUserUI.invoke() // Then coVerifySequence { + SessionManager.getCurrentUserRole() getAllProjectsUI.invoke() consoleIO.write("\n=== Assign Project to User ===") consoleIO.write("Enter Project ID:") consoleIO.read() consoleIO.write("Enter User ID:") consoleIO.read() - sessionManager.getCurrentUserRole() - consoleIO.write("Failed to assign user. Only admins can assign users to projects.") + assignProjectToUserUseCase(UserRole.ADMIN, validProjectId, validUserId) + consoleIO.write("Failed to assign user. Project not found") } + } - coVerify(exactly = 0) { - assignProjectToUserUseCase(any(), any(), any()) + @Test + fun `should handle user not found exception`() = runTest { + // Given + coEvery { SessionManager.getCurrentUserRole() } returns UserRole.ADMIN + coEvery { consoleIO.read() } returnsMany listOf(validProjectIdString, validUserIdString) + + every { validProjectIdString.toUUID() } returns validProjectId + every { validUserIdString.toUUID() } returns validUserId + + coEvery { assignProjectToUserUseCase(UserRole.ADMIN, validProjectId, validUserId) } throws + IllegalArgumentException("User not found") + + // When + assignProjectToUserUI.invoke() + + // Then + coVerifySequence { + SessionManager.getCurrentUserRole() + getAllProjectsUI.invoke() + consoleIO.write("\n=== Assign Project to User ===") + consoleIO.write("Enter Project ID:") + consoleIO.read() + consoleIO.write("Enter User ID:") + consoleIO.read() + assignProjectToUserUseCase(UserRole.ADMIN, validProjectId, validUserId) + consoleIO.write("Failed to assign user. User not found") } } -} +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/user/CreateUserUITest.kt b/src/test/kotlin/presentation/user/CreateUserUITest.kt new file mode 100644 index 0000000..094a91d --- /dev/null +++ b/src/test/kotlin/presentation/user/CreateUserUITest.kt @@ -0,0 +1,150 @@ +package presentation.user + + import data.session_manager.SessionManager + import domain.models.User + import domain.models.User.UserRole + import domain.usecases.user.CreateUserUseCase + import domain.util.isValidPasswordFormat + import io.mockk.* + import kotlinx.coroutines.test.runTest + import org.junit.jupiter.api.BeforeEach + import org.junit.jupiter.api.Test + import presentation.io.ConsoleIO + import java.util.* + + class CreateUserUITest { + private lateinit var createUserUseCase: CreateUserUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var createUserUI: CreateUserUI + + private val username = "test user" + private val validPassword = "Valid1Password!" + private val userRole = UserRole.MATE + private val currentUserRole = UserRole.ADMIN + private val userId = UUID.randomUUID() + + @BeforeEach + fun setUp() { + createUserUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + createUserUI = CreateUserUI(createUserUseCase, consoleIO) + + mockkStatic(::isValidPasswordFormat) + every { isValidPasswordFormat(validPassword) } returns true + + mockkObject(SessionManager) + every { SessionManager.getCurrentUserRole() } returns currentUserRole + + } + + @Test + fun `should create user successfully with valid inputs`() = runTest { + // Given + val inputSequence = listOf(username, validPassword, "2") // 2 for MATE role + var inputIndex = 0 + every { consoleIO.read() } answers { inputSequence[inputIndex++] } + + coEvery { + createUserUseCase( + currentUserRole, + match { + it.id == userId && + it.name == username && + it.role == userRole + }, + validPassword + ) + } + + // When + createUserUI.invoke() + + // Then + coVerifySequence { + consoleIO.write("\n╔══════════════════════════╗") + consoleIO.write("ā•‘ CREATE NEW USER ā•‘") + consoleIO.write("ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•") + consoleIO.write("\nEnter username:") + consoleIO.read() + consoleIO.write(any()) // Password requirements + consoleIO.read() + consoleIO.write("\nSelect role:") + UserRole.entries.forEach { role -> + consoleIO.write("${role.ordinal + 1}. $role") + } + consoleIO.write("Enter choice (1-${UserRole.entries.size}):") + consoleIO.read() + createUserUseCase(currentUserRole, any(), validPassword) + consoleIO.write("\nāœ… User '$username' created successfully!") + } + } + + @Test + fun `should prompt for username until valid input is provided`() = runTest { + // Given + val inputSequence = listOf("", " ", username, validPassword, "2") // First two inputs are invalid + var inputIndex = 0 + every { consoleIO.read() } answers { inputSequence[inputIndex++] } + + // When + createUserUI.invoke() + + // Then + verify(exactly = 3) { consoleIO.write("\nEnter username:") } + verify(exactly = 2) { consoleIO.write("āŒ Username cannot be empty") } + } + + @Test + fun `should prompt for password until valid format is provided`() = runTest { + // Given + val invalidPassword = "weak" + val inputSequence = listOf(username, invalidPassword, validPassword, "2") + var inputIndex = 0 + every { consoleIO.read() } answers { inputSequence[inputIndex++] } + every { isValidPasswordFormat(invalidPassword) } returns false + + // When + createUserUI.invoke() + + // Then + verify(exactly = 2) { + consoleIO.write(match { it.contains("At least one lowercase letter") }) + } + verify { consoleIO.write("āŒ Password must meet the specified criteria") } + } + + @Test + fun `should prompt for role until valid choice is provided`() = runTest { + // Given + val inputSequence = listOf(username, validPassword, "invalid", "0", "10", "2") + var inputIndex = 0 + every { consoleIO.read() } answers { inputSequence[inputIndex++] } + + // When + createUserUI.invoke() + + // Then + verify(exactly = 4) { consoleIO.write("Enter choice (1-${UserRole.entries.size}):") } + verify(exactly = 1) { consoleIO.write("āš ļø Please enter a valid number") } + verify(exactly = 2) { consoleIO.write("āš ļø Please enter a number between 1 and ${UserRole.entries.size}") } + } + + @Test + fun `should handle exception when creating user`() = runTest { + // Given + val errorMessage = "Insufficient permissions" + val inputSequence = listOf(username, validPassword, "2") + var inputIndex = 0 + every { consoleIO.read() } answers { inputSequence[inputIndex++] } + + coEvery { + createUserUseCase(currentUserRole, any(), validPassword) + } throws RuntimeException(errorMessage) + + // When + createUserUI.invoke() + + // Then + verify { consoleIO.write("āŒ Failed to create user: $errorMessage") } + } + } \ No newline at end of file diff --git a/src/test/kotlin/presentation/user/DeleteUserUITest.kt b/src/test/kotlin/presentation/user/DeleteUserUITest.kt new file mode 100644 index 0000000..caeeb3d --- /dev/null +++ b/src/test/kotlin/presentation/user/DeleteUserUITest.kt @@ -0,0 +1,148 @@ +package presentation.user + +import data.mongodb_data.mappers.toUUID +import data.session_manager.SessionManager +import domain.models.User.UserRole +import domain.usecases.user.DeleteUserUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class DeleteUserUITest { + private lateinit var deleteUserUseCase: DeleteUserUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var deleteUserUI: DeleteUserUI + private val userId = UUID.randomUUID() + private val userIdString = userId.toString() + + @BeforeEach + fun setUp() { + deleteUserUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + deleteUserUI = DeleteUserUI(deleteUserUseCase, consoleIO) + + mockkStatic(String::toUUID) + every { userIdString.toUUID() } returns userId + + every { consoleIO.read() } returns userIdString + + mockkObject(SessionManager) + } + + @Test + fun `should delete user successfully with admin role`() = runTest { + // Given + every { SessionManager.getCurrentUserRole() } returns UserRole.ADMIN + coEvery { deleteUserUseCase(UserRole.ADMIN, userId) } returns Unit + + // When + deleteUserUI.invoke() + + // Then + coVerifySequence { + SessionManager.getCurrentUserRole() + consoleIO.write("\n=======================") + consoleIO.write("ā•‘ DELETE USER ā•‘") + consoleIO.write("=======================\n") + consoleIO.write("Enter user ID to delete: ") + consoleIO.read() + deleteUserUseCase(UserRole.ADMIN, userId) + consoleIO.write("āœ… User with ID $userId has been successfully deleted.") + } + } + + @Test + fun `should delete user successfully with non-admin role`() = runTest { + // Given + every { SessionManager.getCurrentUserRole() } returns UserRole.MATE + coEvery { deleteUserUseCase(UserRole.MATE, userId) } returns Unit + + // When + deleteUserUI.invoke() + + // Then + coVerifySequence { + SessionManager.getCurrentUserRole() + consoleIO.write("\n=======================") + consoleIO.write("ā•‘ DELETE USER ā•‘") + consoleIO.write("=======================\n") + consoleIO.write("Enter user ID to delete: ") + consoleIO.read() + deleteUserUseCase(UserRole.MATE, userId) + consoleIO.write("āœ… User with ID $userId has been successfully deleted.") + } + } + + @Test + fun `should handle invalid UUID input`() = runTest { + // Given + val invalidUuid = "not-a-uuid" + every { SessionManager.getCurrentUserRole() } returns UserRole.ADMIN + every { consoleIO.read() } returns invalidUuid + every { invalidUuid.toUUID() } throws IllegalArgumentException("Invalid UUID format") + + // When + deleteUserUI.invoke() + + // Then + coVerifySequence { + SessionManager.getCurrentUserRole() + consoleIO.write("\n=======================") + consoleIO.write("ā•‘ DELETE USER ā•‘") + consoleIO.write("=======================\n") + consoleIO.write("Enter user ID to delete: ") + consoleIO.read() + consoleIO.write("āŒ Error: Invalid UUID format. Please provide a valid user ID.") + } + + coVerify(exactly = 0) { deleteUserUseCase(any(), any()) } + } + + @Test + fun `should handle exception when deleting user`() = runTest { + // Given + val errorMessage = "User not found" + every { SessionManager.getCurrentUserRole() } returns UserRole.ADMIN + coEvery { deleteUserUseCase(UserRole.ADMIN, userId) } throws RuntimeException(errorMessage) + + // When + deleteUserUI.invoke() + + // Then + coVerifySequence { + SessionManager.getCurrentUserRole() + consoleIO.write("\n=======================") + consoleIO.write("ā•‘ DELETE USER ā•‘") + consoleIO.write("=======================\n") + consoleIO.write("Enter user ID to delete: ") + consoleIO.read() + deleteUserUseCase(UserRole.ADMIN, userId) + consoleIO.write("āŒ Failed to delete user. $errorMessage") + } + } + + @Test + fun `should handle null user role`() = runTest { + // Given + every { SessionManager.getCurrentUserRole() } returns UserRole.MATE + coEvery { deleteUserUseCase(UserRole.MATE, userId) } returns Unit + + // When + deleteUserUI.invoke() + + // Then + coVerifySequence { + SessionManager.getCurrentUserRole() + consoleIO.write("\n=======================") + consoleIO.write("ā•‘ DELETE USER ā•‘") + consoleIO.write("=======================\n") + consoleIO.write("Enter user ID to delete: ") + consoleIO.read() + deleteUserUseCase(UserRole.MATE, userId) + consoleIO.write("āœ… User with ID $userId has been successfully deleted.") + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/user/GetAllUserUITest.kt b/src/test/kotlin/presentation/user/GetAllUserUITest.kt new file mode 100644 index 0000000..6d40447 --- /dev/null +++ b/src/test/kotlin/presentation/user/GetAllUserUITest.kt @@ -0,0 +1,154 @@ +package presentation.user + +import data.session_manager.SessionManager +import domain.models.User +import domain.models.User.UserRole +import domain.usecases.user.GetAllUsersUseCase +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO +import java.util.* + +class GetAllUserUITest { + private lateinit var getAllUsersUseCase: GetAllUsersUseCase + private lateinit var consoleIO: ConsoleIO + private lateinit var getAllUserUI: GetAllUserUI + + @BeforeEach + fun setUp() { + getAllUsersUseCase = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + getAllUserUI = GetAllUserUI(getAllUsersUseCase, consoleIO) + + mockkObject(SessionManager) + } + + @Test + fun `should display users successfully when users exist`() = runTest { + // Given + val userId1 = UUID.randomUUID() + val userId2 = UUID.randomUUID() + val taskId1 = UUID.randomUUID() + val taskId2 = UUID.randomUUID() + val userList = listOf( + User(userId1, "test user1", UserRole.ADMIN, emptyList(), listOf(taskId1)), + User(userId2, "test user2", UserRole.MATE, emptyList(), listOf(taskId1, taskId2)) + ) + every { SessionManager.getCurrentUserRole() } returns UserRole.ADMIN + coEvery { getAllUsersUseCase(UserRole.ADMIN) } returns userList + + // When + getAllUserUI.invoke() + + // Then + coVerify { + SessionManager.getCurrentUserRole() + getAllUsersUseCase(UserRole.ADMIN) + } + verifySequence { + consoleIO.write("\n╔══════════════════════════════════════════════════════════╗") + consoleIO.write("ā•‘ USER DIRECTORY ā•‘") + consoleIO.write("ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•") + consoleIO.write("\nā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”") + consoleIO.write("│ Index │ Username │ Role │ Assigned Tasks │") + consoleIO.write("ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤") + consoleIO.write(any()) // Row 1 + consoleIO.write(any()) // Row 2 + consoleIO.write("ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜") + consoleIO.write("\nTotal registered users: 2\n") + } + } + + @Test + fun `should display empty message when no users exist`() = runTest { + // Given + val emptyList = emptyList() + every { SessionManager.getCurrentUserRole() } returns UserRole.ADMIN + coEvery { getAllUsersUseCase(UserRole.ADMIN) } returns emptyList + + // When + getAllUserUI.invoke() + + // Then + coVerify { + SessionManager.getCurrentUserRole() + getAllUsersUseCase(UserRole.ADMIN) + } + verifySequence { + consoleIO.write("\n╔══════════════════════════════════════════════════════════╗") + consoleIO.write("ā•‘ USER DIRECTORY ā•‘") + consoleIO.write("ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•") + consoleIO.write("\nā„¹ļø No users found in the system") + } + } + + @Test + fun `should handle exception when fetching users fails`() = runTest { + // Given + val errorMessage = "Failed to connect to database" + every { SessionManager.getCurrentUserRole() } returns UserRole.ADMIN + coEvery { getAllUsersUseCase(UserRole.ADMIN) } throws RuntimeException(errorMessage) + + // When + getAllUserUI.invoke() + + // Then + coVerify { + SessionManager.getCurrentUserRole() + getAllUsersUseCase(UserRole.ADMIN) + } + verifySequence { + consoleIO.write("\n╔══════════════════════════════════════════════════════════╗") + consoleIO.write("ā•‘ USER DIRECTORY ā•‘") + consoleIO.write("ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•") + consoleIO.write("\nāŒ Failed to load users: $errorMessage") + } + } + + @Test + fun `should display users with different roles`() = runTest { + // Given + val userId1 = UUID.randomUUID() + val userId2 = UUID.randomUUID() + val userList = listOf( + User(userId1, "test user1", UserRole.ADMIN, emptyList(), emptyList()), + User(userId2, "test user2", UserRole.MATE, emptyList(), emptyList()) + ) + every { SessionManager.getCurrentUserRole() } returns UserRole.ADMIN + coEvery { getAllUsersUseCase(UserRole.ADMIN) } returns userList + + // When + getAllUserUI.invoke() + + // Then + coVerify { + SessionManager.getCurrentUserRole() + getAllUsersUseCase(UserRole.ADMIN) + } + verify { + consoleIO.write(match { it.contains("No tasks assigned") }) + } + } + + @Test + fun `should request users with non-admin role`() = runTest { + // Given + val userId = UUID.randomUUID() + val userList = listOf( + User(userId, "test user", UserRole.MATE, emptyList(), emptyList()) + ) + every { SessionManager.getCurrentUserRole() } returns UserRole.MATE + coEvery { getAllUsersUseCase(UserRole.MATE) } returns userList + + // When + getAllUserUI.invoke() + + // Then + coVerify { + SessionManager.getCurrentUserRole() + getAllUsersUseCase(UserRole.MATE) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/presentation/user/UserUITest.kt b/src/test/kotlin/presentation/user/UserUITest.kt new file mode 100644 index 0000000..78b0b8d --- /dev/null +++ b/src/test/kotlin/presentation/user/UserUITest.kt @@ -0,0 +1,173 @@ +package presentation.user + +import io.mockk.* +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import presentation.io.ConsoleIO + +class UserUITest { + private lateinit var createUserUI: CreateUserUI + private lateinit var getAllUserUI: GetAllUserUI + private lateinit var assignProjectToUserUI: AssignProjectToUserUI + private lateinit var getUserByIdUI: GetUserByIdUI + private lateinit var deleteUserUI: DeleteUserUI + private lateinit var consoleIO: ConsoleIO + private lateinit var userUI: UserUI + + @BeforeEach + fun setUp() { + createUserUI = mockk(relaxed = true) + getAllUserUI = mockk(relaxed = true) + assignProjectToUserUI = mockk(relaxed = true) + getUserByIdUI = mockk(relaxed = true) + deleteUserUI = mockk(relaxed = true) + consoleIO = mockk(relaxed = true) + userUI = UserUI( + createUserUI, + getAllUserUI, + assignProjectToUserUI, + getUserByIdUI, + deleteUserUI, + consoleIO + ) + } + + @Test + fun `should display menu and return when option 5 is selected`() = runTest { + // Given + every { consoleIO.read() } returns "5" + + // When + userUI.invoke() + + // Then + coVerifySequence { + getAllUserUI.invoke() + consoleIO.write("\n========== User Management ==========") + consoleIO.write("1. āž• Add User") + consoleIO.write("2. šŸ—‘ļø Delete User") + consoleIO.write("3. šŸ“Ž Assign Project to User") + consoleIO.write("4. šŸ” Get User Details by ID") + consoleIO.write("5. šŸ”™ Back") + consoleIO.write("Enter your option (1–5): ") + consoleIO.read() + consoleIO.write("šŸ”™ Returning to the previous menu...") + } + } + + @Test + fun `should invoke createUserUI when option 1 is selected`() = runTest { + // Given + coEvery { consoleIO.read() } returnsMany listOf("1", "5") + + // When + userUI.invoke() + + // Then + coVerify(exactly = 1) { createUserUI.invoke() } + coVerify(exactly = 2) { getAllUserUI.invoke() } + } + + @Test + fun `should invoke deleteUserUI when option 2 is selected`() = runTest { + // Given + coEvery { consoleIO.read() } returnsMany listOf("2", "5") + + // When + userUI.invoke() + + // Then + coVerify(exactly = 1) { deleteUserUI.invoke() } + coVerify(exactly = 2) { getAllUserUI.invoke() } + } + + @Test + fun `should invoke assignProjectToUserUI when option 3 is selected`() = runTest { + // Given + coEvery { consoleIO.read() } returnsMany listOf("3", "5") + + // When + userUI.invoke() + + // Then + coVerify(exactly = 1) { assignProjectToUserUI.invoke() } + coVerify(exactly = 2) { getAllUserUI.invoke() } + } + + @Test + fun `should invoke getUserByIdUI when option 4 is selected`() = runTest { + // Given + coEvery { consoleIO.read() } returnsMany listOf("4", "5") + + // When + userUI.invoke() + + // Then + coVerify(exactly = 1) { getUserByIdUI.invoke() } + coVerify(exactly = 2) { getAllUserUI.invoke() } + } + + @Test + fun `should handle invalid input`() = runTest { + // Given + coEvery { consoleIO.read() } returnsMany listOf("invalid", "5") + + // When + userUI.invoke() + + // Then + coVerifySequence { + getAllUserUI.invoke() + consoleIO.write("\n========== User Management ==========") + consoleIO.write("1. āž• Add User") + consoleIO.write("2. šŸ—‘ļø Delete User") + consoleIO.write("3. šŸ“Ž Assign Project to User") + consoleIO.write("4. šŸ” Get User Details by ID") + consoleIO.write("5. šŸ”™ Back") + consoleIO.write("Enter your option (1–5): ") + consoleIO.read() + consoleIO.write("āŒ Invalid input. Please enter a number between 1 and 5.") + + // Second loop (exit) + getAllUserUI.invoke() + consoleIO.write("\n========== User Management ==========") + consoleIO.write(any()) + consoleIO.write(any()) + consoleIO.write(any()) + consoleIO.write(any()) + consoleIO.write(any()) + consoleIO.write("Enter your option (1–5): ") + consoleIO.read() + consoleIO.write("šŸ”™ Returning to the previous menu...") + } + } + + @Test + fun `should handle out-of-range input`() = runTest { + // Given + coEvery { consoleIO.read() } returnsMany listOf("6", "5") + + // When + userUI.invoke() + + // Then + coVerify(exactly = 1) { consoleIO.write("āŒ Invalid input. Please enter a number between 1 and 5.") } + } + + @Test + fun `should loop through menu until exit option is selected`() = runTest { + // Given + coEvery { consoleIO.read() } returnsMany listOf("1", "2", "3", "4", "5") + + // When + userUI.invoke() + + // Then + coVerify(exactly = 1) { createUserUI.invoke() } + coVerify(exactly = 1) { deleteUserUI.invoke() } + coVerify(exactly = 1) { assignProjectToUserUI.invoke() } + coVerify(exactly = 1) { getUserByIdUI.invoke() } + coVerify(exactly = 5) { getAllUserUI.invoke() } + } +} \ No newline at end of file