Repository: thunderbird/thunderbird-android (language: kotlin)

Issue:
Convert SearchResponse class from  Java to Kotlin
We should migrate the `com.fsck.k9.mail.store.imap.SearchResponse` class to Kotlin to align with the ongoing modernization efforts and Kotlin-first development approach across the codebase.


Discussion hints:
nan

Pull request: Convert SearchResponse class from Java to Kotlin
- Fixes #9298 
### Implementation Details:
 - Rewrite the com.fsck.k9.mail.store.imap.SearchResponse class from Java to Kotlin using idiomatic Kotlin conventions.
 - Update `com.fsck.k9.mail.store.imap.SearchResponseTest `to use AssertK assertions instead of JUnit assertions.
 - Update `com.fsck.k9.mail.store.imap.SearchResponseTest `to adopt to new test guideline.

Test command: ./gradlew :mail:protocols:imap:test --tests "com.fsck.k9.mail.store.imap.SearchResponseTest" --tests "com.fsck.k9.mail.store.imap.SearchResponseTest"

Fix patch:
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/SearchResponse.java b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/SearchResponse.java
deleted file mode 100644
index 3fac2905df5..00000000000
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/SearchResponse.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.fsck.k9.mail.store.imap;
-
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.fsck.k9.mail.store.imap.ImapResponseParser.equalsIgnoreCase;
-
-
-class SearchResponse {
-    private final List<Long> numbers;
-
-
-    private SearchResponse(List<Long> numbers) {
-        this.numbers = numbers;
-    }
-
-    public static SearchResponse parse(List<ImapResponse> responses) {
-        List<Long> numbers = new ArrayList<>();
-
-        for (ImapResponse response : responses) {
-            parseSingleLine(response, numbers);
-        }
-
-        return new SearchResponse(numbers);
-    }
-
-    private static void parseSingleLine(ImapResponse response, List<Long> numbers) {
-        if (response.isTagged() || response.size() < 2 || !equalsIgnoreCase(response.get(0), Responses.SEARCH)) {
-            return;
-        }
-
-        int end = response.size();
-        for (int i = 1; i < end; i++) {
-            try {
-                long number = response.getLong(i);
-                numbers.add(number);
-            } catch (NumberFormatException e) {
-                return;
-            }
-        }
-    }
-
-    /**
-     * @return A mutable list of numbers from the SEARCH response(s).
-     */
-    public List<Long> getNumbers() {
-        return numbers;
-    }
-}
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/SearchResponse.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/SearchResponse.kt
new file mode 100644
index 00000000000..c5e43560008
--- /dev/null
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/SearchResponse.kt
@@ -0,0 +1,43 @@
+package com.fsck.k9.mail.store.imap
+
+internal class SearchResponse private constructor(
+    /**
+     * @return A list of numbers from the SEARCH response(s).
+     */
+    val numbers: List<Long>,
+) {
+    companion object {
+        @JvmStatic
+        fun parse(responses: List<ImapResponse>): SearchResponse {
+            val numbers = mutableListOf<Long>()
+
+            for (response in responses) {
+                parseSingleLine(response, numbers)
+            }
+
+            return SearchResponse(numbers)
+        }
+
+        private fun parseSingleLine(response: ImapResponse, numbers: MutableList<Long>) {
+            if (response.isTagged ||
+                response.size < 2 ||
+                !ImapResponseParser.equalsIgnoreCase(
+                    response[0],
+                    Responses.SEARCH,
+                )
+            ) {
+                return
+            }
+
+            val end = response.size
+            for (i in 1..<end) {
+                try {
+                    val number = response.getLong(i)
+                    numbers.add(number)
+                } catch (_: NumberFormatException) {
+                    return
+                }
+            }
+        }
+    }
+}


Test patch:
diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SearchResponseTest.java b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SearchResponseTest.java
deleted file mode 100644
index ec7bc2c7346..00000000000
--- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SearchResponseTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package com.fsck.k9.mail.store.imap;
-
-
-import java.util.Collections;
-import java.util.List;
-
-import org.junit.Test;
-
-import static com.fsck.k9.mail.store.imap.ImapResponseHelper.createImapResponseList;
-import static java.util.Arrays.asList;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-
-public class SearchResponseTest {
-    @Test
-    public void parse_withSingleSearchResponse_shouldExtractNumbers() throws Exception {
-        List<ImapResponse> imapResponses = createImapResponseList(
-                "* SEARCH 1 2 3",
-                "* 23 EXISTS",
-                "* SEARCH 4",
-                "1 OK SEARCH completed");
-
-        SearchResponse result = SearchResponse.parse(imapResponses);
-
-        assertNotNull(result);
-        assertEquals(asList(1L, 2L, 3L, 4L), result.getNumbers());
-    }
-
-    @Test
-    public void parse_withMultipleSearchResponses_shouldExtractNumbers() throws Exception {
-        List<ImapResponse> imapResponses = createImapResponseList(
-                "* SEARCH 1 2 3",
-                "* 23 EXISTS",
-                "* SEARCH 4",
-                "1 OK SEARCH completed",
-                "* SEARCH 5 6",
-                "* 19 EXPUNGED",
-                "* SEARCH 7",
-                "2 OK SEARCH completed",
-                "* SEARCH 8",
-                "3 OK SEARCH completed");
-
-        SearchResponse result = SearchResponse.parse(imapResponses);
-
-        assertNotNull(result);
-        assertEquals(asList(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L), result.getNumbers());
-    }
-
-    @Test
-    public void parse_withSingleTaggedSearchResponse_shouldReturnEmptyList() throws Exception {
-        List<ImapResponse> imapResponses = createImapResponseList("x SEARCH 7 8 9");
-
-        SearchResponse result = SearchResponse.parse(imapResponses);
-
-        assertNotNull(result);
-        assertEquals(Collections.emptyList(), result.getNumbers());
-    }
-
-    @Test
-    public void parse_withSingleTooShortResponse_shouldReturnEmptyList() throws Exception {
-        List<ImapResponse> imapResponses = createImapResponseList("* SEARCH");
-
-        SearchResponse result = SearchResponse.parse(imapResponses);
-
-        assertNotNull(result);
-        assertEquals(Collections.emptyList(), result.getNumbers());
-    }
-
-    @Test
-    public void parse_withSingleNoSearchResponse_shouldReturnEmptyList() throws Exception {
-        List<ImapResponse> imapResponses = createImapResponseList("* 23 EXPUNGE");
-
-        SearchResponse result = SearchResponse.parse(imapResponses);
-
-        assertNotNull(result);
-        assertEquals(Collections.emptyList(), result.getNumbers());
-    }
-
-    @Test
-    public void parse_withSingleSearchResponseContainingInvalidNumber_shouldReturnEmptyList() throws Exception {
-        List<ImapResponse> imapResponses = createImapResponseList("* SEARCH A");
-
-        SearchResponse result = SearchResponse.parse(imapResponses);
-
-        assertNotNull(result);
-        assertEquals(Collections.emptyList(), result.getNumbers());
-    }
-}
diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SearchResponseTest.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SearchResponseTest.kt
new file mode 100644
index 00000000000..ac17740f24e
--- /dev/null
+++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SearchResponseTest.kt
@@ -0,0 +1,128 @@
+package com.fsck.k9.mail.store.imap
+
+import assertk.all
+import assertk.assertThat
+import assertk.assertions.containsExactly
+import assertk.assertions.isEmpty
+import assertk.assertions.prop
+import com.fsck.k9.mail.store.imap.SearchResponse.Companion.parse
+import jdk.dynalink.linker.support.Guards.isNotNull
+import kotlin.test.Test
+import org.junit.Assert
+
+class SearchResponseTest {
+    @Test
+    fun parse_withSingleSearchResponse_shouldExtractNumbers() {
+        // Arrange
+        val imapResponses = ImapResponseHelper.createImapResponseList(
+            "* SEARCH 1 2 3",
+            "* 23 EXISTS",
+            "* SEARCH 4",
+            "1 OK SEARCH completed",
+        )
+
+        // Act
+        val result = parse(imapResponses)
+
+        // Assert
+        assertThat(result).all {
+            isNotNull()
+            prop("numbers") { it.numbers }
+                .containsExactly(1L, 2L, 3L, 4L)
+        }
+    }
+
+    @Test
+    fun parse_withMultipleSearchResponses_shouldExtractNumbers() {
+        // Arrange
+        val imapResponses = ImapResponseHelper.createImapResponseList(
+            "* SEARCH 1 2 3",
+            "* 23 EXISTS",
+            "* SEARCH 4",
+            "1 OK SEARCH completed",
+            "* SEARCH 5 6",
+            "* 19 EXPUNGED",
+            "* SEARCH 7",
+            "2 OK SEARCH completed",
+            "* SEARCH 8",
+            "3 OK SEARCH completed",
+        )
+
+        // Act
+        val result = parse(imapResponses)
+
+        // Assert
+        assertThat(result).all {
+            isNotNull()
+            prop("numbers") { it.numbers }
+                .containsExactly(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L)
+        }
+    }
+
+    @Test
+    fun parse_withSingleTaggedSearchResponse_shouldReturnEmptyList() {
+        // Arrange
+        val imapResponses = ImapResponseHelper.createImapResponseList("x SEARCH 7 8 9")
+
+        // Act
+        val result = parse(imapResponses)
+
+        Assert.assertNotNull(result)
+        Assert.assertEquals(emptyList<Any>(), result.numbers)
+
+        // Assert
+        assertThat(result).all {
+            isNotNull()
+            prop("numbers") { it.numbers }
+                .isEmpty()
+        }
+    }
+
+    @Test
+    fun parse_withSingleTooShortResponse_shouldReturnEmptyList() {
+        // Arrange
+        val imapResponses = ImapResponseHelper.createImapResponseList("* SEARCH")
+
+        // Act
+        val result = parse(imapResponses)
+
+        // Assert
+        assertThat(result).all {
+            isNotNull()
+            prop("numbers") { it.numbers }
+                .isEmpty()
+        }
+    }
+
+    @Test
+    fun parse_withSingleNoSearchResponse_shouldReturnEmptyList() {
+        // Arrange
+        val imapResponses = ImapResponseHelper.createImapResponseList("* 23 EXPUNGE")
+
+        // Act
+        val result = parse(imapResponses)
+
+        // Assert
+        assertThat(result).all {
+            isNotNull()
+            prop("numbers") { it.numbers }
+                .isEmpty()
+        }
+    }
+
+    @Test
+    fun parse_withSingleSearchResponseContainingInvalidNumber_shouldReturnEmptyList() {
+        // Arrange
+        val imapResponses = ImapResponseHelper.createImapResponseList("* SEARCH A")
+
+        // Act
+        val result = parse(imapResponses)
+
+        // Assert
+        assertThat(result).all {
+            isNotNull()
+            prop("numbers") { it.numbers }
+                .isEmpty()
+        }
+    }
+}