diff --git a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt index 8258d2b1f3..48f47f4c45 100644 --- a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt +++ b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/FingerprintCaptureViewModel.kt @@ -239,7 +239,11 @@ internal class FingerprintCaptureViewModel @Inject constructor( liveFeedbackState = LiveFeedbackState.START stopLiveFeedbackTask?.cancel() liveFeedbackTask = viewModelScope.launch { - scannerManager.scanner.startLiveFeedback() + try { + scannerManager.scanner.startLiveFeedback() + } catch (e: Throwable) { + handleScannerCommunicationsError(e) + } } } } @@ -354,7 +358,7 @@ internal class FingerprintCaptureViewModel @Inject constructor( ) handleCaptureSuccess(capturedFingerprint) - } catch (ex: CancellationException) { + } catch (_: CancellationException) { // ignore cancellation exception, but log behaviour Simber.d("Fingerprint scanning was cancelled") } catch (ex: Throwable) { diff --git a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/ObserveFingerprintScanStatusUseCase.kt b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/ObserveFingerprintScanStatusUseCase.kt index 20e4b55a11..3708b57fba 100644 --- a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/ObserveFingerprintScanStatusUseCase.kt +++ b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/ObserveFingerprintScanStatusUseCase.kt @@ -1,6 +1,7 @@ package com.simprints.fingerprint.capture.screen import android.content.Context +import androidx.preference.PreferenceManager import com.simprints.fingerprint.infra.scanner.ScannerManager import com.simprints.fingerprint.infra.scanner.capture.FingerprintScanState import com.simprints.fingerprint.infra.scanner.capture.FingerprintScanningStatusTracker @@ -9,14 +10,17 @@ import com.simprints.infra.config.store.models.Vero2Configuration import com.simprints.infra.config.store.models.Vero2Configuration.LedsMode.BASIC import com.simprints.infra.config.store.models.Vero2Configuration.LedsMode.VISUAL_SCAN_FEEDBACK import com.simprints.infra.config.sync.ConfigManager +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.filterNot +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Singleton -import androidx.preference.PreferenceManager -import dagger.hilt.android.qualifiers.ApplicationContext @Singleton class ObserveFingerprintScanStatusUseCase @Inject constructor( @@ -39,14 +43,23 @@ class ObserveFingerprintScanStatusUseCase @Inject constructor( ledsMode = configManager.getProjectConfiguration().fingerprint?.getSdkConfiguration( fingerprintSdk )?.vero2?.ledsMode - statusTracker.state.collect { state -> - provideFeedback(state) - previousState = state - } + + statusTracker.state + .onEach { state -> + if (state is FingerprintScanState.ScanCompleted) { + provideFeedback(state)// Handle ScanCompleted immediately to let the user know to remove the finger + } + } + .filterNot { it is FingerprintScanState.ScanCompleted } // Exclude ScanCompleted + .flatMapConcat { state -> + flow { emit(provideFeedback(state)) } // Process other states sequentially + } + .collect { + // Do nothing + } } } - private suspend fun provideFeedback(state: FingerprintScanState) { when (state) { is FingerprintScanState.Idle -> turnOnFlashingWhiteSmileLeds() @@ -55,6 +68,7 @@ class ObserveFingerprintScanStatusUseCase @Inject constructor( is FingerprintScanState.ImageQualityChecking.Good -> setUiAfterScan(true) is FingerprintScanState.ImageQualityChecking.Bad -> setUiAfterScan(false) } + previousState = state } fun stopObserving() { diff --git a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/PlayAudioBeepUseCase.kt b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/PlayAudioBeepUseCase.kt index 92f505863c..109a86cf38 100644 --- a/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/PlayAudioBeepUseCase.kt +++ b/fingerprint/capture/src/main/java/com/simprints/fingerprint/capture/screen/PlayAudioBeepUseCase.kt @@ -3,6 +3,7 @@ package com.simprints.fingerprint.capture.screen import android.content.Context import android.media.MediaPlayer import com.simprints.fingerprint.capture.R +import com.simprints.infra.logging.Simber import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject @@ -14,6 +15,7 @@ class PlayAudioBeepUseCase @Inject constructor( if (mediaPlayer == null) { mediaPlayer = MediaPlayer.create(context, R.raw.beep) } + Simber.d("Playing beep sound") mediaPlayer?.start() } diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperFactory.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperFactory.kt index 7168baddbf..9793e36050 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperFactory.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperFactory.kt @@ -2,7 +2,6 @@ package com.simprints.fingerprint.infra.scanner.capture import com.simprints.core.DispatcherIO import com.simprints.fingerprint.infra.scanner.exceptions.unexpected.NullScannerException -import com.simprints.fingerprint.infra.scanner.v2.tools.ScannerUiHelper import kotlinx.coroutines.CoroutineDispatcher import javax.inject.Inject import javax.inject.Singleton @@ -12,7 +11,6 @@ import com.simprints.fingerprint.infra.scanner.v2.scanner.Scanner as ScannerV2 @Singleton class FingerprintCaptureWrapperFactory @Inject constructor( @DispatcherIO private val ioDispatcher: CoroutineDispatcher, - private val scannerUiHelper: ScannerUiHelper, private val scanningStatusTracker: FingerprintScanningStatusTracker ) { private var _captureWrapper: FingerprintCaptureWrapper? = null @@ -21,13 +19,10 @@ class FingerprintCaptureWrapperFactory @Inject constructor( get() = _captureWrapper ?: throw NullScannerException() fun createV1(scannerV1: ScannerV1) { - _captureWrapper = - FingerprintCaptureWrapperV1(scannerV1, ioDispatcher) + _captureWrapper = FingerprintCaptureWrapperV1(scannerV1, ioDispatcher) } fun createV2(scannerV2: ScannerV2) { - _captureWrapper = FingerprintCaptureWrapperV2( - scannerV2, scannerUiHelper, ioDispatcher, scanningStatusTracker - ) + _captureWrapper = FingerprintCaptureWrapperV2(scannerV2, scanningStatusTracker) } } diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperV2.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperV2.kt index bfbf53fbd6..4315098ad0 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperV2.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperV2.kt @@ -12,150 +12,94 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.model import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.models.Dpi import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.models.ImageFormatData import com.simprints.fingerprint.infra.scanner.v2.scanner.Scanner -import com.simprints.fingerprint.infra.scanner.v2.tools.ScannerUiHelper -import com.simprints.fingerprint.infra.scanner.v2.tools.wrapErrorFromScanner -import io.reactivex.Completable -import io.reactivex.Single -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.rx2.await -import kotlinx.coroutines.withContext +import com.simprints.fingerprint.infra.scanner.v2.tools.runWithErrorWrapping internal class FingerprintCaptureWrapperV2( private val scannerV2: Scanner, - private val scannerUiHelper: ScannerUiHelper, - private val ioDispatcher: CoroutineDispatcher, private val tracker: FingerprintScanningStatusTracker, ) : FingerprintCaptureWrapper { - override suspend fun acquireImageDistortionMatrixConfiguration(): ByteArray = - withContext(ioDispatcher) { - scannerV2.acquireImageDistortionConfigurationMatrix() - .switchIfEmpty(Single.error(NoImageDistortionConfigurationMatrixException())) - .wrapErrorsFromScanner().await() - } + override suspend fun acquireImageDistortionMatrixConfiguration(): ByteArray = runWithErrorWrapping { + scannerV2.acquireImageDistortionConfigurationMatrix() + ?: throw NoImageDistortionConfigurationMatrixException("Failed to acquire image distortion configuration matrix") + } override suspend fun acquireFingerprintImage(): AcquireFingerprintImageResponse = - withContext(ioDispatcher) { - scannerV2.acquireImage(IMAGE_FORMAT).map { imageBytes -> - AcquireFingerprintImageResponse(imageBytes.image) - } - .switchIfEmpty(Single.error(NoFingerDetectedException("Failed to acquire image"))) - .wrapErrorsFromScanner() - .await() + runWithErrorWrapping { + scannerV2.acquireImage(IMAGE_FORMAT)?.image?.let { imageBytes -> + AcquireFingerprintImageResponse(imageBytes) + } ?: throw NoFingerDetectedException("Failed to acquire image") } override suspend fun acquireUnprocessedImage( captureDpi: Dpi?, - ): AcquireUnprocessedImageResponse = - - withContext(ioDispatcher) { - require(captureDpi != null && (captureDpi.value in MIN_CAPTURE_DPI..MAX_CAPTURE_DPI)) { - "Capture DPI must be between $MIN_CAPTURE_DPI and $MAX_CAPTURE_DPI" - } - // Capture fingerprint and ensure it's OK - scannerV2.captureFingerprint() - .ensureCaptureResultOkOrError() - .andThen(Completable.fromAction { - tracker.completeScan() - }).andThen(acquireUnprocessedImage()) - .switchIfEmpty(Single.error(NoFingerDetectedException("Failed to acquire unprocessed image data"))) - .wrapErrorsFromScanner().await() - + ): AcquireUnprocessedImageResponse = runWithErrorWrapping { + require(captureDpi != null && (captureDpi.value in MIN_CAPTURE_DPI..MAX_CAPTURE_DPI)) { + "Capture DPI must be between $MIN_CAPTURE_DPI and $MAX_CAPTURE_DPI" } - - - private fun acquireUnprocessedImage() = - scannerV2.acquireUnprocessedImage(IMAGE_FORMAT) - .map { imageData -> - AcquireUnprocessedImageResponse( - RawUnprocessedImage( - imageData.image - ) + // Capture fingerprint and ensure it's OK + scannerV2.captureFingerprint().ensureCaptureResultOkOrError() + tracker.completeScan() + // Transfer the unprocessed image from the scanner + scannerV2.acquireUnprocessedImage(IMAGE_FORMAT)?.image?.let { imageData -> + AcquireUnprocessedImageResponse( + RawUnprocessedImage( + imageData ) - } + ) + } ?: throw NoFingerDetectedException("Failed to acquire unprocessed image data") + } override suspend fun acquireFingerprintTemplate( captureDpi: Dpi?, timeOutMs: Int, qualityThreshold: Int, allowLowQualityExtraction: Boolean - ): AcquireFingerprintTemplateResponse = withContext(ioDispatcher) { + ): AcquireFingerprintTemplateResponse = runWithErrorWrapping { require(captureDpi != null && (captureDpi.value in MIN_CAPTURE_DPI..MAX_CAPTURE_DPI)) { "Capture DPI must be between $MIN_CAPTURE_DPI and $MAX_CAPTURE_DPI" } - scannerV2 - .captureFingerprint(captureDpi) - .ensureCaptureResultOkOrError() - .andThen(Completable.fromAction { - tracker.completeScan() - }) - .andThen(scannerV2.getImageQualityScore()) - .switchIfEmpty(Single.error(NoFingerDetectedException("Failed to acquire image quality score"))) - .validateMinimumFingerImageQuality() - .flatMap { qualityScore -> - // If the quality score is below the threshold and we don't allow low quality extraction, return an empty template - if (qualityScore < qualityThreshold && !allowLowQualityExtraction) { - Single.just(AcquireFingerprintTemplateResponse(ByteArray(0), templateFormat, qualityScore)) - } else { - Single.just(qualityScore) - .acquireTemplateAndAssembleResponse() - .switchIfEmpty(Single.error(NoFingerDetectedException("Failed to acquire template"))) - .ifNoFingerDetectedThenSetBadScanLedState() - .wrapErrorsFromScanner() - } - }.wrapErrorsFromScanner().await() + scannerV2.captureFingerprint(captureDpi).ensureCaptureResultOkOrError() + tracker.completeScan() + val qualityScore = scannerV2.getImageQualityScore() + ?: throw NoFingerDetectedException("Failed to acquire image quality score") + validateMinimumFingerImageQuality(qualityScore) + + // If the quality score is below the threshold and we don't allow low quality extraction, return an empty template + if (qualityScore < qualityThreshold && !allowLowQualityExtraction) { + AcquireFingerprintTemplateResponse( + ByteArray(0), templateFormat, qualityScore + ) + + } else { + acquireTemplateAndAssembleResponse(qualityScore) + ?: throw NoFingerDetectedException("Failed to acquire template") + } } - private fun Single.ensureCaptureResultOkOrError() = - flatMapCompletable { - when (it) { - CaptureFingerprintResult.OK -> Completable.complete() - CaptureFingerprintResult.FINGERPRINT_NOT_FOUND -> Completable.error( - NoFingerDetectedException("Fingerprint not found") - ) - - CaptureFingerprintResult.DPI_UNSUPPORTED -> Completable.error( - UnexpectedScannerException("Capture fingerprint DPI unsupported") - ) - - CaptureFingerprintResult.UNKNOWN_ERROR -> Completable.error( - UnknownScannerIssueException("Unknown error when capturing fingerprint") - ) - } + private fun CaptureFingerprintResult.ensureCaptureResultOkOrError() = when (this) { + CaptureFingerprintResult.OK -> { /* Do nothing */ } - private fun Single.validateMinimumFingerImageQuality() = - flatMap { qualityScore -> - if (qualityScore > NO_FINGER_IMAGE_QUALITY_THRESHOLD) { - Single.just(qualityScore) - } else { - Single.error(NoFingerDetectedException("Image quality score below detection threshold")) - } - } + CaptureFingerprintResult.FINGERPRINT_NOT_FOUND -> throw NoFingerDetectedException("Fingerprint not found") + CaptureFingerprintResult.DPI_UNSUPPORTED -> throw UnexpectedScannerException("Capture fingerprint DPI unsupported") + CaptureFingerprintResult.UNKNOWN_ERROR -> throw UnknownScannerIssueException("Unknown error when capturing fingerprint") + } - private fun Single.acquireTemplateAndAssembleResponse() = - flatMapMaybe { imageQuality -> - scannerV2.acquireTemplate() - .map { templateData -> - AcquireFingerprintTemplateResponse( - templateData.template, templateFormat, imageQuality - ) - } + private fun validateMinimumFingerImageQuality(qualityScore: Int) { + if (qualityScore <= NO_FINGER_IMAGE_QUALITY_THRESHOLD) { + throw NoFingerDetectedException("Image quality score below detection threshold") } + } - - private fun Single.ifNoFingerDetectedThenSetBadScanLedState() = - onErrorResumeNext { - if (it is NoFingerDetectedException) { - scannerV2.setSmileLedState(scannerUiHelper.badScanLedState()) - .andThen(Single.error(it)) - } else { - Single.error(it) - } + private suspend fun acquireTemplateAndAssembleResponse(imageQuality: Int) = + scannerV2.acquireTemplate()?.template?.let { templateData -> + AcquireFingerprintTemplateResponse( + templateData, templateFormat, imageQuality + ) } - companion object { private const val NO_FINGER_IMAGE_QUALITY_THRESHOLD = 10 // The image quality at which we decide a fingerprint wasn't detected @@ -163,8 +107,4 @@ internal class FingerprintCaptureWrapperV2( private const val MIN_CAPTURE_DPI = 500 private const val MAX_CAPTURE_DPI = 1700 } - - private fun Single.wrapErrorsFromScanner() = - onErrorResumeNext { Single.error(wrapErrorFromScanner(it)) } - } diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/ConnectionHelper.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/ConnectionHelper.kt index 475d4bd701..eb5b708335 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/ConnectionHelper.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/ConnectionHelper.kt @@ -17,7 +17,6 @@ import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.retry -import kotlinx.coroutines.rx2.await import kotlinx.coroutines.withContext import java.io.IOException import java.util.UUID @@ -91,29 +90,26 @@ internal class ConnectionHelper @Inject constructor( withContext(dispatcher) { socket.connect() } this.socket = socket return socket - } catch (e: IOException) { + } catch (_: IOException) { throw ScannerDisconnectedException() } } - private suspend fun connectScannerObjectWithSocket( - scanner: Scanner, - socket: ComponentBluetoothSocket - ) = withContext(dispatcher) { + private fun connectScannerObjectWithSocket( + scanner: Scanner, socket: ComponentBluetoothSocket + ) { Simber.d("Socket connected. Setting up scanner...") - scanner.connect(socket.getInputStream(), socket.getOutputStream()).await() + scanner.connect(socket.getInputStream(), socket.getOutputStream()) } - suspend fun disconnectScanner(scanner: Scanner): Unit = withContext(dispatcher) { - scanner.disconnect().await() + fun disconnectScanner(scanner: Scanner) { + scanner.disconnect() socket?.close() } suspend fun reconnect( - scanner: Scanner, - macAddress: String, - maxRetries: Long = CONNECT_MAX_RETRIES - )= withContext(dispatcher) { + scanner: Scanner, macAddress: String, maxRetries: Long = CONNECT_MAX_RETRIES + ) { disconnectScanner(scanner) delay(RECONNECT_DELAY_MS) connectScanner(scanner, macAddress, maxRetries).collect() diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/CypressOtaHelper.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/CypressOtaHelper.kt index 16dff49a95..e2cfee29ce 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/CypressOtaHelper.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/CypressOtaHelper.kt @@ -6,12 +6,10 @@ import com.simprints.fingerprint.infra.scanner.domain.versions.ScannerVersion import com.simprints.fingerprint.infra.scanner.exceptions.safe.OtaFailedException import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.CypressExtendedFirmwareVersion import com.simprints.fingerprint.infra.scanner.v2.scanner.Scanner -import io.reactivex.Completable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.rx2.asFlow -import kotlinx.coroutines.rx2.await +import kotlinx.coroutines.flow.map import javax.inject.Inject @@ -57,7 +55,7 @@ internal class CypressOtaHelper @Inject constructor( private suspend fun CypressStep.enterCypressOtaMode(scanner: Scanner) { emit(CypressOtaStep.EnteringOtaMode) - scanner.enterCypressOtaMode().await() + scanner.enterCypressOtaMode() delayForOneSecond() } @@ -68,7 +66,6 @@ internal class CypressOtaHelper @Inject constructor( emit(CypressOtaStep.CommencingTransfer) scanner.startCypressOta(firmwareLocalDataSource.loadCypressFirmwareBytes(firmwareVersion)) .map { CypressOtaStep.TransferInProgress(it) } - .asFlow() .collect { emit(it) } } @@ -92,26 +89,25 @@ internal class CypressOtaHelper @Inject constructor( } private suspend fun validateCypressFirmwareVersion(scanner: Scanner, firmwareVersion: String) = - scanner.getCypressExtendedFirmwareVersion().flatMapCompletable { + scanner.getCypressExtendedFirmwareVersion().let { val actualFirmwareVersion = it.versionAsString if (firmwareVersion != actualFirmwareVersion) { - Completable.error(OtaFailedException("Cypress OTA did not increment firmware version. Expected $firmwareVersion, but was $actualFirmwareVersion")) + throw OtaFailedException("Cypress OTA did not increment firmware version. Expected $firmwareVersion, but was $actualFirmwareVersion") } else { newFirmwareVersion = it - Completable.complete() } - }.await() + } private suspend fun updateUnifiedVersionInformation(scanner: Scanner) = - scanner.getVersionInformation().flatMapCompletable { + scanner.getVersionInformation().let { newFirmwareVersion?.let { newFirmwareVersion -> val oldVersion = it.toScannerVersion() val newVersion = oldVersion.updatedWithCypressVersion(newFirmwareVersion) scanner.setVersionInformation(newVersion.toExtendedVersionInformation()) } - ?: Completable.error(OtaFailedException("Was not able to determine the appropriate new unified version")) - }.await() + ?: throw OtaFailedException("Was not able to determine the appropriate new unified version") + } private fun ScannerVersion.updatedWithCypressVersion(cypressFirmware: CypressExtendedFirmwareVersion) = copy(firmware = firmware.copy(cypress = cypressFirmware.versionAsString)) diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/ScannerInitialSetupHelper.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/ScannerInitialSetupHelper.kt index 1a4ac33475..6d35e11370 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/ScannerInitialSetupHelper.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/ScannerInitialSetupHelper.kt @@ -1,6 +1,5 @@ package com.simprints.fingerprint.infra.scanner.helpers -import com.simprints.core.DispatcherIO import com.simprints.fingerprint.infra.scanner.data.local.FirmwareLocalDataSource import com.simprints.fingerprint.infra.scanner.domain.BatteryInfo import com.simprints.fingerprint.infra.scanner.domain.ota.AvailableOta @@ -13,10 +12,7 @@ import com.simprints.fingerprint.infra.scanner.v2.scanner.Scanner import com.simprints.infra.config.store.models.FingerprintConfiguration import com.simprints.infra.config.store.models.Vero2Configuration import com.simprints.infra.config.sync.ConfigManager -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.delay -import kotlinx.coroutines.rx2.await -import kotlinx.coroutines.withContext import javax.inject.Inject /** @@ -28,7 +24,6 @@ internal class ScannerInitialSetupHelper @Inject constructor( private val batteryLevelChecker: BatteryLevelChecker, private val configManager: ConfigManager, private val firmwareLocalDataSource: FirmwareLocalDataSource, - @DispatcherIO private val dispatcher: CoroutineDispatcher ) { private lateinit var scannerVersion: ScannerVersion @@ -50,15 +45,15 @@ internal class ScannerInitialSetupHelper @Inject constructor( macAddress: String, withScannerVersion: (ScannerVersion) -> Unit, withBatteryInfo: (BatteryInfo) -> Unit, - ) = withContext(dispatcher) { + ) { delay(100) // Speculatively needed - val unifiedVersionInfo = scanner.getVersionInformation().await() + val unifiedVersionInfo = scanner.getVersionInformation() unifiedVersionInfo.toScannerVersion().also { withScannerVersion(it) scannerVersion = it } - scanner.enterMainMode().await() + scanner.enterMainMode() delay(100) // Speculatively needed val batteryInfo = getBatteryInfo(scanner, withBatteryInfo) ifAvailableOtasPrepareScannerThenThrow( @@ -74,10 +69,10 @@ internal class ScannerInitialSetupHelper @Inject constructor( scanner: Scanner, withBatteryInfo: (BatteryInfo) -> Unit, ): BatteryInfo { - val batteryPercent = scanner.getBatteryPercentCharge().await() - val batteryVoltage = scanner.getBatteryVoltageMilliVolts().await() - val batteryMilliAmps = scanner.getBatteryCurrentMilliAmps().await() - val batteryTemperature = scanner.getBatteryTemperatureDeciKelvin().await() + val batteryPercent = scanner.getBatteryPercentCharge() + val batteryVoltage = scanner.getBatteryVoltageMilliVolts() + val batteryMilliAmps = scanner.getBatteryCurrentMilliAmps() + val batteryTemperature = scanner.getBatteryTemperatureDeciKelvin() return BatteryInfo( batteryPercent, diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/StmOtaHelper.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/StmOtaHelper.kt index 41cf04fc73..24ca1d1925 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/StmOtaHelper.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/StmOtaHelper.kt @@ -6,12 +6,10 @@ import com.simprints.fingerprint.infra.scanner.domain.versions.ScannerVersion import com.simprints.fingerprint.infra.scanner.exceptions.safe.OtaFailedException import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.StmExtendedFirmwareVersion import com.simprints.fingerprint.infra.scanner.v2.scanner.Scanner -import io.reactivex.Completable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.rx2.asFlow -import kotlinx.coroutines.rx2.await +import kotlinx.coroutines.flow.map import javax.inject.Inject @@ -72,7 +70,8 @@ internal class StmOtaHelper @Inject constructor( private suspend fun StmStep.enterStmOtaMode(scanner: Scanner) { emit(StmOtaStep.EnteringOtaModeFirstTime) - scanner.enterStmOtaMode().onErrorComplete().await() + // Ignore exception here, as we will reconnect after this step + runCatching { scanner.enterStmOtaMode()} } private suspend fun StmStep.reconnectAfterEnteringStmOtaMode( @@ -86,7 +85,7 @@ internal class StmOtaHelper @Inject constructor( private suspend fun StmStep.reEnterStmOtaMode(scanner: Scanner) { emit(StmOtaStep.EnteringOtaModeSecondTime) - scanner.enterStmOtaMode().await() + scanner.enterStmOtaMode() delayForOneSecond() } @@ -95,7 +94,6 @@ internal class StmOtaHelper @Inject constructor( emit(StmOtaStep.CommencingTransfer) scanner.startStmOta(firmwareLocalDataSource.loadStmFirmwareBytes(firmwareVersion)) .map { StmOtaStep.TransferInProgress(it) } - .asFlow() .collect { emit(it) } } @@ -107,7 +105,7 @@ internal class StmOtaHelper @Inject constructor( private suspend fun StmStep.enterMainMode(scanner: Scanner) { emit(StmOtaStep.EnteringMainMode) - scanner.enterMainMode().await() + scanner.enterMainMode() delayForOneSecond() } @@ -133,26 +131,23 @@ internal class StmOtaHelper @Inject constructor( updateUnifiedVersionInformation(scanner) } - private suspend fun validateStmFirmwareVersion(scanner: Scanner, firmwareVersion: String) = - scanner.getStmFirmwareVersion().flatMapCompletable { - val actualFirmwareVersion = it.versionAsString - if (firmwareVersion != actualFirmwareVersion) { - Completable.error(OtaFailedException("STM OTA did not increment firmware version. Expected $firmwareVersion, but was $actualFirmwareVersion")) - } else { - newFirmwareVersion = it - Completable.complete() - } - }.await() - - private suspend fun updateUnifiedVersionInformation(scanner: Scanner) = - scanner.getVersionInformation().flatMapCompletable { - newFirmwareVersion?.let { newFirmwareVersion -> - val oldVersion = it.toScannerVersion() - val newVersion = oldVersion.updatedWithStmVersion(newFirmwareVersion) - scanner.setVersionInformation(newVersion.toExtendedVersionInformation()) - } - ?: Completable.error(OtaFailedException("Was not able to determine the appropriate new unified version")) - }.await() + private suspend fun validateStmFirmwareVersion(scanner: Scanner, firmwareVersion: String) { + val actualFirmwareVersion = scanner.getStmFirmwareVersion() + if (firmwareVersion != actualFirmwareVersion.versionAsString) { + throw OtaFailedException("STM OTA did not increment firmware version. Expected $firmwareVersion, but was $actualFirmwareVersion") + } else { + newFirmwareVersion = actualFirmwareVersion + } + } + + private suspend fun updateUnifiedVersionInformation(scanner: Scanner) { + val oldVersion = scanner.getVersionInformation().toScannerVersion() + newFirmwareVersion?.let { newFirmwareVersion -> + val newVersion = oldVersion.updatedWithStmVersion(newFirmwareVersion) + scanner.setVersionInformation(newVersion.toExtendedVersionInformation()) + } + ?: throw OtaFailedException("Was not able to determine the appropriate new unified version") + } private fun ScannerVersion.updatedWithStmVersion(stmFirmware: StmExtendedFirmwareVersion) = copy(firmware = firmware.copy(stm = stmFirmware.versionAsString)) diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/Un20OtaHelper.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/Un20OtaHelper.kt index 980ed37f49..1d4c6075eb 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/Un20OtaHelper.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/helpers/Un20OtaHelper.kt @@ -11,8 +11,8 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.rx2.asFlow -import kotlinx.coroutines.rx2.await +import kotlinx.coroutines.flow.map + import javax.inject.Inject @@ -61,20 +61,19 @@ internal class Un20OtaHelper @Inject constructor( private suspend fun Un20Step.enterMainMode(scanner: Scanner) { emit(Un20OtaStep.EnteringMainMode) - scanner.enterMainMode().await() + scanner.enterMainMode() delayForOneSecond() } private suspend fun Un20Step.turnOnUn20BeforeTransfer(scanner: Scanner) { emit(Un20OtaStep.TurningOnUn20BeforeTransfer) - scanner.turnUn20OnAndAwaitStateChangeEvent().await() + scanner.turnUn20On() } private suspend fun Un20Step.transferFirmwareBytes(scanner: Scanner, firmwareVersion: String) { emit(Un20OtaStep.CommencingTransfer) scanner.startUn20Ota(firmwareLocalDataSource.loadUn20FirmwareBytes(firmwareVersion)) .map { Un20OtaStep.TransferInProgress(it) } - .asFlow() .collect { emit(it) } } @@ -85,13 +84,13 @@ internal class Un20OtaHelper @Inject constructor( private suspend fun Un20Step.turnOffUn20AfterTransfer(scanner: Scanner) { emit(Un20OtaStep.TurningOffUn20AfterTransfer) - scanner.turnUn20OffAndAwaitStateChangeEvent().await() + scanner.turnUn20Off() delayForOneSecond() } private suspend fun Un20Step.turnOnUn20AfterTransfer(scanner: Scanner) { emit(Un20OtaStep.TurningOnUn20AfterTransfer) - scanner.turnUn20OnAndAwaitStateChangeEvent().await() + scanner.turnUn20On() } private suspend fun Un20Step.validateRunningFirmwareVersion( @@ -117,25 +116,24 @@ internal class Un20OtaHelper @Inject constructor( } private suspend fun validateUn20FirmwareVersion(scanner: Scanner, firmwareVersion: String) = - scanner.getUn20AppVersion().flatMapCompletable { + scanner.getUn20AppVersion().let { val actualFirmwareVersion = it.versionAsString if (firmwareVersion != actualFirmwareVersion) { - Completable.error(OtaFailedException("UN20 OTA did not increment firmware version. Expected $firmwareVersion, but was $actualFirmwareVersion")) + throw OtaFailedException("UN20 OTA did not increment firmware version. Expected $firmwareVersion, but was $actualFirmwareVersion") } else { newFirmwareVersion = it - Completable.complete() } - }.await() + } private suspend fun updateUnifiedVersionInformation(scanner: Scanner) = - scanner.getVersionInformation().flatMapCompletable { + scanner.getVersionInformation().let { newFirmwareVersion?.let { newFirmwareVersion -> val oldVersion = it.toScannerVersion() val newVersion = oldVersion.updatedWithUn20Version(newFirmwareVersion) scanner.setVersionInformation(newVersion.toExtendedVersionInformation()) } ?: Completable.error(OtaFailedException("Was not able to determine the appropriate new unified version")) - }.await() + } private fun ScannerVersion.updatedWithUn20Version(un20Firmware: Un20ExtendedAppVersion) = copy(firmware = firmware.copy(un20 = un20Firmware.versionAsString)) diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/CypressOtaMessageChannel.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/CypressOtaMessageChannel.kt index 68d16b3ad0..4f05ae4a2c 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/CypressOtaMessageChannel.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/CypressOtaMessageChannel.kt @@ -5,14 +5,20 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.cypressota.CypressOtaRe import com.simprints.fingerprint.infra.scanner.v2.incoming.cypressota.CypressOtaMessageInputStream import com.simprints.fingerprint.infra.scanner.v2.outgoing.cypressota.CypressOtaMessageOutputStream import com.simprints.fingerprint.infra.scanner.v2.tools.reactive.doSimultaneously -import io.reactivex.Single +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.rx2.await class CypressOtaMessageChannel( incoming: CypressOtaMessageInputStream, - outgoing: CypressOtaMessageOutputStream -) : MessageChannel(incoming, outgoing) { + outgoing: CypressOtaMessageOutputStream, + dispatcher: CoroutineDispatcher +) : MessageChannel( + incoming, outgoing, dispatcher +) { - inline fun sendCypressOtaModeCommandAndReceiveResponse(command: CypressOtaCommand): Single = - outgoing.sendMessage(command) - .doSimultaneously(incoming.receiveResponse()) + suspend inline fun sendCommandAndReceiveResponse( + command: CypressOtaCommand + ): R = runLockedTask { + outgoing.sendMessage(command).doSimultaneously(incoming.receiveResponse()).await() + } } diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/MainMessageChannel.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/MainMessageChannel.kt index 88a6596be0..f4945f5b3e 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/MainMessageChannel.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/MainMessageChannel.kt @@ -5,14 +5,28 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.OutgoingMa import com.simprints.fingerprint.infra.scanner.v2.incoming.main.MainMessageInputStream import com.simprints.fingerprint.infra.scanner.v2.outgoing.main.MainMessageOutputStream import com.simprints.fingerprint.infra.scanner.v2.tools.reactive.doSimultaneously -import io.reactivex.Single +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.rx2.await class MainMessageChannel( incoming: MainMessageInputStream, - outgoing: MainMessageOutputStream -) : MessageChannel(incoming, outgoing) { + outgoing: MainMessageOutputStream, + dispatcher: CoroutineDispatcher +) : MessageChannel( + incoming, outgoing, dispatcher +) { - inline fun sendMainModeCommandAndReceiveResponse(command: OutgoingMainMessage): Single = - outgoing.sendMessage(command) - .doSimultaneously(incoming.receiveResponse()) + suspend inline fun sendCommandAndReceiveResponse( + command: OutgoingMainMessage + ): R = runLockedTask { + outgoing.sendMessage(command).doSimultaneously(incoming.receiveResponse()).await() + } + + suspend fun sendMainModeCommand(command: OutgoingMainMessage) = runLockedTask { + outgoing.sendMessage(command).await() + } + + suspend inline fun receiveResponse() = runLockedTask { + incoming.receiveResponse().await() + } } diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/MessageChannel.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/MessageChannel.kt index 93678e5ffb..740246bb2f 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/MessageChannel.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/MessageChannel.kt @@ -2,19 +2,35 @@ package com.simprints.fingerprint.infra.scanner.v2.channel import com.simprints.fingerprint.infra.scanner.v2.incoming.common.MessageInputStream import com.simprints.fingerprint.infra.scanner.v2.outgoing.common.MessageOutputStream +import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandler +import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandlingStrategy import io.reactivex.Flowable +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext import java.io.OutputStream /** - * Base class to hold onto pairs of corresponding [MessageInputStream]s and [MessageOutputStream]s - * for easier sending and receiving. + * Represents a message channel with incoming and outgoing streams, + * providing thread-safe and coroutine-friendly operations. * - * It was found to be better to start observing the response at the same time/slightly before - * sending the command as sometimes the Vero would respond too quickly. + * @param I The type of the input stream, must extend [MessageInputStream]. + * @param O The type of the output stream, must extend [MessageOutputStream]. + * @property incoming The incoming stream for reading data. + * @property outgoing The outgoing stream for writing data. + * @property readWriteLock A mutex for synchronizing read/write access. + * @property responseErrorHandler Handles errors during execution. + * @property dispatcher The coroutine dispatcher for executing tasks. */ abstract class MessageChannel>( val incoming: I, - val outgoing: O + val outgoing: O, + val dispatcher: CoroutineDispatcher, + val readWriteLock: Mutex = Mutex(), + val responseErrorHandler: ResponseErrorHandler = ResponseErrorHandler( + ResponseErrorHandlingStrategy.DEFAULT + ), ) : Connectable { override fun connect(flowableInputStream: Flowable, outputStream: OutputStream) { @@ -26,4 +42,8 @@ abstract class MessageChannel outgoing.disconnect() incoming.disconnect() } + + suspend inline fun runLockedTask( + crossinline block: suspend () -> R, + ): R = withContext(dispatcher) { readWriteLock.withLock { responseErrorHandler.handle(block) } } } diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/RootMessageChannel.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/RootMessageChannel.kt index 8b37f82b0c..2aaf47ad0f 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/RootMessageChannel.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/RootMessageChannel.kt @@ -5,14 +5,19 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.root.RootResponse import com.simprints.fingerprint.infra.scanner.v2.incoming.root.RootMessageInputStream import com.simprints.fingerprint.infra.scanner.v2.outgoing.root.RootMessageOutputStream import com.simprints.fingerprint.infra.scanner.v2.tools.reactive.doSimultaneously -import io.reactivex.Single +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.rx2.await class RootMessageChannel( incoming: RootMessageInputStream, - outgoing: RootMessageOutputStream -) : MessageChannel(incoming, outgoing) { + outgoing: RootMessageOutputStream, + dispatcher: CoroutineDispatcher +) : MessageChannel( + incoming, outgoing, dispatcher +) { - inline fun sendRootModeCommandAndReceiveResponse(command: RootCommand): Single = - outgoing.sendMessage(command) - .doSimultaneously(incoming.receiveResponse()) + suspend inline fun sendCommandAndReceiveResponse(command: RootCommand): R = + runLockedTask { + outgoing.sendMessage(command).doSimultaneously(incoming.receiveResponse()).await() + } } diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/StmOtaMessageChannel.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/StmOtaMessageChannel.kt index 96451d0051..54a1dee53d 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/StmOtaMessageChannel.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/channel/StmOtaMessageChannel.kt @@ -5,14 +5,20 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.stmota.StmOtaResponse import com.simprints.fingerprint.infra.scanner.v2.incoming.stmota.StmOtaMessageInputStream import com.simprints.fingerprint.infra.scanner.v2.outgoing.stmota.StmOtaMessageOutputStream import com.simprints.fingerprint.infra.scanner.v2.tools.reactive.doSimultaneously -import io.reactivex.Single +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.rx2.await class StmOtaMessageChannel( incoming: StmOtaMessageInputStream, - outgoing: StmOtaMessageOutputStream -) : MessageChannel(incoming, outgoing) { + outgoing: StmOtaMessageOutputStream, + dispatcher: CoroutineDispatcher +) : MessageChannel( + incoming, outgoing, dispatcher +) { + + suspend inline fun sendCommandAndReceiveResponse(command: StmOtaCommand): R = + runLockedTask { + outgoing.sendMessage(command).doSimultaneously(incoming.receiveResponse()).await() + } - inline fun sendStmOtaModeCommandAndReceiveResponse(command: StmOtaCommand): Single = - outgoing.sendMessage(command) - .doSimultaneously(incoming.receiveResponse()) } diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/incoming/main/MainMessageInputStream.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/incoming/main/MainMessageInputStream.kt index 95d8ace839..7fc57a633c 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/incoming/main/MainMessageInputStream.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/incoming/main/MainMessageInputStream.kt @@ -59,7 +59,7 @@ class MainMessageInputStream( packetRouter.disconnect() } - inline fun receiveResponse(crossinline withPredicate: (R) -> Boolean = { true }): Single = + inline fun receiveResponse(): Single = Single.defer { when { isSubclass() -> veroResponses @@ -67,7 +67,7 @@ class MainMessageInputStream( isSubclass() -> un20Responses else -> Flowable.error(IllegalArgumentException("Trying to receive invalid response")) } - ?.filterCast(withPredicate) + ?.filterCast() ?.firstOrError() ?: Single.error(IllegalStateException("Trying to receive response before connecting stream")) } diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/CreateScanner.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/CreateScanner.kt index e9f65c03cb..7c43d80346 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/CreateScanner.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/CreateScanner.kt @@ -31,17 +31,16 @@ import com.simprints.fingerprint.infra.scanner.v2.outgoing.root.RootMessageOutpu import com.simprints.fingerprint.infra.scanner.v2.outgoing.root.RootMessageSerializer import com.simprints.fingerprint.infra.scanner.v2.outgoing.stmota.StmOtaMessageOutputStream import com.simprints.fingerprint.infra.scanner.v2.outgoing.stmota.StmOtaMessageSerializer -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandler -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandlingStrategy import com.simprints.fingerprint.infra.scanner.v2.scanner.ota.cypress.CypressOtaController import com.simprints.fingerprint.infra.scanner.v2.scanner.ota.stm.StmOtaController import com.simprints.fingerprint.infra.scanner.v2.scanner.ota.un20.Un20OtaController import com.simprints.fingerprint.infra.scanner.v2.tools.crc.Crc32Calculator +import kotlinx.coroutines.CoroutineDispatcher /** * Helper function to build a new [Scanner] instance with manual dependency injection */ -fun Scanner.Companion.create(): Scanner { +fun Scanner.Companion.create(dispatcher: CoroutineDispatcher): Scanner { val mainMessageChannel = MainMessageChannel( MainMessageInputStream( PacketRouter( @@ -56,7 +55,8 @@ fun Scanner.Companion.create(): Scanner { MainMessageOutputStream( MainMessageSerializer(), OutputStreamDispatcher() - ) + ), + dispatcher ) val rootMessageChannel = RootMessageChannel( @@ -66,14 +66,13 @@ fun Scanner.Companion.create(): Scanner { RootMessageOutputStream( RootMessageSerializer(), OutputStreamDispatcher() - ) + ), + dispatcher ) - val responseErrorHandler = ResponseErrorHandler(ResponseErrorHandlingStrategy.DEFAULT) val scannerInfoReaderHelper = ScannerExtendedInfoReaderHelper( mainMessageChannel, rootMessageChannel, - responseErrorHandler ) return Scanner( @@ -87,7 +86,8 @@ fun Scanner.Companion.create(): Scanner { CypressOtaMessageOutputStream( CypressOtaMessageSerializer(), OutputStreamDispatcher() - ) + ), + dispatcher ), StmOtaMessageChannel( StmOtaMessageInputStream( @@ -96,12 +96,12 @@ fun Scanner.Companion.create(): Scanner { StmOtaMessageOutputStream( StmOtaMessageSerializer(), OutputStreamDispatcher() - ) + ), + dispatcher ), CypressOtaController(Crc32Calculator()), StmOtaController(), Un20OtaController(Crc32Calculator()), - responseErrorHandler ) } diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/Scanner.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/Scanner.kt index c5fedfa47f..449c508ae5 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/Scanner.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/Scanner.kt @@ -6,20 +6,15 @@ import com.simprints.fingerprint.infra.scanner.v2.channel.RootMessageChannel import com.simprints.fingerprint.infra.scanner.v2.channel.StmOtaMessageChannel import com.simprints.fingerprint.infra.scanner.v2.domain.Mode import com.simprints.fingerprint.infra.scanner.v2.domain.Mode.* -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.IncomingMainMessage -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.OutgoingMainMessage import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.commands.* import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.models.* import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.responses.* import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.commands.* import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.events.TriggerButtonPressedEvent import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.events.Un20StateChangeEvent -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.LedState import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.SmileLedState import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.StmExtendedFirmwareVersion import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.responses.* -import com.simprints.fingerprint.infra.scanner.v2.domain.root.RootCommand -import com.simprints.fingerprint.infra.scanner.v2.domain.root.RootResponse import com.simprints.fingerprint.infra.scanner.v2.domain.root.commands.* import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.* import com.simprints.fingerprint.infra.scanner.v2.domain.root.responses.* @@ -27,8 +22,6 @@ import com.simprints.fingerprint.infra.scanner.v2.exceptions.ota.OtaFailedExcept import com.simprints.fingerprint.infra.scanner.v2.exceptions.state.IllegalUn20StateException import com.simprints.fingerprint.infra.scanner.v2.exceptions.state.IncorrectModeException import com.simprints.fingerprint.infra.scanner.v2.exceptions.state.NotConnectedException -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandler -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.handleErrorsWith import com.simprints.fingerprint.infra.scanner.v2.scanner.ota.cypress.CypressOtaController import com.simprints.fingerprint.infra.scanner.v2.scanner.ota.stm.StmOtaController import com.simprints.fingerprint.infra.scanner.v2.scanner.ota.un20.Un20OtaController @@ -37,7 +30,7 @@ import com.simprints.fingerprint.infra.scanner.v2.tools.reactive.* import io.reactivex.* import io.reactivex.disposables.Disposable import io.reactivex.rxkotlin.subscribeBy -import kotlinx.coroutines.rx2.rxSingle +import kotlinx.coroutines.flow.Flow import java.io.IOException import java.io.InputStream import java.io.OutputStream @@ -60,7 +53,6 @@ class Scanner( private val cypressOtaController: CypressOtaController, private val stmOtaController: StmOtaController, private val un20OtaController: Un20OtaController, - private val responseErrorHandler: ResponseErrorHandler ) { private lateinit var flowableDisposable: Disposable @@ -72,7 +64,7 @@ class Scanner( private var scannerTriggerListenerDisposable: Disposable? = null - fun connect(inputStream: InputStream, outputStream: OutputStream): Completable = completable { + fun connect(inputStream: InputStream, outputStream: OutputStream) { this.flowableInputStream = inputStream.toFlowable().subscribeOnIoAndPublish() .also { this.flowableDisposable = it.connect() } this.outputStream = outputStream @@ -82,7 +74,7 @@ class Scanner( rootMessageChannel.connect(flowableInputStream, outputStream) } - fun disconnect(): Completable = completable { + fun disconnect() { // Disconnect scanner only when it is connected if (state.connected) { when (state.mode) { @@ -104,84 +96,83 @@ class Scanner( fun isConnected() = state.connected - private fun assertConnected() = Completable.fromAction { + private fun assertConnected() { if (!state.connected) { throw NotConnectedException("Attempting to access functionality before calling Scanner::connect()") } } - private fun assertMode(mode: Mode) = Completable.fromAction { + private fun assertMode(mode: Mode) { if (state.mode != mode) { throw IncorrectModeException("Attempting to access $mode functionality when current mode is ${state.mode}") } } - private fun assertUn20On() = Completable.fromAction { + private fun assertUn20On() { if (state.un20On != true) { throw IllegalUn20StateException("Attempting to access UN20 functionality when UN20 is off") } } - private inline fun sendRootModeCommandAndReceiveResponse(command: RootCommand): Single = - rootMessageChannel.sendRootModeCommandAndReceiveResponse(command) - .handleErrorsWith(responseErrorHandler) + suspend fun getVersionInformation(): ScannerInformation { + assertConnected() + assertMode(ROOT) + return scannerInfoReaderHelper.readScannerInfo() + } + + suspend fun getExtendedVersionInformation(): ExtendedVersionInformation { + assertConnected() + assertMode(ROOT) + return scannerInfoReaderHelper.getExtendedVersionInfo().version + } - private inline fun sendMainModeCommandAndReceiveResponse(command: OutgoingMainMessage): Single = - mainMessageChannel.sendMainModeCommandAndReceiveResponse(command) - .handleErrorsWith(responseErrorHandler) - fun getVersionInformation(): Single = + suspend fun setVersionInformation(versionInformation: ExtendedVersionInformation) { assertConnected() - .andThen(assertMode(ROOT)) - .andThen(scannerInfoReaderHelper.readScannerInfo()) + assertMode(ROOT) + scannerInfoReaderHelper.setExtendedVersionInformation(versionInformation) + } + suspend fun getCypressFirmwareVersion(): CypressFirmwareVersion { + assertConnected() + assertMode(ROOT) + return scannerInfoReaderHelper.getCypressVersion() + } - fun getExtendedVersionInformation(): Single = + suspend fun getCypressExtendedFirmwareVersion(): CypressExtendedFirmwareVersion { assertConnected() - .andThen(assertMode(ROOT)) - .andThen(rxSingle { scannerInfoReaderHelper.getExtendedVersionInfo().version }) - - - fun setVersionInformation(versionInformation: ExtendedVersionInformation): Completable = - assertConnected().andThen(assertMode(ROOT)) - .andThen(scannerInfoReaderHelper.setExtendedVersionInformation(versionInformation)) - .completeOnceReceived() - - fun getCypressFirmwareVersion(): Single = - assertConnected().andThen(assertMode(ROOT)) - .andThen(scannerInfoReaderHelper.getCypressVersion()) - - - fun getCypressExtendedFirmwareVersion(): Single = - assertConnected().andThen(assertMode(ROOT)) - .andThen(scannerInfoReaderHelper.getCypressExtendedVersion()) - - - fun enterMainMode(): Completable = - assertConnected().andThen(assertMode(ROOT)).andThen( - sendRootModeCommandAndReceiveResponse( - EnterMainModeCommand() - )) - .completeOnceReceived() - .andThen(handleMainModeEntered()) - - fun enterCypressOtaMode(): Completable = - assertConnected().andThen(assertMode(ROOT)).andThen( - sendRootModeCommandAndReceiveResponse( - EnterCypressOtaModeCommand() - )) - .completeOnceReceived() - .andThen(handleCypressOtaModeEntered()) - - fun enterStmOtaMode(): Completable = - assertConnected().andThen(assertMode(ROOT)).andThen( - sendRootModeCommandAndReceiveResponse( - EnterStmOtaModeCommand() - )) - .completeOnceReceived() - .andThen(handleStmOtaModeEntered()) - - private fun handleMainModeEntered() = completable { + assertMode(ROOT) + return scannerInfoReaderHelper.getCypressExtendedVersion() + } + + suspend fun enterMainMode() { + assertConnected() + assertMode(ROOT) + rootMessageChannel.sendCommandAndReceiveResponse( + EnterMainModeCommand() + ) + handleMainModeEntered() + } + + suspend fun enterCypressOtaMode() { + assertConnected() + assertMode(ROOT) + rootMessageChannel.sendCommandAndReceiveResponse( + EnterCypressOtaModeCommand() + ) + handleCypressOtaModeEntered() + } + + suspend fun enterStmOtaMode() { + assertConnected() + assertMode(ROOT) + rootMessageChannel.sendCommandAndReceiveResponse( + EnterStmOtaModeCommand() + ) + handleStmOtaModeEntered() + } + + private fun handleMainModeEntered() { rootMessageChannel.disconnect() mainMessageChannel.connect(flowableInputStream, outputStream) @@ -197,243 +188,231 @@ class Scanner( triggerButtonListeners.forEach { it.onNext(Unit) } }, onError = { it.printStackTrace() }) - private fun handleCypressOtaModeEntered() = completable { + private fun handleCypressOtaModeEntered() { rootMessageChannel.disconnect() cypressOtaMessageChannel.connect(flowableInputStream, outputStream) state.mode = CYPRESS_OTA } - private fun handleStmOtaModeEntered() = completable { + private fun handleStmOtaModeEntered() { rootMessageChannel.disconnect() stmOtaMessageChannel.connect(flowableInputStream, outputStream) state.mode = STM_OTA } - fun getStmFirmwareVersion(): Single = - assertConnected().andThen(assertMode(MAIN)) - .andThen(scannerInfoReaderHelper.getStmExtendedFirmwareVersion()) - - fun getUn20Status(): Single = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( - GetUn20OnCommand() - )) - .map { it.value == StmDigitalValue.TRUE } - .doOnSuccess { state.un20On = it } - - fun turnUn20OnAndAwaitStateChangeEvent(): Completable = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( - SetUn20OnCommand(StmDigitalValue.TRUE) - ).completeOnceReceived() - .doSimultaneously( - mainMessageChannel.incoming.receiveResponse( - withPredicate = { it.value == StmDigitalValue.TRUE } - )).handleErrorsWith(responseErrorHandler)) - .completeOnceReceived() - .doOnComplete { state.un20On = true } - - fun turnUn20OffAndAwaitStateChangeEvent(): Completable = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( - SetUn20OnCommand(StmDigitalValue.FALSE) - ).completeOnceReceived() - .doSimultaneously(mainMessageChannel.incoming.receiveResponse( - withPredicate = { it.value == StmDigitalValue.FALSE } - )).handleErrorsWith(responseErrorHandler)) - .completeOnceReceived() - .doOnComplete { state.un20On = false } - - fun getTriggerButtonStatus(): Single = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( - GetTriggerButtonActiveCommand() - )) - .map { it.value == StmDigitalValue.TRUE } - .doOnSuccess { state.triggerButtonActive = it } - - fun activateTriggerButton(): Completable = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( - SetTriggerButtonActiveCommand(StmDigitalValue.TRUE) - )) - .completeOnceReceived() - .doOnComplete { state.triggerButtonActive = true } - - fun deactivateTriggerButton(): Completable = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( - SetTriggerButtonActiveCommand(StmDigitalValue.FALSE) - )) - .completeOnceReceived() - .doOnComplete { state.triggerButtonActive = false } - - fun getSmileLedState(): Single = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( - GetSmileLedStateCommand() - )) - .map { it.smileLedState } - .doOnSuccess { state.smileLedState = it } - - fun getPowerLedState(): Single = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( - GetPowerLedStateCommand() - )) - .map { it.ledState } - - fun getBluetoothLedState(): Single = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( - GetBluetoothLedStateCommand() - )) - .map { it.ledState } - - fun setSmileLedState(smileLedState: SmileLedState): Completable = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( - SetSmileLedStateCommand(smileLedState) - )) - .completeOnceReceived() - .doOnComplete { state.smileLedState = smileLedState } - - fun getBatteryPercentCharge(): Single = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( + suspend fun getStmFirmwareVersion(): StmExtendedFirmwareVersion { + assertConnected() + assertMode(MAIN) + return scannerInfoReaderHelper.getStmExtendedFirmwareVersion() + } + + suspend fun getUn20Status(): Boolean { + assertConnected() + assertMode(MAIN) + val un20Status = mainMessageChannel + .sendCommandAndReceiveResponse(GetUn20OnCommand()) + .value == StmDigitalValue.TRUE + state.un20On = un20Status + return un20Status + } + + + suspend fun turnUn20On() { + assertConnected() + assertMode(MAIN) + mainMessageChannel.sendCommandAndReceiveResponse( + SetUn20OnCommand(StmDigitalValue.TRUE) + ) + mainMessageChannel.receiveResponse() + state.un20On = true + } + + + suspend fun turnUn20Off() { + assertConnected() + assertMode(MAIN) + mainMessageChannel.sendMainModeCommand(SetUn20OnCommand(StmDigitalValue.FALSE)) + mainMessageChannel.receiveResponse() + state.un20On = false + } + + suspend fun setSmileLedState(smileLedState: SmileLedState) { + if (smileLedState != state.smileLedState) { + assertConnected() + assertMode(MAIN) + mainMessageChannel.sendMainModeCommand(SetSmileLedStateCommand(smileLedState)) + state.smileLedState = smileLedState + } + } + + suspend fun getBatteryPercentCharge(): Int { + assertConnected() + assertMode(MAIN) + state.batteryPercentCharge = + mainMessageChannel.sendCommandAndReceiveResponse( GetBatteryPercentChargeCommand() - )) - .map { it.batteryPercentCharge.percentCharge.unsignedToInt() } - .doOnSuccess { state.batteryPercentCharge = it } + ).batteryPercentCharge.percentCharge.unsignedToInt() + return state.batteryPercentCharge!! + } - fun getBatteryVoltageMilliVolts(): Single = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( + suspend fun getBatteryVoltageMilliVolts(): Int { + assertConnected() + assertMode(MAIN) + state.batteryVoltageMilliVolts = + mainMessageChannel.sendCommandAndReceiveResponse( GetBatteryVoltageCommand() - )) - .map { it.batteryVoltage.milliVolts.unsignedToInt() } - .doOnSuccess { state.batteryVoltageMilliVolts = it } + ).batteryVoltage.milliVolts.unsignedToInt() + + return state.batteryVoltageMilliVolts!! + } - fun getBatteryCurrentMilliAmps(): Single = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( + suspend fun getBatteryCurrentMilliAmps(): Int { + assertConnected() + assertMode(MAIN) + state.batteryCurrentMilliAmps = + mainMessageChannel.sendCommandAndReceiveResponse( GetBatteryCurrentCommand() - )) - .map { it.batteryCurrent.milliAmps.toInt() } - .doOnSuccess { state.batteryCurrentMilliAmps = it } + ).batteryCurrent.milliAmps.toInt() + return state.batteryCurrentMilliAmps!! + } - fun getBatteryTemperatureDeciKelvin(): Single = - assertConnected().andThen(assertMode(MAIN)).andThen( - sendMainModeCommandAndReceiveResponse( + suspend fun getBatteryTemperatureDeciKelvin(): Int { + assertConnected() + assertMode(MAIN) + state.batteryTemperatureDeciKelvin = + mainMessageChannel.sendCommandAndReceiveResponse( GetBatteryTemperatureCommand() - )) - .map { it.batteryTemperature.deciKelvin.unsignedToInt() } - .doOnSuccess { state.batteryTemperatureDeciKelvin = it } + ).batteryTemperature.deciKelvin.unsignedToInt() + return state.batteryTemperatureDeciKelvin!! + } - fun getUn20AppVersion(): Single = - assertConnected().andThen(assertMode(MAIN)).andThen(assertUn20On()) - .andThen(scannerInfoReaderHelper.getUn20ExtendedAppVersion()) + suspend fun getUn20AppVersion(): Un20ExtendedAppVersion { + assertConnected() + assertMode(MAIN) + assertUn20On() + return scannerInfoReaderHelper.getUn20ExtendedAppVersion() + } - fun captureFingerprint(dpi: Dpi = DEFAULT_DPI): Single = - assertConnected().andThen(assertMode(MAIN)).andThen(assertUn20On()).andThen( - sendMainModeCommandAndReceiveResponse( - CaptureFingerprintCommand(dpi) - )) - .map { it.captureFingerprintResult } + + suspend fun captureFingerprint(dpi: Dpi = DEFAULT_DPI): CaptureFingerprintResult { + assertConnected() + assertMode(MAIN) + assertUn20On() + return mainMessageChannel.sendCommandAndReceiveResponse( + CaptureFingerprintCommand(dpi) + ).captureFingerprintResult + } /** Requires UN20 API 1.1 */ - fun setScannerLedStateOn(): Completable = - assertConnected().andThen(assertMode(MAIN)).andThen(assertUn20On()).andThen( - sendMainModeCommandAndReceiveResponse( - SetScanLedStateCommand(Un20DigitalValue.TRUE) - )) - .completeOnceReceived() - .doOnComplete { state.scanLedState = true } + suspend fun setScannerLedStateOn() { + assertConnected() + assertMode(MAIN) + assertUn20On() + mainMessageChannel.sendCommandAndReceiveResponse( + SetScanLedStateCommand(Un20DigitalValue.TRUE) + ) + state.scanLedState = true + } /** Requires UN20 API 1.1 */ - fun setScannerLedStateDefault(): Completable = - assertConnected().andThen(assertMode(MAIN)).andThen(assertUn20On()).andThen( - sendMainModeCommandAndReceiveResponse( - SetScanLedStateCommand(Un20DigitalValue.FALSE) - )) - .completeOnceReceived() - .doOnComplete { state.scanLedState = false } + suspend fun setScannerLedStateDefault() { + assertConnected() + assertMode(MAIN) + assertUn20On() + mainMessageChannel.sendCommandAndReceiveResponse( + SetScanLedStateCommand(Un20DigitalValue.FALSE) + ) + state.scanLedState = false + } /** Requires UN20 API 1.1 * No value emitted if an image could not be captured */ - fun getImageQualityPreview(): Maybe = - assertConnected().andThen(assertMode(MAIN)).andThen(assertUn20On()).andThen( - sendMainModeCommandAndReceiveResponse( - GetImageQualityPreviewCommand() - )) - .mapToMaybeEmptyIfNull { it.imageQualityScore } - - fun getSupportedTemplateTypes(): Single> = - assertConnected().andThen(assertMode(MAIN)).andThen(assertUn20On()).andThen( - sendMainModeCommandAndReceiveResponse( - GetSupportedTemplateTypesCommand() - )) - .map { it.supportedTemplateTypes } + suspend fun getImageQualityPreview(): Int? { + assertConnected() + assertMode(MAIN) + assertUn20On() + return mainMessageChannel.sendCommandAndReceiveResponse( + GetImageQualityPreviewCommand() + ).imageQualityScore + } /** No value emitted if an image has not been captured */ - fun acquireTemplate(templateType: TemplateType = DEFAULT_TEMPLATE_TYPE): Maybe = - assertConnected().andThen(assertMode(MAIN)).andThen(assertUn20On()).andThen( - sendMainModeCommandAndReceiveResponse( - GetTemplateCommand(templateType) - )) - .mapToMaybeEmptyIfNull { it.templateData } - - fun getSupportedImageFormats(): Single> = - assertConnected().andThen(assertMode(MAIN)).andThen(assertUn20On()).andThen( - sendMainModeCommandAndReceiveResponse( - GetSupportedImageFormatsCommand() - )) - .map { it.supportedImageFormats } + suspend fun acquireTemplate(templateType: TemplateType = DEFAULT_TEMPLATE_TYPE): TemplateData? { + assertConnected() + assertMode(MAIN) + assertUn20On() + return mainMessageChannel.sendCommandAndReceiveResponse( + GetTemplateCommand(templateType) + ).templateData + } + /** No value emitted if an image has not been captured */ - fun acquireImage(imageFormatData: ImageFormatData = DEFAULT_IMAGE_FORMAT_DATA): Maybe = - assertConnected().andThen(assertMode(MAIN)).andThen(assertUn20On()).andThen( - sendMainModeCommandAndReceiveResponse( - GetImageCommand(imageFormatData) - )) - .mapToMaybeEmptyIfNull { it.imageData } - - fun acquireUnprocessedImage(imageFormatData: ImageFormatData = DEFAULT_IMAGE_FORMAT_DATA): Maybe = - assertConnected().andThen(assertMode(MAIN)).andThen(assertUn20On()).andThen( - sendMainModeCommandAndReceiveResponse( - GetUnprocessedImageCommand(imageFormatData) - )) - .mapToMaybeEmptyIfNull { it.imageData } - fun acquireImageDistortionConfigurationMatrix(): Maybe = - assertConnected().andThen(assertMode(MAIN)).andThen(assertUn20On()).andThen( - sendMainModeCommandAndReceiveResponse( - GetImageDistortionConfigurationMatrixCommand() - )) - .mapToMaybeEmptyIfNull { it.imageConfigurationMatrix } + suspend fun acquireImage(imageFormatData: ImageFormatData = DEFAULT_IMAGE_FORMAT_DATA): ImageData? { + assertConnected() + assertMode(MAIN) + assertUn20On() + return mainMessageChannel.sendCommandAndReceiveResponse( + GetImageCommand(imageFormatData) + ).imageData + } + + suspend fun acquireUnprocessedImage(imageFormatData: ImageFormatData = DEFAULT_IMAGE_FORMAT_DATA): ImageData? { + assertConnected() + assertMode(MAIN) + assertUn20On() + return mainMessageChannel.sendCommandAndReceiveResponse( + GetUnprocessedImageCommand(imageFormatData) + ).imageData + } + + suspend fun acquireImageDistortionConfigurationMatrix(): ByteArray? { + assertConnected() + assertMode(MAIN) + assertUn20On() + return mainMessageChannel.sendCommandAndReceiveResponse( + GetImageDistortionConfigurationMatrixCommand() + ).imageConfigurationMatrix + } /** No value emitted if an image has not been captured */ - fun getImageQualityScore(): Maybe = - assertConnected().andThen(assertMode(MAIN)).andThen(assertUn20On()).andThen( - sendMainModeCommandAndReceiveResponse( - GetImageQualityCommand() - )) - .mapToMaybeEmptyIfNull { it.imageQualityScore } + suspend fun getImageQualityScore(): Int? { + assertConnected() + assertMode(MAIN) + assertUn20On() + return mainMessageChannel.sendCommandAndReceiveResponse( + GetImageQualityCommand() + ).imageQualityScore + } /** @throws OtaFailedException If a domain error occurs at any step during the OTA process */ - fun startCypressOta(firmwareBinFile: ByteArray): Observable = - assertConnected().andThen(assertMode(CYPRESS_OTA)).andThen( - cypressOtaController.program(cypressOtaMessageChannel, responseErrorHandler, firmwareBinFile)) + suspend fun startCypressOta(firmwareBinFile: ByteArray): Flow { + assertConnected() + assertMode(CYPRESS_OTA) + return cypressOtaController.program( + cypressOtaMessageChannel, firmwareBinFile + ) + } /** @throws OtaFailedException If a domain error occurs at any step during the OTA process */ - fun startStmOta(firmwareBinFile: ByteArray): Observable = - assertConnected().andThen(assertMode(STM_OTA)).andThen( - stmOtaController.program(stmOtaMessageChannel, responseErrorHandler, firmwareBinFile)) + suspend fun startStmOta(firmwareBinFile: ByteArray): Flow { + assertConnected() + assertMode(STM_OTA) + return stmOtaController.program( + stmOtaMessageChannel, firmwareBinFile + ) + } /** @throws OtaFailedException If a domain error occurs at any step during the OTA process */ - fun startUn20Ota(firmwareBinFile: ByteArray): Observable = - assertConnected().andThen(assertMode(MAIN)).andThen(assertUn20On()).andThen( - un20OtaController.program(mainMessageChannel, responseErrorHandler, firmwareBinFile)) + suspend fun startUn20Ota(firmwareBinFile: ByteArray): Flow { + assertConnected() + assertMode(MAIN) + assertUn20On() + return un20OtaController.program( + mainMessageChannel, firmwareBinFile + ) + } companion object { val DEFAULT_DPI = Dpi(500) diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ScannerExtendedInfoReaderHelper.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ScannerExtendedInfoReaderHelper.kt index 6dc9206f31..914cc969da 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ScannerExtendedInfoReaderHelper.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ScannerExtendedInfoReaderHelper.kt @@ -2,16 +2,12 @@ package com.simprints.fingerprint.infra.scanner.v2.scanner import com.simprints.fingerprint.infra.scanner.v2.channel.MainMessageChannel import com.simprints.fingerprint.infra.scanner.v2.channel.RootMessageChannel -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.IncomingMainMessage -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.OutgoingMainMessage import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.commands.GetUn20ExtendedAppVersionCommand import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.models.Un20ExtendedAppVersion import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.responses.GetUn20ExtendedAppVersionResponse import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.commands.GetStmExtendedFirmwareVersionCommand import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.StmExtendedFirmwareVersion import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.responses.GetStmExtendedFirmwareVersionResponse -import com.simprints.fingerprint.infra.scanner.v2.domain.root.RootCommand -import com.simprints.fingerprint.infra.scanner.v2.domain.root.RootResponse import com.simprints.fingerprint.infra.scanner.v2.domain.root.commands.GetCypressExtendedVersionCommand import com.simprints.fingerprint.infra.scanner.v2.domain.root.commands.GetCypressVersionCommand import com.simprints.fingerprint.infra.scanner.v2.domain.root.commands.GetExtendedVersionCommand @@ -28,79 +24,68 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.root.responses.GetExten import com.simprints.fingerprint.infra.scanner.v2.domain.root.responses.GetHardwareVersionResponse import com.simprints.fingerprint.infra.scanner.v2.domain.root.responses.GetVersionResponse import com.simprints.fingerprint.infra.scanner.v2.domain.root.responses.SetVersionResponse -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandler -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.handleErrorsWith -import io.reactivex.Single -import kotlinx.coroutines.rx2.await -import kotlinx.coroutines.rx2.rxSingle + class ScannerExtendedInfoReaderHelper( private val mainMessageChannel: MainMessageChannel, private val rootMessageChannel: RootMessageChannel, - private val responseErrorHandler: ResponseErrorHandler ) { - fun readScannerInfo(): Single { - return getCypressVersion().flatMap { cypressVersion -> - val isLegacyApi = cypressVersion.apiMajorVersion <= CYPRESS_HIGHEST_LEGACY_API_MAJOR_VERSION - && cypressVersion.apiMinorVersion <= CYPRESS_HIGHEST_LEGACY_API_MINOR_VERSION - - rxSingle { readScannerInfoBasedOnApiVersion(isLegacyApi) } - } + suspend fun readScannerInfo(): ScannerInformation { + val cypressVersion = getCypressVersion() + val isLegacyApi = + cypressVersion.apiMajorVersion <= CYPRESS_HIGHEST_LEGACY_API_MAJOR_VERSION && cypressVersion.apiMinorVersion <= CYPRESS_HIGHEST_LEGACY_API_MINOR_VERSION + return readScannerInfoBasedOnApiVersion(isLegacyApi) } - private suspend fun readScannerInfoBasedOnApiVersion(isLegacyApi: Boolean): ScannerInformation { - return if (isLegacyApi) getScannerInformationWithLegacyApi() + private suspend fun readScannerInfoBasedOnApiVersion(isLegacyApi: Boolean): ScannerInformation = + if (isLegacyApi) getScannerInformationWithLegacyApi() else getScannerInformationWithNewApi() - } - fun getCypressVersion(): Single { - return sendRootModeCommandAndReceiveResponse( + + suspend fun getCypressVersion(): CypressFirmwareVersion = + rootMessageChannel.sendCommandAndReceiveResponse( GetCypressVersionCommand() - ).map { it.version } - } + ).version - fun getCypressExtendedVersion(): Single { - return sendRootModeCommandAndReceiveResponse( + suspend fun getCypressExtendedVersion(): CypressExtendedFirmwareVersion = + rootMessageChannel.sendCommandAndReceiveResponse( GetCypressExtendedVersionCommand() - ).map { it.version } - } + ).version - fun getStmExtendedFirmwareVersion(): Single { - return sendMainModeCommandAndReceiveResponse( + suspend fun getStmExtendedFirmwareVersion(): StmExtendedFirmwareVersion = + mainMessageChannel.sendCommandAndReceiveResponse( GetStmExtendedFirmwareVersionCommand() - ).map { it.stmFirmwareVersion } - } + ).stmFirmwareVersion - fun getUn20ExtendedAppVersion(): Single { - return sendMainModeCommandAndReceiveResponse( + suspend fun getUn20ExtendedAppVersion(): Un20ExtendedAppVersion = + mainMessageChannel.sendCommandAndReceiveResponse( GetUn20ExtendedAppVersionCommand() - ).map { it.un20AppVersion } - } + ).un20AppVersion - fun setExtendedVersionInformation(versionInformation: ExtendedVersionInformation): Single { - return sendRootModeCommandAndReceiveResponse( + suspend fun setExtendedVersionInformation(versionInformation: ExtendedVersionInformation): SetVersionResponse = + rootMessageChannel.sendCommandAndReceiveResponse( SetExtendedVersionCommand(versionInformation) ) - } private suspend fun getScannerInformationWithLegacyApi(): ScannerInformation { - val legacyUnifiedVersion = sendRootModeCommandAndReceiveResponse( - GetVersionCommand() - ).await() + val legacyUnifiedVersion = + rootMessageChannel.sendCommandAndReceiveResponse( + GetVersionCommand() + ) val extendedVersionInfo = legacyUnifiedVersion.version.toExtendedVersionInfo() return ScannerInformation( - hardwareVersion = DEFAULT_HARDWARE_VERSION, - firmwareVersions = extendedVersionInfo + hardwareVersion = DEFAULT_HARDWARE_VERSION, firmwareVersions = extendedVersionInfo ) } private suspend fun getScannerInformationWithNewApi(): ScannerInformation { val (unifiedVersion, hardwareVersion) = getExtendedVersionInfoAndHardwareVersionInfo() - val mergedUnifiedVersion = validateUnifiedVersionOrMergeWithOldVersion(unifiedVersion.version) + val mergedUnifiedVersion = + validateUnifiedVersionOrMergeWithOldVersion(unifiedVersion.version) return ScannerInformation( hardwareVersion = hardwareVersion.version.versionIdentifier, @@ -120,8 +105,7 @@ class ScannerExtendedInfoReaderHelper( var mergedScannerInfo = unifiedVersion if (requiresOldApiValues) { - val oldUnifiedVersionInformation = - getScannerInformationWithLegacyApi().firmwareVersions + val oldUnifiedVersionInformation = getScannerInformationWithLegacyApi().firmwareVersions if (unifiedVersion.cypressFirmwareVersion.versionAsString.isEmpty()) { mergedScannerInfo = mergedScannerInfo.copy( @@ -151,26 +135,15 @@ class ScannerExtendedInfoReaderHelper( return Pair(unifiedVersion, hardwareVersionResponse) } - private suspend fun getHardwareVersionInfo(): GetHardwareVersionResponse { - return sendRootModeCommandAndReceiveResponse( + private suspend fun getHardwareVersionInfo(): GetHardwareVersionResponse = + rootMessageChannel.sendCommandAndReceiveResponse( GetHardwareVersionCommand() - ).await() - } + ) - suspend fun getExtendedVersionInfo(): GetExtendedVersionResponse { - return sendRootModeCommandAndReceiveResponse( + suspend fun getExtendedVersionInfo(): GetExtendedVersionResponse = + rootMessageChannel.sendCommandAndReceiveResponse( GetExtendedVersionCommand() - ).await() - } - - private inline fun sendRootModeCommandAndReceiveResponse(command: RootCommand): Single = - rootMessageChannel.sendRootModeCommandAndReceiveResponse(command) - .handleErrorsWith(responseErrorHandler) - - private inline fun sendMainModeCommandAndReceiveResponse(command: OutgoingMainMessage): Single = - mainMessageChannel.sendMainModeCommandAndReceiveResponse(command) - .handleErrorsWith(responseErrorHandler) - + ) companion object { private const val CYPRESS_HIGHEST_LEGACY_API_MAJOR_VERSION = 1 diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/errorhandler/ResponseErrorHandler.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/errorhandler/ResponseErrorHandler.kt index b218c00d63..c311026a94 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/errorhandler/ResponseErrorHandler.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/errorhandler/ResponseErrorHandler.kt @@ -1,26 +1,30 @@ package com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler + import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.responses.CaptureFingerprintResponse import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.responses.GetImageResponse import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.events.Un20StateChangeEvent import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.responses.SetUn20OnResponse -import com.simprints.fingerprint.infra.scanner.v2.tools.reactive.ioScheduler -import io.reactivex.Scheduler -import io.reactivex.Single +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.withTimeout import java.io.IOException -import java.util.concurrent.TimeUnit -import java.util.concurrent.TimeoutException -/** - * For use in an Rx chain using [handleErrorsWith]. Adds a timeout and retries for incoming messages - * in accordance to a supplied [ResponseErrorHandlingStrategy]. - */ class ResponseErrorHandler( - val strategy: ResponseErrorHandlingStrategy, - private val timeOutScheduler: Scheduler = ioScheduler + val strategy: ResponseErrorHandlingStrategy ) { - - inline fun handle(source: Single): Single { + /** + * Handles the execution of a suspending block with a timeout based on the response type. + * + * This function determines the appropriate timeout for the given response type `T` and executes + * the provided suspending block within that timeout. If the block does not complete within the + * specified timeout, an `IOException` is thrown. + * + * @param T The type of the response. + * @param block The suspending block to be executed. + * @return The result of the suspending block. + * @throws IOException If the block does not complete within the specified timeout. + */ + suspend inline fun handle(crossinline block: suspend () -> T): T { val timeOut = when (T::class.java) { SetUn20OnResponse::class.java -> strategy.setUn20ResponseTimeOut Un20StateChangeEvent::class.java -> strategy.un20StateChangeEventTimeOut @@ -28,33 +32,20 @@ class ResponseErrorHandler( GetImageResponse::class.java -> strategy.getImageResponseTimeOut else -> strategy.generalTimeOutMs } - return handle(source, timeOut, strategy.retryTimes) + return handle(block, timeOut) } - fun handle(source: Single, timeOut: Long?, retryTimes: Long?): Single = - source - .run { - if (timeOut != null) timeout(timeOut, TimeUnit.MILLISECONDS, timeOutScheduler) else this - } - .run { - if (retryTimes != null) retry(retryTimes) { it is TimeoutException } else this - } - .wrapExceptionsIfNecessary(timeOut, retryTimes) - - private fun Single.wrapExceptionsIfNecessary(timeOut: Long?, retryTimes: Long?) = - onErrorResumeNext { - when (it) { - is TimeoutException -> Single.error( - IOException("Scanner did not respond after $timeOut milliseconds (with ${ - retryTimes - ?: "no" - } retries)") - ) - - else -> Single.error(it) - } + suspend inline fun handle( + crossinline block: suspend () -> T, + timeoutDelay: Long, + ): T = try { + withTimeout(timeoutDelay) { + block() } + } catch (_: TimeoutCancellationException) { + throw IOException("Scanner did not respond after $timeoutDelay milliseconds") + } } -inline fun Single.handleErrorsWith(handler: ResponseErrorHandler) = - handler.handle(this) + + diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/errorhandler/ResponseErrorHandlingStrategy.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/errorhandler/ResponseErrorHandlingStrategy.kt index 847e13524a..aadcb99ed2 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/errorhandler/ResponseErrorHandlingStrategy.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/errorhandler/ResponseErrorHandlingStrategy.kt @@ -1,29 +1,19 @@ package com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler data class ResponseErrorHandlingStrategy( - val retryTimes: Long? = null, - val generalTimeOutMs: Long? = null, - val setUn20ResponseTimeOut: Long? = null, - val un20StateChangeEventTimeOut: Long? = null, - val captureFingerprintResponseTimeOut: Long? = null, - val getImageResponseTimeOut: Long? = null + val generalTimeOutMs: Long, + val setUn20ResponseTimeOut: Long, + val un20StateChangeEventTimeOut: Long, + val captureFingerprintResponseTimeOut: Long, + val getImageResponseTimeOut: Long ) { companion object { val DEFAULT = ResponseErrorHandlingStrategy( - retryTimes = null, generalTimeOutMs = 15000, setUn20ResponseTimeOut = 15000, un20StateChangeEventTimeOut = 15000, captureFingerprintResponseTimeOut = 5000, getImageResponseTimeOut = 15000 ) - - val NONE = ResponseErrorHandlingStrategy( - retryTimes = null, - generalTimeOutMs = null, - un20StateChangeEventTimeOut = null, - captureFingerprintResponseTimeOut = null, - getImageResponseTimeOut = null - ) } } diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/cypress/CypressOtaController.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/cypress/CypressOtaController.kt index 7b4acd7a3a..b04bf114dd 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/cypress/CypressOtaController.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/cypress/CypressOtaController.kt @@ -1,7 +1,6 @@ package com.simprints.fingerprint.infra.scanner.v2.scanner.ota.cypress import com.simprints.fingerprint.infra.scanner.v2.channel.CypressOtaMessageChannel -import com.simprints.fingerprint.infra.scanner.v2.domain.cypressota.CypressOtaCommand import com.simprints.fingerprint.infra.scanner.v2.domain.cypressota.CypressOtaMessageProtocol import com.simprints.fingerprint.infra.scanner.v2.domain.cypressota.CypressOtaResponse import com.simprints.fingerprint.infra.scanner.v2.domain.cypressota.CypressOtaResponseType @@ -10,15 +9,13 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.cypressota.commands.Pre import com.simprints.fingerprint.infra.scanner.v2.domain.cypressota.commands.SendImageChunk import com.simprints.fingerprint.infra.scanner.v2.domain.cypressota.commands.VerifyImageCommand import com.simprints.fingerprint.infra.scanner.v2.exceptions.ota.OtaFailedException -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandler -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.handleErrorsWith import com.simprints.fingerprint.infra.scanner.v2.tools.crc.Crc32Calculator import com.simprints.fingerprint.infra.scanner.v2.tools.primitives.chunked -import com.simprints.fingerprint.infra.scanner.v2.tools.reactive.completable -import io.reactivex.Completable -import io.reactivex.Observable -import io.reactivex.Single -import io.reactivex.rxkotlin.toObservable +import com.simprints.fingerprint.infra.scanner.v2.tools.primitives.pairWithProgress +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onCompletion /** * Conducts OTA for the Cypress module in accordance to @@ -27,55 +24,47 @@ import io.reactivex.rxkotlin.toObservable */ class CypressOtaController(private val crc32Calculator: Crc32Calculator) { - private inline fun sendCypressOtaModeCommandAndReceiveResponse( - cypressOtaMessageChannel: CypressOtaMessageChannel, - errorHandler: ResponseErrorHandler, - command: CypressOtaCommand - ): Single = - cypressOtaMessageChannel.sendCypressOtaModeCommandAndReceiveResponse(command) - .handleErrorsWith(errorHandler) - fun program( - cypressOtaMessageChannel: CypressOtaMessageChannel, - errorHandler: ResponseErrorHandler, - firmwareBinFile: ByteArray - ): Observable = - sendPrepareDownloadCommand(cypressOtaMessageChannel, errorHandler) - .andThen( - sendDownloadCommand(cypressOtaMessageChannel, errorHandler, firmwareBinFile.size) - ) - .andThen( - createFirmwareChunks(firmwareBinFile) - .pairWithProgress() - .toObservable() - ) - .concatMap { (chunk, progress) -> - sendImageChunk(cypressOtaMessageChannel, errorHandler, chunk) - .andThen(Observable.just(progress)) - } - .concatWith( - sendVerifyImageCommand(cypressOtaMessageChannel, errorHandler, crc32Calculator.calculateCrc32(firmwareBinFile)) + suspend fun program( + cypressOtaMessageChannel: CypressOtaMessageChannel, firmwareBinFile: ByteArray + ): Flow { + sendPrepareDownloadCommand(cypressOtaMessageChannel) + sendDownloadCommand(cypressOtaMessageChannel, firmwareBinFile.size) + val chunks = createFirmwareChunks(firmwareBinFile).pairWithProgress() + return chunks.asFlow().map { (chunk, progress) -> + sendImageChunk(cypressOtaMessageChannel, chunk) + progress + }.onCompletion { + //verify OTA is OK + sendVerifyImageCommand( + cypressOtaMessageChannel, crc32Calculator.calculateCrc32(firmwareBinFile) ) + } + } - private fun sendPrepareDownloadCommand(cypressOtaMessageChannel: CypressOtaMessageChannel, errorHandler: ResponseErrorHandler): Completable = - sendCypressOtaModeCommandAndReceiveResponse(cypressOtaMessageChannel, errorHandler, - PrepareDownloadCommand() - ).verifyResponseIs(CypressOtaResponseType.OK, "PrepareDownloadCommand") + private suspend fun sendPrepareDownloadCommand( + cypressOtaMessageChannel: CypressOtaMessageChannel + ) = cypressOtaMessageChannel + .sendCommandAndReceiveResponse(PrepareDownloadCommand()) + .verifyResponseIs(CypressOtaResponseType.OK, "PrepareDownloadCommand") - private fun sendDownloadCommand(cypressOtaMessageChannel: CypressOtaMessageChannel, errorHandler: ResponseErrorHandler, imageSize: Int): Completable = - sendCypressOtaModeCommandAndReceiveResponse(cypressOtaMessageChannel, errorHandler, - DownloadCommand(imageSize) - ).verifyResponseIs(CypressOtaResponseType.OK, "DownloadCommand") + private suspend fun sendDownloadCommand( + cypressOtaMessageChannel: CypressOtaMessageChannel, imageSize: Int + ) = cypressOtaMessageChannel + .sendCommandAndReceiveResponse(DownloadCommand(imageSize)) + .verifyResponseIs(CypressOtaResponseType.OK, "DownloadCommand") - private fun sendImageChunk(cypressOtaMessageChannel: CypressOtaMessageChannel, errorHandler: ResponseErrorHandler, chunk: ByteArray): Completable = - sendCypressOtaModeCommandAndReceiveResponse(cypressOtaMessageChannel, errorHandler, - SendImageChunk(chunk) - ).verifyResponseIs(CypressOtaResponseType.CONTINUE, "SendImageChunk") + private suspend fun sendImageChunk( + cypressOtaMessageChannel: CypressOtaMessageChannel, chunk: ByteArray + ) = cypressOtaMessageChannel + .sendCommandAndReceiveResponse(SendImageChunk(chunk)) + .verifyResponseIs(CypressOtaResponseType.CONTINUE, "SendImageChunk") - private fun sendVerifyImageCommand(cypressOtaMessageChannel: CypressOtaMessageChannel, errorHandler: ResponseErrorHandler, crc32: Int): Completable = - sendCypressOtaModeCommandAndReceiveResponse(cypressOtaMessageChannel, errorHandler, - VerifyImageCommand(crc32) - ).verifyResponseIs(CypressOtaResponseType.OK, "VerifyImageCommand") + private suspend fun sendVerifyImageCommand( + cypressOtaMessageChannel: CypressOtaMessageChannel, crc32: Int + ) = cypressOtaMessageChannel + .sendCommandAndReceiveResponse(VerifyImageCommand(crc32)) + .verifyResponseIs(CypressOtaResponseType.OK, "VerifyImageCommand") private fun createFirmwareChunks(firmwareBinFile: ByteArray): List { // For some unknown reason, the first payload can only be 16 bytes @@ -85,17 +74,12 @@ class CypressOtaController(private val crc32Calculator: Crc32Calculator) { return listOf(firstPayload) + rest.chunked(CypressOtaMessageProtocol.MAX_PAYLOAD_SIZE) } - private fun List.pairWithProgress(): List> = - mapIndexed { index, chunk -> - Pair(chunk, (index + 1).toFloat() / this.size.toFloat()) - } - - private fun Single.verifyResponseIs(type: CypressOtaResponseType, commandName: String): Completable = - flatMapCompletable { - completable { - if (it.type != type) { - throw OtaFailedException("Received unexpected $type response during Cypress OTA in response to $commandName") - } - } + private fun CypressOtaResponse.verifyResponseIs( + type: CypressOtaResponseType, + commandName: String + ) { + if (this.type != type) { + throw OtaFailedException("Received unexpected $type response during Cypress OTA in response to $commandName") } + } } diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/stm/StmOtaController.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/stm/StmOtaController.kt index 4738d8a1aa..e77facc1e8 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/stm/StmOtaController.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/stm/StmOtaController.kt @@ -1,9 +1,7 @@ package com.simprints.fingerprint.infra.scanner.v2.scanner.ota.stm import com.simprints.fingerprint.infra.scanner.v2.channel.StmOtaMessageChannel -import com.simprints.fingerprint.infra.scanner.v2.domain.stmota.StmOtaCommand import com.simprints.fingerprint.infra.scanner.v2.domain.stmota.StmOtaMessageProtocol -import com.simprints.fingerprint.infra.scanner.v2.domain.stmota.StmOtaResponse import com.simprints.fingerprint.infra.scanner.v2.domain.stmota.commands.EraseMemoryAddressCommand import com.simprints.fingerprint.infra.scanner.v2.domain.stmota.commands.EraseMemoryStartCommand import com.simprints.fingerprint.infra.scanner.v2.domain.stmota.commands.GoAddressCommand @@ -14,18 +12,14 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.stmota.commands.WriteMe import com.simprints.fingerprint.infra.scanner.v2.domain.stmota.commands.WriteMemoryStartCommand import com.simprints.fingerprint.infra.scanner.v2.domain.stmota.responses.CommandAcknowledgement import com.simprints.fingerprint.infra.scanner.v2.exceptions.ota.OtaFailedException -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandler -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.handleErrorsWith -import com.simprints.fingerprint.infra.scanner.v2.scanner.ota.stm.StmOtaController.Companion.GO_ADDRESS -import com.simprints.fingerprint.infra.scanner.v2.scanner.ota.stm.StmOtaController.Companion.START_ADDRESS import com.simprints.fingerprint.infra.scanner.v2.tools.primitives.byteArrayOf import com.simprints.fingerprint.infra.scanner.v2.tools.primitives.chunked import com.simprints.fingerprint.infra.scanner.v2.tools.primitives.toByteArray -import com.simprints.fingerprint.infra.scanner.v2.tools.reactive.completable -import com.simprints.fingerprint.infra.scanner.v2.tools.reactive.single -import io.reactivex.Completable -import io.reactivex.Observable -import io.reactivex.Single +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onCompletion + /** * Conducts OTA for the STM module in accordance to @@ -39,90 +33,88 @@ import io.reactivex.Single */ class StmOtaController { - private inline fun sendStmOtaModeCommandAndReceiveResponse( - stmOtaMessageChannel: StmOtaMessageChannel, - errorHandler: ResponseErrorHandler, - command: StmOtaCommand - ): Single = - stmOtaMessageChannel.sendStmOtaModeCommandAndReceiveResponse(command) - .handleErrorsWith(errorHandler) - /** * @throws OtaFailedException if received a NACK when communicating with STM */ - fun program(stmOtaMessageChannel: StmOtaMessageChannel, errorHandler: ResponseErrorHandler, firmwareBinFile: ByteArray): Observable = - sendInitBootloaderCommand(stmOtaMessageChannel, errorHandler) - .andThen(eraseMemory(stmOtaMessageChannel, errorHandler)) - .andThen(calculateFirmwareFileChunks(firmwareBinFile)) + suspend fun program( + stmOtaMessageChannel: StmOtaMessageChannel, + firmwareBinFile: ByteArray + ): Flow { + sendInitBootloaderCommand(stmOtaMessageChannel) + eraseMemory(stmOtaMessageChannel) + val chunks = createFirmwareChunks(firmwareBinFile) + return chunks .pairWithProgress() - .flattenAsObservable { it } - .concatMap { (chunk, progress) -> - sendOtaPacket(stmOtaMessageChannel, errorHandler, chunk) - .andThen(Observable.just(progress)) - } - .concatWith(sendGoCommandAndAddress(stmOtaMessageChannel, errorHandler)) + .asFlow().map { (chunk, progress) -> + sendOtaPacket(stmOtaMessageChannel, chunk) + progress + }.onCompletion { sendGoCommandAndAddress(stmOtaMessageChannel) } + } - private fun sendInitBootloaderCommand(stmOtaMessageChannel: StmOtaMessageChannel, errorHandler: ResponseErrorHandler): Completable = - sendStmOtaModeCommandAndReceiveResponse(stmOtaMessageChannel, errorHandler, + private fun List.pairWithProgress() = + mapIndexed { index, chunk -> + Pair(chunk, (index + 1).toFloat() / this.size.toFloat()) + } + + + private suspend fun sendInitBootloaderCommand( + stmOtaMessageChannel: StmOtaMessageChannel, + ) { + stmOtaMessageChannel.sendCommandAndReceiveResponse( InitBootloaderCommand() ).verifyResponseIsAck() + } - private fun eraseMemory(stmOtaMessageChannel: StmOtaMessageChannel, errorHandler: ResponseErrorHandler): Completable = - sendStmOtaModeCommandAndReceiveResponse(stmOtaMessageChannel, errorHandler, + private suspend fun eraseMemory( + stmOtaMessageChannel: StmOtaMessageChannel, + ) { + stmOtaMessageChannel.sendCommandAndReceiveResponse( EraseMemoryStartCommand() - ).verifyResponseIsAck().andThen( - sendStmOtaModeCommandAndReceiveResponse(stmOtaMessageChannel, errorHandler, - EraseMemoryAddressCommand(ERASE_ALL_ADDRESS) - ) ).verifyResponseIsAck() + stmOtaMessageChannel.sendCommandAndReceiveResponse( + EraseMemoryAddressCommand(ERASE_ALL_ADDRESS) + ).verifyResponseIsAck() + } - private fun calculateFirmwareFileChunks(firmwareBinFile: ByteArray): Single> = - single { - firmwareBinFile.chunked(MAX_STM_OTA_CHUNK_SIZE) - .mapIndexed { idx, chunk -> - val addressInt = START_ADDRESS + idx * MAX_STM_OTA_CHUNK_SIZE - val address = addressInt.toByteArray(StmOtaMessageProtocol.byteOrder) - FirmwareByteChunk(address, chunk) - } - } - - private fun Single>.pairWithProgress() = - map { chunkList -> - chunkList.mapIndexed { index, chunk -> - Pair(chunk, (index + 1).toFloat() / chunkList.size.toFloat()) + private fun createFirmwareChunks(firmwareBinFile: ByteArray): List = + firmwareBinFile.chunked(MAX_STM_OTA_CHUNK_SIZE) + .mapIndexed { idx, chunk -> + val addressInt = START_ADDRESS + idx * MAX_STM_OTA_CHUNK_SIZE + val address = addressInt.toByteArray(StmOtaMessageProtocol.byteOrder) + FirmwareByteChunk(address, chunk) } - } - private fun sendOtaPacket(stmOtaMessageChannel: StmOtaMessageChannel, errorHandler: ResponseErrorHandler, firmwareByteChunk: FirmwareByteChunk): Completable = - sendStmOtaModeCommandAndReceiveResponse(stmOtaMessageChannel, errorHandler, + private suspend fun sendOtaPacket( + stmOtaMessageChannel: StmOtaMessageChannel, + firmwareByteChunk: FirmwareByteChunk + ) { + stmOtaMessageChannel.sendCommandAndReceiveResponse( WriteMemoryStartCommand() - ).verifyResponseIsAck().andThen( - sendStmOtaModeCommandAndReceiveResponse(stmOtaMessageChannel, errorHandler, - WriteMemoryAddressCommand(firmwareByteChunk.address) - ) - ).verifyResponseIsAck().andThen( - sendStmOtaModeCommandAndReceiveResponse(stmOtaMessageChannel, errorHandler, - WriteMemoryDataCommand(firmwareByteChunk.data) - ) ).verifyResponseIsAck() + stmOtaMessageChannel.sendCommandAndReceiveResponse( + WriteMemoryAddressCommand(firmwareByteChunk.address) + ).verifyResponseIsAck() + stmOtaMessageChannel.sendCommandAndReceiveResponse( + WriteMemoryDataCommand(firmwareByteChunk.data) + ).verifyResponseIsAck() + } - private fun sendGoCommandAndAddress(stmOtaMessageChannel: StmOtaMessageChannel, errorHandler: ResponseErrorHandler): Completable = - sendStmOtaModeCommandAndReceiveResponse(stmOtaMessageChannel, errorHandler, - GoCommand() - ).verifyResponseIsAck().andThen( - stmOtaMessageChannel.outgoing.sendMessage( // The ACK sometimes doesn't make it back before the Cypress module disconnects - GoAddressCommand(GO_ADDRESS.toByteArray(StmOtaMessageProtocol.byteOrder)) - ) + private suspend fun sendGoCommandAndAddress( + stmOtaMessageChannel: StmOtaMessageChannel, + ) { + stmOtaMessageChannel + .sendCommandAndReceiveResponse(GoCommand()) + .verifyResponseIsAck() + stmOtaMessageChannel.outgoing.sendMessage( // The ACK sometimes doesn't make it back before the Cypress module disconnects + GoAddressCommand(GO_ADDRESS.toByteArray(StmOtaMessageProtocol.byteOrder)) ) + } - private fun Single.verifyResponseIsAck(): Completable = - flatMapCompletable { - completable { - if (it.kind != CommandAcknowledgement.Kind.ACK) { - throw OtaFailedException("Received NACK response during STM OTA") - } - } + private fun CommandAcknowledgement.verifyResponseIsAck() { + if (this.kind != CommandAcknowledgement.Kind.ACK) { + throw OtaFailedException("Received NACK response during STM OTA") } + } companion object { val ERASE_ALL_ADDRESS = byteArrayOf(0xFF, 0xFF) diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/un20/Un20OtaController.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/un20/Un20OtaController.kt index b639e92341..b659c411e2 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/un20/Un20OtaController.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/un20/Un20OtaController.kt @@ -1,8 +1,6 @@ package com.simprints.fingerprint.infra.scanner.v2.scanner.ota.un20 import com.simprints.fingerprint.infra.scanner.v2.channel.MainMessageChannel -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.Un20Command -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.Un20Response import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.commands.StartOtaCommand import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.commands.VerifyOtaCommand import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.commands.WriteOtaChunkCommand @@ -11,76 +9,71 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.respo import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.responses.VerifyOtaResponse import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.responses.WriteOtaChunkResponse import com.simprints.fingerprint.infra.scanner.v2.exceptions.ota.OtaFailedException -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandler -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.handleErrorsWith import com.simprints.fingerprint.infra.scanner.v2.tools.crc.Crc32Calculator import com.simprints.fingerprint.infra.scanner.v2.tools.primitives.chunked -import com.simprints.fingerprint.infra.scanner.v2.tools.reactive.completable -import io.reactivex.Completable -import io.reactivex.Observable -import io.reactivex.Single -import io.reactivex.rxkotlin.toObservable +import com.simprints.fingerprint.infra.scanner.v2.tools.primitives.pairWithProgress +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onCompletion import java.util.UUID class Un20OtaController(private val crc32Calculator: Crc32Calculator) { - private inline fun sendUn20CommandAndReceiveResponse( + suspend fun program( mainMessageChannel: MainMessageChannel, - errorHandler: ResponseErrorHandler, - command: Un20Command - ): Single = - mainMessageChannel.sendMainModeCommandAndReceiveResponse(command) - .handleErrorsWith(errorHandler) + firmwareBinFile: ByteArray + ): Flow { + startOta(mainMessageChannel) + val chunks = firmwareBinFile.chunked(MAX_UN20_OTA_CHUNK_SIZE) + return chunks.pairWithProgress().asFlow().map { (chunk, progress) -> + writeOtaChunk(mainMessageChannel, chunk) + progress + }.onCompletion { + verifyOta(mainMessageChannel, crc32Calculator.calculateCrc32(firmwareBinFile)) + } + } - fun program( + private suspend fun startOta( + mainMessageChannel: MainMessageChannel + ) = mainMessageChannel.sendCommandAndReceiveResponse( + StartOtaCommand(UUID.randomUUID().toString()) + ).verifyResultOk() + + private suspend fun writeOtaChunk( mainMessageChannel: MainMessageChannel, - errorHandler: ResponseErrorHandler, - firmwareBinFile: ByteArray - ): Observable = - startOta(mainMessageChannel, errorHandler) - .andThen( - firmwareBinFile.chunked(MAX_UN20_OTA_CHUNK_SIZE) - .pairWithProgress() - .toObservable() - ) - .concatMap { (chunk, progress) -> - writeOtaChunk(mainMessageChannel, errorHandler, chunk) - .andThen(Observable.just(progress)) - } - .concatWith( - verifyOta(mainMessageChannel, errorHandler, crc32Calculator.calculateCrc32(firmwareBinFile)) - ) + chunk: ByteArray + ) = mainMessageChannel.sendCommandAndReceiveResponse( + WriteOtaChunkCommand(chunk) + ).verifyResultOk() - private fun startOta(mainMessageChannel: MainMessageChannel, errorHandler: ResponseErrorHandler): Completable = - sendUn20CommandAndReceiveResponse(mainMessageChannel, errorHandler, - StartOtaCommand(UUID.randomUUID().toString()) - ).verifyResultOk { operationResultCode } + private suspend fun verifyOta( + mainMessageChannel: MainMessageChannel, crc32: Int + ) = mainMessageChannel.sendCommandAndReceiveResponse( + VerifyOtaCommand(crc32) + ).verifyResultOk() - private fun writeOtaChunk(mainMessageChannel: MainMessageChannel, errorHandler: ResponseErrorHandler, chunk: ByteArray): Completable = - sendUn20CommandAndReceiveResponse(mainMessageChannel, errorHandler, - WriteOtaChunkCommand(chunk) - ).verifyResultOk { operationResultCode } - private fun verifyOta(mainMessageChannel: MainMessageChannel, errorHandler: ResponseErrorHandler, crc32: Int): Completable = - sendUn20CommandAndReceiveResponse(mainMessageChannel, errorHandler, - VerifyOtaCommand(crc32) - ).verifyResultOk { operationResultCode } + private fun StartOtaResponse.verifyResultOk() { + if (operationResultCode != OperationResultCode.OK) { + throw OtaFailedException("Received unexpected response code: $operationResultCode during UN20 OTA inside response ${this::class.java.simpleName}") + } + } - private fun List.pairWithProgress(): List> = - mapIndexed { index, chunk -> - Pair(chunk, (index + 1).toFloat() / this.size.toFloat()) + private fun WriteOtaChunkResponse.verifyResultOk() { + if (operationResultCode != OperationResultCode.OK) { + throw OtaFailedException("Received unexpected response code: $operationResultCode during UN20 OTA inside response ${this::class.java.simpleName}") } + } - private inline fun Single.verifyResultOk(crossinline resultCode: R.() -> OperationResultCode): Completable = - flatMapCompletable { - completable { - if (it.resultCode() != OperationResultCode.OK) { - throw OtaFailedException("Received unexpected response code: ${it.resultCode()} during UN20 OTA inside response ${R::class.java.simpleName}") - } - } + private fun VerifyOtaResponse.verifyResultOk() { + if (operationResultCode != OperationResultCode.OK) { + throw OtaFailedException("Received unexpected response code: $operationResultCode during UN20 OTA inside response ${this::class.java.simpleName}") } + } companion object { const val MAX_UN20_OTA_CHUNK_SIZE = 894 } } + diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/tools/WrapErrorFromScanner.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/tools/WrapErrorFromScanner.kt index 5bf45345cc..fee094a2d9 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/tools/WrapErrorFromScanner.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/tools/WrapErrorFromScanner.kt @@ -6,8 +6,10 @@ import com.simprints.fingerprint.infra.scanner.v2.exceptions.ota.OtaFailedExcept import com.simprints.fingerprint.infra.scanner.v2.exceptions.state.NotConnectedException import com.simprints.infra.logging.Simber import java.io.IOException +import kotlin.coroutines.cancellation.CancellationException fun wrapErrorFromScanner(e: Throwable): Throwable = when (e) { + is CancellationException -> e // Propagate cancellation is NotConnectedException, is IOException -> { // Disconnected or timed-out communications with Scanner Simber.d( @@ -31,3 +33,12 @@ fun wrapErrorFromScanner(e: Throwable): Throwable = when (e) { e } } + +suspend fun runWithErrorWrapping(block: suspend () -> T): T { + return try { + block() + } catch (e: Exception) { + throw wrapErrorFromScanner(e) + } + +} diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/tools/primitives/ByteArrayTools.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/tools/primitives/ByteArrayTools.kt index 251f9be72b..a9d988c735 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/tools/primitives/ByteArrayTools.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/tools/primitives/ByteArrayTools.kt @@ -34,3 +34,7 @@ fun ByteArray.chunked(size: Int) = toList().chunked(size).map { it.toByteArray() fun ByteArray.xorAll() = reduce { acc, byte -> acc xor byte } fun ByteArray.nxorAll() = xorAll().inv() + +fun List.pairWithProgress(): List> = mapIndexed { index, chunk -> + Pair(chunk, (index + 1).toFloat() / this.size.toFloat()) +} diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/tools/reactive/RxTools.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/tools/reactive/RxTools.kt index 3771203d8d..73d6219c10 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/tools/reactive/RxTools.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/v2/tools/reactive/RxTools.kt @@ -2,7 +2,6 @@ package com.simprints.fingerprint.infra.scanner.v2.tools.reactive import io.reactivex.Completable import io.reactivex.Flowable -import io.reactivex.Maybe import io.reactivex.Single import io.reactivex.flowables.ConnectableFlowable import io.reactivex.rxkotlin.Singles @@ -22,28 +21,14 @@ fun completable(function: () -> Unit): Completable = Completable.fromAction { function.invoke() } -inline fun Flowable<*>.filterCast( - crossinline predicate: (R) -> Boolean = { true } -) = - this.filter { it is R && predicate(it) } - .map { it as R } +inline fun Flowable<*>.filterCast() = this.filter { it is R }.map { it as R } -fun Single<*>.completeOnceReceived(): Completable = - this.ignoreElement() fun Flowable.subscribeOnIoAndPublish(): ConnectableFlowable = this.subscribeOn(ioScheduler).publish() fun Completable.doSimultaneously(single: Single): Single = Singles.zip(single, this.toSingleDefault(Unit)) { value, _ -> value } -fun Single.mapToMaybeEmptyIfNull(block: (T) -> R?): Maybe = - flatMapMaybe { - val value: R? = block(it) - if (value != null) { - Maybe.just(value) - } else { - Maybe.empty() - } - } + //Todo Make it more generic by injecting scheduler once refactoring the scanner module val ioScheduler = Dispatchers.IO.asScheduler() diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/wrapper/ScannerFactory.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/wrapper/ScannerFactory.kt index a059003532..562d48d35a 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/wrapper/ScannerFactory.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/wrapper/ScannerFactory.kt @@ -65,7 +65,7 @@ class ScannerFactory @Inject internal constructor( } FingerprintConfiguration.VeroGeneration.VERO_2 -> { - scannerV2 = ScannerV2.create() + scannerV2 = ScannerV2.create(ioDispatcher) scannerWrapper = createScannerWrapperV2(macAddress) // Create OTA wrapper for V2 scanner only as V1 scanner doesn't support OTA scannerOtaOperationsWrapper = createScannerOtaOperationsWrapper(macAddress) diff --git a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/wrapper/ScannerWrapperV2.kt b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/wrapper/ScannerWrapperV2.kt index 7a610e8d6e..ddc334bd7f 100644 --- a/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/wrapper/ScannerWrapperV2.kt +++ b/fingerprint/infra/scanner/src/main/java/com/simprints/fingerprint/infra/scanner/wrapper/ScannerWrapperV2.kt @@ -16,15 +16,17 @@ import com.simprints.fingerprint.infra.scanner.helpers.ScannerInitialSetupHelper import com.simprints.fingerprint.infra.scanner.v2.exceptions.state.NotConnectedException import com.simprints.fingerprint.infra.scanner.v2.scanner.ScannerExtendedInfoReaderHelper import com.simprints.fingerprint.infra.scanner.v2.tools.ScannerUiHelper +import com.simprints.fingerprint.infra.scanner.v2.tools.runWithErrorWrapping import com.simprints.fingerprint.infra.scanner.v2.tools.mapPotentialErrorFromScanner import com.simprints.fingerprint.infra.scanner.v2.tools.wrapErrorFromScanner import com.simprints.infra.config.store.models.FingerprintConfiguration -import io.reactivex.Completable import io.reactivex.Observer import io.reactivex.observers.DisposableObserver import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.rx2.await +import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext import com.simprints.fingerprint.infra.scanner.v2.scanner.Scanner as ScannerV2 @@ -46,21 +48,18 @@ internal class ScannerWrapperV2( * * @see setScannerInfoAndCheckAvailableOta */ - override fun versionInformation(): ScannerVersion = - scannerVersion ?: ScannerVersion( - hardwareVersion = ScannerExtendedInfoReaderHelper.UNKNOWN_HARDWARE_VERSION, - generation = ScannerGeneration.VERO_2, - firmware = ScannerFirmwareVersions.UNKNOWN, - ) + override fun versionInformation(): ScannerVersion = scannerVersion ?: ScannerVersion( + hardwareVersion = ScannerExtendedInfoReaderHelper.UNKNOWN_HARDWARE_VERSION, + generation = ScannerGeneration.VERO_2, + firmware = ScannerFirmwareVersions.UNKNOWN, + ) override fun batteryInformation(): BatteryInfo = batteryInfo ?: BatteryInfo.UNKNOWN - override fun isImageTransferSupported(): Boolean = true override suspend fun connect() = - connectionHelper.connectScanner(scannerV2, macAddress) - .mapPotentialErrorFromScanner() + connectionHelper.connectScanner(scannerV2, macAddress).mapPotentialErrorFromScanner() .collect() @@ -73,26 +72,19 @@ internal class ScannerWrapperV2( * @throws OtaFailedException */ override suspend fun setScannerInfoAndCheckAvailableOta(fingerprintSdk: FingerprintConfiguration.BioSdk) = - withContext(ioDispatcher) { - try { - scannerInitialSetupHelper.setupScannerWithOtaCheck( - fingerprintSdk, - scannerV2, - macAddress, - { scannerVersion = it }, - { batteryInfo = it } - ) - } catch (ex: Throwable) { - throw wrapErrorFromScanner(ex) - } - } + runWithErrorWrapping { + scannerInitialSetupHelper.setupScannerWithOtaCheck( + fingerprintSdk, + scannerV2, + macAddress, + { scannerVersion = it }, + { batteryInfo = it } + ) - override suspend fun disconnect() = withContext(ioDispatcher) { - try { - connectionHelper.disconnectScanner(scannerV2) - } catch (ex: Throwable) { - throw wrapErrorFromScanner(ex) } + + override suspend fun disconnect() = runWithErrorWrapping { + connectionHelper.disconnectScanner(scannerV2) } override fun isConnected() = scannerV2.isConnected() @@ -106,12 +98,8 @@ internal class ScannerWrapperV2( * @throws ScannerDisconnectedException * @throws UnexpectedScannerException */ - override suspend fun sensorWakeUp() = withContext(ioDispatcher) { - try { - scannerV2.ensureUn20State(true) - } catch (ex: Throwable) { - throw wrapErrorFromScanner(ex) - } + override suspend fun sensorWakeUp() = runWithErrorWrapping { + scannerV2.ensureUn20State(true) } @@ -125,64 +113,57 @@ internal class ScannerWrapperV2( * @throws UnexpectedScannerException * @throws NotConnectedException */ - override suspend fun sensorShutDown() = withContext(ioDispatcher) { - try { - scannerV2.ensureUn20State(false) - } catch (ex: Throwable) { - throw wrapErrorFromScanner(ex) - } + override suspend fun sensorShutDown() = runWithErrorWrapping { + scannerV2.ensureUn20State(false) } override fun isLiveFeedbackAvailable(): Boolean = true - override suspend fun startLiveFeedback() = withContext(ioDispatcher) { - (if (isLiveFeedbackAvailable()) { + override suspend fun startLiveFeedback(): Unit = runWithErrorWrapping { + if (isLiveFeedbackAvailable()) { scannerV2.setScannerLedStateOn() - .andThen(getImageQualityWhileSettingLEDState()) - .onErrorComplete() + // ignore exceptions from getImageQualityWhileSettingLEDState + runCatching { withContext(ioDispatcher) { getImageQualityWhileSettingLEDState(this) } } } else { - Completable.error(UnavailableVero2FeatureException(UnavailableVero2Feature.LIVE_FEEDBACK)) - }) - .await() + throw UnavailableVero2FeatureException(UnavailableVero2Feature.LIVE_FEEDBACK) + } } - private fun getImageQualityWhileSettingLEDState() = - scannerV2.getImageQualityPreview().flatMapCompletable { quality -> - scannerV2.setSmileLedState( - scannerUiHelper.deduceLedStateFromQualityForLiveFeedback( - quality + private suspend fun getImageQualityWhileSettingLEDState(scope: CoroutineScope) { + while (scope.isActive) { + scannerV2.getImageQualityPreview()?.let { quality -> + scannerV2.setSmileLedState( + scannerUiHelper.deduceLedStateFromQualityForLiveFeedback(quality) ) - ) - }.repeat() + } + // add a delay to prevent the scanner from being overwhelmed + delay(LIVE_FEEDBACK_DELAY_MS) + } + } @SuppressLint("CheckResult") - override suspend fun stopLiveFeedback(): Unit = withContext(ioDispatcher) { + override suspend fun stopLiveFeedback(): Unit = runWithErrorWrapping { if (isLiveFeedbackAvailable()) { - scannerV2 - .setSmileLedState(scannerUiHelper.turnedOffState()) - .onErrorComplete() - scannerV2 - .setScannerLedStateDefault() - .onErrorComplete() + scannerV2.setSmileLedState(scannerUiHelper.turnedOffState()) + scannerV2.setScannerLedStateDefault() } else { throw UnavailableVero2FeatureException(UnavailableVero2Feature.LIVE_FEEDBACK) } } - private suspend fun ScannerV2.ensureUn20State(desiredState: Boolean) = - withContext(ioDispatcher) { - getUn20Status().flatMapCompletable { actualState -> - when { - desiredState && !actualState -> turnUn20OnAndAwaitStateChangeEvent() - !desiredState && actualState -> turnUn20OffAndAwaitStateChangeEvent() - else -> Completable.complete() - } - }.await() + private suspend fun ScannerV2.ensureUn20State(desiredState: Boolean) = runWithErrorWrapping { + getUn20Status().let { actualState -> + when { + desiredState && !actualState -> turnUn20On() + !desiredState && actualState -> turnUn20Off() + } } + } - override suspend fun turnOffSmileLeds(): Unit = withContext(ioDispatcher) { - scannerV2.setSmileLedState(scannerUiHelper.turnedOffState()).onErrorComplete().await() + override suspend fun turnOffSmileLeds(): Unit = runWithErrorWrapping { + // No need to handle exceptions while updating the LED state + runCatching { scannerV2.setSmileLedState(scannerUiHelper.turnedOffState()) } } private val triggerListenerToObserverMap = @@ -207,16 +188,22 @@ internal class ScannerWrapperV2( } } - override suspend fun turnOnFlashingWhiteSmileLeds(): Unit = withContext(ioDispatcher) { - scannerV2.setSmileLedState(scannerUiHelper.whiteFlashingLedState()).onErrorComplete().await() + override suspend fun turnOnFlashingWhiteSmileLeds(): Unit = runWithErrorWrapping { + // No need to handle exceptions while updating the LED state + runCatching { scannerV2.setSmileLedState(scannerUiHelper.whiteFlashingLedState()) } } - override suspend fun setUiGoodCapture(): Unit = withContext(ioDispatcher) { - scannerV2.setSmileLedState(scannerUiHelper.goodScanLedState()).onErrorComplete().await() + override suspend fun setUiGoodCapture(): Unit = runWithErrorWrapping { + // No need to handle exceptions while updating the LED state + runCatching { scannerV2.setSmileLedState(scannerUiHelper.goodScanLedState()) } } + override suspend fun setUiBadCapture(): Unit = runWithErrorWrapping { + // No need to handle exceptions while updating the LED state + runCatching { scannerV2.setSmileLedState(scannerUiHelper.badScanLedState()) } + } - override suspend fun setUiBadCapture(): Unit = withContext(ioDispatcher) { - scannerV2.setSmileLedState(scannerUiHelper.badScanLedState()).onErrorComplete().await() + companion object { + private const val LIVE_FEEDBACK_DELAY_MS = 200L } } diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperFactoryTest.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperFactoryTest.kt index 9c2b198331..cecd6df0a9 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperFactoryTest.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperFactoryTest.kt @@ -2,7 +2,6 @@ package com.simprints.fingerprint.infra.scanner.capture import com.google.common.truth.Truth import com.simprints.fingerprint.infra.scanner.exceptions.unexpected.NullScannerException -import com.simprints.fingerprint.infra.scanner.v2.tools.ScannerUiHelper import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -18,7 +17,7 @@ class FingerprintCaptureWrapperFactoryTest { @Before fun setUp() { fingerprintCaptureWrapperFactory = - FingerprintCaptureWrapperFactory(UnconfinedTestDispatcher(), ScannerUiHelper(), mockk()) + FingerprintCaptureWrapperFactory(UnconfinedTestDispatcher(), mockk()) } @Test(expected = NullScannerException::class) diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperV2Test.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperV2Test.kt index c522c79b74..84ad11a767 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperV2Test.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/capture/FingerprintCaptureWrapperV2Test.kt @@ -12,17 +12,13 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.model import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.models.ImageData import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.models.TemplateData import com.simprints.fingerprint.infra.scanner.v2.scanner.Scanner -import com.simprints.fingerprint.infra.scanner.v2.tools.ScannerUiHelper import com.simprints.testtools.common.syntax.assertThrows import io.mockk.MockKAnnotations -import io.mockk.every +import io.mockk.coEvery +import io.mockk.coJustRun +import io.mockk.coVerify import io.mockk.impl.annotations.MockK -import io.mockk.verify -import io.reactivex.Completable -import io.reactivex.Maybe -import io.reactivex.Single import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -32,9 +28,6 @@ class FingerprintCaptureWrapperV2Test { @MockK private lateinit var scannerV2: Scanner - @MockK - private lateinit var scannerUiHelper: ScannerUiHelper - private lateinit var scannerWrapper: FingerprintCaptureWrapperV2 @MockK @@ -43,16 +36,14 @@ class FingerprintCaptureWrapperV2Test { @Before fun setup() { MockKAnnotations.init(this, relaxed = true) - scannerWrapper = - FingerprintCaptureWrapperV2(scannerV2, scannerUiHelper, UnconfinedTestDispatcher(),tracker) + scannerWrapper = FingerprintCaptureWrapperV2(scannerV2, tracker) } @Test fun `test acquireImageDistortionMatrixConfiguration success`() = runTest { val expectedResp = byteArrayOf(1, 2, 3) - every { scannerV2.acquireImageDistortionConfigurationMatrix() } returns Maybe.just( - expectedResp - ) + coEvery { scannerV2.acquireImageDistortionConfigurationMatrix() } returns expectedResp + val actualResponse = scannerWrapper.acquireImageDistortionMatrixConfiguration() assertThat(actualResponse).isEqualTo(expectedResp) } @@ -67,8 +58,8 @@ class FingerprintCaptureWrapperV2Test { 0x05, 0x06, 0x07, 0x08, 0x05, 0x06, 0x07, 0x08 ), 1 ) - every { scannerV2.captureFingerprint(any()) } returns Single.just(CaptureFingerprintResult.OK) - every { scannerV2.acquireUnprocessedImage(any()) } returns Maybe.just(imageData) + coEvery { scannerV2.captureFingerprint(any()) } returns CaptureFingerprintResult.OK + coEvery { scannerV2.acquireUnprocessedImage(any()) } returns imageData // When val actualResponse = scannerWrapper.acquireUnprocessedImage(Dpi(500)) // Then @@ -83,10 +74,8 @@ class FingerprintCaptureWrapperV2Test { fun `test acquireUnprocessedImage throws NoFingerDetectedException when scanner returns empty`() = runTest { // Given - every { scannerV2.acquireUnprocessedImage(any()) } returns Maybe.empty() - every { scannerV2.captureFingerprint(any()) } returns Single.just( - CaptureFingerprintResult.OK - ) + coEvery { scannerV2.acquireUnprocessedImage(any()) } returns null + coEvery { scannerV2.captureFingerprint(any()) } returns CaptureFingerprintResult.OK // When scannerWrapper.acquireUnprocessedImage(Dpi(500)) // Then throw NoFingerDetectedException @@ -95,79 +84,54 @@ class FingerprintCaptureWrapperV2Test { @Test fun `should throw illegal argument exception when capture DPI is null`() = runTest { - assertThrows { - scannerWrapper.acquireFingerprintTemplate( - null, - 1000, - 50, - false - ) + assertThrows { + scannerWrapper.acquireFingerprintTemplate(null, 1000, 50, false) } } @Test fun `should throw illegal argument exception when capture DPI is less than 500`() = runTest { - assertThrows { - scannerWrapper.acquireFingerprintTemplate( - Dpi(499), - 1000, - 50, - false - ) + assertThrows { + scannerWrapper.acquireFingerprintTemplate(Dpi(499), 1000, 50, false) } } @Test fun `should throw illegal argument exception when capture DPI is greater than 1700`() = runTest { - assertThrows { - scannerWrapper.acquireFingerprintTemplate( - Dpi(1701), - 1000, - 50, - false - ) + assertThrows { + scannerWrapper.acquireFingerprintTemplate(Dpi(1701), 1000, 50, false) } } @Test fun `should throw corresponding errors when capture fingerprint result is not OK`() = runTest { - every { scannerV2.captureFingerprint(any()) } answers { - (Single.just(CaptureFingerprintResult.FINGERPRINT_NOT_FOUND)) + coEvery { scannerV2.captureFingerprint(any()) } answers { + (CaptureFingerprintResult.FINGERPRINT_NOT_FOUND) } andThenAnswer { - Single.just(CaptureFingerprintResult.DPI_UNSUPPORTED) + CaptureFingerprintResult.DPI_UNSUPPORTED } andThenAnswer { - Single.just(CaptureFingerprintResult.UNKNOWN_ERROR) + CaptureFingerprintResult.UNKNOWN_ERROR } - every { scannerV2.setSmileLedState(any()) } returns Completable.complete() - + coJustRun { scannerV2.setSmileLedState(any()) } // first throws NoFingerDetectedException assertThrows { scannerWrapper.acquireFingerprintTemplate( - Dpi(1300), - 1000, - 50, - false + Dpi(1300), 1000, 50, false ) } // and then throws UnexpectedScannerException assertThrows { scannerWrapper.acquireFingerprintTemplate( - Dpi(1300), - 1000, - 50, - false + Dpi(1300), 1000, 50, false ) } // and then throws UnknownScannerIssueException assertThrows { scannerWrapper.acquireFingerprintTemplate( - Dpi(1300), - 1000, - 50, - false + Dpi(1300), 1000, 50, false ) } } @@ -177,37 +141,32 @@ class FingerprintCaptureWrapperV2Test { fun `should return actual image data in ImageResponse when appropriate image-save strategy is provided and image data is returned from scanner`() = runTest { val expectedImageResponse = AcquireFingerprintImageResponse(imageBytes = byteArrayOf()) - every { scannerV2.acquireImage(any()) } returns Maybe.just( - ImageData(expectedImageResponse.imageBytes, 128) + coEvery { scannerV2.acquireImage(any()) } returns ImageData( + expectedImageResponse.imageBytes, 128 ) val actualImageResponse = scannerWrapper.acquireFingerprintImage() - assertThat(actualImageResponse.imageBytes).isEqualTo(expectedImageResponse.imageBytes) } @Test fun `should throw NoFingerDetectedException when trying to acquire fingerprint image and scanner returns a null ImageData`() = runTest { - every { scannerV2.acquireImage(any()) } returns Maybe.empty() - + coEvery { scannerV2.acquireImage(any()) } returns null assertThrows { scannerWrapper.acquireFingerprintImage() } } @Test(expected = UnexpectedScannerException::class) fun `should throw UnexpectedScannerException when DPI_UNSUPPORTED error is returned during capture`() = runTest { - every { + coEvery { scannerV2.captureFingerprint(any()) - } returns Single.just(CaptureFingerprintResult.DPI_UNSUPPORTED) - every { scannerV2.setSmileLedState(any()) } returns Completable.complete() - every { scannerV2.getImageQualityScore() } returns Maybe.empty() + } returns CaptureFingerprintResult.DPI_UNSUPPORTED + coJustRun { scannerV2.setSmileLedState(any()) } + coEvery { scannerV2.getImageQualityScore() } returns null // When scannerWrapper.acquireFingerprintTemplate( - Dpi(1300), - timeOutMs = 30000, - qualityThreshold = 7, - false + Dpi(1300), timeOutMs = 30000, qualityThreshold = 7, false ) } @@ -217,26 +176,19 @@ class FingerprintCaptureWrapperV2Test { runTest { val qualityThreshold = 50 val expectedCaptureResponse = AcquireFingerprintTemplateResponse( - template = byteArrayOf(), - "ISO_19794_2", - imageQualityScore = qualityThreshold + template = byteArrayOf(), "ISO_19794_2", imageQualityScore = qualityThreshold ) - every { scannerV2.getImageQualityScore() } returns Maybe.just(qualityThreshold) - every { scannerV2.setSmileLedState(any()) } returns Completable.complete() - every { scannerV2.captureFingerprint(any()) } answers { - Single.just(CaptureFingerprintResult.OK) + coEvery { scannerV2.getImageQualityScore() } returns qualityThreshold + coJustRun { scannerV2.setSmileLedState(any()) } + coEvery { scannerV2.captureFingerprint(any()) } answers { + CaptureFingerprintResult.OK } - every { scannerV2.acquireTemplate(any()) } returns Maybe.just( - TemplateData( - expectedCaptureResponse.template - ) + coEvery { scannerV2.acquireTemplate(any()) } returns TemplateData( + expectedCaptureResponse.template ) val actualResponse = scannerWrapper.acquireFingerprintTemplate( - Dpi(1300), - 1000, - qualityThreshold, - false + Dpi(1300), 1000, qualityThreshold, false ) assertThat(expectedCaptureResponse.template).isEqualTo(actualResponse.template) @@ -247,19 +199,16 @@ class FingerprintCaptureWrapperV2Test { fun `should throw NoFingerDetectedException when no fingerprint template is returned after fingerprint is captured`() = runTest { val qualityThreshold = 50 - every { scannerV2.getImageQualityScore() } returns Maybe.just(qualityThreshold) - every { scannerV2.setSmileLedState(any()) } returns Completable.complete() - every { scannerV2.captureFingerprint(any()) } answers { - (Single.just(CaptureFingerprintResult.OK)) + coEvery { scannerV2.getImageQualityScore() } returns qualityThreshold + coJustRun { scannerV2.setSmileLedState(any()) } + coEvery { scannerV2.captureFingerprint(any()) } answers { + (CaptureFingerprintResult.OK) } - every { scannerV2.acquireTemplate(any()) } returns Maybe.empty() + coEvery { scannerV2.acquireTemplate(any()) } returns null assertThrows { scannerWrapper.acquireFingerprintTemplate( - Dpi(1300), - 1000, - qualityThreshold, - false + Dpi(1300), 1000, qualityThreshold, false ) } } @@ -268,38 +217,30 @@ class FingerprintCaptureWrapperV2Test { fun `should extract template when captured fingerprint's image quality score is less than specified image quality_threshold and allowLowQualityExtraction is true`() = runTest { val qualityThreshold = 50 - every { scannerV2.getImageQualityScore() } returns Maybe.just(qualityThreshold - 10) - every { scannerV2.setSmileLedState(any()) } returns Completable.complete() - every { scannerV2.captureFingerprint(any()) } answers { - (Single.just(CaptureFingerprintResult.OK)) + coEvery { scannerV2.getImageQualityScore() } returns qualityThreshold - 10 + coJustRun { scannerV2.setSmileLedState(any()) } + coEvery { scannerV2.captureFingerprint(any()) } answers { + (CaptureFingerprintResult.OK) } - every { scannerV2.acquireTemplate(any()) } returns Maybe.just(TemplateData(byteArrayOf())) + coEvery { scannerV2.acquireTemplate(any()) } returns TemplateData(byteArrayOf()) scannerWrapper.acquireFingerprintTemplate( - Dpi(1300), - 1000, - qualityThreshold, - true + Dpi(1300), 1000, qualityThreshold, true ) - verify(exactly = 1) {scannerV2.acquireTemplate(any()) } + coVerify(exactly = 1) { scannerV2.acquireTemplate(any()) } } @Test fun `should throw NoFingerDetectedException when captured fingerprint's image quality score is less than no_image quality_threshold`() = runTest { - every { scannerV2.getImageQualityScore() } returns Maybe.empty() - every { scannerV2.setSmileLedState(any()) } returns Completable.complete() - every { scannerV2.captureFingerprint(any()) } answers { - (Single.just(CaptureFingerprintResult.OK)) - } + coEvery { scannerV2.getImageQualityScore() } returns null + coJustRun { scannerV2.setSmileLedState(any()) } + coEvery { scannerV2.captureFingerprint(any()) } returns (CaptureFingerprintResult.OK) assertThrows { scannerWrapper.acquireFingerprintTemplate( - Dpi(1300), - 1000, - 50, - false + Dpi(1300), 1000, 50, false ) } } diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/ConnectionHelperTest.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/ConnectionHelperTest.kt index 2c0683d75d..2ddbbf8e7c 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/ConnectionHelperTest.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/ConnectionHelperTest.kt @@ -12,9 +12,9 @@ import com.simprints.testtools.common.coroutines.TestCoroutineRule import io.mockk.Ordering import io.mockk.coVerify import io.mockk.every +import io.mockk.justRun import io.mockk.mockk import io.mockk.verify -import io.reactivex.Completable import kotlinx.coroutines.flow.collect import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -39,8 +39,8 @@ class ConnectionHelperTest { } private val mockScanner = mockk { - every { connect(any(), any()) } returns Completable.complete() - every { disconnect() } returns Completable.complete() + justRun { connect(any(), any()) } + justRun { disconnect() } } private val connectionHelper = ConnectionHelper(mockAdapter, testCoroutineRule.testCoroutineDispatcher) diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/CypressOtaHelperTest.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/CypressOtaHelperTest.kt index 32cd2b8f9a..8c6495b926 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/CypressOtaHelperTest.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/CypressOtaHelperTest.kt @@ -13,13 +13,12 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.UnifiedVers import com.simprints.fingerprint.infra.scanner.v2.scanner.Scanner import io.mockk.CapturingSlot import io.mockk.coEvery -import io.mockk.every +import io.mockk.coJustRun +import io.mockk.coVerify import io.mockk.mockk -import io.mockk.verify -import io.reactivex.Completable -import io.reactivex.Observable -import io.reactivex.Single +import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.last +import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest @@ -40,12 +39,12 @@ class CypressOtaHelperTest { coEvery { connectionHelperMock.reconnect(any(), any()) } answers {} - every { scannerMock.enterCypressOtaMode() } returns Completable.complete() - every { scannerMock.startCypressOta(any()) } returns Observable.fromIterable(OTA_PROGRESS_VALUES) - every { scannerMock.getVersionInformation() } returns Single.just(OLD_SCANNER_VERSION) - every { scannerMock.setVersionInformation(any()) } returns Completable.complete() - every { scannerMock.getCypressFirmwareVersion() } returns Single.just(OLD_CYPRESS_VERSION) - every { scannerMock.getCypressExtendedFirmwareVersion() } returns Single.just(NEW_CYPRESS_VERSION) + coJustRun { scannerMock.enterCypressOtaMode() } + coEvery { scannerMock.startCypressOta(any()) } returns OTA_PROGRESS_VALUES.asFlow() + coEvery { scannerMock.getVersionInformation() } returns OLD_SCANNER_VERSION + coJustRun { scannerMock.setVersionInformation(any()) } + coEvery { scannerMock.getCypressFirmwareVersion() } returns OLD_CYPRESS_VERSION + coEvery { scannerMock.getCypressExtendedFirmwareVersion() } returns NEW_CYPRESS_VERSION coEvery { firmwareFileManagerMock.getAvailableScannerFirmwareVersions() @@ -70,21 +69,21 @@ class CypressOtaHelperTest { .inOrder() val sentUnifiedVersion = CapturingSlot() - verify { scannerMock.setVersionInformation(capture(sentUnifiedVersion)) } + coVerify { scannerMock.setVersionInformation(capture(sentUnifiedVersion)) } assertThat(sentUnifiedVersion.captured.toScannerFirmwareVersions()) .isEqualTo(NEW_SCANNER_VERSION.firmwareVersions.toScannerFirmwareVersions()) } @Test(expected = ScannerV2OtaFailedException::class) - fun - cypressOtaFailsDuringTransfer_propagatesError() = runTest { + fun cypressOtaFailsDuringTransfer_propagatesError() = runTest { val progressValues = listOf(0.0f, 0.2f, 0.4f) - val expectedSteps = listOf(CypressOtaStep.EnteringOtaMode, CypressOtaStep.CommencingTransfer) + - progressValues.map { CypressOtaStep.TransferInProgress(it) } + val expectedSteps = + listOf(CypressOtaStep.EnteringOtaMode, CypressOtaStep.CommencingTransfer) + + progressValues.map { CypressOtaStep.TransferInProgress(it) } val error = ScannerV2OtaFailedException("oops!") - every { scannerMock.startCypressOta(any()) } returns - Observable.fromIterable(progressValues).concatWith(Observable.error(error)) + coEvery { scannerMock.startCypressOta(any()) } returns progressValues.asFlow() + .onCompletion { throw error } val otaFlow = cypressOtaHelper.performOtaSteps(scannerMock, "mac address", NEW_CYPRESS_VERSION_STRING) val actualItems = otaFlow @@ -123,15 +122,19 @@ class CypressOtaHelperTest { @Test(expected = OtaFailedException::class) fun cypressOtaFailsToValidate_throwsOtaError() = runTest { - val expectedSteps = listOf(CypressOtaStep.EnteringOtaMode, CypressOtaStep.CommencingTransfer) + - OTA_PROGRESS_VALUES.map { CypressOtaStep.TransferInProgress(it) } + - listOf(CypressOtaStep.ReconnectingAfterTransfer, CypressOtaStep.ValidatingNewFirmwareVersion) - - every { scannerMock.getCypressExtendedFirmwareVersion() } returns Single.just( + val expectedSteps = + listOf(CypressOtaStep.EnteringOtaMode, CypressOtaStep.CommencingTransfer) + + OTA_PROGRESS_VALUES.map { CypressOtaStep.TransferInProgress(it) } + + listOf( + CypressOtaStep.ReconnectingAfterTransfer, + CypressOtaStep.ValidatingNewFirmwareVersion + ) + + coEvery { scannerMock.getCypressExtendedFirmwareVersion() } returns CypressExtendedFirmwareVersion(versionAsString = "") - ) - val otaFlow = cypressOtaHelper.performOtaSteps(scannerMock, "mac address", NEW_CYPRESS_VERSION_STRING) + val otaFlow = + cypressOtaHelper.performOtaSteps(scannerMock, "mac address", NEW_CYPRESS_VERSION_STRING) val actualSteps = otaFlow.take(expectedSteps.size).toList() assertThat(actualSteps).containsExactlyElementsIn(expectedSteps).inOrder() diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/ScannerInitialSetupHelperTest.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/ScannerInitialSetupHelperTest.kt index 475349d21c..d333178389 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/ScannerInitialSetupHelperTest.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/ScannerInitialSetupHelperTest.kt @@ -19,11 +19,10 @@ import com.simprints.infra.config.store.models.Vero2Configuration import com.simprints.infra.config.sync.ConfigManager import com.simprints.testtools.common.syntax.assertThrows import io.mockk.coEvery +import io.mockk.coJustRun import io.mockk.coVerify import io.mockk.every import io.mockk.mockk -import io.reactivex.Completable -import io.reactivex.Single import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest @@ -48,13 +47,13 @@ class ScannerInitialSetupHelperTest { connectionHelperMock, batteryLevelChecker, configManager, - firmwareLocalDataSource, dispatcher + firmwareLocalDataSource ) @Before fun setup() { coEvery { firmwareLocalDataSource.getAvailableScannerFirmwareVersions() } returns LOCAL_SCANNER_VERSION - every { scannerMock.enterMainMode() } returns Completable.complete() + coJustRun { scannerMock.enterMainMode() } coEvery { connectionHelperMock.reconnect( eq(scannerMock), @@ -64,17 +63,17 @@ class ScannerInitialSetupHelperTest { } private fun setupScannerWithBatteryInfo(batteryInfo: BatteryInfo) { - every { scannerMock.getBatteryPercentCharge() } returns Single.just(batteryInfo.charge) - every { scannerMock.getBatteryVoltageMilliVolts() } returns Single.just(batteryInfo.voltage) - every { scannerMock.getBatteryCurrentMilliAmps() } returns Single.just(batteryInfo.current) - every { scannerMock.getBatteryTemperatureDeciKelvin() } returns Single.just(batteryInfo.temperature) + coEvery { scannerMock.getBatteryPercentCharge() } returns batteryInfo.charge + coEvery { scannerMock.getBatteryVoltageMilliVolts() } returns batteryInfo.voltage + coEvery { scannerMock.getBatteryCurrentMilliAmps() } returns batteryInfo.current + coEvery { scannerMock.getBatteryTemperatureDeciKelvin() } returns batteryInfo.temperature } @Test fun ifNoAvailableVersions_completesNormally() = runTest(dispatcher) { - every { scannerMock.getVersionInformation() } returns Single.just(SCANNER_VERSION_LOW) - every { vero2Configuration.firmwareVersions } returns mapOf() - every { batteryLevelChecker.isLowBattery() } returns false + coEvery { scannerMock.getVersionInformation() } returns SCANNER_VERSION_LOW + every { vero2Configuration.firmwareVersions } returns mapOf() + every { batteryLevelChecker.isLowBattery() } returns false setupScannerWithBatteryInfo(HIGH_BATTERY_INFO) @@ -91,7 +90,7 @@ class ScannerInitialSetupHelperTest { @Test fun ifVersionsContainsUnknowns_throwsCorrectOtaAvailableException() = runTest(dispatcher) { - every { scannerMock.getVersionInformation() } returns Single.just(SCANNER_VERSION_LOW) + coEvery { scannerMock.getVersionInformation() } returns SCANNER_VERSION_LOW every { vero2Configuration.firmwareVersions } returns mapOf( HARDWARE_VERSION to Vero2Configuration.Vero2FirmwareVersions( CYPRESS_VERSION_STRING, "", "" @@ -115,7 +114,7 @@ class ScannerInitialSetupHelperTest { @Test fun setupScannerWithOtaCheck_savesVersionAndBatteryInfo() = runTest(dispatcher) { - every { scannerMock.getVersionInformation() } returns Single.just(SCANNER_VERSION_LOW) + coEvery { scannerMock.getVersionInformation() } returns SCANNER_VERSION_LOW every { vero2Configuration.firmwareVersions } returns mapOf( HARDWARE_VERSION to Vero2Configuration.Vero2FirmwareVersions( "", "", "" @@ -140,7 +139,7 @@ class ScannerInitialSetupHelperTest { @Test fun ifAvailableVersionMatchesExistingVersion_completesNormally() = runTest(dispatcher) { - every { scannerMock.getVersionInformation() } returns Single.just(SCANNER_VERSION_LOW) + coEvery { scannerMock.getVersionInformation() } returns SCANNER_VERSION_LOW every { vero2Configuration.firmwareVersions } returns mapOf( HARDWARE_VERSION to Vero2Configuration.Vero2FirmwareVersions( SCANNER_VERSION_LOW.firmwareVersions.cypressFirmwareVersion.versionAsString, @@ -164,7 +163,7 @@ class ScannerInitialSetupHelperTest { @Test fun ifAvailableVersionGreaterThanExistingVersion_throwsOtaAvailableExceptionAndReconnects() = runTest(dispatcher) { - every { scannerMock.getVersionInformation() } returns Single.just(SCANNER_VERSION_LOW) + coEvery { scannerMock.getVersionInformation() } returns SCANNER_VERSION_LOW every { vero2Configuration.firmwareVersions } returns mapOf( HARDWARE_VERSION to Vero2Configuration.Vero2FirmwareVersions( CYPRESS_VERSION_STRING, STM_VERSION_STRING, UN20_VERSION_STRING @@ -197,7 +196,7 @@ class ScannerInitialSetupHelperTest { @Test fun ifAvailableVersionGreaterThanExistingVersion_lowScannerBattery_completesNormally() = runTest(dispatcher) { - every { scannerMock.getVersionInformation() } returns Single.just(SCANNER_VERSION_LOW) + coEvery { scannerMock.getVersionInformation() } returns SCANNER_VERSION_LOW every { vero2Configuration.firmwareVersions } returns mapOf( HARDWARE_VERSION to Vero2Configuration.Vero2FirmwareVersions( CYPRESS_VERSION_STRING, STM_VERSION_STRING, UN20_VERSION_STRING @@ -219,7 +218,7 @@ class ScannerInitialSetupHelperTest { @Test fun ifAvailableVersionGreaterThanExistingVersion_lowPhoneBattery_completesNormally() = runTest(dispatcher) { - every { scannerMock.getVersionInformation() } returns Single.just(SCANNER_VERSION_LOW) + coEvery { scannerMock.getVersionInformation() } returns SCANNER_VERSION_LOW every { vero2Configuration.firmwareVersions } returns mapOf( HARDWARE_VERSION to Vero2Configuration.Vero2FirmwareVersions( CYPRESS_VERSION_STRING, STM_VERSION_STRING, UN20_VERSION_STRING @@ -241,7 +240,7 @@ class ScannerInitialSetupHelperTest { @Test fun ifAvailableVersionGreaterThanExistingVersion_stillSavesVersionAndBatteryInfo() = runTest(dispatcher) { - every { scannerMock.getVersionInformation() } returns Single.just(SCANNER_VERSION_LOW) + coEvery { scannerMock.getVersionInformation() } returns SCANNER_VERSION_LOW every { vero2Configuration.firmwareVersions } returns mapOf( HARDWARE_VERSION to Vero2Configuration.Vero2FirmwareVersions( CYPRESS_VERSION_STRING, STM_VERSION_STRING, UN20_VERSION_STRING diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/StmOtaHelperTest.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/StmOtaHelperTest.kt index 16c6cf7f09..74f3c83567 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/StmOtaHelperTest.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/StmOtaHelperTest.kt @@ -12,13 +12,13 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.ScannerInfo import com.simprints.fingerprint.infra.scanner.v2.scanner.Scanner import io.mockk.CapturingSlot import io.mockk.coEvery -import io.mockk.every +import io.mockk.coVerify +import io.mockk.just import io.mockk.mockk -import io.mockk.verify -import io.reactivex.Completable -import io.reactivex.Observable -import io.reactivex.Single +import io.mockk.runs +import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.last +import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest @@ -38,12 +38,12 @@ class StmOtaHelperTest { fun setup() { coEvery { connectionHelperMock.reconnect(any(), any()) } answers {} - every { scannerMock.enterStmOtaMode() } returns Completable.complete() - every { scannerMock.startStmOta(any()) } returns Observable.fromIterable(OTA_PROGRESS_VALUES) - every { scannerMock.getVersionInformation() } returns Single.just(OLD_SCANNER_VERSION) - every { scannerMock.setVersionInformation(any()) } returns Completable.complete() - every { scannerMock.enterMainMode() } returns Completable.complete() - every { scannerMock.getStmFirmwareVersion() } returns Single.just(NEW_STM_VERSION) + coEvery { scannerMock.enterStmOtaMode() } just runs + coEvery { scannerMock.startStmOta(any()) } returns OTA_PROGRESS_VALUES.asFlow() + coEvery { scannerMock.getVersionInformation() } returns OLD_SCANNER_VERSION + coEvery { scannerMock.setVersionInformation(any()) } just runs + coEvery { scannerMock.enterMainMode() } just runs + coEvery { scannerMock.getStmFirmwareVersion() } returns NEW_STM_VERSION coEvery { firmwareFileManagerMock.loadStmFirmwareBytes(NEW_STM_VERSION_STRING) } returns byteArrayOf(0x00, 0x01, 0x02, 0xFF.toByte()) } @@ -64,7 +64,7 @@ class StmOtaHelperTest { .inOrder() val sentUnifiedVersion = CapturingSlot() - verify { scannerMock.setVersionInformation(capture(sentUnifiedVersion)) } + coVerify { scannerMock.setVersionInformation(capture(sentUnifiedVersion)) } assertThat(sentUnifiedVersion.captured.toScannerFirmwareVersions()).isEqualTo(NEW_SCANNER_VERSION.toScannerFirmwareVersions()) } @@ -76,8 +76,7 @@ class StmOtaHelperTest { progressValues.map { StmOtaStep.TransferInProgress(it) } val error = ScannerV2OtaFailedException("oops!") - every { scannerMock.startStmOta(any()) } returns - Observable.fromIterable(progressValues).concatWith(Observable.error(error)) + coEvery { scannerMock.startStmOta(any()) } returns progressValues.asFlow().onCompletion { throw error} val otaFlow = stmOtaHelper.performOtaSteps(scannerMock, "mac address", NEW_STM_VERSION_STRING) val actualSteps = otaFlow.take(expectedSteps.size).toList() @@ -120,7 +119,7 @@ class StmOtaHelperTest { OTA_PROGRESS_VALUES.map { StmOtaStep.TransferInProgress(it) } + listOf(StmOtaStep.ReconnectingAfterTransfer, StmOtaStep.EnteringMainMode, StmOtaStep.ValidatingNewFirmwareVersion) - every { scannerMock.getStmFirmwareVersion() } returns Single.just(OLD_STM_VERSION) + coEvery { scannerMock.getStmFirmwareVersion() } returns OLD_STM_VERSION val otaFlow = stmOtaHelper.performOtaSteps(scannerMock, "mac address", NEW_STM_VERSION_STRING) val actualSteps = otaFlow.take(expectedSteps.size).toList() diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/Un20OtaHelperTest.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/Un20OtaHelperTest.kt index cf45761273..0c78e0be6e 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/Un20OtaHelperTest.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/helpers/Un20OtaHelperTest.kt @@ -10,13 +10,12 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.ScannerInfo import com.simprints.fingerprint.infra.scanner.v2.scanner.Scanner import io.mockk.CapturingSlot import io.mockk.coEvery -import io.mockk.every +import io.mockk.coJustRun +import io.mockk.coVerify import io.mockk.mockk -import io.mockk.verify -import io.reactivex.Completable -import io.reactivex.Observable -import io.reactivex.Single +import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.last +import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest @@ -36,25 +35,42 @@ class Un20OtaHelperTest { fun setup() { coEvery { connectionHelperMock.reconnect(any(), any()) } answers {} - every { scannerMock.enterMainMode() } returns Completable.complete() - every { scannerMock.turnUn20OnAndAwaitStateChangeEvent() } returns Completable.complete() - every { scannerMock.startUn20Ota(any()) } returns Observable.fromIterable(OTA_PROGRESS_VALUES) - every { scannerMock.turnUn20OffAndAwaitStateChangeEvent() } returns Completable.complete() - every { scannerMock.getVersionInformation() } returns Single.just(OLD_SCANNER_INFORMATION) - every { scannerMock.setVersionInformation(any()) } returns Completable.complete() - every { scannerMock.getUn20AppVersion() } returns Single.just(NEW_UN20_VERSION) - - coEvery { firmwareFileManagerMock.loadUn20FirmwareBytes(NEW_UN20_VERSION_STRING) } returns byteArrayOf(0x00, 0x01, 0x02, 0xFF.toByte()) + coJustRun { scannerMock.enterMainMode() } + coJustRun { scannerMock.turnUn20On() } + coEvery { scannerMock.startUn20Ota(any()) } returns OTA_PROGRESS_VALUES.asFlow() + coJustRun { scannerMock.turnUn20Off() } + coEvery { scannerMock.getVersionInformation() } returns OLD_SCANNER_INFORMATION + coJustRun { scannerMock.setVersionInformation(any()) } + coEvery { scannerMock.getUn20AppVersion() } returns NEW_UN20_VERSION + + coEvery { firmwareFileManagerMock.loadUn20FirmwareBytes(NEW_UN20_VERSION_STRING) } returns byteArrayOf( + 0x00, + 0x01, + 0x02, + 0xFF.toByte() + ) } @Test fun performUn20Ota_allStepsPassing_succeedsWithCorrectStepsAndProgressValues() = runTest { - val expectedSteps = listOf(Un20OtaStep.EnteringMainMode, Un20OtaStep.TurningOnUn20BeforeTransfer, Un20OtaStep.CommencingTransfer) + + val expectedSteps = listOf( + Un20OtaStep.EnteringMainMode, + Un20OtaStep.TurningOnUn20BeforeTransfer, + Un20OtaStep.CommencingTransfer + ) + OTA_PROGRESS_VALUES.map { Un20OtaStep.TransferInProgress(it) } + - listOf(Un20OtaStep.AwaitingCacheCommit, Un20OtaStep.TurningOffUn20AfterTransfer, Un20OtaStep.TurningOnUn20AfterTransfer, - Un20OtaStep.ValidatingNewFirmwareVersion, Un20OtaStep.ReconnectingAfterValidating, Un20OtaStep.UpdatingUnifiedVersionInformation) + listOf( + Un20OtaStep.AwaitingCacheCommit, + Un20OtaStep.TurningOffUn20AfterTransfer, + Un20OtaStep.TurningOnUn20AfterTransfer, + Un20OtaStep.ValidatingNewFirmwareVersion, + Un20OtaStep.ReconnectingAfterValidating, + Un20OtaStep.UpdatingUnifiedVersionInformation + ) - val actualSteps = un20OtaHelper.performOtaSteps(scannerMock, "mac address", NEW_UN20_VERSION_STRING).toList() + val actualSteps = + un20OtaHelper.performOtaSteps(scannerMock, "mac address", NEW_UN20_VERSION_STRING) + .toList() assertThat(actualSteps).containsExactlyElementsIn(expectedSteps).inOrder() assertThat(actualSteps.map { it.totalProgress }) @@ -62,21 +78,28 @@ class Un20OtaHelperTest { .inOrder() val sentUnifiedVersion = CapturingSlot() - verify { scannerMock.setVersionInformation(capture(sentUnifiedVersion)) } - assertThat(sentUnifiedVersion.captured.toScannerFirmwareVersions()).isEqualTo(NEW_SCANNER_VERSION.toScannerFirmwareVersions()) + coVerify { scannerMock.setVersionInformation(capture(sentUnifiedVersion)) } + assertThat(sentUnifiedVersion.captured.toScannerFirmwareVersions()).isEqualTo( + NEW_SCANNER_VERSION.toScannerFirmwareVersions() + ) } @Test(expected = ScannerV2OtaFailedException::class) fun un20OtaFailsDuringTransfer_propagatesError() = runTest { val progressValues = listOf(0.0f, 0.2f, 0.4f) - val expectedSteps = listOf(Un20OtaStep.EnteringMainMode, Un20OtaStep.TurningOnUn20BeforeTransfer, Un20OtaStep.CommencingTransfer) + - progressValues.map { Un20OtaStep.TransferInProgress(it) } + val expectedSteps = listOf( + Un20OtaStep.EnteringMainMode, + Un20OtaStep.TurningOnUn20BeforeTransfer, + Un20OtaStep.CommencingTransfer + ) + progressValues.map { Un20OtaStep.TransferInProgress(it) } val error = ScannerV2OtaFailedException("oops!") - every { scannerMock.startUn20Ota(any()) } returns - Observable.fromIterable(progressValues).concatWith(Observable.error(error)) + coEvery { scannerMock.startUn20Ota(any()) } returns (progressValues).asFlow() + .onCompletion { throw error } - val otaFlow = un20OtaHelper.performOtaSteps(scannerMock, "mac address", NEW_UN20_VERSION_STRING) + val otaFlow = + un20OtaHelper.performOtaSteps(scannerMock, "mac address", NEW_UN20_VERSION_STRING) val actualSteps = otaFlow.take(expectedSteps.size).toList() assertThat(actualSteps).containsExactlyElementsIn(expectedSteps).inOrder() @@ -90,14 +113,23 @@ class Un20OtaHelperTest { @Test(expected = IOException::class) fun un20OtaFailsDuringTurningUn20OnSecondTime_propagatesError() = runTest { - val expectedSteps = listOf(Un20OtaStep.EnteringMainMode, Un20OtaStep.TurningOnUn20BeforeTransfer, Un20OtaStep.CommencingTransfer) + + val expectedSteps = listOf( + Un20OtaStep.EnteringMainMode, + Un20OtaStep.TurningOnUn20BeforeTransfer, + Un20OtaStep.CommencingTransfer + ) + OTA_PROGRESS_VALUES.map { Un20OtaStep.TransferInProgress(it) } + - listOf(Un20OtaStep.AwaitingCacheCommit, Un20OtaStep.TurningOffUn20AfterTransfer, Un20OtaStep.TurningOnUn20AfterTransfer) + listOf( + Un20OtaStep.AwaitingCacheCommit, + Un20OtaStep.TurningOffUn20AfterTransfer, + Un20OtaStep.TurningOnUn20AfterTransfer + ) val error = IOException("oops!") - every { scannerMock.turnUn20OnAndAwaitStateChangeEvent() } returnsMany listOf(Completable.complete(), Completable.error(error)) + coEvery { scannerMock.turnUn20On() } returnsMany listOf(Unit, throw error) - val otaFlow = un20OtaHelper.performOtaSteps(scannerMock, "mac address", NEW_UN20_VERSION_STRING) + val otaFlow = + un20OtaHelper.performOtaSteps(scannerMock, "mac address", NEW_UN20_VERSION_STRING) val actualSteps = otaFlow.take(expectedSteps.size).toList() @@ -113,14 +145,23 @@ class Un20OtaHelperTest { @Test(expected = OtaFailedException::class) fun un20OtaFailsToValidate_throwsOtaError() = runTest { - val expectedSteps = listOf(Un20OtaStep.EnteringMainMode, Un20OtaStep.TurningOnUn20BeforeTransfer, Un20OtaStep.CommencingTransfer) + + val expectedSteps = listOf( + Un20OtaStep.EnteringMainMode, + Un20OtaStep.TurningOnUn20BeforeTransfer, + Un20OtaStep.CommencingTransfer + ) + OTA_PROGRESS_VALUES.map { Un20OtaStep.TransferInProgress(it) } + - listOf(Un20OtaStep.AwaitingCacheCommit, Un20OtaStep.TurningOffUn20AfterTransfer, Un20OtaStep.TurningOnUn20AfterTransfer, - Un20OtaStep.ValidatingNewFirmwareVersion) + listOf( + Un20OtaStep.AwaitingCacheCommit, + Un20OtaStep.TurningOffUn20AfterTransfer, + Un20OtaStep.TurningOnUn20AfterTransfer, + Un20OtaStep.ValidatingNewFirmwareVersion + ) - every { scannerMock.getUn20AppVersion() } returns Single.just(OLD_UN20_VERSION) + coEvery { scannerMock.getUn20AppVersion() } returns OLD_UN20_VERSION - val otaFlow = un20OtaHelper.performOtaSteps(scannerMock, "mac address", NEW_UN20_VERSION_STRING) + val otaFlow = + un20OtaHelper.performOtaSteps(scannerMock, "mac address", NEW_UN20_VERSION_STRING) val actualSteps = otaFlow.take(expectedSteps.size).toList() assertThat(actualSteps).containsExactlyElementsIn(expectedSteps).inOrder() diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ScannerExtendedInfoReaderHelperTest.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ScannerExtendedInfoReaderHelperTest.kt index 08e8dbfc64..cb04069414 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ScannerExtendedInfoReaderHelperTest.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ScannerExtendedInfoReaderHelperTest.kt @@ -18,7 +18,6 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.CypressExte import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.CypressFirmwareVersion import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.ExtendedHardwareVersion import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.ExtendedVersionInformation -import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.ScannerInformation import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.UnifiedVersionInformation import com.simprints.fingerprint.infra.scanner.v2.domain.root.responses.GetCypressExtendedVersionResponse import com.simprints.fingerprint.infra.scanner.v2.domain.root.responses.GetCypressVersionResponse @@ -27,10 +26,7 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.root.responses.GetHardw import com.simprints.fingerprint.infra.scanner.v2.domain.root.responses.GetVersionResponse import com.simprints.fingerprint.infra.scanner.v2.incoming.root.RootMessageInputStream import com.simprints.fingerprint.infra.scanner.v2.outgoing.root.RootMessageOutputStream -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandler -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandlingStrategy import com.simprints.testtools.common.coroutines.TestCoroutineRule -import com.simprints.testtools.common.syntax.awaitAndAssertSuccess import io.mockk.every import io.mockk.justRun import io.mockk.mockk @@ -38,6 +34,7 @@ import io.mockk.spyk import io.reactivex.BackpressureStrategy import io.reactivex.Completable import io.reactivex.subjects.PublishSubject +import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -46,11 +43,10 @@ class ScannerExtendedInfoReaderHelperTest { @get:Rule val testCoroutineRule = TestCoroutineRule() + private val ioDispatcher = testCoroutineRule.testCoroutineDispatcher private val mainMessageChannel: MainMessageChannel = mockk() private val rootMessageChannel: RootMessageChannel = getRootMessageChannel() - private val responseErrorHandler: ResponseErrorHandler = ResponseErrorHandler( - ResponseErrorHandlingStrategy.NONE) private lateinit var expectedVersionResponse: GetVersionResponse private lateinit var expectedHardwareResponse: GetHardwareVersionResponse @@ -61,12 +57,11 @@ class ScannerExtendedInfoReaderHelperTest { private val scannerInfoReader = ScannerExtendedInfoReaderHelper( mainMessageChannel, rootMessageChannel, - responseErrorHandler ) @Test - fun shouldReturn_scannerInformation_withLegacyFirmwareInfo_whenCypressVersion_isOldApi() { + fun shouldReturn_scannerInformation_withLegacyFirmwareInfo_whenCypressVersion_isOldApi() = runTest { val stmMajorFirmwareVersion: Short = 1 val stmMinorFirmwareVersion: Short = 2 val un20MajorFirmwareVersion: Short = 1 @@ -95,22 +90,15 @@ class ScannerExtendedInfoReaderHelperTest { un20AppVersion = Un20ExtendedAppVersion( "$un20MajorFirmwareVersion.$expectedHardware.$un20MinorFirmwareVersion") ) - - val testObserver = scannerInfoReader.readScannerInfo().test() - testObserver.awaitAndAssertSuccess() - - - testObserver.assertValueCount(1) - val scannerInformation = testObserver.values().first() as ScannerInformation + val scannerInformation = scannerInfoReader.readScannerInfo() assertThat(scannerInformation.hardwareVersion).isEqualTo("E-1") assertThat(scannerInformation.firmwareVersions).isEqualTo( expectedFirmwareVersions ) } - @Test - fun shouldReturn_scannerInformation_withExtendedFirmwareInfo_whenCypressVersion_isNewApi() { + fun shouldReturn_scannerInformation_withExtendedFirmwareInfo_whenCypressVersion_isNewApi() = runTest { val expectedCypressVersion = CypressFirmwareVersion(1, 3, 1, 3) expectedCypressVersionResponse = GetCypressVersionResponse(expectedCypressVersion) @@ -126,19 +114,13 @@ class ScannerExtendedInfoReaderHelperTest { ) expectedExtendedVersionResponse = GetExtendedVersionResponse(expectedFirmwareVersions) - - val testObserver = scannerInfoReader.readScannerInfo().test() - testObserver.awaitAndAssertSuccess() - - - testObserver.assertValueCount(1) - val scannerInformation = testObserver.values().first() as ScannerInformation + val scannerInformation = scannerInfoReader.readScannerInfo() assertThat(scannerInformation.hardwareVersion).isEqualTo(expectedHardware) assertThat(scannerInformation.firmwareVersions).isEqualTo(expectedFirmwareVersions) } @Test - fun shouldReturn_scannerInformation_containingOld_firmwareVersion_wheneverPartial_otaUpdateOccurs() { + fun shouldReturn_scannerInformation_containingOld_firmwareVersion_wheneverPartial_otaUpdateOccurs() = runTest { val expectedCypressVersion = CypressFirmwareVersion(1, 3, 1, 3) expectedCypressVersionResponse = GetCypressVersionResponse(expectedCypressVersion) @@ -168,18 +150,17 @@ class ScannerExtendedInfoReaderHelperTest { // for missing version values. val expectedFirmwareVersions = firmwareVersions.copy( stmFirmwareVersion = StmExtendedFirmwareVersion( - expectedUnifiedVersion.stmFirmwareVersion.toNewVersionNamingScheme()), + expectedUnifiedVersion.stmFirmwareVersion.toNewVersionNamingScheme() + ), un20AppVersion = Un20ExtendedAppVersion( - expectedUnifiedVersion.un20AppVersion.toNewVersionNamingScheme()) + expectedUnifiedVersion.un20AppVersion.toNewVersionNamingScheme() + ) ) - val testObserver = scannerInfoReader.readScannerInfo().test() - testObserver.awaitAndAssertSuccess() + val scannerInformation = scannerInfoReader.readScannerInfo() - testObserver.assertValueCount(1) - val scannerInformation = testObserver.values().first() as ScannerInformation assertThat(scannerInformation.hardwareVersion).isEqualTo(expectedHardware) assertThat(scannerInformation.firmwareVersions).isEqualTo(expectedFirmwareVersions) } @@ -210,6 +191,6 @@ class ScannerExtendedInfoReaderHelperTest { } } - return RootMessageChannel(spyRootMessageInputStream, mockRootMessageOutputStream) + return RootMessageChannel(spyRootMessageInputStream, mockRootMessageOutputStream, ioDispatcher) } } diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ScannerTest.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ScannerTest.kt index 3a2810bf32..58c6ae3634 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ScannerTest.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ScannerTest.kt @@ -1,49 +1,51 @@ package com.simprints.fingerprint.infra.scanner.v2.scanner +import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.google.common.truth.Truth.assertThat import com.simprints.fingerprint.infra.scanner.v2.channel.CypressOtaMessageChannel import com.simprints.fingerprint.infra.scanner.v2.channel.MainMessageChannel import com.simprints.fingerprint.infra.scanner.v2.channel.RootMessageChannel import com.simprints.fingerprint.infra.scanner.v2.channel.StmOtaMessageChannel import com.simprints.fingerprint.infra.scanner.v2.domain.Mode +import com.simprints.fingerprint.infra.scanner.v2.domain.cypressota.CypressOtaResponse import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.Un20Response -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.commands.CaptureFingerprintCommand -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.commands.GetImageCommand -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.commands.GetImageDistortionConfigurationMatrixCommand -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.commands.GetTemplateCommand -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.commands.GetUnprocessedImageCommand import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.models.CaptureFingerprintResult import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.models.ImageData import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.models.ImageFormat import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.models.TemplateData +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.models.Un20ExtendedAppVersion import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.responses.CaptureFingerprintResponse -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.responses.GetImageDistortionConfigurationMatrixResponse +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.responses.GetImageQualityPreviewResponse +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.responses.GetImageQualityResponse import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.responses.GetImageResponse import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.responses.GetTemplateResponse import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.VeroEvent import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.VeroResponse -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.commands.SetSmileLedStateCommand -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.commands.SetUn20OnCommand import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.events.TriggerButtonPressedEvent import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.events.Un20StateChangeEvent +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.BatteryCurrent +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.BatteryPercentCharge +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.BatteryTemperature +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.BatteryVoltage import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.DigitalValue import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.LedState import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.OperationResultCode import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.SmileLedState import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.StmExtendedFirmwareVersion -import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.responses.SetSmileLedStateResponse +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.responses.GetBatteryCurrentResponse +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.responses.GetBatteryPercentChargeResponse +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.responses.GetBatteryTemperatureResponse +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.responses.GetBatteryVoltageResponse +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.responses.GetUn20OnResponse import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.responses.SetUn20OnResponse -import com.simprints.fingerprint.infra.scanner.v2.domain.root.RootCommand -import com.simprints.fingerprint.infra.scanner.v2.domain.root.RootResponse -import com.simprints.fingerprint.infra.scanner.v2.domain.root.commands.EnterCypressOtaModeCommand -import com.simprints.fingerprint.infra.scanner.v2.domain.root.commands.EnterMainModeCommand -import com.simprints.fingerprint.infra.scanner.v2.domain.root.commands.EnterStmOtaModeCommand import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.CypressExtendedFirmwareVersion +import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.CypressFirmwareVersion import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.ExtendedVersionInformation +import com.simprints.fingerprint.infra.scanner.v2.domain.root.models.ScannerInformation import com.simprints.fingerprint.infra.scanner.v2.domain.root.responses.EnterCypressOtaModeResponse import com.simprints.fingerprint.infra.scanner.v2.domain.root.responses.EnterMainModeResponse import com.simprints.fingerprint.infra.scanner.v2.domain.root.responses.EnterStmOtaModeResponse -import com.simprints.fingerprint.infra.scanner.v2.domain.root.responses.GetExtendedVersionResponse +import com.simprints.fingerprint.infra.scanner.v2.domain.stmota.StmOtaResponse import com.simprints.fingerprint.infra.scanner.v2.exceptions.state.IllegalUn20StateException import com.simprints.fingerprint.infra.scanner.v2.exceptions.state.IncorrectModeException import com.simprints.fingerprint.infra.scanner.v2.exceptions.state.NotConnectedException @@ -54,63 +56,62 @@ import com.simprints.fingerprint.infra.scanner.v2.incoming.stmota.StmOtaMessageI import com.simprints.fingerprint.infra.scanner.v2.outgoing.main.MainMessageOutputStream import com.simprints.fingerprint.infra.scanner.v2.outgoing.root.RootMessageOutputStream import com.simprints.fingerprint.infra.scanner.v2.outgoing.stmota.StmOtaMessageOutputStream -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandler -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandlingStrategy import com.simprints.fingerprint.infra.scanner.v2.scanner.ota.cypress.CypressOtaController import com.simprints.fingerprint.infra.scanner.v2.scanner.ota.stm.StmOtaController -import com.simprints.fingerprint.infra.scanner.v2.tools.primitives.byteArrayOf +import com.simprints.fingerprint.infra.scanner.v2.scanner.ota.un20.Un20OtaController import com.simprints.fingerprint.infra.scanner.v2.tools.reactive.toFlowable -import com.simprints.testtools.common.syntax.awaitAndAssertSuccess -import com.simprints.testtools.unit.reactive.testSubscribe -import io.mockk.Runs import io.mockk.coEvery +import io.mockk.coJustRun +import io.mockk.coVerify import io.mockk.every -import io.mockk.just +import io.mockk.justRun import io.mockk.mockk import io.mockk.mockkStatic -import io.mockk.runs -import io.mockk.spyk import io.mockk.verify import io.reactivex.BackpressureStrategy import io.reactivex.Completable import io.reactivex.Flowable -import io.reactivex.Observable -import io.reactivex.Single import io.reactivex.disposables.Disposable import io.reactivex.observers.TestObserver -import io.reactivex.rxkotlin.toObservable +import io.reactivex.rxkotlin.toFlowable import io.reactivex.subjects.PublishSubject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Rule import org.junit.Test import java.io.InputStream import java.io.OutputStream class ScannerTest { - private lateinit var eventsSubject: PublishSubject private lateinit var mockkMessageOutputStream: MainMessageOutputStream private lateinit var mockkMessageInputStream: MainMessageInputStream - private val responseErrorHandler = ResponseErrorHandler(ResponseErrorHandlingStrategy.NONE) private lateinit var scanner: Scanner private lateinit var flowableDisposable: Disposable private lateinit var flowable: Flowable private lateinit var mockkInputStream: InputStream + private val un20OtaController: Un20OtaController = mockk() + private val mockkOutputStream = mockk() + + @get:Rule + val rule = InstantTaskExecutorRule() + + @OptIn(ExperimentalCoroutinesApi::class) + private val dispatcher = Dispatchers.IO @Before fun setup() { - eventsSubject = PublishSubject.create() - - mockkMessageInputStream = mockk { - every { connect(any()) } just Runs - every { disconnect() } just Runs - every { veroEvents } returns eventsSubject.toFlowable(BackpressureStrategy.BUFFER) - } + mockkMessageInputStream = setupMainMessageInputStreamMock() mockkMessageOutputStream = mockk { - every { connect(any()) } just Runs - every { disconnect() } just Runs + justRun { connect(any()) } + justRun { disconnect() } + every { sendMessage(any()) } returns Completable.complete() } - flowableDisposable = mockk(relaxed = true) flowable = mockk { every { subscribeOn(any()) } returns this @@ -123,39 +124,164 @@ class ScannerTest { every { toFlowable() } returns flowable } scanner = Scanner( - MainMessageChannel(mockkMessageInputStream, mockkMessageOutputStream), - setupRootMessageChannelMockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - responseErrorHandler + MainMessageChannel(mockkMessageInputStream, mockkMessageOutputStream, dispatcher), + setupRootMessageChannelMock(), + mockk(), mockk(), mockk(), mockk(), mockk(), + un20OtaController, + ) + } + + @Test(expected = NotConnectedException::class) + fun scanner_callEnterModeBeforeConnect_throwsException() = runTest { + scanner.enterMainMode() + } + + @Test + fun scanner_call_batteryInfo() = runTest { + val expectedPercentCharge = 1 + val expectedVoltageMilliVolts = 2 + val expectedCurrentMilliAmps = 3 + val expectedTemperatureDeciKelvin = 4 + + val messageInputStreamMock = setupMainMessageInputStreamMock( + veroMessages = listOf( + GetBatteryPercentChargeResponse(BatteryPercentCharge(expectedPercentCharge.toByte())), + GetBatteryTemperatureResponse(BatteryTemperature(expectedTemperatureDeciKelvin.toShort())), + GetBatteryVoltageResponse(BatteryVoltage(expectedVoltageMilliVolts.toShort())), + GetBatteryCurrentResponse(BatteryCurrent(expectedCurrentMilliAmps.toShort())) + ) + ) + + val scanner = createScanner(messageInputStreamMock, mockkMessageOutputStream) + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() + assertThat(scanner.getBatteryPercentCharge()).isEqualTo(expectedPercentCharge) + assertThat(scanner.getBatteryVoltageMilliVolts()).isEqualTo(expectedVoltageMilliVolts) + assertThat(scanner.getBatteryCurrentMilliAmps()).isEqualTo(expectedCurrentMilliAmps) + assertThat(scanner.getBatteryTemperatureDeciKelvin()).isEqualTo( + expectedTemperatureDeciKelvin ) } @Test - fun scanner_callEnterModeBeforeConnect_throwsException() { - scanner.enterMainMode().testSubscribe().await() - .assertError(NotConnectedException::class.java) + fun scanner_call_getUn20Status() = runTest { + val expectedValue = true + + val messageInputStreamMock = + setupMainMessageInputStreamMock(veroMessages = listOf(GetUn20OnResponse(DigitalValue.TRUE))) + + val scanner = createScanner(messageInputStreamMock, mockkMessageOutputStream) + + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() + + val result = scanner.getUn20Status() + assertThat(result).isEqualTo(expectedValue) } @Test - fun scanner_connectThenDisconnectThenEnterMainMode_throwsException() { - scanner.connect(mockk(), mockk()).blockingAwait() - scanner.disconnect().blockingAwait() - scanner.enterMainMode().testSubscribe().await() - .assertError(NotConnectedException::class.java) + fun scanner_call_getImageQualityScore() = runTest { + val expectedValue = GetImageQualityResponse(1) + + val messageInputStreamMock = + setupMainMessageInputStreamMock(un20Messages = listOf(expectedValue)) + + val scanner = createScanner(messageInputStreamMock, mockkMessageOutputStream) + + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() + scanner.state.un20On = true + val result = scanner.getImageQualityScore() + assertThat(result).isEqualTo(expectedValue.imageQualityScore) } @Test - fun scanner_connect_callsConnectOnRootMessageStreams() { + fun scanner_call_getUn20AppVersion() = runTest { + val expectedValue = Un20ExtendedAppVersion("un20Version") + val scannerInfoReaderMockk = mockk { + coEvery { getUn20ExtendedAppVersion() } returns expectedValue + } + val scanner = createScanner(scannerInfoReaderMockk) + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() + scanner.state.un20On = true + val result = scanner.getUn20AppVersion() + assertThat(result).isEqualTo(expectedValue) + } + + @Test + fun scanner_call_getVersionInformation() = runTest { + val expectedValue = mockk() + + val scannerInfoReaderMockk = mockk { + coEvery { readScannerInfo() } returns expectedValue + } + val scanner = createScanner(scannerInfoReaderMockk) + scanner.connect(mockkInputStream, mockkOutputStream) + + val result = scanner.getVersionInformation() + assertThat(result).isEqualTo(expectedValue) + } + + @Test + fun scanner_call_setVersionInformation() = runTest { + val expectedValue = mockk() + + val scannerInfoReaderMockk = mockk { + coJustRun { setExtendedVersionInformation(expectedValue) } + } + val scanner = createScanner(scannerInfoReaderMockk) + scanner.connect(mockkInputStream, mockkOutputStream) + + scanner.setVersionInformation(expectedValue) + coVerify { scannerInfoReaderMockk.setExtendedVersionInformation(expectedValue) } + } + + @Test + fun scanner_call_getCypressFirmwareVersion() = runTest { + val expectedValue = mockk() + + val scannerInfoReaderMockk = mockk { + coEvery { getCypressVersion() } returns expectedValue + } + val scanner = createScanner(scannerInfoReaderMockk) + scanner.connect(mockkInputStream, mockkOutputStream) + + val result = scanner.getCypressFirmwareVersion() + assertThat(result).isEqualTo(expectedValue) + } + + @Test + fun scanner_call_getImageQualityPreview() = runTest { + val expectedValue = GetImageQualityPreviewResponse(1) + + val messageInputStreamMock = + setupMainMessageInputStreamMock(un20Messages = listOf(expectedValue)) + val scanner = createScanner(messageInputStreamMock, mockkMessageOutputStream) + + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() + scanner.state.un20On = true + + val result = scanner.getImageQualityPreview() + assertThat(result).isEqualTo(expectedValue.imageQualityScore) + } + + + @Test(expected = NotConnectedException::class) + fun scanner_connectThenDisconnectThenEnterMainMode_throwsException() = runTest { + scanner.connect(mockk(), mockk()) + scanner.disconnect() + scanner.enterMainMode() + } + + @Test + fun scanner_connect_callsConnectOnRootMessageStreams() = runTest { val rootMessageChannel: RootMessageChannel = mockk { - every { connect(any(), any()) } just runs + justRun { connect(any(), any()) } } scanner = Scanner( - MainMessageChannel(mockkMessageInputStream, mockkMessageOutputStream), + MainMessageChannel(mockkMessageInputStream, mockkMessageOutputStream, dispatcher), rootMessageChannel, mockk(), mockk(), @@ -163,41 +289,35 @@ class ScannerTest { mockk(), mockk(), mockk(), - responseErrorHandler ) - scanner.connect(mockkInputStream, mockk()).blockingAwait() - + scanner.connect(mockkInputStream, mockkOutputStream) verify { rootMessageChannel.connect(any(), any()) } } @Test fun scanner_connect_stateIsInRootMode() { - scanner.connect(mockkInputStream, mockk()).blockingAwait() + scanner.connect(mockkInputStream, mockkOutputStream) assertThat(scanner.state.mode).isEqualTo(Mode.ROOT) } @Test - fun scanner_connectThenEnterMainMode_callsConnectOnMainMessageStreams() { - val mockkOutputStream = mockk() - scanner.connect(mockkInputStream, mockkOutputStream).blockingAwait() + fun scanner_connectThenEnterMainMode_callsConnectOnMainMessageStreams() = runTest { - scanner.enterMainMode().blockingAwait() + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() verify { mockkMessageInputStream.connect(any()) } verify { mockkMessageOutputStream.connect(mockkOutputStream) } - - scanner.disconnect().blockingAwait() + scanner.disconnect() verify { mockkMessageInputStream.disconnect() } verify { mockkMessageOutputStream.disconnect() } + } @Test - fun scanner_connectThenEnterMainMode_stateIsInMainMode() { - - scanner.connect(mockkInputStream, mockk()).blockingAwait() - - scanner.enterMainMode().blockingAwait() - + fun scanner_connectThenEnterMainMode_stateIsInMainMode() = runTest { + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() assertThat(scanner.state.mode).isEqualTo(Mode.MAIN) } @@ -216,319 +336,249 @@ class ScannerTest { mockk(), mockk(), mockk(), - responseErrorHandler ) - scanner.disconnect().blockingAwait() + scanner.disconnect() verify(exactly = 0) { mockRootMessageChannel.disconnect() } verify(exactly = 0) { mockMainMessageChannel.disconnect() } verify(exactly = 0) { mockStmOtaMessageChannel.disconnect() } verify(exactly = 0) { mockCypressOtaMessageChannel.disconnect() } } - @Test - fun scanner_connectThenEnterCypressOtaMode_callsConnectOnCypressOtaMessageStreams() { - - val mockCypressOtaMessageChannel = mockk { - every { connect(any(), any()) } just Runs - every { disconnect() } just Runs - } - val mockkOutputStream = mockk() - + @Test() + fun scanner_connectThenEnterCypressOtaMode_callsConnectOnCypressOtaMessageStreams() = runTest { + val mockCypressOtaMessageChannel = + CypressOtaMessageChannel(mockk(relaxed = true), mockk(relaxed = true), dispatcher) scanner = Scanner( mockk(), - setupRootMessageChannelMockk(), + setupRootMessageChannelMock(), mockk(), mockCypressOtaMessageChannel, mockk(), mockk(), mockk(), mockk(), - responseErrorHandler ) - scanner.connect(mockkInputStream, mockkOutputStream).blockingAwait() - - scanner.enterCypressOtaMode().blockingAwait() - + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterCypressOtaMode() verify { mockCypressOtaMessageChannel.connect(any(), any()) } - scanner.disconnect().blockingAwait() + scanner.disconnect() verify { mockCypressOtaMessageChannel.disconnect() } } @Test - fun scanner_connectThenEnterCypressOtaMode_stateIsInCypressOtaMode() { + fun scanner_connectThenEnterCypressOtaMode_stateIsInCypressOtaMode() = runTest { val mockkCypressOtaMessageChannel: CypressOtaMessageChannel = mockk { - every { connect(any(), any()) } just Runs + justRun { connect(any(), any()) } } val scanner = Scanner( mockk(), - setupRootMessageChannelMockk(), + setupRootMessageChannelMock(), mockk(), mockkCypressOtaMessageChannel, mockk(), mockk(), mockk(), mockk(), - responseErrorHandler ) - scanner.connect(mockkInputStream, mockk()).blockingAwait() + scanner.connect(mockkInputStream, mockkOutputStream) - scanner.enterCypressOtaMode().blockingAwait() + scanner.enterCypressOtaMode() assertThat(scanner.state.mode).isEqualTo(Mode.CYPRESS_OTA) + } @Test - fun scanner_connectThenEnterCypressOtaModeThenStartCypressOta_receivesProgressCorrectly() { - val progressValues = listOf(0.25f, 0.50f, 0.75f, 1.00f) + fun scanner_connectThenEnterCypressOtaModeThenStartCypressOta_receivesProgressCorrectly() = + runTest { + val progressValues = listOf(0.25f, 0.50f, 0.75f, 1.00f) + + val mockkCypressOtaController = mockk { + coEvery { + program(any(), any()) + } returns progressValues.asFlow() + } - val mockkCypressOtaController = mockk { - every { - program( - any(), - any(), - any() - ) - } returns Observable.defer { progressValues.toObservable() } - } + val mockkMessageInputStream = mockk(relaxed = true) { + every { cypressOtaResponseStream } returns emptyList().toFlowable() + } - val mockkMessageInputStream = mockk(relaxed = true) { - every { cypressOtaResponseStream } returns Flowable.empty() + val scanner = Scanner( + mockk(), + setupRootMessageChannelMock(), + mockk(), + CypressOtaMessageChannel( + mockkMessageInputStream, mockk(relaxed = true), dispatcher + ), + mockk(), + mockkCypressOtaController, + mockk(), + mockk(), + ) + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterCypressOtaMode() + val testObserver = scanner.startCypressOta(byteArrayOf()) + assertThat(testObserver.toList()).containsExactlyElementsIn(progressValues).inOrder() } - val scanner = Scanner( - mockk(), - setupRootMessageChannelMockk(), - mockk(), - CypressOtaMessageChannel(mockkMessageInputStream, mockk(relaxed = true)), - mockk(), - mockkCypressOtaController, - mockk(), - mockk(), - responseErrorHandler - ) - scanner.connect(mockkInputStream, mockk()).blockingAwait() - scanner.enterCypressOtaMode().blockingAwait() - - val testObserver = scanner.startCypressOta(byteArrayOf()).testSubscribe() - - testObserver.awaitAndAssertSuccess() - - assertThat(testObserver.values()).containsExactlyElementsIn(progressValues).inOrder() - testObserver.assertComplete() - } - @Test - fun scanner_connectThenEnterStmOtaMode_callsConnectOnStmOtaMessageStreams() { + fun scanner_connectThenEnterStmOtaMode_callsConnectOnStmOtaMessageStreams() = runTest { val mockkMessageInputStream = mockk { - every { connect(any()) } just Runs - every { disconnect() } just Runs - every { stmOtaResponseStream } returns Flowable.empty() + justRun { connect(any()) } + justRun { disconnect() } + every { stmOtaResponseStream } returns emptyList().toFlowable() } val mockkMessageOutputStream = mockk { - every { connect(any()) } just Runs - every { disconnect() } just Runs + justRun { connect(any()) } + justRun { disconnect() } } val mockkOutputStream = mockk() val scanner = Scanner( mockk(), - setupRootMessageChannelMockk(), + setupRootMessageChannelMock(), mockk(), mockk(), - StmOtaMessageChannel(mockkMessageInputStream, mockkMessageOutputStream), + StmOtaMessageChannel( + mockkMessageInputStream, mockkMessageOutputStream, dispatcher + ), mockk(), mockk(), mockk(), - responseErrorHandler ) - scanner.connect(mockkInputStream, mockkOutputStream).blockingAwait() + scanner.connect(mockkInputStream, mockkOutputStream) - scanner.enterStmOtaMode().blockingAwait() + scanner.enterStmOtaMode() verify { mockkMessageInputStream.connect(any()) } verify { mockkMessageOutputStream.connect(mockkOutputStream) } - scanner.disconnect().blockingAwait() + scanner.disconnect() verify { mockkMessageInputStream.disconnect() } verify { mockkMessageOutputStream.disconnect() } } @Test - fun scanner_connectThenEnterStmOtaMode_stateIsInStmOtaMode() { + fun scanner_connectThenEnterStmOtaMode_stateIsInStmOtaMode() = runTest { val mockkMessageInputStream = mockk { - every { connect(any()) } just Runs - every { stmOtaResponseStream } returns Flowable.empty() + justRun { connect(any()) } + every { stmOtaResponseStream } returns emptyList().toFlowable() } val scanner = Scanner( mockk(), - setupRootMessageChannelMockk(), + setupRootMessageChannelMock(), mockk(), mockk(), - StmOtaMessageChannel(mockkMessageInputStream, mockk(relaxed = true)), + StmOtaMessageChannel( + mockkMessageInputStream, mockk(relaxed = true), dispatcher + ), mockk(), mockk(), mockk(), - responseErrorHandler ) - scanner.connect(mockkInputStream, mockk()).blockingAwait() + scanner.connect(mockkInputStream, mockkOutputStream) + - scanner.enterStmOtaMode().blockingAwait() + scanner.enterStmOtaMode() assertThat(scanner.state.mode).isEqualTo(Mode.STM_OTA) } @Test - fun scanner_connectThenEnterStmOtaModeThenStartStmOta_receivesProgressCorrectly() { + fun scanner_connectThenEnterStmOtaModeThenStartStmOta_receivesProgressCorrectly() = runTest { val progressValues = listOf(0.25f, 0.50f, 0.75f, 1.00f) val mockkStmOtaController = mockk { - every { - program( - any(), - any(), - any() - ) - } returns Observable.defer { progressValues.toObservable() } + coEvery { + program(any(), any()) + } returns progressValues.asFlow() } val scanner = Scanner( mockk(), - setupRootMessageChannelMockk(), + setupRootMessageChannelMock(), mockk(), mockk(), - StmOtaMessageChannel(mockk(relaxed = true), mockk(relaxed = true)), + mockk(relaxed = true), mockk(), mockkStmOtaController, mockk(), - responseErrorHandler ) - scanner.connect(mockkInputStream, mockk()).blockingAwait() - scanner.enterStmOtaMode().blockingAwait() + scanner.connect(mockkInputStream, mockkOutputStream) - val testObserver = scanner.startStmOta(byteArrayOf()).testSubscribe() + scanner.enterStmOtaMode() - testObserver.awaitAndAssertSuccess() + val testObserver = scanner.startStmOta(byteArrayOf()) - assertThat(testObserver.values()).containsExactlyElementsIn(progressValues).inOrder() - testObserver.assertComplete() - } - @Test - fun scanner_connectThenTurnUn20On_throwsException() { - val mockkMainMessageChannel = mockk { - every { - sendMainModeCommandAndReceiveResponse(any()) - } returns Single.just(SetUn20OnResponse(OperationResultCode.OK)) - } - val scanner = Scanner( - mockkMainMessageChannel, - setupRootMessageChannelMockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - responseErrorHandler - ) - scanner.connect(mockkInputStream, mockk()).blockingAwait() + assertThat(testObserver.toList()).containsExactlyElementsIn(progressValues).inOrder() + } - scanner.turnUn20OnAndAwaitStateChangeEvent().testSubscribe().await() - .assertError(IncorrectModeException::class.java) + @Test(expected = IncorrectModeException::class) + fun scanner_connectThenTurnUn20On_throwsException() = runTest { + val scanner = createScanner(setupMainMessageInputStreamMock(), mockkMessageOutputStream) + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.turnUn20On() + assertThat(scanner.state).isNull() } @Test - fun scannerVeroEvents_differentKindsOfEventsCreated_forwardsOnlyTriggerEventsToObservers() { + fun scannerVeroEvents_differentKindsOfEventsCreated_forwardsOnlyTriggerEventsToObservers() = + runTest { + val eventsSubject = PublishSubject.create() + val messageInputStreamMock = setupMainMessageInputStreamMock() + every { messageInputStreamMock.veroEvents } returns eventsSubject.toFlowable( + BackpressureStrategy.BUFFER + ) + scanner = createScanner(messageInputStreamMock, mockkMessageOutputStream) + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() - scanner.connect(mockkInputStream, mockk()).blockingAwait() - scanner.enterMainMode().blockingAwait() + val testObserver = TestObserver() + scanner.triggerButtonListeners.add(testObserver) - val testObserver = TestObserver() - scanner.triggerButtonListeners.add(testObserver) + val numberOfEvents = 3 + repeat(numberOfEvents) { eventsSubject.onNext(Un20StateChangeEvent(DigitalValue.TRUE)) } + repeat(numberOfEvents) { eventsSubject.onNext(TriggerButtonPressedEvent()) } - val numberOfEvents = 3 - repeat(numberOfEvents) { eventsSubject.onNext(Un20StateChangeEvent(DigitalValue.TRUE)) } - repeat(numberOfEvents) { eventsSubject.onNext(TriggerButtonPressedEvent()) } - - assertThat(testObserver.valueCount()).isEqualTo(numberOfEvents) - } + assertThat(testObserver.valueCount()).isEqualTo(numberOfEvents) - @Test - fun scanner_turnOnAndOffUn20_changesStateCorrectlyUponStateChangeEvent() { - val eventsSubject = PublishSubject.create() - val responseSubject = PublishSubject.create() - - val messageInputStreamMock = mockk { - every { connect(any()) } just Runs - every { veroResponses } returns responseSubject.toFlowable(BackpressureStrategy.BUFFER) - every { veroEvents } returns eventsSubject.toFlowable(BackpressureStrategy.BUFFER) - } - val mockkMessageOutputStream = mockk(relaxed = true) { - every { sendMessage(any()) } answers { - Completable.complete().doAfterTerminate { - responseSubject.onNext(SetUn20OnResponse(OperationResultCode.OK)) - eventsSubject.onNext(Un20StateChangeEvent((args[0] as SetUn20OnCommand).value)) - } - } } - val scanner = Scanner( - MainMessageChannel(messageInputStreamMock, mockkMessageOutputStream), - setupRootMessageChannelMockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - responseErrorHandler + @Test + fun scanner_turnOnAndOffUn20_changesStateCorrectlyUponStateChangeEvent() = runTest { + val messageInputStreamMock = setupMainMessageInputStreamMock( + veroMessages = listOf( + SetUn20OnResponse(OperationResultCode.OK), + ), veroEventsMessages = listOf( + Un20StateChangeEvent(DigitalValue.TRUE), + ) ) - scanner.connect(mockkInputStream, mockk()).blockingAwait() - scanner.enterMainMode().blockingAwait() - scanner.turnUn20OnAndAwaitStateChangeEvent().testSubscribe().awaitAndAssertSuccess() + val scanner = createScanner(messageInputStreamMock, mockkMessageOutputStream) + + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() + scanner.turnUn20On() assertThat(scanner.state.un20On).isTrue() - scanner.turnUn20OffAndAwaitStateChangeEvent().testSubscribe().awaitAndAssertSuccess() + scanner.turnUn20Off() assertThat(scanner.state.un20On).isFalse() } @Test - fun scanner_setSmileLedState_changesStateCorrectly() { - val responseSubject = PublishSubject.create() - - val messageInputStreamSpyk = - spyk(MainMessageInputStream(mockk(), mockk(), mockk(), mockk())).apply { - every { connect(any()) } just Runs - veroResponses = responseSubject.toFlowable(BackpressureStrategy.BUFFER) - veroEvents = Flowable.empty() - } - val mockkMessageOutputStream = mockk { - every { connect(any()) } just Runs - every { sendMessage(any()) } answers { - Completable.complete().doAfterTerminate { - responseSubject.onNext(SetSmileLedStateResponse(OperationResultCode.OK)) - } - } - } + fun scanner_setSmileLedState_changesStateCorrectly() = runTest { - val scanner = Scanner( - MainMessageChannel(messageInputStreamSpyk, mockkMessageOutputStream), - setupRootMessageChannelMockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - responseErrorHandler - ) - scanner.connect(mockkInputStream, mockk()).blockingAwait() - scanner.enterMainMode().blockingAwait() + val messageInputStreamMock = setupMainMessageInputStreamMock() + + val scanner = createScanner(messageInputStreamMock, mockkMessageOutputStream) + + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() val smileLedState = SmileLedState( LedState(DigitalValue.FALSE, 0x00, 0x00, 0x04), @@ -537,465 +587,188 @@ class ScannerTest { LedState(DigitalValue.FALSE, 0x00, 0x00, 0x04), LedState(DigitalValue.FALSE, 0x00, 0x00, 0x04) ) - - scanner.setSmileLedState(smileLedState).testSubscribe().awaitAndAssertSuccess() + scanner.setSmileLedState(smileLedState) assertThat(scanner.state.smileLedState).isEqualTo(smileLedState) } @Test - fun scanner_captureFingerprintWithUn20On_receivesFingerprint() { - val responseSubject = PublishSubject.create() - - val messageInputStreamSpyk = - spyk(MainMessageInputStream(mockk(), mockk(), mockk(), mockk())).apply { - every { connect(any()) } just Runs - un20Responses = responseSubject.toFlowable(BackpressureStrategy.BUFFER) - veroEvents = Flowable.empty() - } - val mockkMessageOutputStream = mockk { - every { connect(any()) } just Runs - every { sendMessage(any()) } answers { - Completable.complete().doAfterTerminate { - responseSubject.onNext(CaptureFingerprintResponse(CaptureFingerprintResult.OK)) - } - } - } - - val scanner = Scanner( - MainMessageChannel(messageInputStreamSpyk, mockkMessageOutputStream), - setupRootMessageChannelMockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - responseErrorHandler - ).apply { - connect(mockk(), mockk()).blockingAwait() - enterMainMode().blockingAwait() - state.un20On = true - } - - scanner.captureFingerprint().testSubscribe().awaitAndAssertSuccess() - } - - @Test - fun scanner_captureFingerprintWithUn20Off_throwsException() { - val responseSubject = PublishSubject.create() - - val messageInputStreamSpyk = mockk { - every { connect(any()) } just Runs - every { un20Responses } returns responseSubject.toFlowable(BackpressureStrategy.BUFFER) - every { veroEvents } returns Flowable.empty() - } - val mockkMessageOutputStream = mockk(relaxed = true) { - every { sendMessage(any()) } answers { - Completable.complete().doAfterTerminate { - responseSubject.onNext(CaptureFingerprintResponse(CaptureFingerprintResult.OK)) - } - } - } - - val scanner = Scanner( - MainMessageChannel(messageInputStreamSpyk, mockkMessageOutputStream), - setupRootMessageChannelMockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - responseErrorHandler - ).apply { - connect(mockk(), mockk()).blockingAwait() - enterMainMode().blockingAwait() - state.un20On = null - } - - scanner.captureFingerprint().testSubscribe().await() - .assertError(IllegalUn20StateException::class.java) + fun scanner_captureFingerprintWithUn20On_receivesFingerprint() = runTest { + val captureResponse = CaptureFingerprintResponse(CaptureFingerprintResult.OK) + val messageInputStreamMock = + setupMainMessageInputStreamMock(un20Messages = listOf(captureResponse)) + val mockkMessageOutputStream = mockkMessageOutputStream + val scanner = createScanner(messageInputStreamMock, mockkMessageOutputStream) + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() + scanner.state.un20On = true + val result = scanner.captureFingerprint() + assertThat(result).isEqualTo(captureResponse.captureFingerprintResult) + } + + @Test(expected = IllegalUn20StateException::class) + fun scanner_captureFingerprintWithUn20Off_throwsException() = runTest { + val responseSubject = listOf(CaptureFingerprintResponse(CaptureFingerprintResult.OK)) + val messageInputStreamMock = setupMainMessageInputStreamMock(un20Messages = responseSubject) + val scanner = createScanner(messageInputStreamMock, mockkMessageOutputStream) + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() + scanner.captureFingerprint() } @Test - fun scanner_acquireTemplateWithUn20On_receivesTemplate() { + fun scanner_acquireTemplateWithUn20On_receivesTemplate() = runTest { val template = byteArrayOf(0x10, 0x20, 0x30, 0x40) - val expectedResponseData = byteArrayOf(template) - - val responseSubject = PublishSubject.create() - - val messageInputStreamSpyk = - spyk(MainMessageInputStream(mockk(), mockk(), mockk(), mockk())).apply { - every { connect(any()) } just Runs - un20Responses = responseSubject.toFlowable(BackpressureStrategy.BUFFER) - veroEvents = Flowable.empty() - } - val mockkMessageOutputStream = mockk(relaxed = true) { - every { sendMessage(any()) } answers { - Completable.complete().doAfterTerminate { - responseSubject.onNext( - GetTemplateResponse( - Scanner.DEFAULT_TEMPLATE_TYPE, - TemplateData(template) - ) - ) - } - } - } - - val scanner = Scanner( - MainMessageChannel(messageInputStreamSpyk, mockkMessageOutputStream), - setupRootMessageChannelMockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - responseErrorHandler - ).apply { - connect(mockk(), mockk()).blockingAwait() - enterMainMode().blockingAwait() - state.un20On = true - } - - val testObserver = scanner.acquireTemplate().testSubscribe() - testObserver.awaitAndAssertSuccess() - testObserver.assertValueCount(1) - testObserver.values().first().let { - assertThat(byteArrayOf(it.template)).isEqualTo(expectedResponseData) - } - + val expectedResponseData = template + val responseSubject = + listOf(GetTemplateResponse(Scanner.DEFAULT_TEMPLATE_TYPE, TemplateData(template))) + val messageInputStreamMock = setupMainMessageInputStreamMock(un20Messages = responseSubject) + val scanner = createScanner(messageInputStreamMock, mockkMessageOutputStream) + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() + scanner.state.un20On = true + val result = scanner.acquireTemplate() + assertThat(result).isNotNull() + assertThat(result?.template).isEqualTo(expectedResponseData) } @Test - fun scanner_acquireImageWithUn20On_receivesImage() { + fun scanner_acquireImageWithUn20On_receivesImage() = runTest { val image = byteArrayOf(0x10, 0x20, 0x30, 0x40) val crcCheck = -42 + val responseSubject = listOf(GetImageResponse(ImageFormat.RAW, ImageData(image, crcCheck))) + val messageInputStreamMock = setupMainMessageInputStreamMock(un20Messages = responseSubject) + val scanner = createScanner(messageInputStreamMock, mockkMessageOutputStream) + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() + scanner.state.un20On = true - val responseSubject = PublishSubject.create() - - val messageInputStreamSpyk = - spyk(MainMessageInputStream(mockk(), mockk(), mockk(), mockk())).apply { - every { connect(any()) } just Runs - un20Responses = responseSubject.toFlowable(BackpressureStrategy.BUFFER) - veroEvents = Flowable.empty() - } - val mockkMessageOutputStream = mockk(relaxed = true) { - every { sendMessage(any()) } answers { - Completable.complete().doAfterTerminate { - responseSubject.onNext( - GetImageResponse( - ImageFormat.RAW, - ImageData(image, crcCheck) - ) - ) - } - } - } - - val scanner = Scanner( - MainMessageChannel(messageInputStreamSpyk, mockkMessageOutputStream), - setupRootMessageChannelMockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - responseErrorHandler - ).apply { - connect(mockk(), mockk()).blockingAwait() - enterMainMode().blockingAwait() - state.un20On = true - } - - val testObserver = scanner.acquireImage().testSubscribe() - testObserver.awaitAndAssertSuccess() - testObserver.assertValueCount(1) - assertThat(testObserver.values().first().image).isEqualTo(image) - } - - @Test - fun scanner_acquireUnprocessedImageWithUn20On_receivesImage() { - val image = byteArrayOf(0x10, 0x20, 0x30, 0x40) - val crcCheck = -42 - - val responseSubject = PublishSubject.create() - - val messageInputStreamSpyk = - spyk(MainMessageInputStream(mockk(), mockk(), mockk(), mockk())).apply { - every { connect(any()) } just Runs - un20Responses = responseSubject.toFlowable(BackpressureStrategy.BUFFER) - veroEvents = Flowable.empty() - } - val mockkMessageOutputStream = mockk(relaxed = true) { - every { sendMessage(any()) } answers { - Completable.complete().doAfterTerminate { - responseSubject.onNext( - GetImageResponse( - ImageFormat.RAW, - ImageData(image, crcCheck) - ) - ) - } - } - } - val scanner = Scanner( - MainMessageChannel(messageInputStreamSpyk, mockkMessageOutputStream), - setupRootMessageChannelMockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - responseErrorHandler - ).apply { - connect(mockk(), mockk()).blockingAwait() - enterMainMode().blockingAwait() - state.un20On = true - } - val testObserver = scanner.acquireUnprocessedImage().testSubscribe() - testObserver.awaitAndAssertSuccess() - testObserver.assertValueCount(1) - assertThat(testObserver.values().first().image).isEqualTo(image) - - } - - @Test - fun scanner_acquireImageDistortionConfigurationMatrix_receivesConfigData() { - val imageDistortionConfigurationMatrix = kotlin.byteArrayOf(0x10, 0x20, 0x30, 0x40) - val expectedResponseData = byteArrayOf(imageDistortionConfigurationMatrix) - - val responseSubject = PublishSubject.create() - - val messageInputStreamSpyk = - spyk(MainMessageInputStream(mockk(), mockk(), mockk(), mockk())).apply { - every { connect(any()) } just Runs - un20Responses = responseSubject.toFlowable(BackpressureStrategy.BUFFER) - veroEvents = Flowable.empty() - } - val mockkMessageOutputStream = mockk(relaxed = true) { - every { sendMessage(any()) } answers { - Completable.complete().doAfterTerminate { - responseSubject.onNext( - GetImageDistortionConfigurationMatrixResponse( - imageDistortionConfigurationMatrix - ) - ) - } - } - } - val scanner = Scanner( - MainMessageChannel(messageInputStreamSpyk, mockkMessageOutputStream), - setupRootMessageChannelMockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - responseErrorHandler - ).apply { - connect(mockk(), mockk()).blockingAwait() - enterMainMode().blockingAwait() - state.un20On = true - } - val testObserver = scanner.acquireImageDistortionConfigurationMatrix().testSubscribe() - testObserver.awaitAndAssertSuccess() - testObserver.assertValueCount(1) - testObserver.values().first().let { - assertThat(byteArrayOf(it)).isEqualTo(expectedResponseData) - } + val result = scanner.acquireImage() + assertThat(result).isNotNull() + assertThat(result?.image).isEqualTo(image) } @Test - fun scanner_acquireImage_noImageTakenCallsComplete() { - val responseSubject = PublishSubject.create() - - val messageInputStreamSpyk = - spyk(MainMessageInputStream(mockk(), mockk(), mockk(), mockk())).apply { - every { connect(any()) } just Runs - un20Responses = responseSubject.toFlowable(BackpressureStrategy.BUFFER) - veroEvents = Flowable.empty() - } - val mockkMessageOutputStream = mockk { - every { connect(any()) } just Runs - every { sendMessage(any()) } answers { - Completable.complete().doAfterTerminate { - responseSubject.onNext(GetImageResponse(ImageFormat.RAW, null)) - } - } - } - - val scanner = Scanner( - MainMessageChannel(messageInputStreamSpyk, mockkMessageOutputStream), - setupRootMessageChannelMockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - responseErrorHandler - ).apply { - connect(mockkInputStream, mockk()).blockingAwait() - enterMainMode().blockingAwait() - state.un20On = true - } - - val testObserver = scanner.acquireImage().testSubscribe() - testObserver.awaitAndAssertSuccess() - testObserver.assertValueCount(0) - testObserver.assertComplete() + fun scanner_acquireImage_noImageTakenCallsComplete() = runTest { + val responseSubject = listOf(GetImageResponse(ImageFormat.RAW, null)) + val messageInputStreamMock = setupMainMessageInputStreamMock(un20Messages = responseSubject) + val scanner = createScanner(messageInputStreamMock, mockkMessageOutputStream) + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() + scanner.state.un20On = true + val result = scanner.acquireImage() + assertThat(result).isNull() } @Test fun scanner_connectThenDisconnect_resetsToDisconnectedState() { - scanner.connect(mockkInputStream, mockk()).blockingAwait() - scanner.disconnect().blockingAwait() - + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.disconnect() assertThat(scanner.state).isEqualTo(disconnectedScannerState()) } @Test - fun `test scanner disconnect disposes flowableInputStream`() { - //Given - val scanner = Scanner( - mockk(), - mockk(relaxed = true), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk(), - mockk() - ) - - //When - scanner.connect(mockkInputStream, mockk(relaxed = true)).blockingAwait() - scanner.disconnect().blockingAwait() - //Then - verify { flowableDisposable.dispose() } - } - - @Test - fun scanner_getStmVersion_shouldReturnStmExtendedVersion() { + fun scanner_getStmVersion_shouldReturnStmExtendedVersion() = runTest { val expectedVersion = StmExtendedFirmwareVersion("1.E-1.1") val scannerInfoReaderMockk = mockk { - every { getStmExtendedFirmwareVersion() } returns Single.just(expectedVersion) + coEvery { getStmExtendedFirmwareVersion() } returns expectedVersion } - - - val scanner = Scanner( - MainMessageChannel(mockkMessageInputStream, mockkMessageOutputStream), - setupRootMessageChannelMockk(), - scannerInfoReaderMockk, - mockk(), mockk(), mockk(), mockk(), mockk(), - responseErrorHandler - ) - scanner.connect(mockkInputStream, mockk()).blockingAwait() - scanner.enterMainMode().blockingAwait() - - val testObserver = scanner.getStmFirmwareVersion().test() - testObserver.awaitAndAssertSuccess() - - assertThat(testObserver.values().first()).isEqualTo(expectedVersion) + val scanner = createScanner(scannerInfoReaderMockk) + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() + val result = scanner.getStmFirmwareVersion() + assertThat(result).isEqualTo(expectedVersion) } - @Test - fun scanner_getCypressExtendedVersion_shouldReturnCypressExtendedVersion() { + fun scanner_getCypressExtendedVersion_shouldReturnCypressExtendedVersion() = runTest { val expectedVersion = CypressExtendedFirmwareVersion("1.E-1.1") val scannerInfoReaderMockk = mockk { - every { getCypressExtendedVersion() } returns Single.just(expectedVersion) + coEvery { getCypressExtendedVersion() } returns expectedVersion } val scanner = Scanner( mockk(), - setupRootMessageChannelMockk(), + setupRootMessageChannelMock(), scannerInfoReaderMockk, - mockk(), mockk(), mockk(), mockk(), mockk(), - responseErrorHandler - ) - scanner.connect(mockkInputStream, mockk()).blockingAwait() - - val testObserver = scanner.getCypressExtendedFirmwareVersion().test() - testObserver.awaitAndAssertSuccess() - - assertThat(testObserver.values().first()).isEqualTo(expectedVersion) - } - - @Test - fun scanner_getExtendedVersion_shouldReturn_ExtendedVersionInformation() = runTest { - val expectedVersion = mockk() - val scannerInfoReaderMockk = mockk { - coEvery { getExtendedVersionInfo() } returns GetExtendedVersionResponse(expectedVersion) - } - - val scanner = Scanner( - mockk(), - setupRootMessageChannelMockk(), - scannerInfoReaderMockk, - mockk(), mockk(), mockk(), mockk(), mockk(), - responseErrorHandler - ) - scanner.connect(mockkInputStream, mockk()).blockingAwait() - - val testObserver = scanner.getExtendedVersionInformation().test() - testObserver.awaitAndAssertSuccess() - - assertThat(testObserver.values().first()).isEqualTo(expectedVersion) - } - - @Test - fun `isConnected() correctly passes scanner connection state`() = runTest { - val scanner = Scanner( - mockk(), - setupRootMessageChannelMockk(), mockk(), mockk(), mockk(), mockk(), mockk(), - mockk(), - responseErrorHandler ) - scanner.connect(mockkInputStream, mockk()).blockingAwait() - assertThat(scanner.isConnected()).isTrue() + scanner.connect(mockkInputStream, mockkOutputStream) - scanner.disconnect().blockingAwait() - assertThat(scanner.isConnected()).isFalse() + val result = scanner.getCypressExtendedFirmwareVersion() + assertThat(result).isEqualTo(expectedVersion) } - private fun setupRootMessageChannelMockk(): RootMessageChannel { - - val responseSubject = PublishSubject.create() + @Test + fun test_startUn20Ota() = runTest { + val otaBinary = byteArrayOf() + coEvery { un20OtaController.program(any(), otaBinary) } returns flowOf() + scanner.connect(mockkInputStream, mockkOutputStream) + scanner.enterMainMode() + scanner.state.un20On = true + scanner.startUn20Ota(otaBinary) + coVerify { un20OtaController.program(any(), otaBinary) } + } + + private fun setupMainMessageInputStreamMock( + veroMessages: List = emptyList(), + un20Messages: List = emptyList(), + veroEventsMessages: List = emptyList() + ): MainMessageInputStream = mockk { + justRun { connect(any()) } + justRun { disconnect() } + every { un20Responses } returns un20Messages.toFlowable() + every { veroResponses } returns veroMessages.toFlowable() + every { veroEvents } returns veroEventsMessages.toFlowable() + } + + private fun setupRootMessageChannelMock(): RootMessageChannel { val mockRootMessageInputStream = mockk { - every { connect(any()) } just Runs - every { disconnect() } just Runs - every { rootResponseStream } returns responseSubject.toFlowable(BackpressureStrategy.BUFFER) - } - val mockRootMessageOutputStream = mockk { - every { connect(any()) } just Runs - every { disconnect() } just Runs - every { sendMessage(any()) } answers { - Completable.complete().doAfterTerminate { - responseSubject.onNext( - when (args[0] as RootCommand) { - is EnterMainModeCommand -> EnterMainModeResponse() - is EnterStmOtaModeCommand -> EnterStmOtaModeResponse() - is EnterCypressOtaModeCommand -> EnterCypressOtaModeResponse() - else -> throw IllegalArgumentException() - } - ) - } - } + justRun { connect(any()) } + justRun { disconnect() } + every { rootResponseStream } returns listOf( + EnterMainModeResponse(), EnterStmOtaModeResponse(), EnterCypressOtaModeResponse() + ).toFlowable() } - return RootMessageChannel(mockRootMessageInputStream, mockRootMessageOutputStream) + val mockRootMessageOutputStream = mockk { + justRun { connect(any()) } + justRun { disconnect() } + every { sendMessage(any()) } returns Completable.complete() + } + return RootMessageChannel( + mockRootMessageInputStream, + mockRootMessageOutputStream, + dispatcher + ) } + + private fun createScanner( + messageInputStreamMock: MainMessageInputStream, + mockkMessageOutputStream: MainMessageOutputStream + ) = Scanner( + MainMessageChannel(messageInputStreamMock, mockkMessageOutputStream, dispatcher), + setupRootMessageChannelMock(), + mockk(), + mockk(), + mockk(), + mockk(), + mockk(), + mockk(), + ) + + private fun createScanner(scannerInfoReaderMockk: ScannerExtendedInfoReaderHelper) = Scanner( + MainMessageChannel(mockkMessageInputStream, mockkMessageOutputStream, dispatcher), + setupRootMessageChannelMock(), + scannerInfoReaderMockk, + mockk(), + mockk(), + mockk(), + mockk(), + mockk(), + ) } diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/errorhandler/ResponseErrorHandlerTest.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/errorhandler/ResponseErrorHandlerTest.kt index ea591064c0..02e0d632a0 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/errorhandler/ResponseErrorHandlerTest.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/errorhandler/ResponseErrorHandlerTest.kt @@ -1,128 +1,47 @@ package com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler import com.google.common.truth.Truth.assertThat +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.events.Un20StateChangeEvent +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.DigitalValue +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.models.OperationResultCode +import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.vero.responses.SetUn20OnResponse import com.simprints.fingerprint.infra.scanner.v2.exceptions.parsing.InvalidMessageException -import com.simprints.fingerprint.infra.scanner.v2.tools.helpers.SchedulerHelper.INTERVAL -import com.simprints.fingerprint.infra.scanner.v2.tools.helpers.SchedulerHelper.TIMEOUT -import com.simprints.testtools.common.syntax.awaitAndAssertSuccess -import io.reactivex.Single -import io.reactivex.schedulers.TestScheduler +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runTest import org.junit.Test import java.io.IOException -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicInteger class ResponseErrorHandlerTest { @Test - fun handleError_timesOutThenSucceeds_retriesCorrectNumberOfTimesAndSucceeds() { - val currentTry = AtomicInteger(0) - val shouldSucceedOnTry = 4 - val successValue = "success!" - - val testScheduler = TestScheduler() - - val responseErrorHandler = ResponseErrorHandler(ResponseErrorHandlingStrategy( - generalTimeOutMs = 500, retryTimes = 3 - ), testScheduler) - - val testSubscriber = - Single.defer { - Single.just(successValue) - .run { - if (currentTry.incrementAndGet() != shouldSucceedOnTry) { - delay(2000, TimeUnit.MILLISECONDS, testScheduler) - } else { - this - } - } - }.handleErrorsWith(responseErrorHandler).timeout(TIMEOUT, TimeUnit.SECONDS).test() - - do { - testScheduler.advanceTimeBy(INTERVAL, TimeUnit.MILLISECONDS) - } while (!testSubscriber.isTerminated) + fun handle_succeeds() = runTest { + val successValue = Un20StateChangeEvent(DigitalValue.TRUE) + val responseErrorHandler = ResponseErrorHandler(ResponseErrorHandlingStrategy.DEFAULT) + val result = responseErrorHandler.handle { successValue } - testSubscriber.awaitAndAssertSuccess() - testSubscriber.assertValue(successValue) - assertThat(currentTry.get()).isEqualTo(shouldSucceedOnTry) + assertThat(result).isEqualTo(successValue) } - @Test - fun handleError_timesOutForAllRetries_failsDespiteRetries() { - val currentTry = AtomicInteger(0) - val shouldSucceedOnTry = 4 - val successValue = "success!" - - val testScheduler = TestScheduler() - - val responseErrorHandler = ResponseErrorHandler(ResponseErrorHandlingStrategy( - generalTimeOutMs = 500, retryTimes = 2 - ), testScheduler) - - val testSubscriber = - Single.defer { - Single.just(successValue) - .run { - if (currentTry.incrementAndGet() != shouldSucceedOnTry) { - delay(2000, TimeUnit.MILLISECONDS, testScheduler) - } else { - this - } - } - }.handleErrorsWith(responseErrorHandler).timeout(TIMEOUT, TimeUnit.SECONDS).test() - - do { - testScheduler.advanceTimeBy(INTERVAL, TimeUnit.MILLISECONDS) - } while (!testSubscriber.isTerminated) - - testSubscriber.await() - testSubscriber.assertError(IOException::class.java) + @OptIn(ExperimentalCoroutinesApi::class) + @Test(expected = IOException::class) + fun handle_fails() = runTest { + val responseErrorHandler = ResponseErrorHandler(ResponseErrorHandlingStrategy.DEFAULT) + val delayTimeMilliSeconds = ResponseErrorHandlingStrategy.DEFAULT.generalTimeOutMs + 1000 + responseErrorHandler.handle { + delay(delayTimeMilliSeconds) + SetUn20OnResponse(OperationResultCode.OK) + } + advanceTimeBy(delayTimeMilliSeconds) } - @Test - fun handleError_propagatesNonTimeoutErrors() { + @Test(expected = InvalidMessageException::class) + fun handle_propagatesNonTimeoutErrors() = runTest { val thrownException = InvalidMessageException() - val responseErrorHandler = ResponseErrorHandler(ResponseErrorHandlingStrategy.DEFAULT) - - val testSubscriber = Single - .error(thrownException) - .handleErrorsWith(responseErrorHandler) - .timeout(TIMEOUT, TimeUnit.SECONDS) - .test() - - testSubscriber.await() - testSubscriber.assertError(thrownException) - } - - @Test - fun handleError_givenNoneStrategy_doesNotTimeoutOrRetryAndWaitsUntilSucceeds() { - val currentTry = AtomicInteger(0) - val shouldSucceedOnTry = 4 - val successValue = "success!" - - val testScheduler = TestScheduler() - - val responseErrorHandler = ResponseErrorHandler(ResponseErrorHandlingStrategy.NONE, testScheduler) - - val testSubscriber = - Single.defer { - Single.just(successValue) - .run { - if (currentTry.incrementAndGet() != shouldSucceedOnTry) { - delay(2000, TimeUnit.MILLISECONDS, testScheduler) - } else { - this - } - } - }.handleErrorsWith(responseErrorHandler).timeout(TIMEOUT, TimeUnit.SECONDS).test() - - do { - testScheduler.advanceTimeBy(INTERVAL, TimeUnit.MILLISECONDS) - } while (!testSubscriber.isTerminated) - - testSubscriber.await() - testSubscriber.assertValue(successValue) - assertThat(currentTry.get()).isEqualTo(1) + responseErrorHandler.handle { + throw thrownException + } } } diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/cypress/CypressOtaControllerTest.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/cypress/CypressOtaControllerTest.kt index 16a89c91b6..079a82d1b1 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/cypress/CypressOtaControllerTest.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/cypress/CypressOtaControllerTest.kt @@ -10,11 +10,8 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.cypressota.responses.Er import com.simprints.fingerprint.infra.scanner.v2.domain.cypressota.responses.OkResponse import com.simprints.fingerprint.infra.scanner.v2.exceptions.ota.OtaFailedException import com.simprints.fingerprint.infra.scanner.v2.incoming.cypressota.CypressOtaMessageInputStream -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandler -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandlingStrategy import com.simprints.fingerprint.infra.scanner.v2.tools.crc.Crc32Calculator -import com.simprints.testtools.common.syntax.awaitAndAssertSuccess -import com.simprints.testtools.unit.reactive.testSubscribe +import io.mockk.coVerify import io.mockk.every import io.mockk.justRun import io.mockk.mockk @@ -23,6 +20,10 @@ import io.mockk.verify import io.reactivex.BackpressureStrategy import io.reactivex.Completable import io.reactivex.subjects.PublishSubject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.test.runTest import org.junit.Test import java.util.concurrent.atomic.AtomicInteger import kotlin.math.ceil @@ -30,24 +31,27 @@ import kotlin.math.roundToInt import kotlin.random.Random class CypressOtaControllerTest { - - private val responseErrorHandler = ResponseErrorHandler(ResponseErrorHandlingStrategy.NONE) - @Test - fun program_correctlyEmitsProgressValuesAndCompletes() { + fun program_correctlyEmitsProgressValuesAndCompletes() = runTest { val cypressOtaController = CypressOtaController(configureCrcCalculatorMock()) val binFile = generateRandomBinFile() - val testObserver = cypressOtaController.program(configureMessageStreamMock(), responseErrorHandler, binFile).testSubscribe() + val testObserver = cypressOtaController.program( + configureMessageStreamMock(), + binFile + ).toList() - testObserver.awaitAndAssertSuccess() - assertThat(testObserver.values()).containsExactlyElementsIn(generateExpectedProgressValues(binFile)).inOrder() - testObserver.assertComplete() + assertThat(testObserver.toList()).containsExactlyElementsIn( + generateExpectedProgressValues( + binFile + ) + ).inOrder() + } @Test - fun program_correctlyCallsComputeCrcAndSendCorrectNumberOfTimes() { + fun program_correctlyCallsComputeCrcAndSendCorrectNumberOfTimes() = runTest { val binFile = generateRandomBinFile() val expectedNumberOfCalls = expectedNumberOfChunks(binFile) + 3 @@ -55,67 +59,65 @@ class CypressOtaControllerTest { val messageStreamMock = configureMessageStreamMock() val cypressOtaController = CypressOtaController(crc32Calculator) - val testObserver = cypressOtaController.program(messageStreamMock, responseErrorHandler, binFile).testSubscribe() - - testObserver.awaitAndAssertSuccess() + cypressOtaController.program(messageStreamMock, binFile).toList() verify { crc32Calculator.calculateCrc32(any()) } - verify(exactly = expectedNumberOfCalls) { messageStreamMock.outgoing.sendMessage(any()) } + coVerify(exactly = expectedNumberOfCalls) { messageStreamMock.outgoing.sendMessage(any()) } } - @Test - fun program_receivesErrorAtPrepareDownload_throwsException() { + @Test(expected = OtaFailedException::class) + fun program_receivesErrorAtPrepareDownload_throwsException() = runTest { val cypressOtaController = CypressOtaController(configureCrcCalculatorMock()) - val testObserver = cypressOtaController.program( - configureMessageStreamMock(errorPositions = listOf(0)), responseErrorHandler, generateRandomBinFile()).testSubscribe() + cypressOtaController.program( + configureMessageStreamMock(errorPositions = listOf(0)), + generateRandomBinFile() + ) - testObserver.awaitTerminalEvent() - testObserver.assertError(OtaFailedException::class.java) } - @Test - fun program_receivesErrorAtDownload_throwsException() { + @Test(expected = OtaFailedException::class) + fun program_receivesErrorAtDownload_throwsException() = runTest { val cypressOtaController = CypressOtaController(configureCrcCalculatorMock()) - - val testObserver = cypressOtaController.program( - configureMessageStreamMock(errorPositions = listOf(1)), responseErrorHandler, generateRandomBinFile()).testSubscribe() - - testObserver.awaitTerminalEvent() - testObserver.assertError(OtaFailedException::class.java) + cypressOtaController.program( + configureMessageStreamMock(errorPositions = listOf(1)), + generateRandomBinFile() + ) } - @Test - fun program_receivesErrorDuringSendImageProcess_emitsValueUntilErrorThenThrowsException() { - val cypressOtaController = CypressOtaController(configureCrcCalculatorMock()) + @Test(expected = OtaFailedException::class) + fun program_receivesErrorDuringSendImageProcess_emitsValueUntilErrorThenThrowsException() = + runTest { + val cypressOtaController = CypressOtaController(configureCrcCalculatorMock()) - val binFile = generateRandomBinFile() - val progressValues = generateExpectedProgressValues(binFile) - val testObserver = cypressOtaController.program( - configureMessageStreamMock(errorPositions = listOf(4)), responseErrorHandler, binFile).testSubscribe() + val binFile = generateRandomBinFile() + val progressValues = generateExpectedProgressValues(binFile) + val testObserver = cypressOtaController.program( + configureMessageStreamMock(errorPositions = listOf(4)), + binFile + ) + assertThat(testObserver.toList()).containsExactlyElementsIn(progressValues.slice(0..1)) + .inOrder() - testObserver.awaitTerminalEvent() - assertThat(testObserver.values()).containsExactlyElementsIn(progressValues.slice(0..1)).inOrder() - testObserver.assertError(OtaFailedException::class.java) - } + } - @Test - fun program_receivesErrorAtVerify_throwsException() { + @Test(expected = OtaFailedException::class) + fun program_receivesErrorAtVerify_throwsException() = runTest { val cypressOtaController = CypressOtaController(configureCrcCalculatorMock()) val binFile = generateRandomBinFile() val indexOfVerifyResponse = expectedNumberOfChunks(binFile) + 2 - val testObserver = cypressOtaController.program( - configureMessageStreamMock(errorPositions = listOf(indexOfVerifyResponse)), responseErrorHandler, binFile).testSubscribe() - - testObserver.awaitTerminalEvent() - testObserver.assertError(OtaFailedException::class.java) + cypressOtaController.program( + configureMessageStreamMock(errorPositions = listOf(indexOfVerifyResponse)), + binFile + ).toList() } private fun configureCrcCalculatorMock() = mockk { every { calculateCrc32(any()) } returns 42 } + @OptIn(ExperimentalCoroutinesApi::class) private fun configureMessageStreamMock(errorPositions: List = listOf()): CypressOtaMessageChannel { val responseSubject = PublishSubject.create() val messageIndex = AtomicInteger(0) @@ -141,7 +143,7 @@ class CypressOtaControllerTest { ) } } - } + }, Dispatchers.IO ) } diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/stm/StmOtaControllerTest.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/stm/StmOtaControllerTest.kt index 1a1d86e3f4..ad7c98902d 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/stm/StmOtaControllerTest.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/stm/StmOtaControllerTest.kt @@ -6,18 +6,18 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.stmota.StmOtaResponse import com.simprints.fingerprint.infra.scanner.v2.domain.stmota.responses.CommandAcknowledgement import com.simprints.fingerprint.infra.scanner.v2.exceptions.ota.OtaFailedException import com.simprints.fingerprint.infra.scanner.v2.incoming.stmota.StmOtaMessageInputStream -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandler -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandlingStrategy -import com.simprints.testtools.common.syntax.awaitAndAssertSuccess -import com.simprints.testtools.unit.reactive.testSubscribe +import io.mockk.coVerify import io.mockk.every import io.mockk.justRun import io.mockk.mockk import io.mockk.spyk -import io.mockk.verify import io.reactivex.BackpressureStrategy import io.reactivex.Completable import io.reactivex.subjects.PublishSubject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.test.runTest import org.junit.Test import java.util.concurrent.atomic.AtomicInteger import kotlin.math.ceil @@ -26,64 +26,54 @@ import kotlin.random.Random class StmOtaControllerTest { - private val responseErrorHandler = ResponseErrorHandler(ResponseErrorHandlingStrategy.NONE) - @Test - fun program_correctlyEmitsProgressValuesAndCompletes() { + fun program_correctlyEmitsProgressValuesAndCompletes() = runTest { val stmOtaController = StmOtaController() val firmwareBin = generateRandomBinFile() val expectedProgress = generateExpectedProgressValues(firmwareBin) - val testObserver = stmOtaController.program(configureMessageStreamMock(), responseErrorHandler, firmwareBin).testSubscribe() - - testObserver.awaitAndAssertSuccess() - - assertThat(testObserver.values()).containsExactlyElementsIn(expectedProgress).inOrder() - testObserver.assertComplete() + val testObserver = + stmOtaController.program(configureMessageStreamMock(), firmwareBin).toList() + assertThat(testObserver.toList()).containsExactlyElementsIn(expectedProgress).inOrder() } @Test - fun program_correctlyCallsParseAndSendCorrectNumberOfTimes() { + fun program_correctlyCallsParseAndSendCorrectNumberOfTimes() = runTest { val firmwareBin = generateRandomBinFile() val expectedNumberOfCalls = expectedNumberOfChunks(firmwareBin) * 3 + 5 val messageStreamMock = configureMessageStreamMock() val stmOtaController = StmOtaController() - - val testObserver = stmOtaController.program(messageStreamMock, responseErrorHandler, firmwareBin).testSubscribe() - - testObserver.awaitAndAssertSuccess() - - verify(exactly = expectedNumberOfCalls) { messageStreamMock.outgoing.sendMessage(any()) } + stmOtaController.program(messageStreamMock, firmwareBin).toList() + coVerify(exactly = expectedNumberOfCalls) { messageStreamMock.outgoing.sendMessage(any()) } } - @Test - fun program_receivesNackAtStart_throwsException() { + @Test(expected = OtaFailedException::class) + fun program_receivesNackAtStart_throwsException() = runTest { val stmOtaController = StmOtaController() - - val testObserver = stmOtaController.program( - configureMessageStreamMock(nackPositions = listOf(0)), responseErrorHandler, byteArrayOf()).testSubscribe() - - testObserver.awaitTerminalEvent() - testObserver.assertError(OtaFailedException::class.java) + stmOtaController.program( + configureMessageStreamMock(nackPositions = listOf(0)), + byteArrayOf() + ).toList() } - @Test - fun program_receivesNackDuringProcess_emitsValueUntilNackThenThrowsException() { + @Test(expected = OtaFailedException::class) + fun program_receivesNackDuringProcess_emitsValueUntilNackThenThrowsException() = runTest { val stmOtaController = StmOtaController() val firmwareBin = generateRandomBinFile() val expectedProgress = generateExpectedProgressValues(firmwareBin) val testObserver = stmOtaController.program( - configureMessageStreamMock(nackPositions = listOf(10)), responseErrorHandler, firmwareBin).testSubscribe() - - testObserver.awaitTerminalEvent() - assertThat(testObserver.values()).containsExactlyElementsIn(expectedProgress.slice(0..1)).inOrder() - testObserver.assertError(OtaFailedException::class.java) + configureMessageStreamMock(nackPositions = listOf(10)), + firmwareBin + ).toList() + assertThat(testObserver.toList()).containsExactlyElementsIn(expectedProgress.slice(0..1)) + .inOrder() } + @OptIn(ExperimentalCoroutinesApi::class) private fun configureMessageStreamMock(nackPositions: List = listOf()): StmOtaMessageChannel { val responseSubject = PublishSubject.create() val messageIndex = AtomicInteger(0) @@ -91,21 +81,25 @@ class StmOtaControllerTest { return StmOtaMessageChannel( spyk(StmOtaMessageInputStream(mockk())).apply { justRun { connect(any()) } - every { stmOtaResponseStream } returns responseSubject.toFlowable(BackpressureStrategy.BUFFER) + every { stmOtaResponseStream } returns responseSubject.toFlowable( + BackpressureStrategy.BUFFER + ) }, mockk { every { sendMessage(any()) } answers { Completable.complete().doAfterTerminate { - responseSubject.onNext(CommandAcknowledgement( - if (nackPositions.contains(messageIndex.getAndIncrement())) { - CommandAcknowledgement.Kind.NACK - } else { - CommandAcknowledgement.Kind.ACK - } - )) + responseSubject.onNext( + CommandAcknowledgement( + if (nackPositions.contains(messageIndex.getAndIncrement())) { + CommandAcknowledgement.Kind.NACK + } else { + CommandAcknowledgement.Kind.ACK + } + ) + ) } } - } + }, Dispatchers.IO ) } diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/un20/Un20OtaControllerTest.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/un20/Un20OtaControllerTest.kt index 81569ce255..ff3a1fa128 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/un20/Un20OtaControllerTest.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/v2/scanner/ota/un20/Un20OtaControllerTest.kt @@ -13,11 +13,8 @@ import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.respo import com.simprints.fingerprint.infra.scanner.v2.domain.main.message.un20.responses.WriteOtaChunkResponse import com.simprints.fingerprint.infra.scanner.v2.exceptions.ota.OtaFailedException import com.simprints.fingerprint.infra.scanner.v2.incoming.main.MainMessageInputStream -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandler -import com.simprints.fingerprint.infra.scanner.v2.scanner.errorhandler.ResponseErrorHandlingStrategy import com.simprints.fingerprint.infra.scanner.v2.tools.crc.Crc32Calculator -import com.simprints.testtools.common.syntax.awaitAndAssertSuccess -import com.simprints.testtools.unit.reactive.testSubscribe +import io.mockk.coVerify import io.mockk.every import io.mockk.justRun import io.mockk.mockk @@ -26,6 +23,10 @@ import io.mockk.verify import io.reactivex.BackpressureStrategy import io.reactivex.Completable import io.reactivex.subjects.PublishSubject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.test.runTest import org.junit.Test import java.util.concurrent.atomic.AtomicInteger import kotlin.math.ceil @@ -34,91 +35,86 @@ import kotlin.random.Random class Un20OtaControllerTest { - private val responseErrorHandler = ResponseErrorHandler(ResponseErrorHandlingStrategy.NONE) - @Test - fun program_correctlyEmitsProgressValuesAndCompletes() { - val un20OtaController = Un20OtaController(configureCrcCalculatorMockk()) + fun program_correctlyEmitsProgressValuesAndCompletes() = runTest { + val un20OtaController = Un20OtaController(configureCrcCalculatorMock()) val binFile = generateRandomBinFile() - val testObserver = un20OtaController.program(configureMessageStreamMock(), responseErrorHandler, binFile).testSubscribe() - - testObserver.awaitAndAssertSuccess() + val testObserver = un20OtaController.program(configureMessageStreamMock(), binFile) - assertThat(testObserver.values()).containsExactlyElementsIn(generateExpectedProgressValues(binFile)).inOrder() - testObserver.assertComplete() + assertThat(testObserver.toList()).containsExactlyElementsIn( + generateExpectedProgressValues(binFile) + ).inOrder() } @Test - fun program_correctlyCallsComputeCrcAndSendCorrectNumberOfTimes() { + fun program_correctlyCallsComputeCrcAndSendCorrectNumberOfTimes() = runTest { val binFile = generateRandomBinFile() val expectedNumberOfCalls = expectedNumberOfChunks(binFile) + 2 - val crc32Calculator = configureCrcCalculatorMockk() + val crc32Calculator = configureCrcCalculatorMock() val messageStreamMock = configureMessageStreamMock() val un20OtaController = Un20OtaController(crc32Calculator) - val testObserver = un20OtaController.program(messageStreamMock, responseErrorHandler, binFile).testSubscribe() + un20OtaController.program(messageStreamMock, binFile).toList() - testObserver.awaitAndAssertSuccess() verify { crc32Calculator.calculateCrc32(any()) } - verify(exactly = expectedNumberOfCalls) { messageStreamMock.outgoing.sendMessage(any()) } + coVerify(exactly = expectedNumberOfCalls) { messageStreamMock.outgoing.sendMessage(any()) } } - @Test - fun program_receivesErrorAtPrepareDownload_throwsException() { - val un20OtaController = Un20OtaController(configureCrcCalculatorMockk()) - - val testObserver = un20OtaController.program( - configureMessageStreamMock(errorPositions = listOf(0)), responseErrorHandler, generateRandomBinFile()).testSubscribe() + @Test(expected = OtaFailedException::class) + fun program_receivesErrorAtPrepareDownload_throwsException() = runTest { + val un20OtaController = Un20OtaController(configureCrcCalculatorMock()) - testObserver.awaitTerminalEvent() - testObserver.assertError(OtaFailedException::class.java) + un20OtaController.program( + configureMessageStreamMock(errorPositions = listOf(0)), + generateRandomBinFile(), + ) } - @Test - fun program_receivesErrorAtDownload_throwsException() { - val un20OtaController = Un20OtaController(configureCrcCalculatorMockk()) - - val testObserver = un20OtaController.program( - configureMessageStreamMock(errorPositions = listOf(1)), responseErrorHandler, generateRandomBinFile()).testSubscribe() + @Test(expected = OtaFailedException::class) + fun program_receivesErrorAtDownload_throwsException() = runTest { + val un20OtaController = Un20OtaController(configureCrcCalculatorMock()) - testObserver.awaitTerminalEvent() - testObserver.assertError(OtaFailedException::class.java) + un20OtaController.program( + configureMessageStreamMock(errorPositions = listOf(1)), + generateRandomBinFile(), + ).toList() } - @Test - fun program_receivesErrorDuringSendImageProcess_emitsValueUntilErrorThenThrowsException() { - val un20OtaController = Un20OtaController(configureCrcCalculatorMockk()) - - val binFile = generateRandomBinFile() - val progressValues = generateExpectedProgressValues(binFile) - val testObserver = un20OtaController.program( - configureMessageStreamMock(errorPositions = listOf(4)), responseErrorHandler, binFile).testSubscribe() - - testObserver.awaitTerminalEvent() - assertThat(testObserver.values()).containsExactlyElementsIn(progressValues.slice(0..1)).inOrder() - testObserver.assertError(OtaFailedException::class.java) - } + @Test(expected = OtaFailedException::class) + fun program_receivesErrorDuringSendImageProcess_emitsValueUntilErrorThenThrowsException() = + runTest { + val un20OtaController = Un20OtaController(configureCrcCalculatorMock()) + + val binFile = generateRandomBinFile() + val progressValues = generateExpectedProgressValues(binFile) + val testObserver = un20OtaController.program( + configureMessageStreamMock(errorPositions = listOf(4)), + binFile, + ) + assertThat(testObserver.toList()).containsExactlyElementsIn(progressValues.slice(0..1)) + .inOrder() + } - @Test - fun program_receivesErrorAtVerify_throwsException() { - val un20OtaController = Un20OtaController(configureCrcCalculatorMockk()) + @Test(expected = OtaFailedException::class) + fun program_receivesErrorAtVerify_throwsException() = runTest { + val un20OtaController = Un20OtaController(configureCrcCalculatorMock()) val binFile = generateRandomBinFile() val indexOfVerifyResponse = expectedNumberOfChunks(binFile) + 1 - val testObserver = un20OtaController.program( - configureMessageStreamMock(errorPositions = listOf(indexOfVerifyResponse)), responseErrorHandler, binFile).testSubscribe() - - testObserver.awaitTerminalEvent() - testObserver.assertError(OtaFailedException::class.java) + un20OtaController.program( + configureMessageStreamMock(errorPositions = listOf(indexOfVerifyResponse)), + binFile, + ).toList() } - private fun configureCrcCalculatorMockk() = mockk { + private fun configureCrcCalculatorMock() = mockk { every { calculateCrc32(any()) } returns 42 } + @OptIn(ExperimentalCoroutinesApi::class) private fun configureMessageStreamMock(errorPositions: List = listOf()): MainMessageChannel { val responseSubject = PublishSubject.create() val messageIndex = AtomicInteger(0) @@ -148,7 +144,7 @@ class Un20OtaControllerTest { } } ?: Completable.complete() } - } + }, Dispatchers.IO ) } diff --git a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/wrapper/ScannerWrapperV2Test.kt b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/wrapper/ScannerWrapperV2Test.kt index dafd3c6abc..db65f9e5ed 100644 --- a/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/wrapper/ScannerWrapperV2Test.kt +++ b/fingerprint/infra/scanner/src/test/java/com/simprints/fingerprint/infra/scanner/wrapper/ScannerWrapperV2Test.kt @@ -21,9 +21,6 @@ import com.simprints.fingerprint.infra.scanner.v2.tools.ScannerUiHelper import com.simprints.testtools.common.syntax.assertThrows import io.mockk.* import io.mockk.impl.annotations.MockK -import io.reactivex.Completable -import io.reactivex.Maybe -import io.reactivex.Single import kotlinx.coroutines.* import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -180,40 +177,39 @@ internal class ScannerWrapperV2Test { @Test fun `should not turn off(on) the Un20 sensor when sensorShutdown(sensorWakeup) is called and the sensor's current state is off(on)`() = runTest { - every { scannerV2.getUn20Status() } returns Single.just(false) andThen Single.just(true) - every { scannerV2.turnUn20OffAndAwaitStateChangeEvent() } returns Completable.complete() - every { scannerV2.turnUn20OnAndAwaitStateChangeEvent() } returns Completable.complete() - + coEvery { scannerV2.getUn20Status() } returns false andThen true + coJustRun { scannerV2.turnUn20Off() } + coJustRun { scannerV2.turnUn20On() } scannerWrapper.sensorShutDown() scannerWrapper.sensorWakeUp() - verify(exactly = 0) { - scannerV2.turnUn20OffAndAwaitStateChangeEvent() - scannerV2.turnUn20OnAndAwaitStateChangeEvent() + coVerify(exactly = 0) { + scannerV2.turnUn20Off() + scannerV2.turnUn20On() } } @Test fun `should turn on the Un20 sensor when sensorWakeup is called and the sensor's current state is off`() = runTest { - every { scannerV2.getUn20Status() } returns Single.just(false) - every { scannerV2.turnUn20OnAndAwaitStateChangeEvent() } returns Completable.complete() + coEvery { scannerV2.getUn20Status() } returns false + coJustRun { scannerV2.turnUn20On() } scannerWrapper.sensorWakeUp() - verify(exactly = 1) { scannerV2.turnUn20OnAndAwaitStateChangeEvent() } + coVerify(exactly = 1) { scannerV2.turnUn20On() } } @Test(expected = ScannerDisconnectedException::class) fun `should throw ScannerDisconnectedException if getUn20Status throws IO`() = runTest { - every { scannerV2.getUn20Status() } throws IOException("") + coEvery { scannerV2.getUn20Status() } throws IOException("") scannerWrapper.sensorWakeUp() } @Test(expected = ScannerDisconnectedException::class) fun `should throw ScannerDisconnectedException if getUn20Status throws NotConnectedException`() = runTest { - every { scannerV2.getUn20Status() } throws NotConnectedException("") + coEvery { scannerV2.getUn20Status() } throws NotConnectedException("") scannerWrapper.sensorWakeUp() } @@ -221,12 +217,12 @@ internal class ScannerWrapperV2Test { @Test fun `should turn off the Un20 sensor when sensorShutdown is called and the sensor's current state is on`() = runTest { - every { scannerV2.getUn20Status() } returns Single.just(true) - every { scannerV2.turnUn20OffAndAwaitStateChangeEvent() } returns Completable.complete() + coEvery { scannerV2.getUn20Status() } returns true + coJustRun { scannerV2.turnUn20Off() } scannerWrapper.sensorShutDown() - verify(exactly = 1) { scannerV2.turnUn20OffAndAwaitStateChangeEvent() } + coVerify(exactly = 1) { scannerV2.turnUn20Off() } } @Test(expected = UnexpectedScannerException::class) @@ -240,8 +236,8 @@ internal class ScannerWrapperV2Test { fun `should complete execution successfully when setting scanner UI to default, when startLiveFeedback is called`() = runTest { every { scannerWrapper.isLiveFeedbackAvailable() } returns true - every { scannerV2.setSmileLedState(any()) } returns Completable.complete() - every { scannerV2.getImageQualityPreview() } returns Maybe.just(50) + coJustRun { scannerV2.setSmileLedState(any()) } + coEvery { scannerV2.getImageQualityPreview() } returns 50 // get the job of the continuous feedback val job = launch { @@ -258,8 +254,8 @@ internal class ScannerWrapperV2Test { fun `should complete execution successfully when startLiveFeedback is called and scanner disconnects`() = runTest { every { scannerWrapper.isLiveFeedbackAvailable() } returns true - every { scannerV2.setSmileLedState(any()) } throws IOException() - every { scannerV2.getImageQualityPreview() } returns Maybe.just(50) + coEvery { scannerV2.setSmileLedState(any()) } throws IOException() + coEvery { scannerV2.getImageQualityPreview() } returns 50 // get the job of the continuous feedback val job = launch { @@ -276,8 +272,8 @@ internal class ScannerWrapperV2Test { fun `should complete execution successfully when setting scanner UI to default, when stopLiveFeedback is called`() = runTest { every { scannerWrapper.isLiveFeedbackAvailable() } returns true - every { scannerV2.setSmileLedState(any()) } returns Completable.complete() - every { scannerV2.setScannerLedStateDefault() } returns Completable.complete() + coJustRun { scannerV2.setSmileLedState(any()) } + coJustRun { scannerV2.setScannerLedStateDefault() } scannerWrapper.stopLiveFeedback() } @@ -299,37 +295,37 @@ internal class ScannerWrapperV2Test { @Test fun `isConnected() correctly passes scanner connection status`() { - every { scannerV2.isConnected() } returns false + coEvery { scannerV2.isConnected() } returns false assertThat(scannerWrapper.isConnected()).isFalse() - every { scannerV2.isConnected() } returns true + coEvery { scannerV2.isConnected() } returns true assertThat(scannerWrapper.isConnected()).isTrue() } @Test fun `should set smile leds to flashing white when calling turnFlashingOrangeLeds`() = runTest { - every { scannerV2.setSmileLedState(any()) } returns Completable.complete() + coJustRun { scannerV2.setSmileLedState(any()) } scannerWrapper.turnOnFlashingWhiteSmileLeds() - verify { scannerV2.setSmileLedState(scannerUiHelper.whiteFlashingLedState()) } + coVerify { scannerV2.setSmileLedState(scannerUiHelper.whiteFlashingLedState()) } } @Test fun `should set smile leds to good scan when calling setUiBadCapture`() = runTest { - every { scannerV2.setSmileLedState(any()) } returns Completable.complete() + coJustRun { scannerV2.setSmileLedState(any()) } scannerWrapper.setUiGoodCapture() - verify { scannerV2.setSmileLedState(scannerUiHelper.goodScanLedState()) } + coVerify { scannerV2.setSmileLedState(scannerUiHelper.goodScanLedState()) } } @Test fun `should set smile leds to bad scan when calling setUiBadCapture`() = runTest { - every { scannerV2.setSmileLedState(any()) } returns Completable.complete() + coJustRun { scannerV2.setSmileLedState(any()) } scannerWrapper.setUiBadCapture() - verify { scannerV2.setSmileLedState(scannerUiHelper.badScanLedState()) } + coVerify { scannerV2.setSmileLedState(scannerUiHelper.badScanLedState()) } } }