mana-swift-event-sync/Tests/ManaEventSyncTests/DecryptFailureTests.swift
Till JS 5cfbbf2d45 feat(engine): Decrypt-Failures sichtbar statt Silent-Skip
Persistenter Zähler in Meta + onDecryptFailure-Hook + stats()-Felder;
decryptPulledPayload wirft auch bei NoOp-gegen-verschlüsselt (kein
enc:1-Ciphertext mehr als 'Plaintext' in der Projektion). Parität zu
@mana/event-sync 0.6.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 21:52:37 +02:00

67 lines
3.2 KiB
Swift

import Foundation
import XCTest
@testable import ManaEventSync
/// Decrypt-Failure-Sichtbarkeit: nicht-entschlüsselbare Pull-Payloads
/// werfen (statt Ciphertext in die Projektion zu lassen) und der Verlust
/// ist über die Meta-Keys persistent zählbar.
final class DecryptFailureTests: XCTestCase {
private func makeKey(_ seed: UInt8 = 0) -> Data {
Data((0 ..< 32).map { UInt8($0) &+ seed })
}
func testDecryptPulledPayloadPassesGoodCiphertext() async throws {
let provider = try MasterKeyCryptoProvider(masterKeyBytes: makeKey(), providerId: "test")
let payload = JSONValue.object(["v": .int(1)])
let wire = try await provider.encryptPayload(payload)
let out = try await decryptPulledPayload(wire, eventId: "evt-1", crypto: provider)
XCTAssertEqual(out, payload)
}
func testDecryptPulledPayloadPassesPlaintextThrough() async throws {
// Mixed-Log-Toleranz: Klartext-Payloads (Guest-Phase) bleiben gültig.
let plain = JSONValue.object(["text": .string("guest")])
let out = try await decryptPulledPayload(plain, eventId: "evt-2", crypto: NoOpCryptoProvider())
XCTAssertEqual(out, plain)
}
func testWrongKeyThrows() async throws {
let providerA = try MasterKeyCryptoProvider(masterKeyBytes: makeKey(0), providerId: "a")
let providerB = try MasterKeyCryptoProvider(masterKeyBytes: makeKey(7), providerId: "b")
let wire = try await providerA.encryptPayload(.object(["secret": .bool(true)]))
do {
_ = try await decryptPulledPayload(wire, eventId: "evt-3", crypto: providerB)
XCTFail("erwartete Decrypt-Fehler bei falschem Key")
} catch {
// erwartet — CryptoKit-Fehler (Tag-Mismatch)
}
}
func testNoOpAgainstEncryptedLogThrowsInsteadOfLeakingCiphertext() async throws {
// Der nutriphi-Footgun-Fall: Client ohne Key bekommt enc:1-Events.
// Vorher: Ciphertext-String landete als „Plaintext" in der Projektion
// bzw. das Event verschwand still. Jetzt: expliziter Fehler.
let provider = try MasterKeyCryptoProvider(masterKeyBytes: makeKey(), providerId: "test")
let wire = try await provider.encryptPayload(.object(["secret": .bool(true)]))
do {
_ = try await decryptPulledPayload(wire, eventId: "evt-4", crypto: NoOpCryptoProvider())
XCTFail("erwartete decryptFailed")
} catch let error as EventSyncError {
XCTAssertEqual(error, .decryptFailed(eventId: "evt-4"))
}
}
@MainActor
func testFailureCounterRoundTripInMeta() throws {
let storage = try EventStorage(
storeName: "test-\(UUID().uuidString)",
appGroupIdentifier: nil,
inMemory: true
)
XCTAssertNil(try storage.metaGet(EventStorageKey.decryptFailureCount))
try storage.metaSet(EventStorageKey.decryptFailureCount, "3")
try storage.metaSet(EventStorageKey.decryptFailureLastEvent, "evt-x")
XCTAssertEqual(try storage.metaGet(EventStorageKey.decryptFailureCount), "3")
XCTAssertEqual(try storage.metaGet(EventStorageKey.decryptFailureLastEvent), "evt-x")
}
}