mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			512 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Objective-C
		
	
			
		
		
	
	
			512 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Objective-C
		
	
| //
 | |
| //  Copyright (c) 2017 Open Whisper Systems. All rights reserved.
 | |
| //
 | |
| 
 | |
| #import "Cryptography.h"
 | |
| #import "OWSDisappearingMessagesConfiguration.h"
 | |
| #import "OWSError.h"
 | |
| #import "OWSFakeContactsManager.h"
 | |
| #import "OWSFakeContactsUpdater.h"
 | |
| #import "OWSFakeNetworkManager.h"
 | |
| #import "OWSMessageSender.h"
 | |
| #import "OWSUploadingService.h"
 | |
| #import "TSAccountManager.h"
 | |
| #import "TSContactThread.h"
 | |
| #import "TSGroupModel.h"
 | |
| #import "TSGroupThread.h"
 | |
| #import "TSMessagesManager.h"
 | |
| #import "TSNetworkManager.h"
 | |
| #import "TSOutgoingMessage.h"
 | |
| #import "TSStorageManager.h"
 | |
| #import <AxolotlKit/AxolotlExceptions.h>
 | |
| #import <AxolotlKit/SessionBuilder.h>
 | |
| #import <XCTest/XCTest.h>
 | |
| 
 | |
| NS_ASSUME_NONNULL_BEGIN
 | |
| 
 | |
| @interface OWSMessageSender (Testing)
 | |
| 
 | |
| @property (nonatomic) OWSUploadingService *uploadingService;
 | |
| @property (nonatomic) ContactsUpdater *contactsUpdater;
 | |
| 
 | |
| // Private Methods to test
 | |
| - (NSArray<SignalRecipient *> *)getRecipients:(NSArray<NSString *> *)identifiers error:(NSError **)error;
 | |
| 
 | |
| @end
 | |
| 
 | |
| @implementation OWSMessageSender (Testing)
 | |
| 
 | |
| - (NSArray<NSDictionary *> *)deviceMessages:(TSOutgoingMessage *)message
 | |
|                                forRecipient:(SignalRecipient *)recipient
 | |
|                                    inThread:(TSThread *)thread
 | |
| {
 | |
|     NSLog(@"[OWSFakeMessagesManager] Faking deviceMessages.");
 | |
|     return @[];
 | |
| }
 | |
| 
 | |
| - (void)setContactsUpdater:(ContactsUpdater *)contactsUpdater
 | |
| {
 | |
|     _contactsUpdater = contactsUpdater;
 | |
| }
 | |
| 
 | |
| - (ContactsUpdater *)contactsUpdater
 | |
| {
 | |
|     return _contactsUpdater;
 | |
| }
 | |
| 
 | |
| - (void)setUploadingService:(OWSUploadingService *)uploadingService
 | |
| {
 | |
|     _uploadingService = uploadingService;
 | |
| }
 | |
| 
 | |
| - (OWSUploadingService *)uploadingService
 | |
| {
 | |
|     return _uploadingService;
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| @interface OWSFakeUploadingService : OWSUploadingService
 | |
| 
 | |
| @property (nonatomic, readonly) BOOL shouldSucceed;
 | |
| 
 | |
| @end
 | |
| 
 | |
| @implementation OWSFakeUploadingService
 | |
| 
 | |
| - (instancetype)initWithSuccess:(BOOL)flag
 | |
| {
 | |
|     self = [super initWithNetworkManager:[OWSFakeNetworkManager new]];
 | |
|     if (!self) {
 | |
|         return self;
 | |
|     }
 | |
| 
 | |
|     _shouldSucceed = flag;
 | |
| 
 | |
|     return self;
 | |
| }
 | |
| 
 | |
| - (void)uploadAttachmentStream:(TSAttachmentStream *)attachmentStream
 | |
|                        message:(TSOutgoingMessage *)outgoingMessage
 | |
|                        success:(void (^)())successHandler
 | |
|                        failure:(void (^)(NSError *error))failureHandler
 | |
| {
 | |
|     if (self.shouldSucceed) {
 | |
|         successHandler();
 | |
|     } else {
 | |
|         NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
 | |
|         [error setIsRetryable:NO];
 | |
| 
 | |
|         failureHandler(error);
 | |
|     }
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| @interface OWSFakeURLSessionDataTask : NSURLSessionDataTask
 | |
| 
 | |
| @property (copy) NSHTTPURLResponse *response;
 | |
| 
 | |
| - (instancetype)initWithStatusCode:(long)statusCode;
 | |
| 
 | |
| @end
 | |
| 
 | |
| @implementation OWSFakeURLSessionDataTask
 | |
| 
 | |
| @synthesize response = _response;
 | |
| 
 | |
| - (instancetype)initWithStatusCode:(long)statusCode
 | |
| {
 | |
|     self = [super init];
 | |
| 
 | |
|     if (!self) {
 | |
|         return self;
 | |
|     }
 | |
| 
 | |
|     NSURL *fakeURL = [NSURL URLWithString:@"http://127.0.0.1"];
 | |
|     _response = [[NSHTTPURLResponse alloc] initWithURL:fakeURL statusCode:statusCode HTTPVersion:nil headerFields:nil];
 | |
| 
 | |
|     return self;
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| @interface OWSMessageSenderFakeNetworkManager : OWSFakeNetworkManager
 | |
| 
 | |
| - (instancetype)init;
 | |
| - (instancetype)initWithSuccess:(BOOL)shouldSucceed NS_DESIGNATED_INITIALIZER;
 | |
| 
 | |
| @property (nonatomic, readonly) BOOL shouldSucceed;
 | |
| 
 | |
| @end
 | |
| 
 | |
| @implementation OWSMessageSenderFakeNetworkManager
 | |
| 
 | |
| - (instancetype)initWithSuccess:(BOOL)shouldSucceed
 | |
| {
 | |
|     self = [self init];
 | |
|     if (!self) {
 | |
|         return self;
 | |
|     }
 | |
| 
 | |
|     _shouldSucceed = shouldSucceed;
 | |
| 
 | |
|     return self;
 | |
| }
 | |
| 
 | |
| - (void)makeRequest:(TSRequest *)request
 | |
|             success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
 | |
|             failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
 | |
| {
 | |
|     if ([request isKindOfClass:[TSSubmitMessageRequest class]]) {
 | |
|         if (self.shouldSucceed) {
 | |
|             success([NSURLSessionDataTask new], @{});
 | |
|         } else {
 | |
|             NSError *error
 | |
|                 = OWSErrorWithCodeDescription(OWSErrorCodeFailedToSendOutgoingMessage, @"fake error description");
 | |
|             OWSFakeURLSessionDataTask *task = [[OWSFakeURLSessionDataTask alloc] initWithStatusCode:500];
 | |
|             failure(task, error);
 | |
|         }
 | |
|     } else {
 | |
|         [super makeRequest:request success:success failure:failure];
 | |
|     }
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| @interface TSAccountManager (Testing)
 | |
| 
 | |
| - (void)storeLocalNumber:(NSString *)localNumber;
 | |
| 
 | |
| @end
 | |
| 
 | |
| @interface OWSMessageSenderTest : XCTestCase
 | |
| 
 | |
| @property (nonatomic) TSThread *thread;
 | |
| @property (nonatomic) TSOutgoingMessage *expiringMessage;
 | |
| @property (nonatomic) TSOutgoingMessage *unexpiringMessage;
 | |
| @property (nonatomic) OWSMessageSenderFakeNetworkManager *networkManager;
 | |
| @property (nonatomic) OWSMessageSender *successfulMessageSender;
 | |
| @property (nonatomic) OWSMessageSender *unsuccessfulMessageSender;
 | |
| 
 | |
| @end
 | |
| 
 | |
| @implementation OWSMessageSenderTest
 | |
| 
 | |
| - (void)setUp
 | |
| {
 | |
|     [super setUp];
 | |
| 
 | |
|     // Hack to make sure we don't explode when sending sync message.
 | |
|     [[TSAccountManager sharedInstance] storeLocalNumber:@"+13231231234"];
 | |
| 
 | |
|     self.thread = [[TSContactThread alloc] initWithUniqueId:@"fake-thread-id"];
 | |
|     [self.thread save];
 | |
| 
 | |
|     self.unexpiringMessage = [[TSOutgoingMessage alloc] initWithTimestamp:1
 | |
|                                                                  inThread:self.thread
 | |
|                                                               messageBody:@"outgoing message"
 | |
|                                                             attachmentIds:[NSMutableArray new]
 | |
|                                                          expiresInSeconds:0];
 | |
|     [self.unexpiringMessage save];
 | |
| 
 | |
|     self.expiringMessage = [[TSOutgoingMessage alloc] initWithTimestamp:1
 | |
|                                                                inThread:self.thread
 | |
|                                                             messageBody:@"outgoing message"
 | |
|                                                           attachmentIds:[NSMutableArray new]
 | |
|                                                        expiresInSeconds:30];
 | |
|     [self.expiringMessage save];
 | |
| 
 | |
|     TSStorageManager *storageManager = [TSStorageManager sharedManager];
 | |
|     OWSFakeContactsManager *contactsManager = [OWSFakeContactsManager new];
 | |
|     OWSFakeContactsUpdater *contactsUpdater = [OWSFakeContactsUpdater new];
 | |
| 
 | |
|     // Successful Sending
 | |
|     TSNetworkManager *successfulNetworkManager = [[OWSMessageSenderFakeNetworkManager alloc] initWithSuccess:YES];
 | |
|     self.successfulMessageSender = [[OWSMessageSender alloc] initWithNetworkManager:successfulNetworkManager
 | |
|                                                                      storageManager:storageManager
 | |
|                                                                     contactsManager:contactsManager
 | |
|                                                                     contactsUpdater:contactsUpdater];
 | |
| 
 | |
|     // Unsuccessful Sending
 | |
|     TSNetworkManager *unsuccessfulNetworkManager = [[OWSMessageSenderFakeNetworkManager alloc] initWithSuccess:NO];
 | |
|     self.unsuccessfulMessageSender = [[OWSMessageSender alloc] initWithNetworkManager:unsuccessfulNetworkManager
 | |
|                                                                        storageManager:storageManager
 | |
|                                                                       contactsManager:contactsManager
 | |
|                                                                       contactsUpdater:contactsUpdater];
 | |
| }
 | |
| 
 | |
| - (void)testExpiringMessageTimerStartsOnSuccessWhenDisappearingMessagesEnabled
 | |
| {
 | |
|     [[[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:self.thread.uniqueId enabled:YES durationSeconds:10]
 | |
|         save];
 | |
| 
 | |
|     OWSMessageSender *messageSender = self.successfulMessageSender;
 | |
| 
 | |
|     // Sanity Check
 | |
|     XCTAssertEqual(0, self.expiringMessage.expiresAt);
 | |
| 
 | |
|     XCTestExpectation *messageStartedExpiration = [self expectationWithDescription:@"messageStartedExpiration"];
 | |
|     [messageSender sendMessage:self.expiringMessage
 | |
|         success:^() {
 | |
|             //FIXME remove sleep hack in favor of expiringMessage completion handler
 | |
|             sleep(2);
 | |
|             if (self.expiringMessage.expiresAt > 0) {
 | |
|                 [messageStartedExpiration fulfill];
 | |
|             } else {
 | |
|                 XCTFail(@"Message expiration was supposed to start.");
 | |
|             }
 | |
|         }
 | |
|         failure:^(NSError *error) {
 | |
|             XCTFail(@"Message failed to send");
 | |
|         }];
 | |
| 
 | |
|     [self waitForExpectationsWithTimeout:5
 | |
|                                  handler:^(NSError *error) {
 | |
|                                      NSLog(@"Expiration timer not set in time.");
 | |
|                                  }];
 | |
| }
 | |
| 
 | |
| - (void)testExpiringMessageTimerDoesNotStartsWhenDisabled
 | |
| {
 | |
|     [[[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:self.thread.uniqueId enabled:NO durationSeconds:10]
 | |
|         save];
 | |
| 
 | |
|     OWSMessageSender *messageSender = self.successfulMessageSender;
 | |
| 
 | |
|     XCTestExpectation *messageDidNotStartExpiration = [self expectationWithDescription:@"messageDidNotStartExpiration"];
 | |
|     [messageSender sendMessage:self.unexpiringMessage
 | |
|         success:^() {
 | |
|             if (self.unexpiringMessage.isExpiringMessage || self.unexpiringMessage.expiresAt > 0) {
 | |
|                 XCTFail(@"Message expiration was not supposed to start.");
 | |
|             } else {
 | |
|                 [messageDidNotStartExpiration fulfill];
 | |
|             }
 | |
|         }
 | |
|         failure:^(NSError *error) {
 | |
|             XCTFail(@"Message failed to send");
 | |
|         }];
 | |
| 
 | |
|     [self waitForExpectationsWithTimeout:5
 | |
|                                  handler:^(NSError *error) {
 | |
|                                      NSLog(@"Expiration timer not set in time.");
 | |
|                                  }];
 | |
| }
 | |
| 
 | |
| - (void)testExpiringMessageTimerDoesNotStartsOnFailure
 | |
| {
 | |
|     OWSMessageSender *messageSender = self.unsuccessfulMessageSender;
 | |
| 
 | |
|     // Sanity Check
 | |
|     XCTAssertEqual(0, self.expiringMessage.expiresAt);
 | |
| 
 | |
|     XCTestExpectation *messageDidNotStartExpiration = [self expectationWithDescription:@"messageStartedExpiration"];
 | |
|     [messageSender sendMessage:self.expiringMessage
 | |
|         success:^() {
 | |
|             XCTFail(@"Message sending was supposed to fail.");
 | |
|         }
 | |
|         failure:^(NSError *error) {
 | |
|             if (self.expiringMessage.expiresAt == 0) {
 | |
|                 [messageDidNotStartExpiration fulfill];
 | |
|             } else {
 | |
|                 XCTFail(@"Message expiration was not supposed to start.");
 | |
|             }
 | |
|         }];
 | |
| 
 | |
|     [self waitForExpectationsWithTimeout:5 handler:nil];
 | |
| }
 | |
| 
 | |
| - (void)testTextMessageIsMarkedAsSentOnSuccess
 | |
| {
 | |
|     OWSMessageSender *messageSender = self.successfulMessageSender;
 | |
| 
 | |
|     TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:1
 | |
|                                                                      inThread:self.thread
 | |
|                                                                   messageBody:@"We want punks in the palace."];
 | |
| 
 | |
|     XCTestExpectation *markedAsSent = [self expectationWithDescription:@"markedAsSent"];
 | |
|     [messageSender sendMessage:message
 | |
|         success:^() {
 | |
|             if (message.messageState == TSOutgoingMessageStateSentToService) {
 | |
|                 [markedAsSent fulfill];
 | |
|             } else {
 | |
|                 XCTFail(@"Unexpected message state");
 | |
|             }
 | |
|         }
 | |
|         failure:^(NSError *error) {
 | |
|             XCTFail(@"sendMessage should succeed.");
 | |
|         }];
 | |
| 
 | |
|     [self waitForExpectationsWithTimeout:5 handler:nil];
 | |
| }
 | |
| 
 | |
| - (void)testMediaMessageIsMarkedAsSentOnSuccess
 | |
| {
 | |
|     OWSMessageSender *messageSender = self.successfulMessageSender;
 | |
|     messageSender.uploadingService = [[OWSFakeUploadingService alloc] initWithSuccess:YES];
 | |
| 
 | |
|     TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:1
 | |
|                                                                      inThread:self.thread
 | |
|                                                                   messageBody:@"We want punks in the palace."];
 | |
| 
 | |
|     XCTestExpectation *markedAsSent = [self expectationWithDescription:@"markedAsSent"];
 | |
|     [messageSender sendAttachmentData:[NSData new]
 | |
|         contentType:@"image/gif"
 | |
|         sourceFilename:nil
 | |
|         inMessage:message
 | |
|         success:^() {
 | |
|             if (message.messageState == TSOutgoingMessageStateSentToService) {
 | |
|                 [markedAsSent fulfill];
 | |
|             } else {
 | |
|                 XCTFail(@"Unexpected message state");
 | |
|             }
 | |
|         }
 | |
|         failure:^(NSError *error) {
 | |
|             XCTFail(@"sendMessage should succeed.");
 | |
|         }];
 | |
| 
 | |
|     [self waitForExpectationsWithTimeout:5 handler:nil];
 | |
| }
 | |
| 
 | |
| - (void)testTextMessageIsMarkedAsUnsentOnFailure
 | |
| {
 | |
|     OWSMessageSender *messageSender = self.unsuccessfulMessageSender;
 | |
|     messageSender.uploadingService = [[OWSFakeUploadingService alloc] initWithSuccess:YES];
 | |
| 
 | |
|     TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:1
 | |
|                                                                      inThread:self.thread
 | |
|                                                                   messageBody:@"We want punks in the palace."];
 | |
| 
 | |
|     XCTestExpectation *markedAsUnsent = [self expectationWithDescription:@"markedAsUnsent"];
 | |
|     [messageSender sendMessage:message
 | |
|         success:^() {
 | |
|             XCTFail(@"sendMessage should fail.");
 | |
|         }
 | |
|         failure:^(NSError *error) {
 | |
|             if (message.messageState == TSOutgoingMessageStateUnsent) {
 | |
|                 [markedAsUnsent fulfill];
 | |
|             } else {
 | |
|                 XCTFail(@"Unexpected message state");
 | |
|             }
 | |
|         }];
 | |
| 
 | |
|     [self waitForExpectationsWithTimeout:5 handler:nil];
 | |
| }
 | |
| 
 | |
| - (void)testMediaMessageIsMarkedAsUnsentOnFailureToSend
 | |
| {
 | |
|     OWSMessageSender *messageSender = self.unsuccessfulMessageSender;
 | |
|     // Assume that upload will go well, but that failure happens elsewhere in message sender.
 | |
|     messageSender.uploadingService = [[OWSFakeUploadingService alloc] initWithSuccess:YES];
 | |
| 
 | |
|     TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:1
 | |
|                                                                      inThread:self.thread
 | |
|                                                                   messageBody:@"We want punks in the palace."];
 | |
| 
 | |
|     XCTestExpectation *markedAsUnsent = [self expectationWithDescription:@"markedAsUnsent"];
 | |
|     [messageSender sendAttachmentData:[NSData new]
 | |
|         contentType:@"image/gif"
 | |
|         sourceFilename:nil
 | |
|         inMessage:message
 | |
|         success:^{
 | |
|             XCTFail(@"sendMessage should fail.");
 | |
|         }
 | |
|         failure:^(NSError *_Nonnull error) {
 | |
|             if (message.messageState == TSOutgoingMessageStateUnsent) {
 | |
|                 [markedAsUnsent fulfill];
 | |
|             } else {
 | |
|                 XCTFail(@"Unexpected message state");
 | |
|             }
 | |
|         }];
 | |
| 
 | |
|     [self waitForExpectationsWithTimeout:5 handler:nil];
 | |
| }
 | |
| 
 | |
| - (void)testMediaMessageIsMarkedAsUnsentOnFailureToUpload
 | |
| {
 | |
|     OWSMessageSender *messageSender = self.successfulMessageSender;
 | |
|     // Assume that upload fails, but other sending stuff would succeed.
 | |
|     messageSender.uploadingService = [[OWSFakeUploadingService alloc] initWithSuccess:NO];
 | |
| 
 | |
|     TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:1
 | |
|                                                                      inThread:self.thread
 | |
|                                                                   messageBody:@"We want punks in the palace."];
 | |
| 
 | |
|     XCTestExpectation *markedAsUnsent = [self expectationWithDescription:@"markedAsUnsent"];
 | |
|     [messageSender sendAttachmentData:[NSData new]
 | |
|         contentType:@"image/gif"
 | |
|         sourceFilename:nil
 | |
|         inMessage:message
 | |
|         success:^{
 | |
|             XCTFail(@"sendMessage should fail.");
 | |
|         }
 | |
|         failure:^(NSError *_Nonnull error) {
 | |
|             if (message.messageState == TSOutgoingMessageStateUnsent) {
 | |
|                 [markedAsUnsent fulfill];
 | |
|             } else {
 | |
|                 XCTFail(@"Unexpected message state");
 | |
|             }
 | |
|         }];
 | |
| 
 | |
|     [self waitForExpectationsWithTimeout:5 handler:nil];
 | |
| }
 | |
| 
 | |
| - (void)testGroupSend
 | |
| {
 | |
|     OWSMessageSender *messageSender = self.successfulMessageSender;
 | |
| 
 | |
| 
 | |
|     NSData *groupIdData = [Cryptography generateRandomBytes:32];
 | |
|     SignalRecipient *successfulRecipient =
 | |
|         [[SignalRecipient alloc] initWithTextSecureIdentifier:@"successful-recipient-id" relay:nil];
 | |
|     SignalRecipient *successfulRecipient2 =
 | |
|         [[SignalRecipient alloc] initWithTextSecureIdentifier:@"successful-recipient-id2" relay:nil];
 | |
| 
 | |
|     TSGroupModel *groupModel = [[TSGroupModel alloc]
 | |
|         initWithTitle:@"group title"
 | |
|             memberIds:[@[ successfulRecipient.uniqueId, successfulRecipient2.uniqueId ] mutableCopy]
 | |
|                 image:nil
 | |
|               groupId:groupIdData];
 | |
|     TSGroupThread *groupThread = [TSGroupThread getOrCreateThreadWithGroupModel:groupModel];
 | |
|     TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:1
 | |
|                                                                      inThread:groupThread
 | |
|                                                                   messageBody:@"We want punks in the palace."];
 | |
| 
 | |
|     XCTestExpectation *markedAsSent = [self expectationWithDescription:@"markedAsSent"];
 | |
|     [messageSender sendMessage:message
 | |
|         success:^{
 | |
|             if (message.messageState == TSOutgoingMessageStateSentToService) {
 | |
|                 [markedAsSent fulfill];
 | |
|             } else {
 | |
|                 XCTFail(@"Unexpected message state");
 | |
|             }
 | |
| 
 | |
|         }
 | |
|         failure:^(NSError *_Nonnull error) {
 | |
|             XCTFail(@"sendMessage should not fail.");
 | |
|         }];
 | |
| 
 | |
|     [self waitForExpectationsWithTimeout:5 handler:nil];
 | |
| }
 | |
| 
 | |
| - (void)testGetRecipients
 | |
| {
 | |
|     SignalRecipient *recipient = [[SignalRecipient alloc] initWithTextSecureIdentifier:@"fake-recipient-id" relay:nil];
 | |
|     [recipient save];
 | |
| 
 | |
|     OWSMessageSender *messageSender = self.successfulMessageSender;
 | |
| 
 | |
|     // At the time of writing this test, the ContactsUpdater was relying on global singletons. So if this test
 | |
|     // later fails due to network access that could be why.
 | |
|     messageSender.contactsUpdater = [ContactsUpdater sharedUpdater];
 | |
|     NSError *error;
 | |
|     NSArray<SignalRecipient *> *recipients = [messageSender getRecipients:@[ recipient.uniqueId ] error:&error];
 | |
| 
 | |
|     XCTAssertNil(error);
 | |
|     XCTAssertEqualObjects(recipient, recipients.firstObject);
 | |
| }
 | |
| 
 | |
| @end
 | |
| 
 | |
| NS_ASSUME_NONNULL_END
 |