field_ios

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

TeraRevisionDetailTests.swift (6855B)


      1 import Foundation
      2 @testable import TeraApp
      3 import XCTest
      4 
      5 @MainActor
      6 final class TeraRevisionDetailTests: XCTestCase {
      7   func testActionsReloadCurrentPermissionAndPreservePartialStoppedEvidence() async throws {
      8     let harness = RevisionDetailHarness(value: Self.fixture())
      9     let store = TeraRevisionDetailStore(operations: harness.operations)
     10     store.configure(author: Self.author)
     11     await store.load(Self.id)
     12     XCTAssertTrue(try XCTUnwrap(store.status).canResume)
     13     var stopped = Self.fixture()
     14     stopped.canResume = false
     15     stopped.canCancel = false
     16     stopped.replacementProgress = TeraRevisionBranchStatus(stopped: true, canResume: false, canCancel: false,
     17                                                            targets: stopped.replacementProgress.targets)
     18     await harness.set(stopped)
     19     await store.resume()
     20     await store.cancel()
     21     let counts = await harness.counts()
     22     XCTAssertEqual(counts, [0, 0])
     23     XCTAssertEqual(store.status, stopped)
     24     XCTAssertEqual(store.status?.honestSummary, "Revision has partial or uncertain relay outcomes")
     25     XCTAssertEqual(store.status?.replacementProgress.targets?.targets.first?.accepted, true)
     26   }
     27 
     28   func testExplicitActionsRetainRelationAndUseOneCurrentOperation() async {
     29     let harness = RevisionDetailHarness(value: Self.fixture())
     30     let store = TeraRevisionDetailStore(operations: harness.operations)
     31     store.configure(author: Self.author)
     32     await store.load(Self.id)
     33     await store.resume()
     34     await store.cancel()
     35     let counts = await harness.counts()
     36     XCTAssertEqual(counts, [1, 1])
     37     XCTAssertEqual(store.status?.original?.sourceEventID, String(repeating: "b", count: 64))
     38     XCTAssertEqual(store.status?.retraction?.revisionParentID, Self.id)
     39     XCTAssertEqual(store.status?.replacement.id, Self.id)
     40     XCTAssertEqual(store.status?.retraction?.form, nil)
     41   }
     42 
     43   func testLateSelectionCannotRestoreStoppedOrForeignScopeDetails() async {
     44     let harness = RevisionDetailHarness(value: Self.fixture())
     45     let store = TeraRevisionDetailStore(operations: harness.operations)
     46     store.configure(author: Self.author)
     47     await harness.pauseNext()
     48     let load = Task { await store.load(Self.id) }
     49     while await !(harness.waiting) {
     50       await Task.yield()
     51     }
     52     store.configure(author: String(repeating: "c", count: 64))
     53     await harness.release()
     54     await load.value
     55     XCTAssertNil(store.status)
     56     XCTAssertFalse(store.isWorking)
     57     await store.load(Self.id)
     58     XCTAssertNil(store.status)
     59     XCTAssertNotNil(store.message)
     60     let counts = await harness.counts()
     61     XCTAssertEqual(counts, [0, 0])
     62   }
     63 
     64   func testNewSelectionReplacesSuspendedReadAndCannotBeOverwritten() async {
     65     let harness = RevisionDetailHarness(value: Self.fixture())
     66     let store = TeraRevisionDetailStore(operations: harness.operations)
     67     store.configure(author: Self.author)
     68     await harness.pauseNext()
     69     let old = Task { await store.load(Self.id) }
     70     while await !(harness.waiting) {
     71       await Task.yield()
     72     }
     73     await store.load(Self.id)
     74     XCTAssertNotNil(store.status)
     75     XCTAssertFalse(store.isWorking)
     76     store.stop()
     77     await harness.release()
     78     await old.value
     79     XCTAssertNil(store.status)
     80     XCTAssertFalse(store.isWorking)
     81     await store.load(Self.id)
     82     XCTAssertNotNil(store.status)
     83     XCTAssertFalse(store.isWorking)
     84   }
     85 
     86   private static let author = String(repeating: "a", count: 64)
     87   private static let id = String(repeating: "1", count: 32)
     88 
     89   private static func fixture() -> TeraRevisionStatus {
     90     let replacement = TeraDraftStatus(id: id, revision: 1, authorPublicKey: author, kind: .add,
     91                                       commandType: .createUpdate, form: nil, state: .partiallyDelivered,
     92                                       cardID: String(repeating: "2", count: 64), operationID: id,
     93                                       createdAtUnixMilliseconds: 1000, updatedAtUnixMilliseconds: 1001,
     94                                       media: [], settlement: nil, isRevision: true)
     95     let child = TeraDraftStatus(id: String(repeating: "3", count: 32), revision: 2, authorPublicKey: author,
     96                                 kind: .retraction, commandType: .createUpdate, form: nil, state: .partiallyDelivered,
     97                                 cardID: String(repeating: "4", count: 64), operationID: String(repeating: "5", count: 32),
     98                                 createdAtUnixMilliseconds: 1000, updatedAtUnixMilliseconds: 1002,
     99                                 media: [], settlement: nil, isRevision: false, revisionParentID: id)
    100     let targets = TeraPublicationTargets(requiresDelivery: false, policy: .all, targets: [
    101       TeraPublicationTarget(id: String(repeating: "6", count: 64), endpoint: "wss://relay.example",
    102                             attempted: true, accepted: true, delivered: false, rejected: false,
    103                             uncertain: false, readBackObservedAtUnixMilliseconds: nil),
    104     ], readBackAvailable: true, readBackComplete: true)
    105     let progress = TeraRevisionBranchStatus(stopped: false, canResume: true, canCancel: true, targets: targets)
    106     return TeraRevisionStatus(operationID: id, replacement: replacement, retraction: child,
    107                               policy: .replaceThenRetract, phase: .partialEffect,
    108                               original: TeraRevisionTarget(cardID: child.cardID, sourceEventID: String(repeating: "b", count: 64),
    109                                                            sourceAddress: nil, authorPublicKey: author),
    110                               replacementProgress: progress, retractionProgress: progress, canResume: true, canCancel: true)
    111   }
    112 }
    113 
    114 private actor RevisionDetailHarness {
    115   private var value: TeraRevisionStatus
    116   private var resumed = 0
    117   private var cancelled = 0
    118   private var pause = false
    119   private var continuation: CheckedContinuation<Void, Never>?
    120   var waiting: Bool {
    121     continuation != nil
    122   }
    123 
    124   init(value: TeraRevisionStatus) {
    125     self.value = value
    126   }
    127 
    128   func set(_ value: TeraRevisionStatus) {
    129     self.value = value
    130   }
    131 
    132   func pauseNext() {
    133     pause = true
    134   }
    135 
    136   func release() {
    137     continuation?.resume(); continuation = nil
    138   }
    139 
    140   func counts() -> [Int] {
    141     [resumed, cancelled]
    142   }
    143 
    144   nonisolated var operations: TeraRevisionDetailOperations {
    145     TeraRevisionDetailOperations(load: { _ in await self.load() },
    146                                  resume: { _ in await self.resume() }, cancel: { _ in await self.cancel() })
    147   }
    148 
    149   private func load() async -> TeraRevisionStatus {
    150     let captured = value
    151     if pause {
    152       pause = false
    153       await withCheckedContinuation { continuation = $0 }
    154     }
    155     return captured
    156   }
    157 
    158   private func resume() -> TeraRevisionStatus {
    159     resumed += 1; return value
    160   }
    161 
    162   private func cancel() -> TeraRevisionStatus {
    163     cancelled += 1; return value
    164   }
    165 }