feat: add provider login flow #4
@@ -1,14 +1,7 @@
|
|||||||
package com.flagship.abox.manager.di
|
package com.flagship.abox.manager.di
|
||||||
|
|
||||||
import com.flagship.abox.manager.domain.DeviceCatalog
|
import com.flagship.abox.manager.domain.DeviceCatalog
|
||||||
import com.flagship.abox.manager.domain.InfraredGateway
|
|
||||||
import com.flagship.abox.manager.domain.SocketGateway
|
|
||||||
import com.flagship.abox.manager.domain.TemperatureHumidityGateway
|
|
||||||
import com.flagship.abox.manager.testing.fakes.FakeDeviceCatalog
|
import com.flagship.abox.manager.testing.fakes.FakeDeviceCatalog
|
||||||
import com.flagship.abox.manager.testing.fakes.FakeInfraredGateway
|
|
||||||
import com.flagship.abox.manager.testing.fakes.FakeSessionGateway
|
|
||||||
import com.flagship.abox.manager.testing.fakes.FakeSocketGateway
|
|
||||||
import com.flagship.abox.manager.testing.fakes.FakeTemperatureHumidityGateway
|
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
@@ -18,24 +11,12 @@ import javax.inject.Singleton
|
|||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
object FoundationModule {
|
object FoundationModule {
|
||||||
@Provides
|
// TEMPORARY boundary: ABSDK has no device catalog, and the provider-scoped production
|
||||||
@Singleton
|
// ABox catalog is owned by the device-catalog module. Until that exists, debug builds
|
||||||
fun provideFakeSessionGateway(): FakeSessionGateway = FakeSessionGateway()
|
// keep the fake ABox catalog here. Do NOT route ABox catalog requests to
|
||||||
|
// SimulationDeviceCatalog; release builds must not request a DeviceCatalog binding
|
||||||
|
// until a real release consumer appears.
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideDeviceCatalog(): DeviceCatalog = FakeDeviceCatalog()
|
fun provideDeviceCatalog(): DeviceCatalog = FakeDeviceCatalog()
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideSocketGateway(): SocketGateway = FakeSocketGateway()
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideTemperatureHumidityGateway(): TemperatureHumidityGateway =
|
|
||||||
FakeTemperatureHumidityGateway()
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideInfraredGateway(): InfraredGateway = FakeInfraredGateway()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.flagship.abox.manager.di
|
||||||
|
|
||||||
|
import com.flagship.abox.manager.absdk.bridge.AbsdkBridge
|
||||||
|
import com.flagship.abox.manager.domain.InfraredGateway
|
||||||
|
import com.flagship.abox.manager.domain.ProviderProfileStore
|
||||||
|
import com.flagship.abox.manager.domain.SocketGateway
|
||||||
|
import com.flagship.abox.manager.domain.TemperatureHumidityGateway
|
||||||
|
import com.flagship.abox.manager.simulation.SimulationInfraredGateway
|
||||||
|
import com.flagship.abox.manager.simulation.SimulationSocketGateway
|
||||||
|
import com.flagship.abox.manager.simulation.SimulationTemperatureHumidityGateway
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds each public device gateway to a router that dispatches on the active provider:
|
||||||
|
* ABox goes to the shared real [AbsdkBridge] and local experience goes to the shared
|
||||||
|
* production simulation gateway.
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
object DeviceGatewayModule {
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSimulationSocketGateway(): SimulationSocketGateway = SimulationSocketGateway()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSimulationTemperatureHumidityGateway(): SimulationTemperatureHumidityGateway =
|
||||||
|
SimulationTemperatureHumidityGateway()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSimulationInfraredGateway(): SimulationInfraredGateway = SimulationInfraredGateway()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSocketGateway(
|
||||||
|
profileStore: ProviderProfileStore,
|
||||||
|
bridge: AbsdkBridge,
|
||||||
|
simulation: SimulationSocketGateway,
|
||||||
|
): SocketGateway = RoutingSocketGateway(profileStore, bridge, simulation)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideTemperatureHumidityGateway(
|
||||||
|
profileStore: ProviderProfileStore,
|
||||||
|
bridge: AbsdkBridge,
|
||||||
|
simulation: SimulationTemperatureHumidityGateway,
|
||||||
|
): TemperatureHumidityGateway =
|
||||||
|
RoutingTemperatureHumidityGateway(profileStore, bridge, simulation)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideInfraredGateway(
|
||||||
|
profileStore: ProviderProfileStore,
|
||||||
|
bridge: AbsdkBridge,
|
||||||
|
simulation: SimulationInfraredGateway,
|
||||||
|
): InfraredGateway = RoutingInfraredGateway(profileStore, bridge, simulation)
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package com.flagship.abox.manager.di
|
||||||
|
|
||||||
|
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.InfraredCode
|
||||||
|
import com.flagship.abox.manager.domain.InfraredCodeDownloadResult
|
||||||
|
import com.flagship.abox.manager.domain.InfraredGateway
|
||||||
|
import com.flagship.abox.manager.domain.ProviderProfileId
|
||||||
|
import com.flagship.abox.manager.domain.ProviderProfileStore
|
||||||
|
import com.flagship.abox.manager.domain.ProviderType
|
||||||
|
import com.flagship.abox.manager.domain.SocketGateway
|
||||||
|
import com.flagship.abox.manager.domain.SocketPowerState
|
||||||
|
import com.flagship.abox.manager.domain.SocketStatus
|
||||||
|
import com.flagship.abox.manager.domain.TemperatureHumidityGateway
|
||||||
|
import com.flagship.abox.manager.domain.TemperatureHumidityReading
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the provider type a device operation must be routed to.
|
||||||
|
*
|
||||||
|
* A device call is only valid for the currently active provider profile: a missing
|
||||||
|
* active profile or a mismatched [profileId] is rejected with [DomainError.InvalidRequest]
|
||||||
|
* before any gateway is invoked.
|
||||||
|
*/
|
||||||
|
internal class ActiveProviderGatewayResolver(
|
||||||
|
private val profileStore: ProviderProfileStore,
|
||||||
|
) {
|
||||||
|
suspend fun resolve(profileId: ProviderProfileId): DomainResult<ProviderType> = try {
|
||||||
|
val active = profileStore.activeProfile()
|
||||||
|
when {
|
||||||
|
active == null ->
|
||||||
|
DomainResult.Failure(DomainError.InvalidRequest(detail = "No active provider"))
|
||||||
|
active.id != profileId ->
|
||||||
|
DomainResult.Failure(
|
||||||
|
DomainError.InvalidRequest(detail = "Provider profile is not active"),
|
||||||
|
)
|
||||||
|
else -> DomainResult.Success(active.type)
|
||||||
|
}
|
||||||
|
} catch (error: CancellationException) {
|
||||||
|
throw error
|
||||||
|
} catch (error: Exception) {
|
||||||
|
DomainResult.Failure(
|
||||||
|
DomainError.Unknown(detail = error.message ?: error::class.java.simpleName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Routes socket operations to the real ABSDK bridge or the simulation gateway by active provider. */
|
||||||
|
internal class RoutingSocketGateway(
|
||||||
|
profileStore: ProviderProfileStore,
|
||||||
|
private val real: SocketGateway,
|
||||||
|
private val simulation: SocketGateway,
|
||||||
|
) : SocketGateway {
|
||||||
|
private val resolver = ActiveProviderGatewayResolver(profileStore)
|
||||||
|
|
||||||
|
override suspend fun getStatus(
|
||||||
|
profileId: ProviderProfileId,
|
||||||
|
deviceName: DeviceName,
|
||||||
|
): DomainResult<SocketStatus> = when (val provider = resolver.resolve(profileId)) {
|
||||||
|
is DomainResult.Failure -> provider
|
||||||
|
is DomainResult.Success -> delegate(provider.value).getStatus(profileId, deviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setPower(
|
||||||
|
profileId: ProviderProfileId,
|
||||||
|
deviceName: DeviceName,
|
||||||
|
powerState: SocketPowerState,
|
||||||
|
): DomainResult<SocketStatus> = when (val provider = resolver.resolve(profileId)) {
|
||||||
|
is DomainResult.Failure -> provider
|
||||||
|
is DomainResult.Success -> delegate(provider.value).setPower(profileId, deviceName, powerState)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun delegate(type: ProviderType): SocketGateway =
|
||||||
|
if (type == ProviderType.ABOX) real else simulation
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Routes temperature/humidity operations by active provider, mirroring [RoutingSocketGateway]. */
|
||||||
|
internal class RoutingTemperatureHumidityGateway(
|
||||||
|
profileStore: ProviderProfileStore,
|
||||||
|
private val real: TemperatureHumidityGateway,
|
||||||
|
private val simulation: TemperatureHumidityGateway,
|
||||||
|
) : TemperatureHumidityGateway {
|
||||||
|
private val resolver = ActiveProviderGatewayResolver(profileStore)
|
||||||
|
|
||||||
|
override suspend fun getReading(
|
||||||
|
profileId: ProviderProfileId,
|
||||||
|
deviceName: DeviceName,
|
||||||
|
): DomainResult<TemperatureHumidityReading> = when (val provider = resolver.resolve(profileId)) {
|
||||||
|
is DomainResult.Failure -> provider
|
||||||
|
is DomainResult.Success -> delegate(provider.value).getReading(profileId, deviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun delegate(type: ProviderType): TemperatureHumidityGateway =
|
||||||
|
if (type == ProviderType.ABOX) real else simulation
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Routes infrared operations by active provider, mirroring [RoutingSocketGateway]. */
|
||||||
|
internal class RoutingInfraredGateway(
|
||||||
|
profileStore: ProviderProfileStore,
|
||||||
|
private val real: InfraredGateway,
|
||||||
|
private val simulation: InfraredGateway,
|
||||||
|
) : InfraredGateway {
|
||||||
|
private val resolver = ActiveProviderGatewayResolver(profileStore)
|
||||||
|
|
||||||
|
override suspend fun sendKey(
|
||||||
|
profileId: ProviderProfileId,
|
||||||
|
deviceName: DeviceName,
|
||||||
|
key: String,
|
||||||
|
): DomainResult<Unit> = when (val provider = resolver.resolve(profileId)) {
|
||||||
|
is DomainResult.Failure -> provider
|
||||||
|
is DomainResult.Success -> delegate(provider.value).sendKey(profileId, deviceName, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun learnKey(
|
||||||
|
profileId: ProviderProfileId,
|
||||||
|
deviceName: DeviceName,
|
||||||
|
key: String,
|
||||||
|
): DomainResult<Unit> = when (val provider = resolver.resolve(profileId)) {
|
||||||
|
is DomainResult.Failure -> provider
|
||||||
|
is DomainResult.Success -> delegate(provider.value).learnKey(profileId, deviceName, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun downloadCodes(
|
||||||
|
profileId: ProviderProfileId,
|
||||||
|
codes: List<InfraredCode>,
|
||||||
|
): DomainResult<List<InfraredCodeDownloadResult>> = when (val provider = resolver.resolve(profileId)) {
|
||||||
|
is DomainResult.Failure -> provider
|
||||||
|
is DomainResult.Success -> delegate(provider.value).downloadCodes(profileId, codes)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun delegate(type: ProviderType): InfraredGateway =
|
||||||
|
if (type == ProviderType.ABOX) real else simulation
|
||||||
|
}
|
||||||
+230
@@ -0,0 +1,230 @@
|
|||||||
|
package com.flagship.abox.manager.di
|
||||||
|
|
||||||
|
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.InfraredCode
|
||||||
|
import com.flagship.abox.manager.domain.InfraredCodeDownloadResult
|
||||||
|
import com.flagship.abox.manager.domain.InfraredGateway
|
||||||
|
import com.flagship.abox.manager.domain.ProviderProfile
|
||||||
|
import com.flagship.abox.manager.domain.ProviderProfileId
|
||||||
|
import com.flagship.abox.manager.domain.ProviderProfileStore
|
||||||
|
import com.flagship.abox.manager.domain.ProviderType
|
||||||
|
import com.flagship.abox.manager.domain.SocketGateway
|
||||||
|
import com.flagship.abox.manager.domain.SocketPowerState
|
||||||
|
import com.flagship.abox.manager.domain.SocketStatus
|
||||||
|
import com.flagship.abox.manager.domain.TemperatureHumidityGateway
|
||||||
|
import com.flagship.abox.manager.domain.TemperatureHumidityReading
|
||||||
|
import com.flagship.abox.manager.testing.fakes.FakeProviderProfileStore
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ProviderRoutingGatewaysTest {
|
||||||
|
@Test
|
||||||
|
fun socketRoutesAboxToRealAndLocalToSimulation() = runTest {
|
||||||
|
val store = FakeProviderProfileStore()
|
||||||
|
val real = RecordingSocketGateway()
|
||||||
|
val simulation = RecordingSocketGateway()
|
||||||
|
val router = RoutingSocketGateway(store, real, simulation)
|
||||||
|
|
||||||
|
val aboxId = activate(store, ProviderType.ABOX)
|
||||||
|
router.getStatus(aboxId, DeviceName("socket-1"))
|
||||||
|
assertEquals(1, real.calls)
|
||||||
|
assertEquals(0, simulation.calls)
|
||||||
|
|
||||||
|
val localId = activate(store, ProviderType.LOCAL_EXPERIENCE)
|
||||||
|
router.getStatus(localId, DeviceName("socket-1"))
|
||||||
|
assertEquals(1, real.calls)
|
||||||
|
assertEquals(1, simulation.calls)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun temperatureHumidityRoutesAboxToRealAndLocalToSimulation() = runTest {
|
||||||
|
val store = FakeProviderProfileStore()
|
||||||
|
val real = RecordingTemperatureHumidityGateway()
|
||||||
|
val simulation = RecordingTemperatureHumidityGateway()
|
||||||
|
val router = RoutingTemperatureHumidityGateway(store, real, simulation)
|
||||||
|
|
||||||
|
val aboxId = activate(store, ProviderType.ABOX)
|
||||||
|
router.getReading(aboxId, DeviceName("th-1"))
|
||||||
|
assertEquals(1, real.calls)
|
||||||
|
assertEquals(0, simulation.calls)
|
||||||
|
|
||||||
|
val localId = activate(store, ProviderType.LOCAL_EXPERIENCE)
|
||||||
|
router.getReading(localId, DeviceName("th-1"))
|
||||||
|
assertEquals(1, real.calls)
|
||||||
|
assertEquals(1, simulation.calls)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun infraredRoutesAboxToRealAndLocalToSimulation() = runTest {
|
||||||
|
val store = FakeProviderProfileStore()
|
||||||
|
val real = RecordingInfraredGateway()
|
||||||
|
val simulation = RecordingInfraredGateway()
|
||||||
|
val router = RoutingInfraredGateway(store, real, simulation)
|
||||||
|
|
||||||
|
val aboxId = activate(store, ProviderType.ABOX)
|
||||||
|
router.sendKey(aboxId, DeviceName("ir-1"), "power")
|
||||||
|
assertEquals(1, real.calls)
|
||||||
|
assertEquals(0, simulation.calls)
|
||||||
|
|
||||||
|
val localId = activate(store, ProviderType.LOCAL_EXPERIENCE)
|
||||||
|
router.sendKey(localId, DeviceName("ir-1"), "power")
|
||||||
|
assertEquals(1, real.calls)
|
||||||
|
assertEquals(1, simulation.calls)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun missingActiveProfileReturnsInvalidRequest() = runTest {
|
||||||
|
val router = RoutingSocketGateway(
|
||||||
|
FakeProviderProfileStore(),
|
||||||
|
RecordingSocketGateway(),
|
||||||
|
RecordingSocketGateway(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = router.getStatus(ProviderProfileId("any"), DeviceName("socket-1"))
|
||||||
|
|
||||||
|
assertTrue(result is DomainResult.Failure)
|
||||||
|
assertTrue((result as DomainResult.Failure).error is DomainError.InvalidRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun mismatchedActiveProfileReturnsInvalidRequest() = runTest {
|
||||||
|
val store = FakeProviderProfileStore()
|
||||||
|
val real = RecordingSocketGateway()
|
||||||
|
val simulation = RecordingSocketGateway()
|
||||||
|
val router = RoutingSocketGateway(store, real, simulation)
|
||||||
|
activate(store, ProviderType.ABOX)
|
||||||
|
|
||||||
|
val result = router.getStatus(ProviderProfileId("other"), DeviceName("socket-1"))
|
||||||
|
|
||||||
|
assertTrue(result is DomainResult.Failure)
|
||||||
|
assertTrue((result as DomainResult.Failure).error is DomainError.InvalidRequest)
|
||||||
|
assertEquals(0, real.calls)
|
||||||
|
assertEquals(0, simulation.calls)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun cancellationFromProfileStoreIsRethrown() = runTest {
|
||||||
|
val router = RoutingSocketGateway(
|
||||||
|
CancellingProviderProfileStore(),
|
||||||
|
RecordingSocketGateway(),
|
||||||
|
RecordingSocketGateway(),
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
router.getStatus(ProviderProfileId("any"), DeviceName("socket-1"))
|
||||||
|
error("Expected CancellationException")
|
||||||
|
} catch (expected: CancellationException) {
|
||||||
|
// Rethrown so coroutine cancellation is never swallowed.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun unexpectedProfileStoreErrorReturnsUnknown() = runTest {
|
||||||
|
val router = RoutingSocketGateway(
|
||||||
|
FailingProviderProfileStore(),
|
||||||
|
RecordingSocketGateway(),
|
||||||
|
RecordingSocketGateway(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = router.getStatus(ProviderProfileId("any"), DeviceName("socket-1"))
|
||||||
|
|
||||||
|
assertTrue(result is DomainResult.Failure)
|
||||||
|
val error = (result as DomainResult.Failure).error
|
||||||
|
assertTrue(error is DomainError.Unknown)
|
||||||
|
assertEquals("storage exploded", error.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun activate(
|
||||||
|
store: FakeProviderProfileStore,
|
||||||
|
type: ProviderType,
|
||||||
|
): ProviderProfileId {
|
||||||
|
val id = ProviderProfileId(type.name.lowercase())
|
||||||
|
store.saveProfile(ProviderProfile(id, type, type.name))
|
||||||
|
store.setActiveProfile(id)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RecordingSocketGateway : SocketGateway {
|
||||||
|
var calls: Int = 0
|
||||||
|
private set
|
||||||
|
|
||||||
|
override suspend fun getStatus(
|
||||||
|
profileId: ProviderProfileId,
|
||||||
|
deviceName: DeviceName,
|
||||||
|
): DomainResult<SocketStatus> {
|
||||||
|
calls += 1
|
||||||
|
return DomainResult.Success(SocketStatus(profileId, deviceName, SocketPowerState.OFF))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setPower(
|
||||||
|
profileId: ProviderProfileId,
|
||||||
|
deviceName: DeviceName,
|
||||||
|
powerState: SocketPowerState,
|
||||||
|
): DomainResult<SocketStatus> {
|
||||||
|
calls += 1
|
||||||
|
return DomainResult.Success(SocketStatus(profileId, deviceName, powerState))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RecordingTemperatureHumidityGateway : TemperatureHumidityGateway {
|
||||||
|
var calls: Int = 0
|
||||||
|
private set
|
||||||
|
|
||||||
|
override suspend fun getReading(
|
||||||
|
profileId: ProviderProfileId,
|
||||||
|
deviceName: DeviceName,
|
||||||
|
): DomainResult<TemperatureHumidityReading> {
|
||||||
|
calls += 1
|
||||||
|
return DomainResult.Success(TemperatureHumidityReading(profileId, deviceName, "0", "0"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RecordingInfraredGateway : InfraredGateway {
|
||||||
|
var calls: Int = 0
|
||||||
|
private set
|
||||||
|
|
||||||
|
override suspend fun sendKey(
|
||||||
|
profileId: ProviderProfileId,
|
||||||
|
deviceName: DeviceName,
|
||||||
|
key: String,
|
||||||
|
): DomainResult<Unit> {
|
||||||
|
calls += 1
|
||||||
|
return DomainResult.Success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun learnKey(
|
||||||
|
profileId: ProviderProfileId,
|
||||||
|
deviceName: DeviceName,
|
||||||
|
key: String,
|
||||||
|
): DomainResult<Unit> {
|
||||||
|
calls += 1
|
||||||
|
return DomainResult.Success(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun downloadCodes(
|
||||||
|
profileId: ProviderProfileId,
|
||||||
|
codes: List<InfraredCode>,
|
||||||
|
): DomainResult<List<InfraredCodeDownloadResult>> {
|
||||||
|
calls += 1
|
||||||
|
return DomainResult.Success(codes.map { InfraredCodeDownloadResult(it, successful = true) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CancellingProviderProfileStore(
|
||||||
|
private val delegate: ProviderProfileStore = FakeProviderProfileStore(),
|
||||||
|
) : ProviderProfileStore by delegate {
|
||||||
|
override suspend fun activeProfile(): Nothing = throw CancellationException("cancelled")
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FailingProviderProfileStore(
|
||||||
|
private val delegate: ProviderProfileStore = FakeProviderProfileStore(),
|
||||||
|
) : ProviderProfileStore by delegate {
|
||||||
|
override suspend fun activeProfile(): Nothing =
|
||||||
|
throw IllegalStateException("storage exploded")
|
||||||
|
}
|
||||||
-31
@@ -1,31 +0,0 @@
|
|||||||
package com.flagship.abox.manager.session.gateway
|
|
||||||
|
|
||||||
import com.flagship.abox.manager.domain.DomainError
|
|
||||||
import com.flagship.abox.manager.domain.DomainResult
|
|
||||||
import com.flagship.abox.manager.domain.LoginCredentials
|
|
||||||
import com.flagship.abox.manager.domain.ProviderProfileId
|
|
||||||
import com.flagship.abox.manager.domain.Session
|
|
||||||
import com.flagship.abox.manager.domain.SessionGateway
|
|
||||||
import com.flagship.abox.manager.domain.SessionValidity
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.flowOf
|
|
||||||
|
|
||||||
class UnavailableRemoteSessionGateway : SessionGateway {
|
|
||||||
override fun observeSession(profileId: ProviderProfileId): Flow<Session?> = flowOf(null)
|
|
||||||
|
|
||||||
override suspend fun login(
|
|
||||||
profileId: ProviderProfileId,
|
|
||||||
credentials: LoginCredentials,
|
|
||||||
): DomainResult<Session> = unavailable()
|
|
||||||
|
|
||||||
override suspend fun validateSession(
|
|
||||||
profileId: ProviderProfileId,
|
|
||||||
): DomainResult<SessionValidity> = unavailable()
|
|
||||||
|
|
||||||
override suspend fun logout(profileId: ProviderProfileId): DomainResult<Unit> =
|
|
||||||
DomainResult.Success(Unit)
|
|
||||||
|
|
||||||
private fun <T> unavailable(): DomainResult<T> = DomainResult.Failure(
|
|
||||||
DomainError.InvalidRequest(detail = "Remote providers are not enabled in this build"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user