ca7bf4728304d3b7adf8e145f0b0a48b3ef4a33e
[platform/upstream/grpc.git] / src / objective-c / tests / APIv2Tests / APIv2Tests.m
1 /*
2  *
3  * Copyright 2018 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 <GRPCClient/GRPCCall.h>
20 #import <ProtoRPC/ProtoMethod.h>
21 #import <RemoteTest/Messages.pbobjc.h>
22 #import <XCTest/XCTest.h>
23
24 #include <grpc/grpc.h>
25
26 #import "../version.h"
27
28 // The server address is derived from preprocessor macro, which is
29 // in turn derived from environment variable of the same name.
30 #define NSStringize_helper(x) #x
31 #define NSStringize(x) @NSStringize_helper(x)
32 static NSString *const kHostAddress = NSStringize(HOST_PORT_LOCAL);
33 static NSString *const kRemoteSSLHost = NSStringize(HOST_PORT_REMOTE);
34
35 // Package and service name of test server
36 static NSString *const kPackage = @"grpc.testing";
37 static NSString *const kService = @"TestService";
38
39 static GRPCProtoMethod *kInexistentMethod;
40 static GRPCProtoMethod *kEmptyCallMethod;
41 static GRPCProtoMethod *kUnaryCallMethod;
42 static GRPCProtoMethod *kFullDuplexCallMethod;
43
44 static const int kSimpleDataLength = 100;
45
46 static const NSTimeInterval kTestTimeout = 16;
47
48 // Reveal the _class ivar for testing access
49 @interface GRPCCall2 () {
50  @public
51   GRPCCall *_call;
52 }
53
54 @end
55
56 // Convenience class to use blocks as callbacks
57 @interface ClientTestsBlockCallbacks : NSObject<GRPCResponseHandler>
58
59 - (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
60                                 messageCallback:(void (^)(id))messageCallback
61                                   closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback;
62
63 @end
64
65 @implementation ClientTestsBlockCallbacks {
66   void (^_initialMetadataCallback)(NSDictionary *);
67   void (^_messageCallback)(id);
68   void (^_closeCallback)(NSDictionary *, NSError *);
69   dispatch_queue_t _dispatchQueue;
70 }
71
72 - (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
73                                 messageCallback:(void (^)(id))messageCallback
74                                   closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback {
75   if ((self = [super init])) {
76     _initialMetadataCallback = initialMetadataCallback;
77     _messageCallback = messageCallback;
78     _closeCallback = closeCallback;
79     _dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);
80   }
81   return self;
82 }
83
84 - (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
85   if (self->_initialMetadataCallback) {
86     self->_initialMetadataCallback(initialMetadata);
87   }
88 }
89
90 - (void)didReceiveRawMessage:(GPBMessage *)message {
91   if (self->_messageCallback) {
92     self->_messageCallback(message);
93   }
94 }
95
96 - (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
97   if (self->_closeCallback) {
98     self->_closeCallback(trailingMetadata, error);
99   }
100 }
101
102 - (dispatch_queue_t)dispatchQueue {
103   return _dispatchQueue;
104 }
105
106 @end
107
108 @interface CallAPIv2Tests : XCTestCase<GRPCAuthorizationProtocol>
109
110 @end
111
112 @implementation CallAPIv2Tests
113
114 - (void)setUp {
115   // This method isn't implemented by the remote server.
116   kInexistentMethod =
117       [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"Inexistent"];
118   kEmptyCallMethod =
119       [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"EmptyCall"];
120   kUnaryCallMethod =
121       [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"UnaryCall"];
122   kFullDuplexCallMethod =
123       [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"FullDuplexCall"];
124 }
125
126 - (void)testMetadata {
127   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."];
128
129   RMTSimpleRequest *request = [RMTSimpleRequest message];
130   request.fillUsername = YES;
131   request.fillOauthScope = YES;
132
133   GRPCRequestOptions *callRequest =
134       [[GRPCRequestOptions alloc] initWithHost:(NSString *)kRemoteSSLHost
135                                           path:kUnaryCallMethod.HTTPPath
136                                         safety:GRPCCallSafetyDefault];
137   __block NSDictionary *init_md;
138   __block NSDictionary *trailing_md;
139   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
140   options.oauth2AccessToken = @"bogusToken";
141   GRPCCall2 *call = [[GRPCCall2 alloc]
142       initWithRequestOptions:callRequest
143              responseHandler:[[ClientTestsBlockCallbacks alloc]
144                                  initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) {
145                                    init_md = initialMetadata;
146                                  }
147                                  messageCallback:^(id message) {
148                                    XCTFail(@"Received unexpected response.");
149                                  }
150                                  closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
151                                    trailing_md = trailingMetadata;
152                                    if (error) {
153                                      XCTAssertEqual(error.code, 16,
154                                                     @"Finished with unexpected error: %@", error);
155                                      XCTAssertEqualObjects(init_md,
156                                                            error.userInfo[kGRPCHeadersKey]);
157                                      XCTAssertEqualObjects(trailing_md,
158                                                            error.userInfo[kGRPCTrailersKey]);
159                                      NSString *challengeHeader = init_md[@"www-authenticate"];
160                                      XCTAssertGreaterThan(challengeHeader.length, 0,
161                                                           @"No challenge in response headers %@",
162                                                           init_md);
163                                      [expectation fulfill];
164                                    }
165                                  }]
166                  callOptions:options];
167
168   [call start];
169   [call writeData:[request data]];
170   [call finish];
171
172   [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
173 }
174
175 - (void)testUserAgentPrefix {
176   __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
177   __weak XCTestExpectation *recvInitialMd =
178       [self expectationWithDescription:@"Did not receive initial md."];
179
180   GRPCRequestOptions *request = [[GRPCRequestOptions alloc] initWithHost:kHostAddress
181                                                                     path:kEmptyCallMethod.HTTPPath
182                                                                   safety:GRPCCallSafetyDefault];
183   NSDictionary *headers =
184       [NSDictionary dictionaryWithObjectsAndKeys:@"", @"x-grpc-test-echo-useragent", nil];
185   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
186   options.transportType = GRPCTransportTypeInsecure;
187   options.userAgentPrefix = @"Foo";
188   options.initialMetadata = headers;
189   GRPCCall2 *call = [[GRPCCall2 alloc]
190       initWithRequestOptions:request
191              responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:^(
192                                                                     NSDictionary *initialMetadata) {
193                NSString *userAgent = initialMetadata[@"x-grpc-test-echo-useragent"];
194                // Test the regex is correct
195                NSString *expectedUserAgent = @"Foo grpc-objc/";
196                expectedUserAgent =
197                    [expectedUserAgent stringByAppendingString:GRPC_OBJC_VERSION_STRING];
198                expectedUserAgent = [expectedUserAgent stringByAppendingString:@" grpc-c/"];
199                expectedUserAgent =
200                    [expectedUserAgent stringByAppendingString:GRPC_C_VERSION_STRING];
201                expectedUserAgent = [expectedUserAgent stringByAppendingString:@" (ios; chttp2; "];
202                expectedUserAgent = [expectedUserAgent
203                    stringByAppendingString:[NSString stringWithUTF8String:grpc_g_stands_for()]];
204                expectedUserAgent = [expectedUserAgent stringByAppendingString:@")"];
205                XCTAssertEqualObjects(userAgent, expectedUserAgent);
206
207                NSError *error = nil;
208                // Change in format of user-agent field in a direction that does not match
209                // the regex will likely cause problem for certain gRPC users. For details,
210                // refer to internal doc https://goo.gl/c2diBc
211                NSRegularExpression *regex = [NSRegularExpression
212                    regularExpressionWithPattern:
213                        @" grpc-[a-zA-Z0-9]+(-[a-zA-Z0-9]+)?/[^ ,]+( \\([^)]*\\))?"
214                                         options:0
215                                           error:&error];
216
217                NSString *customUserAgent =
218                    [regex stringByReplacingMatchesInString:userAgent
219                                                    options:0
220                                                      range:NSMakeRange(0, [userAgent length])
221                                               withTemplate:@""];
222                XCTAssertEqualObjects(customUserAgent, @"Foo");
223                [recvInitialMd fulfill];
224              }
225                                  messageCallback:^(id message) {
226                                    XCTAssertNotNil(message);
227                                    XCTAssertEqual([message length], 0,
228                                                   @"Non-empty response received: %@", message);
229                                  }
230                                  closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
231                                    if (error) {
232                                      XCTFail(@"Finished with unexpected error: %@", error);
233                                    } else {
234                                      [completion fulfill];
235                                    }
236                                  }]
237                  callOptions:options];
238   [call writeData:[NSData data]];
239   [call start];
240
241   [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
242 }
243
244 - (void)getTokenWithHandler:(void (^)(NSString *token))handler {
245   dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
246   dispatch_sync(queue, ^{
247     handler(@"test-access-token");
248   });
249 }
250
251 - (void)testOAuthToken {
252   __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
253
254   GRPCRequestOptions *requestOptions =
255       [[GRPCRequestOptions alloc] initWithHost:kHostAddress
256                                           path:kEmptyCallMethod.HTTPPath
257                                         safety:GRPCCallSafetyDefault];
258   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
259   options.transportType = GRPCTransportTypeInsecure;
260   options.authTokenProvider = self;
261   __block GRPCCall2 *call = [[GRPCCall2 alloc]
262       initWithRequestOptions:requestOptions
263              responseHandler:[[ClientTestsBlockCallbacks alloc]
264                                  initWithInitialMetadataCallback:nil
265                                                  messageCallback:nil
266                                                    closeCallback:^(NSDictionary *trailingMetadata,
267                                                                    NSError *error) {
268                                                      [completion fulfill];
269                                                    }]
270                  callOptions:options];
271   [call writeData:[NSData data]];
272   [call start];
273   [call finish];
274
275   [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
276 }
277
278 - (void)testResponseSizeLimitExceeded {
279   __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
280
281   GRPCRequestOptions *requestOptions =
282       [[GRPCRequestOptions alloc] initWithHost:kHostAddress
283                                           path:kUnaryCallMethod.HTTPPath
284                                         safety:GRPCCallSafetyDefault];
285   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
286   options.responseSizeLimit = kSimpleDataLength;
287   options.transportType = GRPCTransportTypeInsecure;
288
289   RMTSimpleRequest *request = [RMTSimpleRequest message];
290   request.payload.body = [NSMutableData dataWithLength:options.responseSizeLimit];
291   request.responseSize = (int32_t)(options.responseSizeLimit * 2);
292
293   GRPCCall2 *call = [[GRPCCall2 alloc]
294       initWithRequestOptions:requestOptions
295              responseHandler:[[ClientTestsBlockCallbacks alloc]
296                                  initWithInitialMetadataCallback:nil
297                                                  messageCallback:nil
298                                                    closeCallback:^(NSDictionary *trailingMetadata,
299                                                                    NSError *error) {
300                                                      XCTAssertNotNil(error,
301                                                                      @"Expecting non-nil error");
302                                                      XCTAssertEqual(error.code,
303                                                                     GRPCErrorCodeResourceExhausted);
304                                                      [completion fulfill];
305                                                    }]
306                  callOptions:options];
307   [call writeData:[request data]];
308   [call start];
309   [call finish];
310
311   [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
312 }
313
314 - (void)testIdempotentProtoRPC {
315   __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."];
316   __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
317
318   RMTSimpleRequest *request = [RMTSimpleRequest message];
319   request.responseSize = kSimpleDataLength;
320   request.fillUsername = YES;
321   request.fillOauthScope = YES;
322   GRPCRequestOptions *requestOptions =
323       [[GRPCRequestOptions alloc] initWithHost:kHostAddress
324                                           path:kUnaryCallMethod.HTTPPath
325                                         safety:GRPCCallSafetyIdempotentRequest];
326
327   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
328   options.transportType = GRPCTransportTypeInsecure;
329   GRPCCall2 *call = [[GRPCCall2 alloc]
330       initWithRequestOptions:requestOptions
331              responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
332                                  messageCallback:^(id message) {
333                                    NSData *data = (NSData *)message;
334                                    XCTAssertNotNil(data, @"nil value received as response.");
335                                    XCTAssertGreaterThan(data.length, 0,
336                                                         @"Empty response received.");
337                                    RMTSimpleResponse *responseProto =
338                                        [RMTSimpleResponse parseFromData:data error:NULL];
339                                    // We expect empty strings, not nil:
340                                    XCTAssertNotNil(responseProto.username,
341                                                    @"Response's username is nil.");
342                                    XCTAssertNotNil(responseProto.oauthScope,
343                                                    @"Response's OAuth scope is nil.");
344                                    [response fulfill];
345                                  }
346                                  closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
347                                    XCTAssertNil(error, @"Finished with unexpected error: %@",
348                                                 error);
349                                    [completion fulfill];
350                                  }]
351                  callOptions:options];
352
353   [call start];
354   [call writeData:[request data]];
355   [call finish];
356
357   [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
358 }
359
360 - (void)testTimeout {
361   __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
362
363   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
364   options.timeout = 0.001;
365   GRPCRequestOptions *requestOptions =
366       [[GRPCRequestOptions alloc] initWithHost:kHostAddress
367                                           path:kFullDuplexCallMethod.HTTPPath
368                                         safety:GRPCCallSafetyDefault];
369
370   GRPCCall2 *call = [[GRPCCall2 alloc]
371       initWithRequestOptions:requestOptions
372              responseHandler:
373                  [[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
374                      messageCallback:^(NSData *data) {
375                        XCTFail(@"Failure: response received; Expect: no response received.");
376                      }
377                      closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
378                        XCTAssertNotNil(error,
379                                        @"Failure: no error received; Expect: receive "
380                                        @"deadline exceeded.");
381                        XCTAssertEqual(error.code, GRPCErrorCodeDeadlineExceeded);
382                        [completion fulfill];
383                      }]
384                  callOptions:options];
385
386   [call start];
387
388   [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
389 }
390
391 - (void)testTimeoutBackoffWithTimeout:(double)timeout Backoff:(double)backoff {
392   const double maxConnectTime = timeout > backoff ? timeout : backoff;
393   const double kMargin = 0.1;
394
395   __weak XCTestExpectation *completion = [self expectationWithDescription:@"Timeout in a second."];
396   NSString *const kDummyAddress = [NSString stringWithFormat:@"127.0.0.1:10000"];
397   GRPCRequestOptions *requestOptions =
398       [[GRPCRequestOptions alloc] initWithHost:kDummyAddress
399                                           path:@"/dummy/path"
400                                         safety:GRPCCallSafetyDefault];
401   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
402   options.connectMinTimeout = timeout;
403   options.connectInitialBackoff = backoff;
404   options.connectMaxBackoff = 0;
405
406   NSDate *startTime = [NSDate date];
407   GRPCCall2 *call = [[GRPCCall2 alloc]
408       initWithRequestOptions:requestOptions
409              responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
410                                  messageCallback:^(NSData *data) {
411                                    XCTFail(@"Received message. Should not reach here.");
412                                  }
413                                  closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
414                                    XCTAssertNotNil(error,
415                                                    @"Finished with no error; expecting error");
416                                    XCTAssertLessThan(
417                                        [[NSDate date] timeIntervalSinceDate:startTime],
418                                        maxConnectTime + kMargin);
419                                    [completion fulfill];
420                                  }]
421                  callOptions:options];
422
423   [call start];
424
425   [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
426 }
427
428 - (void)testTimeoutBackoff1 {
429   [self testTimeoutBackoffWithTimeout:0.7 Backoff:0.4];
430 }
431
432 - (void)testTimeoutBackoff2 {
433   [self testTimeoutBackoffWithTimeout:0.3 Backoff:0.8];
434 }
435
436 - (void)testCompression {
437   __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
438
439   RMTSimpleRequest *request = [RMTSimpleRequest message];
440   request.expectCompressed = [RMTBoolValue message];
441   request.expectCompressed.value = YES;
442   request.responseCompressed = [RMTBoolValue message];
443   request.expectCompressed.value = YES;
444   request.responseSize = kSimpleDataLength;
445   request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
446   GRPCRequestOptions *requestOptions =
447       [[GRPCRequestOptions alloc] initWithHost:kHostAddress
448                                           path:kUnaryCallMethod.HTTPPath
449                                         safety:GRPCCallSafetyDefault];
450
451   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
452   options.transportType = GRPCTransportTypeInsecure;
453   options.compressionAlgorithm = GRPCCompressGzip;
454   GRPCCall2 *call = [[GRPCCall2 alloc]
455       initWithRequestOptions:requestOptions
456              responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
457                                  messageCallback:^(NSData *data) {
458                                    NSError *error;
459                                    RMTSimpleResponse *response =
460                                        [RMTSimpleResponse parseFromData:data error:&error];
461                                    XCTAssertNil(error, @"Error when parsing response: %@", error);
462                                    XCTAssertEqual(response.payload.body.length, kSimpleDataLength);
463                                  }
464                                  closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
465                                    XCTAssertNil(error, @"Received failure: %@", error);
466                                    [completion fulfill];
467                                  }]
468
469                  callOptions:options];
470
471   [call start];
472   [call writeData:[request data]];
473   [call finish];
474
475   [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
476 }
477
478 @end