import { RingCentralCallControl } from '../src/index'; import { USER_AGENT } from '../src/userAgent'; import * as mock from './mock/sdk'; import * as extensionInfo from './mock/data/extensionInfo.json'; import * as telephonySessionInboundProceedingMessage from './mock/data/telephonySessionInboundProceedingMessage.json'; import * as telephonySessionOutboundSetupMessage from './mock/data/telephonySessionOutboundSetupMessage.json'; import * as telephonySessionOutboundDisconnectedMessage from './mock/data/telephonySessionOutboundDisconnectedMessage.json'; import * as telephonySessionOutboundData from './mock/data/telephonySessionOutbound.json'; let sdk; let rcCallControl; function getMockEventMessage({ template, telephonySessionId, sequence, code, reason, party, partyId, }: { template: any; telephonySessionId?: string; sequence?: number; code?: string; reason?: string; partyId?: string; party?: any; }) { let partyData = party || template.body.parties[0]; return { ...template, body: { ...template.body, telephonySessionId: telephonySessionId || template.body.telephonySessionId, sequence: sequence || template.body.sequence, parties: [{ ...partyData, id: partyId || partyData.id, status: { ...partyData.status, code: code || partyData.status.code, reason: reason || partyData.status.reason, }, }] }, }; } describe('RingCentral Call Control :: Index', () => { beforeAll(async () => { mock.mockAuthentication(); sdk = mock.createSDK(); await sdk.platform().login({ username: '...', password: '...' }); }); describe('Initialize', () => { beforeAll(async () => { mock.mockDevice(); mock.mockExtensionInfo(); mock.mockPresence(); mock.mockTelephoneSession(); rcCallControl = new RingCentralCallControl({ sdk }); await rcCallControl.initialize(); }); it('should be ready after initialized', () => { expect(rcCallControl.ready).toEqual(true); }); it('should have default userAgent after initialized', () => { expect(rcCallControl.requestOptions.userAgent).toEqual(USER_AGENT); }); it('should not initialize duplicate', () => { rcCallControl.initialize(); expect(rcCallControl._initializePromise).toEqual(null); }); it('should load devices after initialized', () => { expect(rcCallControl.devices.length).toEqual(1); }); it('should load sessions after initialized', () => { expect(rcCallControl.sessions.length).toEqual(1); expect(Array.from(rcCallControl.sessionsMap.keys()).length).toEqual(1); }); it('should delete session after get disconnected event', () => { rcCallControl.onNotificationEvent(telephonySessionOutboundDisconnectedMessage); expect(rcCallControl.sessions.length).toEqual(0); }); it('should load extensionInfo after initialized', () => { expect(rcCallControl.extensionId).toEqual(String(extensionInfo.id)); expect(rcCallControl.accountId).toEqual(String(extensionInfo.account.id)); }); it('should refresh devices successfully', async () => { mock.mockDevice({ records: [] }); await rcCallControl.refreshDevices(); expect(rcCallControl.devices.length).toEqual(0); mock.mockDevice(); await rcCallControl.refreshDevices(); expect(rcCallControl.devices.length).toEqual(1); }); }); describe('Initialize without preload', () => { beforeAll(async () => { rcCallControl = new RingCentralCallControl({ sdk, preloadDevices: false, preloadSessions: false, extensionInfo: extensionInfo as any, }); await rcCallControl.initialize(); }); it('should be ready after initialized', () => { expect(rcCallControl.ready).toEqual(true); expect(rcCallControl.devices.length).toEqual(0); expect(rcCallControl.sessions.length).toEqual(0); expect(rcCallControl.extensionId).toEqual(String(extensionInfo.id)); expect(rcCallControl.accountId).toEqual(String(extensionInfo.account.id)); }); }); describe('Initialize with API failed', () => { beforeAll(async () => { rcCallControl = new RingCentralCallControl({ sdk }); await rcCallControl.initialize(); }); it('should be ready after initialized', () => { expect(rcCallControl.ready).toEqual(true); }); }); describe('Initialize with User Agent', () => { beforeAll(async () => { rcCallControl = new RingCentralCallControl({ sdk, userAgent: 'TestAgent' }); await rcCallControl.initialize(); }); it('should be ready after initialized', () => { expect(rcCallControl.requestOptions.userAgent).toEqual(`TestAgent ${USER_AGENT}`); }); }); describe('Restore sessions', () => { it('should restore session as new session', async () => { const callControl = new RingCentralCallControl({ sdk, preloadDevices: false, preloadSessions: false, extensionInfo: extensionInfo as any, }); await callControl.initialize(); expect(callControl.sessions.length).toEqual(0); callControl.restoreSessions([ { ...telephonySessionOutboundData, extensionId: String(extensionInfo.id), accountId: String(extensionInfo.account.id), } ]); expect(callControl.sessions.length).toEqual(1); }); it('should restore session with existed session', async () => { mock.mockPresence(); mock.mockTelephoneSession(); const callControl = new RingCentralCallControl({ sdk, preloadDevices: false, extensionInfo: extensionInfo as any, }); await callControl.initialize(); expect(callControl.sessions.length).toEqual(1); const oldSession = callControl.sessions[0]; callControl.restoreSessions([ { ...telephonySessionOutboundData, extensionId: String(extensionInfo.id), accountId: String(extensionInfo.account.id), } ]); expect(callControl.sessions.length).toEqual(1); expect(callControl.sessions[0]).toEqual(oldSession); }); }); describe('Notification Event', () => { beforeAll(async () => { mock.mockDevice(); mock.mockExtensionInfo() mock.mockPresence({ activeCalls: [] }); mock.mockTelephoneSession(); rcCallControl = new RingCentralCallControl({ sdk }); await rcCallControl.initialize(); }); it('should load empty sessions firstly', () => { expect(rcCallControl.sessions.length).toEqual(0); }); it('should not handle no telephony session event', () => { rcCallControl.onNotificationEvent({ event: '/restapi/v1.0/account/170848004/telephony/sessions', body: {} }); expect(rcCallControl.sessions.length).toEqual(0); }); it('should not handle no telephony session id', () => { rcCallControl.onNotificationEvent({ event: '/restapi/v1.0/account/170848004/extension/170848004' }); expect(rcCallControl.sessions.length).toEqual(0); }); it('should not create new sessions when get telephony session disconnected event', () => { let newEventTriggered = false; rcCallControl.once('new', () => { newEventTriggered = true; }); rcCallControl.onNotificationEvent(getMockEventMessage({ template: telephonySessionOutboundDisconnectedMessage, telephonySessionId: '12345', partyId: '12345-1', })); expect(rcCallControl.sessions.length).toEqual(0); expect(newEventTriggered).toEqual(false); }); it('should create new session and new event when get telephony session event', () => { let newEventTriggered = false; rcCallControl.once('new', () => { newEventTriggered = true; }); rcCallControl.onNotificationEvent(getMockEventMessage({ template: telephonySessionOutboundSetupMessage, telephonySessionId: '123456', partyId: '123456-1', })); expect(rcCallControl.sessions.length).toEqual(1); expect(rcCallControl.sessions[0].parties.length).toEqual(1); expect(newEventTriggered).toEqual(true); }); it('should update session when get telephony session updated event', () => { rcCallControl.onNotificationEvent(getMockEventMessage({ template: telephonySessionInboundProceedingMessage, telephonySessionId: '123456', partyId: '123456-2', })); expect(rcCallControl.sessions.length).toEqual(1); expect(rcCallControl.sessions[0].parties.length).toEqual(2); }); it('should not update current party when get telephony session with outdated sequence', () => { const oldStatus = rcCallControl.sessions[0].parties[0].status; rcCallControl.onNotificationEvent(getMockEventMessage({ template: telephonySessionOutboundSetupMessage, telephonySessionId: '123456', partyId: '123456-1', sequence: telephonySessionOutboundSetupMessage.body.sequence - 1, code: 'Disconnected', })); expect(rcCallControl.sessions.length).toEqual(1); expect(rcCallControl.sessions[0].parties[0].status.code).toEqual(oldStatus.code); }); it('should not update session when get telephony session with outdated sequence', () => { const oldStatus = rcCallControl.sessions[0].parties[1].status; rcCallControl.onNotificationEvent(getMockEventMessage({ template: telephonySessionInboundProceedingMessage, telephonySessionId: '123456', partyId: '123456-2', sequence: telephonySessionInboundProceedingMessage.body.sequence - 1, code: 'Disconnected', })); expect(rcCallControl.sessions.length).toEqual(1); expect(rcCallControl.sessions[0].parties[1].status.code).toEqual(oldStatus.code); }); it('should not update session when get telephony session no updated event', () => { const oldStatus = rcCallControl.sessions[0].party.status; rcCallControl.onNotificationEvent(getMockEventMessage({ template: telephonySessionInboundProceedingMessage, telephonySessionId: '123456', partyId: '123456-2', })); expect(rcCallControl.sessions.length).toEqual(1); expect(rcCallControl.sessions[0].party.status.code).toEqual(oldStatus.code); }); it('should not delete session when get telephony session disconnected event with CallSwitch reason', () => { rcCallControl.onNotificationEvent(getMockEventMessage({ template: telephonySessionOutboundDisconnectedMessage, telephonySessionId: '123456', partyId: '123456-1', reason: 'CallSwitch', })); expect(rcCallControl.sessions.length).toEqual(1); expect(rcCallControl.sessions[0].party.status.reason).toEqual('CallSwitch'); }); it('should delete session when get telephony session disconnected event', () => { rcCallControl.onNotificationEvent(getMockEventMessage({ template: telephonySessionOutboundDisconnectedMessage, telephonySessionId: '123456', partyId: '123456-1', })); expect(rcCallControl.sessions.length).toEqual(0); }); it('should not add session after get telephony session Setup event with wrong sequence', () => { rcCallControl.onNotificationEvent(getMockEventMessage({ template: telephonySessionOutboundDisconnectedMessage, telephonySessionId: '1234567', partyId: '1234567-1', sequence: 3, })); expect(rcCallControl.sessions.length).toEqual(0); rcCallControl.onNotificationEvent(getMockEventMessage({ template: telephonySessionOutboundSetupMessage, telephonySessionId: '1234567', partyId: '1234567-1', sequence: 2, })); expect(rcCallControl.sessions.length).toEqual(0); }); it('should clear expired sequence data when get a new telephony session event', () => { const sequenceDataMap = rcCallControl.eventSequenceMap; sequenceDataMap['old-telephone-session-id-1'] = { sequence: 1, telephoneSessionId: 'old-telephone-session-id', updatedAt: Date.now() - 61000, }; rcCallControl.onNotificationEvent(getMockEventMessage({ template: telephonySessionOutboundDisconnectedMessage, telephonySessionId: '123456', partyId: '123456-1', })); expect(!!sequenceDataMap['old-telephone-session-id-1']).toEqual(false); }); it('should emit new event when get telephony session first my party', () => { let newEventTriggered = false; rcCallControl.once('new', () => { newEventTriggered = true; }); const notPartiesMessage = getMockEventMessage({ template: telephonySessionOutboundSetupMessage, telephonySessionId: '12345678', partyId: '12345678-1', party: { ...telephonySessionInboundProceedingMessage.body.parties[0], } }); rcCallControl.onNotificationEvent(notPartiesMessage); expect(rcCallControl.sessions.length).toEqual(1); expect(!!rcCallControl.sessions[0].party).toEqual(false); expect(rcCallControl.sessions[0].otherParties.length).toEqual(1); expect(rcCallControl.sessions[0].parties.length).toEqual(1); expect(newEventTriggered).toEqual(false); rcCallControl.onNotificationEvent(getMockEventMessage({ template: telephonySessionOutboundSetupMessage, telephonySessionId: '12345678', partyId: '12345678-2', })); expect(newEventTriggered).toEqual(true); }); it('should not clear expired sequence data when telephony session is still existed', () => { const sequenceDataMap = rcCallControl.eventSequenceMap; sequenceDataMap['12345678-2'].updatedAt = Date.now() - 61000; rcCallControl.onNotificationEvent(getMockEventMessage({ template: telephonySessionOutboundDisconnectedMessage, telephonySessionId: '123456', partyId: '123456-1', })); expect(!!sequenceDataMap['12345678-2']).toEqual(true); }); }); describe('Create call', () => { beforeAll(async () => { mock.mockDevice(); mock.mockExtensionInfo() mock.mockPresence({ activeCalls: [] }); mock.mockTelephoneSession(); rcCallControl = new RingCentralCallControl({ sdk }); await rcCallControl.initialize(); }); it('should create call session successfully', async () => { mock.mockTelephoneSessionCallOut(); const deviceId = rcCallControl.devices.filter(d => d.status === 'Online')[0].id; const session = await rcCallControl.createCall(deviceId, { phoneNumber: '+12345678900' }); expect(rcCallControl.sessions.length).toEqual(1); expect(rcCallControl.sessions[0].id).toEqual(session.id); }); it('should delete call when get disconnected event', async () => { rcCallControl.onNotificationEvent(telephonySessionOutboundDisconnectedMessage); expect(rcCallControl.sessions.length).toEqual(0); }); }); describe('Create conference', () => { beforeAll(async () => { mock.mockDevice(); mock.mockExtensionInfo() mock.mockPresence({ activeCalls: [] }); mock.mockTelephoneSession(); rcCallControl = new RingCentralCallControl({ sdk }); await rcCallControl.initialize(); }); it('should create conference successfully', async () => { mock.mockTelephoneConference(); const session = await rcCallControl.createConference(); expect(rcCallControl.sessions.length).toEqual(1); expect(rcCallControl.sessions[0].parties.length).toEqual(0); }); }); describe('Account Level', () => { beforeAll(async () => { mock.mockDevice(); mock.mockExtensionInfo() mock.mockAccountPresence(); mock.mockTelephoneSession(); rcCallControl = new RingCentralCallControl({ sdk, accountLevel: true }); await rcCallControl.initialize(); }); it('should load active calls successfully', async () => { expect(rcCallControl.sessions.length).toEqual(1); }); }); });