Skip to content

Add unit tests for session repo #6878

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.launch
import org.jetbrains.annotations.VisibleForTesting

/** Repository to persist session data to be shared between all app processes. */
internal interface SharedSessionRepository {
Expand All @@ -50,16 +49,17 @@ constructor(
@Background private val backgroundDispatcher: CoroutineContext,
) : SharedSessionRepository {
/** Local copy of the session data. Can get out of sync, must be double-checked in datastore. */
@VisibleForTesting lateinit var localSessionData: SessionData
internal lateinit var localSessionData: SessionData

/**
* Either notify the subscribers with general multi-process supported session or fallback local
* session
*/
private enum class NotificationType {
internal enum class NotificationType {
GENERAL,
FALLBACK
}
internal var previousNotificationType: NotificationType = NotificationType.GENERAL

init {
println("session repo init")
Expand Down Expand Up @@ -142,18 +142,19 @@ constructor(
}

private suspend fun notifySubscribers(sessionId: String, type: NotificationType) {
previousNotificationType = type
FirebaseSessionsDependencies.getRegisteredSubscribers().values.forEach { subscriber ->
// Notify subscribers, regardless of sampling and data collection state
subscriber.onSessionChanged(SessionSubscriber.SessionDetails(sessionId))
when (type) {
NotificationType.GENERAL ->
Log.d(TAG, "Notified ${subscriber.sessionSubscriberName} of new session $sessionId")
NotificationType.FALLBACK ->
Log.d(
TAG,
Log.d(
TAG,
when (type) {
NotificationType.GENERAL ->
"Notified ${subscriber.sessionSubscriberName} of new session $sessionId"
NotificationType.FALLBACK ->
"Notified ${subscriber.sessionSubscriberName} of new fallback session $sessionId"
)
}
}
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/*
* Copyright 2025 Google LLC
*
* 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 com.google.firebase.sessions

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import com.google.firebase.FirebaseApp
import com.google.firebase.concurrent.TestOnlyExecutors
import com.google.firebase.sessions.SessionGeneratorTest.Companion.SESSION_ID_1
import com.google.firebase.sessions.SessionGeneratorTest.Companion.SESSION_ID_2
import com.google.firebase.sessions.settings.SessionsSettings
import com.google.firebase.sessions.testing.FakeDataStore
import com.google.firebase.sessions.testing.FakeEventGDTLogger
import com.google.firebase.sessions.testing.FakeFirebaseApp
import com.google.firebase.sessions.testing.FakeFirebaseInstallations
import com.google.firebase.sessions.testing.FakeSettingsProvider
import com.google.firebase.sessions.testing.FakeTimeProvider
import com.google.firebase.sessions.testing.FakeUuidGenerator
import kotlin.time.Duration.Companion.hours
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class SharedSessionRepositoryTest {
private val fakeFirebaseApp = FakeFirebaseApp()
private val fakeEventGDTLogger = FakeEventGDTLogger()
private val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD", "FakeAuthToken")
private var fakeTimeProvider = FakeTimeProvider()
private val sessionGenerator = SessionGenerator(fakeTimeProvider, FakeUuidGenerator())
private var localSettingsProvider = FakeSettingsProvider(true, null, 100.0)
private var remoteSettingsProvider = FakeSettingsProvider(true, null, 100.0)
private var sessionsSettings = SessionsSettings(localSettingsProvider, remoteSettingsProvider)

@After
fun cleanUp() {
FirebaseApp.clearInstancesForTest()
}

@Test
fun initSharedSessionRepo_readFromDatastore() = runTest {
val publisher =
SessionFirelogPublisherImpl(
fakeFirebaseApp.firebaseApp,
firebaseInstallations,
sessionsSettings,
eventGDTLogger = fakeEventGDTLogger,
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext,
)
val fakeDataStore =
FakeDataStore<SessionData>(
SessionData(
SessionDetails(
SESSION_ID_INIT,
SESSION_ID_INIT,
0,
fakeTimeProvider.currentTime().ms,
),
fakeTimeProvider.currentTime(),
)
)
val sharedSessionRepository =
SharedSessionRepositoryImpl(
sessionsSettings = sessionsSettings,
sessionGenerator = sessionGenerator,
sessionFirelogPublisher = publisher,
timeProvider = fakeTimeProvider,
sessionDataStore = fakeDataStore,
backgroundDispatcher =
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext
)
runCurrent()
fakeDataStore.close()
assertThat(sharedSessionRepository.localSessionData.sessionDetails.sessionId)
.isEqualTo(SESSION_ID_INIT)
}

@Test
fun initSharedSessionRepo_initException() = runTest {
val sessionFirelogPublisher =
SessionFirelogPublisherImpl(
fakeFirebaseApp.firebaseApp,
firebaseInstallations,
sessionsSettings,
eventGDTLogger = fakeEventGDTLogger,
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext,
)
val fakeDataStore =
FakeDataStore<SessionData>(
SessionData(
SessionDetails(
SESSION_ID_INIT,
SESSION_ID_INIT,
0,
fakeTimeProvider.currentTime().ms,
),
fakeTimeProvider.currentTime(),
),
IllegalArgumentException("Datastore init failed")
)
val sharedSessionRepository =
SharedSessionRepositoryImpl(
sessionsSettings,
sessionGenerator,
sessionFirelogPublisher,
fakeTimeProvider,
fakeDataStore,
backgroundDispatcher =
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext
)
runCurrent()
fakeDataStore.close()
assertThat(sharedSessionRepository.localSessionData.sessionDetails.sessionId)
.isEqualTo(SESSION_ID_1)
}

@Test
fun appForegroundSharedSessionRepo_updateSuccess() = runTest {
val sessionFirelogPublisher =
SessionFirelogPublisherImpl(
fakeFirebaseApp.firebaseApp,
firebaseInstallations,
sessionsSettings,
eventGDTLogger = fakeEventGDTLogger,
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext,
)
val fakeDataStore =
FakeDataStore<SessionData>(
SessionData(
SessionDetails(
SESSION_ID_INIT,
SESSION_ID_INIT,
0,
fakeTimeProvider.currentTime().ms,
),
fakeTimeProvider.currentTime(),
)
)
val sharedSessionRepository =
SharedSessionRepositoryImpl(
sessionsSettings,
sessionGenerator,
sessionFirelogPublisher,
fakeTimeProvider,
fakeDataStore,
backgroundDispatcher =
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext
)
backgroundScope.launch {
fakeTimeProvider.addInterval(20.hours)
sharedSessionRepository.appForeground()
}
runCurrent()
assertThat(sharedSessionRepository.localSessionData.sessionDetails.sessionId)
.isEqualTo(SESSION_ID_1)
assertThat(sharedSessionRepository.previousNotificationType)
.isEqualTo(SharedSessionRepositoryImpl.NotificationType.GENERAL)
fakeDataStore.close()
}

@Test
fun appForegroundSharedSessionRepo_updateFail() = runTest {
val sessionFirelogPublisher =
SessionFirelogPublisherImpl(
fakeFirebaseApp.firebaseApp,
firebaseInstallations,
sessionsSettings,
eventGDTLogger = fakeEventGDTLogger,
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext,
)
val fakeDataStore =
FakeDataStore<SessionData>(
SessionData(
SessionDetails(
SESSION_ID_INIT,
SESSION_ID_INIT,
0,
fakeTimeProvider.currentTime().ms,
),
fakeTimeProvider.currentTime(),
),
IllegalArgumentException("Datastore init failed")
)
fakeDataStore.throwOnNextUpdateData(IllegalArgumentException("Datastore update failed"))
val sharedSessionRepository =
SharedSessionRepositoryImpl(
sessionsSettings,
sessionGenerator,
sessionFirelogPublisher,
fakeTimeProvider,
fakeDataStore,
backgroundDispatcher =
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext
)

backgroundScope.launch {
fakeTimeProvider.addInterval(20.hours)
sharedSessionRepository.appForeground()
}
runCurrent()
// session_2 here because session_1 is failed when try to init datastore
assertThat(sharedSessionRepository.localSessionData.sessionDetails.sessionId)
.isEqualTo(SESSION_ID_2)
assertThat(sharedSessionRepository.previousNotificationType)
.isEqualTo(SharedSessionRepositoryImpl.NotificationType.FALLBACK)
fakeDataStore.close()
}

companion object {
const val SESSION_ID_INIT = "12345678901234546677960"
}
}
Loading