Fix avatar loading in OS views when app is not running.

This commit is contained in:
Cody Henthorne
2024-08-19 14:10:20 -04:00
committed by mtang-signal
parent 8a4d9fc635
commit 71b5a9f865
22 changed files with 384 additions and 439 deletions

View File

@@ -153,6 +153,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
DatabaseSecretProvider.getOrCreateDatabaseSecret(this), DatabaseSecretProvider.getOrCreateDatabaseSecret(this),
AttachmentSecretProvider.getInstance(this).getOrCreateAttachmentSecret()); AttachmentSecretProvider.getInstance(this).getOrCreateAttachmentSecret());
}) })
.addBlocking("signal-store", () -> SignalStore.init(this))
.addBlocking("logging", () -> { .addBlocking("logging", () -> {
initializeLogging(); initializeLogging();
Log.i(TAG, "onCreate()"); Log.i(TAG, "onCreate()");

View File

@@ -188,7 +188,7 @@ object BackupRepository {
} }
val db = KeyValueDatabase.createWithName(context, "$baseName.db") val db = KeyValueDatabase.createWithName(context, "$baseName.db")
SignalStore(KeyValueStore(db)) SignalStore(context, KeyValueStore(db))
} }
} }

View File

@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.emoji package org.thoughtcrime.securesms.emoji
import android.net.Uri import android.net.Uri
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import org.thoughtcrime.securesms.components.emoji.Emoji import org.thoughtcrime.securesms.components.emoji.Emoji
import org.thoughtcrime.securesms.components.emoji.EmojiPageModel import org.thoughtcrime.securesms.components.emoji.EmojiPageModel
@@ -122,7 +123,8 @@ class EmojiSource(
} }
} }
private fun loadAssetBasedEmojis(): EmojiSource { @VisibleForTesting
fun loadAssetBasedEmojis(): EmojiSource {
val emojiData: InputStream = AppDependencies.application.assets.open("emoji/emoji_data.json") val emojiData: InputStream = AppDependencies.application.assets.open("emoji/emoji_data.json")
emojiData.use { emojiData.use {

View File

@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.preference.PreferenceManager import android.preference.PreferenceManager
import androidx.annotation.VisibleForTesting
import org.signal.core.util.Base64 import org.signal.core.util.Base64
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.IdentityKey import org.signal.libsignal.protocol.IdentityKey
@@ -73,17 +72,10 @@ class AccountValues internal constructor(store: KeyValueStore) : SignalStoreValu
private const val KEY_USERNAME_SYNC_STATE = "phoneNumberPrivacy.usernameSyncState" private const val KEY_USERNAME_SYNC_STATE = "phoneNumberPrivacy.usernameSyncState"
private const val KEY_USERNAME_SYNC_ERROR_COUNT = "phoneNumberPrivacy.usernameErrorCount" private const val KEY_USERNAME_SYNC_ERROR_COUNT = "phoneNumberPrivacy.usernameErrorCount"
@VisibleForTesting private const val KEY_E164 = "account.e164"
const val KEY_E164 = "account.e164" private const val KEY_ACI = "account.aci"
private const val KEY_PNI = "account.pni"
@VisibleForTesting private const val KEY_IS_REGISTERED = "account.is_registered"
const val KEY_ACI = "account.aci"
@VisibleForTesting
const val KEY_PNI = "account.pni"
@VisibleForTesting
const val KEY_IS_REGISTERED = "account.is_registered"
} }
init { init {

View File

@@ -1,7 +1,5 @@
package org.thoughtcrime.securesms.keyvalue package org.thoughtcrime.securesms.keyvalue
import androidx.annotation.VisibleForTesting
/** /**
* Values for managing enable/disable state and corresponding alerts for Notification Profiles. * Values for managing enable/disable state and corresponding alerts for Notification Profiles.
*/ */
@@ -12,14 +10,9 @@ class NotificationProfileValues(store: KeyValueStore) : SignalStoreValues(store)
private const val KEY_LAST_PROFILE_POPUP_TIME = "np.last_profile_popup_time" private const val KEY_LAST_PROFILE_POPUP_TIME = "np.last_profile_popup_time"
private const val KEY_SEEN_TOOLTIP = "np.seen_tooltip" private const val KEY_SEEN_TOOLTIP = "np.seen_tooltip"
@VisibleForTesting private const val KEY_MANUALLY_ENABLED_PROFILE = "np.manually_enabled_profile"
const val KEY_MANUALLY_ENABLED_PROFILE = "np.manually_enabled_profile" private const val KEY_MANUALLY_ENABLED_UNTIL = "np.manually_enabled_until"
private const val KEY_MANUALLY_DISABLED_AT = "np.manually_disabled_at"
@VisibleForTesting
const val KEY_MANUALLY_ENABLED_UNTIL = "np.manually_enabled_until"
@VisibleForTesting
const val KEY_MANUALLY_DISABLED_AT = "np.manually_disabled_at"
} }
public override fun onFirstEverAppLaunch() = Unit public override fun onFirstEverAppLaunch() = Unit

View File

@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.keyvalue package org.thoughtcrime.securesms.keyvalue
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@@ -33,6 +32,7 @@ class PaymentsValues internal constructor(store: KeyValueStore) : SignalStoreVal
companion object { companion object {
private val TAG = Log.tag(PaymentsValues::class.java) private val TAG = Log.tag(PaymentsValues::class.java)
private const val MOB_PAYMENTS_ENABLED = "mob_payments_enabled"
private const val PAYMENTS_ENTROPY = "payments_entropy" private const val PAYMENTS_ENTROPY = "payments_entropy"
private const val MOB_LEDGER = "mob_ledger" private const val MOB_LEDGER = "mob_ledger"
private const val PAYMENTS_CURRENT_CURRENCY = "payments_current_currency" private const val PAYMENTS_CURRENT_CURRENCY = "payments_current_currency"
@@ -50,9 +50,6 @@ class PaymentsValues internal constructor(store: KeyValueStore) : SignalStoreVal
private const val SHOW_SAVE_RECOVERY_PHRASE = "mob_show_save_recovery_phrase" private const val SHOW_SAVE_RECOVERY_PHRASE = "mob_show_save_recovery_phrase"
private val LARGE_BALANCE_THRESHOLD = Money.mobileCoin(BigDecimal.valueOf(500)) private val LARGE_BALANCE_THRESHOLD = Money.mobileCoin(BigDecimal.valueOf(500))
@VisibleForTesting
const val MOB_PAYMENTS_ENABLED = "mob_payments_enabled"
} }
@get:JvmName("isPaymentLockEnabled") @get:JvmName("isPaymentLockEnabled")

View File

@@ -1,15 +1,14 @@
package org.thoughtcrime.securesms.keyvalue package org.thoughtcrime.securesms.keyvalue
import android.app.Application
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.preference.PreferenceDataStore import androidx.preference.PreferenceDataStore
import org.signal.core.util.ResettableLazy
import org.thoughtcrime.securesms.database.KeyValueDatabase import org.thoughtcrime.securesms.database.KeyValueDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies.application
/** /**
* Simple, encrypted key-value store. * Simple, encrypted key-value store.
*/ */
class SignalStore(private val store: KeyValueStore) { class SignalStore(context: Application, private val store: KeyValueStore) {
val accountValues = AccountValues(store) val accountValues = AccountValues(store)
val svrValues = SvrValues(store) val svrValues = SvrValues(store)
@@ -39,15 +38,22 @@ class SignalStore(private val store: KeyValueStore) {
val apkUpdateValues = ApkUpdateValues(store) val apkUpdateValues = ApkUpdateValues(store)
val backupValues = BackupValues(store) val backupValues = BackupValues(store)
val plainTextValues = PlainTextSharedPrefsDataStore(application) val plainTextValues = PlainTextSharedPrefsDataStore(context)
companion object { companion object {
private var instanceOverride: SignalStore? = null private var instance: SignalStore? = null
private val _instance = ResettableLazy {
instanceOverride ?: SignalStore(KeyValueStore(KeyValueDatabase.getInstance(application))) @JvmStatic
fun init(context: Application) {
if (instance == null) {
synchronized(SignalStore::class.java) {
if (instance == null) {
instance = SignalStore(context, KeyValueStore(KeyValueDatabase.getInstance(context)))
}
}
}
} }
private val instance by _instance
@JvmStatic @JvmStatic
fun onFirstEverAppLaunch() { fun onFirstEverAppLaunch() {
@@ -114,7 +120,7 @@ class SignalStore(private val store: KeyValueStore) {
*/ */
@VisibleForTesting @VisibleForTesting
fun resetCache() { fun resetCache() {
instance.store.resetCache() instance!!.store.resetCache()
} }
/** /**
@@ -122,144 +128,144 @@ class SignalStore(private val store: KeyValueStore) {
*/ */
@JvmStatic @JvmStatic
fun onPostBackupRestore() { fun onPostBackupRestore() {
instance.store.resetCache() instance!!.store.resetCache()
} }
@JvmStatic @JvmStatic
@get:JvmName("account") @get:JvmName("account")
val account: AccountValues val account: AccountValues
get() = instance.accountValues get() = instance!!.accountValues
@JvmStatic @JvmStatic
@get:JvmName("svr") @get:JvmName("svr")
val svr: SvrValues val svr: SvrValues
get() = instance.svrValues get() = instance!!.svrValues
@JvmStatic @JvmStatic
@get:JvmName("registration") @get:JvmName("registration")
val registration: RegistrationValues val registration: RegistrationValues
get() = instance.registrationValues get() = instance!!.registrationValues
@JvmStatic @JvmStatic
@get:JvmName("pin") @get:JvmName("pin")
val pin: PinValues val pin: PinValues
get() = instance.pinValues get() = instance!!.pinValues
val remoteConfig: RemoteConfigValues val remoteConfig: RemoteConfigValues
get() = instance.remoteConfigValues get() = instance!!.remoteConfigValues
@JvmStatic @JvmStatic
@get:JvmName("storageService") @get:JvmName("storageService")
val storageService: StorageServiceValues val storageService: StorageServiceValues
get() = instance.storageServiceValues get() = instance!!.storageServiceValues
@JvmStatic @JvmStatic
@get:JvmName("uiHints") @get:JvmName("uiHints")
val uiHints: UiHintValues val uiHints: UiHintValues
get() = instance.uiHintValues get() = instance!!.uiHintValues
@JvmStatic @JvmStatic
@get:JvmName("tooltips") @get:JvmName("tooltips")
val tooltips: TooltipValues val tooltips: TooltipValues
get() = instance.tooltipValues get() = instance!!.tooltipValues
@JvmStatic @JvmStatic
@get:JvmName("misc") @get:JvmName("misc")
val misc: MiscellaneousValues val misc: MiscellaneousValues
get() = instance.miscValues get() = instance!!.miscValues
@JvmStatic @JvmStatic
@get:JvmName("internal") @get:JvmName("internal")
val internal: InternalValues val internal: InternalValues
get() = instance.internalValues get() = instance!!.internalValues
@JvmStatic @JvmStatic
@get:JvmName("emoji") @get:JvmName("emoji")
val emoji: EmojiValues val emoji: EmojiValues
get() = instance.emojiValues get() = instance!!.emojiValues
@JvmStatic @JvmStatic
@get:JvmName("settings") @get:JvmName("settings")
val settings: SettingsValues val settings: SettingsValues
get() = instance.settingsValues get() = instance!!.settingsValues
@JvmStatic @JvmStatic
@get:JvmName("certificate") @get:JvmName("certificate")
val certificate: CertificateValues val certificate: CertificateValues
get() = instance.certificateValues get() = instance!!.certificateValues
@JvmStatic @JvmStatic
@get:JvmName("phoneNumberPrivacy") @get:JvmName("phoneNumberPrivacy")
val phoneNumberPrivacy: PhoneNumberPrivacyValues val phoneNumberPrivacy: PhoneNumberPrivacyValues
get() = instance.phoneNumberPrivacyValues get() = instance!!.phoneNumberPrivacyValues
@JvmStatic @JvmStatic
@get:JvmName("onboarding") @get:JvmName("onboarding")
val onboarding: OnboardingValues val onboarding: OnboardingValues
get() = instance.onboardingValues get() = instance!!.onboardingValues
@JvmStatic @JvmStatic
@get:JvmName("wallpaper") @get:JvmName("wallpaper")
val wallpaper: WallpaperValues val wallpaper: WallpaperValues
get() = instance.wallpaperValues get() = instance!!.wallpaperValues
@JvmStatic @JvmStatic
@get:JvmName("payments") @get:JvmName("payments")
val payments: PaymentsValues val payments: PaymentsValues
get() = instance.paymentsValues get() = instance!!.paymentsValues
@JvmStatic @JvmStatic
@get:JvmName("inAppPayments") @get:JvmName("inAppPayments")
val inAppPayments: InAppPaymentValues val inAppPayments: InAppPaymentValues
get() = instance.inAppPaymentValues get() = instance!!.inAppPaymentValues
@JvmStatic @JvmStatic
@get:JvmName("proxy") @get:JvmName("proxy")
val proxy: ProxyValues val proxy: ProxyValues
get() = instance.proxyValues get() = instance!!.proxyValues
@JvmStatic @JvmStatic
@get:JvmName("rateLimit") @get:JvmName("rateLimit")
val rateLimit: RateLimitValues val rateLimit: RateLimitValues
get() = instance.rateLimitValues get() = instance!!.rateLimitValues
@JvmStatic @JvmStatic
@get:JvmName("chatColors") @get:JvmName("chatColors")
val chatColors: ChatColorsValues val chatColors: ChatColorsValues
get() = instance.chatColorsValues get() = instance!!.chatColorsValues
val imageEditor: ImageEditorValues val imageEditor: ImageEditorValues
get() = instance.imageEditorValues get() = instance!!.imageEditorValues
val notificationProfile: NotificationProfileValues val notificationProfile: NotificationProfileValues
get() = instance.notificationProfileValues get() = instance!!.notificationProfileValues
@JvmStatic @JvmStatic
@get:JvmName("releaseChannel") @get:JvmName("releaseChannel")
val releaseChannel: ReleaseChannelValues val releaseChannel: ReleaseChannelValues
get() = instance.releaseChannelValues get() = instance!!.releaseChannelValues
@JvmStatic @JvmStatic
@get:JvmName("story") @get:JvmName("story")
val story: StoryValues val story: StoryValues
get() = instance.storyValues get() = instance!!.storyValues
val apkUpdate: ApkUpdateValues val apkUpdate: ApkUpdateValues
get() = instance.apkUpdateValues get() = instance!!.apkUpdateValues
@JvmStatic @JvmStatic
@get:JvmName("backup") @get:JvmName("backup")
val backup: BackupValues val backup: BackupValues
get() = instance.backupValues get() = instance!!.backupValues
val groupsV2AciAuthorizationCache: GroupsV2AuthorizationSignalStoreCache val groupsV2AciAuthorizationCache: GroupsV2AuthorizationSignalStoreCache
get() = GroupsV2AuthorizationSignalStoreCache.createAciCache(instance.store) get() = GroupsV2AuthorizationSignalStoreCache.createAciCache(instance!!.store)
val plaintext: PlainTextSharedPrefsDataStore val plaintext: PlainTextSharedPrefsDataStore
get() = instance.plainTextValues get() = instance!!.plainTextValues
fun getPreferenceDataStore(): PreferenceDataStore { fun getPreferenceDataStore(): PreferenceDataStore {
return SignalPreferenceDataStore(instance.store) return SignalPreferenceDataStore(instance!!.store)
} }
/** /**
@@ -268,25 +274,7 @@ class SignalStore(private val store: KeyValueStore) {
*/ */
@JvmStatic @JvmStatic
fun blockUntilAllWritesFinished() { fun blockUntilAllWritesFinished() {
instance.store.blockUntilAllWritesFinished() instance!!.store.blockUntilAllWritesFinished()
}
/**
* Allows you to set a custom KeyValueStore to read from. Only for testing!
*/
@VisibleForTesting
fun testInject(store: KeyValueStore) {
instanceOverride = SignalStore(store)
_instance.reset()
}
/**
* Allows you to set a custom SignalStore to read from. Only for testing!
*/
@VisibleForTesting
fun testInject(store: SignalStore) {
instanceOverride = store
_instance.reset()
} }
} }
} }

View File

@@ -5,6 +5,7 @@
package org.thoughtcrime.securesms.providers package org.thoughtcrime.securesms.providers
import android.app.Application
import android.content.ContentUris import android.content.ContentUris
import android.content.ContentValues import android.content.ContentValues
import android.content.Intent import android.content.Intent
@@ -15,10 +16,16 @@ import android.net.Uri
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BuildConfig import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.SqlCipherLibraryLoader
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.profiles.AvatarHelper import org.thoughtcrime.securesms.profiles.AvatarHelper
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientCreator
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.KeyCachingService import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.util.AdaptiveBitmapMetrics import org.thoughtcrime.securesms.util.AdaptiveBitmapMetrics
@@ -54,6 +61,21 @@ class AvatarProvider : BaseContentProvider() {
} }
} }
private fun init(): Application? {
val application = context as? ApplicationContext ?: return null
SqlCipherLibraryLoader.load()
SignalDatabase.init(
application,
DatabaseSecretProvider.getOrCreateDatabaseSecret(application),
AttachmentSecretProvider.getInstance(application).getOrCreateAttachmentSecret()
)
SignalStore.init(application)
return application
}
override fun onCreate(): Boolean { override fun onCreate(): Boolean {
if (VERBOSE) Log.i(TAG, "onCreate called") if (VERBOSE) Log.i(TAG, "onCreate called")
return true return true
@@ -63,7 +85,9 @@ class AvatarProvider : BaseContentProvider() {
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
if (VERBOSE) Log.i(TAG, "openFile() called!") if (VERBOSE) Log.i(TAG, "openFile() called!")
if (KeyCachingService.isLocked(context)) { val application = init() ?: return null
if (KeyCachingService.isLocked(application)) {
Log.w(TAG, "masterSecret was null, abandoning.") Log.w(TAG, "masterSecret was null, abandoning.")
return null return null
} }
@@ -76,7 +100,7 @@ class AvatarProvider : BaseContentProvider() {
if (uriMatcher.match(uri) == AVATAR) { if (uriMatcher.match(uri) == AVATAR) {
if (VERBOSE) Log.i(TAG, "Loading avatar.") if (VERBOSE) Log.i(TAG, "Loading avatar.")
try { try {
val recipient = getRecipientId(uri)?.let { Recipient.resolved(it) } ?: return null val recipient = getRecipientId(uri)?.let { RecipientCreator.forRecord(application, SignalDatabase.recipients.getRecord(it)) } ?: return null
return getParcelFileDescriptorForAvatar(recipient) return getParcelFileDescriptorForAvatar(recipient)
} catch (ioe: IOException) { } catch (ioe: IOException) {
Log.w(TAG, ioe) Log.w(TAG, ioe)
@@ -91,6 +115,8 @@ class AvatarProvider : BaseContentProvider() {
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? { override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
if (VERBOSE) Log.i(TAG, "query() called: $uri") if (VERBOSE) Log.i(TAG, "query() called: $uri")
val application = init() ?: return null
if (SignalDatabase.instance == null) { if (SignalDatabase.instance == null) {
Log.w(TAG, "SignalDatabase unavailable") Log.w(TAG, "SignalDatabase unavailable")
return null return null
@@ -99,8 +125,8 @@ class AvatarProvider : BaseContentProvider() {
if (uriMatcher.match(uri) == AVATAR) { if (uriMatcher.match(uri) == AVATAR) {
val recipientId = getRecipientId(uri) ?: return null val recipientId = getRecipientId(uri) ?: return null
if (AvatarHelper.hasAvatar(context!!, recipientId)) { if (AvatarHelper.hasAvatar(application, recipientId)) {
val file: File = AvatarHelper.getAvatarFile(context!!, recipientId) val file: File = AvatarHelper.getAvatarFile(application, recipientId)
if (file.exists()) { if (file.exists()) {
return createCursor(projection, file.name, file.length()) return createCursor(projection, file.name, file.length())
} }
@@ -115,6 +141,8 @@ class AvatarProvider : BaseContentProvider() {
override fun getType(uri: Uri): String? { override fun getType(uri: Uri): String? {
if (VERBOSE) Log.i(TAG, "getType() called: $uri") if (VERBOSE) Log.i(TAG, "getType() called: $uri")
init() ?: return null
if (SignalDatabase.instance == null) { if (SignalDatabase.instance == null) {
Log.w(TAG, "SignalDatabase unavailable") Log.w(TAG, "SignalDatabase unavailable")
return null return null

View File

@@ -9,24 +9,15 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import com.annimon.stream.Stream;
import org.signal.core.util.ThreadUtil; import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.database.CallLinkTable;
import org.thoughtcrime.securesms.database.DistributionListTables; import org.thoughtcrime.securesms.database.DistributionListTables;
import org.thoughtcrime.securesms.database.GroupTable; import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.DistributionListRecord;
import org.thoughtcrime.securesms.database.model.RecipientRecord;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@@ -198,62 +189,11 @@ public final class LiveRecipient {
} }
private @NonNull Recipient fetchAndCacheRecipientFromDisk(@NonNull RecipientId id) { private @NonNull Recipient fetchAndCacheRecipientFromDisk(@NonNull RecipientId id) {
RecipientRecord record = recipientTable.getRecord(id); Recipient recipient = RecipientCreator.forRecord(context, recipientTable.getRecord(id));
Recipient recipient;
if (record.getGroupId() != null) {
recipient = getGroupRecipientDetails(record);
} else if (record.getDistributionListId() != null) {
recipient = getDistributionListRecipientDetails(record);
} else if (record.getCallLinkRoomId() != null) {
recipient = getCallLinkRecipientDetails(record);
} else {
recipient = RecipientCreator.forIndividual(context, record);
}
RecipientIdCache.INSTANCE.put(recipient); RecipientIdCache.INSTANCE.put(recipient);
return recipient; return recipient;
} }
@WorkerThread
private @NonNull Recipient getGroupRecipientDetails(@NonNull RecipientRecord record) {
Optional<GroupRecord> groupRecord = groupDatabase.getGroup(record.getId());
if (groupRecord.isPresent()) {
return RecipientCreator.forGroup(groupRecord.get(), record);
} else {
return RecipientCreator.forUnknownGroup(record.getId(), record.getGroupId());
}
}
@WorkerThread
private @NonNull Recipient getDistributionListRecipientDetails(@NonNull RecipientRecord record) {
DistributionListRecord groupRecord = distributionListTables.getList(Objects.requireNonNull(record.getDistributionListId()));
// TODO [stories] We'll have to see what the perf is like for very large distribution lists. We may not be able to support fetching all the members.
if (groupRecord != null) {
String title = groupRecord.isUnknown() ? null : groupRecord.getName();
List<RecipientId> members = Stream.of(groupRecord.getMembers()).filterNot(RecipientId::isUnknown).toList();
return RecipientCreator.forDistributionList(title, members, record);
}
return RecipientCreator.forDistributionList(null, null, record);
}
@WorkerThread
private @NonNull Recipient getCallLinkRecipientDetails(@NonNull RecipientRecord record) {
CallLinkTable.CallLink callLink = SignalDatabase.callLinks().getCallLinkByRoomId(Objects.requireNonNull(record.getCallLinkRoomId()));
if (callLink != null) {
String name = callLink.getState().getName();
return RecipientCreator.forCallLink(name, record, callLink.getAvatarColor());
}
return RecipientCreator.forCallLink(null, record, AvatarColor.UNKNOWN);
}
synchronized void set(@NonNull Recipient recipient) { synchronized void set(@NonNull Recipient recipient) {
this.recipient.set(recipient); this.recipient.set(recipient);
this.liveData.postValue(recipient); this.liveData.postValue(recipient);

View File

@@ -2,8 +2,10 @@ package org.thoughtcrime.securesms.recipients
import android.content.Context import android.content.Context
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import org.thoughtcrime.securesms.conversation.colors.AvatarColor import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState import org.thoughtcrime.securesms.database.RecipientTable.RegisteredState
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.GroupRecord import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.database.model.RecipientRecord import org.thoughtcrime.securesms.database.model.RecipientRecord
import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupId
@@ -101,6 +103,22 @@ object RecipientCreator {
) )
} }
@JvmStatic
@WorkerThread
fun forRecord(context: Context, record: RecipientRecord): Recipient {
val recipient = if (record.groupId != null) {
getGroupRecipientDetails(record)
} else if (record.distributionListId != null) {
getDistributionListRecipientDetails(record)
} else if (record.callLinkRoomId != null) {
getCallLinkRecipientDetails(record)
} else {
forIndividual(context, record)
}
return recipient
}
@JvmStatic @JvmStatic
fun forUnknown(): Recipient { fun forUnknown(): Recipient {
return Recipient.UNKNOWN return Recipient.UNKNOWN
@@ -186,4 +204,43 @@ object RecipientCreator {
note = record.note note = record.note
) )
} }
@WorkerThread
private fun getGroupRecipientDetails(record: RecipientRecord): Recipient {
val groupRecord = SignalDatabase.groups.getGroup(record.id)
return if (groupRecord.isPresent) {
forGroup(groupRecord.get(), record)
} else {
forUnknownGroup(record.id, record.groupId)
}
}
@WorkerThread
private fun getDistributionListRecipientDetails(record: RecipientRecord): Recipient {
val groupRecord = SignalDatabase.distributionLists.getList(record.distributionListId!!)
// TODO [stories] We'll have to see what the perf is like for very large distribution lists. We may not be able to support fetching all the members.
if (groupRecord != null) {
val title = if (groupRecord.isUnknown) null else groupRecord.name
val members = groupRecord.members.filterNot { it.isUnknown }
return forDistributionList(title, members, record)
}
return forDistributionList(null, null, record)
}
@WorkerThread
private fun getCallLinkRecipientDetails(record: RecipientRecord): Recipient {
val callLink = SignalDatabase.callLinks.getCallLinkByRoomId(record.callLinkRoomId!!)
if (callLink != null) {
val name = callLink.state.name
return forCallLink(name, record, callLink.avatarColor)
}
return forCallLink(null, record, AvatarColor.UNKNOWN)
}
} }

View File

@@ -1,42 +0,0 @@
package org.thoughtcrime.securesms
import androidx.test.core.app.ApplicationProvider
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet
import org.thoughtcrime.securesms.keyvalue.KeyValueStore
import org.thoughtcrime.securesms.keyvalue.MockKeyValuePersistentStorage
import org.thoughtcrime.securesms.keyvalue.SignalStore
/**
* Rule to setup [SignalStore] with a mock [KeyValueDataSet]. Must be used with Roboelectric.
*
* Can provide [defaultValues] to set the same values before each test and use [dataSet] directly to add any
* test specific values.
*
* The [dataSet] is reset at the beginning of each test to an empty state.
*/
class SignalStoreRule @JvmOverloads constructor(private val defaultValues: KeyValueDataSet.() -> Unit = {}) : TestRule {
var dataSet = KeyValueDataSet()
private set
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
if (!AppDependencies.isInitialized) {
AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider())
}
dataSet = KeyValueDataSet()
SignalStore.testInject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(dataSet)))
defaultValues.invoke(dataSet)
base.evaluate()
}
}
}
}

View File

@@ -2,50 +2,34 @@ package org.thoughtcrime.securesms.components.emoji
import android.app.Application import android.app.Application
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import io.mockk.every
import io.mockk.mockkObject
import org.junit.Assert import org.junit.Assert
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.MockedStatic
import org.mockito.Mockito
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.robolectric.ParameterizedRobolectricTestRunner import org.robolectric.ParameterizedRobolectricTestRunner
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.emoji.EmojiSource.Companion.refresh import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet import org.thoughtcrime.securesms.emoji.EmojiSource
import org.thoughtcrime.securesms.keyvalue.KeyValueStore
import org.thoughtcrime.securesms.keyvalue.MockKeyValuePersistentStorage
import org.thoughtcrime.securesms.keyvalue.SignalStore
@RunWith(ParameterizedRobolectricTestRunner::class) @RunWith(ParameterizedRobolectricTestRunner::class)
@Config(manifest = Config.NONE, application = Application::class) @Config(manifest = Config.NONE, application = Application::class)
class EmojiUtilTest_isEmoji(private val input: String?, private val output: Boolean) { class EmojiUtilTest_isEmoji(private val input: String?, private val output: Boolean) {
@Rule
@JvmField
val rule: MockitoRule = MockitoJUnit.rule()
@Mock
private val applicationDependenciesMockedStatic: MockedStatic<AppDependencies>? = null
@Mock
private val attachmentSecretProviderMockedStatic: MockedStatic<AttachmentSecretProvider>? = null
@Throws(Exception::class) @Throws(Exception::class)
@Test @Test
fun isEmoji() { fun isEmoji() {
val application = ApplicationProvider.getApplicationContext<Application>() if (!AppDependencies.isInitialized) {
AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider())
}
Mockito.`when`(AppDependencies.application).thenReturn(application) val source = EmojiSource.loadAssetBasedEmojis()
Mockito.`when`(AttachmentSecretProvider.getInstance(ArgumentMatchers.any())).thenThrow(RuntimeException::class.java)
SignalStore.testInject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(KeyValueDataSet())))
refresh()
Assert.assertEquals(output, EmojiUtil.isEmoji(input)) mockkObject(EmojiSource) {
every { EmojiSource.latest } returns source
Assert.assertEquals(output, EmojiUtil.isEmoji(input))
}
} }
companion object { companion object {

View File

@@ -4,6 +4,8 @@ import android.app.Application
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import io.mockk.every import io.mockk.every
import io.mockk.mockkObject import io.mockk.mockkObject
import io.mockk.unmockkAll
import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -12,12 +14,9 @@ import org.robolectric.annotation.Config
import org.thoughtcrime.securesms.assertIs import org.thoughtcrime.securesms.assertIs
import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.AccountValues
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet
import org.thoughtcrime.securesms.keyvalue.KeyValueStore
import org.thoughtcrime.securesms.keyvalue.MockKeyValuePersistentStorage
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.RemoteConfig import org.thoughtcrime.securesms.util.RemoteConfig
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.UUID import java.util.UUID
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@@ -32,15 +31,13 @@ class CrashConfigTest {
AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider()) AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider())
} }
val store = KeyValueStore( mockkObject(SignalStore)
MockKeyValuePersistentStorage.withDataSet( every { SignalStore.account.aci } returns ServiceId.ACI.from(UUID.randomUUID())
KeyValueDataSet().apply { }
putString(AccountValues.KEY_ACI, UUID.randomUUID().toString())
}
)
)
SignalStore.testInject(store) @After
fun tearDown() {
unmockkAll()
} }
@Test @Test

View File

@@ -6,13 +6,15 @@ import android.app.Application
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.slot import io.mockk.slot
import io.mockk.unmockkAll
import io.mockk.verify import io.mockk.verify
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers import org.hamcrest.Matchers
import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.`is`
import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
@@ -28,15 +30,18 @@ import org.signal.storageservice.protos.groups.GroupChangeResponse
import org.signal.storageservice.protos.groups.Member import org.signal.storageservice.protos.groups.Member
import org.signal.storageservice.protos.groups.local.DecryptedGroup import org.signal.storageservice.protos.groups.local.DecryptedGroup
import org.signal.storageservice.protos.groups.local.DecryptedMember import org.signal.storageservice.protos.groups.local.DecryptedMember
import org.thoughtcrime.securesms.SignalStoreRule
import org.thoughtcrime.securesms.TestZkGroupServer import org.thoughtcrime.securesms.TestZkGroupServer
import org.thoughtcrime.securesms.database.GroupStateTestData import org.thoughtcrime.securesms.database.GroupStateTestData
import org.thoughtcrime.securesms.database.GroupTable import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.database.model.databaseprotos.member import org.thoughtcrime.securesms.database.model.databaseprotos.member
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider
import org.thoughtcrime.securesms.groups.v2.GroupCandidateHelper import org.thoughtcrime.securesms.groups.v2.GroupCandidateHelper
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testutil.SystemOutLogger import org.thoughtcrime.securesms.testutil.SystemOutLogger
import org.thoughtcrime.securesms.util.RemoteConfig
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations
@@ -73,12 +78,17 @@ class GroupManagerV2Test_edit {
private lateinit var manager: GroupManagerV2 private lateinit var manager: GroupManagerV2
@get:Rule
val signalStore: SignalStoreRule = SignalStoreRule()
@Suppress("UsePropertyAccessSyntax") @Suppress("UsePropertyAccessSyntax")
@Before @Before
fun setUp() { fun setUp() {
if (!AppDependencies.isInitialized) {
AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider())
}
mockkObject(RemoteConfig)
mockkObject(SignalStore)
every { RemoteConfig.internalUser } returns false
ThreadUtil.enforceAssertions = false ThreadUtil.enforceAssertions = false
Log.initialize(SystemOutLogger()) Log.initialize(SystemOutLogger())
SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger()) SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger())
@@ -106,6 +116,11 @@ class GroupManagerV2Test_edit {
) )
} }
@After
fun tearDown() {
unmockkAll()
}
private fun given(init: GroupStateTestData.() -> Unit) { private fun given(init: GroupStateTestData.() -> Unit) {
val data = GroupStateTestData(masterKey, groupOperations) val data = GroupStateTestData(masterKey, groupOperations)
data.init() data.init()

View File

@@ -2,13 +2,14 @@ package org.thoughtcrime.securesms.groups.v2.processing
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Application import android.app.Application
import androidx.test.core.app.ApplicationProvider
import io.mockk.every import io.mockk.every
import io.mockk.justRun import io.mockk.justRun
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkObject import io.mockk.mockkObject
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.spyk import io.mockk.spyk
import io.mockk.unmockkObject import io.mockk.unmockkAll
import io.mockk.unmockkStatic import io.mockk.unmockkStatic
import io.mockk.verify import io.mockk.verify
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
@@ -16,7 +17,6 @@ import org.hamcrest.Matchers.hasItem
import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.`is`
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
@@ -32,7 +32,6 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroupChange
import org.signal.storageservice.protos.groups.local.DecryptedMember import org.signal.storageservice.protos.groups.local.DecryptedMember
import org.signal.storageservice.protos.groups.local.DecryptedString import org.signal.storageservice.protos.groups.local.DecryptedString
import org.signal.storageservice.protos.groups.local.DecryptedTimer import org.signal.storageservice.protos.groups.local.DecryptedTimer
import org.thoughtcrime.securesms.SignalStoreRule
import org.thoughtcrime.securesms.database.GroupStateTestData import org.thoughtcrime.securesms.database.GroupStateTestData
import org.thoughtcrime.securesms.database.GroupTable import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.database.RecipientTable import org.thoughtcrime.securesms.database.RecipientTable
@@ -46,6 +45,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.requestingMember
import org.thoughtcrime.securesms.database.setNewDescription import org.thoughtcrime.securesms.database.setNewDescription
import org.thoughtcrime.securesms.database.setNewTitle import org.thoughtcrime.securesms.database.setNewTitle
import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider
import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupNotAMemberException import org.thoughtcrime.securesms.groups.GroupNotAMemberException
import org.thoughtcrime.securesms.groups.GroupsV2Authorization import org.thoughtcrime.securesms.groups.GroupsV2Authorization
@@ -54,6 +54,7 @@ import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor.Pr
import org.thoughtcrime.securesms.jobmanager.JobManager import org.thoughtcrime.securesms.jobmanager.JobManager
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testutil.SystemOutLogger import org.thoughtcrime.securesms.testutil.SystemOutLogger
@@ -100,11 +101,15 @@ class GroupsV2StateProcessorTest {
private lateinit var processor: GroupsV2StateProcessor private lateinit var processor: GroupsV2StateProcessor
@get:Rule
val signalStore: SignalStoreRule = SignalStoreRule()
@Before @Before
fun setUp() { fun setUp() {
if (!AppDependencies.isInitialized) {
AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider())
}
mockkObject(SignalStore)
every { SignalStore.internal.gv2IgnoreP2PChanges() } returns false
Log.initialize(SystemOutLogger()) Log.initialize(SystemOutLogger())
SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger()) SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger())
@@ -138,11 +143,7 @@ class GroupsV2StateProcessorTest {
@After @After
fun tearDown() { fun tearDown() {
unmockkStatic(AppDependencies::class) unmockkAll()
unmockkObject(SignalDatabase)
unmockkObject(ProfileAndMessageHelper)
unmockkStatic(DecryptedGroupUtil::class)
unmockkStatic(Recipient::class)
} }
private fun given(init: GroupStateTestData.() -> Unit) { private fun given(init: GroupStateTestData.() -> Unit) {

View File

@@ -3,7 +3,10 @@ package org.thoughtcrime.securesms.keyvalue
import android.app.Application import android.app.Application
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import io.mockk.every import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject import io.mockk.mockkObject
import io.mockk.unmockkAll
import org.junit.After
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@@ -18,6 +21,8 @@ import org.thoughtcrime.securesms.util.RemoteConfig
@Config(manifest = Config.NONE, application = Application::class) @Config(manifest = Config.NONE, application = Application::class)
class PaymentsValuesTest { class PaymentsValuesTest {
private lateinit var paymentValues: PaymentsValues
@Before @Before
fun setup() { fun setup() {
if (!AppDependencies.isInitialized) { if (!AppDependencies.isInitialized) {
@@ -25,29 +30,32 @@ class PaymentsValuesTest {
} }
mockkObject(RemoteConfig) mockkObject(RemoteConfig)
mockkObject(SignalStore)
paymentValues = mockk()
every { paymentValues.paymentsAvailability } answers { callOriginal() }
every { SignalStore.payments } returns paymentValues
every { SignalStore.account.isRegistered } returns true
}
@After
fun tearDown() {
unmockkAll()
} }
@Test @Test
fun `when unregistered, expect NOT_IN_REGION`() { fun `when unregistered, expect NOT_IN_REGION`() {
setupStore( every { SignalStore.account.isRegistered } returns false
KeyValueDataSet().apply {
putBoolean(AccountValues.KEY_IS_REGISTERED, false)
}
)
assertEquals(PaymentsAvailability.NOT_IN_REGION, SignalStore.payments.paymentsAvailability) assertEquals(PaymentsAvailability.NOT_IN_REGION, SignalStore.payments.paymentsAvailability)
} }
@Test @Test
fun `when flag disabled and no account, expect DISABLED_REMOTELY`() { fun `when flag disabled and no account, expect DISABLED_REMOTELY`() {
setupStore( every { SignalStore.account.e164 } returns "+15551234567"
KeyValueDataSet().apply { every { paymentValues.mobileCoinPaymentsEnabled() } returns false
putBoolean(AccountValues.KEY_IS_REGISTERED, true)
putString(AccountValues.KEY_E164, "+15551234567")
putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, false)
}
)
every { RemoteConfig.payments } returns false every { RemoteConfig.payments } returns false
every { RemoteConfig.paymentsCountryBlocklist } returns "" every { RemoteConfig.paymentsCountryBlocklist } returns ""
@@ -56,14 +64,8 @@ class PaymentsValuesTest {
@Test @Test
fun `when flag disabled but has account, expect WITHDRAW_ONLY`() { fun `when flag disabled but has account, expect WITHDRAW_ONLY`() {
setupStore( every { SignalStore.account.e164 } returns "+15551234567"
KeyValueDataSet().apply { every { paymentValues.mobileCoinPaymentsEnabled() } returns true
putBoolean(AccountValues.KEY_IS_REGISTERED, true)
putString(AccountValues.KEY_E164, "+15551234567")
putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, true)
}
)
every { RemoteConfig.payments } returns false every { RemoteConfig.payments } returns false
every { RemoteConfig.paymentsCountryBlocklist } returns "" every { RemoteConfig.paymentsCountryBlocklist } returns ""
@@ -72,14 +74,8 @@ class PaymentsValuesTest {
@Test @Test
fun `when flag enabled and no account, expect REGISTRATION_AVAILABLE`() { fun `when flag enabled and no account, expect REGISTRATION_AVAILABLE`() {
setupStore( every { SignalStore.account.e164 } returns "+15551234567"
KeyValueDataSet().apply { every { paymentValues.mobileCoinPaymentsEnabled() } returns false
putBoolean(AccountValues.KEY_IS_REGISTERED, true)
putString(AccountValues.KEY_E164, "+15551234567")
putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, false)
}
)
every { RemoteConfig.payments } returns true every { RemoteConfig.payments } returns true
every { RemoteConfig.paymentsCountryBlocklist } returns "" every { RemoteConfig.paymentsCountryBlocklist } returns ""
@@ -88,14 +84,8 @@ class PaymentsValuesTest {
@Test @Test
fun `when flag enabled and has account, expect WITHDRAW_AND_SEND`() { fun `when flag enabled and has account, expect WITHDRAW_AND_SEND`() {
setupStore( every { SignalStore.account.e164 } returns "+15551234567"
KeyValueDataSet().apply { every { paymentValues.mobileCoinPaymentsEnabled() } returns true
putBoolean(AccountValues.KEY_IS_REGISTERED, true)
putString(AccountValues.KEY_E164, "+15551234567")
putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, true)
}
)
every { RemoteConfig.payments } returns true every { RemoteConfig.payments } returns true
every { RemoteConfig.paymentsCountryBlocklist } returns "" every { RemoteConfig.paymentsCountryBlocklist } returns ""
@@ -104,14 +94,8 @@ class PaymentsValuesTest {
@Test @Test
fun `when flag enabled and no account and in the country blocklist, expect NOT_IN_REGION`() { fun `when flag enabled and no account and in the country blocklist, expect NOT_IN_REGION`() {
setupStore( every { SignalStore.account.e164 } returns "+15551234567"
KeyValueDataSet().apply { every { paymentValues.mobileCoinPaymentsEnabled() } returns false
putBoolean(AccountValues.KEY_IS_REGISTERED, true)
putString(AccountValues.KEY_E164, "+15551234567")
putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, false)
}
)
every { RemoteConfig.payments } returns true every { RemoteConfig.payments } returns true
every { RemoteConfig.paymentsCountryBlocklist } returns "1" every { RemoteConfig.paymentsCountryBlocklist } returns "1"
@@ -120,31 +104,11 @@ class PaymentsValuesTest {
@Test @Test
fun `when flag enabled and has account and in the country blocklist, expect WITHDRAW_ONLY`() { fun `when flag enabled and has account and in the country blocklist, expect WITHDRAW_ONLY`() {
setupStore( every { SignalStore.account.e164 } returns "+15551234567"
KeyValueDataSet().apply { every { paymentValues.mobileCoinPaymentsEnabled() } returns true
putBoolean(AccountValues.KEY_IS_REGISTERED, true)
putString(AccountValues.KEY_E164, "+15551234567")
putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, true)
}
)
every { RemoteConfig.payments } returns true every { RemoteConfig.payments } returns true
every { RemoteConfig.paymentsCountryBlocklist } returns "1" every { RemoteConfig.paymentsCountryBlocklist } returns "1"
assertEquals(PaymentsAvailability.WITHDRAW_ONLY, SignalStore.payments.paymentsAvailability) assertEquals(PaymentsAvailability.WITHDRAW_ONLY, SignalStore.payments.paymentsAvailability)
} }
/**
* Account values will overwrite some values upon first access, so this takes care of that
*/
private fun setupStore(dataset: KeyValueDataSet) {
val store = KeyValueStore(
MockKeyValuePersistentStorage.withDataSet(
dataset.apply {
putString(AccountValues.KEY_ACI, "")
}
)
)
SignalStore.testInject(store)
}
} }

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.megaphone
import android.app.Application import android.app.Application
import android.net.Uri import android.net.Uri
import androidx.test.core.app.ApplicationProvider
import io.mockk.clearMocks import io.mockk.clearMocks
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
@@ -15,15 +16,15 @@ import org.junit.After
import org.junit.AfterClass import org.junit.AfterClass
import org.junit.Before import org.junit.Before
import org.junit.BeforeClass import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
import org.thoughtcrime.securesms.SignalStoreRule
import org.thoughtcrime.securesms.database.RemoteMegaphoneTable import org.thoughtcrime.securesms.database.RemoteMegaphoneTable
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.RemoteMegaphoneRecord import org.thoughtcrime.securesms.database.model.RemoteMegaphoneRecord
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider
import org.thoughtcrime.securesms.util.toMillis import org.thoughtcrime.securesms.util.toMillis
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.UUID import java.util.UUID
@@ -36,11 +37,11 @@ import java.util.UUID
@Config(manifest = Config.NONE, application = Application::class) @Config(manifest = Config.NONE, application = Application::class)
class RemoteMegaphoneRepositoryTest { class RemoteMegaphoneRepositoryTest {
@get:Rule
val signalStore: SignalStoreRule = SignalStoreRule()
@Before @Before
fun setUp() { fun setUp() {
if (!AppDependencies.isInitialized) {
AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider())
}
} }
@After @After

View File

@@ -1,16 +1,24 @@
package org.thoughtcrime.securesms.notifications.profiles package org.thoughtcrime.securesms.notifications.profiles
import android.app.Application import android.app.Application
import androidx.test.core.app.ApplicationProvider
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkAll
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.nullValue import org.hamcrest.Matchers.nullValue
import org.junit.Rule import org.junit.After
import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
import org.thoughtcrime.securesms.SignalStoreRule import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.NotificationProfileValues import org.thoughtcrime.securesms.keyvalue.NotificationProfileValues
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.toMillis import org.thoughtcrime.securesms.util.toMillis
import java.time.DayOfWeek import java.time.DayOfWeek
import java.time.LocalDateTime import java.time.LocalDateTime
@@ -43,8 +51,26 @@ class NotificationProfilesTest {
schedule = NotificationProfileSchedule(2) schedule = NotificationProfileSchedule(2)
) )
@get:Rule private lateinit var notificationProfileValues: NotificationProfileValues
val signalStore: SignalStoreRule = SignalStoreRule()
@Before
fun setUp() {
if (!AppDependencies.isInitialized) {
AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider())
}
notificationProfileValues = mockk()
every { notificationProfileValues.manuallyEnabledUntil } returns 0
every { notificationProfileValues.manuallyDisabledAt } returns 0
mockkObject(SignalStore)
every { SignalStore.notificationProfile } returns notificationProfileValues
}
@After
fun tearDown() {
unmockkAll()
}
@Test @Test
fun `when no profiles then return null`() { fun `when no profiles then return null`() {
@@ -59,9 +85,9 @@ class NotificationProfilesTest {
@Test @Test
fun `when first is not enabled and second is manually enabled forever then return second`() { fun `when first is not enabled and second is manually enabled forever then return second`() {
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, second.id) every { notificationProfileValues.manuallyEnabledProfile } returns second.id
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, Long.MAX_VALUE) every { notificationProfileValues.manuallyEnabledUntil } returns Long.MAX_VALUE
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, 5000L) every { notificationProfileValues.manuallyDisabledAt } returns 5000L
val profiles = listOf(first, second) val profiles = listOf(first, second)
assertThat("active profile is profile second", NotificationProfiles.getActiveProfile(profiles, 3000L, utc), `is`(profiles[1])) assertThat("active profile is profile second", NotificationProfiles.getActiveProfile(profiles, 3000L, utc), `is`(profiles[1]))
@@ -76,9 +102,9 @@ class NotificationProfilesTest {
@Test @Test
fun `when first is scheduled and second is manually enabled forever within first's schedule then return second`() { fun `when first is scheduled and second is manually enabled forever within first's schedule then return second`() {
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, second.id) every { notificationProfileValues.manuallyEnabledProfile } returns second.id
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, Long.MAX_VALUE) every { notificationProfileValues.manuallyEnabledUntil } returns Long.MAX_VALUE
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC)
val schedule = NotificationProfileSchedule(id = 3L, true, start = 700, daysEnabled = setOf(DayOfWeek.SUNDAY)) val schedule = NotificationProfileSchedule(id = 3L, true, start = 700, daysEnabled = setOf(DayOfWeek.SUNDAY))
val profiles = listOf(first.copy(schedule = schedule), second) val profiles = listOf(first.copy(schedule = schedule), second)
@@ -87,9 +113,9 @@ class NotificationProfilesTest {
@Test @Test
fun `when first is scheduled and second is manually enabled forever before first's schedule start then return first`() { fun `when first is scheduled and second is manually enabled forever before first's schedule start then return first`() {
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, second.id) every { notificationProfileValues.manuallyEnabledProfile } returns second.id
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, Long.MAX_VALUE) every { notificationProfileValues.manuallyEnabledUntil } returns Long.MAX_VALUE
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC)
val schedule = NotificationProfileSchedule(id = 3L, true, start = 900, daysEnabled = setOf(DayOfWeek.SUNDAY)) val schedule = NotificationProfileSchedule(id = 3L, true, start = 900, daysEnabled = setOf(DayOfWeek.SUNDAY))
val profiles = listOf(first.copy(schedule = schedule), second) val profiles = listOf(first.copy(schedule = schedule), second)
@@ -108,9 +134,9 @@ class NotificationProfilesTest {
@Test @Test
fun `when first and second have overlapping schedules and first is created before second and first is manually enabled within overlapping schedule then return first`() { fun `when first and second have overlapping schedules and first is created before second and first is manually enabled within overlapping schedule then return first`() {
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, first.id) every { notificationProfileValues.manuallyEnabledProfile } returns first.id
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, Long.MAX_VALUE) every { notificationProfileValues.manuallyEnabledUntil } returns Long.MAX_VALUE
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC)
val firstSchedule = NotificationProfileSchedule(id = 3L, true, start = 700, daysEnabled = setOf(DayOfWeek.SUNDAY)) val firstSchedule = NotificationProfileSchedule(id = 3L, true, start = 700, daysEnabled = setOf(DayOfWeek.SUNDAY))
val secondSchedule = NotificationProfileSchedule(id = 4L, true, start = 700, daysEnabled = setOf(DayOfWeek.SUNDAY)) val secondSchedule = NotificationProfileSchedule(id = 4L, true, start = 700, daysEnabled = setOf(DayOfWeek.SUNDAY))
@@ -121,9 +147,9 @@ class NotificationProfilesTest {
@Test @Test
fun `when profile is manually enabled for set time after schedule end and now is after schedule end but before manual then return profile`() { fun `when profile is manually enabled for set time after schedule end and now is after schedule end but before manual then return profile`() {
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, first.id) every { notificationProfileValues.manuallyEnabledProfile } returns first.id
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, sunday930am.toMillis(ZoneOffset.UTC)) every { notificationProfileValues.manuallyEnabledUntil } returns sunday930am.toMillis(ZoneOffset.UTC)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC)
val schedule = NotificationProfileSchedule(id = 3L, true, start = 700, end = 845, daysEnabled = setOf(DayOfWeek.SUNDAY)) val schedule = NotificationProfileSchedule(id = 3L, true, start = 700, end = 845, daysEnabled = setOf(DayOfWeek.SUNDAY))
val profiles = listOf(first.copy(schedule = schedule)) val profiles = listOf(first.copy(schedule = schedule))
@@ -132,9 +158,9 @@ class NotificationProfilesTest {
@Test @Test
fun `when profile is manually enabled for set time before schedule end and now is after manual but before schedule end then return null`() { fun `when profile is manually enabled for set time before schedule end and now is after manual but before schedule end then return null`() {
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, first.id) every { notificationProfileValues.manuallyEnabledProfile } returns first.id
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, sunday9am.toMillis(ZoneOffset.UTC)) every { notificationProfileValues.manuallyEnabledUntil } returns sunday9am.toMillis(ZoneOffset.UTC)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC)
val schedule = NotificationProfileSchedule(id = 3L, true, start = 700, end = 1000, daysEnabled = setOf(DayOfWeek.SUNDAY)) val schedule = NotificationProfileSchedule(id = 3L, true, start = 700, end = 1000, daysEnabled = setOf(DayOfWeek.SUNDAY))
val profiles = listOf(first.copy(schedule = schedule)) val profiles = listOf(first.copy(schedule = schedule))
@@ -143,9 +169,9 @@ class NotificationProfilesTest {
@Test @Test
fun `when profile is manually enabled yesterday and is scheduled also for today then return profile`() { fun `when profile is manually enabled yesterday and is scheduled also for today then return profile`() {
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, first.id) every { notificationProfileValues.manuallyEnabledProfile } returns first.id
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, sunday9am.toMillis(ZoneOffset.UTC)) every { notificationProfileValues.manuallyEnabledUntil } returns sunday9am.toMillis(ZoneOffset.UTC)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC)
val schedule = NotificationProfileSchedule(id = 3L, enabled = true, start = 700, end = 900, daysEnabled = setOf(DayOfWeek.SUNDAY, DayOfWeek.MONDAY)) val schedule = NotificationProfileSchedule(id = 3L, enabled = true, start = 700, end = 900, daysEnabled = setOf(DayOfWeek.SUNDAY, DayOfWeek.MONDAY))
val profiles = listOf(first.copy(schedule = schedule)) val profiles = listOf(first.copy(schedule = schedule))
@@ -154,9 +180,9 @@ class NotificationProfilesTest {
@Test @Test
fun `when profile is manually disabled and schedule is on but with start after end and now is before end then return null`() { fun `when profile is manually disabled and schedule is on but with start after end and now is before end then return null`() {
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, 0) every { notificationProfileValues.manuallyEnabledProfile } returns 0
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, 0) every { notificationProfileValues.manuallyEnabledUntil } returns 0
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC)) every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC)
val schedule = NotificationProfileSchedule(id = 3L, enabled = true, start = 2200, end = 1000, daysEnabled = DayOfWeek.values().toSet()) val schedule = NotificationProfileSchedule(id = 3L, enabled = true, start = 2200, end = 1000, daysEnabled = DayOfWeek.values().toSet())
val profiles = listOf(first.copy(schedule = schedule)) val profiles = listOf(first.copy(schedule = schedule))

View File

@@ -3,6 +3,9 @@ package org.thoughtcrime.securesms.recipients
import android.graphics.Color import android.graphics.Color
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkAll
import org.junit.After
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@@ -26,19 +29,23 @@ class Recipient_getChatColorsTest : BaseRecipientTest() {
@Before @Before
fun setUp() { fun setUp() {
wallpaperValues = mockk<WallpaperValues>() wallpaperValues = mockk()
chatColorsValues = mockk<ChatColorsValues>() chatColorsValues = mockk()
val globalWallpaper = createWallpaper(globalWallpaperChatColor) val globalWallpaper = createWallpaper(globalWallpaperChatColor)
every { wallpaperValues.wallpaper } answers { globalWallpaper } every { wallpaperValues.wallpaper } answers { globalWallpaper }
every { chatColorsValues.chatColors } answers { globalChatColor } every { chatColorsValues.chatColors } answers { globalChatColor }
val mockStore = mockk<SignalStore>() mockkObject(SignalStore)
SignalStore.testInject(mockStore)
every { SignalStore.wallpaper } returns wallpaperValues every { SignalStore.wallpaper } returns wallpaperValues
every { SignalStore.chatColors } returns chatColorsValues every { SignalStore.chatColors } returns chatColorsValues
} }
@After
fun tearDown() {
unmockkAll()
}
@Test @Test
fun `Given recipient has custom chat color set, when I getChatColors, then I expect the custom chat color`() { fun `Given recipient has custom chat color set, when I getChatColors, then I expect the custom chat color`() {
// GIVEN // GIVEN

View File

@@ -2,35 +2,26 @@ package org.thoughtcrime.securesms.storage
import android.app.Application import android.app.Application
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkObject
import org.junit.After
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.BeforeClass import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockedStatic
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.internal.configuration.plugins.Plugins
import org.mockito.internal.junit.JUnitRule
import org.mockito.junit.MockitoRule
import org.mockito.quality.Strictness
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.database.RecipientTable import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.AccountValues
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet
import org.thoughtcrime.securesms.keyvalue.KeyValueStore
import org.thoughtcrime.securesms.keyvalue.MockKeyValuePersistentStorage
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testutil.EmptyLogger import org.thoughtcrime.securesms.testutil.EmptyLogger
import org.thoughtcrime.securesms.util.RemoteConfig
import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.api.storage.SignalContactRecord import org.whispersystems.signalservice.api.storage.SignalContactRecord
@@ -42,24 +33,23 @@ import java.util.UUID
@Config(application = Application::class) @Config(application = Application::class)
class ContactRecordProcessorTest { class ContactRecordProcessorTest {
@Rule
@JvmField
val mockitoRule: MockitoRule = JUnitRule(Plugins.getMockitoLogger(), Strictness.STRICT_STUBS)
@Mock
lateinit var recipientTable: RecipientTable lateinit var recipientTable: RecipientTable
@Mock
lateinit var remoteConfig: MockedStatic<RemoteConfig>
@Before @Before
fun setup() { fun setup() {
val mockAccountValues = mock(AccountValues::class.java)
Mockito.lenient().`when`(mockAccountValues.isPrimaryDevice).thenReturn(true)
if (!AppDependencies.isInitialized) { if (!AppDependencies.isInitialized) {
AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider()) AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider())
} }
SignalStore.testInject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(KeyValueDataSet())))
mockkObject(SignalStore)
every { SignalStore.account.isPrimaryDevice } returns true
recipientTable = mockk(relaxed = true)
}
@After
fun tearDown() {
unmockkObject(SignalStore)
} }
@Test @Test

View File

@@ -1,60 +0,0 @@
package org.thoughtcrime.securesms.util;
import android.app.Application;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.ParameterizedRobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.thoughtcrime.securesms.SignalStoreRule;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.keyvalue.AccountValues;
import java.util.Arrays;
import java.util.Collection;
import kotlin.Unit;
import static junit.framework.TestCase.assertEquals;
@RunWith(ParameterizedRobolectricTestRunner.class)
@Config(manifest = Config.NONE, application = Application.class)
public class SignalMeUtilText_parseE164FromLink {
private final String input;
private final String output;
@ParameterizedRobolectricTestRunner.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{ "https://signal.me/#p/+15555555555", "+15555555555" },
{ "https://signal.me/#p/5555555555", null },
{ "https://signal.me", null },
{ "https://signal.me/#p/", null },
{ "signal.me/#p/+15555555555", null },
{ "sgnl://signal.me/#p/+15555555555", "+15555555555" },
{ "sgnl://signal.me/#p/5555555555", null },
{ "sgnl://signal.me", null },
{ "sgnl://signal.me/#p/", null },
{ "", null },
{ null, null }
});
}
@Rule
public SignalStoreRule signalStore = new SignalStoreRule(dataSet -> {
dataSet.putString(AccountValues.KEY_E164, "+15555555555");
return Unit.INSTANCE;
});
public SignalMeUtilText_parseE164FromLink(String input, String output) {
this.input = input;
this.output = output;
}
@Test
public void parse() {
assertEquals(output, SignalMeUtil.parseE164FromLink(AppDependencies.getApplication(), input));
}
}

View File

@@ -0,0 +1,64 @@
package org.thoughtcrime.securesms.util
import android.app.Application
import androidx.test.core.app.ApplicationProvider
import io.mockk.every
import io.mockk.mockkObject
import io.mockk.unmockkAll
import junit.framework.TestCase
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.ParameterizedRobolectricTestRunner
import org.robolectric.annotation.Config
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.AppDependencies.application
import org.thoughtcrime.securesms.dependencies.MockApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.SignalMeUtil.parseE164FromLink
@RunWith(ParameterizedRobolectricTestRunner::class)
@Config(manifest = Config.NONE, application = Application::class)
class SignalMeUtilText_parseE164FromLink(private val input: String?, private val output: String?) {
@Before
fun setUp() {
if (!AppDependencies.isInitialized) {
AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider())
}
mockkObject(SignalStore)
every { SignalStore.account.e164 } returns "+15555555555"
}
@After
fun tearDown() {
unmockkAll()
}
@Test
fun parse() {
TestCase.assertEquals(output, parseE164FromLink(application, input))
}
companion object {
@JvmStatic
@ParameterizedRobolectricTestRunner.Parameters
fun data(): Collection<Array<Any?>> {
return listOf(
arrayOf("https://signal.me/#p/+15555555555", "+15555555555"),
arrayOf("https://signal.me/#p/5555555555", null),
arrayOf("https://signal.me", null),
arrayOf("https://signal.me/#p/", null),
arrayOf("signal.me/#p/+15555555555", null),
arrayOf("sgnl://signal.me/#p/+15555555555", "+15555555555"),
arrayOf("sgnl://signal.me/#p/5555555555", null),
arrayOf("sgnl://signal.me", null),
arrayOf("sgnl://signal.me/#p/", null),
arrayOf("", null),
arrayOf(null, null)
)
}
}
}