field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

ScopeLifecycleTests.kt (8959B)


      1 package tera.smoke
      2 
      3 import java.nio.file.Files
      4 import kotlin.test.Test
      5 import kotlin.test.assertEquals
      6 import kotlin.test.assertFailsWith
      7 import kotlin.test.assertFalse
      8 import kotlin.test.assertNotEquals
      9 import kotlin.test.assertNull
     10 import kotlin.test.assertTrue
     11 import kotlinx.coroutines.CoroutineStart
     12 import kotlinx.coroutines.async
     13 import kotlinx.coroutines.cancelAndJoin
     14 import kotlinx.coroutines.delay
     15 import kotlinx.coroutines.runBlocking
     16 import kotlinx.coroutines.withTimeout
     17 import uniffi.tera_core.FfiInvalidationRevision
     18 import uniffi.tera_core.FfiLocalNetworkRecord
     19 import uniffi.tera_core.FfiMediaOperation
     20 import uniffi.tera_core.FfiRecoveryDisposition
     21 import uniffi.tera_core.FfiRetryDisposition
     22 import uniffi.tera_core.FfiRuntimeChangeKind
     23 import uniffi.tera_core.FfiTodayProjectionUpdate
     24 import uniffi.tera_core.ProtectedDataAvailability
     25 import uniffi.tera_core.TeraAppException
     26 import uniffi.tera_core.classifyErrorRecovery
     27 
     28 class ScopeLifecycleTests {
     29     @Test
     30     fun scopeAndUnsignedContextsCrossRustWithoutSignedNarrowing(): Unit = runBlocking {
     31         SmokeFixture().use { fixture ->
     32             val runtime = fixture.runtime()
     33             var firstEpoch = ""
     34             try {
     35                 val observer = SmokeObserver()
     36                 runtime.subscribeChanges(observer).use { subscription ->
     37                     val initial = observer.next()
     38                     assertEquals(FfiRuntimeChangeKind.INITIAL, initial.kind)
     39                     assertEquals(PUBLIC_KEY, initial.scope.publicKey)
     40                     assertEquals(GENERATION, initial.scope.sourceGeneration)
     41                     assertNull(initial.scope.context)
     42                     assertEquals(32, initial.epoch.length)
     43                     firstEpoch = initial.epoch
     44                     for ((index, generation) in listOf(0uL, 1uL, Long.MAX_VALUE.toULong() + 1uL, ULong.MAX_VALUE).withIndex()) {
     45                         val context = FfiLocalNetworkRecord(1u, "nearby", "Nearby", listOf("wss://relay.example"), null, emptyList(), generation)
     46                         runtime.phase1RefreshToday(context, UNIX_S, FfiTodayProjectionUpdate.INCREMENTAL)
     47                         val changed = observer.next()
     48                         assertEquals(context, changed.scope.context)
     49                         assertEquals(initial.epoch, changed.epoch)
     50                         assertEquals(FfiInvalidationRevision.Current((index + 1).toULong()), changed.revision)
     51                     }
     52                     val valid = FfiLocalNetworkRecord(1u, "nearby", "Nearby", listOf("wss://relay.example"), null, emptyList(), 1uL)
     53                     for ((invalid, code) in listOf(
     54                         valid.copy(id = "") to "invalid_local_network",
     55                         valid.copy(schemaVersion = UShort.MAX_VALUE) to "unsupported_schema_version",
     56                     )) {
     57                         val failure = assertFailsWith<TeraAppException.Failure> {
     58                             runtime.phase1RefreshToday(invalid, UNIX_S, FfiTodayProjectionUpdate.INCREMENTAL)
     59                         }
     60                         assertEquals(code, failure.report.code)
     61                     }
     62                     runtime.configurePublicRelays(listOf("wss://write.example"))
     63                     assertEquals(FfiRuntimeChangeKind.RELAY, observer.next().kind)
     64                     assertTrue(subscription.isActive())
     65                     subscription.unsubscribe()
     66                     subscription.unsubscribe()
     67                     assertFalse(subscription.isActive())
     68                 }
     69             } finally {
     70                 runtime.shutdown()
     71                 runtime.close()
     72             }
     73             val reopened = fixture.runtime()
     74             try {
     75                 assertEquals(PUBLIC_KEY, reopened.identityStatus().publicKey)
     76                 assertTrue(reopened.identityStatus().hostSignerConfigured)
     77                 val observer = SmokeObserver()
     78                 reopened.subscribeChanges(observer).use {
     79                     assertNotEquals(firstEpoch, observer.next().epoch)
     80                     assertTrue(it.isActive())
     81                 }
     82             } finally {
     83                 reopened.shutdown()
     84                 reopened.close()
     85             }
     86         }
     87     }
     88 
     89     @Test
     90     fun cancelledCloseWaitRetainsNativeCallbackDrainAndClosedAdmission(): Unit = runBlocking {
     91         SmokeFixture().use { fixture ->
     92             val runtime = fixture.runtime()
     93             val observer = SmokeObserver(paused = true)
     94             val subscription = runtime.subscribeChanges(observer)
     95             try {
     96                 observer.next()
     97                 val closing = async(start = CoroutineStart.UNDISPATCHED) { runtime.shutdown() }
     98                 withTimeout(5_000) { while (!runtime.info().sdkClosed) delay(1) }
     99                 assertFalse(closing.isCompleted)
    100                 closing.cancelAndJoin()
    101                 assertFalse(subscription.isActive())
    102                 val failure = assertFailsWith<TeraAppException.Failure> { runtime.sdkStorageStatus() }
    103                 assertEquals("client_closed", failure.report.code)
    104                 val repeated = async(start = CoroutineStart.UNDISPATCHED) { runtime.shutdown() }
    105                 assertFalse(repeated.isCompleted)
    106                 observer.released.countDown()
    107                 val receipt = withTimeout(5_000) { repeated.await() }
    108                 assertEquals("closed", receipt.state)
    109                 assertTrue(receipt.alreadyClosed)
    110                 assertTrue(runtime.shutdown().alreadyClosed)
    111             } finally {
    112                 observer.released.countDown()
    113                 subscription.close()
    114                 runtime.shutdown()
    115                 runtime.close()
    116             }
    117         }
    118     }
    119 
    120     @Test
    121     fun independentSubscriptionDisposalStopsOnlyItsObserver(): Unit = runBlocking {
    122         SmokeFixture().use { fixture ->
    123             val runtime = fixture.runtime()
    124             val first = SmokeObserver()
    125             val second = SmokeObserver()
    126             val a = runtime.subscribeChanges(first)
    127             val b = runtime.subscribeChanges(second)
    128             try {
    129                 assertEquals(first.next(), second.next())
    130                 // Closing the live generated handle must dispose its Rust subscription.
    131                 a.close()
    132                 a.close()
    133                 runtime.configurePublicRelays(listOf("wss://write.example"))
    134                 assertEquals(FfiRuntimeChangeKind.RELAY, second.next().kind)
    135                 assertEquals(0, first.pending())
    136                 assertTrue(b.isActive())
    137                 runtime.shutdown()
    138                 assertEquals(FfiRuntimeChangeKind.LIFECYCLE, second.next().kind)
    139                 assertFalse(b.isActive())
    140             } finally {
    141                 a.close()
    142                 b.close()
    143                 runtime.shutdown()
    144                 runtime.close()
    145             }
    146         }
    147     }
    148 
    149     @Test
    150     fun errorRecoveryAndProtectedDataFailureRemainTyped(): Unit = runBlocking {
    151         for ((code, disposition, retry) in listOf(
    152             Triple("draft_revision_conflict", FfiRecoveryDisposition.STALE_REVISION, FfiRetryDisposition.AFTER_RECOVERY),
    153             Triple("ios.runtime.cancelled", FfiRecoveryDisposition.OUTCOME_UNKNOWN, FfiRetryDisposition.RECONCILE_EXISTING_OPERATION),
    154             Triple("deadline_exceeded", FfiRecoveryDisposition.OUTCOME_UNKNOWN, FfiRetryDisposition.RECONCILE_EXISTING_OPERATION),
    155             Triple("unknown_future_retryable_network", FfiRecoveryDisposition.UNKNOWN, FfiRetryDisposition.NOT_ALLOWED),
    156         )) {
    157             val decision = classifyErrorRecovery(1u, code)
    158             assertEquals(disposition, decision.disposition)
    159             assertEquals(retry, decision.retry)
    160         }
    161         assertEquals(FfiRecoveryDisposition.UNSUPPORTED_VERSION, classifyErrorRecovery(UShort.MAX_VALUE, "client_closed").disposition)
    162         SmokeFixture().use { fixture ->
    163             val failure = assertFailsWith<TeraAppException.Failure> { fixture.runtime(ProtectedDataAvailability.UNAVAILABLE) }
    164             assertEquals("protected_data_unavailable", failure.report.code)
    165             assertEquals(1u.toUShort(), failure.report.schemaVersion)
    166             assertFalse(Files.exists(fixture.owner.resolve("runtime.sqlite")))
    167             assertFalse(failure.report.safeMessage.contains(fixture.root.toString()))
    168         }
    169     }
    170 
    171     @Test
    172     fun cancellationHandleKeepsIdentityAndDisposesIdempotently() {
    173         val operation = FfiMediaOperation()
    174         val identity = operation.operationId()
    175         assertEquals(32, identity.length)
    176         assertFalse(operation.isCancelled())
    177         operation.cancel()
    178         operation.cancel()
    179         assertTrue(operation.isCancelled())
    180         assertEquals(identity, operation.operationId())
    181         FfiMediaOperation().use { assertNotEquals(identity, it.operationId()) }
    182         operation.close()
    183         operation.close()
    184         assertFailsWith<IllegalStateException> { operation.isCancelled() }
    185     }
    186 }