Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit d0032aed authored by Rafael Tonholo's avatar Rafael Tonholo
Browse files

refactor(archive): move try-catch out of `use` function for properly handling exception

chore(archive): add SetArchiveFolder's unit tests
parent c24d810f
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -28,7 +28,7 @@ val featureMessageModule = module {
    }
    factory<DomainContract.UseCase.SetArchiveFolder> {
        SetArchiveFolder(
            baseAccountManager = get<AccountManager<BaseAccount>>(),
            accountManager = get<AccountManager<BaseAccount>>(),
            backendStorageFactory = get<BackendStorageFactory<BaseAccount>>(),
            specialFolderUpdaterFactory = get<SpecialFolderUpdater.Factory<BaseAccount>>(),
        )
+11 −11
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@ import net.thunderbird.feature.mail.message.list.domain.SetAccountFolderOutcome
import com.fsck.k9.mail.FolderType as LegacyFolderType

internal class SetArchiveFolder(
    private val baseAccountManager: AccountManager<BaseAccount>,
    private val accountManager: AccountManager<BaseAccount>,
    private val backendStorageFactory: BackendStorageFactory<BaseAccount>,
    private val specialFolderUpdaterFactory: SpecialFolderUpdater.Factory<BaseAccount>,
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
@@ -27,16 +27,16 @@ internal class SetArchiveFolder(
        folder: RemoteFolder,
    ): Outcome<SetAccountFolderOutcome.Success, SetAccountFolderOutcome.Error> {
        val account = withContext(ioDispatcher) {
            baseAccountManager.getAccount(accountUuid)
            accountManager.getAccount(accountUuid)
        } ?: return Outcome.Failure(SetAccountFolderOutcome.Error.AccountNotFound)

        val backend = backendStorageFactory.createBackendStorage(account)
        val specialFolderUpdater = specialFolderUpdaterFactory.create(account)
        return withContext(ioDispatcher) {
        return try {
            withContext(ioDispatcher) {
                backend
                    .createFolderUpdater()
                    .use { updater ->
                    try {
                        updater.changeFolder(
                            folderServerId = folder.serverId,
                            name = folder.name,
@@ -48,13 +48,13 @@ internal class SetArchiveFolder(
                            selection = SpecialFolderSelection.MANUAL,
                        )
                        specialFolderUpdater.updateSpecialFolders()
                        baseAccountManager.saveAccount(account)
                        accountManager.saveAccount(account)

                        Outcome.success(SetAccountFolderOutcome.Success)
                    } catch (e: MessagingException) {
                        Outcome.Failure(SetAccountFolderOutcome.Error.UnhandledError(throwable = e))
                    }
            }
        } catch (e: MessagingException) {
            Outcome.Failure(SetAccountFolderOutcome.Error.UnhandledError(throwable = e))
        }
    }
}
+186 −0
Original line number Diff line number Diff line
package net.thunderbird.feature.mail.message.list.domain.usecase

import assertk.assertThat
import assertk.assertions.hasMessage
import assertk.assertions.isEqualTo
import assertk.assertions.isInstanceOf
import assertk.assertions.prop
import com.fsck.k9.mail.MessagingException
import dev.mokkery.matcher.any
import dev.mokkery.matcher.matching
import dev.mokkery.spy
import dev.mokkery.verify
import dev.mokkery.verify.VerifyMode.Companion.exactly
import kotlin.random.Random
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
import kotlinx.coroutines.test.runTest
import net.thunderbird.core.outcome.Outcome
import net.thunderbird.feature.mail.account.api.BaseAccount
import net.thunderbird.feature.mail.folder.api.FolderType
import net.thunderbird.feature.mail.folder.api.RemoteFolder
import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
import net.thunderbird.feature.mail.message.list.domain.SetAccountFolderOutcome
import net.thunderbird.feature.mail.message.list.fakes.FakeAccount
import net.thunderbird.feature.mail.message.list.fakes.FakeAccountManager
import net.thunderbird.feature.mail.message.list.fakes.FakeBackendFolderUpdater
import net.thunderbird.feature.mail.message.list.fakes.FakeBackendStorageFactory
import net.thunderbird.feature.mail.message.list.fakes.FakeSpecialFolderUpdaterFactory
import org.junit.Test
import com.fsck.k9.mail.FolderType as LegacyFolderType

@OptIn(ExperimentalUuidApi::class)
@Suppress("MaxLineLength")
class SetArchiveFolderTest {
    @Test
    fun `invoke should successfully create folder and update account when given valid input`() = runTest {
        // Arrange
        val accountUuid = Uuid.random().toHexString()
        val accounts = listOf(FakeAccount(uuid = accountUuid))

        val fakeBackendStorageFactory = FakeBackendStorageFactory()
        val fakeAccountManager = spy(FakeAccountManager(accounts))
        val fakeSpecialFolderUpdaterFactory = FakeSpecialFolderUpdaterFactory()
        val testSubject =
            createTestSubject(fakeAccountManager, fakeBackendStorageFactory, fakeSpecialFolderUpdaterFactory)
        val folder = createRemoteFolder()

        // Act
        val outcome = testSubject(accountUuid, folder)

        // Assert
        assertThat(outcome)
            .isInstanceOf<Outcome.Success<SetAccountFolderOutcome.Success>>()
            .prop(name = "data") { it.data }
            .isEqualTo(SetAccountFolderOutcome.Success)

        verify(exactly(1)) {
            fakeBackendStorageFactory.backendFolderUpdater.changeFolder(
                folderServerId = folder.serverId,
                name = folder.name,
                type = LegacyFolderType.ARCHIVE,
            )
        }
        verify(exactly(1)) { fakeBackendStorageFactory.backendFolderUpdater.close() }
        verify(exactly(1)) {
            fakeSpecialFolderUpdaterFactory.specialFolderUpdater.setSpecialFolder(
                type = FolderType.ARCHIVE,
                folderId = folder.id,
                selection = SpecialFolderSelection.MANUAL,
            )
        }
        verify(exactly(1)) {
            fakeSpecialFolderUpdaterFactory.specialFolderUpdater.updateSpecialFolders()
        }
        verify(exactly(1)) {
            fakeAccountManager.saveAccount(
                account = matching {
                    it.uuid == accountUuid
                },
            )
        }
    }

    @Test
    fun `invoke should return AccountNotFound when account is not found`() = runTest {
        // Arrange
        val accounts = listOf<FakeAccount>()
        val testSubject = createTestSubject(accounts)
        val accountUuid = Uuid.random().toHexString()
        val folder = createRemoteFolder()

        // Act
        val outcome = testSubject(accountUuid, folder)

        // Assert
        assertThat(outcome)
            .isInstanceOf<Outcome.Failure<SetAccountFolderOutcome.Error>>()
            .prop(name = "error") { it.error }
            .isEqualTo(SetAccountFolderOutcome.Error.AccountNotFound)
    }

    @Test
    fun `invoke should return UnhandledError when changeFolder throws MessagingException`() = runTest {
        // Arrange
        val accountUuid = Uuid.random().toHexString()
        val accounts = listOf(FakeAccount(uuid = accountUuid))

        val exception = MessagingException("this is an error")
        val fakeBackendStorageFactory = FakeBackendStorageFactory(
            backendFolderUpdater = FakeBackendFolderUpdater(exception = exception),
        )
        val fakeAccountManager = spy(FakeAccountManager(accounts))
        val fakeSpecialFolderUpdaterFactory = FakeSpecialFolderUpdaterFactory()
        val testSubject =
            createTestSubject(fakeAccountManager, fakeBackendStorageFactory, fakeSpecialFolderUpdaterFactory)
        val folder = createRemoteFolder()

        // Act
        val outcome = testSubject(accountUuid, folder)

        // Assert
        assertThat(outcome)
            .isInstanceOf<Outcome.Failure<SetAccountFolderOutcome.Error>>()
            .prop(name = "error") { it.error }
            .isInstanceOf<SetAccountFolderOutcome.Error.UnhandledError>()
            .prop("throwable") { it.throwable }
            .hasMessage(exception.message)

        verify(exactly(1)) {
            fakeBackendStorageFactory.backendFolderUpdater.changeFolder(
                folderServerId = folder.serverId,
                name = folder.name,
                type = LegacyFolderType.ARCHIVE,
            )
        }

        verify(exactly(1)) { fakeBackendStorageFactory.backendFolderUpdater.close() }

        verify(exactly(0)) {
            fakeSpecialFolderUpdaterFactory.specialFolderUpdater.setSpecialFolder(
                type = any(),
                folderId = any(),
                selection = any(),
            )
        }
        verify(exactly(0)) {
            fakeSpecialFolderUpdaterFactory.specialFolderUpdater.updateSpecialFolders()
        }
        verify(exactly(0)) {
            fakeAccountManager.saveAccount(account = any())
        }
    }

    private fun createTestSubject(
        accounts: List<BaseAccount>,
        backendStorageFactory: FakeBackendStorageFactory = FakeBackendStorageFactory(),
        specialFolderUpdaterFactory: FakeSpecialFolderUpdaterFactory = FakeSpecialFolderUpdaterFactory(),
    ): SetArchiveFolder = createTestSubject(
        accountManager = FakeAccountManager(accounts),
        backendStorageFactory = backendStorageFactory,
        specialFolderUpdaterFactory = specialFolderUpdaterFactory,
    )

    private fun createTestSubject(
        accountManager: FakeAccountManager,
        backendStorageFactory: FakeBackendStorageFactory = FakeBackendStorageFactory(),
        specialFolderUpdaterFactory: FakeSpecialFolderUpdaterFactory = FakeSpecialFolderUpdaterFactory(),
    ): SetArchiveFolder {
        return SetArchiveFolder(
            accountManager = accountManager,
            backendStorageFactory = backendStorageFactory,
            specialFolderUpdaterFactory = specialFolderUpdaterFactory,
        )
    }

    private fun createRemoteFolder(
        id: Long = Random.nextLong(),
        serverId: String = "remote_folder_$id",
        name: String = serverId,
    ): RemoteFolder = RemoteFolder(
        id = id,
        serverId = serverId,
        name = name,
        type = FolderType.ARCHIVE,
    )
}
+1 −1
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ import kotlinx.coroutines.flow.Flow
import net.thunderbird.feature.mail.account.api.AccountManager
import net.thunderbird.feature.mail.account.api.BaseAccount

internal class FakeAccountManager(
internal open class FakeAccountManager(
    private val accounts: List<BaseAccount>,
) : AccountManager<BaseAccount> {
    override fun getAccounts(): List<BaseAccount> = error("not implemented.")
+32 −0
Original line number Diff line number Diff line
package net.thunderbird.feature.mail.message.list.fakes

import com.fsck.k9.backend.api.BackendFolderUpdater
import com.fsck.k9.backend.api.FolderInfo
import com.fsck.k9.mail.FolderType

internal open class FakeBackendFolderUpdater(
    private val exception: Exception? = null,
) : BackendFolderUpdater {
    private val ids = mutableSetOf<Long>()
    override fun createFolders(folders: List<FolderInfo>): Set<Long> {
        if (exception != null) throw exception

        var last = ids.last()

        ids.addAll(folders.map { ++last })

        return ids
    }

    override fun deleteFolders(folderServerIds: List<String>) {
        if (exception != null) throw exception
    }

    override fun changeFolder(folderServerId: String, name: String, type: FolderType) {
        if (exception != null) throw exception
    }

    override fun close() {
        if (exception != null) throw exception
    }
}
Loading