// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation import Combine import GRDB import SessionUtil import SessionUtilitiesKit import Quick import Nimble @testable import SessionMessagingKit class MessageSenderGroupsSpec: QuickSpec { override class func spec() { // MARK: Configuration @TestState var dependencies: TestDependencies! = TestDependencies { dependencies in dependencies.dateNow = Date(timeIntervalSince1970: 1234567890) dependencies.forceSynchronous = true } @TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage( customWriter: try! DatabaseQueue(), migrationTargets: [ SNUtilitiesKit.self, SNMessagingKit.self ], using: dependencies, initialData: { db in try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db) try Identity(variant: .x25519PrivateKey, data: Data(hex: TestConstants.privateKey)).insert(db) try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db) try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db) } ) @TestState(singleton: .network, in: dependencies) var mockNetwork: MockNetwork! = MockNetwork() @TestState(singleton: .crypto, in: dependencies) var mockCrypto: MockCrypto! = MockCrypto( initialSetup: { crypto in crypto .when { crypto in crypto.generate(.ed25519KeyPair(seed: any(), using: any())) } .thenReturn( KeyPair( publicKey: Data.data( fromHex: "cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece" )!.bytes, secretKey: Data.data( fromHex: "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210" + "cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece" )!.bytes ) ) } ) @TestState(cache: .general, in: dependencies) var mockGeneralCache: MockGeneralCache! = MockGeneralCache( initialSetup: { cache in cache.when { $0.encodedPublicKey }.thenReturn("05\(TestConstants.publicKey)") } ) @TestState(cache: .sessionUtil, in: dependencies) var mockSessionUtilCache: MockSessionUtilCache! = MockSessionUtilCache( initialSetup: { cache in cache .when { $0.setConfig(for: any(), publicKey: any(), to: any()) } .thenReturn(()) } ) @TestState var disposables: [AnyCancellable]! = [] @TestState var error: Error? @TestState var thread: SessionThread? // MARK: - a MessageSender dealing with Groups describe("a MessageSender dealing with Groups") { // MARK: -- when creating a group context("when creating a group") { beforeEach { var userGroupsConf: UnsafeMutablePointer! var secretKey: [UInt8] = Array(Data(hex: TestConstants.edSecretKey)) _ = user_groups_init(&userGroupsConf, &secretKey, nil, 0, nil) let userGroupsConfig: SessionUtil.Config = .object(userGroupsConf) mockSessionUtilCache .when { $0.config(for: .userGroups, publicKey: any()) } .thenReturn(Atomic(userGroupsConfig)) } // MARK: ---- stores the thread in the db it("stores the thread in the db") { MessageSender .createGroup( name: "Test", displayPicture: nil, members: [ ("051111111111111111111111111111111111111111111111111111111111111111", nil) ], using: dependencies ) .handleEvents(receiveOutput: { result in thread = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) expect(error).to(beNil()) expect(thread).toNot(beNil()) let dbValue: SessionThread? = mockStorage.read { db in try SessionThread.fetchOne(db) } expect(dbValue).to(equal(thread)) expect(dbValue?.id) .to(equal("03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece")) expect(dbValue?.variant).to(equal(.group)) expect(dbValue?.creationDateTimestamp).to(equal(1234567890)) expect(dbValue?.shouldBeVisible).to(beTrue()) expect(dbValue?.notificationSound).to(beNil()) expect(dbValue?.mutedUntilTimestamp).to(beNil()) expect(dbValue?.onlyNotifyForMentions).to(beFalse()) expect(dbValue?.pinnedPriority).to(equal(0)) } // MARK: ---- stores the group in the db it("stores the group in the db") { MessageSender .createGroup( name: "TestGroupName", displayPicture: nil, members: [ ("051111111111111111111111111111111111111111111111111111111111111111", nil) ], using: dependencies ) .handleEvents(receiveOutput: { result in thread = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) expect(error).to(beNil()) expect(thread).toNot(beNil()) let dbValue: ClosedGroup? = mockStorage.read { db in try ClosedGroup.fetchOne(db) } expect(dbValue?.id) .to(equal("03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece")) expect(dbValue?.name).to(equal("TestGroupName")) expect(dbValue?.formationTimestamp).to(equal(1234567890)) expect(dbValue?.displayPictureUrl).to(beNil()) expect(dbValue?.displayPictureFilename).to(beNil()) expect(dbValue?.displayPictureEncryptionKey).to(beNil()) expect(dbValue?.lastDisplayPictureUpdate).to(equal(1234567890)) expect(dbValue?.groupIdentityPrivateKey?.toHexString()) .to(equal( "0123456789abcdef0123456789abcdeffedcba9876543210fedcba9876543210" + "cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece" )) expect(dbValue?.authData).to(beNil()) expect(dbValue?.invited).to(beFalse()) } // MARK: ---- stores the group members in the db it("stores the group members in the db") { MessageSender .createGroup( name: "TestGroupName", displayPicture: nil, members: [ ("051111111111111111111111111111111111111111111111111111111111111111", nil) ], using: dependencies ) .handleEvents(receiveOutput: { result in thread = result }) .mapError { error.setting(to: $0) } .sinkAndStore(in: &disposables) expect(error).to(beNil()) expect(thread).toNot(beNil()) expect(mockStorage.read { db in try GroupMember.fetchSet(db) }) .to(equal([ GroupMember( groupId: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece", profileId: "051111111111111111111111111111111111111111111111111111111111111111", role: .standard, isHidden: false ), GroupMember( groupId: "03cbd569f56fb13ea95a3f0c05c331cc24139c0090feb412069dc49fab34406ece", profileId: "05\(TestConstants.publicKey)", role: .admin, isHidden: false ) ])) } } } } }