Signal-iOS/SignalServiceKit/tests/MessageBackup/MessageBackupOutgoingMessageWithEditsTest.swift

110 lines
5.0 KiB
Swift

//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import LibSignalClient
import XCTest
@testable import SignalServiceKit
final class MessageBackupOutgoingMessageWithEditsTest: MessageBackupIntegrationTestCase {
private struct FailTestError: Error {
init(_ message: String) {
XCTFail(message)
}
}
func testOutgoingMessageWithEdits() async throws {
let hanAci = Aci.constantForTesting("5F8C568D-0119-47BD-81AA-BB87C9B71995")
try await runTest(backupName: "outgoing-message-with-edits") { sdsTx, tx in
let allInteractions = try deps.interactionStore.fetchAllInteractions(tx: tx)
/// The message, and two previous revisions.
XCTAssertEqual(allInteractions.count, 3)
/// The most recent revision will be the oldest message, or the last
/// one fetched.
let mostRecentRevision = allInteractions[2] as! TSOutgoingMessage
/// Ordered newest -> oldest.
let editHistory = try deps.editMessageStore.findEditHistory(for: mostRecentRevision, tx: tx)
XCTAssertEqual(editHistory.count, 2)
/// Original revision is the oldest in edit history...
let originalRevision: TSOutgoingMessage = editHistory[1].message!
/// ...and intermediate revision is the newest.
let middleRevision: TSOutgoingMessage = editHistory[0].message!
/// Confirm all the messages are from Han and in the expected group.
XCTAssertTrue([mostRecentRevision, middleRevision, originalRevision].allSatisfy { outgoingMessage -> Bool in
guard let contactThread = deps.threadStore.fetchThread(
uniqueId: outgoingMessage.uniqueThreadId,
tx: tx
) as? TSContactThread else {
XCTFail("Missing contact thread for outgoing message!")
return false
}
return contactThread.contactUUID == hanAci.serviceIdUppercaseString
})
func singleRecipientState(_ outgoingMessage: TSOutgoingMessage) throws -> TSOutgoingMessageRecipientState {
guard
let recipientAddressStates = outgoingMessage.recipientAddressStates,
recipientAddressStates.count == 1,
let single = recipientAddressStates.first
else { throw FailTestError("Missing single recipient state!") }
return single.value
}
/// Verify the original message contents are correct...
XCTAssertEqual(originalRevision.body, "Original message")
XCTAssertEqual(originalRevision.timestamp, 1000)
XCTAssertNil(try singleRecipientState(originalRevision).errorCode)
XCTAssertEqual(try singleRecipientState(originalRevision).status, .read)
XCTAssertEqual(try singleRecipientState(originalRevision).statusTimestamp, 1001)
XCTAssertEqual(originalRevision.editState, .pastRevision)
/// ...and that we got the intermediate revision...
XCTAssertEqual(middleRevision.body, "First revision")
XCTAssertEqual(middleRevision.timestamp, 2000)
XCTAssertNil(try singleRecipientState(middleRevision).errorCode)
XCTAssertEqual(try singleRecipientState(middleRevision).status, .delivered)
XCTAssertEqual(try singleRecipientState(middleRevision).statusTimestamp, 2001)
XCTAssertEqual(middleRevision.editState, .pastRevision)
/// ...and the final revision...
XCTAssertEqual(mostRecentRevision.body, "Latest revision")
XCTAssertEqual(mostRecentRevision.timestamp, 3000)
XCTAssertNil(try singleRecipientState(mostRecentRevision).errorCode)
XCTAssertEqual(try singleRecipientState(mostRecentRevision).status, .sent)
XCTAssertEqual(try singleRecipientState(mostRecentRevision).statusTimestamp, 3001)
XCTAssertEqual(mostRecentRevision.editState, .latestRevisionRead)
/// ...and its downstream reactions.
let reactions = deps.reactionStore
.allReactions(messageId: mostRecentRevision.uniqueId, tx: tx)
.sorted(by: { $0.sortOrder < $1.sortOrder })
XCTAssertEqual(reactions.map { $0.emoji }, ["👀", "🥂"])
XCTAssertEqual(reactions.map { $0.sentAtTimestamp }, [101, 102])
XCTAssertEqual(reactions.map { $0.reactorAci }, [localIdentifiers.aci, hanAci])
}
}
}
// MARK: -
private extension InteractionStore {
/// Fetches all interactions, from newest -> oldest.
func fetchAllInteractions(tx: any DBReadTransaction) throws -> [TSInteraction] {
var results = [TSInteraction]()
try enumerateAllInteractions(tx: tx) { interaction in
results.append(interaction)
return true
}
return results.sorted { lhs, rhs in
return lhs.sqliteRowId! > rhs.sqliteRowId!
}
}
}