aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/objective-c/tests/APIv2Tests/APIv2Tests.m
diff options
context:
space:
mode:
Diffstat (limited to 'src/objective-c/tests/APIv2Tests/APIv2Tests.m')
-rw-r--r--src/objective-c/tests/APIv2Tests/APIv2Tests.m478
1 files changed, 478 insertions, 0 deletions
diff --git a/src/objective-c/tests/APIv2Tests/APIv2Tests.m b/src/objective-c/tests/APIv2Tests/APIv2Tests.m
new file mode 100644
index 0000000000..ca7bf47283
--- /dev/null
+++ b/src/objective-c/tests/APIv2Tests/APIv2Tests.m
@@ -0,0 +1,478 @@
+/*
+ *
+ * Copyright 2018 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import <GRPCClient/GRPCCall.h>
+#import <ProtoRPC/ProtoMethod.h>
+#import <RemoteTest/Messages.pbobjc.h>
+#import <XCTest/XCTest.h>
+
+#include <grpc/grpc.h>
+
+#import "../version.h"
+
+// The server address is derived from preprocessor macro, which is
+// in turn derived from environment variable of the same name.
+#define NSStringize_helper(x) #x
+#define NSStringize(x) @NSStringize_helper(x)
+static NSString *const kHostAddress = NSStringize(HOST_PORT_LOCAL);
+static NSString *const kRemoteSSLHost = NSStringize(HOST_PORT_REMOTE);
+
+// Package and service name of test server
+static NSString *const kPackage = @"grpc.testing";
+static NSString *const kService = @"TestService";
+
+static GRPCProtoMethod *kInexistentMethod;
+static GRPCProtoMethod *kEmptyCallMethod;
+static GRPCProtoMethod *kUnaryCallMethod;
+static GRPCProtoMethod *kFullDuplexCallMethod;
+
+static const int kSimpleDataLength = 100;
+
+static const NSTimeInterval kTestTimeout = 16;
+
+// Reveal the _class ivar for testing access
+@interface GRPCCall2 () {
+ @public
+ GRPCCall *_call;
+}
+
+@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)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
+ if (self->_initialMetadataCallback) {
+ self->_initialMetadataCallback(initialMetadata);
+ }
+}
+
+- (void)didReceiveRawMessage:(GPBMessage *)message {
+ if (self->_messageCallback) {
+ self->_messageCallback(message);
+ }
+}
+
+- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+ if (self->_closeCallback) {
+ self->_closeCallback(trailingMetadata, error);
+ }
+}
+
+- (dispatch_queue_t)dispatchQueue {
+ return _dispatchQueue;
+}
+
+@end
+
+@interface CallAPIv2Tests : XCTestCase<GRPCAuthorizationProtocol>
+
+@end
+
+@implementation CallAPIv2Tests
+
+- (void)setUp {
+ // This method isn't implemented by the remote server.
+ kInexistentMethod =
+ [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"Inexistent"];
+ kEmptyCallMethod =
+ [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"EmptyCall"];
+ kUnaryCallMethod =
+ [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"UnaryCall"];
+ kFullDuplexCallMethod =
+ [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"FullDuplexCall"];
+}
+
+- (void)testMetadata {
+ __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
+ responseHandler:[[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 writeData:[request data]];
+ [call finish];
+
+ [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
+}
+
+- (void)testUserAgentPrefix {
+ __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
+ responseHandler:[[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 writeData:[NSData data]];
+ [call start];
+
+ [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
+}
+
+- (void)getTokenWithHandler:(void (^)(NSString *token))handler {
+ dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+ dispatch_sync(queue, ^{
+ handler(@"test-access-token");
+ });
+}
+
+- (void)testOAuthToken {
+ __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
+
+ GRPCRequestOptions *requestOptions =
+ [[GRPCRequestOptions alloc] initWithHost:kHostAddress
+ path:kEmptyCallMethod.HTTPPath
+ safety:GRPCCallSafetyDefault];
+ GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+ options.transportType = GRPCTransportTypeInsecure;
+ options.authTokenProvider = self;
+ __block GRPCCall2 *call = [[GRPCCall2 alloc]
+ initWithRequestOptions:requestOptions
+ responseHandler:[[ClientTestsBlockCallbacks alloc]
+ initWithInitialMetadataCallback:nil
+ messageCallback:nil
+ closeCallback:^(NSDictionary *trailingMetadata,
+ NSError *error) {
+ [completion fulfill];
+ }]
+ callOptions:options];
+ [call writeData:[NSData data]];
+ [call start];
+ [call finish];
+
+ [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
+}
+
+- (void)testResponseSizeLimitExceeded {
+ __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
+
+ GRPCRequestOptions *requestOptions =
+ [[GRPCRequestOptions alloc] initWithHost:kHostAddress
+ path:kUnaryCallMethod.HTTPPath
+ safety:GRPCCallSafetyDefault];
+ GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+ options.responseSizeLimit = kSimpleDataLength;
+ options.transportType = GRPCTransportTypeInsecure;
+
+ RMTSimpleRequest *request = [RMTSimpleRequest message];
+ request.payload.body = [NSMutableData dataWithLength:options.responseSizeLimit];
+ request.responseSize = (int32_t)(options.responseSizeLimit * 2);
+
+ GRPCCall2 *call = [[GRPCCall2 alloc]
+ initWithRequestOptions:requestOptions
+ responseHandler:[[ClientTestsBlockCallbacks alloc]
+ initWithInitialMetadataCallback:nil
+ messageCallback:nil
+ closeCallback:^(NSDictionary *trailingMetadata,
+ NSError *error) {
+ XCTAssertNotNil(error,
+ @"Expecting non-nil error");
+ XCTAssertEqual(error.code,
+ GRPCErrorCodeResourceExhausted);
+ [completion fulfill];
+ }]
+ callOptions:options];
+ [call writeData:[request data]];
+ [call start];
+ [call finish];
+
+ [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
+}
+
+- (void)testIdempotentProtoRPC {
+ __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."];
+ __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
+
+ RMTSimpleRequest *request = [RMTSimpleRequest message];
+ request.responseSize = kSimpleDataLength;
+ 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
+ responseHandler:[[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 writeData:[request data]];
+ [call finish];
+
+ [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
+}
+
+- (void)testTimeout {
+ __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
+ responseHandler:
+ [[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
+ messageCallback:^(NSData *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:kTestTimeout handler:nil];
+}
+
+- (void)testTimeoutBackoffWithTimeout:(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:@"127.0.0.1:10000"];
+ GRPCRequestOptions *requestOptions =
+ [[GRPCRequestOptions alloc] initWithHost:kDummyAddress
+ path:@"/dummy/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
+ responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
+ messageCallback:^(NSData *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:kTestTimeout handler:nil];
+}
+
+- (void)testTimeoutBackoff1 {
+ [self testTimeoutBackoffWithTimeout:0.7 Backoff:0.4];
+}
+
+- (void)testTimeoutBackoff2 {
+ [self testTimeoutBackoffWithTimeout:0.3 Backoff:0.8];
+}
+
+- (void)testCompression {
+ __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
+
+ RMTSimpleRequest *request = [RMTSimpleRequest message];
+ request.expectCompressed = [RMTBoolValue message];
+ request.expectCompressed.value = YES;
+ request.responseCompressed = [RMTBoolValue message];
+ request.expectCompressed.value = YES;
+ request.responseSize = kSimpleDataLength;
+ request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength];
+ GRPCRequestOptions *requestOptions =
+ [[GRPCRequestOptions alloc] initWithHost:kHostAddress
+ path:kUnaryCallMethod.HTTPPath
+ safety:GRPCCallSafetyDefault];
+
+ GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+ options.transportType = GRPCTransportTypeInsecure;
+ options.compressionAlgorithm = GRPCCompressGzip;
+ GRPCCall2 *call = [[GRPCCall2 alloc]
+ initWithRequestOptions:requestOptions
+ responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil
+ messageCallback:^(NSData *data) {
+ NSError *error;
+ RMTSimpleResponse *response =
+ [RMTSimpleResponse parseFromData:data error:&error];
+ XCTAssertNil(error, @"Error when parsing response: %@", error);
+ XCTAssertEqual(response.payload.body.length, kSimpleDataLength);
+ }
+ closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
+ XCTAssertNil(error, @"Received failure: %@", error);
+ [completion fulfill];
+ }]
+
+ callOptions:options];
+
+ [call start];
+ [call writeData:[request data]];
+ [call finish];
+
+ [self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
+}
+
+@end