Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-24 15:11:58 +04:00
parent c19963f30d
commit 932fa90397
2 changed files with 46 additions and 7 deletions

View file

@ -71,14 +71,17 @@ class GetAppUpdateStateUseCase(
val latestVersion = info.latestVersion?.let(AppVersion::parseOrNull)
val criticalVersion = info.criticalVersion?.let(AppVersion::parseOrNull)
if (criticalVersion != null && appVersion <= criticalVersion && isEscapable(latestVersion, criticalVersion)) {
if (criticalVersion != null &&
appVersion <= criticalVersion &&
isEscapable(latestVersion, criticalVersion, appVersion)
) {
return blockingStateFor(info.criticalOSVersion, deviceOsVersion, AppUpdateState.Brick)
}
val minSupportedVersion = info.minSupportedVersion?.let(AppVersion::parseOrNull)
if (minSupportedVersion != null &&
appVersion < minSupportedVersion &&
isEscapable(latestVersion, minSupportedVersion)
isEscapable(latestVersion, minSupportedVersion, appVersion)
) {
return blockingStateFor(info.minSupportedOSVersion, deviceOsVersion, AppUpdateState.OsTooOld)
}
@ -91,12 +94,17 @@ class GetAppUpdateStateUseCase(
}
/**
* A blocking threshold is honored only if the advertised latest version is strictly above it i.e.
* updating actually clears the block. A threshold no installable version can satisfy is a backend
* misconfiguration and is ignored.
* A blocking threshold is honored only if the user can actually escape it by updating. Normally that
* means the advertised latest version is strictly above the threshold. But a latest version reported
* below the installed one is stale/misconfigured and must not suppress the block the store almost
* certainly has an installable build, so the threshold is honored. A missing latest version leaves no
* installable target, so the threshold is ignored.
*/
private fun isEscapable(latestVersion: AppVersion?, threshold: AppVersion): Boolean =
latestVersion != null && latestVersion > threshold
private fun isEscapable(latestVersion: AppVersion?, threshold: AppVersion, appVersion: AppVersion): Boolean {
latestVersion ?: return false
if (latestVersion < appVersion) return true
return latestVersion > threshold
}
private suspend fun resolveOptionalUpdate(latestVersion: String, recordOptionalShown: Boolean): AppUpdateState {
if (!recordOptionalShown) return AppUpdateState.OptionalUpdate

View file

@ -111,6 +111,37 @@ internal class GetAppUpdateStateUseCaseTest {
assertThat(useCase.getCached()).isEqualTo(AppUpdateState.OsTooOld)
}
@Test
fun `GIVEN latest below installed and min supported not met WHEN getCached THEN ForceUpdate`() = runTest {
// [REDACTED_TASK_KEY]: a latestVersion below the installed one must not suppress the min-supported force update.
givenCached(
appVersion = "6.1",
osVersion = "16",
info = info(minSupportedVersion = "6.2", minSupportedOSVersion = "15.0", latestVersion = "5.0"),
)
assertThat(useCase.getCached()).isEqualTo(AppUpdateState.ForceUpdate)
}
@Test
fun `GIVEN latest below installed and critical met WHEN getCached THEN ForceUpdate`() = runTest {
// [REDACTED_TASK_KEY]: a latestVersion below the installed one must not suppress the critical force update either.
givenCached(
appVersion = "6.1",
osVersion = "16",
info = info(criticalVersion = "6.1", criticalOSVersion = "15.0", latestVersion = "5.0"),
)
assertThat(useCase.getCached()).isEqualTo(AppUpdateState.ForceUpdate)
}
@Test
fun `GIVEN latest below installed and no thresholds WHEN getCached THEN NoUpdate`() = runTest {
givenCached(appVersion = "6.1", info = info(latestVersion = "5.0"))
assertThat(useCase.getCached()).isEqualTo(AppUpdateState.NoUpdate)
}
@Test
fun `GIVEN critical above latest WHEN getCached THEN not blocking and degraded to optional`() = runTest {
givenCached(appVersion = "5.20", info = info(criticalVersion = "9.99", latestVersion = "5.41"))