1/* 2 * Copyright 2014 The WebRTC Project Authors. All rights reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11#import <Foundation/Foundation.h> 12#import <OCMock/OCMock.h> 13#import <QuartzCore/CoreAnimation.h> 14#import <XCTest/XCTest.h> 15 16#include "rtc_base/ssl_adapter.h" 17 18#import "sdk/objc/api/peerconnection/RTCMediaConstraints.h" 19#import "sdk/objc/api/peerconnection/RTCPeerConnectionFactory.h" 20 21#import "ARDAppClient+Internal.h" 22#import "ARDJoinResponse+Internal.h" 23#import "ARDMessageResponse+Internal.h" 24#import "ARDSettingsModel.h" 25 26@interface ARDAppClientTest : XCTestCase 27@end 28 29@implementation ARDAppClientTest 30 31#pragma mark - Mock helpers 32 33- (id)mockRoomServerClientForRoomId:(NSString *)roomId 34 clientId:(NSString *)clientId 35 isInitiator:(BOOL)isInitiator 36 messages:(NSArray *)messages 37 messageHandler: 38 (void (^)(ARDSignalingMessage *))messageHandler { 39 id mockRoomServerClient = 40 [OCMockObject mockForProtocol:@protocol(ARDRoomServerClient)]; 41 42 // Successful join response. 43 ARDJoinResponse *joinResponse = [[ARDJoinResponse alloc] init]; 44 joinResponse.result = kARDJoinResultTypeSuccess; 45 joinResponse.roomId = roomId; 46 joinResponse.clientId = clientId; 47 joinResponse.isInitiator = isInitiator; 48 joinResponse.messages = messages; 49 50 // Successful message response. 51 ARDMessageResponse *messageResponse = [[ARDMessageResponse alloc] init]; 52 messageResponse.result = kARDMessageResultTypeSuccess; 53 54 // Return join response from above on join. 55 [[[mockRoomServerClient stub] andDo:^(NSInvocation *invocation) { 56 __unsafe_unretained void (^completionHandler)(ARDJoinResponse *response, 57 NSError *error); 58 [invocation getArgument:&completionHandler atIndex:4]; 59 completionHandler(joinResponse, nil); 60 }] joinRoomWithRoomId:roomId isLoopback:NO completionHandler:[OCMArg any]]; 61 62 // Return message response from above on join. 63 [[[mockRoomServerClient stub] andDo:^(NSInvocation *invocation) { 64 __unsafe_unretained ARDSignalingMessage *message; 65 __unsafe_unretained void (^completionHandler)(ARDMessageResponse *response, 66 NSError *error); 67 [invocation getArgument:&message atIndex:2]; 68 [invocation getArgument:&completionHandler atIndex:5]; 69 messageHandler(message); 70 completionHandler(messageResponse, nil); 71 }] sendMessage:[OCMArg any] 72 forRoomId:roomId 73 clientId:clientId 74 completionHandler:[OCMArg any]]; 75 76 // Do nothing on leave. 77 [[[mockRoomServerClient stub] andDo:^(NSInvocation *invocation) { 78 __unsafe_unretained void (^completionHandler)(NSError *error); 79 [invocation getArgument:&completionHandler atIndex:4]; 80 if (completionHandler) { 81 completionHandler(nil); 82 } 83 }] leaveRoomWithRoomId:roomId 84 clientId:clientId 85 completionHandler:[OCMArg any]]; 86 87 return mockRoomServerClient; 88} 89 90- (id)mockSignalingChannelForRoomId:(NSString *)roomId 91 clientId:(NSString *)clientId 92 messageHandler: 93 (void (^)(ARDSignalingMessage *message))messageHandler { 94 id mockSignalingChannel = 95 [OCMockObject niceMockForProtocol:@protocol(ARDSignalingChannel)]; 96 [[mockSignalingChannel stub] registerForRoomId:roomId clientId:clientId]; 97 [[[mockSignalingChannel stub] andDo:^(NSInvocation *invocation) { 98 __unsafe_unretained ARDSignalingMessage *message; 99 [invocation getArgument:&message atIndex:2]; 100 messageHandler(message); 101 }] sendMessage:[OCMArg any]]; 102 return mockSignalingChannel; 103} 104 105- (id)mockTURNClient { 106 id mockTURNClient = 107 [OCMockObject mockForProtocol:@protocol(ARDTURNClient)]; 108 [[[mockTURNClient stub] andDo:^(NSInvocation *invocation) { 109 // Don't return anything in TURN response. 110 __unsafe_unretained void (^completionHandler)(NSArray *turnServers, 111 NSError *error); 112 [invocation getArgument:&completionHandler atIndex:2]; 113 completionHandler([NSArray array], nil); 114 }] requestServersWithCompletionHandler:[OCMArg any]]; 115 return mockTURNClient; 116} 117 118- (id)mockSettingsModel { 119 ARDSettingsModel *model = [[ARDSettingsModel alloc] init]; 120 id partialMock = [OCMockObject partialMockForObject:model]; 121 [[[partialMock stub] andReturn:@[ @"640x480", @"960x540", @"1280x720" ]] 122 availableVideoResolutions]; 123 124 return model; 125} 126 127- (ARDAppClient *)createAppClientForRoomId:(NSString *)roomId 128 clientId:(NSString *)clientId 129 isInitiator:(BOOL)isInitiator 130 messages:(NSArray *)messages 131 messageHandler: 132 (void (^)(ARDSignalingMessage *message))messageHandler 133 connectedHandler:(void (^)(void))connectedHandler 134 localVideoTrackHandler:(void (^)(void))localVideoTrackHandler { 135 id turnClient = [self mockTURNClient]; 136 id signalingChannel = [self mockSignalingChannelForRoomId:roomId 137 clientId:clientId 138 messageHandler:messageHandler]; 139 id roomServerClient = 140 [self mockRoomServerClientForRoomId:roomId 141 clientId:clientId 142 isInitiator:isInitiator 143 messages:messages 144 messageHandler:messageHandler]; 145 id delegate = 146 [OCMockObject niceMockForProtocol:@protocol(ARDAppClientDelegate)]; 147 [[[delegate stub] andDo:^(NSInvocation *invocation) { 148 connectedHandler(); 149 }] appClient:[OCMArg any] 150 didChangeConnectionState:RTCIceConnectionStateConnected]; 151 [[[delegate stub] andDo:^(NSInvocation *invocation) { 152 localVideoTrackHandler(); 153 }] appClient:[OCMArg any] 154 didReceiveLocalVideoTrack:[OCMArg any]]; 155 156 return [[ARDAppClient alloc] initWithRoomServerClient:roomServerClient 157 signalingChannel:signalingChannel 158 turnClient:turnClient 159 delegate:delegate]; 160} 161 162#pragma mark - Cases 163 164// Tests that an ICE connection is established between two ARDAppClient objects 165// where one is set up as a caller and the other the answerer. Network 166// components are mocked out and messages are relayed directly from object to 167// object. It's expected that both clients reach the 168// RTCIceConnectionStateConnected state within a reasonable amount of time. 169- (void)testSession { 170 // Need block arguments here because we're setting up a callbacks before we 171 // create the clients. 172 ARDAppClient *caller = nil; 173 ARDAppClient *answerer = nil; 174 __block __weak ARDAppClient *weakCaller = nil; 175 __block __weak ARDAppClient *weakAnswerer = nil; 176 NSString *roomId = @"testRoom"; 177 NSString *callerId = @"testCallerId"; 178 NSString *answererId = @"testAnswererId"; 179 180 XCTestExpectation *callerConnectionExpectation = 181 [self expectationWithDescription:@"Caller PC connected"]; 182 XCTestExpectation *answererConnectionExpectation = 183 [self expectationWithDescription:@"Answerer PC connected"]; 184 185 caller = [self createAppClientForRoomId:roomId 186 clientId:callerId 187 isInitiator:YES 188 messages:[NSArray array] 189 messageHandler:^(ARDSignalingMessage *message) { 190 ARDAppClient *strongAnswerer = weakAnswerer; 191 [strongAnswerer channel:strongAnswerer.channel didReceiveMessage:message]; 192 } connectedHandler:^{ 193 [callerConnectionExpectation fulfill]; 194 } localVideoTrackHandler:^{ 195 }]; 196 // TODO(tkchin): Figure out why DTLS-SRTP constraint causes thread assertion 197 // crash in Debug. 198 caller.defaultPeerConnectionConstraints = 199 [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:nil 200 optionalConstraints:nil]; 201 weakCaller = caller; 202 203 answerer = [self createAppClientForRoomId:roomId 204 clientId:answererId 205 isInitiator:NO 206 messages:[NSArray array] 207 messageHandler:^(ARDSignalingMessage *message) { 208 ARDAppClient *strongCaller = weakCaller; 209 [strongCaller channel:strongCaller.channel didReceiveMessage:message]; 210 } connectedHandler:^{ 211 [answererConnectionExpectation fulfill]; 212 } localVideoTrackHandler:^{ 213 }]; 214 // TODO(tkchin): Figure out why DTLS-SRTP constraint causes thread assertion 215 // crash in Debug. 216 answerer.defaultPeerConnectionConstraints = 217 [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:nil 218 optionalConstraints:nil]; 219 weakAnswerer = answerer; 220 221 // Kick off connection. 222 [caller connectToRoomWithId:roomId settings:[self mockSettingsModel] isLoopback:NO]; 223 [answerer connectToRoomWithId:roomId settings:[self mockSettingsModel] isLoopback:NO]; 224 [self waitForExpectationsWithTimeout:20 handler:^(NSError *error) { 225 if (error) { 226 XCTFail(@"Expectation failed with error %@.", error); 227 } 228 }]; 229} 230 231// Test to see that we get a local video connection 232// Note this will currently pass even when no camera is connected as a local 233// video track is created regardless (Perhaps there should be a test for that...) 234#if !TARGET_IPHONE_SIMULATOR // Expect to fail on simulator due to no camera support 235- (void)testSessionShouldGetLocalVideoTrackCallback { 236 ARDAppClient *caller = nil; 237 NSString *roomId = @"testRoom"; 238 NSString *callerId = @"testCallerId"; 239 240 XCTestExpectation *localVideoTrackExpectation = 241 [self expectationWithDescription:@"Caller got local video."]; 242 243 caller = [self createAppClientForRoomId:roomId 244 clientId:callerId 245 isInitiator:YES 246 messages:[NSArray array] 247 messageHandler:^(ARDSignalingMessage *message) {} 248 connectedHandler:^{} 249 localVideoTrackHandler:^{ [localVideoTrackExpectation fulfill]; }]; 250 caller.defaultPeerConnectionConstraints = 251 [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] initWithMandatoryConstraints:nil 252 optionalConstraints:nil]; 253 254 // Kick off connection. 255 [caller connectToRoomWithId:roomId 256 settings:[self mockSettingsModel] 257 isLoopback:NO]; 258 [self waitForExpectationsWithTimeout:20 handler:^(NSError *error) { 259 if (error) { 260 XCTFail("Expectation timed out with error: %@.", error); 261 } 262 }]; 263} 264#endif 265 266@end 267