• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
40static NSString *const kHostAddress = @"localhost:5050";
41static NSString *const kPackage = @"grpc.testing";
42static NSString *const kService = @"TestService";
43static NSString *const kRemoteSSLHost = @"grpc-test.sandbox.googleapis.com";
44
45static GRPCProtoMethod *kInexistentMethod;
46static GRPCProtoMethod *kEmptyCallMethod;
47static GRPCProtoMethod *kUnaryCallMethod;
48static 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  // Try to set parameters to nil for GRPCCall. This should cause an exception
366  @try {
367    (void)[[GRPCCall alloc] initWithHost:nil path:nil requestsWriter:nil];
368    XCTFail(@"Did not receive an exception when parameters are nil");
369  } @catch (NSException *theException) {
370    NSLog(@"Received exception as expected: %@", theException.name);
371  }
372
373  // Set state to Finished by force
374  GRXWriter *requestsWriter = [GRXWriter emptyWriter];
375  [requestsWriter finishWithError:nil];
376  @try {
377    (void)[[GRPCCall alloc] initWithHost:kHostAddress
378                                    path:kUnaryCallMethod.HTTPPath
379                          requestsWriter:requestsWriter];
380    XCTFail(@"Did not receive an exception when GRXWriter has incorrect state.");
381  } @catch (NSException *theException) {
382    NSLog(@"Received exception as expected: %@", theException.name);
383  }
384}
385
386- (void)testIdempotentProtoRPC {
387  __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."];
388  __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
389
390  RMTSimpleRequest *request = [RMTSimpleRequest message];
391  request.responseSize = 100;
392  request.fillUsername = YES;
393  request.fillOauthScope = YES;
394  GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
395
396  GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
397                                             path:kUnaryCallMethod.HTTPPath
398                                   requestsWriter:requestsWriter];
399  [GRPCCall setCallSafety:GRPCCallSafetyIdempotentRequest
400                     host:kHostAddress
401                     path:kUnaryCallMethod.HTTPPath];
402
403  id<GRXWriteable> responsesWriteable =
404      [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
405        XCTAssertNotNil(value, @"nil value received as response.");
406        XCTAssertGreaterThan(value.length, 0, @"Empty response received.");
407        RMTSimpleResponse *responseProto = [RMTSimpleResponse parseFromData:value error:NULL];
408        // We expect empty strings, not nil:
409        XCTAssertNotNil(responseProto.username, @"Response's username is nil.");
410        XCTAssertNotNil(responseProto.oauthScope, @"Response's OAuth scope is nil.");
411        [response fulfill];
412      }
413          completionHandler:^(NSError *errorOrNil) {
414            XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
415            [completion fulfill];
416          }];
417
418  [call startWithWriteable:responsesWriteable];
419
420  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
421}
422
423- (void)testAlternateDispatchQueue {
424  const int32_t kPayloadSize = 100;
425  RMTSimpleRequest *request = [RMTSimpleRequest message];
426  request.responseSize = kPayloadSize;
427
428  __weak XCTestExpectation *expectation1 =
429      [self expectationWithDescription:@"AlternateDispatchQueue1"];
430
431  // Use default (main) dispatch queue
432  NSString *main_queue_label =
433      [NSString stringWithUTF8String:dispatch_queue_get_label(dispatch_get_main_queue())];
434
435  GRXWriter *requestsWriter1 = [GRXWriter writerWithValue:[request data]];
436
437  GRPCCall *call1 = [[GRPCCall alloc] initWithHost:kHostAddress
438                                              path:kUnaryCallMethod.HTTPPath
439                                    requestsWriter:requestsWriter1];
440
441  id<GRXWriteable> responsesWriteable1 =
442      [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
443        NSString *label =
444            [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
445        XCTAssert([label isEqualToString:main_queue_label]);
446
447        [expectation1 fulfill];
448      }
449                               completionHandler:^(NSError *errorOrNil){
450                               }];
451
452  [call1 startWithWriteable:responsesWriteable1];
453
454  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
455
456  // Use a custom  queue
457  __weak XCTestExpectation *expectation2 =
458      [self expectationWithDescription:@"AlternateDispatchQueue2"];
459
460  NSString *queue_label = @"test.queue1";
461  dispatch_queue_t queue = dispatch_queue_create([queue_label UTF8String], DISPATCH_QUEUE_SERIAL);
462
463  GRXWriter *requestsWriter2 = [GRXWriter writerWithValue:[request data]];
464
465  GRPCCall *call2 = [[GRPCCall alloc] initWithHost:kHostAddress
466                                              path:kUnaryCallMethod.HTTPPath
467                                    requestsWriter:requestsWriter2];
468
469  [call2 setResponseDispatchQueue:queue];
470
471  id<GRXWriteable> responsesWriteable2 =
472      [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
473        NSString *label =
474            [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
475        XCTAssert([label isEqualToString:queue_label]);
476
477        [expectation2 fulfill];
478      }
479                               completionHandler:^(NSError *errorOrNil){
480                               }];
481
482  [call2 startWithWriteable:responsesWriteable2];
483
484  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
485}
486
487- (void)testTimeout {
488  __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
489
490  GRXBufferedPipe *pipe = [GRXBufferedPipe pipe];
491  GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
492                                             path:kFullDuplexCallMethod.HTTPPath
493                                   requestsWriter:pipe];
494
495  id<GRXWriteable> responsesWriteable =
496      [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
497        XCTAssert(0, @"Failure: response received; Expect: no response received.");
498      }
499          completionHandler:^(NSError *errorOrNil) {
500            XCTAssertNotNil(errorOrNil,
501                            @"Failure: no error received; Expect: receive deadline exceeded.");
502            XCTAssertEqual(errorOrNil.code, GRPCErrorCodeDeadlineExceeded);
503            [completion fulfill];
504          }];
505
506  call.timeout = 0.001;
507  [call startWithWriteable:responsesWriteable];
508
509  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
510}
511
512- (int)findFreePort {
513  struct sockaddr_in addr;
514  unsigned int addr_len = sizeof(addr);
515  memset(&addr, 0, sizeof(addr));
516  addr.sin_family = AF_INET;
517  int fd = socket(AF_INET, SOCK_STREAM, 0);
518  XCTAssertEqual(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), 0);
519  XCTAssertEqual(getsockname(fd, (struct sockaddr *)&addr, &addr_len), 0);
520  XCTAssertEqual(addr_len, sizeof(addr));
521  close(fd);
522  return addr.sin_port;
523}
524
525- (void)testErrorCode {
526  int port = [self findFreePort];
527  NSString *const kDummyAddress = [NSString stringWithFormat:@"localhost:%d", port];
528  __weak XCTestExpectation *completion =
529      [self expectationWithDescription:@"Received correct error code."];
530
531  GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress
532                                             path:kEmptyCallMethod.HTTPPath
533                                   requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
534
535  id<GRXWriteable> responsesWriteable =
536      [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
537        // Should not reach here
538        XCTAssert(NO);
539      }
540          completionHandler:^(NSError *errorOrNil) {
541            XCTAssertNotNil(errorOrNil, @"Finished with no error");
542            XCTAssertEqual(errorOrNil.code, GRPC_STATUS_UNAVAILABLE);
543            [completion fulfill];
544          }];
545
546  [call startWithWriteable:responsesWriteable];
547
548  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
549}
550
551- (void)testTimeoutBackoffWithTimeout:(double)timeout Backoff:(double)backoff {
552  const double maxConnectTime = timeout > backoff ? timeout : backoff;
553  const double kMargin = 0.1;
554
555  __weak XCTestExpectation *completion = [self expectationWithDescription:@"Timeout in a second."];
556  NSString *const kDummyAddress = [NSString stringWithFormat:@"8.8.8.8:1"];
557  GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress
558                                             path:@""
559                                   requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
560  [GRPCCall setMinConnectTimeout:timeout * 1000
561                  initialBackoff:backoff * 1000
562                      maxBackoff:0
563                         forHost:kDummyAddress];
564  NSDate *startTime = [NSDate date];
565  id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(id value) {
566    XCTAssert(NO, @"Received message. Should not reach here");
567  }
568      completionHandler:^(NSError *errorOrNil) {
569        XCTAssertNotNil(errorOrNil, @"Finished with no error");
570        // The call must fail before maxConnectTime. However there is no lower bound on the time
571        // taken for connection. A shorter time happens when connection is actively refused
572        // by 8.8.8.8:1 before maxConnectTime elapsed.
573        XCTAssertLessThan([[NSDate date] timeIntervalSinceDate:startTime],
574                          maxConnectTime + kMargin);
575        [completion fulfill];
576      }];
577
578  [call startWithWriteable:responsesWriteable];
579
580  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
581}
582
583// The numbers of the following three tests are selected to be smaller than the default values of
584// initial backoff (1s) and min_connect_timeout (20s), so that if they fail we know the default
585// values fail to be overridden by the channel args.
586- (void)testTimeoutBackoff2 {
587  [self testTimeoutBackoffWithTimeout:0.7 Backoff:0.3];
588}
589
590- (void)testTimeoutBackoff3 {
591  [self testTimeoutBackoffWithTimeout:0.3 Backoff:0.7];
592}
593
594- (void)testErrorDebugInformation {
595  __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."];
596
597  RMTSimpleRequest *request = [RMTSimpleRequest message];
598  request.fillUsername = YES;
599  request.fillOauthScope = YES;
600  GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
601
602  GRPCCall *call = [[GRPCCall alloc] initWithHost:kRemoteSSLHost
603                                             path:kUnaryCallMethod.HTTPPath
604                                   requestsWriter:requestsWriter];
605
606  call.oauth2AccessToken = @"bogusToken";
607
608  id<GRXWriteable> responsesWriteable =
609      [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
610        XCTFail(@"Received unexpected response: %@", value);
611      }
612          completionHandler:^(NSError *errorOrNil) {
613            XCTAssertNotNil(errorOrNil, @"Finished without error!");
614            NSDictionary *userInfo = errorOrNil.userInfo;
615            NSString *debugInformation = userInfo[NSDebugDescriptionErrorKey];
616            XCTAssertNotNil(debugInformation);
617            XCTAssertNotEqual([debugInformation length], 0);
618            NSString *challengeHeader = call.oauth2ChallengeHeader;
619            XCTAssertGreaterThan(challengeHeader.length, 0, @"No challenge in response headers %@",
620                                 call.responseHeaders);
621            [expectation fulfill];
622          }];
623
624  [call startWithWriteable:responsesWriteable];
625
626  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
627}
628
629@end
630