Eine aufbereitete Darstellung der Quelle

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

Benutzer

Quelle  GroupManagerTests.swift   Sprache: JAVA

 

import RemoteSecretProtocolTestHelper
import ThreemaEssentials

import XCTest

@testable import ThreemaFramework

final class GroupManagerTests: XCTestCase {
    private var ddLoggerMock: DDLoggerMock!
    
    private let groupPhotoSenderMock = GroupPhotoSenderMock()
    
    override func setUpWithError() throws {
        AppGroup.setGroupID("group.ch.threema") // THREEMA_GROUP_IDENTIFIER @"group.ch.threema"
        RemoteSecretProvider.setRemoteSecretManager(RemoteSecretManagerMock())

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

    override func tearDownWithError() throws {
        DDLog.remove(ddLoggerMock)
    }

    /// Spec: https://clients.pages.threema.dev/protocols/threema-protocols/structbuf/csp/#m:e2e:group-setup
    /// Section: When sending this message to all group members: 1.
    func testSendGroupSetupIamNotCreator() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let taskManagerMock = TaskManagerMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("MEMBER01")
        )
        let expectedMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", "MEMBER03"]

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: ContactStoreMock(callOnCompletion: true),
            taskManager: taskManagerMock,
            userSettings: UserSettingsMock(),
            entityManager: TestDatabase().backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        await XCTAssertThrowsAsyncError(
            _ = try await groupManager.createOrUpdate(
                for: expectedGroupIdentity,
                members: expectedMembers,
                systemMessageDate: Date()
            )
        )

        XCTAssertEqual(0, taskManagerMock.addedTasks.count)
    }
    
    /// Spec: https://clients.pages.threema.dev/protocols/threema-protocols/structbuf/csp/#m:e2e:group-setup
    /// Section: When sending this message to all group members: 2.
    func testSendGroupSetupIamCreator() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        let (resultGroup, resultNewMembers) = try await groupManager.createOrUpdate(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date()
        )

        let actualGroup = try XCTUnwrap(resultGroup)
        XCTAssertEqual(actualGroup.groupIdentity, expectedGroupIdentity)
        XCTAssertNil(actualGroup.conversation.contact) // If I'm the creator this should be true
        XCTAssertEqual(actualGroup.allMemberIdentities.count, expectedMembers.count + 1)
        XCTAssertTrue(actualGroup.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertNil(actualGroup.lastSyncRequest)
        
        XCTAssertNil(resultNewMembers)
        
        XCTAssertEqual(1, taskManagerMock.addedTasks.filter { $0 is TaskDefinitionSendGroupCreateMessage }.count)
        
        let task = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendGroupCreateMessage)
        XCTAssertTrue(expectedGroupIdentity.id.elementsEqual(task.groupID!))
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, task.groupCreatorIdentity)
        XCTAssertEqual(expectedMembers, task.members)
        let removedMembers = try XCTUnwrap(task.removedMembers)
        XCTAssertTrue(removedMembers.isEmpty)
        XCTAssertEqual(
            expectedMembers.count,
            task.toMembers
                .filter { $0.elementsEqual("MEMBER01") || $0.elementsEqual("MEMBER02") || $0.elementsEqual("MEMBER03") }
                .count
        )
    }
    
    func testSendGroupSetupIamCreatorAddMember() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedNewMembers: Set<String> = ["MEMBER01"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedNewMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: [],
            systemMessageDate: Date(),
            sourceCaller: .local
        )

        let (resultGroup, resultNewMembers) = try await groupManager.createOrUpdate(
            for: expectedGroupIdentity,
            members: expectedNewMembers,
            systemMessageDate: Date()
        )

        let resultGrp = try XCTUnwrap(resultGroup)
        XCTAssertEqual(resultGrp.groupIdentity, expectedGroupIdentity)
        XCTAssertEqual(resultGrp.allMemberIdentities.count, expectedNewMembers.count + 1)
        XCTAssertTrue(resultGrp.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertNil(resultGrp.lastSyncRequest)

        XCTAssertEqual(expectedNewMembers, resultNewMembers)
        XCTAssertEqual(1, taskManagerMock.addedTasks.filter { $0 is TaskDefinitionSendGroupCreateMessage }.count)

        let task = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendGroupCreateMessage)
        XCTAssertTrue(expectedGroupIdentity.id.elementsEqual(task.groupID!))
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, task.groupCreatorIdentity)
        XCTAssertEqual(expectedNewMembers, task.members)
        XCTAssertTrue(task.removedMembers?.isEmpty ?? false)
        XCTAssertEqual(
            expectedNewMembers.count,
            task.toMembers.filter { $0.elementsEqual("MEMBER01") }.count
        )
    }

    func testSendGroupSetupIamCreatorRemoveMember() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03", "MEMBER04"]
        
        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                let contact = testDatabase.backgroundPreparer.createContact(identity: member)
                contact.isHidden = member == "MEMBER02"
            }
        }
        
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        )

        let expectedNewMembers = expectedMembers.filter { $0 != "MEMBER02" && $0 != "MEMBER04" }
        
        let (resultGroup, resultNewMembers) = try await groupManager.createOrUpdate(
            for: expectedGroupIdentity,
            members: expectedNewMembers,
            systemMessageDate: Date()
        )

        let resultGrp = try XCTUnwrap(resultGroup)
        XCTAssertEqual(resultGrp.groupIdentity, expectedGroupIdentity)
        XCTAssertEqual(resultGrp.allMemberIdentities.count, expectedNewMembers.count + 1)
        XCTAssertTrue(resultGrp.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertNil(resultGrp.lastSyncRequest)

        XCTAssertEqual(0, contactStoreMock.markContactAsDeletedCalls.count)

        XCTAssertNil(resultNewMembers)
        XCTAssertEqual(1, taskManagerMock.addedTasks.filter { $0 is TaskDefinitionSendGroupCreateMessage }.count)
        
        let task = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendGroupCreateMessage)
        XCTAssertTrue(expectedGroupIdentity.id.elementsEqual(task.groupID!))
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, task.groupCreatorIdentity)
        XCTAssertEqual(expectedNewMembers, task.members)
        XCTAssertEqual(2, task.removedMembers?.filter { $0 == "MEMBER02" || $0 == "MEMBER04" }.count)
        XCTAssertEqual(
            expectedNewMembers.count,
            task.toMembers.filter { $0.elementsEqual("MEMBER01") || $0.elementsEqual("MEMBER03") }.count
        )
    }
    
    func testSendGroupSetupIamCreatorRemoveMemberWithRejectedMessages() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03", "MEMBER04"]
        
        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                let contact = testDatabase.backgroundPreparer.createContact(identity: member)
                contact.isHidden = (member == "MEMBER02")
            }
        }
        
        let entityManager = testDatabase.backgroundEntityManager
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: entityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        )

        let expectedNewMembers = expectedMembers.filter { $0 != "MEMBER02" && $0 != "MEMBER04" }
        let leavingMembers = expectedMembers.subtracting(expectedNewMembers)
        
        let messageID: Data! = try testDatabase.backgroundPreparer.save {
            let stayingContactEntity = try XCTUnwrap(
                entityManager.entityFetcher
                    .contactEntity(for: expectedNewMembers.first!)
            )
            let leavingContactEntity = try XCTUnwrap(
                entityManager.entityFetcher
                    .contactEntity(for: leavingMembers.first!)
            )

            let conversation = try XCTUnwrap(groupManager.getConversation(for: expectedGroupIdentity))
            let textMessage = testDatabase.backgroundPreparer.createTextMessage(
                conversation: conversation,
                isOwn: true,
                sent: true,
                sender: nil,
                remoteSentDate: nil
            )
            textMessage.sendFailed = NSNumber(booleanLiteral: true)
            textMessage.addToRejectedBy(stayingContactEntity)
            textMessage.addToRejectedBy(leavingContactEntity)

            return textMessage.id
        }

        let (resultGroup, resultNewMembers) = try await groupManager.createOrUpdate(
            for: expectedGroupIdentity,
            members: expectedNewMembers,
            systemMessageDate: Date()
        )

        let resultGrp = try XCTUnwrap(resultGroup)
        XCTAssertEqual(resultGrp.groupIdentity, expectedGroupIdentity)
        XCTAssertEqual(resultGrp.allMemberIdentities.count, expectedNewMembers.count + 1)
        XCTAssertTrue(resultGrp.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertNil(resultGrp.lastSyncRequest)

        XCTAssertEqual(0, contactStoreMock.markContactAsDeletedCalls.count)
        
        XCTAssertNil(resultNewMembers)
        XCTAssertEqual(1, taskManagerMock.addedTasks.filter { $0 is TaskDefinitionSendGroupCreateMessage }.count)
        
        let task = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendGroupCreateMessage)
        XCTAssertTrue(expectedGroupIdentity.id.elementsEqual(task.groupID!))
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, task.groupCreatorIdentity)
        XCTAssertEqual(expectedNewMembers, task.members)
        XCTAssertEqual(2, task.removedMembers?.filter { $0 == "MEMBER02" || $0 == "MEMBER04" }.count)
        XCTAssertEqual(
            expectedNewMembers.count,
            task.toMembers.filter { $0.elementsEqual("MEMBER01") || $0.elementsEqual("MEMBER03") }.count
        )
        
        // Only the rejectedBy from the leaving member should be removed
        
        let actualBaseMessage = try XCTUnwrap(
            entityManager.entityFetcher.message(with: messageID, in: resultGrp.conversation)
        )
        XCTAssertTrue(actualBaseMessage.sendFailed?.boolValue ?? false)
        XCTAssertEqual(1, actualBaseMessage.rejectedBy?.count ?? -1)
    }

    /// Spec: https://clients.pages.threema.dev/protocols/threema-protocols/structbuf/csp/#m:e2e:group-setup
    /// Section: When receiving this message: 4. / 1.
    func testReceiveGroupSetupFromBlockedContactAndUnknownGroupIamNotMember() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let userSettingsMock = UserSettingsMock()
        userSettingsMock.blacklist = ["MEMBER01"]
        let taskManagerMock = TaskManagerMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("MEMBER01")
        )
        let expectedMembers: Set<String> = ["MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupIdentity.creator.rawValue)

            for member in expectedMembers.filter({ $0 != myIdentityStoreMock.identity }) {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: ContactStoreMock(callOnCompletion: true),
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        await XCTAssertThrowsAsyncError(
            _ = try await groupManager.createOrUpdateDB(
                for: expectedGroupIdentity,
                members: expectedMembers,
                systemMessageDate: Date(),
                sourceCaller: .local
            )
        ) { error in
            if case GroupManager.GroupError.creatorIsBlocked(groupIdentity: expectedGroupIdentity) = error {
                // no-op
            }
            else {
                XCTFail("Wrong error type \(error)")
            }
        }

        XCTAssertEqual(0, taskManagerMock.addedTasks.count)
    }

    /// Spec: https://clients.pages.threema.dev/protocols/threema-protocols/structbuf/csp/#m:e2e:group-setup
    /// Section: When receiving this message: 4. / 2.
    func testReceiveGroupSetupFromBlockedContactAndUnknownGroup() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let userSettingsMock = UserSettingsMock()
        userSettingsMock.blacklist = ["MEMBER01"]
        let taskManagerMock = TaskManagerMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("MEMBER01")
        )
        let expectedMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupIdentity.creator.rawValue)

            for member in expectedMembers.filter({ $0 != myIdentityStoreMock.identity }) {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: ContactStoreMock(callOnCompletion: true),
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        await XCTAssertThrowsAsyncError(
            _ = try await groupManager.createOrUpdateDB(
                for: expectedGroupIdentity,
                members: expectedMembers,
                systemMessageDate: Date(),
                sourceCaller: .local
            )
        ) { error in
            if case GroupManager.GroupError.creatorIsBlocked(groupIdentity: expectedGroupIdentity) = error {
                // no-op
            }
            else {
                XCTFail("Wrong error type \(error)")
            }
        }

        XCTAssertEqual(1, taskManagerMock.addedTasks.count)
        let leaveTask = try XCTUnwrap(
            taskManagerMock.addedTasks
                .first(where: { $0 is TaskDefinitionSendGroupLeaveMessage
                }) as? TaskDefinitionSendGroupLeaveMessage
        )
        XCTAssertTrue(expectedGroupIdentity.id.elementsEqual(leaveTask.groupID!))
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, leaveTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, leaveTask.fromMember)
        XCTAssertEqual(
            expectedMembers.count,
            leaveTask.toMembers
                .filter { $0.elementsEqual("MEMBER01") || $0.elementsEqual("MEMBER02") || $0.elementsEqual("MEMBER03") }
                .count
        )
    }

    /// Spec: https://clients.pages.threema.dev/protocols/threema-protocols/structbuf/csp/#m:e2e:group-setup
    /// Section: When receiving this message: 5.
    func testReceiveGroupSetupExistingGroupIamNotMember() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()

        let expectedGroupCreator = "MEMBER01"
        let expectedInitialMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupCreator)
            for member in expectedInitialMembers.filter({ $0 != myIdentityStoreMock.identity }) {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
            testDatabase.backgroundPreparer.createContact(identity: "MEMBER04")
        }

        for expectedMembers in [Set<String>(["MEMBER02", "MEMBER04"]), Set<String>([])] {
            let expectedGroupIdentity = GroupIdentity(
                id: BytesUtility.generateGroupID(),
                creator: ThreemaIdentity(expectedGroupCreator)
            )

            let groupManager = GroupManager(
                myIdentityStore: myIdentityStoreMock,
                contactStore: ContactStoreMock(callOnCompletion: true),
                taskManager: TaskManagerMock(),
                userSettings: UserSettingsMock(),
                entityManager: testDatabase.backgroundEntityManager,
                groupPhotoSender: {
                    self.groupPhotoSenderMock
                }
            )

            guard let initialGrp = try await groupManager.createOrUpdateDB(
                for: expectedGroupIdentity,
                members: expectedInitialMembers,
                systemMessageDate: Date(),
                sourceCaller: .local
            ) else {
                XCTFail("Creating initial group failed")
                return
            }

            XCTAssertEqual(4, initialGrp.numberOfMembers)
            XCTAssertTrue(initialGrp.allMemberIdentities.contains(myIdentityStoreMock.identity))
            XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER01"))
            XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER02"))
            XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER03"))
            XCTAssertFalse(initialGrp.didCreatorLeave)
            XCTAssertFalse(initialGrp.didLeave)
            XCTAssertFalse(initialGrp.didForcedLeave)
            XCTAssertTrue(initialGrp.isSelfMember)
            XCTAssertFalse(initialGrp.isNoteGroup)

            // Hold old members to list chat history and to clone
            guard let grp = try await groupManager.createOrUpdateDB(
                for: expectedGroupIdentity,
                members: expectedMembers,
                systemMessageDate: Date(),
                sourceCaller: .local
            ) else {
                XCTFail("Updating initial group failed")
                return
            }

            XCTAssertEqual(3, grp.numberOfMembers)
            XCTAssertTrue(grp.allMemberIdentities.contains("MEMBER01"))
            XCTAssertTrue(grp.allMemberIdentities.contains("MEMBER02"))
            XCTAssertTrue(grp.allMemberIdentities.contains("MEMBER03"))
            XCTAssertFalse(grp.didCreatorLeave)
            XCTAssertFalse(grp.didLeave)
            XCTAssertTrue(grp.didForcedLeave)
            XCTAssertFalse(grp.isSelfMember)
            XCTAssertFalse(grp.isNoteGroup)
        }
    }
    
    func testReceiveGroupSetupExistingGroupIamNotMemberWithRejectedMessages() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()

        let expectedGroupCreator = "MEMBER01"
        let expectedInitialMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupCreator)
            for member in expectedInitialMembers.filter({ $0 != myIdentityStoreMock.identity }) {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
            testDatabase.backgroundPreparer.createContact(identity: "MEMBER04")
        }

        let expectedMembers: Set<String> = ["MEMBER02", "MEMBER04"]
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(expectedGroupCreator)
        )
        
        let entityManager = testDatabase.backgroundEntityManager
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: ContactStoreMock(callOnCompletion: true),
            taskManager: TaskManagerMock(),
            userSettings: UserSettingsMock(),
            entityManager: entityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let initialGrp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedInitialMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating initial group failed")
            return
        }

        XCTAssertEqual(4, initialGrp.numberOfMembers)
        XCTAssertTrue(initialGrp.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER01"))
        XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER02"))
        XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER03"))
        XCTAssertFalse(initialGrp.didCreatorLeave)
        XCTAssertFalse(initialGrp.didLeave)
        XCTAssertFalse(initialGrp.didForcedLeave)
        XCTAssertTrue(initialGrp.isSelfMember)
        XCTAssertFalse(initialGrp.isNoteGroup)
        
        let messageID: Data! = try testDatabase.backgroundPreparer.save {
            let member02 = try XCTUnwrap(entityManager.entityFetcher.contactEntity(for: "MEMBER02"))
            let member03 = try XCTUnwrap(entityManager.entityFetcher.contactEntity(for: "MEMBER03"))

            let conversation = try XCTUnwrap(groupManager.getConversation(for: expectedGroupIdentity))
            let textMessage = testDatabase.backgroundPreparer.createTextMessage(
                conversation: conversation,
                isOwn: true,
                sent: true,
                sender: nil,
                remoteSentDate: nil
            )
            textMessage.sendFailed = NSNumber(booleanLiteral: true)
            textMessage.addToRejectedBy(member02)
            textMessage.addToRejectedBy(member03)

            return textMessage.id
        }
        
        // Hold old members to list chat history and to clone
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Updating initial group failed")
            return
        }

        XCTAssertEqual(3, grp.numberOfMembers)
        XCTAssertTrue(grp.allMemberIdentities.contains("MEMBER01"))
        XCTAssertTrue(grp.allMemberIdentities.contains("MEMBER02"))
        XCTAssertTrue(grp.allMemberIdentities.contains("MEMBER03"))
        XCTAssertFalse(grp.didCreatorLeave)
        XCTAssertFalse(grp.didLeave)
        XCTAssertTrue(grp.didForcedLeave)
        XCTAssertFalse(grp.isSelfMember)
        XCTAssertFalse(grp.isNoteGroup)
        
        let actualBaseMessage = try XCTUnwrap(
            entityManager.entityFetcher.message(with: messageID, in: grp.conversation)
        )
        XCTAssertFalse(actualBaseMessage.sendFailed?.boolValue ?? true)
        XCTAssertEqual(0, actualBaseMessage.rejectedBy?.count ?? -1)
    }
    
    func testReceiveGroupSetupWithRevokedMember() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("MEMBER01")
        )
        let expectedInitialMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupIdentity.creator.rawValue)
            for member in expectedInitialMembers.filter({ $0 != myIdentityStoreMock.identity }) {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let expectedMembers = expectedInitialMembers.union(Set(arrayLiteral: "MEMBER04"))

        let error = NSError(domain: NSURLErrorDomain, code: 404)
            
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: ContactStoreMock(callOnCompletion: true, errorHandler: error),
            taskManager: TaskManagerMock(),
            userSettings: UserSettingsMock(),
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let initialGrp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedInitialMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating initial group failed")
            return
        }

        XCTAssertEqual(4, initialGrp.numberOfMembers)
        XCTAssertTrue(initialGrp.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER01"))
        XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER02"))
        XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER03"))
        XCTAssertFalse(initialGrp.didCreatorLeave)
        XCTAssertFalse(initialGrp.didLeave)
        XCTAssertFalse(initialGrp.didForcedLeave)
        XCTAssertTrue(initialGrp.isSelfMember)
        XCTAssertFalse(initialGrp.isNoteGroup)

        // Hold old members to list chat history and to clone
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Updating initial group failed")
            return
        }

        XCTAssertEqual(4, grp.numberOfMembers)
        XCTAssertTrue(grp.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertTrue(grp.allMemberIdentities.contains("MEMBER01"))
        XCTAssertTrue(grp.allMemberIdentities.contains("MEMBER02"))
        XCTAssertTrue(grp.allMemberIdentities.contains("MEMBER03"))
        XCTAssertFalse(grp.didCreatorLeave)
        XCTAssertFalse(grp.didLeave)
        XCTAssertFalse(grp.didForcedLeave)
        XCTAssertTrue(grp.isSelfMember)
        XCTAssertFalse(grp.isNoteGroup)
    }
    
    func testReceiveGroupSetupWithRevokedMemberPartiallyApplyGroupSetup() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("MEMBER01")
        )
        let expectedInitialMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupIdentity.creator.rawValue)
            for member in expectedInitialMembers.filter({ $0 != myIdentityStoreMock.identity }) {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }

            testDatabase.backgroundPreparer.createContact(identity: "MEMBER05")
        }

        let expectedMembers = expectedInitialMembers.union(Set(arrayLiteral: "MEMBER04", "MEMBER05"))
        
        // A made up error; If we ever check for this in the future we need to change this test.
        let error = NSError(domain: NSURLErrorDomain, code: 404)
            
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: ContactStoreMock(callOnCompletion: true, errorHandler: error),
            taskManager: TaskManagerMock(),
            userSettings: UserSettingsMock(),
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let initialGrp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedInitialMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating initial group failed")
            return
        }

        XCTAssertEqual(4, initialGrp.numberOfMembers)
        XCTAssertTrue(initialGrp.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER01"))
        XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER02"))
        XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER03"))
        XCTAssertFalse(initialGrp.didCreatorLeave)
        XCTAssertFalse(initialGrp.didLeave)
        XCTAssertFalse(initialGrp.didForcedLeave)
        XCTAssertTrue(initialGrp.isSelfMember)
        XCTAssertFalse(initialGrp.isNoteGroup)
            
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Updating initial group failed")
            return
        }

        XCTAssertEqual(5, grp.numberOfMembers)
        XCTAssertTrue(grp.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertTrue(grp.allMemberIdentities.contains("MEMBER01"))
        XCTAssertTrue(grp.allMemberIdentities.contains("MEMBER02"))
        XCTAssertTrue(grp.allMemberIdentities.contains("MEMBER03"))
        XCTAssertTrue(grp.allMemberIdentities.contains("MEMBER05"))
        XCTAssertFalse(grp.allMemberIdentities.contains("MEMBER04"))
        XCTAssertFalse(grp.didCreatorLeave)
        XCTAssertFalse(grp.didLeave)
        XCTAssertFalse(grp.didForcedLeave)
        XCTAssertTrue(grp.isSelfMember)
        XCTAssertFalse(grp.isNoteGroup)
    }
    
    func testReceiveGroupSetupWithMissingLocalMember() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("MEMBER01")
        )
        let expectedInitialMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupIdentity.creator.rawValue)
            for member in expectedInitialMembers.filter({ $0 != myIdentityStoreMock.identity }) {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let expectedMembers = expectedInitialMembers.union(Set(arrayLiteral: "MEMBER04"))
        
        // A made up error; If we ever check for this in the future we need to change this test.
        let expectedError = NSError(domain: NSPOSIXErrorDomain, code: 8_765_432_187)

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: ContactStoreMock(callOnCompletion: true, errorHandler: expectedError),
            taskManager: TaskManagerMock(),
            userSettings: UserSettingsMock(),
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let initialGrp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedInitialMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating initial group failed")
            return
        }

        XCTAssertEqual(4, initialGrp.numberOfMembers)
        XCTAssertTrue(initialGrp.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER01"))
        XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER02"))
        XCTAssertTrue(initialGrp.allMemberIdentities.contains("MEMBER03"))
        XCTAssertFalse(initialGrp.didCreatorLeave)
        XCTAssertFalse(initialGrp.didLeave)
        XCTAssertFalse(initialGrp.didForcedLeave)
        XCTAssertTrue(initialGrp.isSelfMember)
        XCTAssertFalse(initialGrp.isNoteGroup)
            
        await XCTAssertThrowsAsyncError(
            _ = try await groupManager.createOrUpdateDB(
                for: expectedGroupIdentity,
                members: expectedMembers,
                systemMessageDate: Date(),
                sourceCaller: .local
            )
        ) { _ in
//            XCTAssertEqual((error as NSError).code, (expectedError as NSError).code)
        }
    }

    func testSetName() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]
        let expectedName = "Test name 123"

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager: GroupManagerProtocol = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        XCTAssertNil(grp.name)
        
        try await groupManager.setName(group: grp, name: expectedName)

        XCTAssertEqual(expectedName, grp.name)
        XCTAssertEqual(1, taskManagerMock.addedTasks.filter { $0 is TaskDefinitionSendGroupRenameMessage }.count)
        let task = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendGroupRenameMessage)
        XCTAssertTrue(expectedGroupIdentity.id.elementsEqual(task.groupID!))
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, task.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, task.fromMember)
        XCTAssertEqual(
            expectedMembers.count,
            task.toMembers
                .filter { $0.elementsEqual("MEMBER01") || $0.elementsEqual("MEMBER02") || $0.elementsEqual("MEMBER03") }
                .count
        )
        XCTAssertEqual(expectedName, task.name)
    }
    
    func testSetAndDeletePhoto() async throws {

        // Setup
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]
        let expectedPhoto: Data = try! Data(contentsOf: ResourceLoader.urlResource("Bild-1-0", "jpg")!)

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        XCTAssertNil(grp.old_ProfilePicture)
        
        // Run set photo test
        
        try await groupManager.setPhoto(group: grp, imageData: expectedPhoto, sentDate: Date())

        // Validate set photo test
        
        XCTAssertTrue(grp.old_ProfilePicture!.elementsEqual(expectedPhoto))
        XCTAssertEqual(1, taskManagerMock.addedTasks.filter { $0 is TaskDefinitionSendGroupSetPhotoMessage }.count)
        
        let setTask = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendGroupSetPhotoMessage)
        XCTAssertTrue(expectedGroupIdentity.id.elementsEqual(setTask.groupID!))
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, setTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, setTask.fromMember)
        XCTAssertEqual(
            expectedMembers.count,
            setTask.toMembers
                .filter { $0.elementsEqual("MEMBER01") || $0.elementsEqual("MEMBER02") || $0.elementsEqual("MEMBER03") }
                .count
        )
        XCTAssertEqual(expectedPhoto.count, Int(setTask.size))
        XCTAssertEqual(groupPhotoSenderMock.blobID, setTask.blobID)
        XCTAssertEqual(groupPhotoSenderMock.encryptionKey, setTask.encryptionKey)
        
        // Run delete photo test
        
        try await groupManager.deletePhoto(groupID: grp.groupID, creator: grp.groupCreatorIdentity, sentDate: Date())

        // Validate delete photo test
        
        XCTAssertNil(grp.old_ProfilePicture)
        XCTAssertEqual(1, taskManagerMock.addedTasks.filter { $0 is TaskDefinitionSendGroupDeletePhotoMessage }.count)
        
        let deleteTask = try XCTUnwrap(
            taskManagerMock.addedTasks
                .first(where: { $0 is TaskDefinitionSendGroupDeletePhotoMessage
                }) as? TaskDefinitionSendGroupDeletePhotoMessage
        )
        XCTAssertTrue(expectedGroupIdentity.id.elementsEqual(deleteTask.groupID!))
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, deleteTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, deleteTask.fromMember)
        XCTAssertEqual(
            expectedMembers.count,
            deleteTask.toMembers
                .filter { $0.elementsEqual("MEMBER01") || $0.elementsEqual("MEMBER02") || $0.elementsEqual("MEMBER03") }
                .count
        )
    }

    // Spec: https://clients.pages.threema.dev/protocols/threema-protocols/structbuf/csp-e2e/#m:e2e:group-leave
    func testSendLeaveAsCreator() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        XCTAssertNotNil(grp)

        groupManager.leave(
            groupID: expectedGroupIdentity.id,
            creator: expectedGroupIdentity.creator.rawValue,
            toMembers: nil,
            systemMessageDate: Date()
        )

        DDLog.flushLog()

        XCTAssertTrue(ddLoggerMock.exists(message: "Group creator can't leave the group"))
        XCTAssertEqual(0, taskManagerMock.addedTasks.count)
    }

    // Spec: https://clients.pages.threema.dev/protocols/threema-protocols/structbuf/csp-e2e/#m:e2e:group-leave
    func testSendLeaveAsMember() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("MEMBER01")
        )
        let expectedMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", "MEMBER03", "MEMBER04"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupIdentity.creator.rawValue)
            for member in expectedMembers {
                let contact = testDatabase.backgroundPreparer.createContact(identity: member)
                contact.isHidden = member == "MEMBER02" || member == "MEMBER04"
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        let initialNumberOfMember = grp.numberOfMembers

        groupManager.leave(
            groupID: expectedGroupIdentity.id,
            creator: expectedGroupIdentity.creator.rawValue,
            toMembers: nil,
            systemMessageDate: Date()
        )

        XCTAssertEqual(1, taskManagerMock.addedTasks.filter { $0 is TaskDefinitionSendGroupLeaveMessage }.count)
        let task = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendGroupLeaveMessage)
        XCTAssertTrue(expectedGroupIdentity.id.elementsEqual(task.groupID!))
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, task.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, task.fromMember)
        XCTAssertEqual(
            4,
            task.toMembers?
                .filter {
                    $0.elementsEqual("MEMBER01") ||
                        $0.elementsEqual("MEMBER02") ||
                        $0.elementsEqual("MEMBER03") ||
                        $0.elementsEqual("MEMBER04")
                }
                .count
        )
        XCTAssertEqual(2, task.hiddenContacts.count)
        XCTAssertTrue(task.hiddenContacts.contains("MEMBER02"))
        XCTAssertTrue(task.hiddenContacts.contains("MEMBER04"))
        XCTAssertEqual(initialNumberOfMember - 1, grp.numberOfMembers)
    }

    func testSendLeaveToParticularMember() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("MEMBER01")
        )
        let expectedMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupIdentity.creator.rawValue)
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        groupManager.leave(
            groupID: expectedGroupIdentity.id,
            creator: expectedGroupIdentity.creator.rawValue,
            toMembers: ["MEMBER02"],
            systemMessageDate: Date()
        )

        XCTAssertEqual(1, taskManagerMock.addedTasks.filter { $0 is TaskDefinitionSendGroupLeaveMessage }.count)
        let task = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionSendGroupLeaveMessage)
        XCTAssertTrue(expectedGroupIdentity.id.elementsEqual(task.groupID!))
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, task.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, task.fromMember)
        XCTAssertEqual(1, task.toMembers.count)
        XCTAssertTrue(try XCTUnwrap(task.toMembers).contains("MEMBER02"))
        XCTAssertEqual(0, task.hiddenContacts.count)
    }

    func testSendLeaveAndAdd() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("MEMBER01")
        )
        let expectedMembers: Set<String> = ["MEMBER02", "MEMBER03", myIdentityStoreMock.identity]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupIdentity.creator.rawValue)
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        XCTAssertEqual(4, grp.allMemberIdentities.count)
        XCTAssertFalse(grp.didLeave)

        groupManager.leave(
            groupID: expectedGroupIdentity.id,
            creator: expectedGroupIdentity.creator.rawValue,
            toMembers: nil,
            systemMessageDate: Date()
        )

        let grpLeft = try XCTUnwrap(
            groupManager
                .getGroup(expectedGroupIdentity.id, creator: expectedGroupIdentity.creator.rawValue)
        )
        XCTAssertEqual(3, grpLeft.allMemberIdentities.count)
        XCTAssertTrue(grpLeft.didLeave)

        guard let grpAdded = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Updating group failed")
            return
        }

        XCTAssertEqual(4, grpAdded.allMemberIdentities.count)
        XCTAssertFalse(grpAdded.didLeave)
    }
    
    func testReceiveLeaveFromMember() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator:
            ThreemaIdentity("MEMBER01")
        )
        let leavingMember = "MEMBER03"
        let initialMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", leavingMember]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupIdentity.creator.rawValue)
            for member in initialMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let entityManager = testDatabase.backgroundEntityManager
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: entityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: initialMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        )

        groupManager.leaveDB(
            groupID: expectedGroupIdentity.id,
            creator: expectedGroupIdentity.creator.rawValue,
            member: leavingMember,
            systemMessageDate: Date()
        )

        let group = try XCTUnwrap(
            groupManager.getGroup(expectedGroupIdentity.id, creator: expectedGroupIdentity.creator.rawValue)
        )

        DDLog.flushLog()

        XCTAssertFalse(group.didLeave)
        XCTAssertFalse(group.didForcedLeave)
        XCTAssertEqual(3, group.allMemberIdentities.count)
        XCTAssertTrue(group.allMemberIdentities.contains("MEMBER02"))
        XCTAssertFalse(group.allMemberIdentities.contains(leavingMember))

        // Check added system messages

        let messageFetcher = MessageFetcher(for: group.conversation, with: entityManager)
        XCTAssertEqual(messageFetcher.count(), 4)

        let systemMessageTypes = messageFetcher.messages(at: 0, count: 0)
            .map { ($0 as? SystemMessageEntity)?.type ?? 0 }
        XCTAssertEqual(
            3,
            systemMessageTypes
                .filter { $0.intValue == SystemMessageEntity.SystemMessageEntityType.groupMemberAdd.rawValue }.count
        )
        XCTAssertEqual(
            SystemMessageEntity.SystemMessageEntityType.groupMemberLeave.rawValue,
            systemMessageTypes.last?.intValue
        )
    }
    
    func testReceiveLeaveFromMemberWithRejectedMessages() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator:
            ThreemaIdentity("MEMBER01")
        )
        let leavingMember = "MEMBER03"
        let initialMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", leavingMember]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupIdentity.creator.rawValue)
            for member in initialMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let entityManager = testDatabase.backgroundEntityManager
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: entityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: initialMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        )

        let group = try XCTUnwrap(
            groupManager.getGroup(expectedGroupIdentity.id, creator: expectedGroupIdentity.creator.rawValue)
        )

        let messageID: Data! = try testDatabase.backgroundPreparer.save {
            let leavingContactEntity = try XCTUnwrap(entityManager.entityFetcher.contactEntity(for: leavingMember))

            let conversation = try XCTUnwrap(groupManager.getConversation(for: expectedGroupIdentity))
            let textMessage = testDatabase.backgroundPreparer.createTextMessage(
                conversation: conversation,
                isOwn: true,
                sender: nil,
                remoteSentDate: nil
            )
            textMessage.sendFailed = NSNumber(booleanLiteral: true)
            textMessage.addToRejectedBy(leavingContactEntity)

            return textMessage.id
        }

        // Run leave

        groupManager.leaveDB(
            groupID: expectedGroupIdentity.id,
            creator: expectedGroupIdentity.creator.rawValue,
            member: leavingMember,
            systemMessageDate: Date()
        )

        DDLog.flushLog()

        XCTAssertFalse(group.didLeave)
        XCTAssertFalse(group.didForcedLeave)
        XCTAssertEqual(3, group.allMemberIdentities.count)
        XCTAssertTrue(group.allMemberIdentities.contains("MEMBER02"))
        XCTAssertFalse(group.allMemberIdentities.contains(leavingMember))

        let actualBaseMessage = try XCTUnwrap(
            entityManager.entityFetcher.message(with: messageID, in: group.conversation)
        )
        XCTAssertFalse(actualBaseMessage.sendFailed?.boolValue ?? true)
        XCTAssertEqual(0, actualBaseMessage.rejectedBy?.count ?? -1)
    }

    // Spec: https://clients.pages.threema.dev/protocols/threema-protocols/structbuf/csp/#m:e2e:group-leave
    // Section: When receiving this message: 1.
    //      -> In step 1 the group creator will be removed from the group. And a system message 'group is not mutable
    //         anymore' will be added.
    //      -> Later in step 1 the group will be left. And system message 'group must be cloned' will be added.
    func testReceiveLeaveFromCreator() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("MEMBER01")
        )
        let expectedMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupIdentity.creator.rawValue)
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let entityManager = testDatabase.backgroundEntityManager
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: entityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        )

        groupManager.leaveDB(
            groupID: expectedGroupIdentity.id,
            creator: expectedGroupIdentity.creator.rawValue,
            member: expectedGroupIdentity.creator.rawValue,
            systemMessageDate: Date()
        )

        let group = try XCTUnwrap(
            groupManager.getGroup(expectedGroupIdentity.id, creator: expectedGroupIdentity.creator.rawValue)
        )

        DDLog.flushLog()

        XCTAssertFalse(group.didLeave)
        XCTAssertFalse(group.didForcedLeave)
        XCTAssertEqual(3, group.allMemberIdentities.count)
        XCTAssertTrue(group.allMemberIdentities.contains("MEMBER02"))
        XCTAssertFalse(group.allMemberIdentities.contains(expectedGroupIdentity.creator.rawValue))

        // Check added system messages

        let messageFetcher = MessageFetcher(for: group.conversation, with: entityManager)
        XCTAssertEqual(messageFetcher.count(), 5)

        let systemMessageTypes = messageFetcher.messages(at: 0, count: 0)
            .map { ($0 as? SystemMessageEntity)?.type ?? 0 }
        XCTAssertEqual(
            3,
            systemMessageTypes
                .filter { $0.intValue == SystemMessageEntity.SystemMessageEntityType.groupMemberAdd.rawValue }
                .count
        )
        XCTAssertEqual(
            SystemMessageEntity.SystemMessageEntityType.groupCreatorLeft.rawValue,
            systemMessageTypes.last?.intValue
        )
    }

    func testDissolveAsAdmin() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                let contactEntity = testDatabase.backgroundPreparer.createContact(identity: member)
                contactEntity.contactState = member == "MEMBER02" ? .invalid : .active
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        groupManager.dissolve(groupID: grp.groupID, to: nil)

        let group = try XCTUnwrap(
            groupManager.getGroup(expectedGroupIdentity.id, creator: expectedGroupIdentity.creator.rawValue)
        )

        XCTAssertTrue(group.didLeave)
        XCTAssertFalse(group.didForcedLeave)
        XCTAssertFalse(group.allMemberIdentities.contains(expectedGroupIdentity.creator.rawValue))
        XCTAssertEqual(3, group.allMemberIdentities.count)

        let task = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionGroupDissolve)
        XCTAssertEqual(2, task.toMembers.count)
        XCTAssertTrue(task.toMembers.contains("MEMBER01"))
        XCTAssertTrue(task.toMembers.contains("MEMBER03"))
    }
    
    func testDissolveAsAdminWithRejectedMessages() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                let contactEntity = testDatabase.backgroundPreparer.createContact(identity: member)
                contactEntity.contactState = member == "MEMBER02" ? .invalid : .active
            }
        }

        let entityManager = testDatabase.backgroundEntityManager
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: entityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        // Create two text messages with rejectedBy
        
        let messageID1: Data! = try testDatabase.backgroundPreparer.save {
            let someContactEntity = try XCTUnwrap(
                entityManager.entityFetcher.contactEntity(for: expectedMembers.randomElement()!)
            )

            let textMessage = testDatabase.backgroundPreparer.createTextMessage(
                conversation: grp.conversation,
                isOwn: true,
                sender: nil,
                remoteSentDate: nil
            )
            textMessage.sendFailed = NSNumber(booleanLiteral: true)
            textMessage.addToRejectedBy(someContactEntity)

            return textMessage.id
        }
        
        let messageID2: Data! = try testDatabase.backgroundPreparer.save {
            let someContactEntity = try XCTUnwrap(
                entityManager.entityFetcher.contactEntity(for: expectedMembers.randomElement()!)
            )

            let textMessage = testDatabase.backgroundPreparer.createTextMessage(
                conversation: grp.conversation,
                isOwn: true,
                sender: nil,
                remoteSentDate: nil
            )
            textMessage.sendFailed = NSNumber(booleanLiteral: true)
            textMessage.addToRejectedBy(someContactEntity)

            return textMessage.id
        }

        groupManager.dissolve(groupID: grp.groupID, to: nil)

        let group = try XCTUnwrap(
            groupManager.getGroup(expectedGroupIdentity.id, creator: expectedGroupIdentity.creator.rawValue)
        )

        XCTAssertTrue(group.didLeave)
        XCTAssertFalse(group.didForcedLeave)
        XCTAssertFalse(group.allMemberIdentities.contains(expectedGroupIdentity.creator.rawValue))
        XCTAssertEqual(3, group.allMemberIdentities.count)

        let task = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionGroupDissolve)
        XCTAssertEqual(2, task.toMembers.count)
        XCTAssertTrue(task.toMembers.contains("MEMBER01"))
        XCTAssertTrue(task.toMembers.contains("MEMBER03"))
        
        // All rejectedBys should be removed
        
        let actualBaseMessage1 = try XCTUnwrap(
            entityManager.entityFetcher.message(with: messageID1, in: group.conversation)
        )
        XCTAssertFalse(actualBaseMessage1.sendFailed?.boolValue ?? true)
        XCTAssertEqual(0, actualBaseMessage1.rejectedBy?.count ?? -1)
        
        let actualBaseMessage2 = try XCTUnwrap(
            entityManager.entityFetcher.message(with: messageID2, in: group.conversation)
        )
        XCTAssertFalse(actualBaseMessage2.sendFailed?.boolValue ?? true)
        XCTAssertEqual(0, actualBaseMessage2.rejectedBy?.count ?? -1)
    }

    func testDissolveAsAdminTwoMembers() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        groupManager.dissolve(groupID: grp.groupID, to: ["MEMBER01", "MEMBER02"])

        let group = try XCTUnwrap(
            groupManager.getGroup(expectedGroupIdentity.id, creator: expectedGroupIdentity.creator.rawValue)
        )

        XCTAssertTrue(group.didLeave)
        XCTAssertFalse(group.didForcedLeave)
        XCTAssertFalse(group.allMemberIdentities.contains(expectedGroupIdentity.creator.rawValue))
        XCTAssertEqual(3, group.allMemberIdentities.count)

        let task = try XCTUnwrap(taskManagerMock.addedTasks.first as? TaskDefinitionGroupDissolve)
        XCTAssertEqual(2, task.toMembers.count)
        XCTAssertTrue(task.toMembers.contains("MEMBER01"))
        XCTAssertTrue(task.toMembers.contains("MEMBER02"))
    }

    func testDissolveAsMember() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("MEMBER01")
        )
        let expectedMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupIdentity.creator.rawValue)
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        groupManager.dissolve(groupID: grp.groupID, to: nil)

        let group = try XCTUnwrap(
            groupManager.getGroup(expectedGroupIdentity.id, creator: expectedGroupIdentity.creator.rawValue)
        )

        XCTAssertFalse(group.didLeave)
        XCTAssertFalse(group.didForcedLeave)
        XCTAssertEqual(4, group.allMemberIdentities.count)
        XCTAssertEqual(0, taskManagerMock.addedTasks.count)
    }
    
    // Test to send empty member list to not member contact
    func testSendEmptyMemberListToContact() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER02", "MEMBER03"]
        let notMembers: Set<ThreemaIdentity> = [ThreemaIdentity("NOMEMBER")]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                guard member != myIdentityStoreMock.identity else {
                    continue
                }
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        groupManager.sendEmptyMemberList(groupIdentity: grp.groupIdentity, to: notMembers)

        let group = try XCTUnwrap(
            groupManager.getGroup(expectedGroupIdentity.id, creator: expectedGroupIdentity.creator.rawValue)
        )
        
        guard let sendGroupCreateMessage = taskManagerMock.addedTasks.first as? TaskDefinitionSendGroupCreateMessage
        else {
            XCTFail("Missing SendGroupCreateMessageTask")
            return
        }

        XCTAssertFalse(group.didLeave)
        XCTAssertFalse(group.didForcedLeave)
        XCTAssertEqual(3, group.allMemberIdentities.count)
        
        XCTAssertEqual(0, sendGroupCreateMessage.members.count)
        XCTAssertEqual(1, sendGroupCreateMessage.toMembers.count)
        XCTAssertEqual(notMembers.first?.rawValue, sendGroupCreateMessage.toMembers.first)
    }

    // Test with blocked contact
    func testSyncAllMembers() async throws {
        // Setup
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]
        let expectedPhoto: Data = try XCTUnwrap(Data(contentsOf: ResourceLoader.urlResource("Bild-1-0", "jpg")!))

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        // Set photo

        try await groupManager.setPhoto(group: grp, imageData: expectedPhoto, sentDate: Date())

        // Run
        
        try await groupManager.sync(group: grp, to: nil, withoutCreateMessage: false)
        
        // Validate
        
        // SetPhoto, Create, Rename, SetPhoto => 4
        XCTAssertEqual(4, taskManagerMock.addedTasks.count)

        let createMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[1] as? TaskDefinitionSendGroupCreateMessage)
        XCTAssertEqual(expectedGroupIdentity.id, createMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, createMessageTask.groupCreatorIdentity)
        XCTAssertEqual(expectedMembers, Set(createMessageTask.toMembers))
        XCTAssertNil(createMessageTask.removedMembers)
        XCTAssertEqual(expectedMembers, createMessageTask.members)
        
        let renameMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[2] as? TaskDefinitionSendGroupRenameMessage)
        XCTAssertEqual(expectedGroupIdentity.id, renameMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, renameMessageTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, renameMessageTask.fromMember)
        XCTAssertEqual(expectedMembers, Set(renameMessageTask.toMembers))
        XCTAssertNil(renameMessageTask.name)
        
        let setPhotoTask = try XCTUnwrap(taskManagerMock.addedTasks[3] as? TaskDefinitionSendGroupSetPhotoMessage)
        XCTAssertEqual(expectedGroupIdentity.id, setPhotoTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, setPhotoTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, setPhotoTask.fromMember)
        XCTAssertEqual(expectedMembers, Set(setPhotoTask.toMembers))
        XCTAssertEqual(groupPhotoSenderMock.blobID, setPhotoTask.blobID)
        XCTAssertEqual(groupPhotoSenderMock.encryptionKey, setPhotoTask.encryptionKey)
    }
    
    func testSyncOneMember() async throws {
        // Setup
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]
        let expectedToMember = Set(["MEMBER01"])

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        // Run
        
        try await groupManager.sync(group: grp, to: expectedToMember, withoutCreateMessage: false)

        // Validate
        
        // Create, Rename, DeletePhoto => 3
        XCTAssertEqual(3, taskManagerMock.addedTasks.count)

        let createMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[0] as? TaskDefinitionSendGroupCreateMessage)
        XCTAssertEqual(expectedGroupIdentity.id, createMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, createMessageTask.groupCreatorIdentity)
        XCTAssertEqual(expectedToMember, Set(createMessageTask.toMembers))
        XCTAssertNil(createMessageTask.removedMembers)
        XCTAssertEqual(expectedMembers, createMessageTask.members)
        
        let renameMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[1] as? TaskDefinitionSendGroupRenameMessage)
        XCTAssertEqual(expectedGroupIdentity.id, renameMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, renameMessageTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, renameMessageTask.fromMember)
        XCTAssertEqual(expectedToMember, Set(renameMessageTask.toMembers))
        XCTAssertNil(renameMessageTask.name)
        
        let setPhotoTask = try XCTUnwrap(taskManagerMock.addedTasks[2] as? TaskDefinitionSendGroupDeletePhotoMessage)
        XCTAssertEqual(expectedGroupIdentity.id, setPhotoTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, setPhotoTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, setPhotoTask.fromMember)
        XCTAssertEqual(expectedToMember, Set(setPhotoTask.toMembers))
    }
    
    func testSyncUnknownMember() async throws {
        // Setup
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]
        
        let unknownMember = "MEMBER04"

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        // Run
        
        try await groupManager.sync(group: grp, to: Set([unknownMember]), withoutCreateMessage: false)

        // Validate
        
        // A group create message with an empty member list should be synced to a unknown contact
        XCTAssertEqual(1, taskManagerMock.addedTasks.count)
        
        let createMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[0] as? TaskDefinitionSendGroupCreateMessage)
        XCTAssertEqual(expectedGroupIdentity.id, createMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, createMessageTask.groupCreatorIdentity)
        XCTAssertEqual(0, createMessageTask.toMembers.count)
        XCTAssertEqual([unknownMember], createMessageTask.removedMembers)
        XCTAssertEqual(expectedMembers, createMessageTask.members)
    }
    
    func testPeriodicSync() async throws {
        // Setup
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let startDate = Date()
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]
        let expectedPhoto: Data = try XCTUnwrap(Data(contentsOf: ResourceLoader.urlResource("Bild-1-0", "jpg")!))
        
        let expectationPhotoTaskAdded = expectation(description: "Photo task added")
        var taskCount = 0
        taskManagerMock.taskAdded = {
            taskCount += 1
            
            if taskCount == 4 {
                expectationPhotoTaskAdded.fulfill()
            }
        }

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let entityManager = testDatabase.backgroundEntityManager
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: entityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        await entityManager.performSave {
            if let grpEntity = entityManager.entityFetcher.groupEntity(
                for: grp.groupIdentity, myIdentity: myIdentityStoreMock.identity
            ) {
                grpEntity.lastPeriodicSync = Date(timeIntervalSinceNow: Double(-kGroupPeriodicSyncInterval))
            }
        }

        // Set photo

        try await groupManager.setPhoto(group: grp, imageData: expectedPhoto, sentDate: Date())

        // Run
        
        groupManager.periodicSyncIfNeeded(for: grp)

        await fulfillment(of: [expectationPhotoTaskAdded])

        // Validate
        
        let lastPeriodicSync = try XCTUnwrap(grp.lastPeriodicSync)
        XCTAssertTrue(lastPeriodicSync > startDate)
        
        // SetPhoto, Create, Rename, send photo
        XCTAssertEqual(4, taskManagerMock.addedTasks.count)
        
        let createMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[1] as? TaskDefinitionSendGroupCreateMessage)
        XCTAssertEqual(expectedGroupIdentity.id, createMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, createMessageTask.groupCreatorIdentity)
        XCTAssertEqual(expectedMembers, Set(createMessageTask.toMembers))
        XCTAssertNil(createMessageTask.removedMembers)
        XCTAssertEqual(expectedMembers, createMessageTask.members)
        
        let renameMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[2] as? TaskDefinitionSendGroupRenameMessage)
        XCTAssertEqual(expectedGroupIdentity.id, renameMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, renameMessageTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, renameMessageTask.fromMember)
        XCTAssertEqual(expectedMembers, Set(renameMessageTask.toMembers))
        XCTAssertNil(renameMessageTask.name)
        
        let setPhotoTask = try XCTUnwrap(taskManagerMock.addedTasks[3] as? TaskDefinitionSendGroupSetPhotoMessage)
        XCTAssertEqual(expectedGroupIdentity.id, setPhotoTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, setPhotoTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, setPhotoTask.fromMember)
        XCTAssertEqual(expectedMembers, Set(setPhotoTask.toMembers))
        XCTAssertEqual(groupPhotoSenderMock.blobID, setPhotoTask.blobID)
        XCTAssertEqual(groupPhotoSenderMock.encryptionKey, setPhotoTask.encryptionKey)
    }
    
    func testPeriodicSyncWithDelayedPhotoSending() async throws {
        // Setup
        
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        let delayedGroupPhotoSenderMock = GroupPhotoSenderMock(delay: 5000)
        
        let startDate = Date()
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]
        let expectedPhoto: Data = try XCTUnwrap(Data(contentsOf: ResourceLoader.urlResource("Bild-1-0", "jpg")!))

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let entityManager = testDatabase.backgroundEntityManager
        let groupManagerForPreparation = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: entityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let grp = try await groupManagerForPreparation.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        entityManager.performAndWaitSave {
            if let grpEntity = entityManager.entityFetcher.groupEntity(
                for: grp.groupIdentity, myIdentity: myIdentityStoreMock.identity
            ) {
                grpEntity.lastPeriodicSync = Date(timeIntervalSinceNow: Double(-kGroupPeriodicSyncInterval))
            }
        }
        
        // Set photo
        try await groupManagerForPreparation.setPhoto(group: grp, imageData: expectedPhoto, sentDate: Date())

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        // Run
        
        groupManager.periodicSyncIfNeeded(for: grp)
        
        // Validate
        
        let lastPeriodicSync = try XCTUnwrap(grp.lastPeriodicSync)
        XCTAssertTrue(lastPeriodicSync > startDate)
        
        // SetPhoto, Create, Rename (photo sending is delayed)
        XCTAssertEqual(3, taskManagerMock.addedTasks.count)
        
        let createMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[1] as? TaskDefinitionSendGroupCreateMessage)
        XCTAssertEqual(expectedGroupIdentity.id, createMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, createMessageTask.groupCreatorIdentity)
        XCTAssertEqual(expectedMembers, Set(createMessageTask.toMembers))
        XCTAssertNil(createMessageTask.removedMembers)
        XCTAssertEqual(expectedMembers, createMessageTask.members)
        
        let renameMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[2] as? TaskDefinitionSendGroupRenameMessage)
        XCTAssertEqual(expectedGroupIdentity.id, renameMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, renameMessageTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, renameMessageTask.fromMember)
        XCTAssertEqual(expectedMembers, Set(renameMessageTask.toMembers))
        XCTAssertNil(renameMessageTask.name)
    }

    func testPeriodicSyncNoPhoto() async throws {
        // Setup

        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        let delayedGroupPhotoSenderMock = GroupPhotoSenderMock(delay: 5000)

        let startDate = Date()
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let entityManager = testDatabase.backgroundEntityManager
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: entityManager,
            groupPhotoSender: {
                delayedGroupPhotoSenderMock
            }
        )

        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        entityManager.performAndWaitSave {
            if let grpEntity = entityManager.entityFetcher.groupEntity(
                for: grp.groupIdentity, myIdentity: myIdentityStoreMock.identity
            ) {
                grpEntity.lastPeriodicSync = Date(timeIntervalSinceNow: Double(-kGroupPeriodicSyncInterval))
            }
        }

        // Run

        groupManager.periodicSyncIfNeeded(for: grp)

        // Validate

        let lastPeriodicSync = try XCTUnwrap(grp.lastPeriodicSync)
        XCTAssertTrue(lastPeriodicSync > startDate)
        
        // Create, Rename and Delete photo
        XCTAssertEqual(3, taskManagerMock.addedTasks.count)

        let createMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[0] as? TaskDefinitionSendGroupCreateMessage)
        XCTAssertEqual(expectedGroupIdentity.id, createMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, createMessageTask.groupCreatorIdentity)
        XCTAssertEqual(expectedMembers, Set(createMessageTask.toMembers))
        XCTAssertNil(createMessageTask.removedMembers)
        XCTAssertEqual(expectedMembers, createMessageTask.members)

        let renameMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[1] as? TaskDefinitionSendGroupRenameMessage)
        XCTAssertEqual(expectedGroupIdentity.id, renameMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, renameMessageTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, renameMessageTask.fromMember)
        XCTAssertEqual(expectedMembers, Set(renameMessageTask.toMembers))
        XCTAssertNil(renameMessageTask.name)

        let deleteTask = try XCTUnwrap(taskManagerMock.addedTasks[2] as? TaskDefinitionSendGroupDeletePhotoMessage)
        XCTAssertTrue(expectedGroupIdentity.id.elementsEqual(deleteTask.groupID!))
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, deleteTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, deleteTask.fromMember)
        XCTAssertEqual(expectedMembers, Set(deleteTask.toMembers))
    }
    
    func testPeriodicSyncNotNeeded() async throws {
        // Setup

        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        let delayedGroupPhotoSenderMock = GroupPhotoSenderMock(delay: 5000)

        let lastPeriodicSync = Date(timeIntervalSinceNow: -24 * 60 * 60) // One day in the past
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let entityManager = testDatabase.backgroundEntityManager
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: entityManager,
            groupPhotoSender: {
                delayedGroupPhotoSenderMock
            }
        )
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        entityManager.performAndWaitSave {
            if let grpEntity = entityManager.entityFetcher.groupEntity(
                for: grp.groupIdentity, myIdentity: myIdentityStoreMock.identity
            ) {
                grpEntity.lastPeriodicSync = lastPeriodicSync
            }
        }

        // Run

        groupManager.periodicSyncIfNeeded(for: grp)

        // Validate

        let actualLastPeriodicSync = try XCTUnwrap(grp.lastPeriodicSync)
        XCTAssertEqual(lastPeriodicSync, actualLastPeriodicSync)

        XCTAssertEqual(0, taskManagerMock.addedTasks.count)
    }
    
    func testGetGroupImCreator() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let expectedGroup = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        let actualGroup = try XCTUnwrap(
            groupManager
                .getGroup(expectedGroupIdentity.id, creator: expectedGroupIdentity.creator.rawValue)
        )
        
        XCTAssertEqual(actualGroup.groupIdentity, expectedGroupIdentity)
        XCTAssertEqual(actualGroup.conversation, expectedGroup.conversation)
    }
    
    func testGetGroupOtherCreator() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("MEMBER01")
        )
        let expectedMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER01", "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let expectedGroup = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        let actualGroup = try XCTUnwrap(
            groupManager
                .getGroup(expectedGroupIdentity.id, creator: expectedGroupIdentity.creator.rawValue)
        )

        XCTAssertEqual(actualGroup.groupIdentity, expectedGroupIdentity)
        XCTAssertEqual(actualGroup.conversation, expectedGroup.conversation)
    }
    
    func testGetAllActiveGroupsOwnGroup() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers: Set<String> = ["MEMBER01", "MEMBER02", "MEMBER03"]
        
        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }
        
        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        )

        let actualActiveGroups = await groupManager.getAllActiveGroups()
        
        XCTAssertEqual(actualActiveGroups.count, 1)
        XCTAssertEqual(actualActiveGroups[0].groupIdentity, expectedGroupIdentity)
        XCTAssertFalse(actualActiveGroups[0].didLeave)
        XCTAssertFalse(actualActiveGroups[0].didForcedLeave)
    }
    
    func testGetAllActiveGroupsOtherGroup() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("MEMBER01")
        )
        let expectedMembers: Set<String> = [myIdentityStoreMock.identity, "MEMBER01", "MEMBER02", "MEMBER03"]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        )

        let actualActiveGroups = await groupManager.getAllActiveGroups()
        
        XCTAssertEqual(actualActiveGroups.count, 1)
        XCTAssertEqual(actualActiveGroups[0].groupIdentity, expectedGroupIdentity)
        XCTAssertFalse(actualActiveGroups[0].didLeave)
        XCTAssertFalse(actualActiveGroups[0].didForcedLeave)
    }
    
    func testGetAllActiveGroupsMultipleGroups() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let allMembers = [
            "MEMBER01",
            "MEMBER02",
            "MEMBER03",
            "MEMBER04",
            "MEMBER05",
            "MEMBER06",
            "MEMBER07",
            "MEMBER08",
            "MEMBER09",
            "MEMBER10",
            "MEMBER11",
            "MEMBER12",
        ]

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in allMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        var expectedGroupIdentities = [GroupIdentity]()
        
        // Group 1: Own
        
        let expectedGroup1 = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )
        let expectedMembers1 = Set<String>(allMembers[0..<3])
        
        try await groupManager.createOrUpdateDB(
            for: expectedGroup1,
            members: expectedMembers1,
            systemMessageDate: Date(),
            sourceCaller: .local
        )

        expectedGroupIdentities.append(expectedGroup1)

        // Group 2: Other
        
        let expectedGroup2 = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(allMembers[3])
        )
        let expectedMembers2 = Set<String>(allMembers[3...] + [myIdentityStoreMock.identity])
        
        try await groupManager.createOrUpdateDB(
            for: expectedGroup2,
            members: expectedMembers2,
            systemMessageDate: Date(),
            sourceCaller: .local
        )

        expectedGroupIdentities.append(expectedGroup2)
        
        // Group 3: Left
        
        let expectedGroup3 = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(allMembers[3])
        )
        let expectedMembers3 = Set<String>(allMembers[3...] + [myIdentityStoreMock.identity])
        
        try await groupManager.createOrUpdateDB(
            for: expectedGroup3,
            members: expectedMembers3,
            systemMessageDate: Date(),
            sourceCaller: .local
        )

        groupManager.leave(groupWith: expectedGroup3, inform: .all)
                
        // Run
        
        let actualActiveGroups = await groupManager.getAllActiveGroups()
        
        // Validate
        
        XCTAssertEqual(actualActiveGroups.count, expectedGroupIdentities.count)
        
        for (index, expectedGroupIdentity) in expectedGroupIdentities.enumerated() {
            XCTAssertEqual(actualActiveGroups[index].groupIdentity, expectedGroupIdentity)
            XCTAssertFalse(actualActiveGroups[index].didLeave)
            XCTAssertFalse(actualActiveGroups[index].didForcedLeave)
        }
    }
    
    func testAddMemberToGroupWithOpenBallot() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )

        let initialMembers: Set<String> = ["MEMBER01", "MEMBER02"]
        let newMember = "MEMBER03"
        let expectedMembers = initialMembers.union(Set([newMember]))
        
        // Setup stuff

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let group = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: initialMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        // Add open ballot
        
        testDatabase.backgroundPreparer.save {
            let conversationEntity = testDatabase.backgroundEntityManager.entityFetcher
                .conversationEntity(with: group.conversationObjectID)!
            let ballotEntity = testDatabase.backgroundPreparer.createBallot(conversation: conversationEntity)
            ballotEntity.creatorID = myIdentityStoreMock.identity
            ballotEntity.state = NSNumber(integerLiteral: BallotEntity.BallotState.open.rawValue)
        }
        
        // Test
        
        let (resultGroup, resultNewMembers) = try await groupManager.createOrUpdate(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date()
        )

        XCTAssertNotNil(resultGroup)
        XCTAssertEqual(resultGroup.groupIdentity, expectedGroupIdentity)
        XCTAssertEqual(resultGroup.allMemberIdentities.count, expectedMembers.count + 1)
        XCTAssertTrue(resultGroup.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertTrue(resultNewMembers!.contains(newMember))
        // Allow MyIdentity injection in the future for `Ballot` to actually create send
        // message tasks and check for them here
    }
    
    func testCreateNoteGroup() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let testDatabase = TestDatabase()
        let entityManager = testDatabase.backgroundEntityManager
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: entityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: [],
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        XCTAssertEqual(grp.groupID, expectedGroupIdentity.id)

        let conversation = entityManager.entityFetcher.conversationEntity(
            for: grp.groupIdentity, myIdentity: myIdentityStoreMock.identity
        )
        
        XCTAssertNotNil(conversation)

        let lastMessage = MessageFetcher(for: conversation!, with: entityManager).lastMessage() as! SystemMessageEntity
        
        XCTAssertEqual(grp.groupIdentity, expectedGroupIdentity)
        XCTAssertEqual(grp.allMemberIdentities.count, 1)
        XCTAssertTrue(grp.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertNil(grp.lastSyncRequest)
        XCTAssertEqual(
            lastMessage.type,
            NSNumber(value: SystemMessageEntity.SystemMessageEntityType.startNoteGroupInfo.rawValue)
        )
    }
    
    func testAddMemberToNoteGroup() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let testDatabase = TestDatabase()
        let entityManager = testDatabase.backgroundEntityManager
        
        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: entityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: [],
            systemMessageDate: Date(),
            sourceCaller: .local
        )
        
        let expectedNewMembers: Set<String> = ["MEMBER01"]

        testDatabase.backgroundPreparer.save {
            for member in expectedNewMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedNewMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        let conversation = entityManager.entityFetcher.conversationEntity(
            for: grp.groupIdentity, myIdentity: myIdentityStoreMock.identity
        )
        XCTAssertNotNil(conversation)
        
        let messageFetcher = MessageFetcher(for: conversation!, with: entityManager)
        var endNoteGroupInfoCount = 0
        for message in messageFetcher.messages(at: 0, count: messageFetcher.count()) {
            if let tmpMessage = message as? SystemMessageEntity,
               tmpMessage.type
               .isEqual(to: NSNumber(value: SystemMessageEntity.SystemMessageEntityType.endNoteGroupInfo.rawValue)) {
                endNoteGroupInfoCount += 1
            }
        }
        
        XCTAssertEqual(grp.groupIdentity, expectedGroupIdentity)
        XCTAssertEqual(grp.allMemberIdentities.count, expectedNewMembers.count + 1)
        XCTAssertTrue(grp.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertNil(grp.lastSyncRequest)
        XCTAssertEqual(endNoteGroupInfoCount, 1)
    }
    
    func testRemoveMemberFromNoteGroup() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let testDatabase = TestDatabase()
        let entityManager = testDatabase.backgroundEntityManager

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )

        let expectedMembers: Set<String> = ["MEMBER01"]

        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: entityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        )
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: [],
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        let conversation = entityManager.entityFetcher.conversationEntity(
            for: grp.groupIdentity, myIdentity: myIdentityStoreMock.identity
        )
        XCTAssertNotNil(conversation)
        
        let messageFetcher = MessageFetcher(for: conversation!, with: entityManager)
        var startNoteGroupInfoCount = 0
        for message in messageFetcher.messages(at: 0, count: messageFetcher.count()) {
            if let tmpMessage = message as? SystemMessageEntity,
               tmpMessage.type
               .isEqual(to: NSNumber(value: SystemMessageEntity.SystemMessageEntityType.startNoteGroupInfo.rawValue)) {
                startNoteGroupInfoCount += 1
            }
        }
        
        XCTAssertEqual(grp.groupIdentity, expectedGroupIdentity)
        XCTAssertEqual(grp.allMemberIdentities.count, 1)
        XCTAssertTrue(grp.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertNil(grp.lastSyncRequest)
        XCTAssertEqual(startNoteGroupInfoCount, 1)
    }
    
    func testAddAndRemoveMemberFromNoteGroup() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let testDatabase = TestDatabase()
        let entityManager = testDatabase.backgroundEntityManager

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: entityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )
        try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: [],
            systemMessageDate: Date(),
            sourceCaller: .local
        )
        
        let expectedNewMembers: Set<String> = ["MEMBER01"]

        testDatabase.backgroundPreparer.save {
            for member in expectedNewMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedNewMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        )
        guard let grp = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: [],
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        let conversation = entityManager.entityFetcher.conversationEntity(
            for: grp.groupIdentity, myIdentity: myIdentityStoreMock.identity
        )
        XCTAssertNotNil(conversation)
        
        let messageFetcher = MessageFetcher(for: conversation!, with: entityManager)
        var startNoteGroupInfoCount = 0
        var endNoteGroupInfoCount = 0
        for message in messageFetcher.messages(at: 0, count: messageFetcher.count()) {
            if let tmpMessage = message as? SystemMessageEntity {
                switch tmpMessage.type.intValue {
                case SystemMessageEntity.SystemMessageEntityType.startNoteGroupInfo.rawValue:
                    startNoteGroupInfoCount += 1
                case SystemMessageEntity.SystemMessageEntityType.endNoteGroupInfo.rawValue:
                    endNoteGroupInfoCount += 1
                default: break
                }
            }
        }
        
        XCTAssertEqual(grp.groupIdentity, expectedGroupIdentity)
        XCTAssertEqual(grp.allMemberIdentities.count, 1)
        XCTAssertTrue(grp.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertTrue(grp.isNoteGroup)
        XCTAssertNil(grp.lastSyncRequest)
        XCTAssertEqual(startNoteGroupInfoCount, 2)
        XCTAssertEqual(endNoteGroupInfoCount, 1)
    }

    func testSyncNoteGroupWhenMultiDeviceIsActivated() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let testDatabase = TestDatabase()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: UserSettingsMock(enableMultiDevice: true),
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let noteGroup = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: [],
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        try await groupManager.sync(group: noteGroup, to: nil, withoutCreateMessage: false)

        XCTAssertEqual(taskManagerMock.addedTasks.count, 3)
        XCTAssertTrue(taskManagerMock.addedTasks.contains(where: { task in
            if let t = task as? TaskDefinitionSendGroupCreateMessage {
                return t.members.isEmpty
            }
            return false
        }))
        XCTAssertTrue(taskManagerMock.addedTasks.contains(where: { task in
            task is TaskDefinitionSendGroupRenameMessage
        }))
        XCTAssertTrue(taskManagerMock.addedTasks.contains(where: { task in
            task is TaskDefinitionSendGroupDeletePhotoMessage
        }))
    }

    func testCreateOrUpdateBlockUnknownContact() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()

        let userSettingsMock = UserSettingsMock()
        userSettingsMock.blockUnknown = true

        let errorBlockUnknown = NSError(
            domain: "ThreemaErrorDomain",
            code: ThreemaProtocolError.blockUnknownContact.rawValue
        )
        let contactStoreMock = ContactStoreMock(callOnCompletion: true, nil, errorHandler: errorBlockUnknown)

        let taskManagerMock = TaskManagerMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity("CREATOR1")
        )
        let expectedGroupMembers = Set(["MEMBER01", "MEMBER02", "MEMBER03", myIdentityStoreMock.identity!])

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            testDatabase.backgroundPreparer.createContact(identity: expectedGroupIdentity.creator.rawValue)
            testDatabase.backgroundPreparer.createContact(identity: "MEMBER02")
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let group = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: expectedGroupMembers,
            systemMessageDate: nil,
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        XCTAssertEqual(3, group.numberOfMembers)
    }
    
    func testCreateOrUpdateNewMemberSync() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )

        let initialMembers: Set<String> = ["MEMBER01", "MEMBER02"]
        let newMembers: Set<String> = ["MEMBER03"]
        let expectedMembers = initialMembers.union(newMembers)

        // Setup stuff

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let group = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: initialMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }

        // Test

        let (resultGroup, resultNewMembers) = try await groupManager.createOrUpdate(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date()
        )

        XCTAssertNotNil(resultGroup)
        XCTAssertEqual(resultGroup.groupIdentity, expectedGroupIdentity)
        XCTAssertEqual(resultGroup.allMemberIdentities.count, expectedMembers.count + 1)
        XCTAssertTrue(resultGroup.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertTrue(resultNewMembers!.contains(newMembers.first!))

        // Validate

        // Create, Rename, DeletePhoto => 3
        XCTAssertEqual(3, taskManagerMock.addedTasks.count)

        let createMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[0] as? TaskDefinitionSendGroupCreateMessage)
        XCTAssertEqual(expectedGroupIdentity.id, createMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, createMessageTask.groupCreatorIdentity)
        XCTAssertEqual(expectedMembers, Set(createMessageTask.toMembers))

        let renameMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[1] as? TaskDefinitionSendGroupRenameMessage)
        XCTAssertEqual(expectedGroupIdentity.id, renameMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, renameMessageTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, renameMessageTask.fromMember)
        XCTAssertEqual(newMembers, Set(renameMessageTask.toMembers))
        XCTAssertNil(renameMessageTask.name)

        let setPhotoTask = try XCTUnwrap(taskManagerMock.addedTasks[2] as? TaskDefinitionSendGroupDeletePhotoMessage)
        XCTAssertEqual(expectedGroupIdentity.id, setPhotoTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, setPhotoTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, setPhotoTask.fromMember)
        XCTAssertEqual(newMembers, Set(setPhotoTask.toMembers))
    }
    
    func testCreateOrUpdateNewMemberSyncWithGroupImage() async throws {
        let myIdentityStoreMock = MyIdentityStoreMock()
        let contactStoreMock = ContactStoreMock(callOnCompletion: true)
        let taskManagerMock = TaskManagerMock()
        let userSettingsMock = UserSettingsMock()

        let expectedGroupIdentity = GroupIdentity(
            id: BytesUtility.generateGroupID(),
            creator: ThreemaIdentity(myIdentityStoreMock.identity)
        )

        let initialMembers: Set<String> = ["MEMBER01", "MEMBER02"]
        let newMembers: Set<String> = ["MEMBER03"]
        let expectedMembers = initialMembers.union(newMembers)
        let expectedPhoto: Data = try! Data(contentsOf: ResourceLoader.urlResource("Bild-1-0", "jpg")!)

        // Setup stuff

        let testDatabase = TestDatabase()
        testDatabase.backgroundPreparer.save {
            for member in expectedMembers {
                testDatabase.backgroundPreparer.createContact(identity: member)
            }
        }

        let groupManager = GroupManager(
            myIdentityStore: myIdentityStoreMock,
            contactStore: contactStoreMock,
            taskManager: taskManagerMock,
            userSettings: userSettingsMock,
            entityManager: testDatabase.backgroundEntityManager,
            groupPhotoSender: {
                self.groupPhotoSenderMock
            }
        )

        guard let group = try await groupManager.createOrUpdateDB(
            for: expectedGroupIdentity,
            members: initialMembers,
            systemMessageDate: Date(),
            sourceCaller: .local
        ) else {
            XCTFail("Creating group failed")
            return
        }
        
        try await groupManager.setPhoto(group: group, imageData: expectedPhoto, sentDate: Date(), send: false)

        // Test

        let (resultGroup, resultNewMembers) = try await groupManager.createOrUpdate(
            for: expectedGroupIdentity,
            members: expectedMembers,
            systemMessageDate: Date()
        )

        XCTAssertNotNil(resultGroup)
        XCTAssertEqual(resultGroup.groupIdentity, expectedGroupIdentity)
        XCTAssertEqual(resultGroup.allMemberIdentities.count, expectedMembers.count + 1)
        XCTAssertTrue(resultGroup.allMemberIdentities.contains(myIdentityStoreMock.identity))
        XCTAssertTrue(resultNewMembers!.contains(newMembers.first!))

        // Validate

        // Create, Rename, DeletePhoto => 3
        XCTAssertEqual(3, taskManagerMock.addedTasks.count)

        let createMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[0] as? TaskDefinitionSendGroupCreateMessage)
        XCTAssertEqual(expectedGroupIdentity.id, createMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, createMessageTask.groupCreatorIdentity)
        XCTAssertEqual(expectedMembers, Set(createMessageTask.toMembers))

        let renameMessageTask = try XCTUnwrap(taskManagerMock.addedTasks[1] as? TaskDefinitionSendGroupRenameMessage)
        XCTAssertEqual(expectedGroupIdentity.id, renameMessageTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, renameMessageTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, renameMessageTask.fromMember)
        XCTAssertEqual(newMembers, Set(renameMessageTask.toMembers))
        XCTAssertNil(renameMessageTask.name)

        let setPhotoTask = try XCTUnwrap(taskManagerMock.addedTasks[2] as? TaskDefinitionSendGroupSetPhotoMessage)
        XCTAssertEqual(expectedGroupIdentity.id, setPhotoTask.groupID)
        XCTAssertEqual(expectedGroupIdentity.creator.rawValue, setPhotoTask.groupCreatorIdentity)
        XCTAssertEqual(myIdentityStoreMock.identity, setPhotoTask.fromMember)
        XCTAssertEqual(newMembers, Set(setPhotoTask.toMembers))
    }
}

Messung
CHZG
100929996

¤ Dauer der Verarbeitung: 0.71 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=499078