Fix test warning (#5558)
* Introduce JsonProvider. It will ensure that classes are using the correct Json instances in the unit tests. * Avoid creating a Json instance many times. * Update ref.
This commit is contained in:
commit
71c853d1a7
23 changed files with 112 additions and 58 deletions
|
|
@ -35,7 +35,6 @@ import kotlinx.coroutines.CoroutineName
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@BindingContainer
|
@BindingContainer
|
||||||
|
|
@ -121,10 +120,4 @@ object AppModule {
|
||||||
fun providesEmojibaseProvider(@ApplicationContext context: Context): EmojibaseProvider {
|
fun providesEmojibaseProvider(@ApplicationContext context: Context): EmojibaseProvider {
|
||||||
return DefaultEmojibaseProvider(context)
|
return DefaultEmojibaseProvider(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@SingleIn(AppScope::class)
|
|
||||||
fun providesJson(): Json = Json {
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 38992f58ef472520a2192696c1bbf20e3066191e
|
Subproject commit c5465c95792004409e0eaa7342171e1cd652914a
|
||||||
|
|
@ -44,6 +44,13 @@ class WebViewAudioManager(
|
||||||
private val coroutineScope: CoroutineScope,
|
private val coroutineScope: CoroutineScope,
|
||||||
private val onInvalidAudioDeviceAdded: (InvalidAudioDeviceReason) -> Unit,
|
private val onInvalidAudioDeviceAdded: (InvalidAudioDeviceReason) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
private val json by lazy {
|
||||||
|
Json {
|
||||||
|
encodeDefaults = true
|
||||||
|
explicitNulls = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to disable bluetooth audio devices. This must be done on Android versions lower than Android 12,
|
* Whether to disable bluetooth audio devices. This must be done on Android versions lower than Android 12,
|
||||||
* since the WebView approach breaks when using the legacy Bluetooth audio APIs.
|
* since the WebView approach breaks when using the legacy Bluetooth audio APIs.
|
||||||
|
|
@ -308,11 +315,7 @@ class WebViewAudioManager(
|
||||||
devices: List<SerializableAudioDevice> = listAudioDevices().map(SerializableAudioDevice::fromAudioDeviceInfo),
|
devices: List<SerializableAudioDevice> = listAudioDevices().map(SerializableAudioDevice::fromAudioDeviceInfo),
|
||||||
) {
|
) {
|
||||||
Timber.d("Updating available audio devices")
|
Timber.d("Updating available audio devices")
|
||||||
val jsonSerializer = Json {
|
val deviceList = json.encodeToString(devices)
|
||||||
encodeDefaults = true
|
|
||||||
explicitNulls = false
|
|
||||||
}
|
|
||||||
val deviceList = jsonSerializer.encodeToString(devices)
|
|
||||||
webView.evaluateJavascript("controls.setAvailableOutputDevices($deviceList);", {
|
webView.evaluateJavascript("controls.setAvailableOutputDevices($deviceList);", {
|
||||||
Timber.d("Audio: setAvailableOutputDevices result: $it")
|
Timber.d("Audio: setAvailableOutputDevices result: $it")
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,18 @@ package io.element.android.features.call.impl.utils
|
||||||
|
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.features.call.impl.data.WidgetMessage
|
import io.element.android.features.call.impl.data.WidgetMessage
|
||||||
|
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
class WidgetMessageSerializer(
|
class WidgetMessageSerializer(
|
||||||
private val json: Json,
|
private val json: JsonProvider,
|
||||||
) {
|
) {
|
||||||
fun deserialize(message: String): Result<WidgetMessage> {
|
fun deserialize(message: String): Result<WidgetMessage> {
|
||||||
return runCatchingExceptions { json.decodeFromString(WidgetMessage.serializer(), message) }
|
return runCatchingExceptions { json().decodeFromString(WidgetMessage.serializer(), message) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun serialize(message: WidgetMessage): String {
|
fun serialize(message: WidgetMessage): String {
|
||||||
return json.encodeToString(WidgetMessage.serializer(), message)
|
return json().encodeToString(WidgetMessage.serializer(), message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import io.element.android.features.call.impl.utils.WidgetMessageSerializer
|
||||||
import io.element.android.features.call.utils.FakeActiveCallManager
|
import io.element.android.features.call.utils.FakeActiveCallManager
|
||||||
import io.element.android.features.call.utils.FakeCallWidgetProvider
|
import io.element.android.features.call.utils.FakeCallWidgetProvider
|
||||||
import io.element.android.features.call.utils.FakeWidgetMessageInterceptor
|
import io.element.android.features.call.utils.FakeWidgetMessageInterceptor
|
||||||
|
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||||
|
|
@ -47,7 +48,6 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
import kotlinx.coroutines.test.advanceTimeBy
|
import kotlinx.coroutines.test.advanceTimeBy
|
||||||
import kotlinx.coroutines.test.runCurrent
|
import kotlinx.coroutines.test.runCurrent
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
@ -412,7 +412,7 @@ class CallScreenPresenterTest {
|
||||||
languageTagProvider = FakeLanguageTagProvider("en-US"),
|
languageTagProvider = FakeLanguageTagProvider("en-US"),
|
||||||
appForegroundStateService = appForegroundStateService,
|
appForegroundStateService = appForegroundStateService,
|
||||||
appCoroutineScope = backgroundScope,
|
appCoroutineScope = backgroundScope,
|
||||||
widgetMessageSerializer = WidgetMessageSerializer(Json { ignoreUnknownKeys = true }),
|
widgetMessageSerializer = WidgetMessageSerializer(DefaultJsonProvider()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
|
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||||
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
interface MessageParser {
|
interface MessageParser {
|
||||||
/**
|
/**
|
||||||
|
|
@ -26,10 +26,10 @@ interface MessageParser {
|
||||||
@Inject
|
@Inject
|
||||||
class DefaultMessageParser(
|
class DefaultMessageParser(
|
||||||
private val accountProviderDataSource: AccountProviderDataSource,
|
private val accountProviderDataSource: AccountProviderDataSource,
|
||||||
private val json: Json,
|
private val json: JsonProvider,
|
||||||
) : MessageParser {
|
) : MessageParser {
|
||||||
override fun parse(message: String): ExternalSession {
|
override fun parse(message: String): ExternalSession {
|
||||||
val response = json.decodeFromString(MobileRegistrationResponse.serializer(), message)
|
val response = json().decodeFromString(MobileRegistrationResponse.serializer(), message)
|
||||||
val userId = response.userId ?: error("No user ID in response")
|
val userId = response.userId ?: error("No user ID in response")
|
||||||
val homeServer = response.homeServer ?: accountProviderDataSource.flow.value.url
|
val homeServer = response.homeServer ?: accountProviderDataSource.flow.value.url
|
||||||
val accessToken = response.accessToken ?: error("No access token in response")
|
val accessToken = response.accessToken ?: error("No access token in response")
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.appconfig.AuthenticationConfig
|
import io.element.android.appconfig.AuthenticationConfig
|
||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
|
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||||
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import org.junit.Assert.assertThrows
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
@ -70,7 +70,7 @@ class DefaultMessageParserTest {
|
||||||
private fun createDefaultMessageParser(): DefaultMessageParser {
|
private fun createDefaultMessageParser(): DefaultMessageParser {
|
||||||
return DefaultMessageParser(
|
return DefaultMessageParser(
|
||||||
accountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
accountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||||
json = Json { ignoreUnknownKeys = true },
|
json = DefaultJsonProvider(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ dependencies {
|
||||||
implementation(libs.androidx.recyclerview)
|
implementation(libs.androidx.recyclerview)
|
||||||
implementation(libs.androidx.exifinterface)
|
implementation(libs.androidx.exifinterface)
|
||||||
implementation(libs.androidx.datastore.preferences)
|
implementation(libs.androidx.datastore.preferences)
|
||||||
|
implementation(libs.serialization.json)
|
||||||
api(libs.androidx.browser)
|
api(libs.androidx.browser)
|
||||||
|
|
||||||
testCommonDependencies(libs)
|
testCommonDependencies(libs)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.androidutils.json
|
||||||
|
|
||||||
|
import dev.zacsweers.metro.AppScope
|
||||||
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
|
import dev.zacsweers.metro.Inject
|
||||||
|
import dev.zacsweers.metro.Provider
|
||||||
|
import dev.zacsweers.metro.SingleIn
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a Json instance configured to ignore unknown keys.
|
||||||
|
*/
|
||||||
|
interface JsonProvider : Provider<Json>
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
@SingleIn(AppScope::class)
|
||||||
|
@Inject
|
||||||
|
class DefaultJsonProvider : JsonProvider {
|
||||||
|
private val json: Json by lazy { Json { ignoreUnknownKeys = true } }
|
||||||
|
override fun invoke() = json
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ android {
|
||||||
setupDependencyInjection()
|
setupDependencyInjection()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.di)
|
implementation(projects.libraries.di)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ package io.element.android.libraries.network
|
||||||
|
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import dev.zacsweers.metro.Provider
|
import dev.zacsweers.metro.Provider
|
||||||
|
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||||
import io.element.android.libraries.core.uri.ensureTrailingSlash
|
import io.element.android.libraries.core.uri.ensureTrailingSlash
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
|
|
@ -19,11 +19,11 @@ import retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||||
@Inject
|
@Inject
|
||||||
class RetrofitFactory(
|
class RetrofitFactory(
|
||||||
private val okHttpClient: Provider<OkHttpClient>,
|
private val okHttpClient: Provider<OkHttpClient>,
|
||||||
private val json: Provider<Json>,
|
private val json: Provider<JsonProvider>,
|
||||||
) {
|
) {
|
||||||
fun create(baseUrl: String): Retrofit = Retrofit.Builder()
|
fun create(baseUrl: String): Retrofit = Retrofit.Builder()
|
||||||
.baseUrl(baseUrl.ensureTrailingSlash())
|
.baseUrl(baseUrl.ensureTrailingSlash())
|
||||||
.addConverterFactory(json().asConverterFactory("application/json".toMediaType()))
|
.addConverterFactory(json()().asConverterFactory("application/json".toMediaType()))
|
||||||
.callFactory { request -> okHttpClient().newCall(request) }
|
.callFactory { request -> okHttpClient().newCall(request) }
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import dev.zacsweers.metro.SingleIn
|
import dev.zacsweers.metro.SingleIn
|
||||||
|
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
|
@ -49,10 +50,12 @@ class DefaultNotificationResolverQueue(
|
||||||
private val appCoroutineScope: CoroutineScope,
|
private val appCoroutineScope: CoroutineScope,
|
||||||
private val workManagerScheduler: WorkManagerScheduler,
|
private val workManagerScheduler: WorkManagerScheduler,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
|
private val json: JsonProvider,
|
||||||
) : NotificationResolverQueue {
|
) : NotificationResolverQueue {
|
||||||
companion object {
|
companion object {
|
||||||
private const val BATCH_WINDOW_MS = 250L
|
private const val BATCH_WINDOW_MS = 250L
|
||||||
}
|
}
|
||||||
|
|
||||||
private val requestQueue = Channel<NotificationEventRequest>(capacity = 100)
|
private val requestQueue = Channel<NotificationEventRequest>(capacity = 100)
|
||||||
|
|
||||||
private var currentProcessingJob: Job? = null
|
private var currentProcessingJob: Job? = null
|
||||||
|
|
@ -94,7 +97,13 @@ class DefaultNotificationResolverQueue(
|
||||||
|
|
||||||
if (featureFlagService.isFeatureEnabled(FeatureFlags.SyncNotificationsWithWorkManager)) {
|
if (featureFlagService.isFeatureEnabled(FeatureFlags.SyncNotificationsWithWorkManager)) {
|
||||||
for ((sessionId, requests) in groupedRequestsById) {
|
for ((sessionId, requests) in groupedRequestsById) {
|
||||||
workManagerScheduler.submit(SyncNotificationWorkManagerRequest(sessionId, requests))
|
workManagerScheduler.submit(
|
||||||
|
SyncNotificationWorkManagerRequest(
|
||||||
|
sessionId = sessionId,
|
||||||
|
notificationEventRequests = requests,
|
||||||
|
json = json,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val sessionIds = groupedRequestsById.keys
|
val sessionIds = groupedRequestsById.keys
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import dev.zacsweers.metro.ContributesIntoMap
|
||||||
import dev.zacsweers.metro.binding
|
import dev.zacsweers.metro.binding
|
||||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||||
|
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||||
|
|
@ -33,7 +34,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
|
@ -47,13 +47,13 @@ class FetchNotificationsWorker(
|
||||||
private val workManagerScheduler: WorkManagerScheduler,
|
private val workManagerScheduler: WorkManagerScheduler,
|
||||||
private val syncOnNotifiableEvent: SyncOnNotifiableEvent,
|
private val syncOnNotifiableEvent: SyncOnNotifiableEvent,
|
||||||
private val coroutineDispatchers: CoroutineDispatchers,
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
private val json: Json,
|
private val json: JsonProvider,
|
||||||
) : CoroutineWorker(context, workerParams) {
|
) : CoroutineWorker(context, workerParams) {
|
||||||
override suspend fun doWork(): Result = withContext(coroutineDispatchers.io) {
|
override suspend fun doWork(): Result = withContext(coroutineDispatchers.io) {
|
||||||
Timber.d("FetchNotificationsWorker started")
|
Timber.d("FetchNotificationsWorker started")
|
||||||
val rawRequestsJson = inputData.getString("requests") ?: return@withContext Result.failure()
|
val rawRequestsJson = inputData.getString("requests") ?: return@withContext Result.failure()
|
||||||
val requests = runCatchingExceptions {
|
val requests = runCatchingExceptions {
|
||||||
json.decodeFromString<List<SyncNotificationWorkManagerRequest.Data>>(rawRequestsJson).map { it.toRequest() }
|
json().decodeFromString<List<SyncNotificationWorkManagerRequest.Data>>(rawRequestsJson).map { it.toRequest() }
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
Timber.e(it, "Failed to deserialize notification requests")
|
Timber.e(it, "Failed to deserialize notification requests")
|
||||||
return@withContext Result.failure()
|
return@withContext Result.failure()
|
||||||
|
|
@ -93,7 +93,13 @@ class FetchNotificationsWorker(
|
||||||
for (failedSessionId in failedSyncForSessions) {
|
for (failedSessionId in failedSyncForSessions) {
|
||||||
val requestsToRetry = groupedRequests[failedSessionId] ?: continue
|
val requestsToRetry = groupedRequests[failedSessionId] ?: continue
|
||||||
Timber.d("Re-scheduling ${requestsToRetry.size} failed notification requests for session $failedSessionId")
|
Timber.d("Re-scheduling ${requestsToRetry.size} failed notification requests for session $failedSessionId")
|
||||||
workManagerScheduler.submit(SyncNotificationWorkManagerRequest(failedSessionId, requestsToRetry))
|
workManagerScheduler.submit(
|
||||||
|
SyncNotificationWorkManagerRequest(
|
||||||
|
sessionId = failedSessionId,
|
||||||
|
notificationEventRequests = requestsToRetry,
|
||||||
|
json = json,
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.OutOfQuotaPolicy
|
import androidx.work.OutOfQuotaPolicy
|
||||||
import androidx.work.WorkRequest
|
import androidx.work.WorkRequest
|
||||||
import androidx.work.workDataOf
|
import androidx.work.workDataOf
|
||||||
|
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -21,22 +22,20 @@ import io.element.android.libraries.workmanager.api.WorkManagerRequestType
|
||||||
import io.element.android.libraries.workmanager.api.workManagerTag
|
import io.element.android.libraries.workmanager.api.workManagerTag
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.security.InvalidParameterException
|
import java.security.InvalidParameterException
|
||||||
|
|
||||||
class SyncNotificationWorkManagerRequest(
|
class SyncNotificationWorkManagerRequest(
|
||||||
private val sessionId: SessionId,
|
private val sessionId: SessionId,
|
||||||
private val notificationEventRequests: List<NotificationEventRequest>,
|
private val notificationEventRequests: List<NotificationEventRequest>,
|
||||||
|
private val json: JsonProvider,
|
||||||
) : WorkManagerRequest {
|
) : WorkManagerRequest {
|
||||||
private val json = Json { ignoreUnknownKeys = true }
|
|
||||||
|
|
||||||
override fun build(): Result<WorkRequest> {
|
override fun build(): Result<WorkRequest> {
|
||||||
if (notificationEventRequests.isEmpty()) {
|
if (notificationEventRequests.isEmpty()) {
|
||||||
return Result.failure(InvalidParameterException("notificationEventRequests cannot be empty"))
|
return Result.failure(InvalidParameterException("notificationEventRequests cannot be empty"))
|
||||||
}
|
}
|
||||||
|
|
||||||
val json = runCatchingExceptions { json.encodeToString(notificationEventRequests.map { it.toData() }) }
|
val json = runCatchingExceptions { json().encodeToString(notificationEventRequests.map { it.toData() }) }
|
||||||
.getOrElse {
|
.getOrElse {
|
||||||
Timber.e(it, "Failed to serialize notification requests")
|
Timber.e(it, "Failed to serialize notification requests")
|
||||||
return Result.failure(it)
|
return Result.failure(it)
|
||||||
|
|
@ -50,7 +49,7 @@ class SyncNotificationWorkManagerRequest(
|
||||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||||
.setTraceTag(workManagerTag(sessionId, WorkManagerRequestType.NOTIFICATION_SYNC))
|
.setTraceTag(workManagerTag(sessionId, WorkManagerRequestType.NOTIFICATION_SYNC))
|
||||||
// TODO investigate using this instead of the resolver queue
|
// TODO investigate using this instead of the resolver queue
|
||||||
// .setInputMerger()
|
// .setInputMerger()
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallType
|
||||||
import io.element.android.features.call.test.FakeElementCallEntryPoint
|
import io.element.android.features.call.test.FakeElementCallEntryPoint
|
||||||
|
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
|
|
@ -714,6 +715,7 @@ class DefaultPushHandlerTest {
|
||||||
appCoroutineScope = backgroundScope,
|
appCoroutineScope = backgroundScope,
|
||||||
workManagerScheduler = workManagerScheduler,
|
workManagerScheduler = workManagerScheduler,
|
||||||
featureFlagService = featureFlagService,
|
featureFlagService = featureFlagService,
|
||||||
|
json = DefaultJsonProvider(),
|
||||||
),
|
),
|
||||||
appCoroutineScope = backgroundScope,
|
appCoroutineScope = backgroundScope,
|
||||||
fallbackNotificationFactory = FallbackNotificationFactory(
|
fallbackNotificationFactory = FallbackNotificationFactory(
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||||
|
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||||
import io.element.android.libraries.push.api.push.SyncOnNotifiableEvent
|
import io.element.android.libraries.push.api.push.SyncOnNotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.FakeNotifiableEventResolver
|
import io.element.android.libraries.push.impl.notifications.FakeNotifiableEventResolver
|
||||||
import io.element.android.libraries.push.impl.notifications.NotificationResolverQueue
|
import io.element.android.libraries.push.impl.notifications.NotificationResolverQueue
|
||||||
|
|
@ -33,7 +34,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.advanceTimeBy
|
import kotlinx.coroutines.test.advanceTimeBy
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
@ -175,7 +175,7 @@ class FetchNotificationWorkerTest {
|
||||||
workManagerScheduler = workManagerScheduler,
|
workManagerScheduler = workManagerScheduler,
|
||||||
syncOnNotifiableEvent = syncOnNotifiableEvent,
|
syncOnNotifiableEvent = syncOnNotifiableEvent,
|
||||||
coroutineDispatchers = testCoroutineDispatchers(),
|
coroutineDispatchers = testCoroutineDispatchers(),
|
||||||
json = Json {},
|
json = DefaultJsonProvider(),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun TestScope.createWorkerParams(
|
private fun TestScope.createWorkerParams(
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,10 @@ package io.element.android.libraries.push.impl.workmanager
|
||||||
import androidx.work.OneTimeWorkRequest
|
import androidx.work.OneTimeWorkRequest
|
||||||
import androidx.work.hasKeyWithValueOfType
|
import androidx.work.hasKeyWithValueOfType
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||||
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
|
import io.element.android.libraries.push.api.push.NotificationEventRequest
|
||||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotificationEventRequest
|
import io.element.android.libraries.push.impl.notifications.fixtures.aNotificationEventRequest
|
||||||
import io.element.android.libraries.workmanager.api.WorkManagerRequestType
|
import io.element.android.libraries.workmanager.api.WorkManagerRequestType
|
||||||
import io.element.android.libraries.workmanager.api.workManagerTag
|
import io.element.android.libraries.workmanager.api.workManagerTag
|
||||||
|
|
@ -20,7 +23,7 @@ import org.junit.Test
|
||||||
class SyncNotificationWorkManagerRequestTest {
|
class SyncNotificationWorkManagerRequestTest {
|
||||||
@Test
|
@Test
|
||||||
fun `build - success`() = runTest {
|
fun `build - success`() = runTest {
|
||||||
val request = SyncNotificationWorkManagerRequest(
|
val request = createSyncNotificationWorkManagerRequest(
|
||||||
sessionId = A_SESSION_ID,
|
sessionId = A_SESSION_ID,
|
||||||
notificationEventRequests = listOf(aNotificationEventRequest())
|
notificationEventRequests = listOf(aNotificationEventRequest())
|
||||||
)
|
)
|
||||||
|
|
@ -37,7 +40,7 @@ class SyncNotificationWorkManagerRequestTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `build - empty list of requests fails`() = runTest {
|
fun `build - empty list of requests fails`() = runTest {
|
||||||
val request = SyncNotificationWorkManagerRequest(
|
val request = createSyncNotificationWorkManagerRequest(
|
||||||
sessionId = A_SESSION_ID,
|
sessionId = A_SESSION_ID,
|
||||||
notificationEventRequests = emptyList()
|
notificationEventRequests = emptyList()
|
||||||
)
|
)
|
||||||
|
|
@ -48,3 +51,12 @@ class SyncNotificationWorkManagerRequestTest {
|
||||||
|
|
||||||
// TODO add test for invalid serialization (how?)
|
// TODO add test for invalid serialization (how?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createSyncNotificationWorkManagerRequest(
|
||||||
|
sessionId: SessionId,
|
||||||
|
notificationEventRequests: List<NotificationEventRequest>,
|
||||||
|
) = SyncNotificationWorkManagerRequest(
|
||||||
|
sessionId = sessionId,
|
||||||
|
notificationEventRequests = notificationEventRequests,
|
||||||
|
json = DefaultJsonProvider(),
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ data class PushDataUnifiedPushNotification(
|
||||||
@SerialName("event_id") val eventId: String? = null,
|
@SerialName("event_id") val eventId: String? = null,
|
||||||
@SerialName("room_id") val roomId: String? = null,
|
@SerialName("room_id") val roomId: String? = null,
|
||||||
@SerialName("counts") val counts: PushDataUnifiedPushCounts? = null,
|
@SerialName("counts") val counts: PushDataUnifiedPushCounts? = null,
|
||||||
|
@SerialName("prio") val prio: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,15 @@
|
||||||
package io.element.android.libraries.pushproviders.unifiedpush
|
package io.element.android.libraries.pushproviders.unifiedpush
|
||||||
|
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
|
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||||
import io.element.android.libraries.core.data.tryOrNull
|
import io.element.android.libraries.core.data.tryOrNull
|
||||||
import io.element.android.libraries.pushproviders.api.PushData
|
import io.element.android.libraries.pushproviders.api.PushData
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
class UnifiedPushParser(
|
class UnifiedPushParser(
|
||||||
private val json: Json,
|
private val json: JsonProvider,
|
||||||
) {
|
) {
|
||||||
fun parse(message: ByteArray, clientSecret: String): PushData? {
|
fun parse(message: ByteArray, clientSecret: String): PushData? {
|
||||||
return tryOrNull { json.decodeFromString<PushDataUnifiedPush>(String(message)) }?.toPushData(clientSecret)
|
return tryOrNull { json().decodeFromString<PushDataUnifiedPush>(String(message)) }?.toPushData(clientSecret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@
|
||||||
package io.element.android.libraries.pushproviders.unifiedpush
|
package io.element.android.libraries.pushproviders.unifiedpush
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.pushproviders.api.PushData
|
import io.element.android.libraries.pushproviders.api.PushData
|
||||||
import io.element.android.tests.testutils.assertThrowsInDebug
|
import io.element.android.tests.testutils.assertThrowsInDebug
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class UnifiedPushParserTest {
|
class UnifiedPushParserTest {
|
||||||
|
|
@ -83,8 +83,6 @@ private fun String.mutate(oldValue: String, newValue: String): ByteArray {
|
||||||
return replace(oldValue, newValue).toByteArray()
|
return replace(oldValue, newValue).toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createUnifiedPushParser(
|
fun createUnifiedPushParser() = UnifiedPushParser(
|
||||||
json: Json = Json { ignoreUnknownKeys = true },
|
json = DefaultJsonProvider(),
|
||||||
) = UnifiedPushParser(
|
|
||||||
json = json,
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,9 @@ dependencies {
|
||||||
implementation(platform(libs.network.retrofit.bom))
|
implementation(platform(libs.network.retrofit.bom))
|
||||||
implementation(libs.network.retrofit)
|
implementation(libs.network.retrofit)
|
||||||
implementation(libs.serialization.json)
|
implementation(libs.serialization.json)
|
||||||
implementation(projects.libraries.architecture)
|
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
|
implementation(projects.libraries.androidutils)
|
||||||
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.network)
|
implementation(projects.libraries.network)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,20 @@ package io.element.android.libraries.wellknown.impl
|
||||||
|
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
|
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||||
import io.element.android.libraries.core.extensions.mapCatchingExceptions
|
import io.element.android.libraries.core.extensions.mapCatchingExceptions
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||||
import io.element.android.libraries.wellknown.api.SessionWellknownRetriever
|
import io.element.android.libraries.wellknown.api.SessionWellknownRetriever
|
||||||
import io.element.android.libraries.wellknown.api.WellKnown
|
import io.element.android.libraries.wellknown.api.WellKnown
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@ContributesBinding(SessionScope::class)
|
@ContributesBinding(SessionScope::class)
|
||||||
@Inject
|
@Inject
|
||||||
class DefaultSessionWellknownRetriever(
|
class DefaultSessionWellknownRetriever(
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
private val json: Json,
|
private val json: JsonProvider,
|
||||||
) : SessionWellknownRetriever {
|
) : SessionWellknownRetriever {
|
||||||
private val domain by lazy { matrixClient.userIdServerName() }
|
private val domain by lazy { matrixClient.userIdServerName() }
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ class DefaultSessionWellknownRetriever(
|
||||||
.getUrl(url)
|
.getUrl(url)
|
||||||
.mapCatchingExceptions {
|
.mapCatchingExceptions {
|
||||||
val data = String(it)
|
val data = String(it)
|
||||||
json.decodeFromString(InternalWellKnown.serializer(), data)
|
json().decodeFromString(InternalWellKnown.serializer(), data)
|
||||||
}
|
}
|
||||||
.onFailure { Timber.e(it, "Failed to retrieve .well-known from $domain") }
|
.onFailure { Timber.e(it, "Failed to retrieve .well-known from $domain") }
|
||||||
.map { it.map() }
|
.map { it.map() }
|
||||||
|
|
@ -45,7 +45,7 @@ class DefaultSessionWellknownRetriever(
|
||||||
.getUrl(url)
|
.getUrl(url)
|
||||||
.mapCatchingExceptions {
|
.mapCatchingExceptions {
|
||||||
val data = String(it)
|
val data = String(it)
|
||||||
json.decodeFromString(InternalElementWellKnown.serializer(), data)
|
json().decodeFromString(InternalElementWellKnown.serializer(), data)
|
||||||
}
|
}
|
||||||
.onFailure { Timber.e(it, "Failed to retrieve Element .well-known from $domain") }
|
.onFailure { Timber.e(it, "Failed to retrieve Element .well-known from $domain") }
|
||||||
.map { it.map() }
|
.map { it.map() }
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
package io.element.android.libraries.wellknown.impl
|
package io.element.android.libraries.wellknown.impl
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||||
|
|
@ -16,7 +17,6 @@ import io.element.android.libraries.wellknown.api.WellKnownBaseConfig
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class DefaultSessionWellknownRetrieverTest {
|
class DefaultSessionWellknownRetrieverTest {
|
||||||
|
|
@ -107,7 +107,7 @@ class DefaultSessionWellknownRetrieverTest {
|
||||||
"other": true
|
"other": true
|
||||||
}""".trimIndent().toByteArray()
|
}""".trimIndent().toByteArray()
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
assertThat(sut.getWellKnown()).isEqualTo(
|
assertThat(sut.getWellKnown()).isEqualTo(
|
||||||
WellKnown(
|
WellKnown(
|
||||||
|
|
@ -201,7 +201,7 @@ class DefaultSessionWellknownRetrieverTest {
|
||||||
"other": true
|
"other": true
|
||||||
}""".trimIndent().toByteArray()
|
}""".trimIndent().toByteArray()
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
assertThat(sut.getElementWellKnown()).isEqualTo(
|
assertThat(sut.getElementWellKnown()).isEqualTo(
|
||||||
ElementWellKnown(
|
ElementWellKnown(
|
||||||
|
|
@ -244,6 +244,6 @@ class DefaultSessionWellknownRetrieverTest {
|
||||||
userIdServerNameLambda = { "user.domain.org" },
|
userIdServerNameLambda = { "user.domain.org" },
|
||||||
getUrlLambda = getUrlLambda,
|
getUrlLambda = getUrlLambda,
|
||||||
),
|
),
|
||||||
json = Json { ignoreUnknownKeys = true },
|
json = DefaultJsonProvider(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue