1/* 2 * 3 * Copyright 2019 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 <GRPCClient/GRPCInterceptor.h> 21#import <GRPCClient/GRPCTransport.h> 22#import <XCTest/XCTest.h> 23 24#define TEST_TIMEOUT (8.0) 25 26static NSString *const kRemoteHost = @"grpc-test.sandbox.googleapis.com:443"; 27 28static const GRPCTransportID kFakeTransportID = "io.grpc.transport.unittest.fake"; 29 30@class GRPCFakeTransportFactory; 31dispatch_once_t initFakeTransportFactory; 32static GRPCFakeTransportFactory *fakeTransportFactory; 33 34@interface GRPCFakeTransportFactory : NSObject <GRPCTransportFactory> 35 36@property(atomic) GRPCTransport *nextTransportInstance; 37- (void)setTransportInterceptorFactories:(NSArray<id<GRPCInterceptorFactory>> *)factories; 38 39@end 40 41@implementation GRPCFakeTransportFactory { 42 NSArray<id<GRPCInterceptorFactory>> *_interceptorFactories; 43} 44 45+ (instancetype)sharedInstance { 46 dispatch_once(&initFakeTransportFactory, ^{ 47 fakeTransportFactory = [[GRPCFakeTransportFactory alloc] init]; 48 }); 49 return fakeTransportFactory; 50} 51 52+ (void)load { 53 [[GRPCTransportRegistry sharedInstance] registerTransportWithID:kFakeTransportID 54 factory:[self sharedInstance]]; 55} 56 57- (GRPCTransport *)createTransportWithManager:(GRPCTransportManager *)transportManager { 58 return _nextTransportInstance; 59} 60 61- (void)setTransportInterceptorFactories:(NSArray<id<GRPCInterceptorFactory>> *)factories { 62 _interceptorFactories = [NSArray arrayWithArray:factories]; 63} 64 65- (NSArray<id<GRPCInterceptorFactory>> *)transportInterceptorFactories { 66 return _interceptorFactories; 67} 68 69@end 70 71@interface DummyInterceptor : GRPCInterceptor 72 73@property(atomic) BOOL hit; 74 75@end 76 77@implementation DummyInterceptor { 78 GRPCInterceptorManager *_manager; 79 BOOL _passthrough; 80} 81 82- (instancetype)initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager 83 dispatchQueue:(dispatch_queue_t)dispatchQueue 84 passthrough:(BOOL)passthrough { 85 if (dispatchQueue == nil) { 86 dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); 87 } 88 if ((self = [super initWithInterceptorManager:interceptorManager dispatchQueue:dispatchQueue])) { 89 _manager = interceptorManager; 90 _passthrough = passthrough; 91 } 92 return self; 93} 94 95- (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions 96 callOptions:(GRPCCallOptions *)callOptions { 97 self.hit = YES; 98 if (_passthrough) { 99 [super startWithRequestOptions:requestOptions callOptions:callOptions]; 100 } else { 101 [_manager 102 forwardPreviousInterceptorCloseWithTrailingMetadata:nil 103 error: 104 [NSError 105 errorWithDomain:kGRPCErrorDomain 106 code:GRPCErrorCodeCancelled 107 userInfo:@{ 108 NSLocalizedDescriptionKey : 109 @"Canceled." 110 }]]; 111 [_manager shutDown]; 112 } 113} 114 115@end 116 117@interface DummyInterceptorFactory : NSObject <GRPCInterceptorFactory> 118 119- (instancetype)initWithPassthrough:(BOOL)passthrough; 120 121@property(nonatomic, readonly) DummyInterceptor *lastInterceptor; 122 123@end 124 125@implementation DummyInterceptorFactory { 126 BOOL _passthrough; 127} 128 129- (instancetype)initWithPassthrough:(BOOL)passthrough { 130 if ((self = [super init])) { 131 _passthrough = passthrough; 132 } 133 return self; 134} 135 136- (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager { 137 _lastInterceptor = [[DummyInterceptor alloc] 138 initWithInterceptorManager:interceptorManager 139 dispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL) 140 passthrough:_passthrough]; 141 return _lastInterceptor; 142} 143 144@end 145 146@interface TestsBlockCallbacks : NSObject <GRPCResponseHandler> 147 148- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback 149 dataCallback:(void (^)(id))dataCallback 150 closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback 151 writeMessageCallback:(void (^)(void))writeMessageCallback; 152 153@end 154 155@implementation TestsBlockCallbacks { 156 void (^_initialMetadataCallback)(NSDictionary *); 157 void (^_dataCallback)(id); 158 void (^_closeCallback)(NSDictionary *, NSError *); 159 void (^_writeMessageCallback)(void); 160 dispatch_queue_t _dispatchQueue; 161} 162 163- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback 164 dataCallback:(void (^)(id))dataCallback 165 closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback 166 writeMessageCallback:(void (^)(void))writeMessageCallback { 167 if ((self = [super init])) { 168 _initialMetadataCallback = initialMetadataCallback; 169 _dataCallback = dataCallback; 170 _closeCallback = closeCallback; 171 _writeMessageCallback = writeMessageCallback; 172 _dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL); 173 } 174 return self; 175} 176 177- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata { 178 if (_initialMetadataCallback) { 179 _initialMetadataCallback(initialMetadata); 180 } 181} 182 183- (void)didReceiveProtoMessage:(id)message { 184 if (_dataCallback) { 185 _dataCallback(message); 186 } 187} 188 189- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error { 190 if (_closeCallback) { 191 _closeCallback(trailingMetadata, error); 192 } 193} 194 195- (void)didWriteMessage { 196 if (_writeMessageCallback) { 197 _writeMessageCallback(); 198 } 199} 200 201- (dispatch_queue_t)dispatchQueue { 202 return _dispatchQueue; 203} 204@end 205 206@interface TransportTests : XCTestCase 207 208@end 209 210@implementation TransportTests 211 212- (void)testTransportInterceptors { 213 __weak XCTestExpectation *expectComplete = 214 [self expectationWithDescription:@"Expect call complete"]; 215 [GRPCFakeTransportFactory sharedInstance].nextTransportInstance = nil; 216 217 DummyInterceptorFactory *factory = [[DummyInterceptorFactory alloc] initWithPassthrough:YES]; 218 DummyInterceptorFactory *factory2 = [[DummyInterceptorFactory alloc] initWithPassthrough:NO]; 219 [[GRPCFakeTransportFactory sharedInstance] 220 setTransportInterceptorFactories:@[ factory, factory2 ]]; 221 GRPCRequestOptions *requestOptions = 222 [[GRPCRequestOptions alloc] initWithHost:kRemoteHost 223 path:@"/UnaryCall" 224 safety:GRPCCallSafetyDefault]; 225 GRPCMutableCallOptions *callOptions = [[GRPCMutableCallOptions alloc] init]; 226 callOptions.transport = kFakeTransportID; 227 GRPCCall2 *call = [[GRPCCall2 alloc] 228 initWithRequestOptions:requestOptions 229 responseHandler:[[TestsBlockCallbacks alloc] 230 initWithInitialMetadataCallback:nil 231 dataCallback:nil 232 closeCallback:^(NSDictionary *trailingMetadata, 233 NSError *error) { 234 XCTAssertNotNil(error); 235 XCTAssertEqual(error.code, 236 GRPCErrorCodeCancelled); 237 [expectComplete fulfill]; 238 } 239 writeMessageCallback:nil] 240 callOptions:callOptions]; 241 [call start]; 242 [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; 243 XCTAssertTrue(factory.lastInterceptor.hit); 244 XCTAssertTrue(factory2.lastInterceptor.hit); 245} 246 247@end 248