diff options
Diffstat (limited to 'src/objective-c/tests/GRPCClientTests.m')
-rw-r--r-- | src/objective-c/tests/GRPCClientTests.m | 291 |
1 files changed, 289 insertions, 2 deletions
diff --git a/src/objective-c/tests/GRPCClientTests.m b/src/objective-c/tests/GRPCClientTests.m index a0de8ba899..bad22d30cb 100644 --- a/src/objective-c/tests/GRPCClientTests.m +++ b/src/objective-c/tests/GRPCClientTests.m @@ -86,6 +86,58 @@ static GRPCProtoMethod *kFullDuplexCallMethod; @end +// Convenience class to use blocks as callbacks +@interface ClientTestsBlockCallbacks : NSObject<GRPCResponseHandler> + +- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback + messageCallback:(void (^)(id))messageCallback + closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback; + +@end + +@implementation ClientTestsBlockCallbacks { + void (^_initialMetadataCallback)(NSDictionary *); + void (^_messageCallback)(id); + void (^_closeCallback)(NSDictionary *, NSError *); + dispatch_queue_t _dispatchQueue; +} + +- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback + messageCallback:(void (^)(id))messageCallback + closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback { + if ((self = [super init])) { + _initialMetadataCallback = initialMetadataCallback; + _messageCallback = messageCallback; + _closeCallback = closeCallback; + _dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL); + } + return self; +} + +- (void)receivedInitialMetadata:(NSDictionary *)initialMetadata { + if (_initialMetadataCallback) { + _initialMetadataCallback(initialMetadata); + } +} + +- (void)receivedMessage:(id)message { + if (_messageCallback) { + _messageCallback(message); + } +} + +- (void)closedWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error { + if (_closeCallback) { + _closeCallback(trailingMetadata, error); + } +} + +- (dispatch_queue_t)dispatchQueue { + return _dispatchQueue; +} + +@end + #pragma mark Tests /** @@ -237,6 +289,55 @@ static GRPCProtoMethod *kFullDuplexCallMethod; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testMetadataWithV2API { + __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."]; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + request.fillUsername = YES; + request.fillOauthScope = YES; + + GRPCRequestOptions *callRequest = + [[GRPCRequestOptions alloc] initWithHost:(NSString *)kRemoteSSLHost + path:kUnaryCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + __block NSDictionary *init_md; + __block NSDictionary *trailing_md; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.oauth2AccessToken = @"bogusToken"; + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:callRequest + handler:[[ClientTestsBlockCallbacks alloc] + initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) { + init_md = initialMetadata; + } + messageCallback:^(id message) { + XCTFail(@"Received unexpected response."); + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + trailing_md = trailingMetadata; + if (error) { + XCTAssertEqual(error.code, 16, + @"Finished with unexpected error: %@", error); + XCTAssertEqualObjects(init_md, + error.userInfo[kGRPCHeadersKey]); + XCTAssertEqualObjects(trailing_md, + error.userInfo[kGRPCTrailersKey]); + NSString *challengeHeader = init_md[@"www-authenticate"]; + XCTAssertGreaterThan(challengeHeader.length, 0, + @"No challenge in response headers %@", + init_md); + [expectation fulfill]; + } + }] + callOptions:options]; + + [call start]; + [call writeWithData:[request data]]; + [call finish]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + - (void)testResponseMetadataKVO { __weak XCTestExpectation *response = [self expectationWithDescription:@"Empty response received."]; @@ -329,6 +430,77 @@ static GRPCProtoMethod *kFullDuplexCallMethod; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testUserAgentPrefixWithV2API { + __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; + __weak XCTestExpectation *recvInitialMd = + [self expectationWithDescription:@"Did not receive initial md."]; + + GRPCRequestOptions *request = [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kEmptyCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + NSDictionary *headers = + [NSDictionary dictionaryWithObjectsAndKeys:@"", @"x-grpc-test-echo-useragent", nil]; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = GRPCTransportTypeInsecure; + options.userAgentPrefix = @"Foo"; + options.initialMetadata = headers; + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:request + handler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:^( + NSDictionary *initialMetadata) { + NSString *userAgent = initialMetadata[@"x-grpc-test-echo-useragent"]; + // Test the regex is correct + NSString *expectedUserAgent = @"Foo grpc-objc/"; + expectedUserAgent = + [expectedUserAgent stringByAppendingString:GRPC_OBJC_VERSION_STRING]; + expectedUserAgent = [expectedUserAgent stringByAppendingString:@" grpc-c/"]; + expectedUserAgent = + [expectedUserAgent stringByAppendingString:GRPC_C_VERSION_STRING]; + expectedUserAgent = + [expectedUserAgent stringByAppendingString:@" (ios; chttp2; "]; + expectedUserAgent = [expectedUserAgent + stringByAppendingString:[NSString + stringWithUTF8String:grpc_g_stands_for()]]; + expectedUserAgent = [expectedUserAgent stringByAppendingString:@")"]; + XCTAssertEqualObjects(userAgent, expectedUserAgent); + + NSError *error = nil; + // Change in format of user-agent field in a direction that does not match + // the regex will likely cause problem for certain gRPC users. For details, + // refer to internal doc https://goo.gl/c2diBc + NSRegularExpression *regex = [NSRegularExpression + regularExpressionWithPattern: + @" grpc-[a-zA-Z0-9]+(-[a-zA-Z0-9]+)?/[^ ,]+( \\([^)]*\\))?" + options:0 + error:&error]; + + NSString *customUserAgent = [regex + stringByReplacingMatchesInString:userAgent + options:0 + range:NSMakeRange(0, [userAgent length]) + withTemplate:@""]; + XCTAssertEqualObjects(customUserAgent, @"Foo"); + [recvInitialMd fulfill]; + } + messageCallback:^(id message) { + XCTAssertNotNil(message); + XCTAssertEqual([message length], 0, + @"Non-empty response received: %@", message); + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + if (error) { + XCTFail(@"Finished with unexpected error: %@", error); + } else { + [completion fulfill]; + } + }] + callOptions:options]; + [call writeWithData:[NSData data]]; + [call start]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + - (void)testTrailers { __weak XCTestExpectation *response = [self expectationWithDescription:@"Empty response received."]; @@ -420,6 +592,52 @@ static GRPCProtoMethod *kFullDuplexCallMethod; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testIdempotentProtoRPCWithV2API { + __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."]; + __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; + + RMTSimpleRequest *request = [RMTSimpleRequest message]; + request.responseSize = 100; + request.fillUsername = YES; + request.fillOauthScope = YES; + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kUnaryCallMethod.HTTPPath + safety:GRPCCallSafetyIdempotentRequest]; + + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.transportType = GRPCTransportTypeInsecure; + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + handler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil + messageCallback:^(id message) { + NSData *data = (NSData *)message; + XCTAssertNotNil(data, @"nil value received as response."); + XCTAssertGreaterThan(data.length, 0, + @"Empty response received."); + RMTSimpleResponse *responseProto = + [RMTSimpleResponse parseFromData:data error:NULL]; + // We expect empty strings, not nil: + XCTAssertNotNil(responseProto.username, + @"Response's username is nil."); + XCTAssertNotNil(responseProto.oauthScope, + @"Response's OAuth scope is nil."); + [response fulfill]; + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + XCTAssertNil(error, @"Finished with unexpected error: %@", + error); + [completion fulfill]; + }] + callOptions:options]; + + [call start]; + [call writeWithData:[request data]]; + [call finish]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + - (void)testAlternateDispatchQueue { const int32_t kPayloadSize = 100; RMTSimpleRequest *request = [RMTSimpleRequest message]; @@ -509,6 +727,38 @@ static GRPCProtoMethod *kFullDuplexCallMethod; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testTimeoutWithV2API { + __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; + + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.timeout = 0.001; + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:kHostAddress + path:kFullDuplexCallMethod.HTTPPath + safety:GRPCCallSafetyDefault]; + + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + handler: + [[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil + messageCallback:^(id data) { + XCTFail( + @"Failure: response received; Expect: no response received."); + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + XCTAssertNotNil(error, + @"Failure: no error received; Expect: receive " + @"deadline exceeded."); + XCTAssertEqual(error.code, GRPCErrorCodeDeadlineExceeded); + [completion fulfill]; + }] + callOptions:options]; + + [call start]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + - (int)findFreePort { struct sockaddr_in addr; unsigned int addr_len = sizeof(addr); @@ -580,15 +830,52 @@ static GRPCProtoMethod *kFullDuplexCallMethod; [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; } +- (void)testTimeoutBackoffWithOptionsWithTimeout:(double)timeout Backoff:(double)backoff { + const double maxConnectTime = timeout > backoff ? timeout : backoff; + const double kMargin = 0.1; + + __weak XCTestExpectation *completion = [self expectationWithDescription:@"Timeout in a second."]; + NSString *const kDummyAddress = [NSString stringWithFormat:@"8.8.8.8:1"]; + GRPCRequestOptions *requestOptions = + [[GRPCRequestOptions alloc] initWithHost:kDummyAddress path:@"" safety:GRPCCallSafetyDefault]; + GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; + options.connectMinTimeout = timeout; + options.connectInitialBackoff = backoff; + options.connectMaxBackoff = 0; + + NSDate *startTime = [NSDate date]; + GRPCCall2 *call = [[GRPCCall2 alloc] + initWithRequestOptions:requestOptions + handler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil + messageCallback:^(id data) { + XCTFail(@"Received message. Should not reach here."); + } + closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { + XCTAssertNotNil(error, + @"Finished with no error; expecting error"); + XCTAssertLessThan( + [[NSDate date] timeIntervalSinceDate:startTime], + maxConnectTime + kMargin); + [completion fulfill]; + }] + callOptions:options]; + + [call start]; + + [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; +} + // The numbers of the following three tests are selected to be smaller than the default values of // initial backoff (1s) and min_connect_timeout (20s), so that if they fail we know the default // values fail to be overridden by the channel args. -- (void)testTimeoutBackoff2 { +- (void)testTimeoutBackoff1 { [self testTimeoutBackoffWithTimeout:0.7 Backoff:0.3]; + [self testTimeoutBackoffWithOptionsWithTimeout:0.7 Backoff:0.4]; } -- (void)testTimeoutBackoff3 { +- (void)testTimeoutBackoff2 { [self testTimeoutBackoffWithTimeout:0.3 Backoff:0.7]; + [self testTimeoutBackoffWithOptionsWithTimeout:0.3 Backoff:0.8]; } - (void)testErrorDebugInformation { |