Skip to content

Commit

Permalink
fix(plugin-cc): ut-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Kesari3008 committed Dec 4, 2024
1 parent 847f0cc commit f381fab
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 42 deletions.
14 changes: 5 additions & 9 deletions packages/@webex/plugin-cc/src/services/task/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,11 @@ export default class Task extends EventEmitter implements ITask {
/**
* This is used for incoming task accept by agent.
*
* @param taskId - Unique Id to identify each task
*
* @returns Promise<TaskResponse>
* @throws Error
* @example
* ```typescript
* task.accept(taskId).then(()=>{}).catch(()=>{})
* task.accept().then(()=>{}).catch(()=>{})
* ```
*/
public async accept(): Promise<TaskResponse> {
Expand All @@ -68,13 +66,13 @@ export default class Task extends EventEmitter implements ITask {
const localStream = await navigator.mediaDevices.getUserMedia(constraints);
const audioTrack = localStream.getAudioTracks()[0];
this.localAudioStream = new LocalMicrophoneStream(new MediaStream([audioTrack]));
this.webCallingService.answerCall(this.localAudioStream, this.data.taskId);
this.webCallingService.answerCall(this.localAudioStream, this.data.interactionId);

return Promise.resolve(); // TODO: Update this with sending the task object received in AgentContactAssigned
}

// TODO: Invoke the accept API from services layer. This is going to be used in Outbound Dialer scenario
return this.contact.accept({interactionId: this.data.taskId});
return this.contact.accept({interactionId: this.data.interactionId});
} catch (error) {
const {error: detailedError} = getErrorDetails(error, 'accept', CC_FILE);
throw detailedError;
Expand All @@ -84,18 +82,16 @@ export default class Task extends EventEmitter implements ITask {
/**
* This is used for the incoming task decline by agent.
*
* @param taskId - Unique Id to identify each task
*
* @returns Promise<TaskResponse>
* @throws Error
* @example
* ```typescript
* task.decline(taskId).then(()=>{}).catch(()=>{})
* task.decline().then(()=>{}).catch(()=>{})
* ```
*/
public async decline(): Promise<TaskResponse> {
try {
this.webCallingService.declineCall(this.data.taskId);
this.webCallingService.declineCall(this.data.interactionId);
this.unregisterWebCallListeners();

return Promise.resolve();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ describe('WebCallingService', () => {
);

mockCall = {
on: jest.fn(),
off: jest.fn(),
answer: jest.fn(),
mute: jest.fn(),
isMuted: jest.fn().mockReturnValue(true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ describe('TaskManager', () => {
webSocketManagerMock.emit('message', JSON.stringify(payload));

expect(Task).toHaveBeenCalledWith(contactMock, webCallingServiceMock , payload.data);
expect(taskIncomingSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, taskManager.task);
expect(taskManager.getTask(payload.data.interactionId)).toBe(taskManager.task);
expect(taskIncomingSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, taskManager.currentTask);
expect(taskManager.getTask(payload.data.interactionId)).toBe(taskManager.currentTask);
expect(taskManager.getAllTasks()).toHaveProperty(payload.data.interactionId);

const assignedPayload = {
Expand All @@ -97,12 +97,31 @@ describe('TaskManager', () => {

webSocketManagerMock.emit('message', JSON.stringify(assignedPayload));

expect(taskAssignedSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, taskManager.task);
expect(taskAssignedSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_ASSIGNED, taskManager.currentTask);
});

it('should return task by ID', () => {
const taskId = 'task123';
const mockTask = jest.fn();
const mockTask = {
accept: jest.fn(),
decline: jest.fn(),
updateTaskData: jest.fn(),
data: {
type: CC_EVENTS.AGENT_CONTACT_ASSIGNED,
agentId: "723a8ffb-a26e-496d-b14a-ff44fb83b64f",
eventTime: 1733211616959,
eventType: "RoutingMessage",
interaction: {},
interactionId: taskId,
orgId: "6ecef209-9a34-4ed1-a07a-7ddd1dbe925a",
trackingId: "575c0ec2-618c-42af-a61c-53aeb0a221ee",
mediaResourceId: '0ae913a4-c857-4705-8d49-76dd3dde75e4',
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
queueMgr: 'aqm'
}
};

taskManager.taskCollection[taskId] = mockTask;

expect(taskManager.getTask(taskId)).toBe(mockTask);
Expand All @@ -111,8 +130,45 @@ describe('TaskManager', () => {
it('should return all tasks', () => {
const taskId1 = 'task123';
const taskId2 = 'task456';
const mockTask1 = jest.fn();
const mockTask2 = jest.fn();
const mockTask1 = {
accept: jest.fn(),
decline: jest.fn(),
updateTaskData: jest.fn(),
data: {
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
agentId: "723a8ffb-a26e-496d-b14a-ff44fb83b64f",
eventTime: 1733211616959,
eventType: "RoutingMessage",
interaction: {},
interactionId: taskId1,
orgId: "6ecef209-9a34-4ed1-a07a-7ddd1dbe925a",
trackingId: "575c0ec2-618c-42af-a61c-53aeb0a221ee",
mediaResourceId: '0ae913a4-c857-4705-8d49-76dd3dde75e4',
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
queueMgr: 'aqm'
}
};

const mockTask2 = {
accept: jest.fn(),
decline: jest.fn(),
updateTaskData: jest.fn(),
data: {
type: CC_EVENTS.AGENT_CONTACT_ASSIGNED,
agentId: "723a8ffb-a26e-496d-b14a-ff44fb83b64f",
eventTime: 1733211616959,
eventType: "RoutingMessage",
interaction: {},
interactionId: taskId2,
orgId: "6ecef209-9a34-4ed1-a07a-7ddd1dbe925a",
trackingId: "575c0ec2-618c-42af-a61c-53aeb0a221ee",
mediaResourceId: '0ae913a4-c857-4705-8d49-76dd3dde75e4',
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
queueMgr: 'aqm'
}
};

taskManager.taskCollection[taskId1] = mockTask1;
taskManager.taskCollection[taskId2] = mockTask2;
Expand Down
122 changes: 95 additions & 27 deletions packages/@webex/plugin-cc/test/unit/spec/services/task/index.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,84 @@
import 'jsdom-global/register';
import {LocalMicrophoneStream} from '@webex/calling';
import {LoginOption} from '../../../../../src/types';
import {CALL_EVENT_KEYS, CallingClientConfig, LocalMicrophoneStream} from '@webex/calling';
import {LoginOption, WebexSDK} from '../../../../../src/types';
import { CC_FILE } from '../../../../../src/constants';
import Task from '../../../../../src/services/task';
import * as Utils from '../../../../../src/services/core/Utils';
import { CC_EVENTS } from '../../../../../src/services/config/types';
import config from '../../../../../src/config';
import WebCallingService from '../../../../../src/services/WebCallingService';

jest.mock('@webex/calling');

describe('Task', () => {
let onSpy;
let task;
let contactMock;
let taskDataMock;
let webCallingServiceMock;
let webCallingService;
let getErrorDetailsSpy;
const taskId = 'taskId123';
let webex: WebexSDK

const taskId = '0ae913a4-c857-4705-8d49-76dd3dde75e4';
const mockTrack = {} as MediaStreamTrack;
const mockStream = {
outputStream: {
getAudioTracks: jest.fn().mockReturnValue([mockTrack]),
}
};

beforeEach(() => {
webex = {
logger: {
log: jest.fn(),
error: jest.fn(),
info: jest.fn()
},
} as unknown as WebexSDK;

contactMock = {
accept: jest.fn().mockResolvedValue({}),
};

webCallingServiceMock = {
loginOption: LoginOption.BROWSER,
answerCall: jest.fn(),
declineCall: jest.fn()
webCallingService = new WebCallingService(
webex,
config.cc.callingClientConfig as CallingClientConfig
);

webCallingService.loginOption = LoginOption.BROWSER;
onSpy = jest.spyOn(webCallingService, 'on');

// Mock task data
taskDataMock = {
type: CC_EVENTS.AGENT_CONTACT_RESERVED,
agentId: "723a8ffb-a26e-496d-b14a-ff44fb83b64f",
eventTime: 1733211616959,
eventType: "RoutingMessage",
interaction: {},
interactionId: taskId,
orgId: "6ecef209-9a34-4ed1-a07a-7ddd1dbe925a",
trackingId: "575c0ec2-618c-42af-a61c-53aeb0a221ee",
mediaResourceId: '0ae913a4-c857-4705-8d49-76dd3dde75e4',
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
queueMgr: 'aqm',
};

// Mock task data
taskDataMock = {};

// Create an instance of Task
task = new Task(contactMock, webCallingServiceMock, taskDataMock);
task = new Task(contactMock, webCallingService, taskDataMock);

// Mock navigator.mediaDevices
global.navigator.mediaDevices = {
getUserMedia: jest.fn(() =>
Promise.resolve({
getAudioTracks: jest.fn().mockReturnValue([''])
getAudioTracks: jest.fn().mockReturnValue([mockTrack])
})
),
};

// Mock MediaStream (if needed)
global.MediaStream = jest.fn().mockImplementation((tracks) => {
return {
getTracks: jest.fn(() => tracks),
};
return mockStream;
});

getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails')
Expand All @@ -56,18 +89,49 @@ describe('Task', () => {
jest.clearAllMocks();
});

it('test the on spy', async () => {
expect(onSpy).toHaveBeenCalledWith(CALL_EVENT_KEYS.REMOTE_MEDIA, onSpy.mock.calls[0][1]);
});

it('test updating the task data', async () => {
const newData = {
type: CC_EVENTS.AGENT_CONTACT_ASSIGNED,
agentId: "723a8ffb-a26e-496d-b14a-ff44fb83b64f",
eventTime: 1733211616959,
eventType: "RoutingMessage",
interaction: {},
interactionId: taskId,
orgId: "6ecef209-9a34-4ed1-a07a-7ddd1dbe925a",
trackingId: "575c0ec2-618c-42af-a61c-53aeb0a221ee",
mediaResourceId: '0ae913a4-c857-4705-8d49-76dd3dde75e4',
destAgentId: 'ebeb893b-ba67-4f36-8418-95c7492b28c2',
owner: '723a8ffb-a26e-496d-b14a-ff44fb83b64f',
queueMgr: 'aqm',
};

expect(task.data).toEqual(taskDataMock);

task.updateTaskData(newData);

expect(task.data).toEqual(newData);
});

it('should accept a task and answer call when using BROWSER login option', async () => {
await task.accept(taskId);
const answerCallSpy = jest.spyOn(webCallingService, 'answerCall');

await task.accept();

expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({ audio: true });
expect(webCallingServiceMock.answerCall).toHaveBeenCalledWith(expect.any(LocalMicrophoneStream), taskId);
expect(LocalMicrophoneStream).toHaveBeenCalledWith(mockStream);
expect(answerCallSpy).toHaveBeenCalledWith(expect.any(LocalMicrophoneStream), taskId);
});

it('should call accept API for Extension login option', async () => {
webCallingServiceMock.loginOption = LoginOption.EXTENSION
await task.accept(taskId);
webCallingService.loginOption = LoginOption.EXTENSION

await task.accept();

expect(contactMock.accept).toHaveBeenCalledWith({ interactionId: taskId });
expect(contactMock.accept).toHaveBeenCalledWith({interactionId: taskId});
});

it('should handle errors in accept method', async () => {
Expand All @@ -80,16 +144,20 @@ describe('Task', () => {
},
};

webCallingServiceMock.answerCall.mockImplementation(() => { throw error });
jest.spyOn(webCallingService, 'answerCall').mockImplementation(() => { throw error });

await expect(task.accept(taskId)).rejects.toThrow(new Error(error.details.data.reason));
await expect(task.accept()).rejects.toThrow(new Error(error.details.data.reason));
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'accept', CC_FILE);
});

it('should decline call using webCallingService', async () => {
await task.decline(taskId);
const declineCallSpy = jest.spyOn(webCallingService, 'declineCall');
const offSpy = jest.spyOn(webCallingService, 'off');

await task.decline();

expect(webCallingServiceMock.declineCall).toHaveBeenCalledWith(taskId);
expect(declineCallSpy).toHaveBeenCalledWith(taskId);
expect(offSpy).toHaveBeenCalledWith(CALL_EVENT_KEYS.REMOTE_MEDIA, offSpy.mock.calls[0][1]);
});

it('should handle errors in decline method', async () => {
Expand All @@ -102,8 +170,8 @@ describe('Task', () => {
},
};

webCallingServiceMock.declineCall.mockImplementation(() => { throw error });
await expect(task.decline(taskId)).rejects.toThrow(new Error(error.details.data.reason));
jest.spyOn(webCallingService, 'declineCall').mockImplementation(() => { throw error });
await expect(task.decline()).rejects.toThrow(new Error(error.details.data.reason));
expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'decline', CC_FILE);
});
});

0 comments on commit f381fab

Please sign in to comment.