@@ -6,26 +6,22 @@ import app.k9mail.legacy.mailstore.MessageStoreFactory
66import app.k9mail.legacy.mailstore.MessageStoreManager
77import app.k9mail.legacy.mailstore.SaveMessageData
88import app.k9mail.legacy.message.extractors.PreviewResult
9+ import assertk.assertFailure
910import assertk.assertThat
1011import assertk.assertions.isEqualTo
12+ import assertk.assertions.isInstanceOf
1113import com.fsck.k9.mail.internet.MimeMessage
1214import com.fsck.k9.mailstore.SaveMessageDataCreator
1315import kotlin.test.Test
1416import kotlinx.coroutines.Dispatchers
1517import kotlinx.coroutines.ExperimentalCoroutinesApi
16- import kotlinx.coroutines.flow.Flow
17- import kotlinx.coroutines.flow.flowOf
1818import kotlinx.coroutines.test.UnconfinedTestDispatcher
1919import kotlinx.coroutines.test.runTest
2020import kotlinx.datetime.LocalDateTime
2121import net.thunderbird.app.common.feature.mail.FakeData
22+ import net.thunderbird.components.core.logging.testing.TestLogger
2223import net.thunderbird.components.core.outcome.Outcome
23- import net.thunderbird.core.android.account.AccountRemovedListener
24- import net.thunderbird.core.android.account.AccountsChangeListener
2524import net.thunderbird.core.android.account.LegacyAccount
26- import net.thunderbird.core.android.account.LegacyAccountDto
27- import net.thunderbird.core.android.account.LegacyAccountDtoManager
28- import net.thunderbird.core.logging.testing.TestLogger
2925import net.thunderbird.feature.account.AccountId
3026import net.thunderbird.feature.mail.folder.FolderId
3127import net.thunderbird.feature.mail.folder.LegacyFolderIdFactory
@@ -40,11 +36,13 @@ import net.thunderbird.feature.mail.message.MessageHeaders
4036import net.thunderbird.feature.mail.message.MessageId
4137import net.thunderbird.feature.mail.message.MessageServerId
4238import net.thunderbird.feature.mail.message.domain.GetMessageIdCriteria
39+ import net.thunderbird.feature.mail.message.domain.MessageLifecycleError
4340import net.thunderbird.feature.mail.message.domain.MessageQueryError
4441import net.thunderbird.feature.mail.message.domain.MessageQueryRepository
4542import net.thunderbird.feature.mail.message.mapper.MessageDataMapper
4643import org.mockito.kotlin.mock
4744import org.mockito.kotlin.verify
45+ import org.mockito.kotlin.verifyNoInteractions
4846import org.mockito.kotlin.whenever
4947import com.fsck.k9.mail.Message as LegacyMessageDto
5048import com.fsck.k9.mail.MessageDownloadState as LegacyMessageDownloadState
@@ -81,7 +79,6 @@ class DefaultMessageLifecycleRepositoryTest {
8179 private val saveMessageDataCreator = mock<SaveMessageDataCreator >()
8280 private val messageQueryRepository = FakeMessageQueryRepository ()
8381 private val messageStoreManager = MessageStoreManager (
84- accountManager = FakeLegacyAccountDtoManager (accounts = listOf (LegacyAccountDto (account.id))),
8582 messageStoreFactory = FakeMessageStoreFactory (
8683 messageStoresByUuid = mapOf (
8784 account.id to ListenableMessageStore (messageStore, mainImmediateDispatcher = Dispatchers .Unconfined ),
@@ -92,14 +89,15 @@ class DefaultMessageLifecycleRepositoryTest {
9289 @OptIn(ExperimentalCoroutinesApi ::class )
9390 private val testSubject = DefaultMessageLifecycleRepository (
9491 logger = TestLogger (),
92+ messageQueryRepository = messageQueryRepository,
9593 messageStoreManager = messageStoreManager,
9694 saveMessageDataCreator = saveMessageDataCreator,
9795 messageMapper = FakeMessageDataMapper (legacyMessage),
9896 outboxFolderManager = FakeOutboxFolderManager (outboxFolderId = OUTBOX_FOLDER_ID ),
9997 ioDispatcher = UnconfinedTestDispatcher (),
10098 )
10199
102- // region create
100+ // region [ create]
103101
104102 @Test
105103 fun `create should save a remote message when it does not exist yet` () = runTest {
@@ -114,6 +112,25 @@ class DefaultMessageLifecycleRepositoryTest {
114112
115113 // Assert
116114 assertThat(outcome).isEqualTo(Outcome .success(LegacyMessageIdFactory .of(SAVED_MESSAGE_ID )))
115+ assertThat(messageQueryRepository.lastCriteria)
116+ .isEqualTo(GetMessageIdCriteria (folderId, MessageServerId (SERVER_ID )))
117+ verify(messageStore).saveRemoteMessage(FOLDER_ID , SERVER_ID , saveMessageData)
118+ }
119+
120+ @Test
121+ fun `create should return MessageAlreadyExists without touching the store when the message exists` () = runTest {
122+ // Arrange
123+ val existingMessageId = LegacyMessageIdFactory .of(EXISTING_MESSAGE_ID )
124+ messageQueryRepository.result = Outcome .success(existingMessageId)
125+ val message = buildDomainMessage(serverId = MessageServerId (SERVER_ID ))
126+
127+ // Act
128+ val outcome = testSubject.create(message, accountId, folderId)
129+
130+ // Assert
131+ assertThat(outcome).isEqualTo(Outcome .failure(MessageLifecycleError .MessageAlreadyExists (existingMessageId)))
132+ verifyNoInteractions(messageStore)
133+ verifyNoInteractions(saveMessageDataCreator)
117134 }
118135
119136 @Test
@@ -164,6 +181,56 @@ class DefaultMessageLifecycleRepositoryTest {
164181 verify(saveMessageDataCreator).createSaveMessageData(legacyMessage, LegacyMessageDownloadState .PARTIAL , null )
165182 }
166183
184+ // endregion [create]
185+
186+ // region [update]
187+
188+ @Test
189+ fun `update should replace the existing local message when the message has no server id` () = runTest {
190+ // Arrange
191+ val message = buildDomainMessage(id = LegacyMessageIdFactory .of(EXISTING_MESSAGE_ID ), serverId = null )
192+ stubSaveMessageData()
193+ whenever(messageStore.saveLocalMessage(FOLDER_ID , saveMessageData, EXISTING_MESSAGE_ID ))
194+ .thenReturn(EXISTING_MESSAGE_ID )
195+
196+ // Act
197+ val outcome = testSubject.update(message, accountId, folderId)
198+
199+ // Assert
200+ assertThat(outcome).isEqualTo(Outcome .success(LegacyMessageIdFactory .of(EXISTING_MESSAGE_ID )))
201+ verify(messageStore).saveLocalMessage(FOLDER_ID , saveMessageData, EXISTING_MESSAGE_ID )
202+ }
203+
204+ @Test
205+ fun `update should save the remote message by folder and server id` () = runTest {
206+ // Arrange
207+ val message = buildDomainMessage(
208+ id = LegacyMessageIdFactory .of(EXISTING_MESSAGE_ID ),
209+ serverId = MessageServerId (SERVER_ID ),
210+ )
211+ stubSaveMessageData()
212+ whenever(messageStore.saveRemoteMessage(FOLDER_ID , SERVER_ID , saveMessageData))
213+ .thenReturn(EXISTING_MESSAGE_ID )
214+
215+ // Act
216+ val outcome = testSubject.update(message, accountId, folderId)
217+
218+ // Assert
219+ assertThat(outcome).isEqualTo(Outcome .success(LegacyMessageIdFactory .of(EXISTING_MESSAGE_ID )))
220+ verify(messageStore).saveRemoteMessage(FOLDER_ID , SERVER_ID , saveMessageData)
221+ }
222+
223+ @Test
224+ fun `update should fail fast when the message has no id` () = runTest {
225+ // Arrange
226+ val message = buildDomainMessage(id = null , serverId = null )
227+
228+ // Act & Assert
229+ assertFailure { testSubject.update(message, accountId, folderId) }
230+ .isInstanceOf<IllegalArgumentException >()
231+ verifyNoInteractions(messageStore)
232+ }
233+
167234 // endregion
168235
169236 private fun stubSaveMessageData () {
@@ -203,26 +270,10 @@ class DefaultMessageLifecycleRepositoryTest {
203270 )
204271}
205272
206- private class FakeLegacyAccountDtoManager (
207- accounts : List <LegacyAccountDto >,
208- ) : LegacyAccountDtoManager {
209- private val accountsByUuid = accounts.associateBy { it.id }
210-
211- override fun getAccounts (): List <LegacyAccountDto > = accountsByUuid.values.toList()
212- override fun getAccountsFlow (): Flow <List <LegacyAccountDto >> = flowOf(getAccounts())
213- override fun getById (accountId : AccountId ): LegacyAccountDto ? = accountsByUuid[accountId]
214- override fun observeById (accountId : AccountId ): Flow <LegacyAccountDto ?> = flowOf(getById(accountId))
215- override fun addAccountRemovedListener (listener : AccountRemovedListener ) = Unit
216- override fun moveAccount (account : LegacyAccountDto , newPosition : Int ) = Unit
217- override fun addOnAccountsChangeListener (accountsChangeListener : AccountsChangeListener ) = Unit
218- override fun removeOnAccountsChangeListener (accountsChangeListener : AccountsChangeListener ) = Unit
219- override fun saveAccount (account : LegacyAccountDto ) = Unit
220- }
221-
222273private class FakeMessageStoreFactory (
223274 private val messageStoresByUuid : Map <AccountId , ListenableMessageStore >,
224275) : MessageStoreFactory {
225- override fun create (account : LegacyAccountDto ): ListenableMessageStore = messageStoresByUuid.getValue(account.id )
276+ override fun create (accountId : AccountId ): ListenableMessageStore = messageStoresByUuid.getValue(accountId )
226277}
227278
228279private class FakeMessageQueryRepository : MessageQueryRepository {
0 commit comments