mirror of
https://github.com/bitwarden/android.git
synced 2026-02-03 18:17:54 -06:00
Add comprehensive tests for AuthenticatorRepositoryImpl (#6424)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
99a6dd7647
commit
0f087b7d15
@ -2,11 +2,12 @@ package com.bitwarden.authenticator.data.authenticator.datasource.disk.util
|
|||||||
|
|
||||||
import com.bitwarden.authenticator.data.authenticator.datasource.disk.AuthenticatorDiskSource
|
import com.bitwarden.authenticator.data.authenticator.datasource.disk.AuthenticatorDiskSource
|
||||||
import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemEntity
|
import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemEntity
|
||||||
|
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.onSubscription
|
||||||
|
|
||||||
class FakeAuthenticatorDiskSource : AuthenticatorDiskSource {
|
class FakeAuthenticatorDiskSource : AuthenticatorDiskSource {
|
||||||
private val mutableItemFlow = MutableSharedFlow<List<AuthenticatorItemEntity>>()
|
private val mutableItemFlow = bufferedMutableSharedFlow<List<AuthenticatorItemEntity>>()
|
||||||
private val storedItems = mutableListOf<AuthenticatorItemEntity>()
|
private val storedItems = mutableListOf<AuthenticatorItemEntity>()
|
||||||
|
|
||||||
override suspend fun saveItem(vararg authenticatorItem: AuthenticatorItemEntity) {
|
override suspend fun saveItem(vararg authenticatorItem: AuthenticatorItemEntity) {
|
||||||
@ -15,6 +16,7 @@ class FakeAuthenticatorDiskSource : AuthenticatorDiskSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getItems(): Flow<List<AuthenticatorItemEntity>> = mutableItemFlow
|
override fun getItems(): Flow<List<AuthenticatorItemEntity>> = mutableItemFlow
|
||||||
|
.onSubscription { emit(storedItems) }
|
||||||
|
|
||||||
override suspend fun deleteItem(itemId: String) {
|
override suspend fun deleteItem(itemId: String) {
|
||||||
storedItems.removeIf { it.id == itemId }
|
storedItems.removeIf { it.id == itemId }
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.bitwarden.authenticator.data.authenticator.repository
|
package com.bitwarden.authenticator.data.authenticator.repository
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.bitwarden.authenticator.data.authenticator.datasource.disk.util.FakeAuthenticatorDiskSource
|
import com.bitwarden.authenticator.data.authenticator.datasource.disk.util.FakeAuthenticatorDiskSource
|
||||||
import com.bitwarden.authenticator.data.authenticator.datasource.entity.createMockAuthenticatorItemEntity
|
import com.bitwarden.authenticator.data.authenticator.datasource.entity.createMockAuthenticatorItemEntity
|
||||||
@ -7,20 +8,33 @@ import com.bitwarden.data.manager.file.FileManager
|
|||||||
import com.bitwarden.authenticator.data.authenticator.manager.TotpCodeManager
|
import com.bitwarden.authenticator.data.authenticator.manager.TotpCodeManager
|
||||||
import com.bitwarden.authenticator.data.authenticator.manager.model.VerificationCodeItem
|
import com.bitwarden.authenticator.data.authenticator.manager.model.VerificationCodeItem
|
||||||
import com.bitwarden.authenticator.data.authenticator.repository.model.AuthenticatorItem
|
import com.bitwarden.authenticator.data.authenticator.repository.model.AuthenticatorItem
|
||||||
|
import com.bitwarden.authenticator.data.authenticator.repository.model.CreateItemResult
|
||||||
|
import com.bitwarden.authenticator.data.authenticator.repository.model.DeleteItemResult
|
||||||
|
import com.bitwarden.authenticator.data.authenticator.repository.model.ExportDataResult
|
||||||
import com.bitwarden.authenticator.data.authenticator.repository.model.SharedVerificationCodesState
|
import com.bitwarden.authenticator.data.authenticator.repository.model.SharedVerificationCodesState
|
||||||
|
import com.bitwarden.authenticator.data.authenticator.repository.model.TotpCodeResult
|
||||||
import com.bitwarden.authenticator.data.authenticator.repository.util.toAuthenticatorItems
|
import com.bitwarden.authenticator.data.authenticator.repository.util.toAuthenticatorItems
|
||||||
import com.bitwarden.authenticator.data.platform.manager.imports.ImportManager
|
import com.bitwarden.authenticator.data.platform.manager.imports.ImportManager
|
||||||
|
import com.bitwarden.authenticator.data.platform.manager.imports.model.ImportDataResult
|
||||||
|
import com.bitwarden.authenticator.data.platform.manager.imports.model.ImportFileFormat
|
||||||
import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
|
import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
|
||||||
|
import com.bitwarden.authenticator.ui.platform.feature.settings.export.model.ExportVaultFormat
|
||||||
import com.bitwarden.authenticatorbridge.manager.AuthenticatorBridgeManager
|
import com.bitwarden.authenticatorbridge.manager.AuthenticatorBridgeManager
|
||||||
import com.bitwarden.authenticatorbridge.manager.model.AccountSyncState
|
import com.bitwarden.authenticatorbridge.manager.model.AccountSyncState
|
||||||
import com.bitwarden.authenticatorbridge.model.SharedAccountData
|
import com.bitwarden.authenticatorbridge.model.SharedAccountData
|
||||||
import com.bitwarden.core.data.manager.dispatcher.FakeDispatcherManager
|
import com.bitwarden.core.data.manager.dispatcher.FakeDispatcherManager
|
||||||
import com.bitwarden.core.data.repository.model.DataState
|
import com.bitwarden.core.data.repository.model.DataState
|
||||||
|
import com.bitwarden.core.data.util.mockBuilder
|
||||||
|
import com.bitwarden.ui.platform.model.FileData
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.just
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkConstructor
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
import io.mockk.runs
|
import io.mockk.runs
|
||||||
|
import io.mockk.unmockkConstructor
|
||||||
import io.mockk.unmockkStatic
|
import io.mockk.unmockkStatic
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@ -39,7 +53,7 @@ class AuthenticatorRepositoryTest {
|
|||||||
private val mockAuthenticatorBridgeManager: AuthenticatorBridgeManager = mockk {
|
private val mockAuthenticatorBridgeManager: AuthenticatorBridgeManager = mockk {
|
||||||
every { accountSyncStateFlow } returns mutableAccountSyncStateFlow
|
every { accountSyncStateFlow } returns mutableAccountSyncStateFlow
|
||||||
}
|
}
|
||||||
private val mockTotpCodeManager = mockk<TotpCodeManager>()
|
private val mockTotpCodeManager = mockk<TotpCodeManager>(relaxed = true)
|
||||||
private val mockFileManager = mockk<FileManager>()
|
private val mockFileManager = mockk<FileManager>()
|
||||||
private val mockImportManager = mockk<ImportManager>()
|
private val mockImportManager = mockk<ImportManager>()
|
||||||
private val mockDispatcherManager = FakeDispatcherManager()
|
private val mockDispatcherManager = FakeDispatcherManager()
|
||||||
@ -59,22 +73,26 @@ class AuthenticatorRepositoryTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
|
mockkStatic(Uri::class)
|
||||||
mockkStatic(List<SharedAccountData.Account>::toAuthenticatorItems)
|
mockkStatic(List<SharedAccountData.Account>::toAuthenticatorItems)
|
||||||
|
|
||||||
|
// Configure Uri.Builder for export tests that call toOtpAuthUriString()
|
||||||
|
val mockBuiltUri = mockk<Uri>(relaxed = true)
|
||||||
|
every { mockBuiltUri.toString() } returns "otpauth://totp/mockIssuer:mockAccountName"
|
||||||
|
|
||||||
|
mockkConstructor(Uri.Builder::class)
|
||||||
|
mockBuilder<Uri.Builder> { it.scheme(any()) }
|
||||||
|
mockBuilder<Uri.Builder> { it.authority(any()) }
|
||||||
|
mockBuilder<Uri.Builder> { it.appendPath(any()) }
|
||||||
|
mockBuilder<Uri.Builder> { it.appendQueryParameter(any(), any()) }
|
||||||
|
every { anyConstructed<Uri.Builder>().build() } returns mockBuiltUri
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
fun teardown() {
|
fun teardown() {
|
||||||
|
unmockkStatic(Uri::class)
|
||||||
unmockkStatic(List<SharedAccountData.Account>::toAuthenticatorItems)
|
unmockkStatic(List<SharedAccountData.Account>::toAuthenticatorItems)
|
||||||
}
|
unmockkConstructor(Uri.Builder::class)
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `ciphersStateFlow initial state should be loading`() = runTest {
|
|
||||||
authenticatorRepository.ciphersStateFlow.test {
|
|
||||||
assertEquals(
|
|
||||||
DataState.Loading,
|
|
||||||
awaitItem(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -193,4 +211,222 @@ class AuthenticatorRepositoryTest {
|
|||||||
expectNoEvents()
|
expectNoEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getItemStateFlow with valid itemId should emit item when found`() = runTest {
|
||||||
|
val mockItem = createMockAuthenticatorItemEntity(1)
|
||||||
|
fakeAuthenticatorDiskSource.saveItem(mockItem)
|
||||||
|
|
||||||
|
authenticatorRepository.getItemStateFlow(mockItem.id).test {
|
||||||
|
assertEquals(DataState.Loaded(mockItem), awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getItemStateFlow with invalid itemId should emit null`() = runTest {
|
||||||
|
val mockItem = createMockAuthenticatorItemEntity(1)
|
||||||
|
fakeAuthenticatorDiskSource.saveItem(mockItem)
|
||||||
|
|
||||||
|
authenticatorRepository.getItemStateFlow("invalid-id").test {
|
||||||
|
assertEquals(DataState.Loaded(null), awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getItemStateFlow should emit Loaded with null for non-existent item`() = runTest {
|
||||||
|
authenticatorRepository.getItemStateFlow("any-id").test {
|
||||||
|
assertEquals(DataState.Loaded(null), awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `emitTotpCodeResult should emit to totpCodeFlow`() = runTest {
|
||||||
|
val expectedResult = TotpCodeResult.TotpCodeScan("test-code")
|
||||||
|
|
||||||
|
authenticatorRepository.totpCodeFlow.test {
|
||||||
|
authenticatorRepository.emitTotpCodeResult(expectedResult)
|
||||||
|
assertEquals(expectedResult, awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `createItem with valid item should return Success`() = runTest {
|
||||||
|
val mockItem = createMockAuthenticatorItemEntity(1)
|
||||||
|
|
||||||
|
val result = authenticatorRepository.createItem(mockItem)
|
||||||
|
|
||||||
|
assertEquals(CreateItemResult.Success, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `addItems with multiple items should return Success`() = runTest {
|
||||||
|
val mockItem1 = createMockAuthenticatorItemEntity(1)
|
||||||
|
val mockItem2 = createMockAuthenticatorItemEntity(2)
|
||||||
|
|
||||||
|
val result = authenticatorRepository.addItems(mockItem1, mockItem2)
|
||||||
|
|
||||||
|
assertEquals(CreateItemResult.Success, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `hardDeleteItem with valid id should return Success`() = runTest {
|
||||||
|
val mockItem = createMockAuthenticatorItemEntity(1)
|
||||||
|
|
||||||
|
val result = authenticatorRepository.hardDeleteItem(mockItem.id)
|
||||||
|
|
||||||
|
assertEquals(DeleteItemResult.Success, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `exportVaultData with JSON format should write to fileUri`() = runTest {
|
||||||
|
val mockItem = createMockAuthenticatorItemEntity(1)
|
||||||
|
fakeAuthenticatorDiskSource.saveItem(mockItem)
|
||||||
|
val mockUri = mockk<Uri>()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
mockFileManager.stringToUri(fileUri = mockUri, dataString = any())
|
||||||
|
} returns true
|
||||||
|
|
||||||
|
val result = authenticatorRepository.exportVaultData(
|
||||||
|
format = ExportVaultFormat.JSON,
|
||||||
|
fileUri = mockUri,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(ExportDataResult.Success, result)
|
||||||
|
coVerify { mockFileManager.stringToUri(fileUri = mockUri, dataString = any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `exportVaultData with JSON format failure should return Error`() = runTest {
|
||||||
|
val mockItem = createMockAuthenticatorItemEntity(1)
|
||||||
|
fakeAuthenticatorDiskSource.saveItem(mockItem)
|
||||||
|
val mockUri = mockk<Uri>()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
mockFileManager.stringToUri(fileUri = mockUri, dataString = any())
|
||||||
|
} returns false
|
||||||
|
|
||||||
|
val result = authenticatorRepository.exportVaultData(
|
||||||
|
format = ExportVaultFormat.JSON,
|
||||||
|
fileUri = mockUri,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(ExportDataResult.Error, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `exportVaultData with CSV format should write to fileUri`() = runTest {
|
||||||
|
val mockItem = createMockAuthenticatorItemEntity(1)
|
||||||
|
fakeAuthenticatorDiskSource.saveItem(mockItem)
|
||||||
|
val mockUri = mockk<Uri>()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
mockFileManager.stringToUri(fileUri = mockUri, dataString = any())
|
||||||
|
} returns true
|
||||||
|
|
||||||
|
val result = authenticatorRepository.exportVaultData(
|
||||||
|
format = ExportVaultFormat.CSV,
|
||||||
|
fileUri = mockUri,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(ExportDataResult.Success, result)
|
||||||
|
coVerify { mockFileManager.stringToUri(fileUri = mockUri, dataString = any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `exportVaultData with CSV format failure should return Error`() = runTest {
|
||||||
|
val mockItem = createMockAuthenticatorItemEntity(1)
|
||||||
|
fakeAuthenticatorDiskSource.saveItem(mockItem)
|
||||||
|
val mockUri = mockk<Uri>()
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
mockFileManager.stringToUri(fileUri = mockUri, dataString = any())
|
||||||
|
} returns false
|
||||||
|
|
||||||
|
val result = authenticatorRepository.exportVaultData(
|
||||||
|
format = ExportVaultFormat.CSV,
|
||||||
|
fileUri = mockUri,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(ExportDataResult.Error, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `importVaultData with valid data should return Success`() = runTest {
|
||||||
|
val mockUri = mockk<Uri>()
|
||||||
|
val mockFileData = FileData(
|
||||||
|
fileName = "test.json",
|
||||||
|
uri = mockUri,
|
||||||
|
sizeBytes = 100L,
|
||||||
|
)
|
||||||
|
val testByteArray = byteArrayOf(1, 2, 3)
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
mockFileManager.uriToByteArray(mockUri)
|
||||||
|
} returns Result.success(testByteArray)
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
mockImportManager.import(
|
||||||
|
importFileFormat = ImportFileFormat.BITWARDEN_JSON,
|
||||||
|
byteArray = testByteArray,
|
||||||
|
)
|
||||||
|
} returns ImportDataResult.Success
|
||||||
|
|
||||||
|
val result = authenticatorRepository.importVaultData(
|
||||||
|
format = ImportFileFormat.BITWARDEN_JSON,
|
||||||
|
fileData = mockFileData,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(ImportDataResult.Success, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `importVaultData with FileManager failure should return Error`() = runTest {
|
||||||
|
val mockUri = mockk<Uri>()
|
||||||
|
val mockFileData = FileData(
|
||||||
|
fileName = "test.json",
|
||||||
|
uri = mockUri,
|
||||||
|
sizeBytes = 100L,
|
||||||
|
)
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
mockFileManager.uriToByteArray(mockUri)
|
||||||
|
} returns Result.failure(RuntimeException("File read error"))
|
||||||
|
|
||||||
|
val result = authenticatorRepository.importVaultData(
|
||||||
|
format = ImportFileFormat.BITWARDEN_JSON,
|
||||||
|
fileData = mockFileData,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(ImportDataResult.Error(), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `importVaultData with ImportManager failure should return Error`() = runTest {
|
||||||
|
val mockUri = mockk<Uri>()
|
||||||
|
val mockFileData = FileData(
|
||||||
|
fileName = "test.json",
|
||||||
|
uri = mockUri,
|
||||||
|
sizeBytes = 100L,
|
||||||
|
)
|
||||||
|
val testByteArray = byteArrayOf(1, 2, 3)
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
mockFileManager.uriToByteArray(mockUri)
|
||||||
|
} returns Result.success(testByteArray)
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
mockImportManager.import(
|
||||||
|
importFileFormat = ImportFileFormat.BITWARDEN_JSON,
|
||||||
|
byteArray = testByteArray,
|
||||||
|
)
|
||||||
|
} returns ImportDataResult.Error()
|
||||||
|
|
||||||
|
val result = authenticatorRepository.importVaultData(
|
||||||
|
format = ImportFileFormat.BITWARDEN_JSON,
|
||||||
|
fileData = mockFileData,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(ImportDataResult.Error(), result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user