metaapi.cloud-sdk
Version:
SDK for MetaApi, a professional cloud forex API which includes MetaTrader REST API and MetaTrader websocket API. Supports both MetaTrader 5 (MT5) and MetaTrader 4 (MT4). CopyFactory copy trading API included. (https://metaapi.cloud)
109 lines (102 loc) • 4.25 kB
text/typescript
;
import * as testHelpers from './testHelpers';
import {AssertionError} from 'assert';
import {DeepPartial} from '../../types/util';
import type sinon from 'sinon';
import util from 'util';
import 'should';
/**
* Asserts that a stub or apy called specified number of times with args matched specified ones
* @param {Function} spy Sinon stub or spy
* @param {Number} calls Expected number of calls
* @param {Array} args Expected call arguments
* @throws {AssertionError} If assertion failed
*/
export function callCountWithMatch<Spy extends sinon.SinonSpy>(
spy: Spy, calls: number, ...args: DeepPartial<Parameters<Spy>>
) {
if (!(spy as any).isSinonProxy) {
throw new AssertionError({message: 'Given function is not a sinon spy'});
}
let matchedCalls = testHelpers.getCallsWithMatch(spy, ...args);
if (matchedCalls.length !== calls) {
throw Object.assign(
new AssertionError({message: `Spy expected to be called ${calls} times with specified arguments but was called ` +
`${matchedCalls.length} times with them of ${spy.callCount} total calls`}),
{callArgs: util.inspect(spy.args, {depth: 3})}
);
}
}
/**
* Asserts that a stub or apy called specified number of times with args that deep equal to specified ones
* @param {Function} spy Sinon stub or spy
* @param {Number} calls Expected number of calls
* @param {Array} args Expected call arguments
* @throws {AssertionError} If assertion failed
*/
export function callCountWithExactly<Spy extends sinon.SinonSpy>(spy: Spy, calls: number, ...args: Parameters<Spy>) {
if (!(spy as any).isSinonProxy) {
throw new AssertionError({message: 'Given function is not a sinon spy'});
}
let matchedCalls = testHelpers.getCallsWithExactly(spy, ...args as DeepPartial<Parameters<Spy>>);
matchedCalls.length.should.equal(calls, `Spy expected to be called ${calls} times with specified arguments ` +
`but was called ${matchedCalls.length} times with them of ${spy.callCount} total calls`);
}
/**
* Asserts that array matches to expected one with and the lengths are equal
* @param {Array} actualArray Actual array to match
* @param {Array} expectedArray Expected array to match to
* @throws {AssertionError} If assertion failed
*/
export function arrayMatchWithEqualLength(actualArray, expectedArray) {
actualArray.should.match(expectedArray);
actualArray.length.should.equal(expectedArray.length);
}
/** Spy call options */
export type SpyCall<TArgs extends readonly any[]> = {
/** Sinon spy */
spy: sinon.SinonSpy<TArgs>,
/** Call index */
call: number,
/** If specified, asserts args match */
matchArgs?: DeepPartial<TArgs>,
/** Logging label. Defaults to `spy` function name, which defaults to `default` */
label?: string
};
/**
* Sinon's callOrder seems to have some bug giving incorrect assertion, so this is a manual implementation
* @param spyCalls spy calls
* @throws assertion error
*/
export function callOrder(spyCalls: SpyCall<any>[]) {
type CallId = {id: number, label: string};
let callIds: CallId[] = spyCalls.map((call, index) => {
const label = call.label || call.spy.name || 'default';
let sinonCall = call.spy.getCall(call.call);
if (!sinonCall) {
throw new AssertionError({message: `Call ${index} (${label}) does not exist`});
}
return {id: (sinonCall as any).callId, label};
});
let previousCallId: CallId;
for (let index = 0; index < spyCalls.length; ++index) {
if (previousCallId !== undefined && callIds[index].id < previousCallId.id) {
throw new AssertionError({
message: `Wrong call order detected in call ids ${callIds.map(id => `\n${id.id} (${id.label})`)}`,
expected: `Call id larger than the previous call ${previousCallId}`,
actual: callIds[index]
});
}
if (spyCalls[index].matchArgs) {
try {
spyCalls[index].spy.getCall(spyCalls[index].call).args.should.match(spyCalls[index].matchArgs);
} catch (err) {
throw Object.assign(
new AssertionError({message: `Call ${index} (${callIds[index].label}) args do not match`}),
{index, label: callIds[index].label, cause: err}
);
}
}
previousCallId = callIds[index];
}
}