Index: third_party/grpc/src/objective-c/tests/GRPCClientTests.m |
diff --git a/third_party/grpc/src/objective-c/tests/GRPCClientTests.m b/third_party/grpc/src/objective-c/tests/GRPCClientTests.m |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7dd6873c8029bbd0e69fc9bafde988e85d8123cf |
--- /dev/null |
+++ b/third_party/grpc/src/objective-c/tests/GRPCClientTests.m |
@@ -0,0 +1,329 @@ |
+/* |
+ * |
+ * Copyright 2015-2016, Google Inc. |
+ * All rights reserved. |
+ * |
+ * Redistribution and use in source and binary forms, with or without |
+ * modification, are permitted provided that the following conditions are |
+ * met: |
+ * |
+ * * Redistributions of source code must retain the above copyright |
+ * notice, this list of conditions and the following disclaimer. |
+ * * Redistributions in binary form must reproduce the above |
+ * copyright notice, this list of conditions and the following disclaimer |
+ * in the documentation and/or other materials provided with the |
+ * distribution. |
+ * * Neither the name of Google Inc. nor the names of its |
+ * contributors may be used to endorse or promote products derived from |
+ * this software without specific prior written permission. |
+ * |
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+ * |
+ */ |
+ |
+#import <UIKit/UIKit.h> |
+#import <XCTest/XCTest.h> |
+ |
+#import <GRPCClient/GRPCCall.h> |
+#import <GRPCClient/GRPCCall+ChannelArg.h> |
+#import <GRPCClient/GRPCCall+OAuth2.h> |
+#import <GRPCClient/GRPCCall+Tests.h> |
+#import <ProtoRPC/ProtoMethod.h> |
+#import <RemoteTest/Messages.pbobjc.h> |
+#import <RxLibrary/GRXWriteable.h> |
+#import <RxLibrary/GRXWriter+Immediate.h> |
+ |
+static NSString * const kHostAddress = @"localhost:5050"; |
+static NSString * const kPackage = @"grpc.testing"; |
+static NSString * const kService = @"TestService"; |
+static NSString * const kRemoteSSLHost = @"grpc-test.sandbox.googleapis.com"; |
+ |
+static ProtoMethod *kInexistentMethod; |
+static ProtoMethod *kEmptyCallMethod; |
+static ProtoMethod *kUnaryCallMethod; |
+ |
+/** Observer class for testing that responseMetadata is KVO-compliant */ |
+@interface PassthroughObserver : NSObject |
+- (instancetype) initWithCallback:(void (^)(NSString*, id, NSDictionary*))callback |
+ NS_DESIGNATED_INITIALIZER; |
+ |
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change |
+ context:(void *)context; |
+@end |
+ |
+@implementation PassthroughObserver { |
+ void (^_callback)(NSString*, id, NSDictionary*); |
+} |
+ |
+- (instancetype)init { |
+ return [self initWithCallback:nil]; |
+} |
+ |
+- (instancetype)initWithCallback:(void (^)(NSString *, id, NSDictionary *))callback { |
+ if (!callback) { |
+ return nil; |
+ } |
+ if ((self = [super init])) { |
+ _callback = callback; |
+ } |
+ return self; |
+} |
+ |
+- (void)observeValueForKeyPath:(NSString *)keyPath |
+ ofObject:(id)object |
+ change:(NSDictionary *)change |
+ context:(void *)context { |
+ _callback(keyPath, object, change); |
+ [object removeObserver:self forKeyPath:keyPath]; |
+} |
+ |
+@end |
+ |
+# pragma mark Tests |
+ |
+/** |
+ * A few tests similar to InteropTests, but which use the generic gRPC client (GRPCCall) rather than |
+ * a generated proto library on top of it. Its RPCs are sent to a local cleartext server. |
+ * |
+ * TODO(jcanizales): Run them also against a local SSL server and against a remote server. |
+ */ |
+@interface GRPCClientTests : XCTestCase |
+@end |
+ |
+@implementation GRPCClientTests |
+ |
+- (void)setUp { |
+ // Add a custom user agent prefix that will be used in test |
+ [GRPCCall setUserAgentPrefix:@"Foo" forHost:kHostAddress]; |
+ // Register test server as non-SSL. |
+ [GRPCCall useInsecureConnectionsForHost:kHostAddress]; |
+ |
+ // This method isn't implemented by the remote server. |
+ kInexistentMethod = [[ProtoMethod alloc] initWithPackage:kPackage |
+ service:kService |
+ method:@"Inexistent"]; |
+ kEmptyCallMethod = [[ProtoMethod alloc] initWithPackage:kPackage |
+ service:kService |
+ method:@"EmptyCall"]; |
+ kUnaryCallMethod = [[ProtoMethod alloc] initWithPackage:kPackage |
+ service:kService |
+ method:@"UnaryCall"]; |
+} |
+ |
+- (void)testConnectionToRemoteServer { |
+ __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Server reachable."]; |
+ |
+ GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
+ path:kInexistentMethod.HTTPPath |
+ requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; |
+ |
+ id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
+ XCTFail(@"Received unexpected response: %@", value); |
+ } completionHandler:^(NSError *errorOrNil) { |
+ XCTAssertNotNil(errorOrNil, @"Finished without error!"); |
+ XCTAssertEqual(errorOrNil.code, 12, @"Finished with unexpected error: %@", errorOrNil); |
+ [expectation fulfill]; |
+ }]; |
+ |
+ [call startWithWriteable:responsesWriteable]; |
+ |
+ [self waitForExpectationsWithTimeout:4 handler:nil]; |
+} |
+ |
+- (void)testEmptyRPC { |
+ __weak XCTestExpectation *response = [self expectationWithDescription:@"Empty response received."]; |
+ __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; |
+ |
+ GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
+ path:kEmptyCallMethod.HTTPPath |
+ requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; |
+ |
+ id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
+ XCTAssertNotNil(value, @"nil value received as response."); |
+ XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value); |
+ [response fulfill]; |
+ } completionHandler:^(NSError *errorOrNil) { |
+ XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); |
+ [completion fulfill]; |
+ }]; |
+ |
+ [call startWithWriteable:responsesWriteable]; |
+ |
+ [self waitForExpectationsWithTimeout:8 handler:nil]; |
+} |
+ |
+- (void)testSimpleProtoRPC { |
+ __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; |
+ GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]]; |
+ |
+ GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
+ path:kUnaryCallMethod.HTTPPath |
+ requestsWriter:requestsWriter]; |
+ |
+ id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
+ XCTAssertNotNil(value, @"nil value received as response."); |
+ XCTAssertGreaterThan(value.length, 0, @"Empty response received."); |
+ RMTSimpleResponse *responseProto = [RMTSimpleResponse parseFromData:value 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]; |
+ } completionHandler:^(NSError *errorOrNil) { |
+ XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); |
+ [completion fulfill]; |
+ }]; |
+ |
+ [call startWithWriteable:responsesWriteable]; |
+ |
+ [self waitForExpectationsWithTimeout:8 handler:nil]; |
+} |
+ |
+- (void)testMetadata { |
+ __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."]; |
+ |
+ RMTSimpleRequest *request = [RMTSimpleRequest message]; |
+ request.fillUsername = YES; |
+ request.fillOauthScope = YES; |
+ GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]]; |
+ |
+ GRPCCall *call = [[GRPCCall alloc] initWithHost:kRemoteSSLHost |
+ path:kUnaryCallMethod.HTTPPath |
+ requestsWriter:requestsWriter]; |
+ |
+ call.oauth2AccessToken = @"bogusToken"; |
+ |
+ id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
+ XCTFail(@"Received unexpected response: %@", value); |
+ } completionHandler:^(NSError *errorOrNil) { |
+ XCTAssertNotNil(errorOrNil, @"Finished without error!"); |
+ XCTAssertEqual(errorOrNil.code, 16, @"Finished with unexpected error: %@", errorOrNil); |
+ XCTAssertEqualObjects(call.responseHeaders, errorOrNil.userInfo[kGRPCHeadersKey], |
+ @"Headers in the NSError object and call object differ."); |
+ XCTAssertEqualObjects(call.responseTrailers, errorOrNil.userInfo[kGRPCTrailersKey], |
+ @"Trailers in the NSError object and call object differ."); |
+ NSString *challengeHeader = call.oauth2ChallengeHeader; |
+ XCTAssertGreaterThan(challengeHeader.length, 0, |
+ @"No challenge in response headers %@", call.responseHeaders); |
+ [expectation fulfill]; |
+ }]; |
+ |
+ [call startWithWriteable:responsesWriteable]; |
+ |
+ [self waitForExpectationsWithTimeout:4 handler:nil]; |
+} |
+ |
+- (void)testResponseMetadataKVO { |
+ __weak XCTestExpectation *response = [self expectationWithDescription:@"Empty response received."]; |
+ __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; |
+ __weak XCTestExpectation *metadata = [self expectationWithDescription:@"Metadata changed."]; |
+ |
+ GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
+ path:kEmptyCallMethod.HTTPPath |
+ requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; |
+ |
+ PassthroughObserver *observer = [[PassthroughObserver alloc] initWithCallback:^(NSString *keypath, id object, NSDictionary * change) { |
+ if ([keypath isEqual: @"responseHeaders"]) { |
+ [metadata fulfill]; |
+ } |
+ }]; |
+ |
+ [call addObserver:observer forKeyPath:@"responseHeaders" options:0 context:NULL]; |
+ |
+ id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
+ XCTAssertNotNil(value, @"nil value received as response."); |
+ XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value); |
+ [response fulfill]; |
+ } completionHandler:^(NSError *errorOrNil) { |
+ XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); |
+ [completion fulfill]; |
+ }]; |
+ |
+ [call startWithWriteable:responsesWriteable]; |
+ |
+ [self waitForExpectationsWithTimeout:8 handler:nil]; |
+} |
+ |
+- (void)testUserAgentPrefix { |
+ __weak XCTestExpectation *response = [self expectationWithDescription:@"Empty response received."]; |
+ __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; |
+ |
+ GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
+ path:kEmptyCallMethod.HTTPPath |
+ requestsWriter:[GRXWriter writerWithValue:[NSData data]]]; |
+ // Setting this special key in the header will cause the interop server to echo back the |
+ // user-agent value, which we confirm. |
+ call.requestHeaders[@"x-grpc-test-echo-useragent"] = @""; |
+ |
+ id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { |
+ XCTAssertNotNil(value, @"nil value received as response."); |
+ XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value); |
+ /* This test needs to be more clever in regards to changing the version of the core. |
+ XCTAssertEqualObjects(call.responseHeaders[@"x-grpc-test-echo-useragent"], |
+ @"Foo grpc-objc/0.13.0 grpc-c/0.14.0-dev (ios)", |
+ @"Did not receive expected user agent %@", |
+ call.responseHeaders[@"x-grpc-test-echo-useragent"]); |
+ */ |
+ [response fulfill]; |
+ } completionHandler:^(NSError *errorOrNil) { |
+ XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil); |
+ [completion fulfill]; |
+ }]; |
+ |
+ [call startWithWriteable:responsesWriteable]; |
+ |
+ [self waitForExpectationsWithTimeout:8 handler:nil]; |
+} |
+ |
+// TODO(makarandd): Move to a different file that contains only unit tests |
+- (void)testExceptions { |
+ // Try to set userAgentPrefix for host that is nil. This should cause |
+ // an exception. |
+ @try { |
+ [GRPCCall setUserAgentPrefix:@"Foo" forHost:nil]; |
+ XCTFail(@"Did not receive an exception when host is nil"); |
+ } @catch(NSException *theException) { |
+ NSLog(@"Received exception as expected: %@", theException.name); |
+ } |
+ |
+ // Try to set parameters to nil for GRPCCall. This should cause an exception |
+ @try { |
+ GRPCCall *call = [[GRPCCall alloc] initWithHost:nil |
+ path:nil |
+ requestsWriter:nil]; |
+ XCTFail(@"Did not receive an exception when parameters are nil"); |
+ } @catch(NSException *theException) { |
+ NSLog(@"Received exception as expected: %@", theException.name); |
+ } |
+ |
+ |
+ // Set state to Finished by force |
+ GRXWriter *requestsWriter = [GRXWriter emptyWriter]; |
+ [requestsWriter finishWithError:nil]; |
+ @try { |
+ GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress |
+ path:kUnaryCallMethod.HTTPPath |
+ requestsWriter:requestsWriter]; |
+ XCTFail(@"Did not receive an exception when GRXWriter has incorrect state."); |
+ } @catch(NSException *theException) { |
+ NSLog(@"Received exception as expected: %@", theException.name); |
+ } |
+ |
+} |
+ |
+@end |