mirror of
https://github.com/bitwarden/android.git
synced 2026-05-27 23:20:03 -05:00
BIT-1361 Setup GCM and Bitwarden push registration (#547)
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import androidx.core.content.edit
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
|
||||
import com.x8bit.bitwarden.data.platform.util.getBinaryLongFromZoneDateTime
|
||||
import com.x8bit.bitwarden.data.platform.util.getZoneDateTimeFromBinaryLong
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
class PushDiskSourceTest {
|
||||
private val fakeSharedPreferences = FakeSharedPreferences()
|
||||
|
||||
private val pushDiskSource = PushDiskSourceImpl(
|
||||
sharedPreferences = fakeSharedPreferences,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `registeredPushToken should pull from and update SharedPreferences`() {
|
||||
val registeredPushTokenKey = "bwPreferencesStorage:pushRegisteredToken"
|
||||
|
||||
// Shared preferences and the repository start with the same value.
|
||||
assertNull(pushDiskSource.registeredPushToken)
|
||||
assertNull(fakeSharedPreferences.getString(registeredPushTokenKey, null))
|
||||
|
||||
// Updating the repository updates shared preferences
|
||||
pushDiskSource.registeredPushToken = "abcd"
|
||||
assertEquals(
|
||||
"abcd",
|
||||
fakeSharedPreferences.getString(registeredPushTokenKey, null),
|
||||
)
|
||||
|
||||
// Update SharedPreferences updates the repository
|
||||
fakeSharedPreferences.edit().putString(registeredPushTokenKey, null).apply()
|
||||
assertNull(pushDiskSource.registeredPushToken)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getCurrentPushToken should pull from SharedPreferences`() {
|
||||
val currentPushTokenBaseKey = "bwPreferencesStorage:pushCurrentToken"
|
||||
val mockUserId = "mockUserId"
|
||||
val mockCurrentPushToken = "abcd"
|
||||
fakeSharedPreferences
|
||||
.edit()
|
||||
.putString(
|
||||
"${currentPushTokenBaseKey}_$mockUserId",
|
||||
mockCurrentPushToken,
|
||||
)
|
||||
.apply()
|
||||
val actual = pushDiskSource.getCurrentPushToken(userId = mockUserId)
|
||||
assertEquals(
|
||||
mockCurrentPushToken,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storeCurrentPushToken should update SharedPreferences`() {
|
||||
val currentPushTokenBaseKey = "bwPreferencesStorage:pushCurrentToken"
|
||||
val mockUserId = "mockUserId"
|
||||
val mockCurrentPushToken = "abcd"
|
||||
pushDiskSource.storeCurrentPushToken(
|
||||
userId = mockUserId,
|
||||
pushToken = mockCurrentPushToken,
|
||||
)
|
||||
val actual = fakeSharedPreferences
|
||||
.getString(
|
||||
"${currentPushTokenBaseKey}_$mockUserId",
|
||||
null,
|
||||
)
|
||||
assertEquals(
|
||||
mockCurrentPushToken,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getLastPushTokenRegistrationDate should pull from SharedPreferences`() {
|
||||
val lastPushTokenBaseKey = "bwPreferencesStorage:pushLastRegistrationDate"
|
||||
val mockUserId = "mockUserId"
|
||||
val mockLastPushTokenRegistration = ZonedDateTime.parse("2024-01-06T22:27:45.904314Z")
|
||||
fakeSharedPreferences
|
||||
.edit()
|
||||
.putLong(
|
||||
"${lastPushTokenBaseKey}_$mockUserId",
|
||||
getBinaryLongFromZoneDateTime(mockLastPushTokenRegistration),
|
||||
)
|
||||
.apply()
|
||||
val actual = pushDiskSource.getLastPushTokenRegistrationDate(userId = mockUserId)!!
|
||||
assertEquals(
|
||||
mockLastPushTokenRegistration,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storeLastPushTokenRegistrationDate for non-null values should update SharedPreferences`() {
|
||||
val lastPushTokenBaseKey = "bwPreferencesStorage:pushLastRegistrationDate"
|
||||
val mockUserId = "mockUserId"
|
||||
val mockLastPushTokenRegistration = ZonedDateTime.parse("2024-01-06T22:27:45.904314Z")
|
||||
pushDiskSource.storeLastPushTokenRegistrationDate(
|
||||
userId = mockUserId,
|
||||
registrationDate = mockLastPushTokenRegistration,
|
||||
)
|
||||
val actual = fakeSharedPreferences
|
||||
.getLong(
|
||||
"${lastPushTokenBaseKey}_$mockUserId",
|
||||
0,
|
||||
)
|
||||
assertEquals(
|
||||
mockLastPushTokenRegistration,
|
||||
getZoneDateTimeFromBinaryLong(actual),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storeLastPushTokenRegistrationDate for null values should clear SharedPreferences`() {
|
||||
val lastPushTokenBaseKey = "bwPreferencesStorage:pushLastRegistrationDate"
|
||||
val mockUserId = "mockUserId"
|
||||
val mockLastPushTokenRegistration = ZonedDateTime.now()
|
||||
val lastPushTokenKey = "${lastPushTokenBaseKey}_$mockUserId"
|
||||
fakeSharedPreferences.edit {
|
||||
putLong(lastPushTokenKey, mockLastPushTokenRegistration.toEpochSecond())
|
||||
}
|
||||
assertTrue(fakeSharedPreferences.contains(lastPushTokenKey))
|
||||
pushDiskSource.storeLastPushTokenRegistrationDate(
|
||||
userId = mockUserId,
|
||||
registrationDate = null,
|
||||
)
|
||||
assertFalse(fakeSharedPreferences.contains(lastPushTokenKey))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.api.PushApi
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.PushTokenRequest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import retrofit2.create
|
||||
import java.util.UUID
|
||||
|
||||
class PushServiceTest : BaseServiceTest() {
|
||||
private val mockAppId = UUID.randomUUID().toString()
|
||||
private val pushApi: PushApi = retrofit.create()
|
||||
|
||||
private val pushService: PushService = PushServiceImpl(
|
||||
pushApi = pushApi,
|
||||
appId = mockAppId,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `putDeviceToken should return the correct response`() = runTest {
|
||||
val pushToken = UUID.randomUUID().toString()
|
||||
server.enqueue(MockResponse())
|
||||
val result = pushService.putDeviceToken(
|
||||
body = PushTokenRequest(
|
||||
pushToken = pushToken,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
Unit,
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSourceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.PushTokenRequest
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.PushService
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.TimeZone
|
||||
|
||||
class PushManagerTest {
|
||||
private val clock = Clock.fixed(
|
||||
Instant.parse("2023-10-27T12:00:00Z"),
|
||||
TimeZone.getTimeZone("UTC").toZoneId(),
|
||||
)
|
||||
|
||||
private val dispatcherManager: DispatcherManager = FakeDispatcherManager()
|
||||
|
||||
private val authDiskSource: AuthDiskSource = FakeAuthDiskSource()
|
||||
|
||||
private val pushDiskSource: PushDiskSource = PushDiskSourceImpl(FakeSharedPreferences())
|
||||
|
||||
private val pushService: PushService = mockk()
|
||||
|
||||
private lateinit var pushManager: PushManager
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
pushManager = PushManagerImpl(
|
||||
authDiskSource = authDiskSource,
|
||||
pushDiskSource = pushDiskSource,
|
||||
pushService = pushService,
|
||||
dispatcherManager = dispatcherManager,
|
||||
clock = clock,
|
||||
)
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class NullUserState {
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
authDiskSource.userState = null
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerPushTokenIfNecessary should update registeredPushToken`() {
|
||||
assertEquals(null, pushDiskSource.registeredPushToken)
|
||||
|
||||
val token = "token"
|
||||
pushManager.registerPushTokenIfNecessary(token)
|
||||
|
||||
assertEquals(token, pushDiskSource.registeredPushToken)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerStoredPushTokenIfNecessary should do nothing`() {
|
||||
pushManager.registerStoredPushTokenIfNecessary()
|
||||
|
||||
assertNull(pushDiskSource.registeredPushToken)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class NonNullUserState {
|
||||
private val existingToken = "existingToken"
|
||||
private val userId = "userId"
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
pushDiskSource.storeCurrentPushToken(userId, existingToken)
|
||||
authDiskSource.userState = UserStateJson(userId, mapOf(userId to mockk()))
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `registerStoredPushTokenIfNecessary should do nothing if registered less than a day before`() {
|
||||
val lastRegistration = ZonedDateTime.ofInstant(
|
||||
clock.instant().minus(23, ChronoUnit.HOURS),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
pushDiskSource.registeredPushToken = existingToken
|
||||
pushDiskSource.storeLastPushTokenRegistrationDate(
|
||||
userId,
|
||||
lastRegistration,
|
||||
)
|
||||
pushManager.registerStoredPushTokenIfNecessary()
|
||||
|
||||
// Assert the last registration value has not changed
|
||||
assertEquals(
|
||||
lastRegistration.toEpochSecond(),
|
||||
pushDiskSource.getLastPushTokenRegistrationDate(userId)!!.toEpochSecond(),
|
||||
)
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class MatchingToken {
|
||||
private val newToken = "existingToken"
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `registerPushTokenIfNecessary should update registeredPushToken and lastPushTokenRegistrationDate`() {
|
||||
pushManager.registerPushTokenIfNecessary(newToken)
|
||||
|
||||
coVerify(exactly = 0) { pushService.putDeviceToken(any()) }
|
||||
assertEquals(newToken, pushDiskSource.registeredPushToken)
|
||||
assertEquals(
|
||||
clock.instant().epochSecond,
|
||||
pushDiskSource.getLastPushTokenRegistrationDate(userId)?.toEpochSecond(),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `registerStoredPushTokenIfNecessary should update registeredPushToken and lastPushTokenRegistrationDate`() {
|
||||
pushDiskSource.registeredPushToken = newToken
|
||||
pushManager.registerStoredPushTokenIfNecessary()
|
||||
|
||||
coVerify(exactly = 0) { pushService.putDeviceToken(any()) }
|
||||
assertEquals(newToken, pushDiskSource.registeredPushToken)
|
||||
assertEquals(
|
||||
clock.instant().epochSecond,
|
||||
pushDiskSource.getLastPushTokenRegistrationDate(userId)?.toEpochSecond(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class DifferentToken {
|
||||
private val newToken = "newToken"
|
||||
|
||||
@Nested
|
||||
inner class SuccessfulRequest {
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
coEvery {
|
||||
pushService.putDeviceToken(any())
|
||||
} returns Unit.asSuccess()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `registerPushTokenIfNecessary should update registeredPushToken, lastPushTokenRegistrationDate and currentPushToken`() {
|
||||
pushManager.registerPushTokenIfNecessary(newToken)
|
||||
|
||||
coVerify(exactly = 1) { pushService.putDeviceToken(PushTokenRequest(newToken)) }
|
||||
assertEquals(
|
||||
clock.instant().epochSecond,
|
||||
pushDiskSource.getLastPushTokenRegistrationDate(userId)?.toEpochSecond(),
|
||||
)
|
||||
assertEquals(newToken, pushDiskSource.registeredPushToken)
|
||||
assertEquals(newToken, pushDiskSource.getCurrentPushToken(userId))
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `registerStoredPushTokenIfNecessary should update registeredPushToken, lastPushTokenRegistrationDate and currentPushToken`() {
|
||||
pushDiskSource.registeredPushToken = newToken
|
||||
pushManager.registerStoredPushTokenIfNecessary()
|
||||
|
||||
coVerify(exactly = 1) { pushService.putDeviceToken(PushTokenRequest(newToken)) }
|
||||
assertEquals(
|
||||
clock.instant().epochSecond,
|
||||
pushDiskSource.getLastPushTokenRegistrationDate(userId)?.toEpochSecond(),
|
||||
)
|
||||
assertEquals(newToken, pushDiskSource.registeredPushToken)
|
||||
assertEquals(newToken, pushDiskSource.getCurrentPushToken(userId))
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class FailedRequest {
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
coEvery {
|
||||
pushService.putDeviceToken(any())
|
||||
} returns Throwable().asFailure()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerPushTokenIfNecessary should update registeredPushToken`() {
|
||||
pushManager.registerPushTokenIfNecessary(newToken)
|
||||
|
||||
coVerify(exactly = 1) { pushService.putDeviceToken(PushTokenRequest(newToken)) }
|
||||
assertNull(pushDiskSource.getLastPushTokenRegistrationDate(userId))
|
||||
assertEquals(newToken, pushDiskSource.registeredPushToken)
|
||||
assertEquals(existingToken, pushDiskSource.getCurrentPushToken(userId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerStoredPushTokenIfNecessary should update registeredPushToken`() {
|
||||
pushDiskSource.registeredPushToken = newToken
|
||||
pushManager.registerStoredPushTokenIfNecessary()
|
||||
|
||||
coVerify(exactly = 1) { pushService.putDeviceToken(PushTokenRequest(newToken)) }
|
||||
assertNull(pushDiskSource.getLastPushTokenRegistrationDate(userId))
|
||||
assertEquals(newToken, pushDiskSource.registeredPushToken)
|
||||
assertEquals(existingToken, pushDiskSource.getCurrentPushToken(userId))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.x8bit.bitwarden.ui.platform.util
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.util.getBinaryLongFromZoneDateTime
|
||||
import com.x8bit.bitwarden.data.platform.util.getZoneDateTimeFromBinaryLong
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
class ZonedDateTimeUtilsTest {
|
||||
@Test
|
||||
fun `getZoneDateTimeFromBinaryLong should correctly convert a Long to a ZonedDateTime`() {
|
||||
val binaryLong = 5250087787086431044L
|
||||
val expectedDateTime = ZonedDateTime.parse("2024-01-06T22:27:45.904314Z")
|
||||
assertEquals(expectedDateTime, getZoneDateTimeFromBinaryLong(binaryLong))
|
||||
|
||||
val a = getZoneDateTimeFromBinaryLong(binaryLong)
|
||||
val b = getBinaryLongFromZoneDateTime(a)
|
||||
assertEquals(binaryLong, b)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getBinaryLongFromZoneDateTime should correctly convert a ZonedDateTime to a Long`() {
|
||||
val dateTime = ZonedDateTime.parse("2024-01-06T22:27:45.904314Z")
|
||||
val expectedBinaryLong = 5250087787086431044L
|
||||
assertEquals(expectedBinaryLong, getBinaryLongFromZoneDateTime(dateTime))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user