import * as _ from "lodash"; import * as all from "./_all"; import { InterceptorContext, IInterceptStrategy, InterceptionAction } from "./InterceptorContext"; import { CurrentInterceptContext } from "./CurrentInterceptContext"; import * as strategy from "./InterceptorStrategies"; export class InterceptorExecute implements all.ICallInterceptor { private _interceptorContext: InterceptorContext; constructor(mock: all.IMock) { this._interceptorContext = new InterceptorContext(mock); } get interceptorContext(): InterceptorContext { return this._interceptorContext; } intercept(invocation: all.ICallContext) { let localCtx = new CurrentInterceptContext(); invocation.invocationType = all.InvocationType.EXECUTE; _.some(this.interceptionStrategies(), (strategy: IInterceptStrategy) => { if (InterceptionAction.Stop === strategy.handleIntercept(invocation, this.interceptorContext, localCtx)) { return true; } }); } removeInvocation(invocation: all.ICallContext) { this._interceptorContext.removeInvocation(invocation); } addExpectedCall(call: all.IProxyCall): void { this._interceptorContext.addExpectedCall(call); } verify(): void { let expectedCalls = this._interceptorContext.expectedCalls(); let verifiableCalls: Array> = []; if (this._interceptorContext.behavior == all.MockBehavior.Strict) { // verifiable by default when strict mocking for (let call of expectedCalls) { if (!call.isVerifiable) call.setVerifiable(); verifiableCalls.push(call); } } else { verifiableCalls = _.filter(expectedCalls, (c: all.IProxyCall) => c.isVerifiable); } for (let v of verifiableCalls) this.verifyCallCount(v, v.expectedCallCount); let orderedCalls: Array> = _.filter(expectedCalls, (c: all.IProxyCall) => c.isInSequence); this.verifyCallsOrder(orderedCalls); } verifyCallCount(call: all.IProxyCall, times: all.Times): void { let expectedCalls = this._interceptorContext.expectedCalls(); let actualCalls = this._interceptorContext.actualInvocations(); let callCount: number = _.filter(actualCalls, (c: all.ICallContext) => call.matches(c)).length; if (!times.verify(callCount)) this.throwVerifyCallCountException(call.setupCall, times, expectedCalls, actualCalls); } private throwVerifyCallCountException(setupCall: all.ICallContext, times: all.Times, expectedCalls: Array>, actualCalls: Array) { let failMsg = times.failMessage(setupCall); let expectedCallsMsg = expectedCalls.reduce((a, x) => `${a} ${x}\n`, ""); let actualCallsMsg = actualCalls.reduce((a, x) => `${a} ${x}\n`, ""); let msg = `${failMsg}\n Configured setups:\n${expectedCallsMsg}\n Performed invocations:\n${actualCallsMsg}`; let e = new all.MockException(all.MockExceptionReason.CallCountVerificationFailed, setupCall, msg); throw e; } private verifyCallsOrder(expectedCalls: Array>): void { let actualCalls = this._interceptorContext.actualInvocations(); this.checkCallOrderExpectations(expectedCalls, actualCalls); } private checkCallOrderExpectations(expectedCalls: Array>, actualCalls: Array): void { let checkOrder = (expectedCallCountList: Array): boolean => { let expectedCallCount = _.sum(expectedCallCountList); let aci = 0; for (let eci = 0; eci < expectedCallCountList.length; eci++) { let expectedCall = expectedCalls[eci]; let expectedCallCount = expectedCallCountList[eci]; for (let count = 1; count <= expectedCallCount; count++) { let actualCall = actualCalls[aci++]; if (!expectedCall.matches(actualCall)) return false; } } return aci === expectedCallCount; } let eureka = false; let execute = (acc: Array, i: number) => { if (!eureka) { if (i === expectedCalls.length) eureka = checkOrder(acc); else for (let j = expectedCalls[i].expectedCallCount.min; j <= expectedCalls[i].expectedCallCount.max; j++) { acc[i] = j; execute(acc, i + 1); } } } execute([], 0); if (!eureka) this.throwVerifyCallOrderException(); } private throwVerifyCallOrderException() { let e = new all.MockException(all.MockExceptionReason.CallOrderVerificationFailed, null); throw e; } reset(): void { this._interceptorContext.reset(); } private interceptionStrategies(): _.List> { let strategies: _.List> = [ new strategy.AddActualInvocation(), new strategy.ExtractProxyCall(), new strategy.ExecuteCall(), new strategy.InvokeBase(), new strategy.HandleMockRecursion() ]; return strategies; } }