'use strict'; import HttpClient from '../httpClient'; import sinon from 'sinon'; import TradingClient from './trading.client'; import DomainClient from '../domain.client'; import StopoutListener from './streaming/stopoutListener'; import UserLogListener from './streaming/userLogListener'; import ConfigurationClient from './configuration.client'; /** * @test {TradingClient} */ describe('TradingClient', () => { let tradingClient; const token = 'header.payload.sign'; let httpClient = new HttpClient(); let domainClient; let configurationClient; let sandbox; let requestStub; before(() => { sandbox = sinon.createSandbox(); }); beforeEach(() => { domainClient = new DomainClient(httpClient, token); configurationClient = new ConfigurationClient(domainClient); tradingClient = new TradingClient(domainClient, configurationClient); requestStub = sandbox.stub(domainClient, 'requestCopyFactory'); }); afterEach(() => { sandbox.restore(); }); /** * @test {TradingClient#resynchronize} */ it('should resynchronize CopyFactory account', async () => { await tradingClient.resynchronize('e8867baa-5ec2-45ae-9930-4d5cea18d0d6', ['ABCD'], ['0123456']); sinon.assert.calledOnceWithExactly(domainClient.requestCopyFactory, { url: '/users/current/subscribers/e8867baa-5ec2-45ae-9930-4d5cea18d0d6/resynchronize', method: 'POST', headers: { 'auth-token': token }, json: true, params: { strategyId: ['ABCD'], positionId: ['0123456'] } }); }); /** * @test {TradingClient#resynchronize} */ it('should not resynchronize account with account token', async () => { domainClient = new DomainClient(httpClient, 'token'); tradingClient = new TradingClient(domainClient, configurationClient); try { await tradingClient.resynchronize('e8867baa-5ec2-45ae-9930-4d5cea18d0d6'); throw new Error('MethodAccessError expected'); } catch (error) { error.name.should.equal('MethodAccessError'); error.message.should.equal( 'You can not invoke resynchronize method, because you have connected with account access token. ' + 'Please use API access token from https://app.metaapi.cloud/token page to invoke this method.' ); } }); /** * @test {TradingClient#getStopouts} */ it('should retrieve stopouts', async () => { let expected = [{ strategyId: 'accountId', reason: 'monthly-balance', stoppedAt: new Date('2020-08-08T07:57:30.328Z'), strategy: { id: 'ABCD', name: 'Strategy' }, reasonDescription: 'total strategy equity drawdown exceeded limit', sequenceNumber: 2 }]; requestStub.resolves(expected); let stopouts = await tradingClient.getStopouts('e8867baa-5ec2-45ae-9930-4d5cea18d0d6'); stopouts.should.equal(expected); sinon.assert.calledOnceWithExactly(domainClient.requestCopyFactory, { url: '/users/current/subscribers/e8867baa-5ec2-45ae-9930-4d5cea18d0d6/stopouts', method: 'GET', headers: { 'auth-token': token }, json: true, }); }); /** * @test {TradingClient#getStopouts} */ it('should not retrieve stopouts from API with account token', async () => { domainClient = new DomainClient(httpClient, 'token'); tradingClient = new TradingClient(domainClient, configurationClient); try { await tradingClient.getStopouts('e8867baa-5ec2-45ae-9930-4d5cea18d0d6'); throw new Error('MethodAccessError expected'); } catch (error) { error.name.should.equal('MethodAccessError'); error.message.should.equal( 'You can not invoke getStopouts method, because you have connected with account access token. ' + 'Please use API access token from https://app.metaapi.cloud/token page to invoke this method.' ); } }); /** * @test {TradingClient#resetSubscriptionStopouts} */ it('should reset stopouts', async () => { await tradingClient.resetSubscriptionStopouts('e8867baa-5ec2-45ae-9930-4d5cea18d0d6', 'ABCD', 'daily-equity'); sinon.assert.calledOnceWithExactly(domainClient.requestCopyFactory, { url: '/users/current/subscribers/' + 'e8867baa-5ec2-45ae-9930-4d5cea18d0d6/subscription-strategies/ABCD/stopouts/daily-equity/reset', method: 'POST', headers: { 'auth-token': token }, json: true, }); }); /** * @test {TradingClient#resetSubscriptionStopouts} */ it('should not reset stopouts with account token', async () => { domainClient = new DomainClient(httpClient, 'token'); tradingClient = new TradingClient(domainClient, configurationClient); try { await tradingClient.resetSubscriptionStopouts('e8867baa-5ec2-45ae-9930-4d5cea18d0d6', 'ABCD', 'daily-equity'); throw new Error('MethodAccessError expected'); } catch (error) { error.name.should.equal('MethodAccessError'); error.message.should.equal( 'You can not invoke resetSubscriptionStopouts method, because you have connected with account access token. ' + 'Please use API access token from https://app.metaapi.cloud/token page to invoke this method.' ); } }); /** * @test {TradingClient#resetSubscriberStopouts} */ it('should reset subscriber stopouts', async () => { await tradingClient.resetSubscriberStopouts('e8867baa-5ec2-45ae-9930-4d5cea18d0d6', 'daily-equity'); sinon.assert.calledOnceWithExactly(domainClient.requestCopyFactory, { url: '/users/current/subscribers/' + 'e8867baa-5ec2-45ae-9930-4d5cea18d0d6/stopouts/daily-equity/reset', method: 'POST', headers: { 'auth-token': token }, json: true, }); }); /** * @test {TradingClient#resetSubcriberStopouts} */ it('should not reset subscriber stopouts with account token', async () => { domainClient = new DomainClient(httpClient, 'token'); tradingClient = new TradingClient(domainClient, configurationClient); try { await tradingClient.resetSubscriberStopouts('e8867baa-5ec2-45ae-9930-4d5cea18d0d6', 'daily-equity'); throw new Error('MethodAccessError expected'); } catch (error) { error.name.should.equal('MethodAccessError'); error.message.should.equal( 'You can not invoke resetSubscriberStopouts method, because you have connected with account access token. ' + 'Please use API access token from https://app.metaapi.cloud/token page to invoke this method.' ); } }); /** * @test {TradingClient#getUserLog} */ it('should retrieve copy trading user log', async () => { let expected = [{ time: new Date('2020-08-08T07:57:30.328Z'), level: 'INFO', message: 'message' }]; requestStub.resolves(expected); let records = await tradingClient.getUserLog('e8867baa-5ec2-45ae-9930-4d5cea18d0d6', new Date('2020-08-01T00:00:00.000Z'), new Date('2020-08-10T00:00:00.000Z'), 'strategyId', 'positionId'); records.should.equal(expected); sinon.assert.calledOnceWithExactly(domainClient.requestCopyFactory, { url: '/users/current/subscribers/e8867baa-5ec2-45ae-9930-4d5cea18d0d6/user-log', method: 'GET', params: { startTime: new Date('2020-08-01T00:00:00.000Z'), endTime: new Date('2020-08-10T00:00:00.000Z'), offset: 0, level: undefined, limit: 1000, strategyId: 'strategyId', positionId: 'positionId' }, headers: { 'auth-token': token }, json: true, }, true); }); /** * @test {TradingClient#getUserLog} */ it('should not retrieve copy trading user log from API with account token', async () => { domainClient = new DomainClient(httpClient, 'token'); tradingClient = new TradingClient(domainClient, configurationClient); try { await tradingClient.getUserLog('e8867baa-5ec2-45ae-9930-4d5cea18d0d6'); throw new Error('MethodAccessError expected'); } catch (error) { error.name.should.equal('MethodAccessError'); error.message.should.equal( 'You can not invoke getUserLog method, because you have connected with account access token. ' + 'Please use API access token from https://app.metaapi.cloud/token page to invoke this method.' ); } }); /** * @test {TradingClient#getStrategyLog} */ it('should retrieve copy trading strategy log', async () => { let expected = [ { time: new Date('2020-08-08T07:57:30.328Z'), level: 'INFO', message: 'message' } ]; requestStub.resolves(expected); let records = await tradingClient.getStrategyLog('ABCD', new Date('2020-08-01T00:00:00.000Z'), new Date('2020-08-10T00:00:00.000Z'), 'positionId', 'DEBUG'); records.should.equal(expected); sinon.assert.calledOnceWithExactly(domainClient.requestCopyFactory, { url: '/users/current/strategies/ABCD/user-log', method: 'GET', params: { startTime: new Date('2020-08-01T00:00:00.000Z'), endTime: new Date('2020-08-10T00:00:00.000Z'), offset: 0, limit: 1000, level: 'DEBUG', positionId: 'positionId' }, headers: { 'auth-token': token }, json: true, }, true); }); /** * @test {TradingClient#getStrategyLog} */ it('should not retrieve copy trading strategy log from API with account token', async () => { domainClient = new DomainClient(httpClient, 'token'); tradingClient = new TradingClient(domainClient, configurationClient); try { await tradingClient.getStrategyLog('ABCD'); throw new Error('MethodAccessError expected'); } catch (error) { error.name.should.equal('MethodAccessError'); error.message.should.equal( 'You can not invoke getStrategyLog method, because you have connected with account access token. ' + 'Please use API access token from https://app.metaapi.cloud/token page to invoke this method.' ); } }); /** * @test {TradingClient#getStrategySignalClient} * @test {TradingClient#getSubscriberSignalClient} */ describe('signal clients', () => { let getAccountStub; beforeEach(() => { getAccountStub = sandbox.stub(domainClient, 'getAccountInfo').withArgs('accountId'); getAccountStub.resolves({id: 'accountId', regions: ['vint-hill']}); sandbox.stub(domainClient, 'getSignalClientHost') .callsFake((regions) => ({ host: 'https://copyfactory-api-v1', regions, domain: 'agiliumtrade.ai' })); }); /** * @test {TradingClient#getSubscriberSignalClient} */ it('should get subscriber signal client', async () => { const client = await tradingClient.getSubscriberSignalClient('accountId'); sinon.assert.match(client._accountId, 'accountId'); sinon.assert.match(client._host.regions, ['vint-hill']); }); /** * @test {TradingClient#getStrategySignalClient} */ it('should get strategy signal client', async () => { let strategyInfo = { _id: 'ABCD', accountId: 'accountId', platformCommissionRate: 0.01, name: 'Test strategy', connectionId: 'e8867baa-5ec2-45ae-9930-4d5cea18d0d6', maxTradeRisk: 0.1, timeSettings: { lifetimeInHours: 192, openingIntervalInMinutes: 5 } }; sandbox.stub(configurationClient, 'getStrategy').resolves(strategyInfo); const client = await tradingClient.getStrategySignalClient('ABCD'); sinon.assert.match(client._accountId, 'accountId'); sinon.assert.match(client._strategyId, 'ABCD'); sinon.assert.match(client._host.regions, ['vint-hill']); }); }); /** * @test {TradingClient#addStopoutListener} * @test {TradingClient#removeStopoutListener} */ describe('stopoutListener', () => { let listener; beforeEach(() => { class Listener extends StopoutListener { async onStopout(strategyStopoutEvent) {} } listener = new Listener(); }); /** * @test {TradingClient#addStopoutListener} */ it('should add stopout listener', async () => { const callStub = sinon.stub(tradingClient._stopoutListenerManager, 'addStopoutListener').returns('listenerId'); const listenerId = tradingClient.addStopoutListener(listener, 'accountId', 'ABCD', 1); sinon.assert.match(listenerId, 'listenerId'); sinon.assert.calledWith(callStub, listener, 'accountId', 'ABCD', 1); }); /** * @test {TradingClient#removeStopoutListener} */ it('should remove stopout listener', async () => { const callStub = sinon.stub(tradingClient._stopoutListenerManager, 'removeStopoutListener'); tradingClient.removeStopoutListener('id'); sinon.assert.calledWith(callStub, 'id'); }); }); /** * @test {TradingClient#addStrategyLogListener} * @test {TradingClient#removeStopoutListener} */ describe('userLogListener', () => { let listener; beforeEach(() => { class Listener extends UserLogListener { async onStopout(strategyStopoutEvent) {} } listener = new Listener(); }); /** * @test {TradingClient#addStrategyLogListener} */ it('should add strategy listener', async () => { const callStub = sinon.stub(tradingClient._userLogListenerManager, 'addStrategyLogListener') .returns('listenerId'); const listenerId = tradingClient.addStrategyLogListener(listener, 'ABCD'); sinon.assert.match(listenerId, 'listenerId'); sinon.assert.calledWith(callStub, listener, 'ABCD'); }); /** * @test {TradingClient#removeStrategyLogListener} */ it('should remove strategy listener', async () => { const callStub = sinon.stub(tradingClient._userLogListenerManager, 'removeStrategyLogListener'); tradingClient.removeStrategyLogListener('id'); sinon.assert.calledWith(callStub, 'id'); }); /** * @test {TradingClient#addSubscriberLogListener} */ it('should add subscriber listener', async () => { const callStub = sinon.stub(tradingClient._userLogListenerManager, 'addSubscriberLogListener') .returns('listenerId'); const listenerId = tradingClient.addSubscriberLogListener(listener, 'accountId'); sinon.assert.match(listenerId, 'listenerId'); sinon.assert.calledWith(callStub, listener, 'accountId'); }); /** * @test {TradingClient#removeSubscriberLogListener} */ it('should remove subscriber listener', async () => { const callStub = sinon.stub(tradingClient._userLogListenerManager, 'removeSubscriberLogListener'); tradingClient.removeSubscriberLogListener('id'); sinon.assert.calledWith(callStub, 'id'); }); }); });