• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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