Imported Upstream version 1.33.1
[platform/upstream/grpc.git] / src / objective-c / tests / UnitTests / GRPCClientTests.m
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)
44 static NSString *const kHostAddress = NSStringize(HOST_PORT_LOCAL);
45 static NSString *const kPackage = @"grpc.testing";
46 static NSString *const kService = @"TestService";
47 static NSString *const kRemoteSSLHost = NSStringize(HOST_PORT_REMOTE);
48
49 static GRPCProtoMethod *kInexistentMethod;
50 static GRPCProtoMethod *kEmptyCallMethod;
51 static GRPCProtoMethod *kUnaryCallMethod;
52 static 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