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 }