field_ios

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

SubmissionReservationTests.kt (5702B)


      1 package tera.smoke
      2 
      3 import kotlin.test.Test
      4 import kotlin.test.assertEquals
      5 import kotlin.test.assertFailsWith
      6 import kotlin.test.assertFalse
      7 import kotlin.test.assertNotEquals
      8 import kotlin.test.assertTrue
      9 import kotlinx.coroutines.async
     10 import kotlinx.coroutines.runBlocking
     11 import uniffi.tera_core.FfiAddCommandType
     12 import uniffi.tera_core.FfiComposerFormRecord
     13 import uniffi.tera_core.FfiComposerSaveRequest
     14 import uniffi.tera_core.FfiComposerScopeRecord
     15 import uniffi.tera_core.FfiConverterTypeFfiSubmissionReservationReceipt
     16 import uniffi.tera_core.FfiRecoveryDisposition
     17 import uniffi.tera_core.FfiSubmissionReservationRequest
     18 import uniffi.tera_core.TeraAppException
     19 import uniffi.tera_core.classifyErrorRecovery
     20 import uniffi.tera_core.composerReserveId
     21 import uniffi.tera_core.submissionReserveId
     22 
     23 class SubmissionReservationTests {
     24     private val scope = FfiComposerScopeRecord(1u, PUBLIC_KEY, "nearby")
     25     private fun savedRequest() = FfiComposerSaveRequest(
     26         schemaVersion = 1u, scope = scope, id = composerReserveId().id,
     27         expectedRevision = null, editSequence = ULong.MAX_VALUE - 1uL,
     28         form = FfiComposerFormRecord(
     29             schemaVersion = 1u, commandType = FfiAddCommandType.CREATE_EVENT,
     30             content = "PRIVATE incomplete\n\u0000é", identifier = null, title = null, summary = null,
     31             location = null, eventTiming = null, eventStartDate = "2026-09-", eventEndDate = null,
     32             eventStartUnixS = null, eventEndUnixS = null, eventTimezone = null, priceAmount = "-",
     33             currency = null, unit = null, quantity = null, foodPublishedAtUnixS = null,
     34             foodStatus = null, media = emptyList(),
     35         ),
     36     )
     37 
     38     @Test
     39     fun reservationRecoversOneHistoricalSourceAcrossConcurrencyAndRestart(): Unit = runBlocking {
     40         SmokeFixture().use { fixture ->
     41             val input = savedRequest()
     42             val request = FfiSubmissionReservationRequest(1u, submissionReserveId().id, scope, input.id, 1uL)
     43             val runtime = fixture.runtime()
     44             val first = try {
     45                 val saved = runtime.composerSave(input)
     46                 val left = async { runtime.submissionReserve(request) }
     47                 val right = async { runtime.submissionReserve(request) }
     48                 val a = left.await()
     49                 val b = right.await()
     50                 assertEquals(a.reservationId, b.reservationId)
     51                 assertNotEquals(a.replayed, b.replayed)
     52                 assertEquals(saved.draft, a.captured)
     53                 assertEquals(a, FfiConverterTypeFfiSubmissionReservationReceipt.lift(
     54                     FfiConverterTypeFfiSubmissionReservationReceipt.lower(a)))
     55                 runtime.composerSave(input.copy(expectedRevision = 1uL, editSequence = ULong.MAX_VALUE,
     56                     form = input.form.copy(content = "later edit")))
     57                 a
     58             } finally {
     59                 runtime.shutdown()
     60                 runtime.close()
     61             }
     62             val reopened = fixture.runtime()
     63             try {
     64                 val replay = reopened.submissionReserve(request)
     65                 assertTrue(replay.replayed)
     66                 assertEquals(first.reservationId, replay.reservationId)
     67                 assertEquals(first.reservedAtUnixMs, replay.reservedAtUnixMs)
     68                 assertEquals(first.captured, replay.captured)
     69                 assertEquals(2uL, reopened.composerLoad(scope, input.id).revision)
     70                 val changed = request.copy(expectedRevision = 2uL)
     71                 val failure = assertFailsWith<TeraAppException.Failure> { reopened.submissionReserve(changed) }
     72                 assertEquals("idempotency_conflict", failure.report.code)
     73                 assertEquals(FfiRecoveryDisposition.IDEMPOTENCY_CONFLICT,
     74                     classifyErrorRecovery(1u, failure.report.code).disposition)
     75                 val second = reopened.submissionReserve(changed.copy(commandId = submissionReserveId().id))
     76                 val third = reopened.submissionReserve(changed.copy(commandId = submissionReserveId().id))
     77                 assertEquals(second.captured, third.captured)
     78                 assertNotEquals(second.reservationId, third.reservationId)
     79                 assertTrue(reopened.phase1DraftHeads(100u).isEmpty())
     80             } finally {
     81                 reopened.shutdown()
     82                 reopened.close()
     83             }
     84         }
     85     }
     86 
     87     @Test
     88     fun invalidReservationVersionsAndUnsignedWidthsFailClosed(): Unit = runBlocking {
     89         SmokeFixture().use { fixture ->
     90             val runtime = fixture.runtime()
     91             try {
     92                 val input = savedRequest()
     93                 runtime.composerSave(input)
     94                 val request = FfiSubmissionReservationRequest(1u, submissionReserveId().id, scope, input.id, 1uL)
     95                 for ((invalid, code) in listOf(
     96                     request.copy(schemaVersion = 2u) to "submission_schema_unsupported",
     97                     request.copy(scope = scope.copy(schemaVersion = 2u)) to "composer_schema_unsupported",
     98                     request.copy(commandId = "00".repeat(16)) to "submission_command_id_invalid",
     99                     request.copy(expectedRevision = ULong.MAX_VALUE) to "composer_revision_invalid",
    100                 )) {
    101                     val failure = assertFailsWith<TeraAppException.Failure> { runtime.submissionReserve(invalid) }
    102                     assertEquals(code, failure.report.code)
    103                     assertFalse(failure.report.retryable)
    104                 }
    105                 assertFalse(runtime.submissionReserve(request).replayed)
    106             } finally {
    107                 runtime.shutdown()
    108                 runtime.close()
    109             }
    110         }
    111     }
    112 }