@ -2,18 +2,21 @@ import CryptoSwift
import PromiseKit
@ testable import SignalServiceKit
import XCTest
import Curve25519Kit
class FriendRequestProtocolTests : XCTestCase {
private var storage : OWSPrimaryStorage { OWSPrimaryStorage . shared ( ) }
private var messageSender : OWSFakeMessageSender { MockSSKEnvironment . shared . messageSender as ! OWSFakeMessageSender }
// MARK: - S e t u p
override func setUp ( ) {
super . setUp ( )
// A c t i v a t e t h e m o c k e n v i r o n m e n t
ClearCurrentAppContextForTests ( )
SetCurrentAppContext ( TestAppContext ( ) )
MockSSKEnvironment . activate ( )
// R e g i s t e r a m o c k u s e r
let identityManager = OWSIdentityManager . shared ( )
let seed = Randomness . generateRandomBytes ( 16 ) !
let keyPair = Curve25519 . generateKeyPair ( fromSeed : seed + seed )
@ -23,46 +26,613 @@ class FriendRequestProtocolTests : XCTestCase {
TSAccountManager . sharedInstance ( ) . didRegister ( )
}
func testMultiDeviceFriendRequestAcceptance ( ) {
// W h e n A l i c e a c c e p t s B o b ' s f r i e n d r e q u e s t , s h e s h o u l d a c c e p t a l l o u t s t a n d i n g f r i e n d r e q u e s t s w i t h B o b ' s
// l i n k e d d e v i c e s a n d t r y t o e s t a b l i s h s e s s i o n s w i t h t h e s u b s e t o f B o b ' s d e v i c e s t h a t h a v e n ' t s e n t a f r i e n d r e q u e s t .
func getDevice ( ) -> DeviceLink . Device ? {
guard let publicKey = Data . getSecureRandomData ( ofSize : 64 ) else { return nil }
let hexEncodedPublicKey = " 05 " + publicKey . toHexString ( )
guard let signature = Data . getSecureRandomData ( ofSize : 64 ) else { return nil }
return DeviceLink . Device ( hexEncodedPublicKey : hexEncodedPublicKey , signature : signature )
// MARK: - H e l p e r s
func isFriendRequestStatus ( oneOf values : [ LKFriendRequestStatus ] , for hexEncodedPublicKey : String , transaction : YapDatabaseReadWriteTransaction ) -> Bool {
let status = storage . getFriendRequestStatus ( for : hexEncodedPublicKey , transaction : transaction )
return values . contains ( status )
}
func isFriendRequestStatus ( _ value : LKFriendRequestStatus , for hexEncodedPublicKey : String , transaction : YapDatabaseReadWriteTransaction ) -> Bool {
return isFriendRequestStatus ( oneOf : [ value ] , for : hexEncodedPublicKey , transaction : transaction )
}
func generateHexEncodedPublicKey ( ) -> String {
return Curve25519 . generateKeyPair ( ) . hexEncodedPublicKey
}
func getDevice ( for hexEncodedPublicKey : String ) -> DeviceLink . Device ? {
guard let signature = Data . getSecureRandomData ( ofSize : 64 ) else { return nil }
return DeviceLink . Device ( hexEncodedPublicKey : hexEncodedPublicKey , signature : signature )
}
func createContactThread ( for hexEncodedPublicKey : String ) -> TSContactThread {
var result : TSContactThread !
storage . dbReadWriteConnection . readWrite { transaction in
result = TSContactThread . getOrCreateThread ( withContactId : hexEncodedPublicKey , transaction : transaction )
}
return result
}
func createGroupThread ( groupType : GroupType ) -> TSGroupThread ? {
let hexEncodedGroupID = Randomness . generateRandomBytes ( kGroupIdLength ) ! . toHexString ( )
let groupID : Data
switch groupType {
case . closedGroup : groupID = LKGroupUtilities . getEncodedClosedGroupIDAsData ( hexEncodedGroupID )
case . openGroup : groupID = LKGroupUtilities . getEncodedOpenGroupIDAsData ( hexEncodedGroupID )
case . rssFeed : groupID = LKGroupUtilities . getEncodedRSSFeedIDAsData ( hexEncodedGroupID )
default : return nil
}
return TSGroupThread . getOrCreateThread ( withGroupId : groupID , groupType : groupType )
}
// MARK: - s h o u l d I n p u t B a r B e E n a b l e d
func test_shouldInputBarBeEnabledReturnsTrueOnGroupThread ( ) {
let allGroupTypes : [ GroupType ] = [ . closedGroup , . openGroup , . rssFeed ]
for groupType in allGroupTypes {
guard let groupThread = createGroupThread ( groupType : groupType ) else { return XCTFail ( ) }
XCTAssertTrue ( FriendRequestProtocol . shouldInputBarBeEnabled ( for : groupThread ) )
}
}
func test_shouldInputBarBeEnabledReturnsTrueOnNoteToSelf ( ) {
guard let master = OWSIdentityManager . shared ( ) . identityKeyPair ( ) ? . hexEncodedPublicKey else { return XCTFail ( ) }
let slave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
let deviceLink = DeviceLink ( between : masterDevice , and : slaveDevice )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( deviceLink , in : transaction )
self . storage . setFriendRequestStatus ( . requestSent , for : master , transaction : transaction )
self . storage . setFriendRequestStatus ( . requestSent , for : slave , transaction : transaction )
}
let masterThread = createContactThread ( for : master )
let slaveThread = createContactThread ( for : slave )
XCTAssertTrue ( FriendRequestProtocol . shouldInputBarBeEnabled ( for : masterThread ) )
XCTAssertTrue ( FriendRequestProtocol . shouldInputBarBeEnabled ( for : slaveThread ) )
}
func test_shouldInputBarBeEnabledReturnsTrueWhenStatusIsNotPending ( ) {
let statuses : [ LKFriendRequestStatus ] = [ . none , . requestExpired , . friends ]
let device = generateHexEncodedPublicKey ( )
let thread = createContactThread ( for : device )
for status in statuses {
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setFriendRequestStatus ( status , for : device , transaction : transaction )
}
XCTAssertTrue ( FriendRequestProtocol . shouldInputBarBeEnabled ( for : thread ) )
}
}
func test_shouldInputBarBeEnabledReturnsFalseWhenStatusIsPending ( ) {
let statuses : [ LKFriendRequestStatus ] = [ . requestSending , . requestSent , . requestReceived ]
let device = generateHexEncodedPublicKey ( )
let thread = createContactThread ( for : device )
for status in statuses {
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setFriendRequestStatus ( status , for : device , transaction : transaction )
}
XCTAssertFalse ( FriendRequestProtocol . shouldInputBarBeEnabled ( for : thread ) )
}
}
func test_shouldInputBarBeEnabledReturnsTrueWhenFriendsWithOneLinkedDevice ( ) {
let master = generateHexEncodedPublicKey ( )
let slave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
let deviceLink = DeviceLink ( between : masterDevice , and : slaveDevice )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( deviceLink , in : transaction )
self . storage . setFriendRequestStatus ( . friends , for : master , transaction : transaction )
self . storage . setFriendRequestStatus ( . requestSent , for : slave , transaction : transaction )
}
let masterThread = createContactThread ( for : master )
let slaveThread = createContactThread ( for : slave )
XCTAssertTrue ( FriendRequestProtocol . shouldInputBarBeEnabled ( for : masterThread ) )
XCTAssertTrue ( FriendRequestProtocol . shouldInputBarBeEnabled ( for : slaveThread ) )
}
func test_shouldInputBarBeEnabledReturnsFalseWhenOneLinkedDeviceIsPending ( ) {
let master = generateHexEncodedPublicKey ( )
let slave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
let deviceLink = DeviceLink ( between : masterDevice , and : slaveDevice )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( deviceLink , in : transaction )
self . storage . setFriendRequestStatus ( . none , for : master , transaction : transaction )
}
let masterThread = createContactThread ( for : master )
let slaveThread = createContactThread ( for : slave )
let statuses : [ LKFriendRequestStatus ] = [ . requestSending , . requestSent , . requestReceived ]
for status in statuses {
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setFriendRequestStatus ( status , for : slave , transaction : transaction )
}
XCTAssertFalse ( FriendRequestProtocol . shouldInputBarBeEnabled ( for : masterThread ) )
XCTAssertFalse ( FriendRequestProtocol . shouldInputBarBeEnabled ( for : slaveThread ) )
}
}
func test_shouldInputBarBeEnabledReturnsTrueWhenAllLinkedDevicesAreNotPendingAndNotFriends ( ) {
let master = generateHexEncodedPublicKey ( )
let slave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
let deviceLink = DeviceLink ( between : masterDevice , and : slaveDevice )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( deviceLink , in : transaction )
self . storage . setFriendRequestStatus ( . none , for : master , transaction : transaction )
self . storage . setFriendRequestStatus ( . none , for : slave , transaction : transaction )
}
let masterThread = createContactThread ( for : master )
let slaveThread = createContactThread ( for : slave )
let statuses : [ LKFriendRequestStatus ] = [ . requestExpired , . none ]
for status in statuses {
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setFriendRequestStatus ( status , for : slave , transaction : transaction )
}
XCTAssertTrue ( FriendRequestProtocol . shouldInputBarBeEnabled ( for : masterThread ) )
XCTAssertTrue ( FriendRequestProtocol . shouldInputBarBeEnabled ( for : slaveThread ) )
}
}
func test_shouldInputBarEnabledShouldStillWorkIfLinkedDeviceThreadDoesNotExist ( ) {
let master = generateHexEncodedPublicKey ( )
let slave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
let deviceLink = DeviceLink ( between : masterDevice , and : slaveDevice )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( deviceLink , in : transaction )
self . storage . setFriendRequestStatus ( . requestSent , for : master , transaction : transaction )
self . storage . setFriendRequestStatus ( . friends , for : slave , transaction : transaction )
}
let masterThread = createContactThread ( for : master )
XCTAssertTrue ( FriendRequestProtocol . shouldInputBarBeEnabled ( for : masterThread ) )
}
// MARK: - s h o u l d A t t a c h m e n t B u t t o n B e E n a b l e d
func test_shouldAttachmentButtonBeEnabledReturnsTrueOnGroupThread ( ) {
let allGroupTypes : [ GroupType ] = [ . closedGroup , . openGroup , . rssFeed ]
for groupType in allGroupTypes {
guard let groupThread = createGroupThread ( groupType : groupType ) else { return XCTFail ( ) }
XCTAssertTrue ( FriendRequestProtocol . shouldAttachmentButtonBeEnabled ( for : groupThread ) )
}
}
func test_shouldAttachmentButtonBeEnabledReturnsTrueOnNoteToSelf ( ) {
guard let master = OWSIdentityManager . shared ( ) . identityKeyPair ( ) ? . hexEncodedPublicKey else { return XCTFail ( ) }
let slave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
let deviceLink = DeviceLink ( between : masterDevice , and : slaveDevice )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( deviceLink , in : transaction )
self . storage . setFriendRequestStatus ( . requestSent , for : master , transaction : transaction )
self . storage . setFriendRequestStatus ( . requestSent , for : slave , transaction : transaction )
}
let masterThread = createContactThread ( for : master )
let slaveThread = createContactThread ( for : slave )
XCTAssertTrue ( FriendRequestProtocol . shouldAttachmentButtonBeEnabled ( for : masterThread ) )
XCTAssertTrue ( FriendRequestProtocol . shouldAttachmentButtonBeEnabled ( for : slaveThread ) )
}
func test_shouldAttachmentButtonBeEnabledReturnsTrueWhenFriends ( ) {
let device = generateHexEncodedPublicKey ( )
let thread = createContactThread ( for : device )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setFriendRequestStatus ( . friends , for : device , transaction : transaction )
}
XCTAssertTrue ( FriendRequestProtocol . shouldAttachmentButtonBeEnabled ( for : thread ) )
}
func test_shouldAttachmentButtonBeEnabledReturnsFalseWhenNotFriends ( ) {
let statuses : [ LKFriendRequestStatus ] = [ . requestSending , . requestSent , . requestReceived , . none , . requestExpired ]
let device = generateHexEncodedPublicKey ( )
let thread = createContactThread ( for : device )
for status in statuses {
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setFriendRequestStatus ( status , for : device , transaction : transaction )
}
XCTAssertFalse ( FriendRequestProtocol . shouldAttachmentButtonBeEnabled ( for : thread ) )
}
}
func test_shouldAttachmentButtonBeEnabledReturnsTrueWhenFriendsWithOneLinkedDevice ( ) {
let master = generateHexEncodedPublicKey ( )
let slave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
let deviceLink = DeviceLink ( between : masterDevice , and : slaveDevice )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( deviceLink , in : transaction )
self . storage . setFriendRequestStatus ( . friends , for : master , transaction : transaction )
self . storage . setFriendRequestStatus ( . requestSent , for : slave , transaction : transaction )
}
let masterThread = createContactThread ( for : master )
let slaveThread = createContactThread ( for : slave )
XCTAssertTrue ( FriendRequestProtocol . shouldAttachmentButtonBeEnabled ( for : masterThread ) )
XCTAssertTrue ( FriendRequestProtocol . shouldAttachmentButtonBeEnabled ( for : slaveThread ) )
}
func test_shouldAttachmentButtonBeEnabledShouldStillWorkIfLinkedDeviceThreadDoesNotExist ( ) {
let master = generateHexEncodedPublicKey ( )
let slave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
let deviceLink = DeviceLink ( between : masterDevice , and : slaveDevice )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( deviceLink , in : transaction )
self . storage . setFriendRequestStatus ( . none , for : master , transaction : transaction )
self . storage . setFriendRequestStatus ( . friends , for : slave , transaction : transaction )
}
let masterThread = createContactThread ( for : master )
XCTAssertTrue ( FriendRequestProtocol . shouldAttachmentButtonBeEnabled ( for : masterThread ) )
}
// MARK: - g e t F r i e n d R e q u e s t U I S t a t e
func test_getFriendRequestUIStateShouldReturnNoneForGroupThreads ( ) {
let allGroupTypes : [ GroupType ] = [ . closedGroup , . openGroup , . rssFeed ]
for groupType in allGroupTypes {
guard let groupThread = createGroupThread ( groupType : groupType ) else { return XCTFail ( ) }
XCTAssertTrue ( FriendRequestProtocol . getFriendRequestUIStatus ( for : groupThread ) = = . none )
}
}
func test_getFriendRequestUIStateShouldReturnNoneOnNoteToSelf ( ) {
guard let master = OWSIdentityManager . shared ( ) . identityKeyPair ( ) ? . hexEncodedPublicKey else { return XCTFail ( ) }
let slave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
let deviceLink = DeviceLink ( between : masterDevice , and : slaveDevice )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( deviceLink , in : transaction )
self . storage . setFriendRequestStatus ( . friends , for : master , transaction : transaction )
self . storage . setFriendRequestStatus ( . friends , for : slave , transaction : transaction )
}
let masterThread = createContactThread ( for : master )
let slaveThread = createContactThread ( for : slave )
XCTAssertTrue ( FriendRequestProtocol . getFriendRequestUIStatus ( for : masterThread ) = = . none )
XCTAssertTrue ( FriendRequestProtocol . getFriendRequestUIStatus ( for : slaveThread ) = = . none )
}
func test_getFriendRequestUIStateShouldReturnTheCorrectStates ( ) {
let bob = generateHexEncodedPublicKey ( )
let bobThread = createContactThread ( for : bob )
let expectedStatuses : [ LKFriendRequestStatus : FriendRequestProtocol . FriendRequestUIStatus ] = [
. none : . none ,
. requestExpired : . expired ,
. requestSending : . sent ,
. requestSent : . sent ,
. requestReceived : . received ,
. friends : . friends ,
]
for ( friendRequestStatus , uiState ) in expectedStatuses {
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setFriendRequestStatus ( friendRequestStatus , for : bob , transaction : transaction )
}
XCTAssertEqual ( FriendRequestProtocol . getFriendRequestUIStatus ( for : bobThread ) , uiState , " Expected FriendRequestUIStatus to be \( uiState ) . " )
}
}
func test_getFriendRequestUIStateShouldWorkWithMultiDevice ( ) {
let master = generateHexEncodedPublicKey ( )
let slave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
let deviceLink = DeviceLink ( between : masterDevice , and : slaveDevice )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( deviceLink , in : transaction )
self . storage . setFriendRequestStatus ( . none , for : master , transaction : transaction )
}
let masterThread = createContactThread ( for : master )
let slaveThread = createContactThread ( for : slave )
let expectedStatuses : [ LKFriendRequestStatus : FriendRequestProtocol . FriendRequestUIStatus ] = [
. none : . none ,
. requestExpired : . expired ,
. requestSending : . sent ,
. requestSent : . sent ,
. requestReceived : . received ,
. friends : . friends ,
]
for ( friendRequestStatus , uiState ) in expectedStatuses {
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setFriendRequestStatus ( friendRequestStatus , for : slave , transaction : transaction )
}
XCTAssertEqual ( FriendRequestProtocol . getFriendRequestUIStatus ( for : masterThread ) , uiState , " Expected FriendRequestUIStatus to be \( uiState . rawValue ) . " )
XCTAssertEqual ( FriendRequestProtocol . getFriendRequestUIStatus ( for : slaveThread ) , uiState , " Expected FriendRequestUIStatus to be \( uiState . rawValue ) . " )
}
func createThread ( for hexEncodedPublicKey : String ) -> TSContactThread {
var result : TSContactThread !
}
func test_getFriendRequestUIStateShouldPreferFriendsOverRequestReceived ( ) {
// C a s e : W e d o n ' t w a n t t o c o n f u s e t h e u s e r b y s h o w i n g a f r i e n d r e q u e s t b o x w h e n t h e y ' r e a l r e a d y f r i e n d s .
let master = generateHexEncodedPublicKey ( )
let slave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
let masterThread = createContactThread ( for : master )
let deviceLink = DeviceLink ( between : masterDevice , and : slaveDevice )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( deviceLink , in : transaction )
self . storage . setFriendRequestStatus ( . requestReceived , for : master , transaction : transaction )
self . storage . setFriendRequestStatus ( . friends , for : slave , transaction : transaction )
}
XCTAssertTrue ( FriendRequestProtocol . getFriendRequestUIStatus ( for : masterThread ) = = . friends )
}
func test_getFriendRequestUIStateShouldPreferReceivedOverSent ( ) {
// C a s e : W e s e n t B o b a f r i e n d r e q u e s t a n d h e s e n t o n e b a c k t o u s t h r o u g h a n o t h e r d e v i c e .
// I f s o m e t h i n g w e n t w r o n g t h e n w e s h o u l d b e a b l e t o f a l l b a c k t o m a n u a l l y a c c e p t i n g t h e f r i e n d r e q u e s t e v e n i f w e s e n t o n e .
let master = generateHexEncodedPublicKey ( )
let slave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
let masterThread = createContactThread ( for : master )
let deviceLink = DeviceLink ( between : masterDevice , and : slaveDevice )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( deviceLink , in : transaction )
self . storage . setFriendRequestStatus ( . requestSent , for : master , transaction : transaction )
self . storage . setFriendRequestStatus ( . requestReceived , for : slave , transaction : transaction )
}
XCTAssertTrue ( FriendRequestProtocol . getFriendRequestUIStatus ( for : masterThread ) = = . received )
}
// MARK: - a c c e p t F r i e n d R e q u e s t
func test_acceptFriendRequestShouldSetStatusToFriendsIfWeReceivedAFriendRequest ( ) {
// C a s e : B o b s e n t u s a f r i e n d r e q u e s t , w e s h o u l d b e c o m e f r i e n d s w i t h h i m o n a c c e p t i n g .
let bob = generateHexEncodedPublicKey ( )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setFriendRequestStatus ( . requestReceived , for : bob , transaction : transaction )
}
storage . dbReadWriteConnection . readWrite { transaction in
FriendRequestProtocol . acceptFriendRequest ( from : bob , using : transaction )
XCTAssertTrue ( self . storage . getFriendRequestStatus ( for : bob , transaction : transaction ) = = . friends )
}
}
// TODO: A d d t e s t t o s e e i f a n a c c e p t m e s s a g e i s s e n t o u t
func test_acceptFriendRequestShouldSendAFriendRequestMessageIfStatusIsNoneOrExpired ( ) {
// C a s e : S o m e h o w o u r f r i e n d r e q u e s t s t a t u s d o e s n ' t m a t c h t h e U I .
// S i n c e u s e r a c c e p t e d t h e n w e s h o u l d s e n d a f r i e n d r e q u e s t m e s s a g e .
let statuses : [ LKFriendRequestStatus ] = [ . none , . requestExpired ]
for status in statuses {
let bob = generateHexEncodedPublicKey ( )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setFriendRequestStatus ( status , for : bob , transaction : transaction )
}
let expectation = self . expectation ( description : " sent message " )
let messageSender = self . messageSender
messageSender . sendMessageWasCalledBlock = { sentMessage in
guard sentMessage is FriendRequestMessage else {
return XCTFail ( " Expected a friend request to be sent, but found: \( sentMessage ) . " )
}
expectation . fulfill ( )
messageSender . sendMessageWasCalledBlock = nil
}
storage . dbReadWriteConnection . readWrite { transaction in
result = TSContactThread . getOrCreateThread ( withContactId : hexEncodedPublicKey , transaction : transaction )
FriendRequestProtocol. acceptFriendRequest ( from : bob , using : transaction )
}
return result
wait ( for : [ expectation ] , timeout : 1 )
}
}
func test_acceptFriendRequestShouldNotDoAnythingIfRequestHasBeenSent ( ) {
// C a s e : W e s e n t B o b a f r i e n d r e q u e s t .
// W e c a n ' t a c c e p t b e c a u s e w e d o n ' t h a v e k e y s t o c o m m u n i c a t e w i t h B o b .
let bob = generateHexEncodedPublicKey ( )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setFriendRequestStatus ( . requestSent , for : bob , transaction : transaction )
}
storage . dbReadWriteConnection . readWrite { transaction in
FriendRequestProtocol . acceptFriendRequest ( from : bob , using : transaction )
XCTAssertTrue ( self . isFriendRequestStatus ( . requestSent , for : bob , transaction : transaction ) )
}
// G e t d e v i c e s
guard let bobMasterDevice = getDevice ( ) else { return XCTFail ( ) }
guard let bobSlaveDevice = getDevice ( ) else { return XCTFail ( ) }
// C r e a t e d e v i c e l i n k
let bobDeviceLink = DeviceLink ( between : bobMasterDevice , and : bobSlaveDevice )
}
func test_acceptFriendRequestShouldWorkWithMultiDevice ( ) {
// C a s e : B o b s e n t a f r i e n d r e q u e s t f r o m h i s s l a v e d e v i c e .
// A c c e p t i n g t h e f r i e n d r e q u e s t s h o u l d s e t i t t o f r i e n d s .
// W e s h o u l d a l s o s e n d o u t a f r i e n d r e q u e s t t o B o b ' s o t h e r d e v i c e s i f p o s s i b l e .
let master = generateHexEncodedPublicKey ( )
let slave = generateHexEncodedPublicKey ( )
let otherSlave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
guard let otherSlaveDevice = getDevice ( for : otherSlave ) else { return XCTFail ( ) }
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( bobDeviceLink , in : transaction )
self . storage . addDeviceLink ( DeviceLink ( between : masterDevice , and : slaveDevice ) , in : transaction )
self . storage . addDeviceLink ( DeviceLink ( between : masterDevice , and : otherSlaveDevice ) , in : transaction )
self . storage . setFriendRequestStatus ( . none , for : master , transaction : transaction )
self . storage . setFriendRequestStatus ( . requestReceived , for : slave , transaction : transaction )
self . storage . setFriendRequestStatus ( . requestSent , for : otherSlave , transaction : transaction )
}
// C r e a t e t h r e a d s
let bobMasterThread = createThread ( for : bobMasterDevice . hexEncodedPublicKey )
let bobSlaveThread = createThread ( for : bobSlaveDevice . hexEncodedPublicKey )
// S c e n a r i o 1 : A l i c e h a s a p e n d i n g f r i e n d r e q u e s t f r o m B o b ' s m a s t e r d e v i c e , a n d n o t h i n g
// f r o m h i s s l a v e d e v i c e . A f t e r a c c e p t i n g t h e p e n d i n g f r i e n d r e q u e s t w e ' d e x p e c t t h e
// f r i e n d r e q u e s t s t a t u s f o r B o b ' s m a s t e r t h r e a d t o b e ` f r i e n d s ` , a n d t h a t o f B o b ' s
// s l a v e t h r e a d t o b e ` r e q u e s t S e n t ` .
storage . dbReadWriteConnection . readWrite { transaction in
FriendRequestProtocol . acceptFriendRequest ( from : master , using : transaction )
}
eventually {
self . storage . dbReadWriteConnection . readWrite { transaction in
// TODO: R e - e n a b l e t h i s c a s e w h e n w e s p l i t f r i e n d r e q u e s t l o g i c f r o m O W S M e s s a g e S e n d e r
// X C T A s s e r t T r u e ( s e l f . i s F r i e n d R e q u e s t S t a t u s ( [ . r e q u e s t S e n d i n g , . r e q u e s t S e n t ] , f o r : m a s t e r , t r a n s a c t i o n : t r a n s a c t i o n ) )
XCTAssertTrue ( self . isFriendRequestStatus ( . friends , for : slave , transaction : transaction ) )
XCTAssertTrue ( self . isFriendRequestStatus ( . requestSent , for : otherSlave , transaction : transaction ) )
}
}
}
func test_acceptFriendRequestShouldNotChangeStatusIfDevicesAreNotLinked ( ) {
let alice = generateHexEncodedPublicKey ( )
let bob = generateHexEncodedPublicKey ( )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setFriendRequestStatus ( . requestReceived , for : alice , transaction : transaction )
self . storage . setFriendRequestStatus ( . requestReceived , for : bob , transaction : transaction )
}
storage . dbReadWriteConnection . readWrite { transaction in
FriendRequestProtocol . acceptFriendRequest ( from : alice , using : transaction )
XCTAssertTrue ( self . isFriendRequestStatus ( . friends , for : alice , transaction : transaction ) )
XCTAssertTrue ( self . isFriendRequestStatus ( . requestReceived , for : bob , transaction : transaction ) )
}
}
// MARK: - d e c l i n e F r i e n d R e q u e s t
func test_declineFriendRequestShouldChangeStatusFromReceivedToNone ( ) {
let bob = generateHexEncodedPublicKey ( )
storage . dbReadWriteConnection . readWrite { transaction in
bobMasterThread . saveFriendRequestStatus ( . requestReceived , with : transaction )
bobSlaveThread . saveFriendRequestStatus ( . none , with : transaction )
self . storage . setFriendRequestStatus ( . requestReceived , for : bob , transaction : transaction )
}
storage . dbReadWriteConnection . readWrite { transaction in
FriendRequestProtocol . declineFriendRequest ( from : bob , using : transaction )
XCTAssertTrue ( self . isFriendRequestStatus ( . none , for : bob , transaction : transaction ) )
}
}
func test_declineFriendRequestShouldNotChangeStatusToNoneFromOtherStatuses ( ) {
let statuses : [ LKFriendRequestStatus ] = [ . none , . requestSending , . requestSent , . requestExpired , . friends ]
let bob = generateHexEncodedPublicKey ( )
for status in statuses {
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setFriendRequestStatus ( status , for : bob , transaction : transaction )
}
storage . dbReadWriteConnection . readWrite { transaction in
FriendRequestProtocol . declineFriendRequest ( from : bob , using : transaction )
XCTAssertTrue ( self . isFriendRequestStatus ( status , for : bob , transaction : transaction ) )
}
}
}
func test_declineFriendRequestShouldDeletePreKeyBundleIfNeeded ( ) {
let shouldExpectDeletedPreKeyBundle : ( LKFriendRequestStatus ) -> Bool = { status in
return status = = . requestReceived
}
let statuses : [ LKFriendRequestStatus ] = [ . none , . requestSending , . requestSent , . requestReceived , . requestExpired , . friends ]
for status in statuses {
let bob = generateHexEncodedPublicKey ( )
let bundle = storage . generatePreKeyBundle ( forContact : bob )
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . setPreKeyBundle ( bundle , forContact : bob , transaction : transaction )
self . storage . setFriendRequestStatus ( status , for : bob , transaction : transaction )
}
storage . dbReadWriteConnection . readWrite { transaction in
FriendRequestProtocol . declineFriendRequest ( from : bob , using : transaction )
}
let storedBundle = storage . getPreKeyBundle ( forContact : bob )
if ( shouldExpectDeletedPreKeyBundle ( status ) ) {
XCTAssertNil ( storedBundle , " Expected PreKeyBundle to be deleted for friend request status \( status . rawValue ) . " )
} else {
XCTAssertNotNil ( storedBundle , " Expected PreKeyBundle to not be deleted for friend request status \( status . rawValue ) . " )
}
}
}
func test_declineFriendRequestShouldWorkWithMultipleLinkedDevices ( ) {
// C a s e : B o b s e n d s 2 f r i e n d r e q u e s t s t o A l i c e .
// W h e n A l i c e d e c l i n e s , i t s h o u l d c h a n g e t h e s t a t u s e s f r o m r e q u e s t R e c e i v e d t o n o n e s o f r i e n d r e q u e s t l o g i c c a n b e r e - t r i g g e r e d .
let master = generateHexEncodedPublicKey ( )
let slave = generateHexEncodedPublicKey ( )
let otherSlave = generateHexEncodedPublicKey ( )
guard let masterDevice = getDevice ( for : master ) else { return XCTFail ( ) }
guard let slaveDevice = getDevice ( for : slave ) else { return XCTFail ( ) }
guard let otherSlaveDevice = getDevice ( for : otherSlave ) else { return XCTFail ( ) }
storage . dbReadWriteConnection . readWrite { transaction in
self . storage . addDeviceLink ( DeviceLink ( between : masterDevice , and : slaveDevice ) , in : transaction )
self . storage . addDeviceLink ( DeviceLink ( between : masterDevice , and : otherSlaveDevice ) , in : transaction )
self . storage . setFriendRequestStatus ( . requestSent , for : master , transaction : transaction )
self . storage . setFriendRequestStatus ( . requestReceived , for : slave , transaction : transaction )
self . storage . setFriendRequestStatus ( . requestReceived , for : otherSlave , transaction : transaction )
}
storage . dbReadWriteConnection . readWrite { transaction in
FriendRequestProtocol . acceptFriendRequest ( from : bobMasterDevice . hexEncodedPublicKey , in : bobMasterThread , using : transaction )
FriendRequestProtocol . declineFriendRequest ( from : master , using : transaction )
XCTAssertTrue ( self . isFriendRequestStatus ( . requestSent , for : master , transaction : transaction ) )
XCTAssertTrue ( self . isFriendRequestStatus ( . none , for : slave , transaction : transaction ) )
XCTAssertTrue ( self . isFriendRequestStatus ( . none , for : otherSlave , transaction : transaction ) )
}
XCTAssert ( bobMasterThread . friendRequestStatus = = . friends )
XCTAssert ( bobSlaveThread . friendRequestStatus = = . requestSent )
// TODO: A d d o t h e r s c e n a r i o s
}
}