// // Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation import LibSignalClient import XCTest @testable import SignalServiceKit private extension Aci { static let aci1 = Aci.randomForTesting() static let aci2 = Aci.randomForTesting() static let aci3 = Aci.randomForTesting() } class GroupModelsTest: SSKBaseTest { func testGroupMembershipChangingFullMembers() { var builder1 = GroupMembership.Builder() builder1.addFullMember(.aci1, role: .normal) let membership1 = builder1.build() var builder2 = GroupMembership.Builder() builder2.addFullMember(.aci1, role: .administrator) let membership2 = builder2.build() var builder3 = GroupMembership.Builder() builder3.addFullMember(.aci1, role: .normal) builder3.addFullMember(.aci2, role: .normal) let membership3 = builder3.build() var builder4 = GroupMembership.Builder() builder4.addFullMember(.aci1, role: .normal) let membership4 = builder4.build() XCTAssertEqual(membership1, membership4) XCTAssertNotEqual(membership1, membership2) XCTAssertNotEqual(membership1, membership3) XCTAssertNotEqual(membership2, membership3) } func testGroupMembershipChangingDidJoinFromInviteLink() { var builder1 = GroupMembership.Builder() builder1.addFullMember(.aci1, role: .normal, didJoinFromInviteLink: true) let membership1 = builder1.build() var builder2 = GroupMembership.Builder() builder2.addFullMember(.aci1, role: .normal, didJoinFromInviteLink: false) let membership2 = builder2.build() XCTAssertEqual(membership1, membership2) } func testGroupMembershipChangingRequestingMembers() { var builder1 = GroupMembership.Builder() builder1.addFullMember(.aci1, role: .normal) let membership1 = builder1.build() var builder2 = GroupMembership.Builder() builder2.addFullMember(.aci1, role: .normal) builder2.addRequestingMember(Aci.aci2) let membership2 = builder2.build() var builder3 = GroupMembership.Builder() builder3.addFullMember(.aci1, role: .normal) builder3.addRequestingMember(Aci.aci3) let membership3 = builder3.build() var builder4 = GroupMembership.Builder() builder4.addFullMember(.aci1, role: .normal) builder4.addRequestingMember(Aci.aci2) let membership4 = builder4.build() XCTAssertFalse(membership1 == membership2) XCTAssertFalse(membership1 == membership3) XCTAssertFalse(membership2 == membership3) XCTAssertTrue(membership2 == membership4) } func testGroupMembershipChangingBannedMembers() { var builder1 = GroupMembership.Builder() builder1.addFullMember(.aci1, role: .normal) let membership1 = builder1.build() var builder2 = membership1.asBuilder builder2.addFullMember(.aci1, role: .normal) builder2.addBannedMember(.aci2, bannedAtTimestamp: 3) let membership2 = builder2.build() var builder3 = GroupMembership.Builder() builder3.addFullMember(.aci1, role: .normal) builder3.addBannedMember(.aci2, bannedAtTimestamp: 12) let membership3 = builder3.build() var builder4 = GroupMembership.Builder() builder4.addFullMember(.aci1, role: .normal) builder4.addBannedMember(.aci2, bannedAtTimestamp: 3) builder4.addBannedMember(.aci3, bannedAtTimestamp: 4) let membership4 = builder4.build() var builder5 = GroupMembership.Builder() builder5.addFullMember(.aci1, role: .normal) builder5.addBannedMember(.aci2, bannedAtTimestamp: 3) builder5.addBannedMember(.aci3, bannedAtTimestamp: 4) let membership5 = builder5.build() XCTAssertNotEqual(membership1, membership2) XCTAssertNotEqual(membership2, membership3) XCTAssertNotEqual(membership3, membership4) XCTAssertEqual(membership4, membership5) } func testTSGroupModelBackwardsCompatibleDeserialization() throws { let groupIdLength = 16 // Taken from kGroupIdLength at the time of archiving. let expectedGroupId = Data(repeating: 8, count: groupIdLength) // hexstring generated by archiving: // let model = TSGroupModel( // groupId: expectedGroupId, // name: "name", // avatarData: nil, // members: [], // addedBy: nil // ) // try NSKeyedArchiver.archivedData(withRootObject: model, requiringSecureCoding: false).hexadecimalString let hexData = "62706c6973743030d4010203040506070a582476657273696f6e592461726368697665725424746f7058246f626a6563747312000186a05f100f4e534b657965644172636869766572d1080954726f6f748001a90b0c191a1b1c1d212755246e756c6cd60d0e0f1011121314151617185767726f757049645624636c6173735967726f75704e616d655c67726f75704d656d626572735f100f4d544c4d6f64656c56657273696f6e5f101767726f75704d6f64656c536368656d6156657273696f6e80058008800480068002800310001002546e616d654f101008080808080808080808080808080808d21e0e1f205a4e532e6f626a65637473a08007d2222324255a24636c6173736e616d655824636c6173736573574e534172726179a22426584e534f626a656374d2222328295c545347726f75704d6f64656ca32a2b265c545347726f75704d6f64656c584d544c4d6f64656c00080011001a00240029003200370049004c00510053005d006300700078007f0089009600a800c200c400c600c800ca00cc00ce00d000d200d700ea00ef00fa00fb00fd0102010d0116011e0121012a012f013c0140014d0000000000000201000000000000002c00000000000000000000000000000156" let encodedData = Data.data(fromHex: hexData)! let groupModel = try XCTUnwrap(NSKeyedUnarchiver.unarchivedObject(ofClass: TSGroupModel.self, from: encodedData, requiringSecureCoding: false)) XCTAssertEqual(groupModel.groupId, expectedGroupId) } func testTSGroupModelV2BackwardsCompatibleDeserialization() throws { /// The value of ``kGroupIdLengthV2`` at the time of serialization. let groupIdLength = 32 /// Generates a hex string of a serialized ``TSGroupModel`` with the /// given parameters, and the rest hardcoded. /// /// Use this to generate new models for back-compat deserialization /// testing as necessary. func generateHexString( groupIdRepeats: UInt8, isJoinRequestPlaceholder: Bool ) -> String { let model = TSGroupModelV2( groupId: Data(repeating: groupIdRepeats, count: groupIdLength), name: "name", descriptionText: "description", avatarData: nil, groupMembership: .empty, groupAccess: .allAccess, revision: 1, secretParamsData: Data(repeating: 8, count: 10), avatarUrlPath: nil, inviteLinkPassword: nil, isAnnouncementsOnly: false, isJoinRequestPlaceholder: isJoinRequestPlaceholder, wasJustMigrated: false, didJustAddSelfViaGroupLink: false, addedByAddress: nil, droppedMembers: [] ) return try! NSKeyedArchiver.archivedData( withRootObject: model, requiringSecureCoding: false ).hexadecimalString } let testCases: [(encodedData: Data, expectedGroupId: Data, isJoinRequestPlaceholder: Bool)] = [ ( encodedData: .data( fromHex: "62706c6973743030d4010203040506070a582476657273696f6e592461726368697665725424746f7058246f626a6563747312000186a05f100f4e534b657965644172636869766572d1080954726f6f748001af10140b0c2d2e2f30313a3b3c44454d4e54585c5f626355246e756c6cdf10100d0e0f101112131415161718191a1b1c1d1e1f20211d2324251d2728292a1d1d5f10197761734a7573744372656174656442794c6f63616c557365725f1010736563726574506172616d73446174615f100f4d544c4d6f64656c56657273696f6e566163636573735f100f6465736372697074696f6e546578745f10126973506c616365686f6c6465724d6f64656c587265766973696f6e5a6d656d626572736869705624636c6173735f101a6469644a75737441646453656c6656696147726f75704c696e6b5e64726f707065644d656d626572735f101767726f75704d6f64656c536368656d6156657273696f6e5967726f75704e616d655767726f757049645f10136973416e6e6f756e63656d656e74734f6e6c795f100f7761734a7573744d696772617465648005800380028006800a80058008800b8013800580108007801280048005800510004a080808080808080808084f1020080808080808080808080808080808080808080808080808080808080808080808d515320f333435231f28285f101161646446726f6d496e766974654c696e6b576d656d626572735a617474726962757465738009800880028007800710021001d23d3e3f405a24636c6173736e616d655824636c61737365735f101c5369676e616c536572766963654b69742e47726f7570416363657373a34142435f101c5369676e616c536572766963654b69742e47726f7570416363657373584d544c4d6f64656c584e534f626a6563745b6465736372697074696f6ed415464748494a4a4c5f1010696e76616c6964496e766974654d61705d62616e6e65644d656d626572735c6d656d626572537461746573800f800d800d800c425b5dd34f5015515253574e532e6b6579735a4e532e6f626a65637473a0a0800ed23d3e55565c4e5344696374696f6e617279a257435c4e5344696374696f6e617279d23d3e595a5f10205369676e616c536572766963654b69742e47726f75704d656d62657273686970a35b42435f10205369676e616c536572766963654b69742e47726f75704d656d62657273686970d250155d5ea08011d23d3e6061574e534172726179a26043546e616d65d23d3e64655f101f5369676e616c536572766963654b69742e545347726f75704d6f64656c5632a4666742435f101f5369676e616c536572766963654b69742e545347726f75704d6f64656c56325c545347726f75704d6f64656c00080011001a00240029003200370049004c00510053006a0070009300af00c200d400db00ed0102010b0116011d013a01490163016d0175018b019d019f01a101a301a501a701a901ab01ad01af01b101b301b501b701b901bb01bd01bf01ca01ed01ee01f9020d021502200222022402260228022a022c022e0233023e02470266026a02890292029b02a702b002c302d102de02e002e202e402e602e902f002f80303030403050307030c0319031c0329032e035103550378037d037e03800385038d03900395039a03bc03c103e300000000000002010000000000000068000000000000000000000000000003f0" )!, expectedGroupId: Data(repeating: 8, count: groupIdLength), isJoinRequestPlaceholder: false ), ( encodedData: .data( fromHex: "62706c6973743030d4010203040506070a582476657273696f6e592461726368697665725424746f7058246f626a6563747312000186a05f100f4e534b657965644172636869766572d1080954726f6f748001af10140b0c2b2c2d2e37383941424a4b5155595c5f606155246e756c6cdf100f0d0e0f101112131415161718191a1b1c1d1e1f20212223202526272820205f1010736563726574506172616d73446174615f100f4d544c4d6f64656c56657273696f6e566163636573735f100f6465736372697074696f6e546578745f10126973506c616365686f6c6465724d6f64656c587265766973696f6e5a6d656d626572736869705624636c6173735f101a6469644a75737441646453656c6656696147726f75704c696e6b5e64726f707065644d656d626572735f101767726f75704d6f64656c536368656d6156657273696f6e5967726f75704e616d655767726f757049645f10136973416e6e6f756e63656d656e74734f6e6c795f100f7761734a7573744d69677261746564800380028005800980048007800a80138004800f8006801180128004800410004a0808080808080808080808d5142f0e303132211d26265f101161646446726f6d496e766974654c696e6b576d656d626572735a617474726962757465738008800780028006800610021001d23a3b3c3d5a24636c6173736e616d655824636c61737365735f101c5369676e616c536572766963654b69742e47726f7570416363657373a33e3f405f101c5369676e616c536572766963654b69742e47726f7570416363657373584d544c4d6f64656c584e534f626a6563745b6465736372697074696f6ed414434445464747495f1010696e76616c6964496e766974654d61705d62616e6e65644d656d626572735c6d656d626572537461746573800e800c800c800b425b5dd34c4d144e4f50574e532e6b6579735a4e532e6f626a65637473a0a0800dd23a3b52535c4e5344696374696f6e617279a254405c4e5344696374696f6e617279d23a3b56575f10205369676e616c536572766963654b69742e47726f75704d656d62657273686970a3583f405f10205369676e616c536572766963654b69742e47726f75704d656d62657273686970d24d145a5ba08010d23a3b5d5e574e534172726179a25d40546e616d654f10200808080808080808080808080808080808080808080808080808080808080808d23a3b62635f101f5369676e616c536572766963654b69742e545347726f75704d6f64656c5632a464653f405f101f5369676e616c536572766963654b69742e545347726f75704d6f64656c56325c545347726f75704d6f64656c00080011001a00240029003200370049004c00510053006a0070009100a400b600bd00cf00e400ed00f800ff011c012b0145014f0157016d017f01810183018501870189018b018d018f01910193019501970199019b019d019f01aa01ab01b601ca01d201dd01df01e101e301e501e701e901eb01f001fb0204022302270246024f02580264026d0280028e029b029d029f02a102a302a602ad02b502c002c102c202c402c902d602d902e602eb030e03120335033a033b033d0342034a034d03520375037a039c03a103c300000000000002010000000000000066000000000000000000000000000003d0" )!, expectedGroupId: Data(repeating: 8, count: groupIdLength), isJoinRequestPlaceholder: false ), ( encodedData: .data( fromHex: "62706c6973743030d4010203040506070a582476657273696f6e592461726368697665725424746f7058246f626a6563747312000186a05f100f4e534b657965644172636869766572d1080954726f6f748001af10150b0c2b2c2d2e3738394142434b4c52565a5d60616255246e756c6cdf100f0d0e0f101112131415161718191a1b1c1d1e1f20212223242526272824245f1010736563726574506172616d73446174615f100f4d544c4d6f64656c56657273696f6e566163636573735f100f6465736372697074696f6e546578745f10126973506c616365686f6c6465724d6f64656c587265766973696f6e5a6d656d626572736869705624636c6173735f101a6469644a75737441646453656c6656696147726f75704c696e6b5e64726f707065644d656d626572735f101767726f75704d6f64656c536368656d6156657273696f6e5967726f75704e616d655767726f757049645f10136973416e6e6f756e63656d656e74734f6e6c795f100f7761734a7573744d696772617465648003800280058009800a8007800b8014800480108006801280138004800410004a0808080808080808080808d5142f0e303132211d26265f101161646446726f6d496e766974654c696e6b576d656d626572735a617474726962757465738008800780028006800610021001d23a3b3c3d5a24636c6173736e616d655824636c61737365735f101c5369676e616c536572766963654b69742e47726f7570416363657373a33e3f405f101c5369676e616c536572766963654b69742e47726f7570416363657373584d544c4d6f64656c584e534f626a6563745b6465736372697074696f6e09d4144445464748484a5f1010696e76616c6964496e766974654d61705d62616e6e65644d656d626572735c6d656d626572537461746573800f800d800d800c425b5dd34d4e144f5051574e532e6b6579735a4e532e6f626a65637473a0a0800ed23a3b53545c4e5344696374696f6e617279a255405c4e5344696374696f6e617279d23a3b57585f10205369676e616c536572766963654b69742e47726f75704d656d62657273686970a3593f405f10205369676e616c536572766963654b69742e47726f75704d656d62657273686970d24e145b5ca08011d23a3b5e5f574e534172726179a25e40546e616d654f10200707070707070707070707070707070707070707070707070707070707070707d23a3b63645f101f5369676e616c536572766963654b69742e545347726f75704d6f64656c5632a465663f405f101f5369676e616c536572766963654b69742e545347726f75704d6f64656c56325c545347726f75704d6f64656c00080011001a00240029003200370049004c00510053006b0071009200a500b700be00d000e500ee00f90100011d012c014601500158016e01800182018401860188018a018c018e01900192019401960198019a019c019e01a001ab01ac01b701cb01d301de01e001e201e401e601e801ea01ec01f101fc02050224022802470250025902650266026f02820290029d029f02a102a302a502a802af02b702c202c302c402c602cb02d802db02e802ed031003140337033c033d033f0344034c034f03540377037c039e03a303c500000000000002010000000000000067000000000000000000000000000003d2" )!, expectedGroupId: Data(repeating: 7, count: groupIdLength), isJoinRequestPlaceholder: true ) ] for (encodedData, expectedGroupId, isJoinRequestPlaceholder) in testCases { let groupModel = try XCTUnwrap(NSKeyedUnarchiver.unarchivedObject( ofClass: TSGroupModelV2.self, from: encodedData, requiringSecureCoding: false )) XCTAssertEqual(groupModel.groupId, expectedGroupId) XCTAssertEqual(groupModel.groupName, "name") XCTAssertEqual(groupModel.descriptionText, "description") XCTAssertNil(groupModel.avatarData) XCTAssertEqual(groupModel.groupMembership, .empty) XCTAssertEqual(groupModel.access, .allAccess) XCTAssertEqual(groupModel.revision, 1) XCTAssertEqual(groupModel.secretParamsData, Data(repeating: 8, count: 10)) XCTAssertNil(groupModel.avatarUrlPath) XCTAssertNil(groupModel.inviteLinkPassword) XCTAssertFalse(groupModel.isAnnouncementsOnly) XCTAssertEqual(groupModel.isJoinRequestPlaceholder, isJoinRequestPlaceholder) XCTAssertFalse(groupModel.wasJustMigrated) XCTAssertFalse(groupModel.didJustAddSelfViaGroupLink) XCTAssertNil(groupModel.addedByAddress) XCTAssertEqual(groupModel.droppedMembers, []) } } }