3 * Copyright 2018 gRPC authors.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #import <GRPCClient/GRPCCall.h>
20 #import <ProtoRPC/ProtoMethod.h>
21 #import <RemoteTest/Messages.pbobjc.h>
22 #import <XCTest/XCTest.h>
24 #include <grpc/grpc.h>
26 #import "../version.h"
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);
35 // Package and service name of test server
36 static NSString *const kPackage = @"grpc.testing";
37 static NSString *const kService = @"TestService";
39 static GRPCProtoMethod *kInexistentMethod;
40 static GRPCProtoMethod *kEmptyCallMethod;
41 static GRPCProtoMethod *kUnaryCallMethod;
42 static GRPCProtoMethod *kFullDuplexCallMethod;
44 static const int kSimpleDataLength = 100;
46 static const NSTimeInterval kTestTimeout = 16;
48 // Reveal the _class ivar for testing access
49 @interface GRPCCall2 () {
56 // Convenience class to use blocks as callbacks
57 @interface ClientTestsBlockCallbacks : NSObject<GRPCResponseHandler>
59 - (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
60 messageCallback:(void (^)(id))messageCallback
61 closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback;
65 @implementation ClientTestsBlockCallbacks {
66 void (^_initialMetadataCallback)(NSDictionary *);
67 void (^_messageCallback)(id);
68 void (^_closeCallback)(NSDictionary *, NSError *);
69 dispatch_queue_t _dispatchQueue;
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);
84 - (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
85 if (self->_initialMetadataCallback) {
86 self->_initialMetadataCallback(initialMetadata);
90 - (void)didReceiveRawMessage:(GPBMessage *)message {
91 if (self->_messageCallback) {
92 self->_messageCallback(message);
96 - (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
97 if (self->_closeCallback) {
98 self->_closeCallback(trailingMetadata, error);
102 - (dispatch_queue_t)dispatchQueue {
103 return _dispatchQueue;
108 @interface CallAPIv2Tests : XCTestCase<GRPCAuthorizationProtocol>
112 @implementation CallAPIv2Tests
115 // This method isn't implemented by the remote server.
117 [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"Inexistent"];
119 [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"EmptyCall"];
121 [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"UnaryCall"];
122 kFullDuplexCallMethod =
123 [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"FullDuplexCall"];
126 - (void)testMetadata {
127 __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."];
129 RMTSimpleRequest *request = [RMTSimpleRequest message];
130 request.fillUsername = YES;
131 request.fillOauthScope = YES;
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;
147 messageCallback:^(id message) {
148 XCTFail(@"Received unexpected response.");
150 closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
151 trailing_md = trailingMetadata;
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 %@",
163 [expectation fulfill];
166 callOptions:options];
169 [call writeData:[request data]];
172 [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
175 - (void)testUserAgentPrefix {
176 __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
177 __weak XCTestExpectation *recvInitialMd =
178 [self expectationWithDescription:@"Did not receive initial md."];
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/";
197 [expectedUserAgent stringByAppendingString:GRPC_OBJC_VERSION_STRING];
198 expectedUserAgent = [expectedUserAgent stringByAppendingString:@" grpc-c/"];
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);
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]+)?/[^ ,]+( \\([^)]*\\))?"
217 NSString *customUserAgent =
218 [regex stringByReplacingMatchesInString:userAgent
220 range:NSMakeRange(0, [userAgent length])
222 XCTAssertEqualObjects(customUserAgent, @"Foo");
223 [recvInitialMd fulfill];
225 messageCallback:^(id message) {
226 XCTAssertNotNil(message);
227 XCTAssertEqual([message length], 0,
228 @"Non-empty response received: %@", message);
230 closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
232 XCTFail(@"Finished with unexpected error: %@", error);
234 [completion fulfill];
237 callOptions:options];
238 [call writeData:[NSData data]];
241 [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
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");
251 - (void)testOAuthToken {
252 __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
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
266 closeCallback:^(NSDictionary *trailingMetadata,
268 [completion fulfill];
270 callOptions:options];
271 [call writeData:[NSData data]];
275 [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
278 - (void)testResponseSizeLimitExceeded {
279 __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
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;
289 RMTSimpleRequest *request = [RMTSimpleRequest message];
290 request.payload.body = [NSMutableData dataWithLength:options.responseSizeLimit];
291 request.responseSize = (int32_t)(options.responseSizeLimit * 2);
293 GRPCCall2 *call = [[GRPCCall2 alloc]
294 initWithRequestOptions:requestOptions
295 responseHandler:[[ClientTestsBlockCallbacks alloc]
296 initWithInitialMetadataCallback:nil
298 closeCallback:^(NSDictionary *trailingMetadata,
300 XCTAssertNotNil(error,
301 @"Expecting non-nil error");
302 XCTAssertEqual(error.code,
303 GRPCErrorCodeResourceExhausted);
304 [completion fulfill];
306 callOptions:options];
307 [call writeData:[request data]];
311 [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
314 - (void)testIdempotentProtoRPC {
315 __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."];
316 __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
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];
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.");
346 closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
347 XCTAssertNil(error, @"Finished with unexpected error: %@",
349 [completion fulfill];
351 callOptions:options];
354 [call writeData:[request data]];
357 [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
360 - (void)testTimeout {
361 __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
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];
370 GRPCCall2 *call = [[GRPCCall2 alloc]
371 initWithRequestOptions:requestOptions
373 [[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
374 messageCallback:^(NSData *data) {
375 XCTFail(@"Failure: response received; Expect: no response received.");
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];
384 callOptions:options];
388 [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
391 - (void)testTimeoutBackoffWithTimeout:(double)timeout Backoff:(double)backoff {
392 const double maxConnectTime = timeout > backoff ? timeout : backoff;
393 const double kMargin = 0.1;
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
400 safety:GRPCCallSafetyDefault];
401 GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
402 options.connectMinTimeout = timeout;
403 options.connectInitialBackoff = backoff;
404 options.connectMaxBackoff = 0;
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.");
413 closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
414 XCTAssertNotNil(error,
415 @"Finished with no error; expecting error");
417 [[NSDate date] timeIntervalSinceDate:startTime],
418 maxConnectTime + kMargin);
419 [completion fulfill];
421 callOptions:options];
425 [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
428 - (void)testTimeoutBackoff1 {
429 [self testTimeoutBackoffWithTimeout:0.7 Backoff:0.4];
432 - (void)testTimeoutBackoff2 {
433 [self testTimeoutBackoffWithTimeout:0.3 Backoff:0.8];
436 - (void)testCompression {
437 __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
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];
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) {
459 RMTSimpleResponse *response =
460 [RMTSimpleResponse parseFromData:data error:&error];
461 XCTAssertNil(error, @"Error when parsing response: %@", error);
462 XCTAssertEqual(response.payload.body.length, kSimpleDataLength);
464 closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
465 XCTAssertNil(error, @"Received failure: %@", error);
466 [completion fulfill];
469 callOptions:options];
472 [call writeData:[request data]];
475 [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];