Skip to content

Commit fa19083

Browse files
authored
Merge 8063dbf into ad11092
2 parents ad11092 + 8063dbf commit fa19083

File tree

2 files changed

+244
-11
lines changed

2 files changed

+244
-11
lines changed

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SharedSessionRepository.kt

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import kotlin.coroutines.CoroutineContext
2929
import kotlinx.coroutines.CoroutineScope
3030
import kotlinx.coroutines.flow.catch
3131
import kotlinx.coroutines.launch
32-
import org.jetbrains.annotations.VisibleForTesting
3332

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

5554
/**
5655
* Either notify the subscribers with general multi-process supported session or fallback local
5756
* session
5857
*/
59-
private enum class NotificationType {
58+
internal enum class NotificationType {
6059
GENERAL,
6160
FALLBACK
6261
}
62+
internal var previousNotificationType: NotificationType = NotificationType.GENERAL
6363

6464
init {
6565
println("session repo init")
@@ -142,18 +142,19 @@ constructor(
142142
}
143143

144144
private suspend fun notifySubscribers(sessionId: String, type: NotificationType) {
145+
previousNotificationType = type
145146
FirebaseSessionsDependencies.getRegisteredSubscribers().values.forEach { subscriber ->
146147
// Notify subscribers, regardless of sampling and data collection state
147148
subscriber.onSessionChanged(SessionSubscriber.SessionDetails(sessionId))
148-
when (type) {
149-
NotificationType.GENERAL ->
150-
Log.d(TAG, "Notified ${subscriber.sessionSubscriberName} of new session $sessionId")
151-
NotificationType.FALLBACK ->
152-
Log.d(
153-
TAG,
149+
Log.d(
150+
TAG,
151+
when (type) {
152+
NotificationType.GENERAL ->
153+
"Notified ${subscriber.sessionSubscriberName} of new session $sessionId"
154+
NotificationType.FALLBACK ->
154155
"Notified ${subscriber.sessionSubscriberName} of new fallback session $sessionId"
155-
)
156-
}
156+
}
157+
)
157158
}
158159
}
159160

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.sessions
18+
19+
import androidx.test.ext.junit.runners.AndroidJUnit4
20+
import com.google.common.truth.Truth.assertThat
21+
import com.google.firebase.FirebaseApp
22+
import com.google.firebase.concurrent.TestOnlyExecutors
23+
import com.google.firebase.sessions.SessionGeneratorTest.Companion.SESSION_ID_1
24+
import com.google.firebase.sessions.SessionGeneratorTest.Companion.SESSION_ID_2
25+
import com.google.firebase.sessions.settings.SessionsSettings
26+
import com.google.firebase.sessions.testing.FakeDataStore
27+
import com.google.firebase.sessions.testing.FakeEventGDTLogger
28+
import com.google.firebase.sessions.testing.FakeFirebaseApp
29+
import com.google.firebase.sessions.testing.FakeFirebaseInstallations
30+
import com.google.firebase.sessions.testing.FakeSettingsProvider
31+
import com.google.firebase.sessions.testing.FakeTimeProvider
32+
import com.google.firebase.sessions.testing.FakeUuidGenerator
33+
import kotlin.time.Duration.Companion.hours
34+
import kotlinx.coroutines.ExperimentalCoroutinesApi
35+
import kotlinx.coroutines.asCoroutineDispatcher
36+
import kotlinx.coroutines.launch
37+
import kotlinx.coroutines.test.runCurrent
38+
import kotlinx.coroutines.test.runTest
39+
import org.junit.After
40+
import org.junit.Test
41+
import org.junit.runner.RunWith
42+
43+
@OptIn(ExperimentalCoroutinesApi::class)
44+
@RunWith(AndroidJUnit4::class)
45+
class SharedSessionRepositoryTest {
46+
private val fakeFirebaseApp = FakeFirebaseApp()
47+
private val fakeEventGDTLogger = FakeEventGDTLogger()
48+
private val firebaseInstallations = FakeFirebaseInstallations("FaKeFiD", "FakeAuthToken")
49+
private var fakeTimeProvider = FakeTimeProvider()
50+
private val sessionGenerator = SessionGenerator(fakeTimeProvider, FakeUuidGenerator())
51+
private var localSettingsProvider = FakeSettingsProvider(true, null, 100.0)
52+
private var remoteSettingsProvider = FakeSettingsProvider(true, null, 100.0)
53+
private var sessionsSettings = SessionsSettings(localSettingsProvider, remoteSettingsProvider)
54+
55+
@After
56+
fun cleanUp() {
57+
FirebaseApp.clearInstancesForTest()
58+
}
59+
60+
@Test
61+
fun initSharedSessionRepo_readFromDatastore() = runTest {
62+
val publisher =
63+
SessionFirelogPublisherImpl(
64+
fakeFirebaseApp.firebaseApp,
65+
firebaseInstallations,
66+
sessionsSettings,
67+
eventGDTLogger = fakeEventGDTLogger,
68+
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext,
69+
)
70+
val fakeDataStore =
71+
FakeDataStore<SessionData>(
72+
SessionData(
73+
SessionDetails(
74+
SESSION_ID_INIT,
75+
SESSION_ID_INIT,
76+
0,
77+
fakeTimeProvider.currentTime().ms,
78+
),
79+
fakeTimeProvider.currentTime(),
80+
)
81+
)
82+
val sharedSessionRepository =
83+
SharedSessionRepositoryImpl(
84+
sessionsSettings = sessionsSettings,
85+
sessionGenerator = sessionGenerator,
86+
sessionFirelogPublisher = publisher,
87+
timeProvider = fakeTimeProvider,
88+
sessionDataStore = fakeDataStore,
89+
backgroundDispatcher =
90+
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext
91+
)
92+
runCurrent()
93+
fakeDataStore.close()
94+
assertThat(sharedSessionRepository.localSessionData.sessionDetails.sessionId)
95+
.isEqualTo(SESSION_ID_INIT)
96+
}
97+
98+
@Test
99+
fun initSharedSessionRepo_initException() = runTest {
100+
val sessionFirelogPublisher =
101+
SessionFirelogPublisherImpl(
102+
fakeFirebaseApp.firebaseApp,
103+
firebaseInstallations,
104+
sessionsSettings,
105+
eventGDTLogger = fakeEventGDTLogger,
106+
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext,
107+
)
108+
val fakeDataStore =
109+
FakeDataStore<SessionData>(
110+
SessionData(
111+
SessionDetails(
112+
SESSION_ID_INIT,
113+
SESSION_ID_INIT,
114+
0,
115+
fakeTimeProvider.currentTime().ms,
116+
),
117+
fakeTimeProvider.currentTime(),
118+
),
119+
IllegalArgumentException("Datastore init failed")
120+
)
121+
val sharedSessionRepository =
122+
SharedSessionRepositoryImpl(
123+
sessionsSettings,
124+
sessionGenerator,
125+
sessionFirelogPublisher,
126+
fakeTimeProvider,
127+
fakeDataStore,
128+
backgroundDispatcher =
129+
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext
130+
)
131+
runCurrent()
132+
fakeDataStore.close()
133+
assertThat(sharedSessionRepository.localSessionData.sessionDetails.sessionId)
134+
.isEqualTo(SESSION_ID_1)
135+
}
136+
137+
@Test
138+
fun appForegroundSharedSessionRepo_updateSuccess() = runTest {
139+
val sessionFirelogPublisher =
140+
SessionFirelogPublisherImpl(
141+
fakeFirebaseApp.firebaseApp,
142+
firebaseInstallations,
143+
sessionsSettings,
144+
eventGDTLogger = fakeEventGDTLogger,
145+
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext,
146+
)
147+
val fakeDataStore =
148+
FakeDataStore<SessionData>(
149+
SessionData(
150+
SessionDetails(
151+
SESSION_ID_INIT,
152+
SESSION_ID_INIT,
153+
0,
154+
fakeTimeProvider.currentTime().ms,
155+
),
156+
fakeTimeProvider.currentTime(),
157+
)
158+
)
159+
val sharedSessionRepository =
160+
SharedSessionRepositoryImpl(
161+
sessionsSettings,
162+
sessionGenerator,
163+
sessionFirelogPublisher,
164+
fakeTimeProvider,
165+
fakeDataStore,
166+
backgroundDispatcher =
167+
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext
168+
)
169+
backgroundScope.launch {
170+
fakeTimeProvider.addInterval(20.hours)
171+
sharedSessionRepository.appForeground()
172+
}
173+
runCurrent()
174+
assertThat(sharedSessionRepository.localSessionData.sessionDetails.sessionId)
175+
.isEqualTo(SESSION_ID_1)
176+
assertThat(sharedSessionRepository.previousNotificationType)
177+
.isEqualTo(SharedSessionRepositoryImpl.NotificationType.GENERAL)
178+
fakeDataStore.close()
179+
}
180+
181+
@Test
182+
fun appForegroundSharedSessionRepo_updateFail() = runTest {
183+
val sessionFirelogPublisher =
184+
SessionFirelogPublisherImpl(
185+
fakeFirebaseApp.firebaseApp,
186+
firebaseInstallations,
187+
sessionsSettings,
188+
eventGDTLogger = fakeEventGDTLogger,
189+
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext,
190+
)
191+
val fakeDataStore =
192+
FakeDataStore<SessionData>(
193+
SessionData(
194+
SessionDetails(
195+
SESSION_ID_INIT,
196+
SESSION_ID_INIT,
197+
0,
198+
fakeTimeProvider.currentTime().ms,
199+
),
200+
fakeTimeProvider.currentTime(),
201+
),
202+
IllegalArgumentException("Datastore init failed")
203+
)
204+
fakeDataStore.throwOnNextUpdateData(IllegalArgumentException("Datastore update failed"))
205+
val sharedSessionRepository =
206+
SharedSessionRepositoryImpl(
207+
sessionsSettings,
208+
sessionGenerator,
209+
sessionFirelogPublisher,
210+
fakeTimeProvider,
211+
fakeDataStore,
212+
backgroundDispatcher =
213+
TestOnlyExecutors.background().asCoroutineDispatcher() + coroutineContext
214+
)
215+
216+
backgroundScope.launch {
217+
fakeTimeProvider.addInterval(20.hours)
218+
sharedSessionRepository.appForeground()
219+
}
220+
runCurrent()
221+
// session_2 here because session_1 is failed when try to init datastore
222+
assertThat(sharedSessionRepository.localSessionData.sessionDetails.sessionId)
223+
.isEqualTo(SESSION_ID_2)
224+
assertThat(sharedSessionRepository.previousNotificationType)
225+
.isEqualTo(SharedSessionRepositoryImpl.NotificationType.FALLBACK)
226+
fakeDataStore.close()
227+
}
228+
229+
companion object {
230+
const val SESSION_ID_INIT = "12345678901234546677960"
231+
}
232+
}

0 commit comments

Comments
 (0)