Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  MessageSenderTests.swift   Sprache: JAVA

 

import Foundation
import RemoteSecretProtocolTestHelper
import ThreemaEssentials

import XCTest

@testable import ThreemaFramework

final class MessageSenderTests: XCTestCase {
    private var taskManagerMock: TaskManagerMock!
    private var ddLoggerMock: DDLoggerMock!

    private var testDatabase: TestDatabase!
    private var blobMessageSender: BlobMessageSender!

    override func setUpWithError() throws {
        AppGroup.setGroupID("group.ch.threema")
        RemoteSecretProvider.setRemoteSecretManager(RemoteSecretManagerMock())

        taskManagerMock = TaskManagerMock()

        testDatabase = TestDatabase()

        blobMessageSender = BlobMessageSender(
            businessInjector: BusinessInjectorMock(entityManager: testDatabase.entityManager),
            taskManager: taskManagerMock
        )

        ddLoggerMock = DDLoggerMock()
        DDTTYLogger.sharedInstance?.logFormatter = LogFormatterCustom()
        DDLog.add(ddLoggerMock)
    }

    override func tearDown() {
        DDLog.remove(ddLoggerMock)
        super.tearDown()
    }

    func testSendBlobMessage() async throws {
        // Setup
        
        let testItemData = Data("Test item data.".utf8)
        let testFileName = "Testfile"
        let urlSenderItem = try XCTUnwrap(URLSenderItem(
            data: testItemData,
            fileName: testFileName,
            type: "application/octet-stream",
            renderType: 0,
            sendAsFile: true
        ))
        
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")
        let conversation = testDatabase.backgroundPreparer.save {
            let contactEntity = testDatabase.backgroundPreparer
                .createContact(identity: expectedThreemaIdentity.rawValue)
            return testDatabase.backgroundPreparer.createConversation(contactEntity: contactEntity)
        }
        
        let blobManagerMock = BlobManagerMock()
        blobManagerMock.syncHandler = { objectID in
            guard let blobData = self.testDatabase.backgroundEntityManager.entityFetcher
                .existingObject(with: objectID) as? BlobData
            else {
                XCTFail("A message should exist")
                return .failed
            }
            
            self.testDatabase.backgroundEntityManager.performAndWaitSave {
                blobData.blobIdentifier = BytesUtility.generateBlobID()
            }
            
            return .uploaded
        }
        
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: UserSettingsMock(),
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.backgroundEntityManager,
            blobManager: blobManagerMock,
            blobMessageSender: blobMessageSender
        )
        
        // Run
        
        try await messageSender.sendBlobMessage(
            for: urlSenderItem,
            in: conversation.objectID,
            correlationID: nil,
            webRequestID: nil
        )
        
        // Validate
        
        XCTAssertEqual(taskManagerMock.addedTasks.count, 1)
        let addedSendBaseMessageTask = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendBaseMessage)
        
        XCTAssertEqual(addedSendBaseMessageTask.receiverIdentity, expectedThreemaIdentity.rawValue)
        XCTAssertNil(addedSendBaseMessageTask.groupID)
        XCTAssertNil(addedSendBaseMessageTask.groupCreatorIdentity)
        XCTAssertNil(addedSendBaseMessageTask.groupName)
        XCTAssertNil(addedSendBaseMessageTask.receivingGroupMembers)
        
        // Check that a message with the blob was created
        let blobMessage = try XCTUnwrap(
            testDatabase.backgroundEntityManager.entityFetcher.message(
                with: addedSendBaseMessageTask.messageID,
                in: conversation,
                isOwn: true
            ) as? FileMessageProvider
        )
        XCTAssertEqual(blobMessage.blobData, testItemData)
    }
    
    func testSendBaseMessage() async throws {
        // Setup
        
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")
        let expectedMessageID = BytesUtility.generateMessageID()

        let message = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)

            let conversation = testDatabase.preparer.createConversation(contactEntity: contactEntity)
            let textMessage = testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: true,
                sender: contactEntity,
                remoteSentDate: Date()
            )
            textMessage.sendFailed = true
            
            return textMessage
        }

        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: UserSettingsMock(),
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        // Run
        
        await messageSender.sendBaseMessage(with: message.objectID)
        
        // Validate
        
        XCTAssertEqual(taskManagerMock.addedTasks.count, 1)
        let addedSendBaseMessageTask = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendBaseMessage)
        
        XCTAssertEqual(addedSendBaseMessageTask.receiverIdentity, expectedThreemaIdentity.rawValue)
        XCTAssertNil(addedSendBaseMessageTask.groupID)
        XCTAssertNil(addedSendBaseMessageTask.groupCreatorIdentity)
        XCTAssertNil(addedSendBaseMessageTask.groupName)
        XCTAssertNil(addedSendBaseMessageTask.receivingGroupMembers)

        XCTAssertFalse(message.sendFailed?.boolValue ?? false)
    }
    
    func testSendBaseMessageBlob() async throws {
        // Setup
        
        let testItemData = Data("Test item data.".utf8)
        
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")
        let message = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)
            let conversation = testDatabase.preparer.createConversation(contactEntity: contactEntity)
            
            let fileDataEntity = testDatabase.preparer.createFileDataEntity(data: testItemData)
            
            return testDatabase.preparer.createFileMessageEntity(
                conversation: conversation,
                data: fileDataEntity,
                mimeType: "application/octet-stream",
                type: 0
            )
        }
        
        let blobManagerMock = BlobManagerMock()
        blobManagerMock.syncHandler = { objectID in
            guard let blobData = self.testDatabase.entityManager.entityFetcher
                .existingObject(with: objectID) as? BlobData else {
                XCTFail("A message should exist")
                return .failed
            }
            
            self.testDatabase.entityManager.performAndWaitSave {
                blobData.blobIdentifier = BytesUtility.generateBlobID()
            }
            
            return .uploaded
        }

        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: UserSettingsMock(),
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: blobManagerMock,
            blobMessageSender: blobMessageSender
        )
        
        // Run
        
        await messageSender.sendBaseMessage(with: message.objectID)
        
        // Validate
        
        XCTAssertEqual(taskManagerMock.addedTasks.count, 1)
        let addedSendBaseMessageTask = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendBaseMessage)
        
        XCTAssertEqual(addedSendBaseMessageTask.receiverIdentity, expectedThreemaIdentity.rawValue)
        XCTAssertNil(addedSendBaseMessageTask.groupID)
        XCTAssertNil(addedSendBaseMessageTask.groupCreatorIdentity)
        XCTAssertNil(addedSendBaseMessageTask.groupName)
        XCTAssertNil(addedSendBaseMessageTask.receivingGroupMembers)

        XCTAssertFalse(message.sendFailed?.boolValue ?? false)
    }
    
    func testSendBaseMessageBlobWithError() async throws {
        // Setup
                
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")
        let message = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)
            let conversation = testDatabase.preparer.createConversation(contactEntity: contactEntity)
            return testDatabase.preparer.createFileMessageEntity(conversation: conversation)
        }
        
        let blobManagerMock = BlobManagerMock()
        blobManagerMock.syncHandler = { _ in
            throw BlobManagerError.noData
        }

        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: UserSettingsMock(),
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: blobManagerMock,
            blobMessageSender: blobMessageSender
        )
        
        // Run
        
        await messageSender.sendBaseMessage(with: message.objectID)
        
        // Validate
        
        XCTAssertEqual(taskManagerMock.addedTasks.count, 0)
        XCTAssertTrue(message.sendFailed?.boolValue ?? false)
    }
    
    func testSendBaseMessageToGroup() async throws {
        // Setup
        
        let expectedMessageID = BytesUtility.generateMessageID()
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        
        let expectedGroupID = BytesUtility.generateGroupID()
        let membersWithoutCreator = ["MEMBER01", "MEMBER02", "MEMBER03"]
        let expectedReceivers = Set(membersWithoutCreator)

        // Setup initial group in DB

        let message = testDatabase.preparer.save {
            let members = membersWithoutCreator.map { identityString in
                testDatabase.preparer.createContact(
                    publicKey: BytesUtility.generatePublicKey(),
                    identity: identityString
                )
            }
            
            _ = testDatabase.preparer.createGroupEntity(
                groupID: expectedGroupID,
                groupCreator: nil
            )
            let conversation = testDatabase.preparer.createConversation(
                typing: false,
                unreadMessageCount: 0,
                visibility: .default
            ) { conversation in
                conversation.groupID = expectedGroupID
                conversation.groupMyIdentity = myIdentityStoreMock.identity
                conversation.members?.formUnion(members)
            }
            
            let textMessage = testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: true,
                sender: nil,
                remoteSentDate: nil
            )
            textMessage.sendFailed = true
            
            return textMessage
        }

        let userSettingMock = UserSettingsMock()
        
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: ContactStoreMock(),
            taskManager: taskManagerMock,
            userSettings: userSettingMock,
            entityManager: testDatabase.entityManager,
            groupPhotoSender: {
                GroupPhotoSenderMock()
            }
        )
        
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: myIdentityStoreMock,
            userSettings: userSettingMock,
            groupManager: groupManager,
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        // Run
        
        await messageSender.sendBaseMessage(with: message.objectID)
        
        // Validate
        
        XCTAssertEqual(taskManagerMock.addedTasks.count, 1)
        let addedSendBaseMessageTask = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendBaseMessage)
        
        XCTAssertNil(addedSendBaseMessageTask.receiverIdentity)
        XCTAssertEqual(addedSendBaseMessageTask.groupID, expectedGroupID)
        XCTAssertEqual(addedSendBaseMessageTask.groupCreatorIdentity, myIdentityStoreMock.identity)
        XCTAssertNil(addedSendBaseMessageTask.groupName) // We didn't set a group name
        XCTAssertEqual(addedSendBaseMessageTask.receivingGroupMembers, expectedReceivers)

        XCTAssertFalse(message.sendFailed?.boolValue ?? false)
    }
    
    func testSendBaseMessageToSubsetOfGroupMembers() async throws {
        // Setup
        
        let expectedMessageID = BytesUtility.generateMessageID()
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        
        let expectedGroupID = BytesUtility.generateGroupID()
        let membersWithoutCreator = ["MEMBER01", "MEMBER02", "MEMBER03"]
        let expectedReceivers = Set(membersWithoutCreator[0...1])

        // Setup initial group in DB

        let message = testDatabase.preparer.save {
            let members = membersWithoutCreator.map { identityString in
                testDatabase.preparer.createContact(
                    publicKey: BytesUtility.generatePublicKey(),
                    identity: identityString
                )
            }
            let rejectedByContacts = members.filter { contact in
                expectedReceivers.contains(contact.identity)
            }
            
            _ = testDatabase.preparer.createGroupEntity(
                groupID: expectedGroupID,
                groupCreator: nil
            )
            let conversation = testDatabase.preparer.createConversation(
                typing: false,
                unreadMessageCount: 0,
                visibility: .default
            ) { conversation in
                conversation.groupID = expectedGroupID
                conversation.groupMyIdentity = myIdentityStoreMock.identity
                conversation.members = Set(members)
            }
            
            let textMessage = testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: true,
                sender: nil,
                remoteSentDate: nil
            )
            textMessage.sendFailed = true
            textMessage.rejectedBy = Set(rejectedByContacts)
            
            return textMessage
        }

        let userSettingMock = UserSettingsMock()
        
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: ContactStoreMock(),
            taskManager: taskManagerMock,
            userSettings: userSettingMock,
            entityManager: testDatabase.entityManager,
            groupPhotoSender: {
                GroupPhotoSenderMock()
            }
        )
        
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: myIdentityStoreMock,
            userSettings: userSettingMock,
            groupManager: groupManager,
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        // Run
        
        await messageSender.sendBaseMessage(
            with: message.objectID,
            to: .groupMembers(expectedReceivers.map { ThreemaIdentity($0) })
        )
        
        // Validate
        
        XCTAssertEqual(taskManagerMock.addedTasks.count, 1)
        let addedSendBaseMessageTask = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendBaseMessage)
        
        XCTAssertNil(addedSendBaseMessageTask.receiverIdentity)
        XCTAssertEqual(addedSendBaseMessageTask.groupID, expectedGroupID)
        XCTAssertEqual(addedSendBaseMessageTask.groupCreatorIdentity, myIdentityStoreMock.identity)
        XCTAssertNil(addedSendBaseMessageTask.groupName) // We didn't set a group name
        XCTAssertEqual(addedSendBaseMessageTask.receivingGroupMembers, expectedReceivers)

        XCTAssertFalse(message.sendFailed?.boolValue ?? false)
    }
    
    func testSendBaseMessageToSubsetOfRejectedGroupMembers() async throws {
        // Setup
        
        let expectedMessageID = BytesUtility.generateMessageID()
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        
        let expectedGroupID = BytesUtility.generateGroupID()
        let membersWithoutCreator = ["MEMBER01", "MEMBER02", "MEMBER03", "MEMBER04"]
        let rejectedBy = membersWithoutCreator[1...]
        let expectedReceivers = Set(membersWithoutCreator[2...])

        // Setup initial group in DB

        let message = testDatabase.preparer.save {
            let members = membersWithoutCreator.map { identityString in
                testDatabase.preparer.createContact(
                    publicKey: BytesUtility.generatePublicKey(),
                    identity: identityString
                )
            }
            let rejectedByContacts = members.filter { contact in
                rejectedBy.contains(contact.identity)
            }
            
            _ = testDatabase.preparer.createGroupEntity(
                groupID: expectedGroupID,
                groupCreator: nil
            )
            let conversation = testDatabase.preparer.createConversation(
                typing: false,
                unreadMessageCount: 0,
                visibility: .default
            ) { conversation in
                conversation.groupID = expectedGroupID
                conversation.groupMyIdentity = myIdentityStoreMock.identity
                conversation.members = Set(members)
            }
            
            let textMessage = testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: true,
                sender: nil,
                remoteSentDate: nil
            )
            textMessage.sendFailed = true
            textMessage.rejectedBy = Set(rejectedByContacts)
            
            return textMessage
        }

        let userSettingMock = UserSettingsMock()
        
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: ContactStoreMock(),
            taskManager: taskManagerMock,
            userSettings: userSettingMock,
            entityManager: testDatabase.entityManager,
            groupPhotoSender: {
                GroupPhotoSenderMock()
            }
        )
        
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: myIdentityStoreMock,
            userSettings: userSettingMock,
            groupManager: groupManager,
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        // Run
        
        await messageSender.sendBaseMessage(
            with: message.objectID,
            to: .groupMembers(expectedReceivers.map { ThreemaIdentity($0) })
        )
        
        // Validate
        
        XCTAssertEqual(taskManagerMock.addedTasks.count, 1)
        let addedSendBaseMessageTask = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendBaseMessage)
        
        XCTAssertNil(addedSendBaseMessageTask.receiverIdentity)
        XCTAssertEqual(addedSendBaseMessageTask.groupID, expectedGroupID)
        XCTAssertEqual(addedSendBaseMessageTask.groupCreatorIdentity, myIdentityStoreMock.identity)
        XCTAssertNil(addedSendBaseMessageTask.groupName) // We didn't set a group name
        XCTAssertEqual(addedSendBaseMessageTask.receivingGroupMembers, expectedReceivers)

        XCTAssertTrue(message.sendFailed?.boolValue ?? false)
    }
    
    func testSendTextMessageToDistributionList() async throws {
        // Setup
        
        let recipients = ["MEMBER01", "MEMBER02", "MEMBER03"]

        // Setup initial group in DB

        let conversation = testDatabase.preparer.save {
            let recipients = recipients.map { identityString in
                testDatabase.preparer.createContact(
                    publicKey: BytesUtility.generatePublicKey(),
                    identity: identityString
                )
            }
            
            // We create one conversation for the recipients, the rest should be auto created.
            let conversation = testDatabase.preparer.createConversation(
                contactEntity: recipients.first!,
                typing: false,
                unreadMessageCount: 0,
                visibility: .default
            ) { conversation in
                conversation.members = Set(recipients)
            }

            testDatabase.preparer.createDistributionListEntity(id: 0, conversation: conversation)

            return conversation
        }

        let userSettingMock = UserSettingsMock()
        
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: userSettingMock,
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        // Run
        
        await messageSender.sendTextMessage(containing: "Test", in: conversation)
        
        // Validate
        
        XCTAssertEqual(taskManagerMock.addedTasks.count, 3)
        let addedSendBaseMessageTask = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendBaseMessage)
    }

    func testSendDeliveryReceipt() throws {
        let testMessages: [AbstractMessage] = [
            BoxAudioMessage(),
            BoxBallotCreateMessage(),
            BoxBallotVoteMessage(),
            BoxFileMessage(),
            BoxImageMessage(),
            BoxLocationMessage(),
            BoxTextMessage(),
            BoxVideoMessage(),
            BoxVoIPCallAnswerMessage(),
            BoxVoIPCallHangupMessage(),
            BoxVoIPCallIceCandidatesMessage(),
            BoxVoIPCallOfferMessage(),
            BoxVoIPCallRingingMessage(),
            ContactDeletePhotoMessage(),
            ContactRequestPhotoMessage(),
            ContactSetPhotoMessage(),
            DeliveryReceiptMessage(),
            GroupAudioMessage(),
            GroupBallotCreateMessage(),
            GroupBallotVoteMessage(),
            GroupCreateMessage(),
            GroupDeletePhotoMessage(),
            GroupDeliveryReceiptMessage(),
            GroupFileMessage(),
            GroupImageMessage(),
            GroupLeaveMessage(),
            GroupLocationMessage(),
            GroupRenameMessage(),
            GroupRequestSyncMessage(),
            GroupSetPhotoMessage(),
            GroupTextMessage(),
            GroupVideoMessage(),
            TypingIndicatorMessage(),
            UnknownTypeMessage(),
            GroupCallStartMessage(),
        ]

        for testMessage in testMessages {
            testMessage.fromIdentity = "ECHOECHO"
            taskManagerMock.addedTasks = []
            
            let messageSender = MessageSender(
                serverConnector: ServerConnectorMock(),
                myIdentityStore: MyIdentityStoreMock(),
                userSettings: UserSettingsMock(),
                groupManager: GroupManagerMock(),
                taskManager: taskManagerMock,
                entityManager: testDatabase.entityManager,
                blobManager: BlobManagerMock(),
                blobMessageSender: blobMessageSender
            )

            let expect = expectation(description: "Send delivery receipt")

            _ = messageSender.sendDeliveryReceipt(for: testMessage)
                .done {
                    expect.fulfill()
                }

            wait(for: [expect], timeout: 3)

            XCTAssertEqual(taskManagerMock.addedTasks.count, testMessage.noDeliveryReceiptFlagSet() ? 0 : 1)
        }
    }

    func testSendUserAck() async throws {
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")
        let expectedMessageID = BytesUtility.generateMessageID()
        let expectedReadDate = Date()

        let message = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)

            let conversation = testDatabase.preparer.createConversation(contactEntity: contactEntity)
            return testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                readDate: expectedReadDate,
                sender: contactEntity,
                remoteSentDate: Date()
            )
        }

        let taskManagerMock = TaskManagerMock()

        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: UserSettingsMock(),
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager
        )

        await messageSender.sendUserAck(for: message, toIdentity: expectedThreemaIdentity)

        XCTAssertFalse(taskManagerMock.addedTasks.isEmpty)
        XCTAssertEqual(
            1,
            taskManagerMock.addedTasks.filter { task in
                guard let task = task as? TaskDefinitionSendDeliveryReceiptsMessage else {
                    return false
                }
                return task.receiptType == .ack &&
                    task.toIdentity == expectedThreemaIdentity.rawValue &&
                    task.receiptMessageIDs.contains(expectedMessageID) &&
                    task.receiptReadDates.isEmpty
            }.count
        )
    }

    func testSendReadReceipt() async throws {
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")
        let expectedMessageID = BytesUtility.generateMessageID()
        let expectedReadDate = Date()

        let message = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)

            let conversation = testDatabase.preparer.createConversation(contactEntity: contactEntity)
            return testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                readDate: expectedReadDate,
                sender: contactEntity,
                remoteSentDate: Date()
            )
        }

        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: UserSettingsMock(),
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        await messageSender.sendReadReceipt(for: [message], toIdentity: expectedThreemaIdentity)

        XCTAssertFalse(taskManagerMock.addedTasks.isEmpty)
        XCTAssertEqual(
            1,
            taskManagerMock.addedTasks.filter { task in
                guard let task = task as? TaskDefinitionSendDeliveryReceiptsMessage else {
                    return false
                }
                return task.receiptType == .read &&
                    task.toIdentity == expectedThreemaIdentity.rawValue &&
                    task.receiptMessageIDs.contains(expectedMessageID) &&
                    task.receiptReadDates.contains(expectedReadDate)
            }.count
        )
    }

    func testSendReadReceiptDefaultNoReadReceipt() async throws {
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")

        let message = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)
            let conversation = testDatabase.preparer.createConversation(contactEntity: contactEntity)
            return testDatabase.preparer.createTextMessage(
                conversation: conversation,
                isOwn: false,
                sender: contactEntity,
                remoteSentDate: Date()
            )
        }

        let userSettings = UserSettingsMock()
        userSettings.sendReadReceipts = false

        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: userSettings,
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        await messageSender.sendReadReceipt(for: [message], toIdentity: expectedThreemaIdentity)
        XCTAssertTrue(taskManagerMock.addedTasks.isEmpty)
    }

    func testSendReadReceiptContactNoReadReceipt() async throws {
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")

        let message = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)
            contactEntity.readReceipt = .doNotSend

            let conversation = testDatabase.preparer.createConversation(contactEntity: contactEntity)
            return testDatabase.preparer.createTextMessage(
                conversation: conversation,
                isOwn: false,
                sender: contactEntity,
                remoteSentDate: Date()
            )
        }

        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: UserSettingsMock(),
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        await messageSender.sendReadReceipt(for: [message], toIdentity: expectedThreemaIdentity)

        XCTAssertTrue(taskManagerMock.addedTasks.isEmpty)
    }

    func testSendReadReceiptContactNoReadReceiptButMultiDeviceActivated() async throws {
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")
        let expectedMessageID = BytesUtility.generateMessageID()
        let expectedReadDate = Date()

        let message = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)
            contactEntity.readReceipt = .doNotSend

            let conversation = testDatabase.preparer.createConversation(contactEntity: contactEntity)
            return testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                readDate: expectedReadDate,
                sender: contactEntity,
                remoteSentDate: Date()
            )
        }

        let serverConnectorMock = ServerConnectorMock(
            connectionState: .loggedIn,
            deviceID: MockMultiDevice.deviceID,
            deviceGroupKeys: MockMultiDevice.deviceGroupKeys
        )

        let messageSender = MessageSender(
            serverConnector: serverConnectorMock,
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: UserSettingsMock(enableMultiDevice: true),
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        await messageSender.sendReadReceipt(for: [message], toIdentity: expectedThreemaIdentity)

        XCTAssertFalse(taskManagerMock.addedTasks.isEmpty)
        XCTAssertEqual(
            1,
            taskManagerMock.addedTasks.filter { task in
                guard let task = task as? TaskDefinitionSendDeliveryReceiptsMessage else {
                    return false
                }
                return task.receiptType == .read &&
                    task.toIdentity == expectedThreemaIdentity.rawValue &&
                    task.receiptMessageIDs.contains(expectedMessageID) &&
                    task.receiptReadDates.contains(expectedReadDate)
            }.count
        )
    }

    func testSendReadReceiptGroup() async throws {
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("ECHOECHO")
        )
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")

        let (groupEntity, message) = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)

            let groupEntity = testDatabase.preparer.createGroupEntity(
                groupID: expectedGroupIdentity.id,
                groupCreator: nil
            )
            let conversation = testDatabase.preparer.createConversation(groupID: groupEntity.groupID)
            return (groupEntity, testDatabase.preparer.createTextMessage(
                conversation: conversation,
                isOwn: false,
                sender: contactEntity,
                remoteSentDate: Date()
            ))
        }

        let groupManagerMock = GroupManagerMock()
        groupManagerMock.getGroupReturns.append(Group(
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: UserSettingsMock(),
            pushSettingManager: PushSettingManagerMock(),
            groupEntity: groupEntity,
            conversation: message.conversation,
            lastSyncRequest: nil
        ))

        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: UserSettingsMock(),
            groupManager: groupManagerMock,
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        await messageSender.sendReadReceipt(for: [message], toGroupIdentity: expectedGroupIdentity)

        XCTAssertTrue(taskManagerMock.addedTasks.isEmpty)
    }

    func testSendReadReceiptGroupButMultiDeviceActivated() async throws {
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(MyIdentityStoreMock().identity)
        )
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")
        let expectedMessageID = BytesUtility.generateMessageID()
        let expectedReadDate = Date()

        let (groupEntity, message) = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)

            let groupEntity = testDatabase.preparer.createGroupEntity(
                groupID: expectedGroupIdentity.id,
                groupCreator: nil
            )
            let conversation = testDatabase.preparer.createConversation(groupID: groupEntity.groupID)
            return (groupEntity, testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                readDate: expectedReadDate,
                sender: contactEntity,
                remoteSentDate: Date()
            ))
        }

        let userSettingsMock = UserSettingsMock(enableMultiDevice: true)
        let serverConnectorMock = ServerConnectorMock(
            connectionState: .loggedIn,
            deviceID: MockMultiDevice.deviceID,
            deviceGroupKeys: MockMultiDevice.deviceGroupKeys
        )
        let groupManagerMock = GroupManagerMock()
        groupManagerMock.getGroupReturns.append(Group(
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: userSettingsMock,
            pushSettingManager: PushSettingManagerMock(),
            groupEntity: groupEntity,
            conversation: message.conversation,
            lastSyncRequest: nil
        ))

        let messageSender = MessageSender(
            serverConnector: serverConnectorMock,
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: userSettingsMock,
            groupManager: groupManagerMock,
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        await messageSender.sendReadReceipt(for: [message], toGroupIdentity: expectedGroupIdentity)

        XCTAssertFalse(taskManagerMock.addedTasks.isEmpty)
        XCTAssertEqual(
            1,
            taskManagerMock.addedTasks.filter { task in
                guard let task = task as? TaskDefinitionSendGroupDeliveryReceiptsMessage else {
                    return false
                }
                print(task.groupID == expectedGroupIdentity.id)
                print(task.groupCreatorIdentity == expectedGroupIdentity.creator.rawValue)
                print(task.receiptMessageIDs.contains(expectedMessageID))

                return task.receiptType == .read &&
                    task.groupID == expectedGroupIdentity.id &&
                    task.receiptMessageIDs.contains(expectedMessageID) &&
                    task.receiptReadDates.contains(expectedReadDate)
            }.count
        )
    }

    func testNotAllowedDonateInteractionForOutgoingMessage() throws {
        let userSettingsMock = UserSettingsMock()
        userSettingsMock.allowOutgoingDonations = false

        var objectID: NSManagedObjectID!
        testDatabase.preparer
            .createConversation(typing: false, unreadMessageCount: 100, visibility: .default) { conversation in
                conversation.groupID = BytesUtility.generateRandomBytes(length: ThreemaProtocol.groupIDLength)!
                objectID = conversation.objectID
            }
        let expectation = XCTestExpectation()

        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: userSettingsMock,
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        messageSender.donateInteractionForOutgoingMessage(in: objectID).done { success in
            if success {
                XCTFail("Donations are not allowed")
            }
            
            expectation.fulfill()
        }
        
        wait(for: [expectation], timeout: 10.0)
        
        DDLog.sharedInstance.flushLog()
        
        XCTAssertTrue(ddLoggerMock.exists(message: "Donations are disabled by the user"))
    }
    
    func testDoNotDonateInteractionForOutgoingMessageIfConversationIsPrivate() throws {
        let userSettingsMock = UserSettingsMock()
        userSettingsMock.allowOutgoingDonations = true

        let objectID: NSManagedObjectID! = testDatabase.backgroundPreparer.performAndWait {
            let conversationEntity: ConversationEntity = self.testDatabase.backgroundPreparer.save {
                self.testDatabase.backgroundPreparer
                    .createConversation(typing: false, unreadMessageCount: 100, visibility: .default) { conversation in
                        conversation.groupID = BytesUtility.generateGroupID()
                        conversation.changeCategory(to: .private)
                    }
            }
            return conversationEntity.objectID
        }

        let expectation = XCTestExpectation()
        
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: userSettingsMock,
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        messageSender.donateInteractionForOutgoingMessage(
            in: objectID,
            backgroundEntityManager: testDatabase.backgroundEntityManager
        ).done { success in
            if success {
                XCTFail("Donations are not allowed")
            }
            
            expectation.fulfill()
        }
        
        wait(for: [expectation], timeout: 10.0)
        
        DDLog.sharedInstance.flushLog()
        
        XCTAssertTrue(ddLoggerMock.exists(message: "Do not donate for private conversations"))
    }
    
    func testAllowedDonateInteractionForOutgoingMessage() async throws {
        let userSettingsMock = UserSettingsMock()
        userSettingsMock.allowOutgoingDonations = true

        let objectID: NSManagedObjectID! = await testDatabase.backgroundPreparer.perform {
            let conversationEntity: ConversationEntity = self.testDatabase.backgroundPreparer.save {
                self.testDatabase.backgroundPreparer
                    .createConversation(typing: false, unreadMessageCount: 100, visibility: .default) { conversation in
                        conversation.groupID = BytesUtility.generateGroupID()
                    }
            }
            return conversationEntity.objectID
        }

        let expectation = XCTestExpectation()
        
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: userSettingsMock,
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.backgroundEntityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        messageSender.donateInteractionForOutgoingMessage(
            in: objectID,
            backgroundEntityManager: testDatabase.backgroundEntityManager
        ).done { _ in
            // We don't care about success here and only check the absence of a certain log message below
            // because we don't have enabled all entitlements in all targets

            expectation.fulfill()
        }
        
        wait(for: [expectation], timeout: 10.0)
        
        DDLog.sharedInstance.flushLog()
        XCTAssertFalse(ddLoggerMock.exists(message: "Donations are disabled by the user"))
    }

    func testDoSendReadReceiptToContactEntityIsNil() throws {
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: UserSettingsMock(),
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager,
            blobManager: BlobManagerMock(),
            blobMessageSender: blobMessageSender
        )

        XCTAssertFalse(messageSender.doSendReadReceipt(to: nil))
    }
    
    // MARK: - Reaction tests
    
    /// Situation:
    /// Local support: Phase 2
    /// Remote support: Legacy
    /// Action: Apply
    /// Mapping: None
    func testApplyReactionNoMappingYesLocalNoRemote() async throws {
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")
        let expectedMessageID = BytesUtility.generateMessageID()
        let expectedReadDate = Date()
        
        let reaction = Emoji.abacus
        
        let message = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)
            contactEntity.setFeatureMask(to: 1)

            let conversation = testDatabase.preparer.createConversation(contactEntity: contactEntity)
            return testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                readDate: expectedReadDate,
                sender: contactEntity,
                remoteSentDate: Date()
            )
        }
        
        let userSettingsMock = UserSettingsMock()
        userSettingsMock.sendEmojiReactions = true
        
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: userSettingsMock,
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager
        )
        let result = try await messageSender.sendReaction(
            to: message.objectID,
            reaction: EmojiVariant(base: reaction, skintone: nil)
        )
        
        XCTAssertEqual(result, .noSupportRemoteSingle)
        XCTAssertTrue(taskManagerMock.addedTasks.isEmpty)
    }
    
    /// Situation:
    /// Local support: Phase 2
    /// Remote support: Legacy
    /// Action: Apply
    /// Mapping: Success
    func testApplyReactionMappingYesLocalNoRemote() async throws {
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")
        let expectedMessageID = BytesUtility.generateMessageID()
        let expectedReadDate = Date()
        let reaction = Emoji.thumbsUp
        
        let message = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)
            contactEntity.setFeatureMask(to: 1)

            let conversation = testDatabase.preparer.createConversation(contactEntity: contactEntity)
            return testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                readDate: expectedReadDate,
                sender: contactEntity,
                remoteSentDate: Date()
            )
        }
        
        let userSettingsMock = UserSettingsMock()
        userSettingsMock.sendEmojiReactions = true
        
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: userSettingsMock,
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager
        )
        let result = try await messageSender.sendReaction(
            to: message.objectID,
            reaction: EmojiVariant(base: reaction, skintone: nil)
        )
        
        let addedTasks = taskManagerMock.addedTasks.filter { task in
            guard task is TaskDefinitionSendDeliveryReceiptsMessage else {
                return false
            }
            return true
        }
        
        let testTask = try XCTUnwrap(addedTasks.first as? TaskDefinitionSendDeliveryReceiptsMessage)
       
        XCTAssertFalse(taskManagerMock.addedTasks.isEmpty)
        XCTAssertEqual(1, addedTasks.count)
        XCTAssertEqual(ReceiptType.ack, testTask.receiptType)
        XCTAssertEqual(expectedThreemaIdentity.rawValue, testTask.toIdentity)
        
        XCTAssertEqual(result, .success)
    }
    
    /// Situation:
    /// Local support: Phase 2
    /// Remote support: Legacy
    /// Action: Withdraw
    /// Mapping: Success
    func testWithdrawReactionMappingYesLocalNoRemote() async throws {
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")
        let expectedMessageID = BytesUtility.generateMessageID()
        let expectedReadDate = Date()
        let reaction = Emoji.thumbsUp
        
        let message = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)
            contactEntity.setFeatureMask(to: 1)

            let conversation = testDatabase.preparer.createConversation(contactEntity: contactEntity)
            
            let message = testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                readDate: expectedReadDate,
                sender: contactEntity,
                remoteSentDate: Date()
            )
            
            let _ = MessageReactionEntity(
                context: testDatabase.context.main,
                reaction: reaction.rawValue,
                message: message
            )
            
            return message
        }
        
        let userSettingsMock = UserSettingsMock()
        userSettingsMock.sendEmojiReactions = true
        
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: userSettingsMock,
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager
        )
        let result = try await messageSender.sendReaction(
            to: message.objectID,
            reaction: EmojiVariant(base: reaction, skintone: nil)
        )
        
        XCTAssertEqual(result, .noSupportRemoteSingle)
    }
    
    /// Situation:
    /// Local support: Phase 2
    /// Remote support: Phase 1 or 2
    /// Action: Apply
    /// Mapping: None
    func testApplyReactionNoMappingYesLocalYesRemote() async throws {
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")
        let expectedMessageID = BytesUtility.generateMessageID()
        let expectedReadDate = Date()
        let reaction = Emoji.abacus
        
        let message = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)
            contactEntity.setFeatureMask(to: 2024)

            let conversation = testDatabase.preparer.createConversation(contactEntity: contactEntity)
            return testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                readDate: expectedReadDate,
                sender: contactEntity,
                remoteSentDate: Date()
            )
        }
        
        let userSettingsMock = UserSettingsMock()
        userSettingsMock.sendEmojiReactions = true
        
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: userSettingsMock,
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager
        )
        let result = try await messageSender.sendReaction(
            to: message.objectID,
            reaction: EmojiVariant(base: reaction, skintone: nil)
        )
        
        let addedTasks = taskManagerMock.addedTasks.filter { task in
            guard task is TaskDefinitionSendReactionMessage else {
                return false
            }
            return true
        }
        
        let testTask = try XCTUnwrap(addedTasks.first as? TaskDefinitionSendReactionMessage)
        XCTAssertEqual(result, .success)
        XCTAssertFalse(taskManagerMock.addedTasks.isEmpty)
        XCTAssertEqual(1, addedTasks.count)
        XCTAssertEqual(reaction.data(), testTask.reaction.apply)
        XCTAssertEqual(Data(), testTask.reaction.withdraw)
        XCTAssertEqual(expectedThreemaIdentity.rawValue, testTask.receiverIdentity)
    }
    
    /// Situation:
    /// Local support: Phase 2
    /// Remote support: Phase 1 or 2
    /// Action: Withdraw
    /// Mapping: None
    func testWithdrawReactionNoMappingYesLocalYesRemote() async throws {
        let expectedThreemaIdentity = ThreemaIdentity("ECHOECHO")
        let expectedMessageID = BytesUtility.generateMessageID()
        let expectedReadDate = Date()
        let reaction = Emoji.abacus
        
        let message = testDatabase.preparer.save {
            let contactEntity = testDatabase.preparer.createContact(identity: expectedThreemaIdentity.rawValue)
            contactEntity.setFeatureMask(to: 2024)

            let conversation = testDatabase.preparer.createConversation(contactEntity: contactEntity)
            
            let message = testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                readDate: expectedReadDate,
                sender: contactEntity,
                remoteSentDate: Date()
            )
            
            let _ = MessageReactionEntity(
                context: testDatabase.context.main,
                reaction: reaction.rawValue,
                message: message
            )
            
            return message
        }
        
        let userSettingsMock = UserSettingsMock()
        userSettingsMock.sendEmojiReactions = true
        
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: MyIdentityStoreMock(),
            userSettings: userSettingsMock,
            groupManager: GroupManagerMock(),
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager
        )
        
        let result = try await messageSender.sendReaction(
            to: message.objectID,
            reaction: EmojiVariant(base: reaction, skintone: nil)
        )
        
        let addedTasks = taskManagerMock.addedTasks.filter { task in
            guard task is TaskDefinitionSendReactionMessage else {
                return false
            }
            return true
        }
        
        let testTask = try XCTUnwrap(addedTasks.first as? TaskDefinitionSendReactionMessage)
        
        XCTAssertEqual(result, .success)
        XCTAssertFalse(taskManagerMock.addedTasks.isEmpty)
        XCTAssertEqual(1, addedTasks.count)
        XCTAssertEqual(Data(), testTask.reaction.apply)
        XCTAssertEqual(reaction.data(), testTask.reaction.withdraw)
        XCTAssertEqual(expectedThreemaIdentity.rawValue, testTask.receiverIdentity)
    }
    
    /// Situation:
    /// Local support: Phase 1 or 2
    /// Remote support: All legacy
    /// Action: Apply
    /// Mapping: None
    func testApplyReactionNoMappingYesLocalNoneRemote() async throws {
        let membersWithoutCreator = ["MEMBER01", "MEMBER02"]
        let expectedGroupID = BytesUtility.generateGroupID()
        let expectedMessageID = BytesUtility.generateMessageID()
        
        let reaction = Emoji.abacus
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        let userSettingMock = UserSettingsMock()
        userSettingMock.sendEmojiReactions = true
        
        let (message, group) = testDatabase.preparer.save {
            let members = membersWithoutCreator.map { identityString in
                let contactEntity = testDatabase.preparer.createContact(identity: identityString)
                contactEntity.setFeatureMask(to: 1)
                return contactEntity
            }
            
            let groupEntity = testDatabase.preparer.createGroupEntity(
                groupID: expectedGroupID,
                groupCreator: nil
            )
            
            let conversation = testDatabase.preparer.createConversation(groupID: expectedGroupID) { conversation in
                conversation.groupMyIdentity = myIdentityStoreMock.identity
                conversation.members?.formUnion(members)
            }
            
            let group = Group(
                myIdentityStore: myIdentityStoreMock,
                userSettings: userSettingMock,
                pushSettingManager: PushSettingManagerMock(),
                groupEntity: groupEntity,
                conversation: conversation,
                lastSyncRequest: nil
            )
            
            let message = testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                sender: members.first,
                remoteSentDate: Date()
            )
            
            return (message, group)
        }
        
        let groupManager = GroupManagerMock()
        groupManager.getGroupReturns = [group]
    
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: myIdentityStoreMock,
            userSettings: userSettingMock,
            groupManager: groupManager,
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager
        )
        
        let result = try await messageSender.sendReaction(
            to: message.objectID,
            reaction: EmojiVariant(base: reaction, skintone: nil)
        )
        
        XCTAssertEqual(result, .noSupportRemoteGroup)
        XCTAssertTrue(taskManagerMock.addedTasks.isEmpty)
    }
    
    /// Situation:
    /// Local support: Phase 1 or 2
    /// Remote support: All legacy
    /// Action: Apply
    /// Mapping: Success
    func testApplyReactionMappingYesLocalNoneRemote() async throws {
        let membersWithoutCreator = ["MEMBER01", "MEMBER02"]
        let expectedGroupID = BytesUtility.generateGroupID()
        let expectedMessageID = BytesUtility.generateMessageID()
        
        let reaction = Emoji.thumbsUp
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        let userSettingMock = UserSettingsMock()
        userSettingMock.sendEmojiReactions = true
        
        let (message, group) = testDatabase.preparer.save {
            let members = membersWithoutCreator.map { identityString in
                let contactEntity = testDatabase.preparer.createContact(identity: identityString)
                contactEntity.setFeatureMask(to: 1)
                return contactEntity
            }
            
            let groupEntity = testDatabase.preparer.createGroupEntity(
                groupID: expectedGroupID,
                groupCreator: nil
            )
            let conversation = testDatabase.preparer.createConversation(groupID: expectedGroupID) { conversation in
                conversation.groupMyIdentity = myIdentityStoreMock.identity
                conversation.members?.formUnion(members)
            }
            let group = Group(
                myIdentityStore: myIdentityStoreMock,
                userSettings: userSettingMock,
                pushSettingManager: PushSettingManagerMock(),
                groupEntity: groupEntity,
                conversation: conversation,
                lastSyncRequest: nil
            )
            
            let message = testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                sender: members.first,
                remoteSentDate: Date()
            )
            
            return (message, group)
        }
                
        let groupManager = GroupManagerMock()
        groupManager.getGroupReturns = [group]
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: myIdentityStoreMock,
            userSettings: userSettingMock,
            groupManager: groupManager,
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager
        )
        
        let result = try await messageSender.sendReaction(
            to: message.objectID,
            reaction: EmojiVariant(base: reaction, skintone: nil)
        )
        
        let addedTasks = taskManagerMock.addedTasks.filter { task in
            guard task is TaskDefinitionSendGroupDeliveryReceiptsMessage else {
                return false
            }
            return true
        }
        
        let testTask = try XCTUnwrap(addedTasks.first as? TaskDefinitionSendGroupDeliveryReceiptsMessage)
       
        XCTAssertFalse(taskManagerMock.addedTasks.isEmpty)
        XCTAssertEqual(1, addedTasks.count)
        XCTAssertEqual(ReceiptType.ack, testTask.receiptType)
        
        XCTAssertEqual(result, .success)
    }
    
    /// Situation:
    /// Local support: Phase 1 or 2
    /// Remote support: Some legacy, some phase 1 or 2
    /// Action: Apply
    /// Mapping: None
    func testApplyReactionNoMappingYesLocalSomeRemote() async throws {
        let expectedGroupID = BytesUtility.generateGroupID()
        let expectedMessageID = BytesUtility.generateMessageID()
        
        let notSupportingMemberID = "MEMBER01"
        let supportingMemberID = "MEMBER02"
        
        let reaction = Emoji.abacus
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        let userSettingMock = UserSettingsMock()
        userSettingMock.sendEmojiReactions = true
        
        let (message, _) = testDatabase.preparer.save {
            let contactEntity1 = testDatabase.preparer.createContact(identity: notSupportingMemberID)
            contactEntity1.setFeatureMask(to: 1)

            let contactEntity2 = testDatabase.preparer.createContact(identity: supportingMemberID)
            contactEntity2.setFeatureMask(to: 2024)

            let members = [contactEntity1, contactEntity2]
            
            let groupEntity = testDatabase.preparer.createGroupEntity(
                groupID: expectedGroupID,
                groupCreator: nil
            )
            let conversation = testDatabase.preparer.createConversation(groupID: expectedGroupID) { conversation in
                conversation.groupMyIdentity = myIdentityStoreMock.identity
                conversation.members?.formUnion(members)
            }
            let group = Group(
                myIdentityStore: myIdentityStoreMock,
                userSettings: userSettingMock,
                pushSettingManager: PushSettingManagerMock(),
                groupEntity: groupEntity,
                conversation: conversation,
                lastSyncRequest: nil
            )
            let message = testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                sender: members.first,
                remoteSentDate: Date()
            )
            
            return (message, group)
        }
        
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: ContactStoreMock(),
            taskManager: taskManagerMock,
            userSettings: userSettingMock,
            entityManager: testDatabase.entityManager,
            groupPhotoSender: {
                GroupPhotoSenderMock()
            }
        )
    
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: myIdentityStoreMock,
            userSettings: userSettingMock,
            groupManager: groupManager,
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager
        )
        
        let result = try await messageSender.sendReaction(
            to: message.objectID,
            reaction: EmojiVariant(base: reaction, skintone: nil)
        )
        
        let addedTasks = taskManagerMock.addedTasks.filter { task in
            guard task is TaskDefinitionSendReactionMessage else {
                return false
            }
            return true
        }
        
        let testTask = try XCTUnwrap(addedTasks.first as? TaskDefinitionSendReactionMessage)
        XCTAssertEqual(result, .partialSupportRemoteGroup)
        XCTAssertFalse(taskManagerMock.addedTasks.isEmpty)
        XCTAssertEqual(1, addedTasks.count)
        XCTAssertEqual(reaction.data(), testTask.reaction.apply)
        XCTAssertEqual(Data(), testTask.reaction.withdraw)
        let receivingGroupMembers = try XCTUnwrap(testTask.receivingGroupMembers)
        XCTAssertTrue(receivingGroupMembers.contains(supportingMemberID))
        XCTAssertFalse(receivingGroupMembers.contains(notSupportingMemberID))
    }
    
    /// Situation:
    /// Local support: Phase 1 or 2
    /// Remote support: Some legacy, some phase 1 or 2
    /// Action: Apply
    /// Mapping: Success
    func testApplyReactionMappingYesLocalSomeRemote() async throws {
        let expectedGroupID = BytesUtility.generateGroupID()
        let expectedMessageID = BytesUtility.generateMessageID()
       
        let notSupportingMemberID = "MEMBER01"
        let supportingMemberID = "MEMBER02"
        
        let reaction = Emoji.thumbsUp
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        let userSettingMock = UserSettingsMock()
        userSettingMock.sendEmojiReactions = true
        
        let (message, group) = testDatabase.preparer.save {
            let contactEntity1 = testDatabase.preparer.createContact(identity: notSupportingMemberID)
            contactEntity1.setFeatureMask(to: 1)

            let contactEntity2 = testDatabase.preparer.createContact(identity: supportingMemberID)
            contactEntity2.setFeatureMask(to: 2024)

            let members = [contactEntity1, contactEntity2]
            
            let groupEntity = testDatabase.preparer.createGroupEntity(
                groupID: expectedGroupID,
                groupCreator: nil
            )
            let conversation = testDatabase.preparer.createConversation(groupID: expectedGroupID) { conversation in
                conversation.groupMyIdentity = myIdentityStoreMock.identity
                conversation.members?.formUnion(members)
            }
            let group = Group(
                myIdentityStore: myIdentityStoreMock,
                userSettings: userSettingMock,
                pushSettingManager: PushSettingManagerMock(),
                groupEntity: groupEntity,
                conversation: conversation,
                lastSyncRequest: nil
            )
            
            let message = testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                sender: members.first,
                remoteSentDate: Date()
            )
            return (message, group)
        }
        
        let groupManager = GroupManagerMock()
        groupManager.getGroupReturns = [group]
    
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: myIdentityStoreMock,
            userSettings: userSettingMock,
            groupManager: groupManager,
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager
        )
        
        let result = try await messageSender.sendReaction(
            to: message.objectID,
            reaction: EmojiVariant(base: reaction, skintone: nil)
        )
        
        let addedTasks = taskManagerMock.addedTasks.filter { task in
            guard task is TaskDefinitionSendReactionMessage || task is TaskDefinitionSendGroupDeliveryReceiptsMessage
            else {
                return false
            }
            return true
        }
        
        XCTAssertEqual(result, .success)
        XCTAssertFalse(taskManagerMock.addedTasks.isEmpty)
        XCTAssertEqual(2, addedTasks.count)
        
        let reactionTask = addedTasks.first { task in
            task is TaskDefinitionSendReactionMessage
        }
        let unwrappedReactionTask = try XCTUnwrap(reactionTask as? TaskDefinitionSendReactionMessage)
        XCTAssertEqual(reaction.data(), unwrappedReactionTask.reaction.apply)
        XCTAssertEqual(Data(), unwrappedReactionTask.reaction.withdraw)
        let receivingReactionTaskGroupMembers = try XCTUnwrap(unwrappedReactionTask.receivingGroupMembers)
        XCTAssertTrue(receivingReactionTaskGroupMembers.contains(supportingMemberID))
        XCTAssertFalse(receivingReactionTaskGroupMembers.contains(notSupportingMemberID))
        
        let legacyTask = addedTasks.first { task in
            task is TaskDefinitionSendGroupDeliveryReceiptsMessage
        }
        let unwrappedLegacyTask = try XCTUnwrap(legacyTask as? TaskDefinitionSendGroupDeliveryReceiptsMessage)
        XCTAssertEqual(ReceiptType.ack, unwrappedLegacyTask.receiptType)
        let receivingLegacyTaskGroupMembers = try XCTUnwrap(unwrappedLegacyTask.toMembers)
        XCTAssertTrue(receivingLegacyTaskGroupMembers.contains(notSupportingMemberID))
        XCTAssertFalse(receivingLegacyTaskGroupMembers.contains(supportingMemberID))
    }

    /// Situation:
    /// Local support: Phase 1 or 2
    /// Remote support: Phase 1 or 2
    /// Action: Apply
    /// Mapping: None
    func testApplyReactionNoMappingYesLocalAllRemote() async throws {
        let expectedGroupID = BytesUtility.generateGroupID()
        let expectedMessageID = BytesUtility.generateMessageID()
        
        let membersWithoutCreator = ["MEMBER01", "MEMBER02"]
        
        let reaction = Emoji.abacus
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        let userSettingMock = UserSettingsMock()
        userSettingMock.sendEmojiReactions = true
        
        let (message, group) = testDatabase.preparer.save {
            let members = membersWithoutCreator.map { identityString in
                let contactEntity = testDatabase.preparer.createContact(identity: identityString)
                contactEntity.setFeatureMask(to: 2024)
                return contactEntity
            }
            
            let groupEntity = testDatabase.preparer.createGroupEntity(
                groupID: expectedGroupID,
                groupCreator: nil
            )
            let conversation = testDatabase.preparer.createConversation(groupID: expectedGroupID) { conversation in
                conversation.groupMyIdentity = myIdentityStoreMock.identity
                conversation.members?.formUnion(members)
            }
            let group = Group(
                myIdentityStore: myIdentityStoreMock,
                userSettings: userSettingMock,
                pushSettingManager: PushSettingManagerMock(),
                groupEntity: groupEntity,
                conversation: conversation,
                lastSyncRequest: nil
            )
            
            let message = testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                sender: members.first,
                remoteSentDate: Date()
            )
            return (message, group)
        }
        
        let groupManager = GroupManagerMock()
        groupManager.getGroupReturns = [group]
    
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: myIdentityStoreMock,
            userSettings: userSettingMock,
            groupManager: groupManager,
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager
        )
        
        let result = try await messageSender.sendReaction(
            to: message.objectID,
            reaction: EmojiVariant(base: reaction, skintone: nil)
        )
        
        let addedTasks = taskManagerMock.addedTasks.filter { task in
            guard task is TaskDefinitionSendReactionMessage else {
                return false
            }
            return true
        }
        
        let testTask = try XCTUnwrap(addedTasks.first as? TaskDefinitionSendReactionMessage)
        XCTAssertEqual(result, .success)
        XCTAssertFalse(taskManagerMock.addedTasks.isEmpty)
        XCTAssertEqual(1, addedTasks.count)
        XCTAssertEqual(reaction.data(), testTask.reaction.apply)
        XCTAssertEqual(Data(), testTask.reaction.withdraw)
    }
    
    /// Situation:
    /// Local support: Phase 1 or 2
    /// Remote support: Phase 1 or 2
    /// Action: Apply
    /// Mapping: Success
    func testApplyReactionMappingYesLocalAllRemote() async throws {
        let expectedGroupID = BytesUtility.generateGroupID()
        let expectedMessageID = BytesUtility.generateMessageID()
       
        let membersWithoutCreator = ["MEMBER01", "MEMBER02"]
        
        let reaction = Emoji.thumbsUp
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        let userSettingMock = UserSettingsMock()
        userSettingMock.sendEmojiReactions = true
        
        let (message, group) = testDatabase.preparer.save {
            let members = membersWithoutCreator.map { identityString in
                let contactEntity = testDatabase.preparer.createContact(identity: identityString)
                contactEntity.setFeatureMask(to: 2024)
                return contactEntity
            }
            
            let groupEntity = testDatabase.preparer.createGroupEntity(
                groupID: expectedGroupID,
                groupCreator: nil
            )
            let conversation = testDatabase.preparer.createConversation(groupID: expectedGroupID) { conversation in
                conversation.groupMyIdentity = myIdentityStoreMock.identity
                conversation.members?.formUnion(members)
            }
            let group = Group(
                myIdentityStore: myIdentityStoreMock,
                userSettings: userSettingMock,
                pushSettingManager: PushSettingManagerMock(),
                groupEntity: groupEntity,
                conversation: conversation,
                lastSyncRequest: nil
            )
            
            let message = testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                sender: members.first,
                remoteSentDate: Date()
            )
            return (message, group)
        }
        
        let groupManager = GroupManagerMock()
        groupManager.getGroupReturns = [group]
    
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: myIdentityStoreMock,
            userSettings: userSettingMock,
            groupManager: groupManager,
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager
        )
        
        let result = try await messageSender.sendReaction(
            to: message.objectID,
            reaction: EmojiVariant(base: reaction, skintone: nil)
        )
        
        let addedTasks = taskManagerMock.addedTasks.filter { task in
            guard task is TaskDefinitionSendReactionMessage else {
                return false
            }
            return true
        }
        
        let testTask = try XCTUnwrap(addedTasks.first as? TaskDefinitionSendReactionMessage)
        XCTAssertEqual(result, .success)
        XCTAssertFalse(taskManagerMock.addedTasks.isEmpty)
        XCTAssertEqual(1, addedTasks.count)
        XCTAssertEqual(reaction.data(), testTask.reaction.apply)
        XCTAssertEqual(Data(), testTask.reaction.withdraw)
    }
    
    /// Situation:
    /// Local support: Phase 2
    /// Remote support: NoteGroup
    /// Action: Apply
    /// Mapping: Success
    func testApplyReactionMappingYesLocalNoteGroup() async throws {
        let expectedGroupID = BytesUtility.generateGroupID()
        let expectedMessageID = BytesUtility.generateMessageID()
        let reaction = Emoji.ant
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        let userSettingMock = UserSettingsMock()
        userSettingMock.sendEmojiReactions = true
        
        let (message, group) = testDatabase.preparer.save {
            let groupEntity = testDatabase.preparer.createGroupEntity(
                groupID: expectedGroupID,
                groupCreator: nil
            )
            let conversation = testDatabase.preparer.createConversation(groupID: expectedGroupID) { conversation in
                conversation.groupMyIdentity = myIdentityStoreMock.identity
            }
            let group = Group(
                myIdentityStore: myIdentityStoreMock,
                userSettings: userSettingMock,
                pushSettingManager: PushSettingManagerMock(),
                groupEntity: groupEntity,
                conversation: conversation,
                lastSyncRequest: nil
            )
            
            let message = testDatabase.preparer.createTextMessage(
                conversation: conversation,
                id: expectedMessageID,
                isOwn: false,
                sender: nil,
                remoteSentDate: Date()
            )
            return (message, group)
        }
        
        let groupManager = GroupManagerMock()
        groupManager.getGroupReturns = [group]
    
        let messageSender = MessageSender(
            serverConnector: ServerConnectorMock(),
            myIdentityStore: myIdentityStoreMock,
            userSettings: userSettingMock,
            groupManager: groupManager,
            taskManager: taskManagerMock,
            entityManager: testDatabase.entityManager
        )
        
        let result = try await messageSender.sendReaction(
            to: message.objectID,
            reaction: EmojiVariant(base: reaction, skintone: nil)
        )
        
        let addedTasks = taskManagerMock.addedTasks.filter { task in
            guard task is TaskDefinitionSendReactionMessage else {
                return false
            }
            return true
        }
        
        let testTask = try XCTUnwrap(addedTasks.first as? TaskDefinitionSendReactionMessage)
        XCTAssertEqual(result, .success)
        XCTAssertFalse(taskManagerMock.addedTasks.isEmpty)
        XCTAssertEqual(1, addedTasks.count)
        XCTAssertEqual(reaction.data(), testTask.reaction.apply)
        XCTAssertEqual(Data(), testTask.reaction.withdraw)
    }
}

Messung
CHZG
839110091

¤ Dauer der Verarbeitung: 0.42 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104838
#Domains=499075