1/*
2 *
3 * Copyright 2015 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19#import <XCTest/XCTest.h>
20#import <grpc/grpc.h>
21#import <grpc/support/port_platform.h>
22
23#import <GRPCClient/GRPCCall+ChannelArg.h>
24#import <GRPCClient/GRPCCall+OAuth2.h>
25#import <GRPCClient/GRPCCall+Tests.h>
26#import <GRPCClient/GRPCCall.h>
27#import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
28#import <ProtoRPC/ProtoMethod.h>
29#import <RxLibrary/GRXBufferedPipe.h>
30#import <RxLibrary/GRXWriteable.h>
31#import <RxLibrary/GRXWriter+Immediate.h>
32#import "src/objective-c/tests/RemoteTestClient/Messages.pbobjc.h"
33
34#include <netinet/in.h>
35
36#import "../version.h"
37
38#define TEST_TIMEOUT 16
39
40// The server address is derived from preprocessor macro, which is
41// in turn derived from environment variable of the same name.
42#define NSStringize_helper(x) #x
43#define NSStringize(x) @NSStringize_helper(x)
44static NSString *const kHostAddress = NSStringize(HOST_PORT_LOCAL);
45static NSString *const kPackage = @"grpc.testing";
46static NSString *const kService = @"TestService";
47static NSString *const kRemoteSSLHost = NSStringize(HOST_PORT_REMOTE);
48
49static GRPCProtoMethod *kInexistentMethod;
50static GRPCProtoMethod *kEmptyCallMethod;
51static GRPCProtoMethod *kUnaryCallMethod;
52static GRPCProtoMethod *kFullDuplexCallMethod;
53
54/** Observer class for testing that responseMetadata is KVO-compliant */
55@interface PassthroughObserver : NSObject
56- (instancetype)initWithCallback:(void (^)(NSString *, id, NSDictionary *))callback
57    NS_DESIGNATED_INITIALIZER;
58
59- (void)observeValueForKeyPath:(NSString *)keyPath
60                      ofObject:(id)object
61                        change:(NSDictionary *)change
62                       context:(void *)context;
63@end
64
65@implementation PassthroughObserver {
66  void (^_callback)(NSString *, id, NSDictionary *);
67}
68
69- (instancetype)init {
70  return [self initWithCallback:nil];
71}
72
73- (instancetype)initWithCallback:(void (^)(NSString *, id, NSDictionary *))callback {
74  if (!callback) {
75    return nil;
76  }
77  if ((self = [super init])) {
78    _callback = callback;
79  }
80  return self;
81}
82
83- (void)observeValueForKeyPath:(NSString *)keyPath
84                      ofObject:(id)object
85                        change:(NSDictionary *)change
86                       context:(void *)context {
87  _callback(keyPath, object, change);
88  [object removeObserver:self forKeyPath:keyPath];
89}
90
91@end
92
93#pragma mark Tests
94
95/**
96 * A few tests similar to InteropTests, but which use the generic gRPC client (GRPCCall) rather than
97 * a generated proto library on top of it. Its RPCs are sent to a local cleartext server.
98 *
99 * TODO(jcanizales): Run them also against a local SSL server and against a remote server.
100 */
101@interface GRPCClientTests : XCTestCase
102@end
103
104@implementation GRPCClientTests
105
106+ (void)setUp {
107  NSLog(@"GRPCClientTests Started");
108}
109
110- (void)setUp {
111  // Add a custom user agent prefix that will be used in test
112  [GRPCCall setUserAgentPrefix:@"Foo" forHost:kHostAddress];
113  // Register test server as non-SSL.
114  [GRPCCall useInsecureConnectionsForHost:kHostAddress];
115
116  // This method isn't implemented by the remote server.
117  kInexistentMethod = [[GRPCProtoMethod alloc] initWithPackage:kPackage
118                                                       service:kService
119                                                        method:@"Inexistent"];
120  kEmptyCallMethod = [[GRPCProtoMethod alloc] initWithPackage:kPackage
121                                                      service:kService
122                                                       method:@"EmptyCall"];
123  kUnaryCallMethod = [[GRPCProtoMethod alloc] initWithPackage:kPackage
124                                                      service:kService
125                                                       method:@"UnaryCall"];
126  kFullDuplexCallMethod = [[GRPCProtoMethod alloc] initWithPackage:kPackage
127                                                           service:kService
128                                                            method:@"FullDuplexCall"];
129}
130
131- (void)testConnectionToRemoteServer {
132  __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Server reachable."];
133
134  GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
135                                             path:kInexistentMethod.HTTPPath
136                                   requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
137
138  id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc]
139      initWithValueHandler:^(NSData *value) {
140        XCTFail(@"Received unexpected response: %@", value);
141      }
142      completionHandler:^(NSError *errorOrNil) {
143        XCTAssertNotNil(errorOrNil, @"Finished without error!");
144        XCTAssertEqual(errorOrNil.code, 12, @"Finished with unexpected error: %@", errorOrNil);
145        [expectation fulfill];
146      }];
147
148  [call startWithWriteable:responsesWriteable];
149
150  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
151}
152
153- (void)testEmptyRPC {
154  __weak XCTestExpectation *response =
155      [self expectationWithDescription:@"Empty response received."];
156  __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
157
158  GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
159                                             path:kEmptyCallMethod.HTTPPath
160                                   requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
161
162  id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc]
163      initWithValueHandler:^(NSData *value) {
164        XCTAssertNotNil(value, @"nil value received as response.");
165        XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value);
166        [response fulfill];
167      }
168      completionHandler:^(NSError *errorOrNil) {
169        XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
170        [completion fulfill];
171      }];
172
173  [call startWithWriteable:responsesWriteable];
174
175  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
176}
177
178- (void)testSimpleProtoRPC {
179  __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."];
180  __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
181
182  RMTSimpleRequest *request = [RMTSimpleRequest message];
183  request.responseSize = 100;
184  request.fillUsername = YES;
185  request.fillOauthScope = YES;
186  GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
187
188  GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
189                                             path:kUnaryCallMethod.HTTPPath
190                                   requestsWriter:requestsWriter];
191
192  id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc]
193      initWithValueHandler:^(NSData *value) {
194        XCTAssertNotNil(value, @"nil value received as response.");
195        XCTAssertGreaterThan(value.length, 0, @"Empty response received.");
196        RMTSimpleResponse *responseProto = [RMTSimpleResponse parseFromData:value error:NULL];
197        // We expect empty strings, not nil:
198        XCTAssertNotNil(responseProto.username, @"Response's username is nil.");
199        XCTAssertNotNil(responseProto.oauthScope, @"Response's OAuth scope is nil.");
200        [response fulfill];
201      }
202      completionHandler:^(NSError *errorOrNil) {
203        XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
204        [completion fulfill];
205      }];
206
207  [call startWithWriteable:responsesWriteable];
208
209  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
210}
211
212- (void)testMetadata {
213  __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."];
214
215  RMTSimpleRequest *request = [RMTSimpleRequest message];
216  request.fillUsername = YES;
217  request.fillOauthScope = YES;
218  GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
219
220  GRPCCall *call = [[GRPCCall alloc] initWithHost:kRemoteSSLHost
221                                             path:kUnaryCallMethod.HTTPPath
222                                   requestsWriter:requestsWriter];
223
224  call.oauth2AccessToken = @"bogusToken";
225
226  id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc]
227      initWithValueHandler:^(NSData *value) {
228        XCTFail(@"Received unexpected response: %@", value);
229      }
230      completionHandler:^(NSError *errorOrNil) {
231        XCTAssertNotNil(errorOrNil, @"Finished without error!");
232        XCTAssertEqual(errorOrNil.code, 16, @"Finished with unexpected error: %@", errorOrNil);
233        XCTAssertEqualObjects(call.responseHeaders, errorOrNil.userInfo[kGRPCHeadersKey],
234                              @"Headers in the NSError object and call object differ.");
235        XCTAssertEqualObjects(call.responseTrailers, errorOrNil.userInfo[kGRPCTrailersKey],
236                              @"Trailers in the NSError object and call object differ.");
237        NSString *challengeHeader = call.oauth2ChallengeHeader;
238        XCTAssertGreaterThan(challengeHeader.length, 0, @"No challenge in response headers %@",
239                             call.responseHeaders);
240        [expectation fulfill];
241      }];
242
243  [call startWithWriteable:responsesWriteable];
244
245  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
246}
247
248- (void)testResponseMetadataKVO {
249  __weak XCTestExpectation *response =
250      [self expectationWithDescription:@"Empty response received."];
251  __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
252  __weak XCTestExpectation *metadata = [self expectationWithDescription:@"Metadata changed."];
253
254  GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
255                                             path:kEmptyCallMethod.HTTPPath
256                                   requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
257
258  PassthroughObserver *observer = [[PassthroughObserver alloc]
259      initWithCallback:^(NSString *keypath, id object, NSDictionary *change) {
260        if ([keypath isEqual:@"responseHeaders"]) {
261          [metadata fulfill];
262        }
263      }];
264
265  [call addObserver:observer forKeyPath:@"responseHeaders" options:0 context:NULL];
266
267  id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc]
268      initWithValueHandler:^(NSData *value) {
269        XCTAssertNotNil(value, @"nil value received as response.");
270        XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value);
271        [response fulfill];
272      }
273      completionHandler:^(NSError *errorOrNil) {
274        XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
275        [completion fulfill];
276      }];
277
278  [call startWithWriteable:responsesWriteable];
279
280  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
281}
282
283- (void)testUserAgentPrefix {
284  __weak XCTestExpectation *response =
285      [self expectationWithDescription:@"Empty response received."];
286  __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
287
288  GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
289                                             path:kEmptyCallMethod.HTTPPath
290                                   requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
291  // Setting this special key in the header will cause the interop server to echo back the
292  // user-agent value, which we confirm.
293  call.requestHeaders[@"x-grpc-test-echo-useragent"] = @"";
294
295  id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc]
296      initWithValueHandler:^(NSData *value) {
297        XCTAssertNotNil(value, @"nil value received as response.");
298        XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value);
299
300        NSString *userAgent = call.responseHeaders[@"x-grpc-test-echo-useragent"];
301        NSError *error = nil;
302
303        // Test the regex is correct
304        NSString *expectedUserAgent = @"Foo grpc-objc/";
305        expectedUserAgent = [expectedUserAgent stringByAppendingString:GRPC_OBJC_VERSION_STRING];
306        expectedUserAgent = [expectedUserAgent stringByAppendingString:@" grpc-c/"];
307        expectedUserAgent = [expectedUserAgent stringByAppendingString:GRPC_C_VERSION_STRING];
308        expectedUserAgent = [expectedUserAgent stringByAppendingString:@" ("];
309        expectedUserAgent = [expectedUserAgent stringByAppendingString:@GPR_PLATFORM_STRING];
310        expectedUserAgent = [expectedUserAgent stringByAppendingString:@"; chttp2)"];
311        XCTAssertEqualObjects(userAgent, expectedUserAgent);
312
313        // Change in format of user-agent field in a direction that does not match the regex will
314        // likely cause problem for certain gRPC users. For details, refer to internal doc
315        // https://goo.gl/c2diBc
316        NSRegularExpression *regex = [NSRegularExpression
317            regularExpressionWithPattern:@" grpc-[a-zA-Z0-9]+(-[a-zA-Z0-9]+)?/[^ ,]+( \\([^)]*\\))?"
318                                 options:0
319                                   error:&error];
320        NSString *customUserAgent =
321            [regex stringByReplacingMatchesInString:userAgent
322                                            options:0
323                                              range:NSMakeRange(0, [userAgent length])
324                                       withTemplate:@""];
325        XCTAssertEqualObjects(customUserAgent, @"Foo");
326
327        [response fulfill];
328      }
329      completionHandler:^(NSError *errorOrNil) {
330        XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
331        [completion fulfill];
332      }];
333
334  [call startWithWriteable:responsesWriteable];
335
336  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
337}
338
339- (void)testTrailers {
340  __weak XCTestExpectation *response =
341      [self expectationWithDescription:@"Empty response received."];
342  __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
343
344  GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
345                                             path:kEmptyCallMethod.HTTPPath
346                                   requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
347  // Setting this special key in the header will cause the interop server to echo back the
348  // trailer data.
349  const unsigned char raw_bytes[] = {1, 2, 3, 4};
350  NSData *trailer_data = [NSData dataWithBytes:raw_bytes length:sizeof(raw_bytes)];
351  call.requestHeaders[@"x-grpc-test-echo-trailing-bin"] = trailer_data;
352
353  id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc]
354      initWithValueHandler:^(NSData *value) {
355        XCTAssertNotNil(value, @"nil value received as response.");
356        XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value);
357        [response fulfill];
358      }
359      completionHandler:^(NSError *errorOrNil) {
360        XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
361        XCTAssertEqualObjects((NSData *)call.responseTrailers[@"x-grpc-test-echo-trailing-bin"],
362                              trailer_data, @"Did not receive expected trailer");
363        [completion fulfill];
364      }];
365
366  [call startWithWriteable:responsesWriteable];
367  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
368}
369
370// TODO(makarandd): Move to a different file that contains only unit tests
371- (void)testExceptions {
372  GRXWriter *writer = [GRXWriter writerWithValue:[NSData data]];
373  // Try to set parameters to nil for GRPCCall. This should cause an exception
374  @try {
375    (void)[[GRPCCall alloc] initWithHost:nil path:nil requestsWriter:writer];
376    XCTFail(@"Did not receive an exception when parameters are nil");
377  } @catch (NSException *theException) {
378    NSLog(@"Received exception as expected: %@", theException.name);
379  }
380
381  // Set state to Finished by force
382  GRXWriter *requestsWriter = [GRXWriter emptyWriter];
383  [requestsWriter finishWithError:nil];
384  @try {
385    (void)[[GRPCCall alloc] initWithHost:kHostAddress
386                                    path:kUnaryCallMethod.HTTPPath
387                          requestsWriter:requestsWriter];
388    XCTFail(@"Did not receive an exception when GRXWriter has incorrect state.");
389  } @catch (NSException *theException) {
390    NSLog(@"Received exception as expected: %@", theException.name);
391  }
392}
393
394- (void)testIdempotentProtoRPC {
395  __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."];
396  __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
397
398  RMTSimpleRequest *request = [RMTSimpleRequest message];
399  request.responseSize = 100;
400  request.fillUsername = YES;
401  request.fillOauthScope = YES;
402  GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
403
404  GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
405                                             path:kUnaryCallMethod.HTTPPath
406                                   requestsWriter:requestsWriter];
407  [GRPCCall setCallSafety:GRPCCallSafetyIdempotentRequest
408                     host:kHostAddress
409                     path:kUnaryCallMethod.HTTPPath];
410
411  id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc]
412      initWithValueHandler:^(NSData *value) {
413        XCTAssertNotNil(value, @"nil value received as response.");
414        XCTAssertGreaterThan(value.length, 0, @"Empty response received.");
415        RMTSimpleResponse *responseProto = [RMTSimpleResponse parseFromData:value error:NULL];
416        // We expect empty strings, not nil:
417        XCTAssertNotNil(responseProto.username, @"Response's username is nil.");
418        XCTAssertNotNil(responseProto.oauthScope, @"Response's OAuth scope is nil.");
419        [response fulfill];
420      }
421      completionHandler:^(NSError *errorOrNil) {
422        XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
423        [completion fulfill];
424      }];
425
426  [call startWithWriteable:responsesWriteable];
427
428  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
429}
430
431- (void)testAlternateDispatchQueue {
432  const int32_t kPayloadSize = 100;
433  RMTSimpleRequest *request = [RMTSimpleRequest message];
434  request.responseSize = kPayloadSize;
435
436  __weak XCTestExpectation *expectation1 =
437      [self expectationWithDescription:@"AlternateDispatchQueue1"];
438
439  // Use default (main) dispatch queue
440  NSString *main_queue_label =
441      [NSString stringWithUTF8String:dispatch_queue_get_label(dispatch_get_main_queue())];
442
443  GRXWriter *requestsWriter1 = [GRXWriter writerWithValue:[request data]];
444
445  GRPCCall *call1 = [[GRPCCall alloc] initWithHost:kHostAddress
446                                              path:kUnaryCallMethod.HTTPPath
447                                    requestsWriter:requestsWriter1];
448
449  id<GRXWriteable> responsesWriteable1 = [[GRXWriteable alloc]
450      initWithValueHandler:^(NSData *value) {
451        NSString *label =
452            [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
453        XCTAssert([label isEqualToString:main_queue_label]);
454
455        [expectation1 fulfill];
456      }
457         completionHandler:^(NSError *errorOrNil){
458         }];
459
460  [call1 startWithWriteable:responsesWriteable1];
461
462  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
463
464  // Use a custom  queue
465  __weak XCTestExpectation *expectation2 =
466      [self expectationWithDescription:@"AlternateDispatchQueue2"];
467
468  NSString *queue_label = @"test.queue1";
469  dispatch_queue_t queue = dispatch_queue_create([queue_label UTF8String], DISPATCH_QUEUE_SERIAL);
470
471  GRXWriter *requestsWriter2 = [GRXWriter writerWithValue:[request data]];
472
473  GRPCCall *call2 = [[GRPCCall alloc] initWithHost:kHostAddress
474                                              path:kUnaryCallMethod.HTTPPath
475                                    requestsWriter:requestsWriter2];
476
477  [call2 setResponseDispatchQueue:queue];
478
479  id<GRXWriteable> responsesWriteable2 = [[GRXWriteable alloc]
480      initWithValueHandler:^(NSData *value) {
481        NSString *label =
482            [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
483        XCTAssert([label isEqualToString:queue_label]);
484
485        [expectation2 fulfill];
486      }
487         completionHandler:^(NSError *errorOrNil){
488         }];
489
490  [call2 startWithWriteable:responsesWriteable2];
491
492  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
493}
494
495- (void)testTimeout {
496  __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
497
498  GRXBufferedPipe *pipe = [GRXBufferedPipe pipe];
499  GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
500                                             path:kFullDuplexCallMethod.HTTPPath
501                                   requestsWriter:pipe];
502
503  id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc]
504      initWithValueHandler:^(NSData *value) {
505        XCTAssert(0, @"Failure: response received; Expect: no response received.");
506      }
507      completionHandler:^(NSError *errorOrNil) {
508        XCTAssertNotNil(errorOrNil,
509                        @"Failure: no error received; Expect: receive deadline exceeded.");
510        XCTAssertEqual(errorOrNil.code, GRPCErrorCodeDeadlineExceeded);
511        [completion fulfill];
512      }];
513
514  call.timeout = 0.001;
515  [call startWithWriteable:responsesWriteable];
516
517  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
518}
519
520- (int)findFreePort {
521  struct sockaddr_in addr;
522  unsigned int addr_len = sizeof(addr);
523  memset(&addr, 0, sizeof(addr));
524  addr.sin_family = AF_INET;
525  int fd = socket(AF_INET, SOCK_STREAM, 0);
526  XCTAssertEqual(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), 0);
527  XCTAssertEqual(getsockname(fd, (struct sockaddr *)&addr, &addr_len), 0);
528  XCTAssertEqual(addr_len, sizeof(addr));
529  close(fd);
530  return addr.sin_port;
531}
532
533- (void)testErrorCode {
534  int port = [self findFreePort];
535  NSString *const kDummyAddress = [NSString stringWithFormat:@"localhost:%d", port];
536  __weak XCTestExpectation *completion =
537      [self expectationWithDescription:@"Received correct error code."];
538
539  GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress
540                                             path:kEmptyCallMethod.HTTPPath
541                                   requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
542
543  id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc]
544      initWithValueHandler:^(NSData *value) {
545        // Should not reach here
546        XCTAssert(NO);
547      }
548      completionHandler:^(NSError *errorOrNil) {
549        XCTAssertNotNil(errorOrNil, @"Finished with no error");
550        XCTAssertEqual(errorOrNil.code, GRPC_STATUS_UNAVAILABLE);
551        [completion fulfill];
552      }];
553
554  [call startWithWriteable:responsesWriteable];
555
556  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
557}
558
559- (void)testTimeoutBackoffWithTimeout:(double)timeout Backoff:(double)backoff {
560  const double maxConnectTime = timeout > backoff ? timeout : backoff;
561  const double kMargin = 0.1;
562
563  __weak XCTestExpectation *completion = [self expectationWithDescription:@"Timeout in a second."];
564  NSString *const kDummyAddress = [NSString stringWithFormat:@"8.8.8.8:1"];
565  [GRPCCall useInsecureConnectionsForHost:kDummyAddress];
566  [GRPCCall setMinConnectTimeout:timeout * 1000
567                  initialBackoff:backoff * 1000
568                      maxBackoff:0
569                         forHost:kDummyAddress];
570  GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress
571                                             path:@"/dummyPath"
572                                   requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
573  NSDate *startTime = [NSDate date];
574  id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc]
575      initWithValueHandler:^(id value) {
576        XCTAssert(NO, @"Received message. Should not reach here");
577      }
578      completionHandler:^(NSError *errorOrNil) {
579        XCTAssertNotNil(errorOrNil, @"Finished with no error");
580        // The call must fail before maxConnectTime. However there is no lower bound on the time
581        // taken for connection. A shorter time happens when connection is actively refused
582        // by 8.8.8.8:1 before maxConnectTime elapsed.
583        XCTAssertLessThan([[NSDate date] timeIntervalSinceDate:startTime],
584                          maxConnectTime + kMargin);
585        [completion fulfill];
586      }];
587
588  [call startWithWriteable:responsesWriteable];
589
590  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
591}
592
593// The numbers of the following three tests are selected to be smaller than the default values of
594// initial backoff (1s) and min_connect_timeout (20s), so that if they fail we know the default
595// values fail to be overridden by the channel args.
596- (void)testTimeoutBackoff1 {
597  [self testTimeoutBackoffWithTimeout:0.7 Backoff:0.3];
598}
599
600- (void)testTimeoutBackoff2 {
601  [self testTimeoutBackoffWithTimeout:0.3 Backoff:0.7];
602}
603
604- (void)testErrorDebugInformation {
605  __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."];
606
607  RMTSimpleRequest *request = [RMTSimpleRequest message];
608  request.fillUsername = YES;
609  request.fillOauthScope = YES;
610  GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
611
612  GRPCCall *call = [[GRPCCall alloc] initWithHost:kRemoteSSLHost
613                                             path:kUnaryCallMethod.HTTPPath
614                                   requestsWriter:requestsWriter];
615
616  call.oauth2AccessToken = @"bogusToken";
617
618  id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc]
619      initWithValueHandler:^(NSData *value) {
620        XCTFail(@"Received unexpected response: %@", value);
621      }
622      completionHandler:^(NSError *errorOrNil) {
623        XCTAssertNotNil(errorOrNil, @"Finished without error!");
624        NSDictionary *userInfo = errorOrNil.userInfo;
625        NSString *debugInformation = userInfo[NSDebugDescriptionErrorKey];
626        XCTAssertNotNil(debugInformation);
627        XCTAssertNotEqual([debugInformation length], 0);
628        NSString *challengeHeader = call.oauth2ChallengeHeader;
629        XCTAssertGreaterThan(challengeHeader.length, 0, @"No challenge in response headers %@",
630                             call.responseHeaders);
631        [expectation fulfill];
632      }];
633
634  [call startWithWriteable:responsesWriteable];
635
636  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
637}
638
639@end
640