fix: reject invalid infrared batch responses

This commit is contained in:
2026-08-23 07:13:15 +08:00
parent 0e9b7dfd4b
commit 13fd18a9f3
2 changed files with 36 additions and 3 deletions
@@ -249,12 +249,19 @@ class AbsdkBridge internal constructor(
codes: List<InfraredCode>, codes: List<InfraredCode>,
response: AbsdkResponse?, response: AbsdkResponse?,
): DomainResult<List<InfraredCodeDownloadResult>> { ): DomainResult<List<InfraredCodeDownloadResult>> {
val results = response?.data?.get(IR_RESULTS_KEY) as? List<*> val validResponse = response?.takeIf { it.code in IR_DOWNLOAD_RESULT_CODES }
?: return DomainResult.Failure(AbsdkErrorMapper.fromResponse(response)) ?: return DomainResult.Failure(AbsdkErrorMapper.fromResponse(response))
val results = validResponse.data[IR_RESULTS_KEY] as? List<*>
?: return DomainResult.Failure(
DomainError.OperationFailed(
externalCode = validResponse.code,
detail = "Missing infrared download results",
),
)
if (results.size != codes.size || results.any { it !is AbsdkIrResult }) { if (results.size != codes.size || results.any { it !is AbsdkIrResult }) {
return DomainResult.Failure( return DomainResult.Failure(
DomainError.OperationFailed( DomainError.OperationFailed(
externalCode = response.code, externalCode = validResponse.code,
detail = "Malformed infrared download result", detail = "Malformed infrared download result",
), ),
) )
@@ -267,7 +274,7 @@ class AbsdkBridge internal constructor(
) { ) {
return DomainResult.Failure( return DomainResult.Failure(
DomainError.OperationFailed( DomainError.OperationFailed(
externalCode = response.code, externalCode = validResponse.code,
detail = "Infrared download results do not match their requests", detail = "Infrared download results do not match their requests",
), ),
) )
@@ -318,6 +325,8 @@ class AbsdkBridge internal constructor(
} }
private const val SUCCESS_CODE = "00000" private const val SUCCESS_CODE = "00000"
private const val PARTIAL_IR_DOWNLOAD_CODE = "20204"
private val IR_DOWNLOAD_RESULT_CODES = setOf(SUCCESS_CODE, PARTIAL_IR_DOWNLOAD_CODE)
private sealed interface ClientCall { private sealed interface ClientCall {
data class Completed(val response: AbsdkResponse?) : ClientCall data class Completed(val response: AbsdkResponse?) : ClientCall
@@ -11,6 +11,7 @@ import com.flagship.abox.manager.domain.SocketPowerState
import com.flagship.abox.manager.domain.Username import com.flagship.abox.manager.domain.Username
import java.io.IOException import java.io.IOException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
@@ -114,6 +115,29 @@ class AbsdkDeviceBridgeTest {
) )
} }
@Test
fun infraredTokenErrorCannotBeMaskedByValidResults() = runTest {
val code = InfraredCode(DeviceName("ir-1"), "power", "code-1")
val client = ScriptedAbsdkClient().apply {
downloadResponse = AbsdkResponse(
code = "10002",
message = "expired",
data = mapOf(
"ret" to listOf(AbsdkIrResult("ir-1", "power", "1")),
),
)
}
val bridge = authenticatedBridge(client)
val result = bridge.downloadCodes(profile, listOf(code))
assertTrue((result as DomainResult.Failure).error is DomainError.TokenInvalid)
assertEquals(null, bridge.observeSession(profile).first())
val second = bridge.sendKey(profile, DeviceName("ir-1"), "power")
assertTrue((second as DomainResult.Failure).error is DomainError.TokenInvalid)
assertEquals(listOf("login:demo", "ir-download"), client.calls)
}
@Test @Test
fun infraredMalformedResultsFailTheBatch() = runTest { fun infraredMalformedResultsFailTheBatch() = runTest {
val code = InfraredCode(DeviceName("ir-1"), "power", "code-1") val code = InfraredCode(DeviceName("ir-1"), "power", "code-1")