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.entity.AuthenticatorItemEntity
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
|
||||
class FakeAuthenticatorDiskSource : AuthenticatorDiskSource {
|
||||
private val mutableItemFlow = MutableSharedFlow<List<AuthenticatorItemEntity>>()
|
||||
private val mutableItemFlow = bufferedMutableSharedFlow<List<AuthenticatorItemEntity>>()
|
||||
private val storedItems = mutableListOf<AuthenticatorItemEntity>()
|
||||
|
||||
override suspend fun saveItem(vararg authenticatorItem: AuthenticatorItemEntity) {
|
||||
@ -15,6 +16,7 @@ class FakeAuthenticatorDiskSource : AuthenticatorDiskSource {
|
||||
}
|
||||
|
||||
override fun getItems(): Flow<List<AuthenticatorItemEntity>> = mutableItemFlow
|
||||
.onSubscription { emit(storedItems) }
|
||||
|
||||
override suspend fun deleteItem(itemId: String) {
|
||||
storedItems.removeIf { it.id == itemId }
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.bitwarden.authenticator.data.authenticator.repository
|
||||
|
||||
import android.net.Uri
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.authenticator.data.authenticator.datasource.disk.util.FakeAuthenticatorDiskSource
|
||||
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.model.VerificationCodeItem
|
||||
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.TotpCodeResult
|
||||
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.model.ImportDataResult
|
||||
import com.bitwarden.authenticator.data.platform.manager.imports.model.ImportFileFormat
|
||||
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.model.AccountSyncState
|
||||
import com.bitwarden.authenticatorbridge.model.SharedAccountData
|
||||
import com.bitwarden.core.data.manager.dispatcher.FakeDispatcherManager
|
||||
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.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkConstructor
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkConstructor
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@ -39,7 +53,7 @@ class AuthenticatorRepositoryTest {
|
||||
private val mockAuthenticatorBridgeManager: AuthenticatorBridgeManager = mockk {
|
||||
every { accountSyncStateFlow } returns mutableAccountSyncStateFlow
|
||||
}
|
||||
private val mockTotpCodeManager = mockk<TotpCodeManager>()
|
||||
private val mockTotpCodeManager = mockk<TotpCodeManager>(relaxed = true)
|
||||
private val mockFileManager = mockk<FileManager>()
|
||||
private val mockImportManager = mockk<ImportManager>()
|
||||
private val mockDispatcherManager = FakeDispatcherManager()
|
||||
@ -59,22 +73,26 @@ class AuthenticatorRepositoryTest {
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(Uri::class)
|
||||
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
|
||||
fun teardown() {
|
||||
unmockkStatic(Uri::class)
|
||||
unmockkStatic(List<SharedAccountData.Account>::toAuthenticatorItems)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ciphersStateFlow initial state should be loading`() = runTest {
|
||||
authenticatorRepository.ciphersStateFlow.test {
|
||||
assertEquals(
|
||||
DataState.Loading,
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
unmockkConstructor(Uri.Builder::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -193,4 +211,222 @@ class AuthenticatorRepositoryTest {
|
||||
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