Первые тесты

This commit is contained in:
2026-05-19 00:48:07 +03:00
parent fd6f2e5879
commit eecaf44b72
58 changed files with 1634 additions and 501 deletions

View File

@@ -20,4 +20,5 @@ dependencies {
implementation(libs.kotlinx.serialization.json)
implementation(libs.java.otp)
testImplementation(libs.junit)
testImplementation(libs.kotlinx.coroutines.test)
}

View File

@@ -1,42 +1,19 @@
package com.github.nullptroma.wallenc.usecases
import com.github.nullptroma.wallenc.domain.datatypes.DataPage
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import com.github.nullptroma.wallenc.domain.datatypes.TextSecretEntryRecord
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalEntry
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncLock
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
import com.github.nullptroma.wallenc.domain.interfaces.IFile
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo
import com.github.nullptroma.wallenc.domain.interfaces.IStorageMetaInfo
import com.github.nullptroma.wallenc.domain.tasks.TaskProgress
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.emptyFlow
import com.github.nullptroma.wallenc.usecases.fakes.FakeStorage
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.time.Instant
import java.util.UUID
class StorageDomainUseCasesTest {
@Test
fun twoFaCrudWorksAndPersists() = runBlocking {
fun twoFaCrudWorksAndPersists() = runTest {
val storage = FakeStorage()
val useCase = ManageTwoFaTokensUseCase()
@@ -63,7 +40,7 @@ class StorageDomainUseCasesTest {
}
@Test
fun twoFaInvalidJsonFallsBackToEmptyList() = runBlocking {
fun twoFaInvalidJsonFallsBackToEmptyList() = runTest {
val storage = FakeStorage().apply {
setDomainFile(StorageDomainDataFiles.TWO_FA_TOKENS_FILE, "not-json")
}
@@ -72,7 +49,7 @@ class StorageDomainUseCasesTest {
}
@Test
fun textSecretsCrudWorksWithOptionalLabels() = runBlocking {
fun textSecretsCrudWorksWithOptionalLabels() = runTest {
val storage = FakeStorage()
val useCase = ManageTextSecretsUseCase()
@@ -104,7 +81,7 @@ class StorageDomainUseCasesTest {
}
@Test
fun textSecretsInvalidJsonFallsBackToEmptyList() = runBlocking {
fun textSecretsInvalidJsonFallsBackToEmptyList() = runTest {
val storage = FakeStorage().apply {
setDomainFile(StorageDomainDataFiles.TEXT_SECRETS_FILE, "{broken")
}
@@ -112,131 +89,3 @@ class StorageDomainUseCasesTest {
assertTrue(useCase.observe(storage).first().isEmpty())
}
}
private class FakeStorage : IStorage {
private val accessorImpl = FakeStorageAccessor()
override val uuid: UUID = UUID.randomUUID()
override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true)
override val size: StateFlow<Long?> = MutableStateFlow(0L)
override val numberOfFiles: StateFlow<Int?> = MutableStateFlow(0)
override val isEmpty: Flow<Boolean?> = flowOf(true)
override val metaInfo: StateFlow<IStorageMetaInfo> = MutableStateFlow(FakeMetaInfo())
override val isVirtualStorage: Boolean = false
override val accessor: IStorageAccessor = accessorImpl
override suspend fun rename(newName: String) = Unit
override suspend fun setEncInfo(encInfo: StorageEncryptionInfo?) = Unit
override suspend fun clearAllContent(onProgress: suspend (TaskProgress) -> Unit) = Unit
fun setDomainFile(path: String, value: String) {
accessorImpl.dataFiles[path] = value.encodeToByteArray()
}
}
private class FakeMetaInfo : IStorageMetaInfo {
override val encInfo: StorageEncryptionInfo? = null
override val name: String = "Fake"
override val lastModified: Instant = Instant.now()
}
private class FakeStorageAccessor : IStorageAccessor {
val dataFiles: MutableMap<String, ByteArray> = mutableMapOf()
private val systemFiles: MutableMap<String, ByteArray> = mutableMapOf()
private val _filesUpdates = MutableSharedFlow<DataPage<IFile>>(extraBufferCapacity = 16)
override val size: StateFlow<Long?> = MutableStateFlow(0L)
override val numberOfFiles: StateFlow<Int?> = MutableStateFlow(0)
override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true)
override val filesUpdates: SharedFlow<DataPage<IFile>> = _filesUpdates
override val dirsUpdates: SharedFlow<DataPage<IDirectory>> = MutableSharedFlow()
override suspend fun getAllFiles(): List<IFile> = emptyList()
override suspend fun getFiles(path: String): List<IFile> = emptyList()
override fun getFilesFlow(path: String): Flow<DataPage<IFile>> = emptyFlow()
override suspend fun getAllDirs(): List<IDirectory> = emptyList()
override suspend fun getDirs(path: String): List<IDirectory> = emptyList()
override fun getDirsFlow(path: String): Flow<DataPage<IDirectory>> = emptyFlow()
override suspend fun getFileInfo(path: String): IFile {
error("Not implemented in tests")
}
override suspend fun getDirInfo(path: String): IDirectory {
error("Not implemented in tests")
}
override suspend fun setHidden(path: String, hidden: Boolean) = Unit
override suspend fun touchFile(path: String) = Unit
override suspend fun touchDir(path: String) = Unit
override suspend fun delete(path: String) = Unit
override suspend fun openWrite(path: String): OutputStream {
return object : ByteArrayOutputStream() {
override fun close() {
dataFiles[path] = toByteArray()
_filesUpdates.tryEmit(
DataPage(
listOf(FakeFile(path)),
pageLength = 1,
pageIndex = 0,
),
)
}
}
}
override suspend fun openRead(path: String): InputStream {
val bytes = dataFiles[path] ?: throw IllegalStateException("File not found: $path")
return ByteArrayInputStream(bytes)
}
override suspend fun moveToTrash(path: String) = Unit
override suspend fun openReadSystemFile(name: String): InputStream {
val bytes = systemFiles[name] ?: ByteArray(0)
return ByteArrayInputStream(bytes)
}
override suspend fun openWriteSystemFile(name: String): OutputStream {
return object : ByteArrayOutputStream() {
override fun close() {
systemFiles[name] = toByteArray()
}
}
}
override suspend fun readSyncJournal(): List<StorageSyncJournalEntry> = emptyList()
override suspend fun appendSyncJournal(entries: List<StorageSyncJournalEntry>) = Unit
override suspend fun rewriteSyncJournal(entries: List<StorageSyncJournalEntry>) = Unit
override suspend fun readSyncLock(): StorageSyncLock? = null
override suspend fun tryAcquireSyncLock(holderId: String, leaseUntil: Instant): Boolean = true
override suspend fun releaseSyncLock(holderId: String) = Unit
override suspend fun forceClearSyncLock() = Unit
}
private class FakeFile(path: String) : IFile {
override val metaInfo: IMetaInfo = object : IMetaInfo {
override val size: Long = 0L
override val isDeleted: Boolean = false
override val isHidden: Boolean = false
override val lastModified: Instant = Instant.now()
override val path: String = path
}
}

View File

@@ -0,0 +1,152 @@
package com.github.nullptroma.wallenc.usecases
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalEntry
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncOperation
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncRevision
import com.github.nullptroma.wallenc.domain.interfaces.StorageSyncGroup
import com.github.nullptroma.wallenc.domain.interfaces.StorageSyncGroupEncryptionKind
import com.github.nullptroma.wallenc.domain.tasks.TaskProgressLabel
import com.github.nullptroma.wallenc.usecases.fakes.FakeStorage
import com.github.nullptroma.wallenc.usecases.fakes.FakeStorageSyncGroupStore
import com.github.nullptroma.wallenc.usecases.fakes.FakeVaultsManager
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import java.time.Instant
class StorageSyncEngineTest {
@Test
fun syncAllGroupsReportsNoGroupsWhenEmpty() = runBlocking {
val labels = mutableListOf<TaskProgressLabel?>()
val engine = createEngine(storages = emptyList(), groups = emptyList())
engine.syncAllGroups { _, label -> labels.add(label) }
assertTrue(labels.any { it is TaskProgressLabel.SyncNoGroups })
}
@Test
fun syncGroupCopiesFileFromSourceToTarget() = runBlocking {
val source = FakeStorage()
val target = FakeStorage()
val path = "shared/doc.txt"
val payload = "sync-payload".encodeToByteArray()
source.putFile(path, payload)
val entry = StorageSyncJournalEntry(
path = path,
operation = StorageSyncOperation.UPSERT,
revision = StorageSyncRevision(
sequence = 1L,
actorId = "actor-a",
createdAt = Instant.parse("2024-01-01T00:00:00Z"),
),
size = payload.size.toLong(),
originStorageUuid = source.uuid,
)
source.addSyncJournalEntry(entry)
val groupId = "group-1"
val group = StorageSyncGroup(
id = groupId,
storageUuids = setOf(source.uuid, target.uuid),
encryptionKind = StorageSyncGroupEncryptionKind.NONE,
)
val labels = mutableListOf<TaskProgressLabel?>()
val engine = createEngine(
storages = listOf(source, target),
groups = listOf(group),
)
engine.syncGroup(groupId) { _, label -> labels.add(label) }
assertArrayEquals(payload, target.fileBytes(path))
assertTrue(labels.any { it is TaskProgressLabel.SyncGroupCompleted })
assertTrue(target.syncJournalEntries().any { it.path == path })
}
@Test
fun syncGroupSkippedWhenFewerThanTwoStorages() = runBlocking {
val only = FakeStorage()
val groupId = "solo"
val group = StorageSyncGroup(
id = groupId,
storageUuids = setOf(only.uuid),
encryptionKind = StorageSyncGroupEncryptionKind.NONE,
)
val labels = mutableListOf<TaskProgressLabel?>()
val engine = createEngine(storages = listOf(only), groups = listOf(group))
engine.syncGroup(groupId) { _, label -> labels.add(label) }
assertTrue(labels.any { it is TaskProgressLabel.SyncGroupSkippedTooFewStorages })
}
@Test
fun syncGroupDeleteRemovesFileOnTarget() = runBlocking {
val source = FakeStorage()
val target = FakeStorage()
val path = "obsolete.bin"
target.putFile(path, "old".encodeToByteArray())
val entry = StorageSyncJournalEntry(
path = path,
operation = StorageSyncOperation.DELETE,
revision = StorageSyncRevision(
sequence = 2L,
actorId = "actor-b",
createdAt = Instant.parse("2024-06-01T00:00:00Z"),
),
)
source.addSyncJournalEntry(entry)
val group = StorageSyncGroup(
id = "delete-group",
storageUuids = setOf(source.uuid, target.uuid),
encryptionKind = StorageSyncGroupEncryptionKind.NONE,
)
val engine = createEngine(
storages = listOf(source, target),
groups = listOf(group),
)
engine.syncGroup(group.id) { _, _ -> }
assertNull(target.fileBytes(path))
}
@Test
fun syncGroupStopsWhenLockCannotBeAcquired() = runBlocking {
val first = FakeStorage()
val second = FakeStorage().apply { setAcquireLockResult(false) }
val group = StorageSyncGroup(
id = "lock-fail",
storageUuids = setOf(first.uuid, second.uuid),
encryptionKind = StorageSyncGroupEncryptionKind.NONE,
)
first.addSyncJournalEntry(
StorageSyncJournalEntry(
path = "a.txt",
operation = StorageSyncOperation.UPSERT,
revision = StorageSyncRevision(1L, "x", Instant.EPOCH),
),
)
val labels = mutableListOf<TaskProgressLabel?>()
val engine = createEngine(
storages = listOf(first, second),
groups = listOf(group),
)
engine.syncGroup(group.id) { _, label -> labels.add(label) }
assertTrue(labels.any { it is TaskProgressLabel.SyncGroupLockFailed })
}
private fun createEngine(
storages: List<FakeStorage>,
groups: List<StorageSyncGroup>,
): StorageSyncEngine {
val vaultsManager = FakeVaultsManager(storages)
val findStorage = FindStorageUseCase(vaultsManager)
return StorageSyncEngine(
vaultsManager = vaultsManager,
groupStore = FakeStorageSyncGroupStore(groups),
findStorageUseCase = findStorage,
)
}
}

View File

@@ -0,0 +1,88 @@
package com.github.nullptroma.wallenc.usecases
import com.github.nullptroma.wallenc.domain.datatypes.TwoFaTokenRecord
import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test
import java.time.Duration
import java.time.Instant
import javax.crypto.spec.SecretKeySpec
class TwoFaTotpTest {
@Test
fun buildTwoFaCodeStateMatchesJavaOtpForKnownSecret() {
val secretBase32 = "JBSWY3DPEHPK3PXP"
val token = TwoFaTokenRecord(
id = "1",
issuer = "Test",
account = "user",
secret = secretBase32,
digits = 6,
periodSeconds = 30,
algorithm = "SHA1",
notes = null,
)
val nowMillis = 1_700_000_000_000L
val state = buildTwoFaCodeState(token, nowMillis)
assertNotNull(state)
val expected = generateReferenceTotp(secretBase32, nowMillis, 6, 30, "HmacSHA1")
assertEquals(expected, state!!.code)
assertTrue(state.secondsUntilRefresh in 1..30)
}
@Test
fun buildTwoFaCodeStateReturnsNullForInvalidSecret() {
val token = TwoFaTokenRecord(
id = "1",
issuer = "Test",
account = "user",
secret = "!!!not-base32!!!",
digits = 6,
periodSeconds = 30,
algorithm = "SHA1",
notes = null,
)
assertEquals(null, buildTwoFaCodeState(token, System.currentTimeMillis()))
}
private fun generateReferenceTotp(
secretBase32: String,
nowMillis: Long,
digits: Int,
periodSeconds: Int,
macAlgorithm: String,
): String {
val key = decodeBase32(secretBase32)
val generator = TimeBasedOneTimePasswordGenerator(
Duration.ofSeconds(periodSeconds.toLong()),
digits,
macAlgorithm,
)
val otp = generator.generateOneTimePassword(
SecretKeySpec(key, "RAW"),
Instant.ofEpochMilli(nowMillis),
)
return otp.toString().padStart(digits, '0')
}
private fun decodeBase32(input: String): ByteArray {
val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
val clean = input.uppercase().replace(" ", "").replace("=", "")
var buffer = 0
var bitsLeft = 0
val out = ArrayList<Byte>()
for (ch in clean) {
val value = alphabet.indexOf(ch)
buffer = (buffer shl 5) or value
bitsLeft += 5
if (bitsLeft >= 8) {
out.add(((buffer shr (bitsLeft - 8)) and 0xFF).toByte())
bitsLeft -= 8
}
}
return out.toByteArray()
}
}

View File

@@ -0,0 +1,60 @@
package com.github.nullptroma.wallenc.usecases.fakes
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalEntry
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import com.github.nullptroma.wallenc.domain.interfaces.IStorageMetaInfo
import com.github.nullptroma.wallenc.domain.tasks.TaskProgress
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
import java.time.Instant
import java.util.UUID
class FakeStorage(
override val uuid: UUID = UUID.randomUUID(),
private val accessorImpl: FakeStorageAccessor = FakeStorageAccessor(),
private val meta: FakeMetaInfo = FakeMetaInfo(),
) : IStorage {
override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true)
override val size: StateFlow<Long?> = MutableStateFlow(0L)
override val numberOfFiles: StateFlow<Int?> = MutableStateFlow(0)
override val isEmpty: Flow<Boolean?> = flowOf(true)
override val metaInfo: StateFlow<IStorageMetaInfo> = MutableStateFlow(meta)
override val isVirtualStorage: Boolean = false
override val accessor: IStorageAccessor = accessorImpl
override suspend fun rename(newName: String) = Unit
override suspend fun setEncInfo(encInfo: StorageEncryptionInfo?) = Unit
override suspend fun clearAllContent(onProgress: suspend (TaskProgress) -> Unit) = Unit
fun setDomainFile(path: String, value: String) {
putFile(path, value.encodeToByteArray())
}
fun putFile(path: String, bytes: ByteArray) {
accessorImpl.dataFiles[path] = bytes
}
fun fileBytes(path: String): ByteArray? = accessorImpl.dataFiles[path]
fun addSyncJournalEntry(entry: StorageSyncJournalEntry) {
accessorImpl.syncJournal.add(entry)
}
fun syncJournalEntries(): List<StorageSyncJournalEntry> = accessorImpl.syncJournal.toList()
fun setAcquireLockResult(result: Boolean) {
accessorImpl.acquireLockResult = result
}
}
class FakeMetaInfo(
override val encInfo: StorageEncryptionInfo? = null,
override val name: String = "Fake",
override val lastModified: Instant = Instant.now(),
) : IStorageMetaInfo

View File

@@ -0,0 +1,140 @@
package com.github.nullptroma.wallenc.usecases.fakes
import com.github.nullptroma.wallenc.domain.datatypes.DataPage
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncJournalEntry
import com.github.nullptroma.wallenc.domain.datatypes.StorageSyncLock
import com.github.nullptroma.wallenc.domain.interfaces.IDirectory
import com.github.nullptroma.wallenc.domain.interfaces.IFile
import com.github.nullptroma.wallenc.domain.interfaces.IMetaInfo
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.emptyFlow
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.time.Instant
class FakeStorageAccessor : IStorageAccessor {
val dataFiles: MutableMap<String, ByteArray> = mutableMapOf()
private val systemFiles: MutableMap<String, ByteArray> = mutableMapOf()
private val _filesUpdates = MutableSharedFlow<DataPage<IFile>>(extraBufferCapacity = 16)
var syncJournal: MutableList<StorageSyncJournalEntry> = mutableListOf()
var syncLock: StorageSyncLock? = null
var acquireLockResult: Boolean = true
override val size: StateFlow<Long?> = MutableStateFlow(0L)
override val numberOfFiles: StateFlow<Int?> = MutableStateFlow(0)
override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true)
override val filesUpdates: SharedFlow<DataPage<IFile>> = _filesUpdates
override val dirsUpdates: SharedFlow<DataPage<IDirectory>> = MutableSharedFlow()
override suspend fun getAllFiles(): List<IFile> = emptyList()
override suspend fun getFiles(path: String): List<IFile> = emptyList()
override fun getFilesFlow(path: String): Flow<DataPage<IFile>> = emptyFlow()
override suspend fun getAllDirs(): List<IDirectory> = emptyList()
override suspend fun getDirs(path: String): List<IDirectory> = emptyList()
override fun getDirsFlow(path: String): Flow<DataPage<IDirectory>> = emptyFlow()
override suspend fun getFileInfo(path: String): IFile {
error("Not implemented in tests")
}
override suspend fun getDirInfo(path: String): IDirectory {
error("Not implemented in tests")
}
override suspend fun setHidden(path: String, hidden: Boolean) = Unit
override suspend fun touchFile(path: String) = Unit
override suspend fun touchDir(path: String) = Unit
override suspend fun delete(path: String) {
dataFiles.remove(path)
}
override suspend fun openWrite(path: String): OutputStream {
return object : ByteArrayOutputStream() {
override fun close() {
dataFiles[path] = toByteArray()
_filesUpdates.tryEmit(
DataPage(
listOf(FakeFile(path)),
pageLength = 1,
pageIndex = 0,
),
)
}
}
}
override suspend fun openRead(path: String): InputStream {
val bytes = dataFiles[path] ?: throw IllegalStateException("File not found: $path")
return ByteArrayInputStream(bytes)
}
override suspend fun moveToTrash(path: String) = Unit
override suspend fun openReadSystemFile(name: String): InputStream {
val bytes = systemFiles[name] ?: ByteArray(0)
return ByteArrayInputStream(bytes)
}
override suspend fun openWriteSystemFile(name: String): OutputStream {
return object : ByteArrayOutputStream() {
override fun close() {
systemFiles[name] = toByteArray()
}
}
}
override suspend fun readSyncJournal(): List<StorageSyncJournalEntry> = syncJournal.toList()
override suspend fun appendSyncJournal(entries: List<StorageSyncJournalEntry>) {
syncJournal.addAll(entries)
}
override suspend fun rewriteSyncJournal(entries: List<StorageSyncJournalEntry>) {
syncJournal.clear()
syncJournal.addAll(entries)
}
override suspend fun readSyncLock(): StorageSyncLock? = syncLock
override suspend fun tryAcquireSyncLock(holderId: String, leaseUntil: Instant): Boolean {
if (!acquireLockResult) return false
syncLock = StorageSyncLock(holderId, leaseUntil, Instant.now())
return true
}
override suspend fun releaseSyncLock(holderId: String) {
if (syncLock?.holderId == holderId) {
syncLock = null
}
}
override suspend fun forceClearSyncLock() {
syncLock = null
}
}
class FakeFile(path: String) : IFile {
override val metaInfo: IMetaInfo = object : IMetaInfo {
override val size: Long = 0L
override val isDeleted: Boolean = false
override val isHidden: Boolean = false
override val lastModified: Instant = Instant.now()
override val path: String = path
}
}

View File

@@ -0,0 +1,18 @@
package com.github.nullptroma.wallenc.usecases.fakes
import com.github.nullptroma.wallenc.domain.interfaces.IStorageSyncGroupStore
import com.github.nullptroma.wallenc.domain.interfaces.StorageSyncGroup
class FakeStorageSyncGroupStore(
private var groups: List<StorageSyncGroup> = emptyList(),
) : IStorageSyncGroupStore {
override suspend fun getGroups(): List<StorageSyncGroup> = groups
override suspend fun putGroup(group: StorageSyncGroup) {
groups = groups.filterNot { it.id == group.id } + group
}
override suspend fun removeGroup(groupId: String) {
groups = groups.filterNot { it.id == groupId }
}
}

View File

@@ -0,0 +1,30 @@
package com.github.nullptroma.wallenc.usecases.fakes
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
import com.github.nullptroma.wallenc.domain.interfaces.IVault
import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
class FakeVaultsManager(
storages: List<IStorage>,
) : IVaultsManager {
override val vaults: StateFlow<List<IVault>> = MutableStateFlow(emptyList())
override val allStorages: StateFlow<List<IStorage>> = MutableStateFlow(storages)
override val unlockManager: IUnlockManager = FakeUnlockManager()
}
class FakeUnlockManager : IUnlockManager {
override val openedStorages: StateFlow<Map<UUID, IStorage>> = MutableStateFlow(emptyMap())
override fun getOpenedStorageKey(uuid: UUID): EncryptKey? = null
override suspend fun open(storage: IStorage, key: EncryptKey, rememberPassword: Boolean): IStorage = storage
override suspend fun close(storage: IStorage) = Unit
override suspend fun close(uuid: UUID) = Unit
}