Skip to content
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

SpeziStorage #108

Merged
merged 24 commits into from
Oct 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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 @@ -10,7 +10,6 @@ import edu.stanford.spezi.modules.storage.di.Storage
import edu.stanford.spezi.modules.storage.key.KeyValueStorage
import edu.stanford.spezi.modules.storage.key.getSerializableList
import edu.stanford.spezi.modules.storage.key.putSerializable
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand All @@ -23,14 +22,11 @@ internal class BLEPairedDevicesStorage @Inject constructor(
private val bluetoothAdapter: BluetoothAdapter,
private val bleDevicePairingNotifier: BLEDevicePairingNotifier,
@Storage.Encrypted
private val encryptedKeyValueStorage: KeyValueStorage,
private val storage: KeyValueStorage,
private val timeProvider: TimeProvider,
@Dispatching.IO private val ioScope: CoroutineScope,
) {
private val logger by speziLogger()
private val coroutineExceptionHandler = CoroutineExceptionHandler { _, error ->
logger.e(error) { "Error executing paired devices storage operations" }
}

private val _pairedDevices = MutableStateFlow(emptyList<BLEDevice>())
val pairedDevices = _pairedDevices.asStateFlow()
Expand All @@ -40,8 +36,8 @@ internal class BLEPairedDevicesStorage @Inject constructor(
observeUnpairingEvents()
}

fun updateDeviceConnection(device: BluetoothDevice, connected: Boolean) = execute {
if (isPaired(device).not()) return@execute
fun updateDeviceConnection(device: BluetoothDevice, connected: Boolean) {
if (isPaired(device).not()) return
val currentDevices = getCurrentStoredDevices()
currentDevices.removeAll { it.address == device.address }
val newDevice = BLEDevice(
Expand All @@ -54,24 +50,24 @@ internal class BLEPairedDevicesStorage @Inject constructor(
update(devices = currentDevices + newDevice)
}

private fun refreshState() = execute {
val systemBoundDevices = bluetoothAdapter.bondedDevices ?: return@execute
private fun refreshState() {
val systemBoundDevices = bluetoothAdapter.bondedDevices ?: return
val newDevices = getCurrentStoredDevices().filter { storedDevice ->
systemBoundDevices.any { it.address == storedDevice.address }
}
logger.i { "refreshing with $newDevices" }
update(devices = newDevices)
}

fun onStopped() = execute {
fun onStopped() {
val devices = getCurrentStoredDevices().map {
it.copy(connected = false, lastSeenTimeStamp = timeProvider.currentTimeMillis())
}
update(devices = devices)
}

private fun update(devices: List<BLEDevice>) = execute {
encryptedKeyValueStorage.putSerializable(key = KEY, devices)
private fun update(devices: List<BLEDevice>) {
storage.putSerializable(key = KEY, devices)
_pairedDevices.update { devices }
logger.i { "Updating local storage with $devices" }
}
Expand Down Expand Up @@ -112,12 +108,8 @@ internal class BLEPairedDevicesStorage @Inject constructor(
}
}

private suspend fun getCurrentStoredDevices() =
encryptedKeyValueStorage.getSerializableList<BLEDevice>(key = KEY).toMutableList()

private fun execute(block: suspend () -> Unit) {
ioScope.launch(coroutineExceptionHandler) { block() }
}
private fun getCurrentStoredDevices() =
storage.getSerializableList<BLEDevice>(key = KEY).toMutableList()

private companion object {
const val KEY = "paired_ble_devices"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class BLEPairedDevicesStorageTest {
private val pairedDevicesStorage by lazy {
BLEPairedDevicesStorage(
bluetoothAdapter = adapter,
encryptedKeyValueStorage = storage,
storage = storage,
ioScope = SpeziTestScope(),
bleDevicePairingNotifier = bleDevicePairingNotifier,
timeProvider = timeProvider,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package edu.stanford.spezi.modules.storage.credential

import com.google.common.truth.Truth.assertThat
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import edu.stanford.spezi.core.utils.UUID
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject

@HiltAndroidTest
class CredentialStorageTests {
@get:Rule
val hiltRule = HiltAndroidRule(this)

@Inject
lateinit var credentialStorage: CredentialStorage

private val serverCredential = Credential(
username = "@Schmiedmayer",
password = "top-secret",
server = "apple.com"
)

private val nonServerCredential = Credential(
username = "@Spezi",
password = "123456",
)

@Before
fun setup() {
hiltRule.inject()
}

@After
fun tearDown() {
credentialStorage.deleteAll(CredentialTypes.All)
}

@Test
fun `it should store server credentials correctly`() {
// given
credentialStorage.store(serverCredential)

// when
val serverCredentials = credentialStorage.retrieveAll(server = serverCredential.server!!)
val userServerCredential = credentialStorage.retrieve(
username = serverCredential.username,
server = serverCredential.server,
)

// then
assertThat(serverCredentials).containsExactly(serverCredential)
assertThat(userServerCredential).isEqualTo(serverCredential)
}

@Test
fun `it should store non server credentials correctly`() {
// given
credentialStorage.store(nonServerCredential)

// when
val userServerCredential = credentialStorage.retrieve(
username = nonServerCredential.username
)
val userCredentials = credentialStorage.retrieve(nonServerCredential.username)

// then
assertThat(userCredentials).isEqualTo(nonServerCredential)
assertThat(userServerCredential).isEqualTo(nonServerCredential)
}

@Test
fun `it should retrieve all server credentials correctly`() {
// given
val server = "edu.stanford.spezi"
val credentials = List(10) { serverCredential.copy(username = "User$it", server = server) }
credentials.forEach { credentialStorage.store(it) }

// when
val storedCredentials = credentialStorage.retrieveAll(server)

// then
assertThat(storedCredentials).containsExactlyElementsIn(credentials)
assertThat(storedCredentials.all { it.server == server }).isTrue()
}

@Test
fun `it should update credentials correctly`() {
// given
val updatedUserName = serverCredential.username + "- @Spezi"
val newPassword = serverCredential.password.plus(UUID().toString())
credentialStorage.store(serverCredential)
val updatedCredential = serverCredential.copy(
username = updatedUserName,
password = newPassword,
)
credentialStorage.update(
username = serverCredential.username,
server = serverCredential.server,
newCredential = updatedCredential,
)

// when
val oldCredential = credentialStorage.retrieve(
username = serverCredential.username,
server = serverCredential.server,
)
val newCredential = credentialStorage.retrieve(
username = updatedCredential.username,
server = updatedCredential.server,
)

// then
assertThat(oldCredential).isNull()
assertThat(newCredential).isEqualTo(updatedCredential)
}

@Test
fun `it should delete credentials correctly`() {
// given
credentialStorage.store(serverCredential)
val beforeDeleteCredential = credentialStorage.retrieve(
username = serverCredential.username,
server = "apple.com",
)

// when
credentialStorage.delete(
username = serverCredential.username,
server = serverCredential.server,
)
val afterDeleteCredential = credentialStorage.retrieve(
username = serverCredential.username,
server = serverCredential.server,
)

// then
assertThat(beforeDeleteCredential).isEqualTo(serverCredential)
assertThat(afterDeleteCredential).isNull()
}

@Test
fun `it should handle deleting all server credentials correctly`() {
// given
listOf(serverCredential, nonServerCredential).forEach { credentialStorage.store(it) }

// when
credentialStorage.deleteAll(CredentialTypes.Server)
val storedServerCredential = credentialStorage.retrieve(
username = serverCredential.username,
)
val storedNonServerCredential = credentialStorage.retrieve(
username = nonServerCredential.username,
)

// then
assertThat(storedServerCredential).isNull()
assertThat(storedNonServerCredential).isEqualTo(nonServerCredential)
}

@Test
fun `it should handle deleting non server credentials correctly`() {
// given
listOf(serverCredential, nonServerCredential).forEach { credentialStorage.store(it) }

// when
credentialStorage.deleteAll(CredentialTypes.NonServer)
val storedServerCredential = credentialStorage.retrieve(
username = serverCredential.username,
server = serverCredential.server,
)
val storedNonServerCredential = credentialStorage.retrieve(
username = nonServerCredential.username,
)

// then
assertThat(storedServerCredential).isEqualTo(serverCredential)
assertThat(storedNonServerCredential).isNull()
}

@Test
fun `it should handle deleting all credentials correctly`() {
// given
listOf(serverCredential, nonServerCredential).forEach { credentialStorage.store(it) }

// when
credentialStorage.deleteAll(CredentialTypes.All)
val storedServerCredential = credentialStorage.retrieve(
username = serverCredential.username,
)
val storedNonServerCredential = credentialStorage.retrieve(
username = nonServerCredential.username,
)

// then
assertThat(storedServerCredential).isNull()
assertThat(storedNonServerCredential).isNull()
}
}

This file was deleted.

Loading
Loading