Add small amount of unit testing for MessageContentProcessor.
This commit is contained in:
parent
d1df069669
commit
16cbc971a5
6 changed files with 272 additions and 1 deletions
|
@ -0,0 +1,31 @@
|
||||||
|
package org.thoughtcrime.securesms.messages
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.thoughtcrime.securesms.messages.MessageContentProcessor.ExceptionMetadata
|
||||||
|
import org.thoughtcrime.securesms.messages.MessageContentProcessor.MessageState
|
||||||
|
import org.thoughtcrime.securesms.testing.SignalActivityRule
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||||
|
|
||||||
|
abstract class MessageContentProcessorTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val harness = SignalActivityRule()
|
||||||
|
|
||||||
|
protected fun MessageContentProcessor.doProcess(
|
||||||
|
messageState: MessageState = MessageState.DECRYPTED_OK,
|
||||||
|
content: SignalServiceContent,
|
||||||
|
exceptionMetadata: ExceptionMetadata = ExceptionMetadata("sender", 1),
|
||||||
|
timestamp: Long = 100L,
|
||||||
|
smsMessageId: Long = -1L
|
||||||
|
) {
|
||||||
|
process(messageState, content, exceptionMetadata, timestamp, smsMessageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun createNormalContentTestSubject(): MessageContentProcessor {
|
||||||
|
val context = ApplicationProvider.getApplicationContext<Application>()
|
||||||
|
|
||||||
|
return MessageContentProcessor.forNormalContent(context)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
package org.thoughtcrime.securesms.messages
|
||||||
|
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.signal.core.util.requireLong
|
||||||
|
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||||
|
import org.signal.storageservice.protos.groups.Member
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedMember
|
||||||
|
import org.thoughtcrime.securesms.database.MessageDatabase
|
||||||
|
import org.thoughtcrime.securesms.database.MmsHelper
|
||||||
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
|
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
|
||||||
|
import org.thoughtcrime.securesms.database.model.ParentStoryId
|
||||||
|
import org.thoughtcrime.securesms.database.model.StoryType
|
||||||
|
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.testing.TestProtos
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlagsTestUtil
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||||
|
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
@Suppress("ClassName")
|
||||||
|
class MessageContentProcessor__handleStoryMessageTest : MessageContentProcessorTest() {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
FeatureFlagsTestUtil.setStoriesEnabled(true)
|
||||||
|
SignalDatabase.mms.deleteAllThreads()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
SignalDatabase.mms.deleteAllThreads()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun givenContentWithAGroupStoryReplyWhenIProcessThenIInsertAReplyToTheCorrectStory() {
|
||||||
|
val sender = Recipient.resolved(harness.others[0])
|
||||||
|
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
|
||||||
|
val decryptedGroupState = DecryptedGroup.newBuilder()
|
||||||
|
.addAllMembers(
|
||||||
|
listOf(
|
||||||
|
DecryptedMember.newBuilder()
|
||||||
|
.setUuid(harness.self.requireServiceId().toByteString())
|
||||||
|
.setJoinedAtRevision(0)
|
||||||
|
.setRole(Member.Role.DEFAULT)
|
||||||
|
.build(),
|
||||||
|
DecryptedMember.newBuilder()
|
||||||
|
.setUuid(sender.requireServiceId().toByteString())
|
||||||
|
.setJoinedAtRevision(0)
|
||||||
|
.setRole(Member.Role.DEFAULT)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setRevision(0)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val group = SignalDatabase.groups.create(
|
||||||
|
groupMasterKey,
|
||||||
|
decryptedGroupState
|
||||||
|
)
|
||||||
|
|
||||||
|
val groupRecipient = Recipient.externalGroupExact(group)
|
||||||
|
val threadForGroup = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
|
||||||
|
|
||||||
|
val insertResult = MmsHelper.insert(
|
||||||
|
message = IncomingMediaMessage(
|
||||||
|
from = sender.id,
|
||||||
|
sentTimeMillis = 100L,
|
||||||
|
serverTimeMillis = 101L,
|
||||||
|
receivedTimeMillis = 102L,
|
||||||
|
storyType = StoryType.STORY_WITH_REPLIES
|
||||||
|
),
|
||||||
|
threadId = threadForGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
val expectedBody = "Hello, World!"
|
||||||
|
val storyContent: SignalServiceContentProto = TestProtos.build {
|
||||||
|
serviceContent(
|
||||||
|
localAddress = address(uuid = harness.self.requireServiceId().uuid()).build(),
|
||||||
|
metadata = metadata(
|
||||||
|
address = address(uuid = sender.requireServiceId().uuid()).build()
|
||||||
|
).build()
|
||||||
|
).apply {
|
||||||
|
content = content().apply {
|
||||||
|
dataMessage = dataMessage().apply {
|
||||||
|
storyContext = storyContext(
|
||||||
|
sentTimestamp = 100L,
|
||||||
|
authorUuid = sender.requireServiceId().toString()
|
||||||
|
).build()
|
||||||
|
|
||||||
|
groupV2 = groupContextV2(masterKeyBytes = groupMasterKey.serialize()).build()
|
||||||
|
body = expectedBody
|
||||||
|
}.build()
|
||||||
|
}.build()
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
runTestWithContent(storyContent)
|
||||||
|
|
||||||
|
val replyId = SignalDatabase.mms.getStoryReplies(insertResult.get().messageId).use { cursor ->
|
||||||
|
assertEquals(1, cursor.count)
|
||||||
|
cursor.moveToFirst()
|
||||||
|
cursor.requireLong(MessageDatabase.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
val replyRecord = SignalDatabase.mms.getMessageRecord(replyId) as MediaMmsMessageRecord
|
||||||
|
assertEquals(ParentStoryId.GroupReply(insertResult.get().messageId).serialize(), replyRecord.parentStoryId?.serialize())
|
||||||
|
assertEquals(threadForGroup, replyRecord.threadId)
|
||||||
|
assertEquals(expectedBody, replyRecord.body)
|
||||||
|
|
||||||
|
SignalDatabase.mms.deleteGroupStoryReplies(insertResult.get().messageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runTestWithContent(contentProto: SignalServiceContentProto) {
|
||||||
|
val content = SignalServiceContent.createFromProto(contentProto)
|
||||||
|
val testSubject = createNormalContentTestSubject()
|
||||||
|
testSubject.doProcess(content = content)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package org.thoughtcrime.securesms.messages
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||||
|
import org.thoughtcrime.securesms.testing.TestProtos
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceContent
|
||||||
|
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
|
||||||
|
|
||||||
|
@Suppress("ClassName")
|
||||||
|
class MessageContentProcessor__handleTextMessageTest : MessageContentProcessorTest() {
|
||||||
|
@Test
|
||||||
|
fun givenContentWithATextMessageWhenIProcessThenIInsertTheTextMessage() {
|
||||||
|
val testSubject: MessageContentProcessor = createNormalContentTestSubject()
|
||||||
|
val expectedBody = "Hello, World!"
|
||||||
|
val contentProto: SignalServiceContentProto = TestProtos.build {
|
||||||
|
val dataMessage = dataMessage().apply { body = expectedBody }
|
||||||
|
val content = content().apply { this.dataMessage = dataMessage.build() }
|
||||||
|
serviceContent().apply { this.content = content.build() }
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
val content = SignalServiceContent.createFromProto(contentProto)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
testSubject.doProcess(content = content)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
val record = SignalDatabase.sms.getMessageRecord(1)
|
||||||
|
val threadSize = SignalDatabase.mmsSms.getConversationCount(record.threadId)
|
||||||
|
assertEquals(1, threadSize)
|
||||||
|
|
||||||
|
assertTrue(record.isSecure)
|
||||||
|
assertEquals(expectedBody, record.body)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package org.thoughtcrime.securesms.testing
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString
|
||||||
|
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||||
|
import org.whispersystems.signalservice.api.push.ServiceId
|
||||||
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||||
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
|
||||||
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
|
||||||
|
import org.whispersystems.signalservice.internal.serialize.protos.AddressProto
|
||||||
|
import org.whispersystems.signalservice.internal.serialize.protos.MetadataProto
|
||||||
|
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
|
||||||
|
import java.util.UUID
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class TestProtos private constructor() {
|
||||||
|
fun address(
|
||||||
|
uuid: UUID = UUID.randomUUID()
|
||||||
|
): AddressProto.Builder {
|
||||||
|
return AddressProto.newBuilder()
|
||||||
|
.setUuid(ServiceId.from(uuid).toByteString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun metadata(
|
||||||
|
address: AddressProto = address().build(),
|
||||||
|
): MetadataProto.Builder {
|
||||||
|
return MetadataProto.newBuilder()
|
||||||
|
.setAddress(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun groupContextV2(
|
||||||
|
revision: Int = 0,
|
||||||
|
masterKeyBytes: ByteArray = Random.Default.nextBytes(GroupMasterKey.SIZE)
|
||||||
|
): GroupContextV2.Builder {
|
||||||
|
return GroupContextV2.newBuilder()
|
||||||
|
.setRevision(revision)
|
||||||
|
.setMasterKey(ByteString.copyFrom(masterKeyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun storyContext(
|
||||||
|
sentTimestamp: Long = Random.nextLong(),
|
||||||
|
authorUuid: String = UUID.randomUUID().toString()
|
||||||
|
): DataMessage.StoryContext.Builder {
|
||||||
|
return DataMessage.StoryContext.newBuilder()
|
||||||
|
.setAuthorUuid(authorUuid)
|
||||||
|
.setSentTimestamp(sentTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dataMessage(): DataMessage.Builder {
|
||||||
|
return DataMessage.newBuilder()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun content(): SignalServiceProtos.Content.Builder {
|
||||||
|
return SignalServiceProtos.Content.newBuilder()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun serviceContent(
|
||||||
|
localAddress: AddressProto = address().build(),
|
||||||
|
metadata: MetadataProto = metadata().build()
|
||||||
|
): SignalServiceContentProto.Builder {
|
||||||
|
return SignalServiceContentProto.newBuilder()
|
||||||
|
.setLocalAddress(localAddress)
|
||||||
|
.setMetadata(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun <T> build(buildFn: TestProtos.() -> T): T {
|
||||||
|
return TestProtos().buildFn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.thoughtcrime.securesms.util
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to enable / disable feature flags via forced values.
|
||||||
|
*/
|
||||||
|
object FeatureFlagsTestUtil {
|
||||||
|
fun setStoriesEnabled(isEnabled: Boolean) {
|
||||||
|
FeatureFlags.FORCED_VALUES[FeatureFlags.STORIES] = isEnabled
|
||||||
|
}
|
||||||
|
}
|
|
@ -82,7 +82,7 @@ public final class FeatureFlags {
|
||||||
private static final String RETRY_RECEIPTS = "android.retryReceipts";
|
private static final String RETRY_RECEIPTS = "android.retryReceipts";
|
||||||
private static final String MAX_GROUP_CALL_RING_SIZE = "global.calling.maxGroupCallRingSize";
|
private static final String MAX_GROUP_CALL_RING_SIZE = "global.calling.maxGroupCallRingSize";
|
||||||
private static final String GROUP_CALL_RINGING = "android.calling.groupCallRinging";
|
private static final String GROUP_CALL_RINGING = "android.calling.groupCallRinging";
|
||||||
private static final String STORIES = "android.stories.7";
|
static final String STORIES = "android.stories.7";
|
||||||
private static final String STORIES_TEXT_FUNCTIONS = "android.stories.text.functions";
|
private static final String STORIES_TEXT_FUNCTIONS = "android.stories.text.functions";
|
||||||
private static final String HARDWARE_AEC_BLOCKLIST_MODELS = "android.calling.hardwareAecBlockList";
|
private static final String HARDWARE_AEC_BLOCKLIST_MODELS = "android.calling.hardwareAecBlockList";
|
||||||
private static final String SOFTWARE_AEC_BLOCKLIST_MODELS = "android.calling.softwareAecBlockList";
|
private static final String SOFTWARE_AEC_BLOCKLIST_MODELS = "android.calling.softwareAecBlockList";
|
||||||
|
|
Loading…
Add table
Reference in a new issue