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