From fdff11c535c4b77451c06b75a375dd51c4a2f025 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Mon, 17 Apr 2023 18:17:03 +1000 Subject: [PATCH] feat: setup android unit tests for verifying storage behaviours and state of shared configs --- app/build.gradle | 36 ++++----- .../network/loki/messenger/LibSessionTests.kt | 81 +++++++++++++++++++ .../securesms/database/Storage.kt | 2 +- .../service/ExpiringMessageManager.java | 13 +-- gradle.properties | 4 +- libsession/build.gradle | 4 - 6 files changed, 108 insertions(+), 32 deletions(-) create mode 100644 app/src/androidTest/java/network/loki/messenger/LibSessionTests.kt diff --git a/app/build.gradle b/app/build.gradle index 971c67cd1d..02928b761a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -125,37 +125,35 @@ dependencies { implementation "com.opencsv:opencsv:4.6" testImplementation "junit:junit:$junitVersion" testImplementation 'org.assertj:assertj-core:3.11.1' - testImplementation "org.mockito:mockito-inline:4.0.0" + testImplementation "org.mockito:mockito-inline:4.10.0" testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion" - testImplementation 'org.powermock:powermock-api-mockito:1.6.1' - testImplementation 'org.powermock:powermock-module-junit4:1.6.1' - testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1' - testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1' + androidTestImplementation "org.mockito:mockito-android:4.10.0" + androidTestImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion" testImplementation "androidx.test:core:$testCoreVersion" - testImplementation "androidx.arch.core:core-testing:2.1.0" + testImplementation "androidx.arch.core:core-testing:2.2.0" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion" // Core library - androidTestImplementation 'androidx.test:core:1.4.0' + androidTestImplementation "androidx.test:core:$testCoreVersion" // AndroidJUnitRunner and JUnit Rules - androidTestImplementation 'androidx.test:runner:1.4.0' - androidTestImplementation 'androidx.test:rules:1.4.0' + androidTestImplementation 'androidx.test:runner:1.5.2' + androidTestImplementation 'androidx.test:rules:1.5.0' // Assertions - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.ext:truth:1.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.ext:truth:1.5.0' androidTestImplementation 'com.google.truth:truth:1.1.3' // Espresso dependencies - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0' - androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0' - androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.4.0' - androidTestImplementation 'androidx.test.espresso:espresso-web:3.4.0' - androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.4.0' - androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.4.0' - androidTestUtil 'androidx.test:orchestrator:1.4.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-web:3.5.1' + androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.5.1' + androidTestUtil 'androidx.test:orchestrator:1.4.2' testImplementation 'org.robolectric:robolectric:4.4' testImplementation 'org.robolectric:shadows-multidex:4.4' diff --git a/app/src/androidTest/java/network/loki/messenger/LibSessionTests.kt b/app/src/androidTest/java/network/loki/messenger/LibSessionTests.kt new file mode 100644 index 0000000000..a47175f5d7 --- /dev/null +++ b/app/src/androidTest/java/network/loki/messenger/LibSessionTests.kt @@ -0,0 +1,81 @@ +package network.loki.messenger + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import network.loki.messenger.libsession_util.Contacts +import network.loki.messenger.libsession_util.util.Contact +import network.loki.messenger.libsession_util.util.ExpiryMode +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.utilities.KeyHelper +import org.session.libsignal.utilities.hexEncodedPublicKey +import org.thoughtcrime.securesms.ApplicationContext +import org.thoughtcrime.securesms.crypto.KeyPairUtilities +import kotlin.random.Random + +@RunWith(AndroidJUnit4::class) +@SmallTest +class LibSessionTests { + + private fun randomSeedBytes() = (0 until 16).map { Random.nextInt(UByte.MAX_VALUE.toInt()).toByte() } + private fun randomKeyPair() = KeyPairUtilities.generate(randomSeedBytes().toByteArray()) + private fun randomSessionId() = randomKeyPair().x25519KeyPair.hexEncodedPublicKey + + private fun maybeGetUserInfo(): Pair? { + val appContext = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as ApplicationContext + val prefs = appContext.prefs + val localUserPublicKey = prefs.getLocalNumber() + val secretKey = with(appContext) { + val edKey = KeyPairUtilities.getUserED25519KeyPair(this) ?: return null + edKey.secretKey.asBytes + } + return if (localUserPublicKey == null || secretKey == null) null + else secretKey to localUserPublicKey + } + + private fun buildContactMessage(contactList: List): ByteArray { + val (key,_) = maybeGetUserInfo()!! + val contacts = Contacts.Companion.newInstance(key) + contactList.forEach { contact -> + contacts.set(contact) + } + return contacts.dump() + } + + @Before + fun setupUser() { + val newBytes = randomSeedBytes().toByteArray() + val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext + val kp = KeyPairUtilities.generate(newBytes) + KeyPairUtilities.store(context, kp.seed, kp.ed25519KeyPair, kp.x25519KeyPair) + val registrationID = KeyHelper.generateRegistrationId(false) + TextSecurePreferences.setLocalRegistrationId(context, registrationID) + TextSecurePreferences.setLocalNumber(context, kp.x25519KeyPair.hexEncodedPublicKey) + TextSecurePreferences.setRestorationTime(context, 0) + TextSecurePreferences.setHasViewedSeed(context, false) + } + + @Test + fun migration_one_to_ones() { + val storageSpy = spy(MessagingModuleConfiguration.shared.storage) + + val newContactId = randomSessionId() + val singleContact = Contact( + id = newContactId, + approved = true, + expiryMode = ExpiryMode.NONE + ) + val newContactMerge = buildContactMessage(listOf(singleContact)) + MessagingModuleConfiguration.shared.configFactory.contacts!!.merge("abc123" to newContactMerge) + + verify(storageSpy).addLibSessionContacts(any()) + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 18309249c7..4be84d2191 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -89,7 +89,7 @@ import org.thoughtcrime.securesms.util.SessionMetaProtocol import java.security.MessageDigest import network.loki.messenger.libsession_util.util.Contact as LibSessionContact -class Storage(context: Context, helper: SQLCipherOpenHelper, private val configFactory: ConfigFactory) : Database(context, helper), StorageProtocol, +open class Storage(context: Context, helper: SQLCipherOpenHelper, private val configFactory: ConfigFactory) : Database(context, helper), StorageProtocol, ThreadDatabase.ConversationThreadUpdateListener { init { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java index 532477b646..d671de96cb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -17,6 +17,7 @@ import org.session.libsignal.messages.SignalServiceGroup; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.guava.Optional; import org.thoughtcrime.securesms.database.MmsDatabase; +import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; @@ -37,12 +38,14 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM private final SmsDatabase smsDatabase; private final MmsDatabase mmsDatabase; + private final MmsSmsDatabase mmsSmsDatabase; private final Context context; public ExpiringMessageManager(Context context) { this.context = context.getApplicationContext(); this.smsDatabase = DatabaseComponent.get(context).smsDatabase(); this.mmsDatabase = DatabaseComponent.get(context).mmsDatabase(); + this.mmsSmsDatabase = DatabaseComponent.get(context).mmsSmsDatabase(); executor.execute(new LoadTask()); executor.execute(new ProcessTask()); @@ -81,12 +84,11 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM } if (message.getId() != null) { - DatabaseComponent.get(context).smsDatabase().deleteMessage(message.getId()); + smsDatabase.deleteMessage(message.getId()); } } private void insertIncomingExpirationTimerMessage(ExpirationTimerUpdate message) { - MmsDatabase database = DatabaseComponent.get(context).mmsDatabase(); String senderPublicKey = message.getSender(); Long sentTimestamp = message.getSentTimestamp(); @@ -126,7 +128,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM Optional.absent(), Optional.absent()); //insert the timer update message - database.insertSecureDecryptedMessageInbox(mediaMessage, threadId, true); + mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, threadId, true); //set the timer to the conversation MessagingModuleConfiguration.getShared().getStorage().setExpirationTimer(recipient.getAddress().serialize(), duration); @@ -137,7 +139,6 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM } private void insertOutgoingExpirationTimerMessage(ExpirationTimerUpdate message) { - MmsDatabase database = DatabaseComponent.get(context).mmsDatabase(); Long sentTimestamp = message.getSentTimestamp(); String groupId = message.getGroupPublicKey(); @@ -152,7 +153,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM try { OutgoingExpirationUpdateMessage timerUpdateMessage = new OutgoingExpirationUpdateMessage(recipient, sentTimestamp, duration * 1000L, groupId); - database.insertSecureDecryptedMessageOutbox(timerUpdateMessage, -1, sentTimestamp, true); + mmsDatabase.insertSecureDecryptedMessageOutbox(timerUpdateMessage, -1, sentTimestamp, true); if (groupId != null) { // we need the group ID as recipient for setExpireMessages below @@ -173,7 +174,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM @Override public void startAnyExpiration(long timestamp, @NotNull String author) { - MessageRecord messageRecord = DatabaseComponent.get(context).mmsSmsDatabase().getMessageFor(timestamp, author); + MessageRecord messageRecord = mmsSmsDatabase.getMessageFor(timestamp, author); if (messageRecord != null) { boolean mms = messageRecord.isMms(); Recipient recipient = messageRecord.getRecipient(); diff --git a/gradle.properties b/gradle.properties index 5738c0772a..928c64b38b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,6 +21,6 @@ preferenceVersion=1.2.0 coreVersion=1.8.0 junitVersion=4.13.2 -mockitoKotlinVersion=4.0.0 -testCoreVersion=1.4.0 +mockitoKotlinVersion=4.1.0 +testCoreVersion=1.5.0 pagingVersion=3.0.0 \ No newline at end of file diff --git a/libsession/build.gradle b/libsession/build.gradle index eeb9b91f6c..045648e090 100644 --- a/libsession/build.gradle +++ b/libsession/build.gradle @@ -46,10 +46,6 @@ dependencies { testImplementation 'org.assertj:assertj-core:3.11.1' testImplementation "org.mockito:mockito-inline:4.0.0" testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion" - testImplementation 'org.powermock:powermock-api-mockito:1.6.1' - testImplementation 'org.powermock:powermock-module-junit4:1.6.1' - testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1' - testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1' testImplementation "androidx.test:core:$testCoreVersion" testImplementation "androidx.arch.core:core-testing:2.1.0" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"