fix(infrared): unify atomic config state snapshot, avoid message race, and rollback cancelled learning status

This commit is contained in:
2026-08-25 12:36:19 +08:00
parent 3561f3dd4a
commit 8cd44c93e1
4 changed files with 117 additions and 56 deletions
@@ -5,6 +5,9 @@ import com.flagship.abox.manager.domain.DomainError
import com.flagship.abox.manager.domain.DomainResult
import com.flagship.abox.manager.domain.InfraredGateway
import com.flagship.abox.manager.domain.ProviderProfileId
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.withContext
class LearnInfraredKey(
private val repository: InfraredKeyRepository,
@@ -28,13 +31,27 @@ class LearnInfraredKey(
is DomainResult.Failure -> return result
}
return when (
val result = gateway.learnKey(
val result = try {
gateway.learnKey(
profileId = key.profileId,
deviceName = key.deviceName,
key = key.sdkKey,
)
) {
} catch (e: CancellationException) {
withContext(NonCancellable) {
repository.transitionStatus(
ref = key.ref(),
expectedStatuses = setOf(InfraredKeyStatus.LEARNING),
targetStatus = InfraredKeyStatus.FAILED,
lastErrorCode = null,
lastErrorDetail = "Cancelled",
updatedAtEpochMillis = clock.now(),
)
}
throw e
}
return when (result) {
is DomainResult.Success -> when (
val transition = repository.transitionStatus(
ref = key.ref(),
@@ -101,13 +118,27 @@ class RetryInfraredKeyLearning(
is DomainResult.Failure -> return result
}
return when (
val result = gateway.learnKey(
val result = try {
gateway.learnKey(
profileId = key.profileId,
deviceName = key.deviceName,
key = key.sdkKey,
)
) {
} catch (e: CancellationException) {
withContext(NonCancellable) {
repository.transitionStatus(
ref = ref,
expectedStatuses = setOf(InfraredKeyStatus.LEARNING),
targetStatus = InfraredKeyStatus.FAILED,
lastErrorCode = null,
lastErrorDetail = "Cancelled",
updatedAtEpochMillis = clock.now(),
)
}
throw e
}
return when (result) {
is DomainResult.Success -> repository.transitionStatus(
ref = ref,
expectedStatuses = setOf(InfraredKeyStatus.LEARNING),
@@ -269,6 +269,22 @@ class LearnInfraredKeyTest {
assertEquals(1, gateway.learnedKeys.size)
}
@Test
fun learningCancellationTransitionsStatusToFailed() = runTest {
gateway.onLearn = { _, _, _ ->
throw kotlinx.coroutines.CancellationException("User cancelled")
}
try {
learn(profile, device, "Power")
} catch (_: kotlinx.coroutines.CancellationException) {
// expected
}
assertEquals(InfraredKeyStatus.FAILED, repository.onlyKey().status)
assertEquals("Cancelled", repository.onlyKey().lastErrorDetail)
}
private fun failedKey(
id: InfraredKeyId = keyId,
sdkKey: String = "app_ir_key1",