import ThreemaEssentials
import XCTest
@testable import ThreemaFramework
final class GroupMessageProcessorTests: XCTestCase {
private var testDatabase: TestDatabase!
private var databasePreparer: TestDatabasePreparer!
private var ddLoggerMock: DDLoggerMock!
override func setUpWithError() throws {
AppGroup.setGroupID("group.ch.threema") // THREEMA_GROUP_IDENTIFIER @"group.ch.threema"
testDatabase = TestDatabase()
databasePreparer = testDatabase.preparer
// Workaround to ensure remote secret is initialized
AppLaunchManager.shared.setRemoteSecretManager(testDatabase.remoteSecretManagerMock)
ddLoggerMock = DDLoggerMock()
DDTTYLogger.sharedInstance?.logFormatter = LogFormatterCustom()
DDLog.add(ddLoggerMock)
}
override func tearDownWithError() throws {
DDLog.remove(ddLoggerMock)
}
func testHandleMessageGroupNotExists() throws {
let myIdentityStoreMock = MyIdentityStoreMock()
// 0: test description
// test config:
// 1: group message type
// 2: group creator
// 3: message sender
// expected results:
// 4: sync request calls
let tests = [
[
"Send GroupRequestSyncMessage and add to pending messages, because group not found for regular group message (creator=sender)",
GroupTextMessage(),
"CREATOR1",
"CREATOR1",
1,
],
[
"Send GroupRequestSyncMessage and add to pending messages, because group not found for regular group message",
GroupTextMessage(),
"CREATOR1",
"JAMES007",
1,
],
// Spec group-sync-request:
// When receiving this message as a group control message (wrapped by
// [`group-creator-container`](ref:e2e.group-creator-container)):
//
// 1. Look up the group. If the group could not be found, discard the message
// and abort these steps.
[
"Do nothing, because no group found for incoming GroupRequestSyncMessage (creator=sender)",
GroupRequestSyncMessage(),
"CREATOR1",
"CREATOR1",
0,
],
[
"Do nothing, because no group found for incoming GroupRequestSyncMessage",
GroupRequestSyncMessage(),
"CREATOR1",
"JAMES007",
0,
],
// Spec group-leave:
// When receiving this message as a group control message (wrapped by
// [`group-member-container`](ref:e2e.group-member-container)):
//
// 1. If the sender is the creator of the group, discard the message and
// abort these steps.
[
"Do nothing, because no group found for incoming GroupLeaveMessage from creator",
GroupLeaveMessage(),
"CREATOR1",
"CREATOR1",
0,
],
// 3. If the group could not be found or is marked as _left_:
// 1. If the user is the creator of the group (as alleged by the
// message), discard the message and abort these steps.
[
"Do nothing, because no group found for incoming GroupLeaveMessage from member and i'm creator",
GroupLeaveMessage(),
myIdentityStoreMock.identity,
"JAMES007",
0,
],
// 3. If the group could not be found or is marked as _left_:
// 2. Send a [`group-sync-request`](ref:e2e.group-sync-request) to the
// group creator, discard the message and abort these steps.
[
"Send GroupRequestSyncMessage and add do pending messages, because no group found for regular GroupLeaveMessage from member",
GroupLeaveMessage(),
"CREATOR1",
"JAMES007",
1,
],
]
// run tests
for test in tests {
let testDescription = test[0] as! String
let userSettingsMock = UserSettingsMock()
let groupManagerMock: GroupManagerProtocolObjc = GroupManagerMock()
(test[1] as! AbstractGroupMessage).nonce = BytesUtility.generateMessageNonce()
(test[1] as! AbstractGroupMessage).groupID = BytesUtility.generateGroupID()
(test[1] as! AbstractGroupMessage).groupCreator = test[2] as? String
(test[1] as! AbstractGroupMessage).fromIdentity = test[3] as? String
let groupMessageProcessor = GroupMessageProcessor(
message: test[1] as! AbstractGroupMessage,
myIdentityStore: myIdentityStoreMock,
userSettings: userSettingsMock,
groupManager: groupManagerMock as! NSObject,
entityManager: testDatabase.entityManager,
nonceGuard: NonceGuardMock()
)
let expec = expectation(description: "Group message processed")
var resultDidHandleMessage: Bool?
var resultError: Error?
groupMessageProcessor.handleMessage(onCompletion: { didHandleMessage in
resultDidHandleMessage = didHandleMessage
expec.fulfill()
}, onError: { error in
resultError = error
expec.fulfill()
})
wait(for: [expec], timeout: 2)
XCTAssertNil(resultError, testDescription)
XCTAssertTrue(try XCTUnwrap(resultDidHandleMessage), testDescription)
XCTAssertEqual(
test[4] as! Int,
(groupManagerMock as! GroupManagerMock).sendSyncRequestCalls.count,
testDescription
)
}
}
func testHandleMessageGroupExistsIamMember() async throws {
let expectedMember01 = ThreemaIdentity("MEMBER01")
let expectedMember02 = ThreemaIdentity("MEMBER02")
let expectedMember03 = ThreemaIdentity("MEMBER03")
// prepare test db
for member in [expectedMember01, expectedMember02, expectedMember03] {
databasePreparer.createContact(identity: member.rawValue)
}
// 0: test description
// incoming message:
// 1: message
// 2: sender
// expected results:
// 3: request sync
// 4: left the group
// 5: sync add member
// 6: sync remove member
// 7: did handle message
let tests = [
[
"Group found all good, group chat message can be processed",
GroupTextMessage(),
expectedMember01.rawValue,
false,
false,
false,
false,
false,
],
// Spec receiving:
// The following steps are defined as _Common Group Receive Steps_ and will
// be applied in most cases for group messages:
//
// 4. If the sender is not a member of the group:
// 2. Send a [`group-sync-request`](ref:e2e.group-sync-request) to the
// group creator, discard the message and abort these steps.
[
"Send GroupRequestSyncMessage, because the member of this message is not in group and i'm NOT creator",
GroupTextMessage(),
"MEMBER04",
true,
false,
false,
false,
true,
],
[
"Do nothing, sender is creator of the group",
GroupRequestSyncMessage(),
expectedMember01.rawValue,
false,
false,
false,
false,
true,
],
[
"Do nothing, because received GroupRequestSyncMessage form member but i'm not creator",
GroupRequestSyncMessage(),
expectedMember02.rawValue,
false,
false,
false,
false,
true,
],
// Spec group-leave:
// When receiving this message as a group control message (wrapped by
// [`group-member-container`](ref:e2e.group-member-container)):
//
// 4. Remove the member from the local group.
[
"Member left the group",
GroupLeaveMessage(),
expectedMember02.rawValue,
false,
true,
false,
false,
true,
],
[
"Creator left the group",
GroupLeaveMessage(),
expectedMember01.rawValue,
false,
true,
false,
false,
true,
],
]
// run tests
for test in tests {
let testDescription = test[0] as! String
let myIdentityStoreMock = MyIdentityStoreMock()
let userSettingsMock = UserSettingsMock()
let taskManagerMock = TaskManagerMock()
let entityManager = testDatabase.entityManager
let groupManager = GroupManager(
myIdentityStore: myIdentityStoreMock,
contactStore: ContactStoreMock(callOnCompletion: true),
taskManager: taskManagerMock,
userSettings: UserSettingsMock(),
entityManager: entityManager,
groupPhotoSender: {
GroupPhotoSenderMock()
}
)
let expectedGroupIdentity = GroupIdentity(id: BytesUtility.generateGroupID(), creator: expectedMember01)
guard let group = try await groupManager.createOrUpdateDB(
for: expectedGroupIdentity,
members: Set<String>([
myIdentityStoreMock.identity,
expectedMember01.rawValue,
expectedMember02.rawValue,
expectedMember03.rawValue,
]),
systemMessageDate: Date(),
sourceCaller: .local
) else {
XCTFail("Creating group failed")
return
}
groupManager.deleteAllSyncRequestRecords()
let message: AbstractGroupMessage = (test[1] as! AbstractGroupMessage)
message.nonce = BytesUtility.generateRandomBytes(length: Int(kNonceLen))
message.groupID = expectedGroupIdentity.id
message.groupCreator = expectedGroupIdentity.creator.rawValue
message.fromIdentity = test[2] as? String
message.toIdentity = myIdentityStoreMock.identity
let groupMessageProcessor = GroupMessageProcessor(
message: message,
myIdentityStore: myIdentityStoreMock,
userSettings: userSettingsMock,
groupManager: groupManager,
entityManager: entityManager,
nonceGuard: NonceGuardMock()
)
let expec = expectation(description: "Group message processed")
var resultDidHandleMessage: Bool?
var resultError: Error?
groupMessageProcessor.handleMessage(onCompletion: { didHandleMessage in
resultDidHandleMessage = didHandleMessage
expec.fulfill()
}, onError: { error in
resultError = error
expec.fulfill()
})
await fulfillment(of: [expec])
DDLog.flushLog()
XCTAssertNil(resultError, testDescription)
XCTAssertEqual(
test[3] as! Bool,
ddLoggerMock
.exists(
message: "Group ID \(expectedGroupIdentity.id.hexString) (creator \(expectedGroupIdentity.creator.rawValue)) not found. Requesting sync from creator."
),
testDescription
)
XCTAssertEqual(
test[4] as! Bool,
ddLoggerMock
.exists(
message: "Member \(message.fromIdentity!) left the group \(message.groupID.hexString) \(message.groupCreator!)"
),
testDescription
)
XCTAssertEqual(
test[5] as! Bool,
(taskManagerMock.addedTasks.first as? TaskDefinitionSendGroupCreateMessage)?
.toMembers
.filter { $0 == (test[2] as! String) }.count ?? 0 == 1,
testDescription
)
XCTAssertEqual(
test[6] as! Bool,
(taskManagerMock.addedTasks.first as? TaskDefinitionSendGroupCreateMessage)?
.removedMembers?
.filter { $0 == "MEMBER04" }.count ?? 0 == 1,
testDescription
)
XCTAssertEqual(test[7] as! Bool, try XCTUnwrap(resultDidHandleMessage), testDescription)
}
}
func testHandleMessageGroupExistsIamCreator() async throws {
let myIdentityStoreMock = MyIdentityStoreMock()
let expectedMember01 = ThreemaIdentity("MEMBER01")
let expectedMember02 = ThreemaIdentity("MEMBER02")
let expectedMember03 = ThreemaIdentity("MEMBER03")
let expectedCreator = ThreemaIdentity(myIdentityStoreMock.identity)
// prepare test db
var members = Set<ContactEntity>()
for member in [expectedMember01, expectedMember02, expectedMember03] {
members.insert(databasePreparer.createContact(identity: member.rawValue))
}
// 0: test description
// test config:
// 1: message
// 2: sender
// expected results:
// 3: request sync
// 4: left the group
// 5: sync add member
// 6: did handle message
let tests = [
[
"Group found all good, process group message",
GroupTextMessage(),
expectedMember01.rawValue,
false,
false,
false,
false,
],
[
"Sync this group (remove member), because member is not in group and i'm creator",
GroupTextMessage(),
"MEMBER04",
false,
false,
true,
true,
],
[
"Do nothing, sender is creator of the group",
GroupRequestSyncMessage(),
expectedCreator.rawValue,
false,
false,
true,
true,
],
// Spec group-sync-request:
// When receiving this message as a group control message (wrapped by
// [`group-creator-container`](ref:e2e.group-creator-container)):
//
// 3. If the group is marked as _left_ or the sender is not a member of the
// group, send a [`group-setup`](ref:e2e.group-setup) with an empty
// members list back to the sender and abort these steps.
[
"Sync this group, because answer regular GroupRequestSyncMessage from member",
GroupRequestSyncMessage(),
"MEMBER04",
false,
false,
true,
true,
],
// 4. Send a [`group-setup`](ref:e2e.group-setup) message with the current
// group members, followed by a [`group-name`](ref:e2e.group-name)
// message to the sender.
[
"Sync this group, because answer regular GroupRequestSyncMessage from member",
GroupRequestSyncMessage(),
expectedMember02.rawValue,
false,
false,
true,
true,
],
// Spec group-leave:
// When receiving this message as a group control message (wrapped by
// [`group-member-container`](ref:e2e.group-member-container)):
//
// 4. Remove the member from the local group.
[
"Member left the group",
GroupLeaveMessage(),
expectedMember02.rawValue,
false,
true,
false,
true,
],
[
"Creator left the group",
GroupLeaveMessage(),
expectedCreator.rawValue,
false,
true,
false,
true,
],
]
// run tests
for test in tests {
let testDescription = test[0] as! String
let userSettingsMock = UserSettingsMock()
let entityManager = testDatabase.entityManager
let groupManagerMock = GroupManagerMock(myIdentityStoreMock)
let expectedGroupIdentity = GroupIdentity(id: BytesUtility.generateGroupID(), creator: expectedCreator)
let groupEntity = databasePreparer.save {
databasePreparer.createGroupEntity(
groupID: expectedGroupIdentity.id,
groupCreator: expectedGroupIdentity.creator.rawValue == myIdentityStoreMock
.identity ? nil : expectedGroupIdentity.creator.rawValue
)
}
let conversation = databasePreparer.save {
databasePreparer.createConversation(
groupID: expectedGroupIdentity.id,
typing: false,
unreadMessageCount: 0,
visibility: .default
) { conversation in
conversation.groupMyIdentity = myIdentityStoreMock.identity
conversation.members = members
}
}
let group = Group(
myIdentityStore: myIdentityStoreMock,
userSettings: UserSettingsMock(),
pushSettingManager: PushSettingManagerMock(),
groupEntity: groupEntity,
conversation: conversation,
lastSyncRequest: .now
)
groupManagerMock.getGroupReturns = [group]
let message: AbstractGroupMessage = (test[1] as! AbstractGroupMessage)
message.nonce = BytesUtility.generateRandomBytes(length: Int(kNonceLen))
message.groupID = expectedGroupIdentity.id
message.groupCreator = expectedGroupIdentity.creator.rawValue
message.fromIdentity = test[2] as? String
message.toIdentity = myIdentityStoreMock.identity
let groupMessageProcessor = GroupMessageProcessor(
message: message,
myIdentityStore: myIdentityStoreMock,
userSettings: userSettingsMock,
groupManager: groupManagerMock,
entityManager: entityManager,
nonceGuard: NonceGuardMock()
)
let expec = expectation(description: "Group message processed")
var resultDidHandleMessage: Bool?
var resultError: Error?
groupMessageProcessor.handleMessage(onCompletion: { didHandleMessage in
resultDidHandleMessage = didHandleMessage
expec.fulfill()
}, onError: { error in
resultError = error
expec.fulfill()
})
await fulfillment(of: [expec])
DDLog.flushLog()
XCTAssertNil(resultError, testDescription)
XCTAssertEqual(
test[3] as! Bool,
!groupManagerMock.sendSyncRequestCalls.isEmpty,
testDescription
)
XCTAssertEqual(
test[4] as! Bool,
!groupManagerMock.leaveDBCalls.isEmpty,
testDescription
)
XCTAssertEqual(
test[5] as! Bool,
!groupManagerMock.syncCalls.isEmpty,
testDescription
)
XCTAssertEqual(test[6] as! Bool, try XCTUnwrap(resultDidHandleMessage), testDescription)
}
}
func testHandleMessageGroupExistsILeftTheGroup() async throws {
let expectedMember01 = "MEMBER01"
let expectedMember02 = "MEMBER02"
let expectedMember03 = "MEMBER03"
for member in [expectedMember01, expectedMember02, expectedMember03] {
databasePreparer.createContact(identity: member)
}
let myIdentityStoreMock = MyIdentityStoreMock()
// 0: test description
// test config:
// 1: message
// 2: sender
// 3: group creator
// 4: group members before leaving
// expected results:
// 4: dissolve group
// 5: sent leave group
let tests = [
// Spec e2e:receiving
// Section: 3.
[
"Dissolve group to sender, because I'm creator but left the group",
GroupTextMessage(),
expectedMember01,
myIdentityStoreMock.identity,
[expectedMember01, expectedMember02, expectedMember03],
true,
false,
],
[
"Send leave group to sender, because I left the group",
GroupRequestSyncMessage(),
expectedMember02,
expectedMember01,
[myIdentityStoreMock.identity, expectedMember02, expectedMember03],
false,
true,
],
]
// run tests
for test in tests {
let testDescription = test[0] as! String
let userSettingsMock = UserSettingsMock()
let taskManagerMock = TaskManagerMock()
let entityManager = testDatabase.entityManager
let groupManager = GroupManager(
myIdentityStore: myIdentityStoreMock,
contactStore: ContactStoreMock(callOnCompletion: true),
taskManager: taskManagerMock,
userSettings: userSettingsMock,
entityManager: entityManager,
groupPhotoSender: {
GroupPhotoSenderMock()
}
)
let expectedGroupIdentity = GroupIdentity(
id: BytesUtility.generateGroupID(),
creator: ThreemaIdentity(test[3] as! String)
)
guard let group = try await groupManager.createOrUpdateDB(
for: expectedGroupIdentity,
members: Set<String>(test[4] as! [String]),
systemMessageDate: Date(),
sourceCaller: .local
) else {
XCTFail("Creating group failed")
return
}
XCTAssertEqual(.active, group.state)
groupManager.leaveDB(
groupID: expectedGroupIdentity.id,
creator: expectedGroupIdentity.creator.rawValue,
member: myIdentityStoreMock.identity,
systemMessageDate: Date()
)
XCTAssertEqual(.left, group.state)
let message: AbstractGroupMessage = (test[1] as! AbstractGroupMessage)
message.nonce = BytesUtility.generateRandomBytes(length: Int(kNonceLen))
message.groupID = expectedGroupIdentity.id
message.groupCreator = expectedGroupIdentity.creator.rawValue
message.fromIdentity = (test[2] as! String)
message.toIdentity = myIdentityStoreMock.identity
let groupMessageProcessor = GroupMessageProcessor(
message: message,
myIdentityStore: myIdentityStoreMock,
userSettings: userSettingsMock,
groupManager: groupManager,
entityManager: entityManager,
nonceGuard: NonceGuardMock()
)
let expec = expectation(description: "Group message processed")
var resultDidHandleMessage: Bool?
var resultError: Error?
groupMessageProcessor.handleMessage(onCompletion: { didHandleMessage in
resultDidHandleMessage = didHandleMessage
expec.fulfill()
}, onError: { error in
resultError = error
expec.fulfill()
})
await fulfillment(of: [expec])
DDLog.flushLog()
XCTAssertNil(resultError, testDescription)
XCTAssertTrue(try XCTUnwrap(resultDidHandleMessage), testDescription)
XCTAssertEqual(
test[5] as! Bool,
(taskManagerMock.addedTasks.first as? TaskDefinitionGroupDissolve)?
.toMembers.contains(where: { $0 == (test[2] as! String) }) ?? false,
testDescription
)
XCTAssertEqual(
test[6] as! Bool,
(taskManagerMock.addedTasks.first as? TaskDefinitionSendGroupLeaveMessage)?
.toMembers.contains(where: { $0 == (test[2] as! String) }) ?? false,
testDescription
)
}
}
}
¤ Dauer der Verarbeitung: 0.14 Sekunden
¤
*© Formatika GbR, Deutschland
|
|