From d55282a775cab6cd4001f02fc069dab01a9b0ece Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Nov 2023 09:56:45 +0100 Subject: [PATCH 1/6] Add test for IntentProviderImpl --- app/build.gradle.kts | 1 + .../x/intent/IntentProviderImplTest.kt | 92 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 app/src/test/kotlin/io/element/android/x/intent/IntentProviderImplTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 69a1877455..f936e991fc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -225,6 +225,7 @@ dependencies { kapt(libs.dagger.compiler) testImplementation(libs.test.junit) + testImplementation(libs.test.robolectric) testImplementation(libs.coroutines.test) testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) diff --git a/app/src/test/kotlin/io/element/android/x/intent/IntentProviderImplTest.kt b/app/src/test/kotlin/io/element/android/x/intent/IntentProviderImplTest.kt new file mode 100644 index 0000000000..9f0cde38cf --- /dev/null +++ b/app/src/test/kotlin/io/element/android/x/intent/IntentProviderImplTest.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.x.intent + +import android.content.Context +import android.content.Intent +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.deeplink.DeepLinkCreator +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.A_THREAD_ID +import io.element.android.x.MainActivity +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment + +@RunWith(RobolectricTestRunner::class) +class IntentProviderImplTest { + + @Test + fun `test getViewRoomIntent with Session`() { + val sut = createIntentProviderImpl() + val result = sut.getViewRoomIntent( + sessionId = A_SESSION_ID, + roomId = null, + threadId = null, + ) + result.commonAssertions() + assertThat(result.data.toString()).isEqualTo("elementx://open/@alice:server.org") + } + + @Test + fun `test getViewRoomIntent with Session and Room`() { + val sut = createIntentProviderImpl() + val result = sut.getViewRoomIntent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = null, + ) + result.commonAssertions() + assertThat(result.data.toString()).isEqualTo("elementx://open/@alice:server.org/!aRoomId:domain") + } + + @Test + fun `test getViewRoomIntent with Session, Room and Thread`() { + val sut = createIntentProviderImpl() + val result = sut.getViewRoomIntent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = A_THREAD_ID, + ) + result.commonAssertions() + assertThat(result.data.toString()).isEqualTo("elementx://open/@alice:server.org/!aRoomId:domain/\$aThreadId") + } + + @Test + fun `test getInviteListIntent`() { + val sut = createIntentProviderImpl() + val result = sut.getInviteListIntent( + sessionId = A_SESSION_ID, + ) + result.commonAssertions() + assertThat(result.data.toString()).isEqualTo("elementx://open/@alice:server.org/invites") + } + + private fun createIntentProviderImpl(): IntentProviderImpl { + return IntentProviderImpl( + context = RuntimeEnvironment.getApplication() as Context, + deepLinkCreator = DeepLinkCreator(), + ) + } + + private fun Intent.commonAssertions() { + assertThat(action).isEqualTo(Intent.ACTION_VIEW) + assertThat(component?.className).isEqualTo(MainActivity::class.java.name) + } +} From b4872033abfd37c11b801492775f186163372b87 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Nov 2023 10:22:23 +0100 Subject: [PATCH 2/6] Add test for IntentResolver --- appnav/build.gradle.kts | 2 + .../appnav/intent/IntentResolverTest.kt | 173 ++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts index 88f0741ebe..9407b16592 100644 --- a/appnav/build.gradle.kts +++ b/appnav/build.gradle.kts @@ -58,12 +58,14 @@ dependencies { implementation(projects.services.analytics.api) testImplementation(libs.test.junit) + testImplementation(libs.test.robolectric) testImplementation(libs.coroutines.test) testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.features.networkmonitor.test) + testImplementation(projects.features.login.impl) testImplementation(projects.tests.testutils) testImplementation(projects.features.rageshake.test) testImplementation(projects.features.rageshake.impl) diff --git a/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt new file mode 100644 index 0000000000..c2a4bcbd3a --- /dev/null +++ b/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.appnav.intent + +import android.app.Activity +import android.content.Intent +import androidx.core.net.toUri +import com.google.common.truth.Truth.assertThat +import io.element.android.features.login.api.oidc.OidcAction +import io.element.android.features.login.impl.oidc.DefaultOidcIntentResolver +import io.element.android.features.login.impl.oidc.OidcUrlParser +import io.element.android.libraries.deeplink.DeepLinkCreator +import io.element.android.libraries.deeplink.DeeplinkData +import io.element.android.libraries.deeplink.DeeplinkParser +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.A_THREAD_ID +import org.junit.Assert.assertThrows +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment + +@RunWith(RobolectricTestRunner::class) +class IntentResolverTest { + @Test + fun `test resolve navigation intent root`() { + val sut = createIntentResolver() + val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply { + action = Intent.ACTION_VIEW + data = DeepLinkCreator().room( + sessionId = A_SESSION_ID, + roomId = null, + threadId = null, + ) + .toUri() + } + val result = sut.resolve(intent) + assertThat(result).isEqualTo( + ResolvedIntent.Navigation( + deeplinkData = DeeplinkData.Root( + sessionId = A_SESSION_ID, + ) + ) + ) + } + + @Test + fun `test resolve navigation intent room`() { + val sut = createIntentResolver() + val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply { + action = Intent.ACTION_VIEW + data = DeepLinkCreator().room( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = null, + ) + .toUri() + } + val result = sut.resolve(intent) + assertThat(result).isEqualTo( + ResolvedIntent.Navigation( + deeplinkData = DeeplinkData.Room( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = null, + ) + ) + ) + } + + @Test + fun `test resolve navigation intent thread`() { + val sut = createIntentResolver() + val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply { + action = Intent.ACTION_VIEW + data = DeepLinkCreator().room( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = A_THREAD_ID, + ) + .toUri() + } + val result = sut.resolve(intent) + assertThat(result).isEqualTo( + ResolvedIntent.Navigation( + deeplinkData = DeeplinkData.Room( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = A_THREAD_ID, + ) + ) + ) + } + + @Test + fun `test resolve oidc go back`() { + val sut = createIntentResolver() + val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply { + action = Intent.ACTION_VIEW + data = "io.element:/callback?error=access_denied&state=IFF1UETGye2ZA8pO".toUri() + } + val result = sut.resolve(intent) + assertThat(result).isEqualTo( + ResolvedIntent.Oidc( + oidcAction = OidcAction.GoBack + ) + ) + } + + @Test + fun `test resolve oidc success`() { + val sut = createIntentResolver() + val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply { + action = Intent.ACTION_VIEW + data = "io.element:/callback?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB".toUri() + } + val result = sut.resolve(intent) + assertThat(result).isEqualTo( + ResolvedIntent.Oidc( + oidcAction = OidcAction.Success( + url = "io.element:/callback?state=IFF1UETGye2ZA8pO&code=y6X1GZeqA3xxOWcTeShgv8nkgFJXyzWB" + ) + ) + ) + } + + @Test + fun `test resolve oidc invalid`() { + val sut = createIntentResolver() + val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply { + action = Intent.ACTION_VIEW + data = "io.element:/callback/invalid".toUri() + } + assertThrows(IllegalStateException::class.java) { + sut.resolve(intent) + } + } + + @Test + fun `test resolve invalid`() { + val sut = createIntentResolver() + val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply { + action = Intent.ACTION_VIEW + data = "io.element:/invalid".toUri() + } + val result = sut.resolve(intent) + assertThat(result).isNull() + } + + private fun createIntentResolver(): IntentResolver { + return IntentResolver( + deeplinkParser = DeeplinkParser(), + oidcIntentResolver = DefaultOidcIntentResolver( + oidcUrlParser = OidcUrlParser() + ), + ) + } +} From b7aea758159c80df84885f826e60385fb82d7d28 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Nov 2023 10:32:56 +0100 Subject: [PATCH 3/6] Add test for VectorUncaughtExceptionHandler --- features/rageshake/impl/build.gradle.kts | 1 + .../VectorUncaughtExceptionHandlerTest.kt | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/VectorUncaughtExceptionHandlerTest.kt diff --git a/features/rageshake/impl/build.gradle.kts b/features/rageshake/impl/build.gradle.kts index f119cbf6c8..99f57a1936 100644 --- a/features/rageshake/impl/build.gradle.kts +++ b/features/rageshake/impl/build.gradle.kts @@ -50,6 +50,7 @@ dependencies { ksp(libs.showkase.processor) testImplementation(libs.test.junit) + testImplementation(libs.test.robolectric) testImplementation(libs.coroutines.test) testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/VectorUncaughtExceptionHandlerTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/VectorUncaughtExceptionHandlerTest.kt new file mode 100644 index 0000000000..dc01dfa2db --- /dev/null +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/VectorUncaughtExceptionHandlerTest.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.rageshake.impl.crash + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.test.AN_EXCEPTION +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment + +@RunWith(RobolectricTestRunner::class) +class VectorUncaughtExceptionHandlerTest { + @Test + fun `activate should change the default handler`() { + val sut = VectorUncaughtExceptionHandler(RuntimeEnvironment.getApplication()) + sut.activate() + assertThat(Thread.getDefaultUncaughtExceptionHandler()).isInstanceOf(VectorUncaughtExceptionHandler::class.java) + } + + @Test + fun `uncaught exception`() = runTest { + val crashDataStore = PreferencesCrashDataStore(RuntimeEnvironment.getApplication()) + assertThat(crashDataStore.appHasCrashed().first()).isFalse() + assertThat(crashDataStore.crashInfo().first()).isEmpty() + val sut = VectorUncaughtExceptionHandler(RuntimeEnvironment.getApplication()) + sut.uncaughtException(Thread(), AN_EXCEPTION) + assertThat(crashDataStore.appHasCrashed().first()).isTrue() + val crashInfo = crashDataStore.crashInfo().first() + assertThat(crashInfo).isNotEmpty() + assertThat(crashInfo).contains("Memory statuses") + crashDataStore.resetAppHasCrashed() + assertThat(crashDataStore.appHasCrashed().first()).isFalse() + } +} From 4ca6ed37d8b1ee1191e553849c949d745a58a995 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Nov 2023 10:56:08 +0100 Subject: [PATCH 4/6] Cleanup DefaultBugReporter before testing it. --- .../rageshake/api/reporter/BugReporter.kt | 8 - .../api/reporter/BugReporterListener.kt | 2 +- .../rageshake/api/reporter/ReportType.kt | 26 --- .../impl/bugreport/BugReportPresenter.kt | 11 +- .../impl/reporter/DefaultBugReporter.kt | 199 +----------------- .../impl/src/main/res/values/strings.xml | 10 +- .../impl/bugreport/FakeBugReporter.kt | 7 +- 7 files changed, 21 insertions(+), 242 deletions(-) delete mode 100644 features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/ReportType.kt diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt index 99849ef1d4..38992f25f5 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt @@ -22,27 +22,19 @@ interface BugReporter { /** * Send a bug report. * - * @param reportType The report type (bug, suggestion, feedback) * @param withDevicesLogs true to include the device log * @param withCrashLogs true to include the crash logs - * @param withKeyRequestHistory true to include the crash logs * @param withScreenshot true to include the screenshot * @param theBugDescription the bug description - * @param serverVersion version of the server * @param canContact true if the user opt in to be contacted directly - * @param customFields fields which will be sent with the report * @param listener the listener */ suspend fun sendBugReport( - reportType: ReportType, withDevicesLogs: Boolean, withCrashLogs: Boolean, - withKeyRequestHistory: Boolean, withScreenshot: Boolean, theBugDescription: String, - serverVersion: String, canContact: Boolean = false, - customFields: Map? = null, listener: BugReporterListener? ) diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporterListener.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporterListener.kt index 8f2ae90d1c..328a117ad1 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporterListener.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporterListener.kt @@ -42,5 +42,5 @@ interface BugReporterListener { /** * The bug report upload succeeded. */ - fun onUploadSucceed(reportUrl: String?) + fun onUploadSucceed() } diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/ReportType.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/ReportType.kt deleted file mode 100644 index 17b75ea1a7..0000000000 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/ReportType.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2022 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.features.rageshake.api.reporter - -enum class ReportType { - BUG_REPORT, - SUGGESTION, - SPACE_BETA_FEEDBACK, - THREADS_BETA_FEEDBACK, - AUTO_UISI, - AUTO_UISI_SENDER, -} diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt index 9258909201..e90787ab5c 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt @@ -25,12 +25,11 @@ import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable +import io.element.android.features.rageshake.api.crash.CrashDataStore import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.features.rageshake.api.reporter.BugReporterListener -import io.element.android.features.rageshake.api.reporter.ReportType -import io.element.android.features.rageshake.api.crash.CrashDataStore -import io.element.android.features.rageshake.impl.logs.VectorFileLogger import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder +import io.element.android.features.rageshake.impl.logs.VectorFileLogger import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import kotlinx.coroutines.CoroutineScope @@ -64,7 +63,7 @@ class BugReportPresenter @Inject constructor( sendingAction.value = Async.Loading() } - override fun onUploadSucceed(reportUrl: String?) { + override fun onUploadSucceed() { sendingProgress.floatValue = 0f sendingAction.value = Async.Success(Unit) } @@ -135,15 +134,11 @@ class BugReportPresenter @Inject constructor( listener: BugReporterListener, ) = launch { bugReporter.sendBugReport( - reportType = ReportType.BUG_REPORT, withDevicesLogs = formState.sendLogs, withCrashLogs = hasCrashLogs && formState.sendLogs, - withKeyRequestHistory = false, withScreenshot = formState.sendScreenshot, theBugDescription = formState.description, - serverVersion = "", canContact = formState.canContact, - customFields = emptyMap(), listener = listener ) } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt index a693189d3c..5d7a0160fc 100755 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt @@ -25,14 +25,12 @@ import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.rageshake.api.crash.CrashDataStore import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.features.rageshake.api.reporter.BugReporterListener -import io.element.android.features.rageshake.api.reporter.ReportType import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder import io.element.android.features.rageshake.impl.R import io.element.android.libraries.androidutils.file.compressFile import io.element.android.libraries.androidutils.file.safeDelete import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.data.tryOrNull -import io.element.android.libraries.core.extensions.toOnOff import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.AppScope @@ -77,26 +75,11 @@ class DefaultBugReporter @Inject constructor( private val userAgentProvider: UserAgentProvider, private val sessionStore: SessionStore, private val buildMeta: BuildMeta, - /* - private val versionProvider: VersionProvider, - private val vectorPreferences: VectorPreferences, - private val vectorFileLogger: VectorFileLogger, - private val systemLocaleProvider: SystemLocaleProvider, - private val matrix: Matrix, - private val processInfo: ProcessInfo, - private val sdkIntProvider: BuildVersionSdkIntProvider, - private val vectorLocale: VectorLocaleProvider, - */ ) : BugReporter { - var inMultiWindowMode = false - companion object { // filenames - private const val LOG_CAT_ERROR_FILENAME = "logcatError.log" private const val LOG_CAT_FILENAME = "logcat.log" private const val LOG_DIRECTORY_NAME = "logs" - // private const val KEY_REQUESTS_FILENAME = "keyRequests.log" - private const val BUFFER_SIZE = 1024 * 1024 * 50 } @@ -105,58 +88,20 @@ class DefaultBugReporter @Inject constructor( // boolean to cancel the bug report private val isCancelled = false - - /* - val adapter = MatrixJsonParser.getMoshi() - .adapter(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)) - */ - - private val logcatCommandError = arrayOf( - "logcat", // /< Run 'logcat' command - "-d", // /< Dump the log rather than continue outputting it - "-v", // formatting - "threadtime", // include timestamps - "AndroidRuntime:E " + // /< Pick all AndroidRuntime errors (such as uncaught exceptions)"communicatorjni:V " + ///< All communicatorjni logging - "libcommunicator:V " + // /< All libcommunicator logging - "DEBUG:V " + // /< All DEBUG logging - which includes native land crashes (seg faults, etc) - "*:S" // /< Everything else silent, so don't pick it.. - ) - private val logcatCommandDebug = arrayOf("logcat", "-d", "-v", "threadtime", "*:*") - /** - * Send a bug report. - * - * @param reportType The report type (bug, suggestion, feedback) - * @param withDevicesLogs true to include the device log - * @param withCrashLogs true to include the crash logs - * @param withKeyRequestHistory true to include the crash logs - * @param withScreenshot true to include the screenshot - * @param theBugDescription the bug description - * @param serverVersion version of the server - * @param canContact true if the user opt in to be contacted directly - * @param customFields fields which will be sent with the report - * @param listener the listener - */ override suspend fun sendBugReport( - reportType: ReportType, withDevicesLogs: Boolean, withCrashLogs: Boolean, - withKeyRequestHistory: Boolean, withScreenshot: Boolean, theBugDescription: String, - serverVersion: String, canContact: Boolean, - customFields: Map?, listener: BugReporterListener? ) { // enumerate files to delete val bugReportFiles: MutableList = ArrayList() - try { - var serverError: String? = null - var reportURL: String? = null withContext(coroutineDispatchers.io) { var bugDescription = theBugDescription val crashCallStack = crashDataStore.crashInfo().first() @@ -181,7 +126,7 @@ class DefaultBugReporter @Inject constructor( } if (!isCancelled && (withCrashLogs || withDevicesLogs)) { - val gzippedLogcat = saveLogCat(false) + val gzippedLogcat = saveLogCat() if (null != gzippedLogcat) { if (gzippedFiles.size == 0) { @@ -192,68 +137,21 @@ class DefaultBugReporter @Inject constructor( } } - /* - activeSessionHolder.getSafeActiveSession() - ?.takeIf { !mIsCancelled && withKeyRequestHistory } - ?.cryptoService() - ?.getGossipingEvents() - ?.let { GossipingEventsSerializer().serialize(it) } - ?.toByteArray() - ?.let { rawByteArray -> - File(context.cacheDir.absolutePath, KEY_REQUESTS_FILENAME) - .also { - it.outputStream() - .use { os -> os.write(rawByteArray) } - } - } - ?.let { compressFile(it) } - ?.let { gzippedFiles.add(it) } - */ - val sessionData = sessionStore.getLatestSession() val deviceId = sessionData?.deviceId ?: "undefined" val userId = sessionData?.userId ?: "undefined" - var olmVersion = "undefined" if (!isCancelled) { - val text = when (reportType) { - ReportType.BUG_REPORT -> bugDescription - ReportType.SUGGESTION -> "[Suggestion] $bugDescription" - ReportType.SPACE_BETA_FEEDBACK -> "[spaces-feedback] $bugDescription" - ReportType.THREADS_BETA_FEEDBACK -> "[threads-feedback] $bugDescription" - ReportType.AUTO_UISI_SENDER, - ReportType.AUTO_UISI -> bugDescription - } - // build the multi part request val builder = BugReporterMultipartBody.Builder() - .addFormDataPart("text", text) - .addFormDataPart("app", rageShakeAppNameForReport(reportType)) + .addFormDataPart("text", bugDescription) + .addFormDataPart("app", context.getString(R.string.bug_report_app_name)) .addFormDataPart("user_agent", userAgentProvider.provide()) .addFormDataPart("user_id", userId) .addFormDataPart("can_contact", canContact.toString()) .addFormDataPart("device_id", deviceId) - // .addFormDataPart("version", versionProvider.getVersion(longFormat = true)) - // .addFormDataPart("branch_name", buildMeta.gitBranchName) - // .addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion()) - .addFormDataPart("olm_version", olmVersion) .addFormDataPart("device", Build.MODEL.trim()) - // .addFormDataPart("verbose_log", vectorPreferences.labAllowedExtendedLogging().toOnOff()) - .addFormDataPart("multi_window", inMultiWindowMode.toOnOff()) - // .addFormDataPart( - // "os", Build.VERSION.RELEASE + " (API " + sdkIntProvider.get() + ") " + - // Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME - // ) .addFormDataPart("locale", Locale.getDefault().toString()) - // .addFormDataPart("app_language", vectorLocale.applicationLocale.toString()) - // .addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString()) - // .addFormDataPart("theme", ThemeUtils.getApplicationTheme(context)) - .addFormDataPart("server_version", serverVersion) - .apply { - customFields?.forEach { (name, value) -> - addFormDataPart(name, value) - } - } // add the gzipped files, don't cancel the whole upload if only some file failed to upload var uploadedSomeLogs = false @@ -293,31 +191,6 @@ class DefaultBugReporter @Inject constructor( // add some github labels builder.addFormDataPart("label", buildMeta.versionName) - // builder.addFormDataPart("label", buildMeta.flavorDescription) - // builder.addFormDataPart("label", buildMeta.gitBranchName) - - // Possible values for BuildConfig.BUILD_TYPE: "debug", "nightly", "release". - // builder.addFormDataPart("label", BuildConfig.BUILD_TYPE) - - when (reportType) { - ReportType.BUG_REPORT -> { - /* nop */ - } - ReportType.SUGGESTION -> builder.addFormDataPart("label", "[Suggestion]") - ReportType.SPACE_BETA_FEEDBACK -> builder.addFormDataPart("label", "spaces-feedback") - ReportType.THREADS_BETA_FEEDBACK -> builder.addFormDataPart("label", "threads-feedback") - ReportType.AUTO_UISI -> { - builder.addFormDataPart("label", "Z-UISI") - builder.addFormDataPart("label", "android") - builder.addFormDataPart("label", "uisi-recipient") - } - ReportType.AUTO_UISI_SENDER -> { - builder.addFormDataPart("label", "Z-UISI") - builder.addFormDataPart("label", "android") - builder.addFormDataPart("label", "uisi-sender") - } - } - if (crashCallStack.isNotEmpty() && withCrashLogs) { builder.addFormDataPart("label", "crash") } @@ -379,7 +252,6 @@ class DefaultBugReporter @Inject constructor( } else { try { val inputStream = response.body!!.byteStream() - serverError = inputStream.use { buildString { var ch = it.read() @@ -389,7 +261,6 @@ class DefaultBugReporter @Inject constructor( } } } - // check if the error message serverError?.let { try { @@ -401,7 +272,6 @@ class DefaultBugReporter @Inject constructor( Timber.e(e, "doInBackground ; Json conversion failed") } } - // should never happen if (null == serverError) { serverError = "Failed with error $responseCode" @@ -412,25 +282,17 @@ class DefaultBugReporter @Inject constructor( Timber.e(e, "## sendBugReport() : failed to parse error") } } - } else { - /* - reportURL = response?.body?.string()?.let { stringBody -> - adapter.fromJson(stringBody)?.get("report_url")?.toString() - } - */ } } } - withContext(coroutineDispatchers.main) { bugReportCall = null - if (null != listener) { try { if (isCancelled) { listener.onUploadCancelled() } else if (null == serverError) { - listener.onUploadSucceed(reportURL) + listener.onUploadSucceed() } else { listener.onUploadFailed(serverError) } @@ -449,47 +311,6 @@ class DefaultBugReporter @Inject constructor( } } - /** - * Send a bug report either with email or with Vector. - */ - /* TODO Remove - fun openBugReportScreen(activity: FragmentActivity, reportType: ReportType = ReportType.BUG_REPORT) { - screenshot = takeScreenshot(activity) - logDbInfo() - logProcessInfo() - logOtherInfo() - activity.startActivity(BugReportActivity.intent(activity, reportType)) - } - */ - - // private fun logOtherInfo() { - // Timber.i("SyncThread state: " + activeSessionHolder.getSafeActiveSession()?.syncService()?.getSyncState()) - // } - - // private fun logDbInfo() { - // val dbInfo = matrix.debugService().getDbUsageInfo() - // Timber.i(dbInfo) - // } - - // private fun logProcessInfo() { - // val pInfo = processInfo.getInfo() - // Timber.i(pInfo) - // } - - private fun rageShakeAppNameForReport(reportType: ReportType): String { - // As per https://github.com/matrix-org/rageshake - // app: Identifier for the application (eg 'riot-web'). - // Should correspond to a mapping configured in the configuration file for github issue reporting to work. - // (see R.string.bug_report_url for configured RS server) - return context.getString( - when (reportType) { - ReportType.AUTO_UISI_SENDER, - ReportType.AUTO_UISI -> R.string.bug_report_auto_uisi_app_name - else -> R.string.bug_report_app_name - } - ) - } - override fun logDirectory(): File { return File(context.cacheDir, LOG_DIRECTORY_NAME) } @@ -545,11 +366,10 @@ class DefaultBugReporter @Inject constructor( /** * Save the logcat. * - * @param isErrorLogcat true to save the error logcat * @return the file if the operation succeeds */ - private fun saveLogCat(isErrorLogcat: Boolean): File? { - val logCatErrFile = File(context.cacheDir.absolutePath, if (isErrorLogcat) LOG_CAT_ERROR_FILENAME else LOG_CAT_FILENAME) + private fun saveLogCat(): File? { + val logCatErrFile = File(context.cacheDir.absolutePath, LOG_CAT_FILENAME) if (logCatErrFile.exists()) { logCatErrFile.safeDelete() @@ -557,7 +377,7 @@ class DefaultBugReporter @Inject constructor( try { logCatErrFile.writer().use { - getLogCatError(it, isErrorLogcat) + getLogCatError(it) } return compressFile(logCatErrFile) @@ -578,13 +398,12 @@ class DefaultBugReporter @Inject constructor( * Retrieves the logs. * * @param streamWriter the stream writer - * @param isErrorLogCat true to save the error logs */ - private fun getLogCatError(streamWriter: OutputStreamWriter, isErrorLogCat: Boolean) { + private fun getLogCatError(streamWriter: OutputStreamWriter) { val logcatProc: Process try { - logcatProc = Runtime.getRuntime().exec(if (isErrorLogCat) logcatCommandError else logcatCommandDebug) + logcatProc = Runtime.getRuntime().exec(logcatCommandDebug) } catch (e1: IOException) { return } diff --git a/features/rageshake/impl/src/main/res/values/strings.xml b/features/rageshake/impl/src/main/res/values/strings.xml index 48eff44d57..dc1069570b 100644 --- a/features/rageshake/impl/src/main/res/values/strings.xml +++ b/features/rageshake/impl/src/main/res/values/strings.xml @@ -1,5 +1,4 @@ - - https://riot.im/bugreports/submit + element-x-android - element-auto-uisi + diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt index 82edaf563d..00343f8f6e 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt @@ -18,22 +18,17 @@ package io.element.android.features.rageshake.impl.bugreport import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.features.rageshake.api.reporter.BugReporterListener -import io.element.android.features.rageshake.api.reporter.ReportType import io.element.android.libraries.matrix.test.A_FAILURE_REASON import kotlinx.coroutines.delay import java.io.File class FakeBugReporter(val mode: FakeBugReporterMode = FakeBugReporterMode.Success) : BugReporter { override suspend fun sendBugReport( - reportType: ReportType, withDevicesLogs: Boolean, withCrashLogs: Boolean, - withKeyRequestHistory: Boolean, withScreenshot: Boolean, theBugDescription: String, - serverVersion: String, canContact: Boolean, - customFields: Map?, listener: BugReporterListener?, ) { delay(100) @@ -54,7 +49,7 @@ class FakeBugReporter(val mode: FakeBugReporterMode = FakeBugReporterMode.Succes } listener?.onProgress(100) delay(100) - listener?.onUploadSucceed(null) + listener?.onUploadSucceed() } override fun cleanLogDirectoryIfNeeded() { From 628d02beb1e95832ca8ae6058ff13ec28ed3a532 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Nov 2023 16:10:41 +0100 Subject: [PATCH 5/6] Add test for DefaultBugReporter --- features/rageshake/impl/build.gradle.kts | 9 ++ .../impl/reporter/BugReporterUrlProvider.kt | 23 +++ .../impl/reporter/DefaultBugReporter.kt | 3 +- .../reporter/DefaultBugReporterUrlProvider.kt | 34 ++++ .../impl/reporter/DefaultBugReporterTest.kt | 150 ++++++++++++++++++ .../DefaultBugReporterUrlProviderTest.kt | 31 ++++ gradle/libs.versions.toml | 1 + .../test/strings/FakeStringProvider.kt | 35 ++++ 8 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterUrlProvider.kt create mode 100644 features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProvider.kt create mode 100755 features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt create mode 100644 features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt create mode 100644 services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt diff --git a/features/rageshake/impl/build.gradle.kts b/features/rageshake/impl/build.gradle.kts index 99f57a1936..f87858ddc2 100644 --- a/features/rageshake/impl/build.gradle.kts +++ b/features/rageshake/impl/build.gradle.kts @@ -23,6 +23,12 @@ plugins { android { namespace = "io.element.android.features.rageshake.impl" + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } } anvil { @@ -57,6 +63,9 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(libs.test.mockk) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.sessionStorage.implMemory) testImplementation(projects.features.rageshake.test) testImplementation(projects.tests.testutils) + testImplementation(projects.services.toolbox.test) + testImplementation(libs.network.mockwebserver) } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterUrlProvider.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterUrlProvider.kt new file mode 100644 index 0000000000..c380ab157d --- /dev/null +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterUrlProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.rageshake.impl.reporter + +import okhttp3.HttpUrl + +fun interface BugReporterUrlProvider { + fun provide(): HttpUrl +} diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt index 5d7a0160fc..3bd7405f52 100755 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt @@ -75,6 +75,7 @@ class DefaultBugReporter @Inject constructor( private val userAgentProvider: UserAgentProvider, private val sessionStore: SessionStore, private val buildMeta: BuildMeta, + private val bugReporterUrlProvider: BugReporterUrlProvider, ) : BugReporter { companion object { // filenames @@ -223,7 +224,7 @@ class DefaultBugReporter @Inject constructor( // build the request val request = Request.Builder() - .url(context.getString(R.string.bug_report_url)) + .url(bugReporterUrlProvider.provide()) .post(requestBody) .build() diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProvider.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProvider.kt new file mode 100644 index 0000000000..5907a9a1c9 --- /dev/null +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProvider.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.rageshake.impl.reporter + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.rageshake.impl.R +import io.element.android.libraries.di.AppScope +import io.element.android.services.toolbox.api.strings.StringProvider +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultBugReporterUrlProvider @Inject constructor( + private val stringProvider: StringProvider +) : BugReporterUrlProvider { + override fun provide(): HttpUrl { + return stringProvider.getString(R.string.bug_report_url).toHttpUrl() + } +} diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt new file mode 100755 index 0000000000..53039176a8 --- /dev/null +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.rageshake.impl.reporter + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.rageshake.api.reporter.BugReporterListener +import io.element.android.features.rageshake.test.crash.FakeCrashDataStore +import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder +import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.libraries.network.useragent.DefaultUserAgentProvider +import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore +import io.element.android.services.toolbox.test.systemclock.FakeSystemClock +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import okhttp3.OkHttpClient +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment + +@RunWith(RobolectricTestRunner::class) +class DefaultBugReporterTest { + @Test + fun `test sendBugReport success`() = runTest { + val server = MockWebServer() + server.enqueue( + MockResponse() + .setResponseCode(200) + ) + server.start() + val sut = createDefaultBugReporter(server) + var onUploadCancelledCalled = false + var onUploadFailedCalled = false + var progressValues = mutableListOf() + var onUploadSucceedCalled = false + sut.sendBugReport( + withDevicesLogs = true, + withCrashLogs = true, + withScreenshot = true, + theBugDescription = "a bug occurred", + canContact = true, + listener = object : BugReporterListener { + override fun onUploadCancelled() { + onUploadCancelledCalled = true + } + + override fun onUploadFailed(reason: String?) { + onUploadFailedCalled = true + } + + override fun onProgress(progress: Int) { + progressValues.add(progress) + } + + override fun onUploadSucceed() { + onUploadSucceedCalled = true + } + }, + ) + server.shutdown() + assertThat(onUploadCancelledCalled).isFalse() + assertThat(onUploadFailedCalled).isFalse() + assertThat(progressValues.size).isEqualTo(10) + assertThat(onUploadSucceedCalled).isTrue() + } + + @Test + fun `test sendBugReport error`() = runTest { + val server = MockWebServer() + server.enqueue( + MockResponse() + .setResponseCode(400) + .setBody("""{"error": "An error body"}""") + ) + server.start() + val sut = createDefaultBugReporter(server) + var onUploadCancelledCalled = false + var onUploadFailedCalled = false + var onUploadFailedReason: String? = null + var progressValues = mutableListOf() + var onUploadSucceedCalled = false + sut.sendBugReport( + withDevicesLogs = true, + withCrashLogs = true, + withScreenshot = true, + theBugDescription = "a bug occurred", + canContact = true, + listener = object : BugReporterListener { + override fun onUploadCancelled() { + onUploadCancelledCalled = true + } + + override fun onUploadFailed(reason: String?) { + onUploadFailedCalled = true + onUploadFailedReason = reason + } + + override fun onProgress(progress: Int) { + progressValues.add(progress) + } + + override fun onUploadSucceed() { + onUploadSucceedCalled = true + } + }, + ) + server.shutdown() + assertThat(onUploadCancelledCalled).isFalse() + assertThat(onUploadFailedCalled).isTrue() + assertThat(onUploadFailedReason).isEqualTo("An error body") + assertThat(progressValues.size).isEqualTo(10) + assertThat(onUploadSucceedCalled).isFalse() + } + + private fun TestScope.createDefaultBugReporter( + server: MockWebServer + ): DefaultBugReporter { + val buildMeta = aBuildMeta() + return DefaultBugReporter( + context = RuntimeEnvironment.getApplication(), + screenshotHolder = FakeScreenshotHolder(), + crashDataStore = FakeCrashDataStore(), + coroutineScope = this, + systemClock = FakeSystemClock(), + coroutineDispatchers = testCoroutineDispatchers(), + okHttpClient = { OkHttpClient.Builder().build() }, + userAgentProvider = DefaultUserAgentProvider(buildMeta), + sessionStore = InMemorySessionStore(), + buildMeta = buildMeta, + bugReporterUrlProvider = { server.url("/") } + ) + } +} diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt new file mode 100644 index 0000000000..a5c36df185 --- /dev/null +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.rageshake.impl.reporter + +import com.google.common.truth.Truth.assertThat +import io.element.android.services.toolbox.test.strings.FakeStringProvider +import okhttp3.HttpUrl.Companion.toHttpUrl +import org.junit.Test + +class DefaultBugReporterUrlProviderTest { + @Test + fun `test DefaultBugReporterUrlProvider`() { + val sut = DefaultBugReporterUrlProvider(FakeStringProvider("https://example.org")) + val result = sut.provide() + assertThat(result).isEqualTo("https://example.org".toHttpUrl()) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a79d92b7e1..ae2a0f71a2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -113,6 +113,7 @@ network_okhttp_bom = "com.squareup.okhttp3:okhttp-bom:4.12.0" network_okhttp_logging = { module = "com.squareup.okhttp3:logging-interceptor" } network_okhttp_okhttp = { module = "com.squareup.okhttp3:okhttp" } network_okhttp = { module = "com.squareup.okhttp3:okhttp" } +network_mockwebserver = { module = "com.squareup.okhttp3:mockwebserver" } network_retrofit = "com.squareup.retrofit2:retrofit:2.9.0" network_retrofit_converter_serialization = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0" diff --git a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt new file mode 100644 index 0000000000..c9f9a84fc9 --- /dev/null +++ b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.services.toolbox.test.strings + +import io.element.android.services.toolbox.api.strings.StringProvider + +class FakeStringProvider( + private val defaultResult: String = "A string" +) : StringProvider { + override fun getString(resId: Int): String { + return defaultResult + } + + override fun getString(resId: Int, vararg formatArgs: Any?): String { + return defaultResult + } + + override fun getQuantityString(resId: Int, quantity: Int, vararg formatArgs: Any?): String { + return defaultResult + } +} From 93ec54b4ce70de7667cde8f69c453a122901b4a4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 24 Nov 2023 16:47:38 +0100 Subject: [PATCH 6/6] Test the request which has been done. --- .../rageshake/impl/reporter/DefaultBugReporterTest.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt index 53039176a8..44e085c569 100755 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt @@ -74,6 +74,9 @@ class DefaultBugReporterTest { } }, ) + val request = server.takeRequest() + assertThat(request.path).isEqualTo("/") + assertThat(request.method).isEqualTo("POST") server.shutdown() assertThat(onUploadCancelledCalled).isFalse() assertThat(onUploadFailedCalled).isFalse() @@ -121,6 +124,9 @@ class DefaultBugReporterTest { } }, ) + val request = server.takeRequest() + assertThat(request.path).isEqualTo("/") + assertThat(request.method).isEqualTo("POST") server.shutdown() assertThat(onUploadCancelledCalled).isFalse() assertThat(onUploadFailedCalled).isTrue()