TeraErrorRecoveryTests.swift (4260B)
1 import Foundation 2 @testable import TeraApp 3 import TeraKitBindings 4 import XCTest 5 6 final class TeraErrorRecoveryTests: XCTestCase { 7 @MainActor 8 func testStableCodesDriveDistinctMessagesAndHonestStoreStates() { 9 let cases: [(String, TeraUserMessageKey)] = [ 10 ("database_busy", .secureStateUnavailable), 11 ("store_incomplete", .secureStateUnavailable), 12 ("storage_integrity_failed", .secureStateUnavailable), 13 ("identity_unavailable", .identityOperationFailed), 14 ("protected_data_unavailable", .protectedDataUnavailable), 15 ("storage_space_insufficient", .storageFull), 16 ("schema_too_new", .versionUnsupported), 17 ("cancelled_no_commit", .operationCancelled), 18 ("ios.runtime.cancelled", .operationOutcomeUnknown), 19 ("ios.runtime.deadline_exceeded", .operationOutcomeUnknown), 20 ("idempotency_conflict", .operationConflict), 21 ("draft_revision_conflict", .stateChanged), 22 ("today_cursor_invalid", .todayChanged), 23 ("relay_auth_required", .networkPolicy), 24 ("relay_rate_limited", .networkPolicy), 25 ("today_relay_partial", .partialResult), 26 ("future_corrupt_network", .todayUnavailable), 27 ] 28 for (code, key) in cases { 29 let failure = makeFailure(code: code) 30 let wrapped = TeraRuntimeClientError.today(failure) 31 XCTAssertEqual(TeraUserMessages.key(for: wrapped, fallback: .todayUnavailable), key, code) 32 XCTAssertEqual( 33 TeraTodayFailure(wrapped), 34 code == "today_cursor_invalid" 35 ? .staleCursor(message: TeraUserMessages.text(key)) 36 : .failed(message: TeraUserMessages.text(key)), code 37 ) 38 XCTAssertEqual(TeraMediaStore.failureState(wrapped), .failed, code) 39 } 40 let offline = makeFailure(code: "today_relay_offline") 41 XCTAssertEqual( 42 TeraTodayFailure(offline), 43 .offline(message: TeraUserMessages.text(.networkUnavailable)) 44 ) 45 XCTAssertEqual( 46 TeraMediaStore.failureState(makeFailure(code: "blossom_transport_failed")), 47 .networkUnavailable 48 ) 49 XCTAssertEqual(TeraMediaStore.failureState(makeFailure(code: "today_media_corrupt")), .corrupt) 50 } 51 52 func testDiagnosticTextAndClaimedRetryActionsCannotChangeRecovery() { 53 for code in ["database_busy", "today_relay_offline", "future_corrupt_network"] { 54 let first = makeFailure(code: code) 55 let second = TeraRuntimeFailure( 56 schemaVersion: 1, code: code, category: "storage", retryable: false, 57 recoveryActions: [], operationID: "another-diagnostic", capabilityID: nil, 58 safeMessage: "Un texte traduit sans indication de réseau." 59 ) 60 XCTAssertEqual(first.recovery, second.recovery) 61 XCTAssertEqual( 62 TeraUserMessages.key(for: first, fallback: .todayUnavailable), 63 TeraUserMessages.key(for: second, fallback: .todayUnavailable) 64 ) 65 XCTAssertFalse(TeraUserMessages.text(for: first, fallback: .todayUnavailable).contains(first.safeMessage)) 66 } 67 let unknown = makeFailure(code: "future_corrupt_network") 68 XCTAssertEqual(unknown.recovery.disposition, .unknown) 69 XCTAssertEqual(unknown.recovery.retry, .notAllowed) 70 let unsupported = makeFailure(code: "today_relay_offline", schemaVersion: 2) 71 XCTAssertEqual(unsupported.recovery.disposition, .unsupportedVersion) 72 XCTAssertEqual(unsupported.recovery.retry, .notAllowed) 73 } 74 75 func testCallerCancellationAndTimeoutRequireReconciliation() { 76 for code in ["ios.runtime.cancelled", "ios.runtime.deadline_exceeded", "deadline_exceeded"] { 77 XCTAssertEqual(makeFailure(code: code).recovery.retry, .reconcileExistingOperation) 78 } 79 XCTAssertEqual( 80 TeraUserMessages.key(for: CancellationError(), fallback: .addOperationFailed), 81 .operationOutcomeUnknown 82 ) 83 XCTAssertEqual(makeFailure(code: "cancelled_no_commit").recovery.disposition, .cancelledBeforeEffect) 84 } 85 86 private func makeFailure(code: String, schemaVersion: UInt16 = 1) -> TeraRuntimeFailure { 87 TeraRuntimeFailure( 88 schemaVersion: schemaVersion, code: code, category: "network relay corrupt", 89 retryable: true, recoveryActions: ["publish_again"], operationID: "existing-operation", 90 capabilityID: "transport", safeMessage: "Texte localisé: offline retry corruption" 91 ) 92 } 93 }