feat: define infrared key contracts

This commit is contained in:
2026-08-25 11:40:22 +08:00
committed by KremeCN
parent cd564cbfa1
commit f1f00701d4
4 changed files with 130 additions and 0 deletions
@@ -0,0 +1,46 @@
package com.flagship.abox.manager.domain.infrared
import com.flagship.abox.manager.domain.DeviceName
import com.flagship.abox.manager.domain.DomainError
import com.flagship.abox.manager.domain.DomainResult
import com.flagship.abox.manager.domain.ProviderProfileId
fun interface InfraredKeyIdGenerator { fun nextId(): InfraredKeyId }
fun interface EpochMillisClock { fun now(): Long }
class InfraredKeyFactory(
private val idGenerator: InfraredKeyIdGenerator,
private val clock: EpochMillisClock,
) {
fun create(
profileId: ProviderProfileId,
deviceName: DeviceName,
displayName: String,
): DomainResult<SavedInfraredKey> {
val normalizedName = displayName.trim()
if (profileId.value.isBlank() || deviceName.value.isBlank() || normalizedName.isBlank()) {
return DomainResult.Failure(
DomainError.InvalidRequest(detail = "Profile, device name, and display name are required"),
)
}
val id = idGenerator.nextId()
val normalizedId = id.value.lowercase().filter { it in 'a'..'z' || it in '0'..'9' }
if (normalizedId.isBlank()) {
return DomainResult.Failure(DomainError.InvalidRequest(detail = "Infrared key id is invalid"))
}
val now = clock.now()
return DomainResult.Success(
SavedInfraredKey(
id = id,
profileId = profileId,
deviceName = deviceName,
displayName = normalizedName,
sdkKey = "app_ir_${normalizedId.take(40)}",
status = InfraredKeyStatus.LEARNING,
lastErrorCode = null,
createdAtEpochMillis = now,
updatedAtEpochMillis = now,
),
)
}
}
@@ -0,0 +1,26 @@
package com.flagship.abox.manager.domain.infrared
import com.flagship.abox.manager.domain.DeviceName
import com.flagship.abox.manager.domain.ProviderProfileId
@JvmInline
value class InfraredKeyId(val value: String)
data class InfraredKeyRef(
val profileId: ProviderProfileId,
val keyId: InfraredKeyId,
)
enum class InfraredKeyStatus { LEARNING, READY, FAILED }
data class SavedInfraredKey(
val id: InfraredKeyId,
val profileId: ProviderProfileId,
val deviceName: DeviceName,
val displayName: String,
val sdkKey: String,
val status: InfraredKeyStatus,
val lastErrorCode: String?,
val createdAtEpochMillis: Long,
val updatedAtEpochMillis: Long,
)
@@ -0,0 +1,20 @@
package com.flagship.abox.manager.domain.infrared
import com.flagship.abox.manager.domain.DeviceName
import com.flagship.abox.manager.domain.DomainResult
import com.flagship.abox.manager.domain.ProviderProfileId
import kotlinx.coroutines.flow.Flow
interface InfraredKeyRepository {
fun observeKeys(profileId: ProviderProfileId, deviceName: DeviceName): Flow<DomainResult<List<SavedInfraredKey>>>
suspend fun getKey(ref: InfraredKeyRef): DomainResult<SavedInfraredKey?>
suspend fun insert(key: SavedInfraredKey): DomainResult<Unit>
suspend fun update(key: SavedInfraredKey): DomainResult<Unit>
suspend fun delete(ref: InfraredKeyRef): DomainResult<Unit>
suspend fun findByStatus(profileId: ProviderProfileId, status: InfraredKeyStatus): DomainResult<List<SavedInfraredKey>>
}
interface InfraredDeviceConfigurationRepository {
fun observeDeviceName(profileId: ProviderProfileId): Flow<DomainResult<DeviceName?>>
suspend fun setDeviceName(profileId: ProviderProfileId, deviceName: DeviceName): DomainResult<Unit>
}
@@ -0,0 +1,38 @@
package com.flagship.abox.manager.domain.infrared
import com.flagship.abox.manager.domain.DeviceName
import com.flagship.abox.manager.domain.DomainResult
import com.flagship.abox.manager.domain.ProviderProfileId
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class InfraredKeyFactoryTest {
private val id = InfraredKeyId("01J5MOLERABC123")
private val factory = InfraredKeyFactory(
idGenerator = InfraredKeyIdGenerator { id },
clock = EpochMillisClock { 1_724_435_200_000L },
)
@Test
fun createsLearningKeyWithStableAsciiSdkKey() {
val result = factory.create(
ProviderProfileId("abox"),
DeviceName("ir-1"),
"空调开关",
) as DomainResult.Success
assertEquals(id, result.value.id)
assertEquals("app_ir_01j5molerabc123", result.value.sdkKey)
assertEquals(InfraredKeyStatus.LEARNING, result.value.status)
assertEquals("空调开关", result.value.displayName)
assertEquals(1_724_435_200_000L, result.value.createdAtEpochMillis)
assertEquals(result.value.createdAtEpochMillis, result.value.updatedAtEpochMillis)
}
@Test
fun rejectsBlankDeviceOrDisplayName() {
assertTrue(factory.create(ProviderProfileId("abox"), DeviceName(""), "Power") is DomainResult.Failure)
assertTrue(factory.create(ProviderProfileId("abox"), DeviceName("ir-1"), " ") is DomainResult.Failure)
}
}