TeraNativeRepairPreviewTests.swift (4359B)
1 import Foundation 2 @testable import TeraApp 3 import XCTest 4 5 @MainActor 6 final class TeraNativeRepairPreviewTests: XCTestCase { 7 func testLaterPageCannotHideEarlierRepairAndDurableResolutionClearsIt() async throws { 8 let backend = try TeraScopeBackend() 9 let client = try await TeraScopeFixtures.client(backend) 10 let issue = Self.issue(1) 11 let pages = NativeRepairPages([ 12 .init(visited: 64, remaining: 100, needsAttention: true, issues: [issue]), 13 .init(visited: 1, remaining: 0, needsAttention: false), 14 ]) 15 let store = TeraNativeRepairStore(client: client, media: pages) 16 store.configure(author: "first") 17 await store.reconcile() 18 await store.reconcile() 19 XCTAssertEqual(store.issues, [issue]) 20 XCTAssertNotNil(store.message) 21 await backend.setNativeRepairValue(.init(key: issue.key, reason: .resolved, revision: 2, firstObservedUnixMS: 1, updatedAtUnixMS: 2)) 22 await store.reconcile() 23 XCTAssertTrue(store.issues.isEmpty) 24 XCTAssertNil(store.message) 25 store.configure(author: "second") 26 XCTAssertNil(store.progress) 27 store.stop() 28 _ = try await client.stop() 29 } 30 31 func testRepairPreviewRemainsBoundedAcrossFullPages() async throws { 32 let backend = try TeraScopeBackend() 33 let client = try await TeraScopeFixtures.client(backend) 34 let pages = NativeRepairPages([ 35 .init(visited: 64, remaining: 64, needsAttention: true, issues: (1 ... 64).map(Self.issue)), 36 .init(visited: 64, remaining: 0, needsAttention: true, issues: (65 ... 128).map(Self.issue)), 37 ]) 38 let store = TeraNativeRepairStore(client: client, media: pages) 39 await store.reconcile() 40 await store.reconcile() 41 XCTAssertEqual(store.issues.count, 64) 42 XCTAssertEqual(Set(store.issues.map(\.key)), Set((1 ... 64).map { Self.issue($0).key })) 43 store.stop() 44 _ = try await client.stop() 45 } 46 47 func testSelectedCheckClearsOnlyAcknowledgedKeyAndRetainsOtherNotice() async throws { 48 let backend = try TeraScopeBackend() 49 let client = try await TeraScopeFixtures.client(backend) 50 let first = Self.issue(1), second = Self.issue(2) 51 let pages = NativeRepairPages([.init(visited: 0, remaining: 12, needsAttention: true, issues: [first, second], pause: .protectedData)]) 52 let store = TeraNativeRepairStore(client: client, media: pages) 53 await store.reconcile() 54 store.check(Self.issue(3)) 55 XCTAssertFalse(store.isRunning, "Unretained keys cannot be admitted by a stale UI action") 56 store.check(first) 57 await TeraScopeFixtures.eventually { !store.isRunning } 58 let keys = await pages.selectedKeys 59 XCTAssertEqual(keys, [first.key]) 60 XCTAssertEqual(store.issues, [second]) 61 XCTAssertEqual(store.progress?.remaining, 12, "Selected check preserves the unvisited sweep positions") 62 XCTAssertNotNil(store.message) 63 store.stop() 64 _ = try await client.stop() 65 } 66 67 private static func issue(_ index: Int) -> TeraNativeRecoveryIssue { 68 .init(key: String(format: "%064x", index), reason: .missingParent, status: nil) 69 } 70 } 71 72 extension TeraScopeBackend { 73 func nativeRecoveryStatus(key: String) -> TeraNativeRecoveryStatus? { 74 nativeRepairValues[key] 75 } 76 77 func setNativeRepairValue(_ value: TeraNativeRecoveryStatus) { 78 nativeRepairValues[value.key] = value 79 } 80 } 81 82 private actor NativeRepairPages: TeraAddMediaHandling { 83 var pages: [TeraNativeRecoveryProgress] 84 private(set) var selectedKeys: [String] = [] 85 init(_ pages: [TeraNativeRecoveryProgress]) { 86 self.pages = pages 87 } 88 89 func support() -> TeraAddMediaSupport { 90 .unavailable 91 } 92 93 func recoverNativeUploads(client _: TeraRuntimeClient) -> TeraNativeRecoveryProgress { 94 pages.isEmpty ? .init(visited: 0, remaining: 0, needsAttention: false) : pages.removeFirst() 95 } 96 97 func recoverNativeUpload(key: String, client _: TeraRuntimeClient) -> TeraNativeRecoveryProgress { 98 selectedKeys.append(key) 99 return .init(visited: 1, remaining: 0, needsAttention: false, issues: [.init(key: key, reason: .resolved, status: nil)]) 100 } 101 102 func importImages(limit _: Int) throws -> [TeraPreparedMedia] { 103 throw TeraComposerAcknowledgment.unconfirmed 104 } 105 106 func captureImage() throws -> TeraPreparedMedia { 107 throw TeraComposerAcknowledgment.unconfirmed 108 } 109 110 func open(_: [TeraPreparedMedia]) throws -> TeraOpenedMedia { 111 throw TeraComposerAcknowledgment.unconfirmed 112 } 113 }