Imported Upstream version 1.21.3
[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 <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 <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:@" ("];
301         expectedUserAgent = [expectedUserAgent stringByAppendingString:@GPR_PLATFORM_STRING];
302         expectedUserAgent = [expectedUserAgent stringByAppendingString:@"; chttp2; "];
303         expectedUserAgent = [expectedUserAgent
304             stringByAppendingString:[NSString stringWithUTF8String:grpc_g_stands_for()]];
305         expectedUserAgent = [expectedUserAgent stringByAppendingString:@")"];
306         XCTAssertEqualObjects(userAgent, expectedUserAgent);
307
308         // Change in format of user-agent field in a direction that does not match the regex will
309         // likely cause problem for certain gRPC users. For details, refer to internal doc
310         // https://goo.gl/c2diBc
311         NSRegularExpression *regex = [NSRegularExpression
312             regularExpressionWithPattern:@" grpc-[a-zA-Z0-9]+(-[a-zA-Z0-9]+)?/[^ ,]+( \\([^)]*\\))?"
313                                  options:0
314                                    error:&error];
315         NSString *customUserAgent =
316             [regex stringByReplacingMatchesInString:userAgent
317                                             options:0
318                                               range:NSMakeRange(0, [userAgent length])
319                                        withTemplate:@""];
320         XCTAssertEqualObjects(customUserAgent, @"Foo");
321
322         [response fulfill];
323       }
324           completionHandler:^(NSError *errorOrNil) {
325             XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
326             [completion fulfill];
327           }];
328
329   [call startWithWriteable:responsesWriteable];
330
331   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
332 }
333
334 - (void)testTrailers {
335   __weak XCTestExpectation *response =
336       [self expectationWithDescription:@"Empty response received."];
337   __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
338
339   GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
340                                              path:kEmptyCallMethod.HTTPPath
341                                    requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
342   // Setting this special key in the header will cause the interop server to echo back the
343   // trailer data.
344   const unsigned char raw_bytes[] = {1, 2, 3, 4};
345   NSData *trailer_data = [NSData dataWithBytes:raw_bytes length:sizeof(raw_bytes)];
346   call.requestHeaders[@"x-grpc-test-echo-trailing-bin"] = trailer_data;
347
348   id<GRXWriteable> responsesWriteable =
349       [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
350         XCTAssertNotNil(value, @"nil value received as response.");
351         XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value);
352         [response fulfill];
353       }
354           completionHandler:^(NSError *errorOrNil) {
355             XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
356             XCTAssertEqualObjects((NSData *)call.responseTrailers[@"x-grpc-test-echo-trailing-bin"],
357                                   trailer_data, @"Did not receive expected trailer");
358             [completion fulfill];
359           }];
360
361   [call startWithWriteable:responsesWriteable];
362   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
363 }
364
365 // TODO(makarandd): Move to a different file that contains only unit tests
366 - (void)testExceptions {
367   GRXWriter *writer = [GRXWriter writerWithValue:[NSData data]];
368   // Try to set parameters to nil for GRPCCall. This should cause an exception
369   @try {
370     (void)[[GRPCCall alloc] initWithHost:nil path:nil requestsWriter:writer];
371     XCTFail(@"Did not receive an exception when parameters are nil");
372   } @catch (NSException *theException) {
373     NSLog(@"Received exception as expected: %@", theException.name);
374   }
375
376   // Set state to Finished by force
377   GRXWriter *requestsWriter = [GRXWriter emptyWriter];
378   [requestsWriter finishWithError:nil];
379   @try {
380     (void)[[GRPCCall alloc] initWithHost:kHostAddress
381                                     path:kUnaryCallMethod.HTTPPath
382                           requestsWriter:requestsWriter];
383     XCTFail(@"Did not receive an exception when GRXWriter has incorrect state.");
384   } @catch (NSException *theException) {
385     NSLog(@"Received exception as expected: %@", theException.name);
386   }
387 }
388
389 - (void)testIdempotentProtoRPC {
390   __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."];
391   __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
392
393   RMTSimpleRequest *request = [RMTSimpleRequest message];
394   request.responseSize = 100;
395   request.fillUsername = YES;
396   request.fillOauthScope = YES;
397   GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
398
399   GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
400                                              path:kUnaryCallMethod.HTTPPath
401                                    requestsWriter:requestsWriter];
402   [GRPCCall setCallSafety:GRPCCallSafetyIdempotentRequest
403                      host:kHostAddress
404                      path:kUnaryCallMethod.HTTPPath];
405
406   id<GRXWriteable> responsesWriteable =
407       [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
408         XCTAssertNotNil(value, @"nil value received as response.");
409         XCTAssertGreaterThan(value.length, 0, @"Empty response received.");
410         RMTSimpleResponse *responseProto = [RMTSimpleResponse parseFromData:value error:NULL];
411         // We expect empty strings, not nil:
412         XCTAssertNotNil(responseProto.username, @"Response's username is nil.");
413         XCTAssertNotNil(responseProto.oauthScope, @"Response's OAuth scope is nil.");
414         [response fulfill];
415       }
416           completionHandler:^(NSError *errorOrNil) {
417             XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
418             [completion fulfill];
419           }];
420
421   [call startWithWriteable:responsesWriteable];
422
423   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
424 }
425
426 - (void)testAlternateDispatchQueue {
427   const int32_t kPayloadSize = 100;
428   RMTSimpleRequest *request = [RMTSimpleRequest message];
429   request.responseSize = kPayloadSize;
430
431   __weak XCTestExpectation *expectation1 =
432       [self expectationWithDescription:@"AlternateDispatchQueue1"];
433
434   // Use default (main) dispatch queue
435   NSString *main_queue_label =
436       [NSString stringWithUTF8String:dispatch_queue_get_label(dispatch_get_main_queue())];
437
438   GRXWriter *requestsWriter1 = [GRXWriter writerWithValue:[request data]];
439
440   GRPCCall *call1 = [[GRPCCall alloc] initWithHost:kHostAddress
441                                               path:kUnaryCallMethod.HTTPPath
442                                     requestsWriter:requestsWriter1];
443
444   id<GRXWriteable> responsesWriteable1 =
445       [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
446         NSString *label =
447             [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
448         XCTAssert([label isEqualToString:main_queue_label]);
449
450         [expectation1 fulfill];
451       }
452                                completionHandler:^(NSError *errorOrNil){
453                                }];
454
455   [call1 startWithWriteable:responsesWriteable1];
456
457   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
458
459   // Use a custom  queue
460   __weak XCTestExpectation *expectation2 =
461       [self expectationWithDescription:@"AlternateDispatchQueue2"];
462
463   NSString *queue_label = @"test.queue1";
464   dispatch_queue_t queue = dispatch_queue_create([queue_label UTF8String], DISPATCH_QUEUE_SERIAL);
465
466   GRXWriter *requestsWriter2 = [GRXWriter writerWithValue:[request data]];
467
468   GRPCCall *call2 = [[GRPCCall alloc] initWithHost:kHostAddress
469                                               path:kUnaryCallMethod.HTTPPath
470                                     requestsWriter:requestsWriter2];
471
472   [call2 setResponseDispatchQueue:queue];
473
474   id<GRXWriteable> responsesWriteable2 =
475       [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
476         NSString *label =
477             [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
478         XCTAssert([label isEqualToString:queue_label]);
479
480         [expectation2 fulfill];
481       }
482                                completionHandler:^(NSError *errorOrNil){
483                                }];
484
485   [call2 startWithWriteable:responsesWriteable2];
486
487   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
488 }
489
490 - (void)testTimeout {
491   __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
492
493   GRXBufferedPipe *pipe = [GRXBufferedPipe pipe];
494   GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
495                                              path:kFullDuplexCallMethod.HTTPPath
496                                    requestsWriter:pipe];
497
498   id<GRXWriteable> responsesWriteable =
499       [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
500         XCTAssert(0, @"Failure: response received; Expect: no response received.");
501       }
502           completionHandler:^(NSError *errorOrNil) {
503             XCTAssertNotNil(errorOrNil,
504                             @"Failure: no error received; Expect: receive deadline exceeded.");
505             XCTAssertEqual(errorOrNil.code, GRPCErrorCodeDeadlineExceeded);
506             [completion fulfill];
507           }];
508
509   call.timeout = 0.001;
510   [call startWithWriteable:responsesWriteable];
511
512   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
513 }
514
515 - (int)findFreePort {
516   struct sockaddr_in addr;
517   unsigned int addr_len = sizeof(addr);
518   memset(&addr, 0, sizeof(addr));
519   addr.sin_family = AF_INET;
520   int fd = socket(AF_INET, SOCK_STREAM, 0);
521   XCTAssertEqual(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), 0);
522   XCTAssertEqual(getsockname(fd, (struct sockaddr *)&addr, &addr_len), 0);
523   XCTAssertEqual(addr_len, sizeof(addr));
524   close(fd);
525   return addr.sin_port;
526 }
527
528 - (void)testErrorCode {
529   int port = [self findFreePort];
530   NSString *const kDummyAddress = [NSString stringWithFormat:@"localhost:%d", port];
531   __weak XCTestExpectation *completion =
532       [self expectationWithDescription:@"Received correct error code."];
533
534   GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress
535                                              path:kEmptyCallMethod.HTTPPath
536                                    requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
537
538   id<GRXWriteable> responsesWriteable =
539       [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
540         // Should not reach here
541         XCTAssert(NO);
542       }
543           completionHandler:^(NSError *errorOrNil) {
544             XCTAssertNotNil(errorOrNil, @"Finished with no error");
545             XCTAssertEqual(errorOrNil.code, GRPC_STATUS_UNAVAILABLE);
546             [completion fulfill];
547           }];
548
549   [call startWithWriteable:responsesWriteable];
550
551   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
552 }
553
554 - (void)testTimeoutBackoffWithTimeout:(double)timeout Backoff:(double)backoff {
555   const double maxConnectTime = timeout > backoff ? timeout : backoff;
556   const double kMargin = 0.1;
557
558   __weak XCTestExpectation *completion = [self expectationWithDescription:@"Timeout in a second."];
559   NSString *const kDummyAddress = [NSString stringWithFormat:@"8.8.8.8:1"];
560   [GRPCCall useInsecureConnectionsForHost:kDummyAddress];
561   [GRPCCall setMinConnectTimeout:timeout * 1000
562                   initialBackoff:backoff * 1000
563                       maxBackoff:0
564                          forHost:kDummyAddress];
565   GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress
566                                              path:@"/dummyPath"
567                                    requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
568   NSDate *startTime = [NSDate date];
569   id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(id value) {
570     XCTAssert(NO, @"Received message. Should not reach here");
571   }
572       completionHandler:^(NSError *errorOrNil) {
573         XCTAssertNotNil(errorOrNil, @"Finished with no error");
574         // The call must fail before maxConnectTime. However there is no lower bound on the time
575         // taken for connection. A shorter time happens when connection is actively refused
576         // by 8.8.8.8:1 before maxConnectTime elapsed.
577         XCTAssertLessThan([[NSDate date] timeIntervalSinceDate:startTime],
578                           maxConnectTime + kMargin);
579         [completion fulfill];
580       }];
581
582   [call startWithWriteable:responsesWriteable];
583
584   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
585 }
586
587 // The numbers of the following three tests are selected to be smaller than the default values of
588 // initial backoff (1s) and min_connect_timeout (20s), so that if they fail we know the default
589 // values fail to be overridden by the channel args.
590 - (void)testTimeoutBackoff1 {
591   [self testTimeoutBackoffWithTimeout:0.7 Backoff:0.3];
592 }
593
594 - (void)testTimeoutBackoff2 {
595   [self testTimeoutBackoffWithTimeout:0.3 Backoff:0.7];
596 }
597
598 - (void)testErrorDebugInformation {
599   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."];
600
601   RMTSimpleRequest *request = [RMTSimpleRequest message];
602   request.fillUsername = YES;
603   request.fillOauthScope = YES;
604   GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
605
606   GRPCCall *call = [[GRPCCall alloc] initWithHost:kRemoteSSLHost
607                                              path:kUnaryCallMethod.HTTPPath
608                                    requestsWriter:requestsWriter];
609
610   call.oauth2AccessToken = @"bogusToken";
611
612   id<GRXWriteable> responsesWriteable =
613       [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
614         XCTFail(@"Received unexpected response: %@", value);
615       }
616           completionHandler:^(NSError *errorOrNil) {
617             XCTAssertNotNil(errorOrNil, @"Finished without error!");
618             NSDictionary *userInfo = errorOrNil.userInfo;
619             NSString *debugInformation = userInfo[NSDebugDescriptionErrorKey];
620             XCTAssertNotNil(debugInformation);
621             XCTAssertNotEqual([debugInformation length], 0);
622             NSString *challengeHeader = call.oauth2ChallengeHeader;
623             XCTAssertGreaterThan(challengeHeader.length, 0, @"No challenge in response headers %@",
624                                  call.responseHeaders);
625             [expectation fulfill];
626           }];
627
628   [call startWithWriteable:responsesWriteable];
629
630   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
631 }
632
633 @end