Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: Removed moderator rights to edit posts; added test #119

Merged
merged 10 commits into from
Nov 29, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performScrollToKey
import androidx.test.platform.app.InstrumentationRegistry
import de.tum.informatics.www1.artemis.native_app.core.common.test.DefaultTestTimeoutMillis
import de.tum.informatics.www1.artemis.native_app.core.common.test.EndToEndTest
import de.tum.informatics.www1.artemis.native_app.core.test.BaseComposeTest
import de.tum.informatics.www1.artemis.native_app.core.test.coreTestModules
import de.tum.informatics.www1.artemis.native_app.core.common.test.DefaultTestTimeoutMillis
import de.tum.informatics.www1.artemis.native_app.core.test.test_setup.DefaultTimeoutMillis
import de.tum.informatics.www1.artemis.native_app.core.test.test_setup.course_creation.createCourse
import de.tum.informatics.www1.artemis.native_app.feature.login.loginModule
import de.tum.informatics.www1.artemis.native_app.feature.login.test.getAdminAccessToken
import de.tum.informatics.www1.artemis.native_app.feature.login.test.performTestLogin
import de.tum.informatics.www1.artemis.native_app.feature.login.test.testLoginModule
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.experimental.categories.Category
Expand All @@ -30,6 +31,10 @@ import org.koin.test.KoinTestRule
import org.koin.test.get
import org.robolectric.RobolectricTestRunner

@Ignore("There seems to be a problem related to the docker files, where the mysql database is not " +
"reset properly. This causes the newly created courses by this E2e test to pile up and " +
"causes the server to take very long to return all the courses. This results in a timeout." +
"Issue: https://github.com/ls1intum/artemis-android/issues/169")
@OptIn(ExperimentalTestApi::class)
@Category(EndToEndTest::class)
@RunWith(RobolectricTestRunner::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
import de.tum.informatics.www1.artemis.native_app.feature.metis.shared.content.dto.IAnswerPost
import de.tum.informatics.www1.artemis.native_app.feature.metis.shared.content.dto.IBasePost

data class PostActions(
Expand Down Expand Up @@ -46,24 +45,24 @@ fun rememberPostActions(
onRequestRetrySend,
clipboardManager
) {
if (post != null) {
val doesPostExistOnServer = post.serverPostId != null
val hasEditPostRights = hasModerationRights || post.authorId == clientId
val hasResolvePostRights = isAtLeastTutorInCourse || post.authorId == clientId

PostActions(
requestEditPost = if (doesPostExistOnServer && hasEditPostRights) onRequestEdit else null,
requestDeletePost = if (hasEditPostRights) onRequestDelete else null,
onClickReaction = if (doesPostExistOnServer) onClickReaction else null,
onCopyText = {
clipboardManager.setText(AnnotatedString(post.content.orEmpty()))
},
onReplyInThread = if (doesPostExistOnServer) onReplyInThread else null,
onResolvePost = if (hasResolvePostRights) onResolvePost else null,
onRequestRetrySend = onRequestRetrySend
)
} else {
PostActions()
if (post == null) {
return@remember PostActions()
}

val doesPostExistOnServer = post.serverPostId != null
val isPostAuthor = post.authorId == clientId
val hasResolvePostRights = isAtLeastTutorInCourse || post.authorId == clientId

PostActions(
requestEditPost = if (doesPostExistOnServer && isPostAuthor) onRequestEdit else null,
requestDeletePost = if (isPostAuthor || hasModerationRights) onRequestDelete else null,
onClickReaction = if (doesPostExistOnServer) onClickReaction else null,
onCopyText = {
clipboardManager.setText(AnnotatedString(post.content.orEmpty()))
},
onReplyInThread = if (doesPostExistOnServer) onReplyInThread else null,
onResolvePost = if (hasResolvePostRights) onResolvePost else null,
onRequestRetrySend = onRequestRetrySend
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ package de.tum.informatics.www1.artemis.native_app.feature.metis.conversation

import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import de.tum.informatics.www1.artemis.native_app.core.data.DataState
import de.tum.informatics.www1.artemis.native_app.core.model.Course
import de.tum.informatics.www1.artemis.native_app.core.model.account.User
import de.tum.informatics.www1.artemis.native_app.core.test.BaseComposeTest
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.MetisModificationFailure
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.impl.EmojiServiceStub
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.chatlist.ChatListItem
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.chatlist.MetisChatList
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.chatlist.PostsDataState
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.thread.MetisThreadUi
import de.tum.informatics.www1.artemis.native_app.feature.metis.shared.content.dto.IBasePost
import de.tum.informatics.www1.artemis.native_app.feature.metis.shared.content.dto.IStandalonePost
import de.tum.informatics.www1.artemis.native_app.feature.metis.shared.content.dto.UserRole
import de.tum.informatics.www1.artemis.native_app.feature.metis.shared.content.dto.conversation.OneToOneChat
import de.tum.informatics.www1.artemis.native_app.feature.metis.shared.db.pojo.AnswerPostPojo
Expand All @@ -19,11 +25,11 @@ import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import kotlinx.datetime.Clock

abstract class BaseThreadUITest : BaseComposeTest() {
abstract class BaseChatUItest : BaseComposeTest() {

val clientId = 20L

val course: Course = Course(id = 1)
private val course: Course = Course(id = 1)
val conversation = OneToOneChat(id = 2)

val answers = (0..2).map { index ->
Expand Down Expand Up @@ -94,4 +100,36 @@ abstract class BaseThreadUITest : BaseComposeTest() {
}
}

fun setupChatUi(
posts: List<IStandalonePost>,
currentUser: User = User(id = clientId),
hasModerationRights: Boolean = false
) {
composeTestRule.setContent {
val list = posts.map { post -> ChatListItem.PostChatListItem(post) }.toMutableList()
MetisChatList(
modifier = Modifier.fillMaxSize(),
initialReplyTextProvider = remember { TestInitialReplyTextProvider() },
posts = PostsDataState.Loaded.WithList(list, PostsDataState.NotLoading),
clientId = currentUser.id,
hasModerationRights = hasModerationRights,
isAtLeastTutorInCourse = false,
listContentPadding = PaddingValues(),
serverUrl = "",
courseId = course.id!!,
state = rememberLazyListState(),
emojiService = EmojiServiceStub,
bottomItem = null,
isReplyEnabled = true,
onCreatePost = { CompletableDeferred() },
onEditPost = { _, _ -> CompletableDeferred() },
onDeletePost = { CompletableDeferred() },
onRequestReactWithEmoji = { _, _, _ -> CompletableDeferred() },
onClickViewPost = {},
onRequestRetrySend = { _ -> },
title = "Title"
)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import org.robolectric.RobolectricTestRunner
@Ignore("There is an open issue about onClick events not working for the ModalBottomSheetLayout with" +
"the robolectric test runner. Enable this test again as soon as the following issue is resolved:" +
"https://github.com/robolectric/robolectric/issues/9595")
class ConversationAnswerMessagesUITest : BaseThreadUITest() {
class ConversationAnswerMessagesUITest : BaseChatUItest() {

private fun testTagForAnswerPost(answerPostId: String) = "answerPost$answerPostId"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.post

import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performSemanticsAction
import androidx.test.ext.junit.runners.AndroidJUnit4
import de.tum.informatics.www1.artemis.native_app.core.common.test.UnitTest
import de.tum.informatics.www1.artemis.native_app.core.model.account.User
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.BaseChatUItest
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.R
import de.tum.informatics.www1.artemis.native_app.feature.metis.shared.content.dto.StandalonePost
import org.junit.Test
import org.junit.experimental.categories.Category
import org.junit.runner.RunWith

@Category(UnitTest::class)
@RunWith(AndroidJUnit4::class)
class ConversationBottomSheetUiTest : BaseChatUItest() {

private val currentUser = User(id = clientId)
private val otherUser = User(id = 1234)

private val postContent = "Post content"

@Test
fun `test GIVEN a post WHEN long pressing the post THEN Edit action is shown`() {
setupChatUi(
posts = listOf(StandalonePost(
id = 1,
author = currentUser,
content = postContent,
)),
currentUser = currentUser
)

composeTestRule.assertPostActionVisibility(R.string.post_edit, isVisible = true)
}


@Test
fun `test GIVEN a user with moderation-rights WHEN long pressing the post THEN Edit action is not shown`() {
setupChatUi(
posts = listOf(StandalonePost(
id = 1,
author = otherUser,
content = postContent,
)),
currentUser = currentUser,
hasModerationRights = true
)

composeTestRule.assertPostActionVisibility(R.string.post_edit, isVisible = false)
}

@Test
fun `test GIVEN a user with moderation-rights WHEN long pressing the post THEN delete option is shown`() {
setupChatUi(
posts = listOf(StandalonePost(
id = 1,
author = otherUser,
content = postContent,
)),
currentUser = currentUser,
hasModerationRights = true
)

composeTestRule.assertPostActionVisibility(R.string.post_delete, isVisible = true)
}

@Test
fun `test GIVEN a post WHEN long pressing the post as the post author THEN delete option is shown`() {
setupChatUi(
posts = listOf(StandalonePost(
id = 1,
author = currentUser,
content = postContent,
)),
currentUser = currentUser
)

composeTestRule.assertPostActionVisibility(R.string.post_delete, isVisible = true)
}

@Test
fun `test GIVEN a post WHEN long pressing the post as non-moderator THEN delete option is not shown`() {
setupChatUi(
posts = listOf(StandalonePost(
id = 1,
author = otherUser,
content = postContent,
)),
currentUser = currentUser
)

composeTestRule.assertPostActionVisibility(R.string.post_delete, isVisible = false)
}

private fun ComposeTestRule.assertPostActionVisibility(
stringResId: Int,
isVisible: Boolean
) {
onNodeWithText(postContent)
.performSemanticsAction(SemanticsActions.OnLongClick)

onNodeWithTag(TEST_TAG_POST_CONTEXT_BOTTOM_SHEET)
.assertIsDisplayed()

val actionNode = onNodeWithText(context.getString(stringResId))
if (isVisible) {
actionNode.assertExists().assertIsDisplayed()
} else {
actionNode.assertDoesNotExist()
}
}

}
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
package de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.reply

import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import de.tum.informatics.www1.artemis.native_app.core.common.test.EndToEndTest
import de.tum.informatics.www1.artemis.native_app.core.common.test.UnitTest
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.BaseThreadUITest
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.TestInitialReplyTextProvider
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.service.impl.EmojiServiceStub
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.chatlist.ChatListItem
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.chatlist.MetisChatList
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.ui.chatlist.PostsDataState
import de.tum.informatics.www1.artemis.native_app.feature.metis.shared.db.pojo.PostPojo
import de.tum.informatics.www1.artemis.native_app.feature.metis.conversation.BaseChatUItest
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.CompletableDeferred
import org.junit.Test
Expand All @@ -26,7 +14,7 @@ import org.robolectric.RobolectricTestRunner

@Category(UnitTest::class)
@RunWith(RobolectricTestRunner::class)
class ReplyTextFieldVisibilityUITest : BaseThreadUITest() {
class ReplyTextFieldVisibilityUITest : BaseChatUItest() {

@Test
fun `test GIVEN the thread view is shown containing one post and three answer posts WHEN the markdown text field is clicked THEN the keyboard is shown below the markdown text field`() {
Expand Down Expand Up @@ -54,32 +42,4 @@ class ReplyTextFieldVisibilityUITest : BaseThreadUITest() {
.assertIsDisplayed()
assertTrue("Text field should move up when the keyboard appears", newPosition < initialPosition)
}

private fun setupChatUi(posts: List<PostPojo>) {
composeTestRule.setContent {
val list = posts.map { post -> ChatListItem.PostChatListItem(post) }.toMutableList()
MetisChatList(
modifier = Modifier.fillMaxSize(),
initialReplyTextProvider = remember { TestInitialReplyTextProvider() },
posts = PostsDataState.Loaded.WithList(list, PostsDataState.NotLoading),
clientId = clientId,
hasModerationRights = false,
isAtLeastTutorInCourse = false,
listContentPadding = PaddingValues(),
serverUrl = "",
courseId = course.id!!,
state = rememberLazyListState(),
emojiService = EmojiServiceStub,
bottomItem = posts.lastOrNull(),
isReplyEnabled = true,
onCreatePost = { CompletableDeferred() },
onEditPost = { _, _ -> CompletableDeferred() },
onDeletePost = { CompletableDeferred() },
onRequestReactWithEmoji = { _, _, _ -> CompletableDeferred() },
onClickViewPost = {},
onRequestRetrySend = { _ -> },
title = "Title"
)
}
}
}
Loading