From ec49767bc2ff670db45fc1758f2ba821a3d010b8 Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Sat, 25 May 2024 19:46:16 -0700 Subject: [PATCH 1/2] Fix tests in LoggingIdentifierControllerTest. --- .../LoggingIdentifierControllerTest.kt | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/LoggingIdentifierControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/LoggingIdentifierControllerTest.kt index b2c74d34ad0..f8e19d76d58 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/LoggingIdentifierControllerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/LoggingIdentifierControllerTest.kt @@ -1,9 +1,11 @@ package org.oppia.android.domain.oppialogger import android.app.Application +import android.app.Instrumentation import android.content.Context import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import com.google.common.truth.Truth.assertThat import com.google.protobuf.MessageLite import dagger.BindsInstance @@ -73,6 +75,7 @@ class LoggingIdentifierControllerTest { @Before fun setUp() { + TestLoggingIdentifierModule.applicationIdSeed = INITIAL_APPLICATION_ID setUpTestApplicationComponent() } @@ -99,7 +102,8 @@ class LoggingIdentifierControllerTest { @Test fun testGetInstallationId_secondAppOpen_providerReturnsSameInstallationIdValue() { monitorFactory.ensureDataProviderExecutes(loggingIdentifierController.getInstallationId()) - setUpTestApplicationComponent() // Simulate an app re-open. + TestLoggingIdentifierModule.applicationIdSeed = SECOND_APP_OPEN_APPLICATION_ID + setUpNewTestApplicationComponent() // Simulate an app re-open with a new app ID. val installationId = monitorFactory.waitForNextSuccessfulResult(loggingIdentifierController.getInstallationId()) @@ -130,7 +134,8 @@ class LoggingIdentifierControllerTest { @Test fun testFetchInstallationId_secondAppOpen_returnsSameInstallationIdValue() { monitorFactory.ensureDataProviderExecutes(loggingIdentifierController.getInstallationId()) - setUpTestApplicationComponent() // Simulate an app re-open. + TestLoggingIdentifierModule.applicationIdSeed = SECOND_APP_OPEN_APPLICATION_ID + setUpNewTestApplicationComponent() // Simulate an app re-open with a new app ID. val installationId = fetchSuccessfulAsyncValue(loggingIdentifierController::fetchInstallationId) @@ -167,6 +172,19 @@ class LoggingIdentifierControllerTest { assertThat(sessionId).isEqualTo("4d0a66f3-82b6-3aa9-8f61-140bdd5f49d3") } + @Test + fun testGetSessionId_secondAppOpen_returnsNewRandomId() { + monitorFactory.ensureDataProviderExecutes(loggingIdentifierController.getSessionId()) + TestLoggingIdentifierModule.applicationIdSeed = SECOND_APP_OPEN_APPLICATION_ID + setUpNewTestApplicationComponent() // Simulate an app re-open with a new app ID. + + val sessionIdProvider = loggingIdentifierController.getSessionId() + + // The second call should return the same ID (since the ID doesn't automatically change). + val sessionId = monitorFactory.waitForNextSuccessfulResult(sessionIdProvider) + assertThat(sessionId).isEqualTo("18c2816d-f7ad-312f-b696-d3fdd51f2e92") + } + @Test fun testGetSessionIdFlow_initialState_returnsFlowWithRandomId() { val sessionIdFlow = loggingIdentifierController.getSessionIdFlow() @@ -292,6 +310,17 @@ class LoggingIdentifierControllerTest { ApplicationProvider.getApplicationContext().inject(this) } + private fun setUpNewTestApplicationComponent() { + createNewTestApplication().inject(this) + } + + private fun createNewTestApplication(): TestApplication { + return Instrumentation.newApplication( + TestApplication::class.java, + InstrumentationRegistry.getInstrumentation().targetContext + ) as TestApplication + } + // TODO(#89): Move this to a common test application component. @Module class TestModule { @@ -327,12 +356,12 @@ class LoggingIdentifierControllerTest { @Module class TestLoggingIdentifierModule { companion object { - internal const val applicationIdSeed = 1L + internal var applicationIdSeed: Long? = null } @Provides @ApplicationIdSeed - fun provideApplicationIdSeed(): Long = applicationIdSeed + fun provideApplicationIdSeed(): Long = applicationIdSeed!! // Fail if not initialized. } @Module @@ -398,4 +427,9 @@ class LoggingIdentifierControllerTest { override fun getDataProvidersInjector(): DataProvidersInjector = component } + + companion object { + private const val INITIAL_APPLICATION_ID = 1L + private const val SECOND_APP_OPEN_APPLICATION_ID = 2L + } } From 3d109e91f5b1e9c58180fe649d80dccf6e4bad58 Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Sat, 25 May 2024 20:04:42 -0700 Subject: [PATCH 2/2] Improve cache corruption & deletion tests. --- .../LoggingIdentifierControllerTest.kt | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/LoggingIdentifierControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/LoggingIdentifierControllerTest.kt index f8e19d76d58..af4b5b5d538 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/LoggingIdentifierControllerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/LoggingIdentifierControllerTest.kt @@ -114,15 +114,35 @@ class LoggingIdentifierControllerTest { @Test fun testGetInstallationId_secondAppOpen_emptiedDatabase_providerReturnsEmptyString() { + // Simulate initing the installation ID, then emptying/corrupting it, then reopening the app. + monitorFactory.ensureDataProviderExecutes(loggingIdentifierController.getInstallationId()) writeFileCache("device_context_database", DeviceContextDatabase.getDefaultInstance()) + TestLoggingIdentifierModule.applicationIdSeed = SECOND_APP_OPEN_APPLICATION_ID + setUpNewTestApplicationComponent() // Simulate an app re-open with a new app ID. val installationId = monitorFactory.waitForNextSuccessfulResult(loggingIdentifierController.getInstallationId()) - // The installation ID is empty since the database was overwritten. + // If the file was emptied, no installation ID can be loaded (this is a critical failure case). assertThat(installationId).isEmpty() } + @Test + fun testGetInstallationId_secondAppOpen_deletedDatabase_providerReturnsNewInstallationIdValue() { + // Simulate initing the installation ID, then deleting it, then reopening the app. + monitorFactory.ensureDataProviderExecutes(loggingIdentifierController.getInstallationId()) + deleteCacheFile("device_context_database") + TestLoggingIdentifierModule.applicationIdSeed = SECOND_APP_OPEN_APPLICATION_ID + setUpNewTestApplicationComponent() // Simulate an app re-open with a new app ID. + + val installationId = + monitorFactory.waitForNextSuccessfulResult(loggingIdentifierController.getInstallationId()) + + // It should seem like a reinstallation since the app's data has been cleared after restarting. + assertThat(installationId).isEqualTo("a52e69fcfedc") + assertThat(installationId.length).isEqualTo(12) + } + @Test fun testFetchInstallationId_initialAppState_returnsNewInstallationIdValue() { val installationId = fetchSuccessfulAsyncValue(loggingIdentifierController::fetchInstallationId) @@ -145,14 +165,34 @@ class LoggingIdentifierControllerTest { @Test fun testFetchInstallationId_secondAppOpen_emptiedDatabase_returnsNull() { + // Simulate initing the installation ID, then emptying/corrupting it, then reopening the app. + monitorFactory.ensureDataProviderExecutes(loggingIdentifierController.getInstallationId()) writeFileCache("device_context_database", DeviceContextDatabase.getDefaultInstance()) + TestLoggingIdentifierModule.applicationIdSeed = SECOND_APP_OPEN_APPLICATION_ID + setUpNewTestApplicationComponent() // Simulate an app re-open with a new app ID. val installationId = fetchSuccessfulAsyncValue(loggingIdentifierController::fetchInstallationId) - // The installation ID is null since the database was overwritten. + // If the file was emptied, no installation ID can be loaded (this is a critical failure case). assertThat(installationId).isNull() } + @Test + fun testFetchInstallationId_secondAppOpen_deletedDatabase_returnsNewInstallationIdValue() { + // Simulate initing the installation ID, then deleting it, then reopening the app. + monitorFactory.ensureDataProviderExecutes(loggingIdentifierController.getInstallationId()) + deleteCacheFile("device_context_database") + TestLoggingIdentifierModule.applicationIdSeed = SECOND_APP_OPEN_APPLICATION_ID + setUpNewTestApplicationComponent() // Simulate an app re-open with a new app ID. + monitorFactory.ensureDataProviderExecutes(loggingIdentifierController.getInstallationId()) + + val installationId = fetchSuccessfulAsyncValue(loggingIdentifierController::fetchInstallationId) + + // The installation ID is null since the database was overwritten. + assertThat(installationId).isEqualTo("a52e69fcfedc") + assertThat(installationId?.length).isEqualTo(12) + } + @Test fun testGetSessionId_initialState_returnsRandomId() { val sessionIdProvider = loggingIdentifierController.getSessionId() @@ -273,6 +313,10 @@ class LoggingIdentifierControllerTest { getCacheFile(cacheName).writeBytes(value.toByteArray()) } + private fun deleteCacheFile(cacheName: String) { + check(getCacheFile(cacheName).delete()) { "Failed to delete: $cacheName." } + } + private fun getCacheFile(cacheName: String) = File(context.filesDir, "$cacheName.cache") private fun File.writeBytes(data: ByteArray) {