From 13eb89746ba9aa7c601f6fda38f1635f16acdb60 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Thu, 1 Sep 2022 13:17:31 -0300 Subject: [PATCH] Add unit testing to story download enqueuer. --- .../securesms/database/SignalDatabase.kt | 7 + .../securesms/database/FakeMessageRecords.kt | 177 ++++++++++++++++++ .../securesms/stories/StoriesTest.kt | 116 ++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 app/src/test/java/org/thoughtcrime/securesms/database/FakeMessageRecords.kt create mode 100644 app/src/test/java/org/thoughtcrime/securesms/stories/StoriesTest.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt index a7b5b79558..def88b685f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.database import android.app.Application import android.content.Context +import androidx.annotation.VisibleForTesting import net.zetetic.database.sqlcipher.SQLiteOpenHelper import org.signal.core.util.SqlUtil import org.signal.core.util.logging.Log @@ -221,6 +222,12 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data } } + @JvmStatic + @VisibleForTesting + fun setSignalDatabaseInstanceForTesting(signalDatabase: SignalDatabase) { + this.instance = signalDatabase + } + @JvmStatic val rawDatabase: net.zetetic.database.sqlcipher.SQLiteDatabase get() = instance!!.rawWritableDatabase diff --git a/app/src/test/java/org/thoughtcrime/securesms/database/FakeMessageRecords.kt b/app/src/test/java/org/thoughtcrime/securesms/database/FakeMessageRecords.kt new file mode 100644 index 0000000000..0c4ce08af1 --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/database/FakeMessageRecords.kt @@ -0,0 +1,177 @@ +package org.thoughtcrime.securesms.database + +import org.thoughtcrime.securesms.attachments.AttachmentId +import org.thoughtcrime.securesms.attachments.DatabaseAttachment +import org.thoughtcrime.securesms.audio.AudioHash +import org.thoughtcrime.securesms.blurhash.BlurHash +import org.thoughtcrime.securesms.contactshare.Contact +import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch +import org.thoughtcrime.securesms.database.documents.NetworkFailure +import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord +import org.thoughtcrime.securesms.database.model.ParentStoryId +import org.thoughtcrime.securesms.database.model.Quote +import org.thoughtcrime.securesms.database.model.ReactionRecord +import org.thoughtcrime.securesms.database.model.StoryType +import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList +import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge +import org.thoughtcrime.securesms.linkpreview.LinkPreview +import org.thoughtcrime.securesms.mms.SlideDeck +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.stickers.StickerLocator +import org.thoughtcrime.securesms.util.MediaUtil + +/** + * Builds MessageRecords and related components for direct usage in unit testing. Does not modify the database. + */ +object FakeMessageRecords { + + fun buildDatabaseAttachment( + attachmentId: AttachmentId = AttachmentId(1, 1), + mmsId: Long = 1, + hasData: Boolean = true, + hasThumbnail: Boolean = true, + contentType: String = MediaUtil.IMAGE_JPEG, + transferProgress: Int = AttachmentDatabase.TRANSFER_PROGRESS_DONE, + size: Long = 0L, + fileName: String = "", + cdnNumber: Int = 1, + location: String = "", + key: String = "", + relay: String = "", + digest: ByteArray = byteArrayOf(), + fastPreflightId: String = "", + voiceNote: Boolean = false, + borderless: Boolean = false, + videoGif: Boolean = false, + width: Int = 0, + height: Int = 0, + quote: Boolean = false, + caption: String? = null, + stickerLocator: StickerLocator? = null, + blurHash: BlurHash? = null, + audioHash: AudioHash? = null, + transformProperties: AttachmentDatabase.TransformProperties? = null, + displayOrder: Int = 0, + uploadTimestamp: Long = 200 + ): DatabaseAttachment { + return DatabaseAttachment( + attachmentId, + mmsId, + hasData, + hasThumbnail, + contentType, + transferProgress, + size, + fileName, + cdnNumber, + location, + key, + relay, + digest, + fastPreflightId, + voiceNote, + borderless, + videoGif, + width, + height, + quote, + caption, + stickerLocator, + blurHash, + audioHash, + transformProperties, + displayOrder, + uploadTimestamp + ) + } + + fun buildLinkPreview( + url: String = "", + title: String = "", + description: String = "", + date: Long = 200, + attachmentId: AttachmentId? = null + ): LinkPreview { + return LinkPreview( + url, + title, + description, + date, + attachmentId + ) + } + + fun buildMediaMmsMessageRecord( + id: Long = 1, + conversationRecipient: Recipient = Recipient.UNKNOWN, + individualRecipient: Recipient = conversationRecipient, + recipientDeviceId: Int = 1, + dateSent: Long = 200, + dateReceived: Long = 400, + dateServer: Long = 300, + deliveryReceiptCount: Int = 0, + threadId: Long = 1, + body: String = "body", + slideDeck: SlideDeck = SlideDeck(), + partCount: Int = slideDeck.slides.count(), + mailbox: Long = MmsSmsColumns.Types.BASE_INBOX_TYPE, + mismatches: Set = emptySet(), + failures: Set = emptySet(), + subscriptionId: Int = -1, + expiresIn: Long = -1, + expireStarted: Long = -1, + viewOnce: Boolean = false, + readReceiptCount: Int = 0, + quote: Quote? = null, + contacts: List = emptyList(), + linkPreviews: List = emptyList(), + unidentified: Boolean = false, + reactions: List = emptyList(), + remoteDelete: Boolean = false, + mentionsSelf: Boolean = false, + notifiedTimestamp: Long = 350, + viewedReceiptCount: Int = 0, + receiptTimestamp: Long = 0, + messageRanges: BodyRangeList? = null, + storyType: StoryType = StoryType.NONE, + parentStoryId: ParentStoryId? = null, + giftBadge: GiftBadge? = null + ): MediaMmsMessageRecord { + return MediaMmsMessageRecord( + id, + conversationRecipient, + individualRecipient, + recipientDeviceId, + dateSent, + dateReceived, + dateServer, + deliveryReceiptCount, + threadId, + body, + slideDeck, + partCount, + mailbox, + mismatches, + failures, + subscriptionId, + expiresIn, + expireStarted, + viewOnce, + readReceiptCount, + quote, + contacts, + linkPreviews, + unidentified, + reactions, + remoteDelete, + mentionsSelf, + notifiedTimestamp, + viewedReceiptCount, + receiptTimestamp, + messageRanges, + storyType, + parentStoryId, + giftBadge + ) + } +} diff --git a/app/src/test/java/org/thoughtcrime/securesms/stories/StoriesTest.kt b/app/src/test/java/org/thoughtcrime/securesms/stories/StoriesTest.kt new file mode 100644 index 0000000000..f96a2ef837 --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/stories/StoriesTest.kt @@ -0,0 +1,116 @@ +package org.thoughtcrime.securesms.stories + +import io.reactivex.rxjava3.plugins.RxJavaPlugins +import io.reactivex.rxjava3.schedulers.TestScheduler +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockedStatic +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.kotlin.any +import org.mockito.kotlin.isA +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.thoughtcrime.securesms.attachments.AttachmentId +import org.thoughtcrime.securesms.database.AttachmentDatabase +import org.thoughtcrime.securesms.database.FakeMessageRecords +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.jobmanager.JobManager +import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob + +class StoriesTest { + + @Rule + @JvmField + val mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Mock + private lateinit var mockAttachmentDatabase: AttachmentDatabase + + @Mock + private lateinit var mockJobManager: JobManager + + @Mock + private lateinit var mockApplicationDependenciesStatic: MockedStatic + + @Mock + private lateinit var mockSignalDatabaseStatic: MockedStatic + + @Mock + private lateinit var mockSignalDatabase: SignalDatabase + + private val testScheduler = TestScheduler() + + @Before + fun setUp() { + RxJavaPlugins.setInitIoSchedulerHandler { testScheduler } + RxJavaPlugins.setIoSchedulerHandler { testScheduler } + + SignalDatabase.setSignalDatabaseInstanceForTesting(mockSignalDatabase) + whenever(SignalDatabase.attachments).thenReturn(mockAttachmentDatabase) + whenever(ApplicationDependencies.getJobManager()).thenReturn(mockJobManager) + whenever(mockAttachmentDatabase.getAttachmentsForMessage(any())).thenReturn(emptyList()) + } + + @After + fun tearDown() { + RxJavaPlugins.reset() + } + + @Test + fun `Given a MessageRecord with no attachments and a LinkPreview without a thumbnail, when I enqueueAttachmentsFromStoryForDownload, then I enqueue nothing`() { + // GIVEN + val messageRecord = FakeMessageRecords.buildMediaMmsMessageRecord( + linkPreviews = listOf(FakeMessageRecords.buildLinkPreview()) + ) + + // WHEN + val testObserver = Stories.enqueueAttachmentsFromStoryForDownload(messageRecord, true).test() + testScheduler.triggerActions() + + // THEN + testObserver.assertComplete() + verify(mockJobManager, never()).add(any()) + } + + @Test + fun `Given a MessageRecord with no attachments and a LinkPreview with a thumbnail, when I enqueueAttachmentsFromStoryForDownload, then I enqueue once`() { + // GIVEN + val messageRecord = FakeMessageRecords.buildMediaMmsMessageRecord( + linkPreviews = listOf( + FakeMessageRecords.buildLinkPreview( + attachmentId = AttachmentId(1, 2) + ) + ) + ) + + // WHEN + val testObserver = Stories.enqueueAttachmentsFromStoryForDownload(messageRecord, true).test() + testScheduler.triggerActions() + + // THEN + testObserver.assertComplete() + verify(mockJobManager).add(isA()) + } + + @Test + fun `Given a MessageRecord with an attachment, when I enqueueAttachmentsFromStoryForDownload, then I enqueue once`() { + // GIVEN + val attachment = FakeMessageRecords.buildDatabaseAttachment() + val messageRecord = FakeMessageRecords.buildMediaMmsMessageRecord() + whenever(mockAttachmentDatabase.getAttachmentsForMessage(any())).thenReturn(listOf(attachment)) + + // WHEN + val testObserver = Stories.enqueueAttachmentsFromStoryForDownload(messageRecord, true).test() + testScheduler.triggerActions() + + // THEN + testObserver.assertComplete() + verify(mockJobManager).add(isA()) + } +}