fix(kanban): address PR correctness and CI regressions (#1137)
This commit is contained in:
1 parent
190a93a4cf
commit
1735de7d9d
25 files changed
+809
-159
No files matched your search
@@ -289,4 +289,29 @@ class KanbanSerializationTest {
|
||||
assertEquals("M", estimate.complexity)
|
||||
assertEquals("Multi-file changes needed", estimate.rationale)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBulkAssignmentUsesExplicitEmptyStringAndReclaim() {
|
||||
val assignJson =
|
||||
json.encodeToString(
|
||||
BulkTasksBody(
|
||||
ids = listOf("t_1"),
|
||||
assignee = "researcher",
|
||||
reclaimFirst = true,
|
||||
),
|
||||
)
|
||||
val unassignJson =
|
||||
json.encodeToString(
|
||||
BulkTasksBody(
|
||||
ids = listOf("t_1"),
|
||||
assignee = "",
|
||||
reclaimFirst = true,
|
||||
),
|
||||
)
|
||||
|
||||
assertTrue(assignJson.contains("\"assignee\":\"researcher\""))
|
||||
assertTrue(assignJson.contains("\"reclaim_first\":true"))
|
||||
assertTrue(unassignJson.contains("\"assignee\":\"\""))
|
||||
assertTrue(unassignJson.contains("\"reclaim_first\":true"))
|
||||
}
|
||||
}
|
||||
@@ -63,4 +63,20 @@ class KanbanModelOverrideTest {
|
||||
assertEquals("low", patch.reasoningEffort)
|
||||
assertFalse(patch.clearReasoningEffort)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFullReasoningScaleAndCapabilities() {
|
||||
assertEquals(
|
||||
listOf("none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"),
|
||||
supportedKanbanReasoningEfforts(supportsReasoning = true, canDisableReasoning = true),
|
||||
)
|
||||
assertEquals(
|
||||
listOf("minimal", "low", "medium", "high", "xhigh", "max", "ultra"),
|
||||
supportedKanbanReasoningEfforts(supportsReasoning = true, canDisableReasoning = false),
|
||||
)
|
||||
assertEquals(
|
||||
listOf(""),
|
||||
supportedKanbanReasoningEfforts(supportsReasoning = false, canDisableReasoning = false),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -374,6 +374,40 @@ class KanbanMutationTest {
|
||||
assertEquals("Board created: Sprint 2", vm.uiState.value.toastMessage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBulkAssignUsesReclaimAndRetainsOnlyFailures() =
|
||||
runTest(testDispatcher) {
|
||||
val board = KanbanBoard(id = "dev", name = "Dev")
|
||||
coEvery { mockRepository.getBoard("dev") } returns
|
||||
NetworkResult.Success(KanbanBoardResponse(columns = listOf(KanbanColumn("todo", emptyList()))))
|
||||
coEvery { mockRepository.bulkTasks("dev", any()) } returns
|
||||
NetworkResult.Success(
|
||||
BulkTasksResponse(
|
||||
results =
|
||||
listOf(
|
||||
BulkTaskResult("t_1", ok = true),
|
||||
BulkTaskResult("t_2", ok = false, error = "locked"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val vm = createViewModel()
|
||||
vm.selectBoard(board)
|
||||
testDispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
var failedIds = emptySet<String>()
|
||||
vm.bulkAssign(listOf("t_1", "t_2"), assignee = null) { failedIds = it }
|
||||
testDispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
coVerify {
|
||||
mockRepository.bulkTasks(
|
||||
"dev",
|
||||
match { it.ids == listOf("t_1", "t_2") && it.assignee == "" && it.reclaimFirst },
|
||||
)
|
||||
}
|
||||
assertEquals(setOf("t_2"), failedIds)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRenameBoardSuccess() =
|
||||
runTest(testDispatcher) {
|
||||
@@ -408,11 +442,13 @@ class KanbanMutationTest {
|
||||
)
|
||||
|
||||
val vm = createViewModel()
|
||||
preferences.setSelectedBoard("http://127.0.0.1:9119", "old-board")
|
||||
vm.deleteBoard("old-board")
|
||||
testDispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
coVerify { mockRepository.deleteBoard("old-board", delete = false) }
|
||||
assertEquals("Board archived", vm.uiState.value.toastMessage)
|
||||
assertEquals(null, preferences.getSelectedBoard("http://127.0.0.1:9119"))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -15,6 +15,7 @@ import io.mockk.mockk
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.test.setMain
|
||||
@@ -31,10 +32,11 @@ class KanbanTaskViewModelTest {
|
||||
private val mockRepository = mockk<KanbanRepository>(relaxed = true)
|
||||
private val mockEventsClient = mockk<KanbanEventsClient>(relaxed = true)
|
||||
|
||||
private fun createViewModel(): KanbanTaskViewModel =
|
||||
private fun createViewModel(enableFallbackPolling: Boolean = false): KanbanTaskViewModel =
|
||||
KanbanTaskViewModel(
|
||||
repository = mockRepository,
|
||||
eventsClientProvider = { mockEventsClient },
|
||||
enableFallbackPolling = enableFallbackPolling,
|
||||
)
|
||||
|
||||
@Before
|
||||
@@ -285,4 +287,38 @@ class KanbanTaskViewModelTest {
|
||||
assertTrue(deleted)
|
||||
assertEquals("Task deleted", vm.uiState.value.toastMessage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFallbackPollingReloadsTaskWithoutEvents() =
|
||||
runTest(testDispatcher) {
|
||||
val first = KanbanTaskFull(id = "t_1", title = "Before", status = "todo")
|
||||
val second = KanbanTaskFull(id = "t_1", title = "After", status = "todo")
|
||||
coEvery { mockRepository.getTask("t_1", "dev") } returnsMany
|
||||
listOf(
|
||||
NetworkResult.Success(KanbanTaskDetailResponse(task = first)),
|
||||
NetworkResult.Success(KanbanTaskDetailResponse(task = second)),
|
||||
)
|
||||
|
||||
val vm = createViewModel(enableFallbackPolling = true)
|
||||
vm.loadTask("dev", "t_1")
|
||||
testDispatcher.scheduler.runCurrent()
|
||||
assertEquals(
|
||||
"Before",
|
||||
vm.uiState.value.detail
|
||||
?.task
|
||||
?.title,
|
||||
)
|
||||
|
||||
testDispatcher.scheduler.advanceTimeBy(30_000L)
|
||||
testDispatcher.scheduler.runCurrent()
|
||||
|
||||
assertEquals(
|
||||
"After",
|
||||
vm.uiState.value.detail
|
||||
?.task
|
||||
?.title,
|
||||
)
|
||||
coVerify(exactly = 2) { mockRepository.getTask("t_1", "dev") }
|
||||
vm.onCleared()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.m57.hermescontrol.util
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okio.Buffer
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
|
||||
class StreamingUriRequestBodyTest {
|
||||
@Test
|
||||
fun testStreamsDirectlyIntoSink() {
|
||||
val testData = "test archive data streaming content".toByteArray(Charsets.UTF_8)
|
||||
val mockResolver = mockk<ContentResolver>()
|
||||
val mockUri = mockk<Uri>()
|
||||
|
||||
every { mockResolver.openInputStream(mockUri) } answers {
|
||||
ByteArrayInputStream(testData)
|
||||
}
|
||||
|
||||
val mediaType = "application/gzip".toMediaTypeOrNull()
|
||||
val requestBody =
|
||||
StreamingUriRequestBody(
|
||||
contentResolver = mockResolver,
|
||||
uri = mockUri,
|
||||
contentType = mediaType,
|
||||
contentLength = testData.size.toLong(),
|
||||
)
|
||||
|
||||
assertEquals(mediaType, requestBody.contentType())
|
||||
assertEquals(testData.size.toLong(), requestBody.contentLength())
|
||||
|
||||
val buffer = Buffer()
|
||||
requestBody.writeTo(buffer)
|
||||
|
||||
assertEquals("test archive data streaming content", buffer.readUtf8())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testThrowsWhenInputStreamCannotBeOpened() {
|
||||
val mockResolver = mockk<ContentResolver>()
|
||||
val mockUri = mockk<Uri>()
|
||||
|
||||
every { mockResolver.openInputStream(mockUri) } returns null
|
||||
|
||||
val requestBody =
|
||||
StreamingUriRequestBody(
|
||||
contentResolver = mockResolver,
|
||||
uri = mockUri,
|
||||
contentType = null,
|
||||
)
|
||||
|
||||
var thrown = false
|
||||
try {
|
||||
val buffer = Buffer()
|
||||
requestBody.writeTo(buffer)
|
||||
} catch (e: IOException) {
|
||||
thrown = true
|
||||
}
|
||||
|
||||
assertTrue(thrown)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user