/*
*
* Copyright 2019 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 <GRPCClient/GRPCInterceptor.h>
#import <GRPCClient/GRPCTransport.h>
#import <XCTest/XCTest.h>

#define TEST_TIMEOUT (8.0)

static NSString *const kRemoteHost = @"grpc-test.sandbox.googleapis.com:443";

static const GRPCTransportID kFakeTransportID = "io.grpc.transport.unittest.fake";

@class GRPCFakeTransportFactory;
dispatch_once_t initFakeTransportFactory;
static GRPCFakeTransportFactory *fakeTransportFactory;

@interface GRPCFakeTransportFactory : NSObject <GRPCTransportFactory>

@property(atomic) GRPCTransport *nextTransportInstance;
- (void)setTransportInterceptorFactories:(NSArray<id<GRPCInterceptorFactory>> *)factories;

@end

@implementation GRPCFakeTransportFactory {
NSArray<id<GRPCInterceptorFactory>> *_interceptorFactories;
}

+ (instancetype)sharedInstance {
dispatch_once(&initFakeTransportFactory, ^{
fakeTransportFactory = [[GRPCFakeTransportFactory alloc] init];
});
return fakeTransportFactory;
}

+ (void)load {
[[GRPCTransportRegistry sharedInstance] registerTransportWithID:kFakeTransportID
factory:[self sharedInstance]];
}

- (GRPCTransport *)createTransportWithManager:(GRPCTransportManager *)transportManager {
return _nextTransportInstance;
}

- (void)setTransportInterceptorFactories:(NSArray<id<GRPCInterceptorFactory>> *)factories {
_interceptorFactories = [NSArray arrayWithArray:factories];
}

- (NSArray<id<GRPCInterceptorFactory>> *)transportInterceptorFactories {
return _interceptorFactories;
}

@end

@interface PhonyInterceptor : GRPCInterceptor

@property(atomic) BOOL hit;

@end

@implementation PhonyInterceptor {
GRPCInterceptorManager *_manager;
BOOL _passthrough;
}

- (instancetype)initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
dispatchQueue:(dispatch_queue_t)dispatchQueue
passthrough:(BOOL)passthrough {
if (dispatchQueue == nil) {
dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
}
if ((self = [super initWithInterceptorManager:interceptorManager dispatchQueue:dispatchQueue])) {
_manager = interceptorManager;
_passthrough = passthrough;
}
return self;
}

- (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions
callOptions:(GRPCCallOptions *)callOptions {
self.hit = YES;
if (_passthrough) {
[super startWithRequestOptions:requestOptions callOptions:callOptions];
} else {
[_manager
forwardPreviousInterceptorCloseWithTrailingMetadata:nil
error:
[NSError
errorWithDomain:kGRPCErrorDomain
code:GRPCErrorCodeCancelled
userInfo:@{
NSLocalizedDescriptionKey :
@"Canceled."
}]];
[_manager shutDown];
}
}

@end

@interface PhonyInterceptorFactory : NSObject <GRPCInterceptorFactory>

- (instancetype)initWithPassthrough:(BOOL)passthrough;

@property(nonatomic, readonly) PhonyInterceptor *lastInterceptor;

@end

@implementation PhonyInterceptorFactory {
BOOL _passthrough;
}

- (instancetype)initWithPassthrough:(BOOL)passthrough {
if ((self = [super init])) {
_passthrough = passthrough;
}
return self;
}

- (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager {
_lastInterceptor = [[PhonyInterceptor alloc]
initWithInterceptorManager:interceptorManager
dispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
passthrough:_passthrough];
return _lastInterceptor;
}

@end

@interface TestsBlockCallbacks : NSObject <GRPCResponseHandler>

- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
dataCallback:(void (^)(id))dataCallback
closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback
writeMessageCallback:(void (^)(void))writeMessageCallback;

@end

@implementation TestsBlockCallbacks {
void (^_initialMetadataCallback)(NSDictionary *);
void (^_dataCallback)(id);
void (^_closeCallback)(NSDictionary *, NSError *);
void (^_writeMessageCallback)(void);
dispatch_queue_t _dispatchQueue;
}

- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback
dataCallback:(void (^)(id))dataCallback
closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback
writeMessageCallback:(void (^)(void))writeMessageCallback {
if ((self = [super init])) {
_initialMetadataCallback = initialMetadataCallback;
_dataCallback = dataCallback;
_closeCallback = closeCallback;
_writeMessageCallback = writeMessageCallback;
_dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);
}
return self;
}

- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
if (_initialMetadataCallback) {
_initialMetadataCallback(initialMetadata);
}
}

- (void)didReceiveProtoMessage:(id)message {
if (_dataCallback) {
_dataCallback(message);
}
}

- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
if (_closeCallback) {
_closeCallback(trailingMetadata, error);
}
}

- (void)didWriteMessage {
if (_writeMessageCallback) {
_writeMessageCallback();
}
}

- (dispatch_queue_t)dispatchQueue {
return _dispatchQueue;
}
@end

@interface TransportTests : XCTestCase

@end

@implementation TransportTests

- (void)testTransportInterceptors {
__weak XCTestExpectation *expectComplete =
[self expectationWithDescription:@"Expect call complete"];
[GRPCFakeTransportFactory sharedInstance].nextTransportInstance = nil;

PhonyInterceptorFactory *factory = [[PhonyInterceptorFactory alloc] initWithPassthrough:YES];
PhonyInterceptorFactory *factory2 = [[PhonyInterceptorFactory alloc] initWithPassthrough:NO];
[[GRPCFakeTransportFactory sharedInstance]
setTransportInterceptorFactories:@[ factory, factory2 ]];
GRPCRequestOptions *requestOptions =
[[GRPCRequestOptions alloc] initWithHost:kRemoteHost
path:@"/UnaryCall"
safety:GRPCCallSafetyDefault];
GRPCMutableCallOptions *callOptions = [[GRPCMutableCallOptions alloc] init];
callOptions.transport = kFakeTransportID;
GRPCCall2 *call = [[GRPCCall2 alloc]
initWithRequestOptions:requestOptions
responseHandler:[[TestsBlockCallbacks alloc]
initWithInitialMetadataCallback:nil
dataCallback:nil
closeCallback:^(NSDictionary *trailingMetadata,
NSError *error) {
XCTAssertNotNil(error);
XCTAssertEqual(error.code,
GRPCErrorCodeCancelled);
[expectComplete fulfill];
}
writeMessageCallback:nil]
callOptions:callOptions];
[call start];
[self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
XCTAssertTrue(factory.lastInterceptor.hit);
XCTAssertTrue(factory2.lastInterceptor.hit);
}

@end