field_ios

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

TeraSubmissionReservationTests.swift (7225B)


      1 import Foundation
      2 @testable import TeraApp
      3 import TeraKitBindings
      4 import XCTest
      5 
      6 final class TeraSubmissionReservationTests: XCTestCase {
      7   private let scope = TeraComposerScope(
      8     authorPublicKey: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", localNetworkID: "nearby"
      9   )
     10 
     11   func testProductionReservationSurvivesSessionChangeAndNewerEditingWithoutSigning() async throws {
     12     let fixture = try MediaOwnershipFixture()
     13     defer { fixture.remove() }
     14     let signer = ComposerForbiddenSigner()
     15     let client = TeraRuntimeClient.production()
     16     let configuration = configuration(fixture, signer: signer)
     17     _ = try await client.start(configuration: configuration)
     18     let firstLifecycle = await client.lifecycle()
     19     let id = try await client.reserveComposerID()
     20     var form = TeraComposerForm(commandType: .createEvent)
     21     form.content = "PRIVATE unfinished café"
     22     form.eventStartDate = "2026-09-"
     23     let saved = try await client.saveComposer(request: TeraComposerSaveRequest(
     24       scope: scope, id: id, expectedRevision: nil, editSequence: 1, form: form
     25     ))
     26     let commandID = try await client.reserveSubmissionID()
     27     let request = TeraSubmissionRequest(commandID: commandID, scope: scope, composerID: id, expectedRevision: 1)
     28     let first = try await client.reserveSubmission(request: request)
     29     XCTAssertEqual(first.captured, saved.draft)
     30     XCTAssertFalse(first.replayed)
     31     form.content = "later editing"
     32     let latest = try await client.saveComposer(request: TeraComposerSaveRequest(
     33       scope: scope, id: id, expectedRevision: 1, editSequence: 2, form: form
     34     ))
     35     _ = try await client.stop()
     36     _ = try await client.start(configuration: configuration)
     37     let secondLifecycle = await client.lifecycle()
     38     XCTAssertNotEqual(firstLifecycle, secondLifecycle)
     39     let replay = try await client.reserveSubmission(request: request)
     40     XCTAssertTrue(replay.replayed)
     41     XCTAssertEqual(replay.captured, saved.draft)
     42     XCTAssertEqual(replay.reservationID, first.reservationID)
     43     XCTAssertEqual(replay.reservedAtUnixMilliseconds, first.reservedAtUnixMilliseconds)
     44     let current = try await client.loadComposer(scope: scope, id: id)
     45     XCTAssertEqual(current, latest.draft)
     46     let operations = try await client.draftHeads()
     47     XCTAssertTrue(operations.isEmpty)
     48     _ = try await client.stop()
     49     let calls = await signer.requests
     50     XCTAssertEqual(calls, 0)
     51   }
     52 
     53   func testGeneratedReservationsReuseDuplicatesAndRejectChangedSources() async throws {
     54     let fixture = try MediaOwnershipFixture()
     55     defer { fixture.remove() }
     56     let runtime = try await fixture.runtime()
     57     let id = try composerReserveId().id
     58     let form = TeraComposerForm(commandType: .createUpdate)
     59     let saved = try await runtime.composerSave(request: TeraComposerSaveRequest(
     60       scope: scope, id: id, expectedRevision: nil, editSequence: 1, form: form
     61     ).generatedValue)
     62     let request = try FfiSubmissionReservationRequest(schemaVersion: 1, commandId: submissionReserveId().id,
     63                                                       scope: scope.generatedValue, composerId: id, expectedRevision: 1)
     64     async let left = runtime.submissionReserve(request: request)
     65     async let right = runtime.submissionReserve(request: request)
     66     let (first, replay) = try await (left, right)
     67     XCTAssertEqual(first.reservationId, replay.reservationId)
     68     XCTAssertNotEqual(first.replayed, replay.replayed)
     69     XCTAssertEqual(first.captured, saved.draft)
     70     var changed = request
     71     changed.expectedRevision = 2
     72     try await assertFailure(runtime, request: changed, code: "idempotency_conflict")
     73     changed = request
     74     changed.scope.localNetworkId = "elsewhere"
     75     try await assertFailure(runtime, request: changed, code: "idempotency_conflict")
     76     changed = request
     77     changed.commandId = try submissionReserveId().id
     78     let intentional = try await runtime.submissionReserve(request: changed)
     79     XCTAssertEqual(intentional.captured, first.captured)
     80     XCTAssertNotEqual(intentional.reservationId, first.reservationId)
     81     _ = try await runtime.shutdown()
     82   }
     83 
     84   func testGeneratedVersionAndIdentityFailuresAndUnconfirmedReceiptsFailClosed() async throws {
     85     let fixture = try MediaOwnershipFixture()
     86     defer { fixture.remove() }
     87     let runtime = try await fixture.runtime()
     88     let id = try composerReserveId().id
     89     let saved = try await runtime.composerSave(request: TeraComposerSaveRequest(
     90       scope: scope, id: id, expectedRevision: nil, editSequence: 1, form: TeraComposerForm(commandType: .createUpdate)
     91     ).generatedValue)
     92     let request = try FfiSubmissionReservationRequest(schemaVersion: 1, commandId: submissionReserveId().id,
     93                                                       scope: scope.generatedValue, composerId: id, expectedRevision: 1)
     94     var invalid = request
     95     invalid.schemaVersion = 2
     96     try await assertFailure(runtime, request: invalid, code: "submission_schema_unsupported")
     97     invalid = request
     98     invalid.scope.schemaVersion = 2
     99     try await assertFailure(runtime, request: invalid, code: "composer_schema_unsupported")
    100     invalid = request
    101     invalid.commandId = String(repeating: "0", count: 32)
    102     try await assertFailure(runtime, request: invalid, code: "submission_command_id_invalid")
    103     let receipt = try await runtime.submissionReserve(request: request)
    104     let input = TeraSubmissionRequest(commandID: request.commandId, scope: scope, composerID: id, expectedRevision: 1)
    105     XCTAssertEqual(try TeraGeneratedSubmission.translate(receipt, request: input).captured, try saved.draft.composerAppValue)
    106     var wrong = receipt
    107     wrong.captured.revision = 2
    108     XCTAssertThrowsError(try TeraGeneratedSubmission.translate(wrong, request: input))
    109     wrong = receipt
    110     wrong.schemaVersion = 2
    111     XCTAssertThrowsError(try TeraGeneratedSubmission.translate(wrong, request: input))
    112     wrong = receipt
    113     wrong.reservationId = String(repeating: "0", count: 32)
    114     XCTAssertThrowsError(try TeraGeneratedSubmission.translate(wrong, request: input))
    115     _ = try await runtime.shutdown()
    116   }
    117 
    118   private func assertFailure(_ runtime: TeraRuntime, request: FfiSubmissionReservationRequest, code: String) async throws {
    119     do {
    120       _ = try await runtime.submissionReserve(request: request)
    121       XCTFail("Invalid reservation must not be acknowledged.")
    122     } catch let TeraAppError.Failure(report) {
    123       XCTAssertEqual(report.code, code)
    124       XCTAssertFalse(report.retryable)
    125     }
    126   }
    127 
    128   private func configuration(_ fixture: MediaOwnershipFixture, signer: ComposerForbiddenSigner) -> TeraRuntimeLaunchConfiguration {
    129     TeraRuntimeLaunchConfiguration(
    130       applicationSupportDirectory: fixture.root.path, publicKeyHex: scope.authorPublicKey,
    131       sourceGenerationHex: String(repeating: "04", count: 32), sourceGenerationCreatedAtUnixMilliseconds: 1_800_000_000_000,
    132       protectedData: .available, networkProfile: .publicNetwork, writableRelays: ["wss://relay.example"], blossom: nil,
    133       app: TeraRuntimeAppMetadata(bundleIdentifier: "test.submission", version: "1", buildNumber: "1", buildSHA: nil),
    134       signerGeneration: "submission-test", signer: signer, adoptBootstrapSettings: false
    135     )
    136   }
    137 }