diff --git a/legacy/storage/src/main/java/com/fsck/k9/storage/messages/SaveMessageOperations.kt b/legacy/storage/src/main/java/com/fsck/k9/storage/messages/SaveMessageOperations.kt index 1490ae8966b..23dd03105e5 100644 --- a/legacy/storage/src/main/java/com/fsck/k9/storage/messages/SaveMessageOperations.kt +++ b/legacy/storage/src/main/java/com/fsck/k9/storage/messages/SaveMessageOperations.kt @@ -426,8 +426,7 @@ internal class SaveMessageOperations( } return if (replaceMessageId != null) { - values.put("id", replaceMessageId) - database.replace("messages", null, values) + database.update("messages", values, "id = ?", arrayOf(replaceMessageId.toString())) replaceMessageId } else { database.insert("messages", null, values) diff --git a/legacy/storage/src/test/java/com/fsck/k9/storage/messages/SaveMessageOperationsTest.kt b/legacy/storage/src/test/java/com/fsck/k9/storage/messages/SaveMessageOperationsTest.kt index fe8fd7c86b2..ba20d70fd73 100644 --- a/legacy/storage/src/test/java/com/fsck/k9/storage/messages/SaveMessageOperationsTest.kt +++ b/legacy/storage/src/test/java/com/fsck/k9/storage/messages/SaveMessageOperationsTest.kt @@ -379,6 +379,46 @@ class SaveMessageOperationsTest : RobolectricTest() { assertThat(thread.messageId).isEqualTo(message.id) } + @Test + fun `replace envelope message with full message should preserve thread entry`() { + // Arrange: simulate remote search saving a message as ENVELOPE (no body) + val envelopeMessageData = buildMessage { + header("Message-ID", "") + header("Subject", "Search Result") + }.toSaveMessageData( + downloadState = MessageDownloadState.ENVELOPE, + ) + saveMessageOperations.saveRemoteMessage(folderId = 1, messageServerId = "uid1", envelopeMessageData) + + val threadsBeforeReplace = sqliteDatabase.readThreads() + assertThat(threadsBeforeReplace).hasSize(1) + val threadBeforeReplace = threadsBeforeReplace.first() + + // Act: simulate sync re-downloading the same message as FULL + val fullMessageData = buildMessage { + header("Message-ID", "") + header("Subject", "Search Result") + textBody("Full body content") + }.toSaveMessageData( + downloadState = MessageDownloadState.FULL, + ) + saveMessageOperations.saveRemoteMessage(folderId = 1, messageServerId = "uid1", fullMessageData) + + // Assert: thread entry must still exist and point to the same message + val messages = sqliteDatabase.readMessages() + assertThat(messages).hasSize(1) + val message = messages.first() + assertThat(message.flags).isEqualTo("X_DOWNLOADED_FULL") + + val threads = sqliteDatabase.readThreads() + assertThat(threads).hasSize(1) + val thread = threads.first() + assertThat(thread.id).isEqualTo(threadBeforeReplace.id) + assertThat(thread.messageId).isEqualTo(message.id) + assertThat(thread.root).isEqualTo(thread.id) + assertThat(thread.parent).isNull() + } + @Test fun `save local message`() { val messageData = buildMessage {