Files
LibreMail/app/src/test/kotlin/org/libremail/mail/ImapClientTest.kt
T
JMR-devandClaude Opus 4.8 57b628f90f Add server-side IMAP search
Search previously only filtered the cached inbox. Now a query also runs an IMAP SEARCH
on the server and folds the matches into the cache, so messages beyond the synced
window surface in the results.

- ImapClient.search(query) ORs SUBJECT/FROM/BODY terms and fetches matching headers
  (extracted a shared toFetchedMessage mapper, reused by fetchRecentInbox).
- MailRepository.searchServer inserts/updates matches into the message cache (no
  pruning); MailboxViewModel triggers it from a debounced, deduplicated search query.
- assemble/test/lint green, including a new ImapClient test asserting SEARCH returns
  only the matching message against GreenMail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 17:00:28 -05:00

91 lines
3.3 KiB
Kotlin

// SPDX-License-Identifier: GPL-3.0-or-later
package org.libremail.mail
import com.icegreen.greenmail.util.GreenMail
import com.icegreen.greenmail.util.GreenMailUtil
import com.icegreen.greenmail.util.ServerSetupTest
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.libremail.domain.model.ImapConnectionParams
import org.libremail.domain.model.MailSecurity
class ImapClientTest {
private lateinit var greenMail: GreenMail
private val client = ImapClient()
@Before
fun setUp() {
greenMail = GreenMail(ServerSetupTest.SMTP_IMAP)
greenMail.start()
greenMail.setUser("alice@example.org", "secret")
}
@After
fun tearDown() {
greenMail.stop()
}
private fun params(secret: String = "secret") = ImapConnectionParams(
host = "127.0.0.1",
port = greenMail.imap.port,
security = MailSecurity.NONE,
username = "alice@example.org",
secret = secret,
useXoauth2 = false,
)
@Test
fun `listFolders returns INBOX for a valid login`() = runTest {
assertTrue(client.listFolders(params()).any { it.equals("INBOX", ignoreCase = true) })
}
@Test
fun `listFolders fails for a wrong password`() = runTest {
assertFailsWith<Exception> { client.listFolders(params(secret = "wrong-password")) }
}
@Test
fun `fetchRecentInbox returns delivered messages newest first`() = runTest {
GreenMailUtil.sendTextEmailTest("alice@example.org", "bob@example.org", "First subject", "Body one")
GreenMailUtil.sendTextEmailTest("alice@example.org", "carol@example.org", "Second subject", "Body two")
greenMail.waitForIncomingEmail(2)
val messages = client.fetchRecentInbox(params(), limit = 50)
assertEquals(2, messages.size)
assertEquals("Second subject", messages.first().subject)
assertEquals(setOf("First subject", "Second subject"), messages.map { it.subject }.toSet())
assertEquals("bob@example.org", messages.first { it.subject == "First subject" }.senderEmail)
}
@Test
fun `fetchBodyMarkingSeen returns the body and marks the message read`() = runTest {
GreenMailUtil.sendTextEmailTest("alice@example.org", "bob@example.org", "Hello", "The quick brown fox.")
greenMail.waitForIncomingEmail(1)
val uid = client.fetchRecentInbox(params(), limit = 50).first().uid
val content = client.fetchBodyMarkingSeen(params(), uid)
assertTrue(content.body.contains("quick brown fox"), "body=${content.body}")
assertTrue(client.fetchRecentInbox(params(), limit = 50).first().isRead, "should be marked read")
}
@Test
fun `search returns only messages matching the query`() = runTest {
GreenMailUtil.sendTextEmailTest("alice@example.org", "bob@example.org", "Vacation plans", "Beach trip")
GreenMailUtil.sendTextEmailTest("alice@example.org", "carol@example.org", "Invoice 42", "Payment due")
greenMail.waitForIncomingEmail(2)
val results = client.search(params(), query = "Vacation", limit = 50)
assertEquals(1, results.size)
assertEquals("Vacation plans", results.first().subject)
}
}