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),
AttachmentSecretProvider.getInstance(this).getOrCreateAttachmentSecret());
})
.addBlocking("signal-store", () -> SignalStore.init(this))
.addBlocking("logging", () -> {
initializeLogging();
Log.i(TAG, "onCreate()");

View File

@@ -188,7 +188,7 @@ object BackupRepository {
}
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
import android.net.Uri
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import org.thoughtcrime.securesms.components.emoji.Emoji
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")
emojiData.use {

View File

@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.preference.PreferenceManager
import androidx.annotation.VisibleForTesting
import org.signal.core.util.Base64
import org.signal.core.util.logging.Log
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_ERROR_COUNT = "phoneNumberPrivacy.usernameErrorCount"
@VisibleForTesting
const val KEY_E164 = "account.e164"
@VisibleForTesting
const val KEY_ACI = "account.aci"
@VisibleForTesting
const val KEY_PNI = "account.pni"
@VisibleForTesting
const val KEY_IS_REGISTERED = "account.is_registered"
private const val KEY_E164 = "account.e164"
private const val KEY_ACI = "account.aci"
private const val KEY_PNI = "account.pni"
private const val KEY_IS_REGISTERED = "account.is_registered"
}
init {

View File

@@ -1,7 +1,5 @@
package org.thoughtcrime.securesms.keyvalue
import androidx.annotation.VisibleForTesting
/**
* 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_SEEN_TOOLTIP = "np.seen_tooltip"
@VisibleForTesting
const val KEY_MANUALLY_ENABLED_PROFILE = "np.manually_enabled_profile"
@VisibleForTesting
const val KEY_MANUALLY_ENABLED_UNTIL = "np.manually_enabled_until"
@VisibleForTesting
const val KEY_MANUALLY_DISABLED_AT = "np.manually_disabled_at"
private 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"
}
public override fun onFirstEverAppLaunch() = Unit

View File

@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.keyvalue
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
@@ -33,6 +32,7 @@ class PaymentsValues internal constructor(store: KeyValueStore) : SignalStoreVal
companion object {
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 MOB_LEDGER = "mob_ledger"
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 val LARGE_BALANCE_THRESHOLD = Money.mobileCoin(BigDecimal.valueOf(500))
@VisibleForTesting
const val MOB_PAYMENTS_ENABLED = "mob_payments_enabled"
}
@get:JvmName("isPaymentLockEnabled")

View File

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

View File

@@ -5,6 +5,7 @@
package org.thoughtcrime.securesms.providers
import android.app.Application
import android.content.ContentUris
import android.content.ContentValues
import android.content.Intent
@@ -15,10 +16,16 @@ import android.net.Uri
import android.os.ParcelFileDescriptor
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.ApplicationContext
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.SqlCipherLibraryLoader
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.profiles.AvatarHelper
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientCreator
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.KeyCachingService
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 {
if (VERBOSE) Log.i(TAG, "onCreate called")
return true
@@ -63,7 +85,9 @@ class AvatarProvider : BaseContentProvider() {
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
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.")
return null
}
@@ -76,7 +100,7 @@ class AvatarProvider : BaseContentProvider() {
if (uriMatcher.match(uri) == AVATAR) {
if (VERBOSE) Log.i(TAG, "Loading avatar.")
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)
} catch (ioe: IOException) {
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? {
if (VERBOSE) Log.i(TAG, "query() called: $uri")
val application = init() ?: return null
if (SignalDatabase.instance == null) {
Log.w(TAG, "SignalDatabase unavailable")
return null
@@ -99,8 +125,8 @@ class AvatarProvider : BaseContentProvider() {
if (uriMatcher.match(uri) == AVATAR) {
val recipientId = getRecipientId(uri) ?: return null
if (AvatarHelper.hasAvatar(context!!, recipientId)) {
val file: File = AvatarHelper.getAvatarFile(context!!, recipientId)
if (AvatarHelper.hasAvatar(application, recipientId)) {
val file: File = AvatarHelper.getAvatarFile(application, recipientId)
if (file.exists()) {
return createCursor(projection, file.name, file.length())
}
@@ -115,6 +141,8 @@ class AvatarProvider : BaseContentProvider() {
override fun getType(uri: Uri): String? {
if (VERBOSE) Log.i(TAG, "getType() called: $uri")
init() ?: return null
if (SignalDatabase.instance == null) {
Log.w(TAG, "SignalDatabase unavailable")
return null

View File

@@ -9,24 +9,15 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import com.annimon.stream.Stream;
import org.signal.core.util.ThreadUtil;
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.GroupTable;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.RecipientTable;
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 java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicReference;
@@ -198,62 +189,11 @@ public final class LiveRecipient {
}
private @NonNull Recipient fetchAndCacheRecipientFromDisk(@NonNull RecipientId id) {
RecipientRecord record = 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);
}
Recipient recipient = RecipientCreator.forRecord(context, recipientTable.getRecord(id));
RecipientIdCache.INSTANCE.put(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) {
this.recipient.set(recipient);
this.liveData.postValue(recipient);

View File

@@ -2,8 +2,10 @@ package org.thoughtcrime.securesms.recipients
import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
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.RecipientRecord
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
fun forUnknown(): Recipient {
return Recipient.UNKNOWN
@@ -186,4 +204,43 @@ object RecipientCreator {
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 androidx.test.core.app.ApplicationProvider
import io.mockk.every
import io.mockk.mockkObject
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
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.annotation.Config
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.emoji.EmojiSource.Companion.refresh
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.dependencies.MockApplicationDependencyProvider
import org.thoughtcrime.securesms.emoji.EmojiSource
@RunWith(ParameterizedRobolectricTestRunner::class)
@Config(manifest = Config.NONE, application = Application::class)
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)
@Test
fun isEmoji() {
val application = ApplicationProvider.getApplicationContext<Application>()
if (!AppDependencies.isInitialized) {
AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider())
}
Mockito.`when`(AppDependencies.application).thenReturn(application)
Mockito.`when`(AttachmentSecretProvider.getInstance(ArgumentMatchers.any())).thenThrow(RuntimeException::class.java)
SignalStore.testInject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(KeyValueDataSet())))
refresh()
val source = EmojiSource.loadAssetBasedEmojis()
Assert.assertEquals(output, EmojiUtil.isEmoji(input))
mockkObject(EmojiSource) {
every { EmojiSource.latest } returns source
Assert.assertEquals(output, EmojiUtil.isEmoji(input))
}
}
companion object {

View File

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

View File

@@ -6,13 +6,15 @@ 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.slot
import io.mockk.unmockkAll
import io.mockk.verify
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
import org.hamcrest.Matchers.`is`
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
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.local.DecryptedGroup
import org.signal.storageservice.protos.groups.local.DecryptedMember
import org.thoughtcrime.securesms.SignalStoreRule
import org.thoughtcrime.securesms.TestZkGroupServer
import org.thoughtcrime.securesms.database.GroupStateTestData
import org.thoughtcrime.securesms.database.GroupTable
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.keyvalue.SignalStore
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger
import org.thoughtcrime.securesms.recipients.Recipient
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.GroupsV2Api
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations
@@ -73,12 +78,17 @@ class GroupManagerV2Test_edit {
private lateinit var manager: GroupManagerV2
@get:Rule
val signalStore: SignalStoreRule = SignalStoreRule()
@Suppress("UsePropertyAccessSyntax")
@Before
fun setUp() {
if (!AppDependencies.isInitialized) {
AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider())
}
mockkObject(RemoteConfig)
mockkObject(SignalStore)
every { RemoteConfig.internalUser } returns false
ThreadUtil.enforceAssertions = false
Log.initialize(SystemOutLogger())
SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger())
@@ -106,6 +116,11 @@ class GroupManagerV2Test_edit {
)
}
@After
fun tearDown() {
unmockkAll()
}
private fun given(init: GroupStateTestData.() -> Unit) {
val data = GroupStateTestData(masterKey, groupOperations)
data.init()

View File

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

View File

@@ -3,7 +3,10 @@ package org.thoughtcrime.securesms.keyvalue
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.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -18,6 +21,8 @@ import org.thoughtcrime.securesms.util.RemoteConfig
@Config(manifest = Config.NONE, application = Application::class)
class PaymentsValuesTest {
private lateinit var paymentValues: PaymentsValues
@Before
fun setup() {
if (!AppDependencies.isInitialized) {
@@ -25,29 +30,32 @@ class PaymentsValuesTest {
}
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
fun `when unregistered, expect NOT_IN_REGION`() {
setupStore(
KeyValueDataSet().apply {
putBoolean(AccountValues.KEY_IS_REGISTERED, false)
}
)
every { SignalStore.account.isRegistered } returns false
assertEquals(PaymentsAvailability.NOT_IN_REGION, SignalStore.payments.paymentsAvailability)
}
@Test
fun `when flag disabled and no account, expect DISABLED_REMOTELY`() {
setupStore(
KeyValueDataSet().apply {
putBoolean(AccountValues.KEY_IS_REGISTERED, true)
putString(AccountValues.KEY_E164, "+15551234567")
putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, false)
}
)
every { SignalStore.account.e164 } returns "+15551234567"
every { paymentValues.mobileCoinPaymentsEnabled() } returns false
every { RemoteConfig.payments } returns false
every { RemoteConfig.paymentsCountryBlocklist } returns ""
@@ -56,14 +64,8 @@ class PaymentsValuesTest {
@Test
fun `when flag disabled but has account, expect WITHDRAW_ONLY`() {
setupStore(
KeyValueDataSet().apply {
putBoolean(AccountValues.KEY_IS_REGISTERED, true)
putString(AccountValues.KEY_E164, "+15551234567")
putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, true)
}
)
every { SignalStore.account.e164 } returns "+15551234567"
every { paymentValues.mobileCoinPaymentsEnabled() } returns true
every { RemoteConfig.payments } returns false
every { RemoteConfig.paymentsCountryBlocklist } returns ""
@@ -72,14 +74,8 @@ class PaymentsValuesTest {
@Test
fun `when flag enabled and no account, expect REGISTRATION_AVAILABLE`() {
setupStore(
KeyValueDataSet().apply {
putBoolean(AccountValues.KEY_IS_REGISTERED, true)
putString(AccountValues.KEY_E164, "+15551234567")
putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, false)
}
)
every { SignalStore.account.e164 } returns "+15551234567"
every { paymentValues.mobileCoinPaymentsEnabled() } returns false
every { RemoteConfig.payments } returns true
every { RemoteConfig.paymentsCountryBlocklist } returns ""
@@ -88,14 +84,8 @@ class PaymentsValuesTest {
@Test
fun `when flag enabled and has account, expect WITHDRAW_AND_SEND`() {
setupStore(
KeyValueDataSet().apply {
putBoolean(AccountValues.KEY_IS_REGISTERED, true)
putString(AccountValues.KEY_E164, "+15551234567")
putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, true)
}
)
every { SignalStore.account.e164 } returns "+15551234567"
every { paymentValues.mobileCoinPaymentsEnabled() } returns true
every { RemoteConfig.payments } returns true
every { RemoteConfig.paymentsCountryBlocklist } returns ""
@@ -104,14 +94,8 @@ class PaymentsValuesTest {
@Test
fun `when flag enabled and no account and in the country blocklist, expect NOT_IN_REGION`() {
setupStore(
KeyValueDataSet().apply {
putBoolean(AccountValues.KEY_IS_REGISTERED, true)
putString(AccountValues.KEY_E164, "+15551234567")
putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, false)
}
)
every { SignalStore.account.e164 } returns "+15551234567"
every { paymentValues.mobileCoinPaymentsEnabled() } returns false
every { RemoteConfig.payments } returns true
every { RemoteConfig.paymentsCountryBlocklist } returns "1"
@@ -120,31 +104,11 @@ class PaymentsValuesTest {
@Test
fun `when flag enabled and has account and in the country blocklist, expect WITHDRAW_ONLY`() {
setupStore(
KeyValueDataSet().apply {
putBoolean(AccountValues.KEY_IS_REGISTERED, true)
putString(AccountValues.KEY_E164, "+15551234567")
putBoolean(PaymentsValues.MOB_PAYMENTS_ENABLED, true)
}
)
every { SignalStore.account.e164 } returns "+15551234567"
every { paymentValues.mobileCoinPaymentsEnabled() } returns true
every { RemoteConfig.payments } returns true
every { RemoteConfig.paymentsCountryBlocklist } returns "1"
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.net.Uri
import androidx.test.core.app.ApplicationProvider
import io.mockk.clearMocks
import io.mockk.every
import io.mockk.mockk
@@ -15,15 +16,15 @@ import org.junit.After
import org.junit.AfterClass
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.thoughtcrime.securesms.SignalStoreRule
import org.thoughtcrime.securesms.database.RemoteMegaphoneTable
import org.thoughtcrime.securesms.database.SignalDatabase
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 java.time.LocalDateTime
import java.util.UUID
@@ -36,11 +37,11 @@ import java.util.UUID
@Config(manifest = Config.NONE, application = Application::class)
class RemoteMegaphoneRepositoryTest {
@get:Rule
val signalStore: SignalStoreRule = SignalStoreRule()
@Before
fun setUp() {
if (!AppDependencies.isInitialized) {
AppDependencies.init(ApplicationProvider.getApplicationContext(), MockApplicationDependencyProvider())
}
}
@After

View File

@@ -1,16 +1,24 @@
package org.thoughtcrime.securesms.notifications.profiles
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.Matchers.`is`
import org.hamcrest.Matchers.nullValue
import org.junit.Rule
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
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.SignalStore
import org.thoughtcrime.securesms.util.toMillis
import java.time.DayOfWeek
import java.time.LocalDateTime
@@ -43,8 +51,26 @@ class NotificationProfilesTest {
schedule = NotificationProfileSchedule(2)
)
@get:Rule
val signalStore: SignalStoreRule = SignalStoreRule()
private lateinit var notificationProfileValues: NotificationProfileValues
@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
fun `when no profiles then return null`() {
@@ -59,9 +85,9 @@ class NotificationProfilesTest {
@Test
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)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, Long.MAX_VALUE)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, 5000L)
every { notificationProfileValues.manuallyEnabledProfile } returns second.id
every { notificationProfileValues.manuallyEnabledUntil } returns Long.MAX_VALUE
every { notificationProfileValues.manuallyDisabledAt } returns 5000L
val profiles = listOf(first, second)
assertThat("active profile is profile second", NotificationProfiles.getActiveProfile(profiles, 3000L, utc), `is`(profiles[1]))
@@ -76,9 +102,9 @@ class NotificationProfilesTest {
@Test
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)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, Long.MAX_VALUE)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC))
every { notificationProfileValues.manuallyEnabledProfile } returns second.id
every { notificationProfileValues.manuallyEnabledUntil } returns Long.MAX_VALUE
every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC)
val schedule = NotificationProfileSchedule(id = 3L, true, start = 700, daysEnabled = setOf(DayOfWeek.SUNDAY))
val profiles = listOf(first.copy(schedule = schedule), second)
@@ -87,9 +113,9 @@ class NotificationProfilesTest {
@Test
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)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, Long.MAX_VALUE)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC))
every { notificationProfileValues.manuallyEnabledProfile } returns second.id
every { notificationProfileValues.manuallyEnabledUntil } returns Long.MAX_VALUE
every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC)
val schedule = NotificationProfileSchedule(id = 3L, true, start = 900, daysEnabled = setOf(DayOfWeek.SUNDAY))
val profiles = listOf(first.copy(schedule = schedule), second)
@@ -108,9 +134,9 @@ class NotificationProfilesTest {
@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`() {
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, first.id)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, Long.MAX_VALUE)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC))
every { notificationProfileValues.manuallyEnabledProfile } returns first.id
every { notificationProfileValues.manuallyEnabledUntil } returns Long.MAX_VALUE
every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC)
val firstSchedule = NotificationProfileSchedule(id = 3L, 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
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)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, sunday930am.toMillis(ZoneOffset.UTC))
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC))
every { notificationProfileValues.manuallyEnabledProfile } returns first.id
every { notificationProfileValues.manuallyEnabledUntil } returns sunday930am.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 profiles = listOf(first.copy(schedule = schedule))
@@ -132,9 +158,9 @@ class NotificationProfilesTest {
@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`() {
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_PROFILE, first.id)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, sunday9am.toMillis(ZoneOffset.UTC))
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC))
every { notificationProfileValues.manuallyEnabledProfile } returns first.id
every { notificationProfileValues.manuallyEnabledUntil } returns sunday9am.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 profiles = listOf(first.copy(schedule = schedule))
@@ -143,9 +169,9 @@ class NotificationProfilesTest {
@Test
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)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, sunday9am.toMillis(ZoneOffset.UTC))
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC))
every { notificationProfileValues.manuallyEnabledProfile } returns first.id
every { notificationProfileValues.manuallyEnabledUntil } returns sunday9am.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 profiles = listOf(first.copy(schedule = schedule))
@@ -154,9 +180,9 @@ class NotificationProfilesTest {
@Test
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)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_ENABLED_UNTIL, 0)
signalStore.dataSet.putLong(NotificationProfileValues.KEY_MANUALLY_DISABLED_AT, sunday830am.toMillis(ZoneOffset.UTC))
every { notificationProfileValues.manuallyEnabledProfile } returns 0
every { notificationProfileValues.manuallyEnabledUntil } returns 0
every { notificationProfileValues.manuallyDisabledAt } returns sunday830am.toMillis(ZoneOffset.UTC)
val schedule = NotificationProfileSchedule(id = 3L, enabled = true, start = 2200, end = 1000, daysEnabled = DayOfWeek.values().toSet())
val profiles = listOf(first.copy(schedule = schedule))

View File

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

View File

@@ -2,35 +2,26 @@ package org.thoughtcrime.securesms.storage
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.unmockkObject
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
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.annotation.Config
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.dependencies.AppDependencies
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.testutil.EmptyLogger
import org.thoughtcrime.securesms.util.RemoteConfig
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.api.storage.SignalContactRecord
@@ -42,24 +33,23 @@ import java.util.UUID
@Config(application = Application::class)
class ContactRecordProcessorTest {
@Rule
@JvmField
val mockitoRule: MockitoRule = JUnitRule(Plugins.getMockitoLogger(), Strictness.STRICT_STUBS)
@Mock
lateinit var recipientTable: RecipientTable
@Mock
lateinit var remoteConfig: MockedStatic<RemoteConfig>
@Before
fun setup() {
val mockAccountValues = mock(AccountValues::class.java)
Mockito.lenient().`when`(mockAccountValues.isPrimaryDevice).thenReturn(true)
if (!AppDependencies.isInitialized) {
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

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)
)
}
}
}