diff --git a/docs/samples/contact-center/app.js b/docs/samples/contact-center/app.js index af640440959..ea49291f072 100644 --- a/docs/samples/contact-center/app.js +++ b/docs/samples/contact-center/app.js @@ -2127,6 +2127,7 @@ function renderTaskList(taskList) { const isNew = isIncomingTask(task, agentId); const isTelephony = task.data.interaction.mediaType === 'telephony'; const isBrowserPhone = agentDeviceType === 'BROWSER'; + const isAutoAnswering = task.data.isAutoAnswering || false; // Determine which buttons to show const showAcceptButton = isNew && (isBrowserPhone || !isTelephony); @@ -2136,8 +2137,8 @@ function renderTaskList(taskList) { taskElement.innerHTML = `

${callerDisplay}

- ${showAcceptButton ? `` : ''} - ${showDeclineButton ? `` : ''} + ${showAcceptButton ? `` : ''} + ${showDeclineButton ? `` : ''}

`; @@ -2208,24 +2209,40 @@ function renderTaskList(taskList) { function enableAnswerDeclineButtons(task) { const callerDisplay = task.data.interaction?.callAssociatedDetails?.ani; const isNew = isIncomingTask(task, agentId); - const chatAndSocial = ['chat', 'social']; + const isAutoAnswering = task.data.isAutoAnswering || false; + const chatAndSocial = ['chat', 'social']; + if (task.data.interaction.mediaType === 'telephony') { if (agentDeviceType === 'BROWSER') { - answerElm.disabled = !isNew; - declineElm.disabled = !isNew; + // Disable buttons if auto-answering or not new + answerElm.disabled = !isNew || isAutoAnswering; + declineElm.disabled = !isNew || isAutoAnswering; incomingDetailsElm.innerText = `Call from ${callerDisplay}`; + + // Log auto-answer status for debugging + if (isAutoAnswering) { + console.log('✅ Auto-answer in progress for task:', task.data.interactionId); + } } else { incomingDetailsElm.innerText = `Call from ${callerDisplay}...please answer on the endpoint where the agent's extension is registered`; } } else if (chatAndSocial.includes(task.data.interaction.mediaType)) { - answerElm.disabled = !isNew; + answerElm.disabled = !isNew || isAutoAnswering; declineElm.disabled = true; incomingDetailsElm.innerText = `Chat from ${callerDisplay}`; + + if (isAutoAnswering) { + console.log('✅ Auto-answer in progress for task:', task.data.interactionId); + } } else if (task.data.interaction.mediaType === 'email') { - answerElm.disabled = !isNew; + answerElm.disabled = !isNew || isAutoAnswering; declineElm.disabled = true; incomingDetailsElm.innerText = `Email from ${callerDisplay}`; + + if (isAutoAnswering) { + console.log('✅ Auto-answer in progress for task:', task.data.interactionId); + } } } diff --git a/packages/@webex/contact-center/src/cc.ts b/packages/@webex/contact-center/src/cc.ts index 8cbe43fb0aa..889f8f77d8f 100644 --- a/packages/@webex/contact-center/src/cc.ts +++ b/packages/@webex/contact-center/src/cc.ts @@ -709,6 +709,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter // TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere this.taskManager.setWrapupData(this.agentConfig.wrapUpData); this.taskManager.setAgentId(this.agentConfig.agentId); + this.taskManager.setWebRtcEnabled(this.agentConfig.webRtcEnabled); if ( this.agentConfig.webRtcEnabled && diff --git a/packages/@webex/contact-center/src/services/task/TaskManager.ts b/packages/@webex/contact-center/src/services/task/TaskManager.ts index 35db7ce8e56..060568ec8b9 100644 --- a/packages/@webex/contact-center/src/services/task/TaskManager.ts +++ b/packages/@webex/contact-center/src/services/task/TaskManager.ts @@ -18,6 +18,7 @@ import { isParticipantInMainInteraction, isPrimary, isSecondaryEpDnAgent, + shouldAutoAnswerTask, } from './TaskUtils'; /** @internal */ @@ -36,6 +37,7 @@ export default class TaskManager extends EventEmitter { private static taskManager; private wrapupData: WrapupData; private agentId: string; + private webRtcEnabled: boolean; /** * @param contact - Routing Contact layer. Talks to AQMReq layer to convert events to promises * @param webCallingService - Webrtc Service Layer @@ -73,6 +75,10 @@ export default class TaskManager extends EventEmitter { return this.agentId; } + public setWebRtcEnabled(webRtcEnabled: boolean) { + this.webRtcEnabled = webRtcEnabled; + } + private handleIncomingWebCall = (call: ICall) => { const currentTask = Object.values(this.taskCollection).find( (task) => task.data.interaction.mediaType === 'telephony' @@ -130,6 +136,14 @@ export default class TaskManager extends EventEmitter { interactionId: payload.data.interactionId, }); + // Check if auto-answer should happen for this task + const shouldAutoAnswer = shouldAutoAnswerTask( + payload.data, + this.agentId, + this.webCallingService.loginOption, + this.webRtcEnabled + ); + task = new Task( this.contact, this.webCallingService, @@ -138,6 +152,7 @@ export default class TaskManager extends EventEmitter { wrapUpRequired: payload.data.interaction?.participants?.[this.agentId]?.isWrapUp || false, isConferenceInProgress: getIsConferenceInProgress(payload.data), + isAutoAnswering: shouldAutoAnswer, // Set flag before emitting }, this.wrapupData, this.agentId @@ -169,13 +184,22 @@ export default class TaskManager extends EventEmitter { } break; - case CC_EVENTS.AGENT_CONTACT_RESERVED: + case CC_EVENTS.AGENT_CONTACT_RESERVED: { + // Check if auto-answer should happen for this task + const shouldAutoAnswerReserved = shouldAutoAnswerTask( + payload.data, + this.agentId, + this.webCallingService.loginOption, + this.webRtcEnabled + ); + task = new Task( this.contact, this.webCallingService, { ...payload.data, isConsulted: false, + isAutoAnswering: shouldAutoAnswerReserved, // Set flag before emitting }, this.wrapupData, this.agentId @@ -190,6 +214,7 @@ export default class TaskManager extends EventEmitter { this.emit(TASK_EVENTS.TASK_INCOMING, task); } break; + } case CC_EVENTS.AGENT_OFFER_CONTACT: // We don't have to emit any event here since this will be result of promise. task = this.updateTaskData(task, payload.data); @@ -199,6 +224,9 @@ export default class TaskManager extends EventEmitter { interactionId: payload.data?.interactionId, }); this.emit(TASK_EVENTS.TASK_OFFER_CONTACT, task); + + // Handle auto-answer for offer contact + this.handleAutoAnswer(task); break; case CC_EVENTS.AGENT_OUTBOUND_FAILED: // We don't have to emit any event here since this will be result of promise. @@ -301,6 +329,9 @@ export default class TaskManager extends EventEmitter { isConsulted: true, // This ensures that the task is marked as us being requested for a consult }); task.emit(TASK_EVENTS.TASK_OFFER_CONSULT, task); + + // Handle auto-answer for consult offer + this.handleAutoAnswer(task); break; case CC_EVENTS.AGENT_CONSULTING: // Received when agent is in an active consult state @@ -538,6 +569,47 @@ export default class TaskManager extends EventEmitter { } } + /** + * Handles auto-answer logic for incoming tasks + * Automatically accepts tasks when isAutoAnswering flag is set + * The flag is set during task creation based on: + * 1. WebRTC calls with auto-answer enabled in agent profile + * 2. Agent-initiated WebRTC outdial calls + * 3. Agent-initiated digital outbound (Email/SMS) without previous transfers + * + * @param task - The task to auto-answer + * @private + */ + private async handleAutoAnswer(task: ITask): Promise { + if (!task || !task.data || !task.data.isAutoAnswering) { + return; + } + + LoggerProxy.info(`Auto-answering task`, { + module: TASK_MANAGER_FILE, + method: 'handleAutoAnswer', + interactionId: task.data.interactionId, + }); + + try { + await task.accept(); + LoggerProxy.info(`Task auto-answered successfully`, { + module: TASK_MANAGER_FILE, + method: 'handleAutoAnswer', + interactionId: task.data.interactionId, + }); + } catch (error) { + // Reset isAutoAnswering flag on failure + task.updateTaskData({...task.data, isAutoAnswering: false}); + LoggerProxy.error(`Failed to auto-answer task`, { + module: TASK_MANAGER_FILE, + method: 'handleAutoAnswer', + interactionId: task.data.interactionId, + error, + }); + } + } + /** * Handles cleanup of task resources including Desktop/WebRTC call cleanup and task removal * @param task - The task to clean up diff --git a/packages/@webex/contact-center/src/services/task/TaskUtils.ts b/packages/@webex/contact-center/src/services/task/TaskUtils.ts index e59fd5b488e..f1c49d38264 100644 --- a/packages/@webex/contact-center/src/services/task/TaskUtils.ts +++ b/packages/@webex/contact-center/src/services/task/TaskUtils.ts @@ -1,5 +1,7 @@ /* eslint-disable import/prefer-default-export */ -import {Interaction, ITask, TaskData} from './types'; +import {Interaction, ITask, TaskData, MEDIA_CHANNEL} from './types'; +import {OUTDIAL_DIRECTION, OUTDIAL_MEDIA_TYPE, OUTBOUND_TYPE} from '../../constants'; +import {LoginOption} from '../../types'; /** * Determines if the given agent is the primary agent (owner) of the task @@ -111,3 +113,109 @@ export const isSecondaryEpDnAgent = (interaction: Interaction): boolean => { return interaction.mediaType === 'telephony' && isSecondaryAgent(interaction); }; + +/** + * Checks if auto-answer is enabled for the agent participant + * @param interaction - The interaction object + * @param agentId - Current agent ID + * @returns true if auto-answer is enabled, false otherwise + */ +export const isAutoAnswerEnabled = (interaction: Interaction, agentId: string): boolean => { + return interaction.participants?.[agentId]?.autoAnswerEnabled === true; +}; + +/** + * Checks if the interaction is a WebRTC call eligible for auto-answer + * @param interaction - The interaction object + * @param loginOption - The agent's login option (BROWSER, AGENT_DN, etc.) + * @param webRtcEnabled - Whether WebRTC is enabled for the agent + * @returns true if this is a WebRTC call, false otherwise + */ +export const isWebRTCCall = ( + interaction: Interaction, + loginOption: string, + webRtcEnabled: boolean +): boolean => { + return ( + webRtcEnabled && + loginOption === LoginOption.BROWSER && + interaction.mediaType === OUTDIAL_MEDIA_TYPE + ); +}; + +/** + * Checks if the interaction is a digital outbound (Email/SMS) + * @param interaction - The interaction object + * @returns true if this is a digital outbound, false otherwise + */ +export const isDigitalOutbound = (interaction: Interaction): boolean => { + return ( + interaction.contactDirection?.type === OUTDIAL_DIRECTION && + interaction.outboundType === OUTBOUND_TYPE && + (interaction.mediaChannel === MEDIA_CHANNEL.EMAIL || + interaction.mediaChannel === MEDIA_CHANNEL.SMS) + ); +}; + +/** + * Checks if the outdial was initiated by the current agent + * @param interaction - The interaction object + * @param agentId - Current agent ID + * @returns true if agent initiated the outdial, false otherwise + */ +export const isAgentInitiatedOutdial = (interaction: Interaction, agentId: string): boolean => { + return ( + interaction.contactDirection?.type === OUTDIAL_DIRECTION && + interaction.outboundType === OUTBOUND_TYPE && + interaction.callProcessingDetails?.outdialAgentId === agentId && + interaction.owner === agentId && + !interaction.callProcessingDetails?.BLIND_TRANSFER_IN_PROGRESS + ); +}; + +/** + * Determines if a task should be auto-answered based on interaction data + * Auto-answer logic handles: + * 1. WebRTC calls with auto-answer enabled in agent profile + * 2. Agent-initiated WebRTC outdial calls + * 3. Agent-initiated digital outbound (Email/SMS) without previous transfers + * + * @param taskData - The task data + * @param agentId - Current agent ID + * @param loginOption - Agent's login option + * @param webRtcEnabled - Whether WebRTC is enabled for the agent + * @returns true if task should be auto-answered, false otherwise + */ +export const shouldAutoAnswerTask = ( + taskData: TaskData, + agentId: string, + loginOption: string, + webRtcEnabled: boolean +): boolean => { + const {interaction} = taskData; + + if (!interaction || !agentId) { + return false; + } + + // Check if auto-answer is enabled for this agent + const autoAnswerEnabled = isAutoAnswerEnabled(interaction, agentId); + + // Check if this is an agent-initiated outdial + const agentInitiatedOutdial = isAgentInitiatedOutdial(interaction, agentId); + + // WebRTC telephony calls + if (isWebRTCCall(interaction, loginOption, webRtcEnabled)) { + return autoAnswerEnabled || agentInitiatedOutdial; + } + + // Digital outbound (Email/SMS) + if (isDigitalOutbound(interaction) && agentInitiatedOutdial) { + // Don't auto-answer if task has been transferred (has previous vteams) + const hasPreviousVteams = interaction.previousVTeams && interaction.previousVTeams.length > 0; + + return !hasPreviousVteams; + } + + return false; +}; diff --git a/packages/@webex/contact-center/src/services/task/constants.ts b/packages/@webex/contact-center/src/services/task/constants.ts index 53a79177bba..803cc3fc732 100644 --- a/packages/@webex/contact-center/src/services/task/constants.ts +++ b/packages/@webex/contact-center/src/services/task/constants.ts @@ -34,6 +34,8 @@ export const PRESERVED_TASK_DATA_FIELDS = { WRAP_UP_REQUIRED: 'wrapUpRequired', /** Indicates if a conference is currently in progress (2+ active agents) */ IS_CONFERENCE_IN_PROGRESS: 'isConferenceInProgress', + /** Indicates if auto-answer is in progress for this task */ + IS_AUTO_ANSWERING: 'isAutoAnswering', }; /** diff --git a/packages/@webex/contact-center/src/services/task/types.ts b/packages/@webex/contact-center/src/services/task/types.ts index 2f461ab20f9..56030e3d756 100644 --- a/packages/@webex/contact-center/src/services/task/types.ts +++ b/packages/@webex/contact-center/src/services/task/types.ts @@ -660,6 +660,8 @@ export type Interaction = { BLIND_TRANSFER_IN_PROGRESS?: boolean; /** Desktop view configuration for Flow Control */ fcDesktopView?: string; + /** Agent ID who initiated the outdial call */ + outdialAgentId?: string; }; /** Main interaction identifier for related interactions */ mainInteractionId?: string; @@ -785,6 +787,8 @@ export type TaskData = { reservedAgentChannelId?: string; /** Indicates if wrap-up is required for this task */ wrapUpRequired?: boolean; + /** Indicates if auto-answer is in progress for this task */ + isAutoAnswering?: boolean; }; /** diff --git a/packages/@webex/contact-center/test/unit/spec/cc.ts b/packages/@webex/contact-center/test/unit/spec/cc.ts index 953c848bc92..590346bf585 100644 --- a/packages/@webex/contact-center/test/unit/spec/cc.ts +++ b/packages/@webex/contact-center/test/unit/spec/cc.ts @@ -141,6 +141,7 @@ describe('webex.cc', () => { task: undefined, setWrapupData: jest.fn(), setAgentId: jest.fn(), + setWebRtcEnabled: jest.fn(), registerIncomingCallEvent: jest.fn(), registerTaskListeners: jest.fn(), getTask: jest.fn(), diff --git a/packages/@webex/contact-center/test/unit/spec/services/task/TaskManager.ts b/packages/@webex/contact-center/test/unit/spec/services/task/TaskManager.ts index 08cd5c39f47..b1e32e66700 100644 --- a/packages/@webex/contact-center/test/unit/spec/services/task/TaskManager.ts +++ b/packages/@webex/contact-center/test/unit/spec/services/task/TaskManager.ts @@ -2105,5 +2105,6 @@ describe('TaskManager', () => { ); }); }); + }); diff --git a/packages/@webex/contact-center/test/unit/spec/services/task/TaskUtils.ts b/packages/@webex/contact-center/test/unit/spec/services/task/TaskUtils.ts index aebfc02a988..b290f28bd68 100644 --- a/packages/@webex/contact-center/test/unit/spec/services/task/TaskUtils.ts +++ b/packages/@webex/contact-center/test/unit/spec/services/task/TaskUtils.ts @@ -1,8 +1,16 @@ -import { checkParticipantNotInInteraction, +import { + checkParticipantNotInInteraction, getIsConferenceInProgress, isParticipantInMainInteraction, - isPrimary,} from '../../../../../src/services/task/TaskUtils'; -import {ITask} from '../../../../../src/services/task/types'; + isPrimary, + isAutoAnswerEnabled, + isWebRTCCall, + isDigitalOutbound, + isAgentInitiatedOutdial, + shouldAutoAnswerTask, +} from '../../../../../src/services/task/TaskUtils'; +import {ITask, Interaction, TaskData} from '../../../../../src/services/task/types'; +import {LoginOption} from '../../../../../src/types'; describe('TaskUtils', () => { let mockTask: ITask; @@ -128,4 +136,298 @@ describe('TaskUtils', () => { expect(getIsConferenceInProgress(mockTask.data)).toBe(false); }); }); + + describe('Auto-Answer Helper Functions', () => { + let mockInteraction: Interaction; + + beforeEach(() => { + mockInteraction = { + interactionId: 'interaction-123', + mediaType: 'telephony', + mediaChannel: 'telephony', + participants: { + [mockAgentId]: { + id: mockAgentId, + pType: 'Agent', + autoAnswerEnabled: false, + hasJoined: false, + hasLeft: false, + }, + }, + owner: mockAgentId, + contactDirection: {type: 'INBOUND'}, + outboundType: null, + callProcessingDetails: { + QMgrName: 'aqm', + taskToBeSelfServiced: 'false', + ani: '+1234567890', + displayAni: '+1234567890', + dnis: '+0987654321', + tenantId: 'tenant-123', + QueueId: 'queue-123', + vteamId: 'vteam-123', + customerName: 'Test Customer', + virtualTeamName: 'Test Team', + ronaTimeout: '30', + category: 'Support', + reason: 'General', + sourceNumber: '+1234567890', + sourcePage: 'web', + appUser: 'test-app', + customerNumber: '+1234567890', + reasonCode: '100', + IvrPath: '/ivr/path', + pathId: 'path-123', + fromAddress: 'customer@example.com', + }, + previousVTeams: [], + state: 'new', + currentVTeam: 'vteam-123', + isFcManaged: false, + isTerminated: false, + orgId: 'org-123', + createdTimestamp: Date.now(), + media: {}, + } as any; + }); + + describe('isAutoAnswerEnabled', () => { + it('should return true when autoAnswerEnabled is true', () => { + mockInteraction.participants[mockAgentId].autoAnswerEnabled = true; + expect(isAutoAnswerEnabled(mockInteraction, mockAgentId)).toBe(true); + }); + + it('should return false when autoAnswerEnabled is false', () => { + mockInteraction.participants[mockAgentId].autoAnswerEnabled = false; + expect(isAutoAnswerEnabled(mockInteraction, mockAgentId)).toBe(false); + }); + + it('should return false when autoAnswerEnabled is not set', () => { + delete mockInteraction.participants[mockAgentId].autoAnswerEnabled; + expect(isAutoAnswerEnabled(mockInteraction, mockAgentId)).toBe(false); + }); + + it('should return false when participant does not exist', () => { + expect(isAutoAnswerEnabled(mockInteraction, 'non-existent-agent')).toBe(false); + }); + }); + + describe('isWebRTCCall', () => { + it('should return true for BROWSER login with telephony media type and webRTC enabled', () => { + expect(isWebRTCCall(mockInteraction, LoginOption.BROWSER, true)).toBe(true); + }); + + it('should return false when webRTC is disabled', () => { + expect(isWebRTCCall(mockInteraction, LoginOption.BROWSER, false)).toBe(false); + }); + + it('should return false for AGENT_DN login', () => { + expect(isWebRTCCall(mockInteraction, LoginOption.AGENT_DN, true)).toBe(false); + }); + + it('should return false for EXTENSION login', () => { + expect(isWebRTCCall(mockInteraction, LoginOption.EXTENSION, true)).toBe(false); + }); + + it('should return false for BROWSER login with non-telephony media type', () => { + mockInteraction.mediaType = 'email'; + expect(isWebRTCCall(mockInteraction, LoginOption.BROWSER, true)).toBe(false); + }); + }); + + describe('isDigitalOutbound', () => { + it('should return true for email outdial', () => { + mockInteraction.contactDirection = {type: 'OUTBOUND'}; + mockInteraction.outboundType = 'OUTDIAL'; + mockInteraction.mediaChannel = 'email'; + expect(isDigitalOutbound(mockInteraction)).toBe(true); + }); + + it('should return true for SMS outdial', () => { + mockInteraction.contactDirection = {type: 'OUTBOUND'}; + mockInteraction.outboundType = 'OUTDIAL'; + mockInteraction.mediaChannel = 'sms'; + expect(isDigitalOutbound(mockInteraction)).toBe(true); + }); + + it('should return false for telephony outdial', () => { + mockInteraction.contactDirection = {type: 'OUTBOUND'}; + mockInteraction.outboundType = 'OUTDIAL'; + mockInteraction.mediaChannel = 'telephony'; + expect(isDigitalOutbound(mockInteraction)).toBe(false); + }); + + it('should return false for inbound email', () => { + mockInteraction.contactDirection = {type: 'INBOUND'}; + mockInteraction.mediaChannel = 'email'; + expect(isDigitalOutbound(mockInteraction)).toBe(false); + }); + + it('should return false when outboundType is not OUTDIAL', () => { + mockInteraction.contactDirection = {type: 'OUTBOUND'}; + mockInteraction.outboundType = 'CALLBACK'; + mockInteraction.mediaChannel = 'email'; + expect(isDigitalOutbound(mockInteraction)).toBe(false); + }); + }); + + describe('isAgentInitiatedOutdial', () => { + beforeEach(() => { + mockInteraction.contactDirection = {type: 'OUTBOUND'}; + mockInteraction.outboundType = 'OUTDIAL'; + mockInteraction.owner = mockAgentId; + mockInteraction.callProcessingDetails.outdialAgentId = mockAgentId; + mockInteraction.callProcessingDetails.BLIND_TRANSFER_IN_PROGRESS = false; + }); + + it('should return true for agent-initiated outdial', () => { + expect(isAgentInitiatedOutdial(mockInteraction, mockAgentId)).toBe(true); + }); + + it('should return false when not outbound', () => { + mockInteraction.contactDirection = {type: 'INBOUND'}; + expect(isAgentInitiatedOutdial(mockInteraction, mockAgentId)).toBe(false); + }); + + it('should return false when not OUTDIAL type', () => { + mockInteraction.outboundType = 'CALLBACK'; + expect(isAgentInitiatedOutdial(mockInteraction, mockAgentId)).toBe(false); + }); + + it('should return false when outdialAgentId does not match', () => { + mockInteraction.callProcessingDetails.outdialAgentId = mockOtherAgentId; + expect(isAgentInitiatedOutdial(mockInteraction, mockAgentId)).toBe(false); + }); + + it('should return false when owner does not match', () => { + mockInteraction.owner = mockOtherAgentId; + expect(isAgentInitiatedOutdial(mockInteraction, mockAgentId)).toBe(false); + }); + + it('should return false when blind transfer is in progress', () => { + mockInteraction.callProcessingDetails.BLIND_TRANSFER_IN_PROGRESS = true; + expect(isAgentInitiatedOutdial(mockInteraction, mockAgentId)).toBe(false); + }); + }); + + describe('shouldAutoAnswerTask', () => { + let mockTaskData: TaskData; + + beforeEach(() => { + mockTaskData = { + interactionId: 'interaction-123', + agentId: mockAgentId, + interaction: mockInteraction, + } as any; + }); + + describe('WebRTC scenarios', () => { + beforeEach(() => { + mockInteraction.mediaType = 'telephony'; + }); + + it('should return true when auto-answer is enabled for WebRTC call', () => { + mockInteraction.participants[mockAgentId].autoAnswerEnabled = true; + expect(shouldAutoAnswerTask(mockTaskData, mockAgentId, LoginOption.BROWSER, true)).toBe( + true + ); + }); + + it('should return true for agent-initiated WebRTC outdial', () => { + mockInteraction.contactDirection = {type: 'OUTBOUND'}; + mockInteraction.outboundType = 'OUTDIAL'; + mockInteraction.owner = mockAgentId; + mockInteraction.callProcessingDetails.outdialAgentId = mockAgentId; + mockInteraction.callProcessingDetails.BLIND_TRANSFER_IN_PROGRESS = false; + expect(shouldAutoAnswerTask(mockTaskData, mockAgentId, LoginOption.BROWSER, true)).toBe( + true + ); + }); + + it('should return false for WebRTC call without auto-answer or outdial', () => { + mockInteraction.participants[mockAgentId].autoAnswerEnabled = false; + expect(shouldAutoAnswerTask(mockTaskData, mockAgentId, LoginOption.BROWSER, true)).toBe( + false + ); + }); + + it('should return false when webRTC is disabled', () => { + mockInteraction.participants[mockAgentId].autoAnswerEnabled = true; + expect(shouldAutoAnswerTask(mockTaskData, mockAgentId, LoginOption.BROWSER, false)).toBe( + false + ); + }); + + it('should return false for non-BROWSER login', () => { + mockInteraction.participants[mockAgentId].autoAnswerEnabled = true; + expect(shouldAutoAnswerTask(mockTaskData, mockAgentId, LoginOption.AGENT_DN, true)).toBe( + false + ); + }); + }); + + describe('Digital outbound scenarios', () => { + beforeEach(() => { + mockInteraction.contactDirection = {type: 'OUTBOUND'}; + mockInteraction.outboundType = 'OUTDIAL'; + mockInteraction.owner = mockAgentId; + mockInteraction.callProcessingDetails.outdialAgentId = mockAgentId; + mockInteraction.previousVTeams = []; + }); + + it('should return true for agent-initiated email outdial', () => { + mockInteraction.mediaType = 'email'; + mockInteraction.mediaChannel = 'email'; + expect(shouldAutoAnswerTask(mockTaskData, mockAgentId, LoginOption.BROWSER, true)).toBe( + true + ); + }); + + it('should return true for agent-initiated SMS outdial', () => { + mockInteraction.mediaType = 'sms'; + mockInteraction.mediaChannel = 'sms'; + expect(shouldAutoAnswerTask(mockTaskData, mockAgentId, LoginOption.BROWSER, true)).toBe( + true + ); + }); + + it('should return false when digital outbound has previous vteams', () => { + mockInteraction.mediaType = 'email'; + mockInteraction.mediaChannel = 'email'; + mockInteraction.previousVTeams = ['vteam-1']; + expect(shouldAutoAnswerTask(mockTaskData, mockAgentId, LoginOption.BROWSER, true)).toBe( + false + ); + }); + + it('should return false when digital outbound is not agent-initiated', () => { + mockInteraction.mediaType = 'email'; + mockInteraction.mediaChannel = 'email'; + mockInteraction.owner = mockOtherAgentId; + expect(shouldAutoAnswerTask(mockTaskData, mockAgentId, LoginOption.BROWSER, true)).toBe( + false + ); + }); + }); + + describe('Edge cases', () => { + it('should return false when interaction is null', () => { + mockTaskData.interaction = null as any; + expect(shouldAutoAnswerTask(mockTaskData, mockAgentId, LoginOption.BROWSER, true)).toBe( + false + ); + }); + + it('should return false when agentId is null', () => { + expect(shouldAutoAnswerTask(mockTaskData, null as any, LoginOption.BROWSER, true)).toBe( + false + ); + }); + + it('should return false when agentId is empty string', () => { + expect(shouldAutoAnswerTask(mockTaskData, '', LoginOption.BROWSER, true)).toBe(false); + }); + }); + }); + }); });