@webex/contact-center 3.12.0-next.122 → 3.12.0-next.124

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@ import TaskManager from '../../../../../src/services/task/TaskManager';
7
7
  import * as contact from '../../../../../src/services/task/contact';
8
8
  import Task from '../../../../../src/services/task/Task';
9
9
  import {TASK_EVENTS} from '../../../../../src/services/task/types';
10
- import {TaskEvent} from '../../../../../src/services/task/state-machine';
10
+ import {TaskEvent, TaskState} from '../../../../../src/services/task/state-machine';
11
11
  import WebRTC from '../../../../../src/services/task/voice/WebRTC';
12
12
  import {Profile} from '../../../../../src/services/config/types';
13
13
  import WebCallingService from '../../../../../src/services/WebCallingService';
@@ -2617,6 +2617,22 @@ describe('TaskManager', () => {
2617
2617
  sendStateMachineEventSpy.mockRestore();
2618
2618
  });
2619
2619
 
2620
+ it('stamps wrapUpRequired on AGENT_WRAPUP when the payload omits it', () => {
2621
+ const payload = {
2622
+ data: {
2623
+ type: CC_EVENTS.AGENT_WRAPUP,
2624
+ interactionId: taskId,
2625
+ },
2626
+ };
2627
+ const task = taskManager.getTask(taskId);
2628
+ webSocketManagerMock.emit('message', JSON.stringify(payload));
2629
+ const stateMachineEvent = task.sendStateMachineEvent.mock.calls.at(-1)?.[0];
2630
+
2631
+ expect(stateMachineEvent?.type).toBe(TaskEvent.TASK_WRAPUP);
2632
+ expect(stateMachineEvent?.taskData.wrapUpRequired).toBe(true);
2633
+ expect(task.data.wrapUpRequired).toBe(true);
2634
+ });
2635
+
2620
2636
  it('should not attempt cleanup twice when AGENT_CONTACT_UNASSIGNED is followed by AGENT_WRAPUP', () => {
2621
2637
  webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
2622
2638
  const task = taskManager.getTask(taskId);
@@ -3716,6 +3732,405 @@ describe('TaskManager', () => {
3716
3732
  });
3717
3733
  });
3718
3734
 
3735
+ const stampWrapUp = (
3736
+ eventType: CC_EVENTS,
3737
+ payload: Record<string, unknown>,
3738
+ agentId = 'test-agent-id',
3739
+ task?: unknown
3740
+ ) => (TaskManager as any).stampWrapUpRequiredForEvent(eventType, payload, agentId, task);
3741
+
3742
+ it('maps conference wrap-up events without restamping wrapUpRequired', () => {
3743
+ const unstamped = {...taskDataMock, wrapUpRequired: false};
3744
+ const conferenceEnded = (TaskManager as any).mapEventToTaskStateMachineEvent(
3745
+ CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED,
3746
+ unstamped,
3747
+ 'test-agent-id'
3748
+ );
3749
+ const participantLeft = (TaskManager as any).mapEventToTaskStateMachineEvent(
3750
+ CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
3751
+ {...unstamped, participantId: 'test-agent-id'},
3752
+ 'test-agent-id'
3753
+ );
3754
+ const transferred = (TaskManager as any).mapEventToTaskStateMachineEvent(
3755
+ CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED,
3756
+ unstamped,
3757
+ 'test-agent-id'
3758
+ );
3759
+
3760
+ expect(conferenceEnded.taskData).toBe(unstamped);
3761
+ expect(participantLeft.taskData.wrapUpRequired).toBe(false);
3762
+ expect(participantLeft.participantId).toBe('test-agent-id');
3763
+ expect(transferred.taskData).toBe(unstamped);
3764
+ });
3765
+
3766
+ it('stamps wrapUpRequired on AGENT_CONSULT_CONFERENCE_ENDED only from explicit signals', () => {
3767
+ const wrapping = stampWrapUp(CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED, {
3768
+ ...taskDataMock,
3769
+ agentsPendingWrapUp: ['test-agent-id'],
3770
+ });
3771
+ const remaining = stampWrapUp(CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED, {
3772
+ ...taskDataMock,
3773
+ owner: 'test-agent-id',
3774
+ isConsulted: false,
3775
+ });
3776
+
3777
+ expect(wrapping.wrapUpRequired).toBe(true);
3778
+ expect(remaining.wrapUpRequired).toBe(false);
3779
+ });
3780
+
3781
+ it('stamps wrapUpRequired on PARTICIPANT_LEFT_CONFERENCE only when this agent left', () => {
3782
+ const selfLeft = stampWrapUp(CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, {
3783
+ ...taskDataMock,
3784
+ participantId: 'test-agent-id',
3785
+ interaction: {
3786
+ ...taskDataMock.interaction,
3787
+ owner: 'test-agent-id',
3788
+ participants: {},
3789
+ },
3790
+ });
3791
+ const otherLeft = stampWrapUp(CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, {
3792
+ ...taskDataMock,
3793
+ participantId: 'other-agent-id',
3794
+ interaction: {
3795
+ ...taskDataMock.interaction,
3796
+ owner: 'test-agent-id',
3797
+ participants: {
3798
+ 'test-agent-id': {id: 'test-agent-id', pType: 'Agent', hasLeft: false},
3799
+ },
3800
+ },
3801
+ });
3802
+
3803
+ expect(selfLeft.wrapUpRequired).toBe(true);
3804
+ expect(otherLeft.wrapUpRequired).toBe(false);
3805
+ });
3806
+
3807
+ it('stamps wrapUpRequired true when this agent left and pending is empty or absent', () => {
3808
+ const emptyPending = stampWrapUp(CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, {
3809
+ ...taskDataMock,
3810
+ participantId: 'test-agent-id',
3811
+ agentsPendingWrapUp: [],
3812
+ interaction: {
3813
+ ...taskDataMock.interaction,
3814
+ participants: {},
3815
+ },
3816
+ });
3817
+ const absentPending = stampWrapUp(CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, {
3818
+ ...taskDataMock,
3819
+ participantId: 'test-agent-id',
3820
+ interaction: {
3821
+ ...taskDataMock.interaction,
3822
+ participants: {},
3823
+ },
3824
+ });
3825
+
3826
+ expect(emptyPending.wrapUpRequired).toBe(true);
3827
+ expect(absentPending.wrapUpRequired).toBe(true);
3828
+ });
3829
+
3830
+ it('does not stamp wrapUpRequired when this agent left but pending excludes self', () => {
3831
+ const leftExcluded = stampWrapUp(CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, {
3832
+ ...taskDataMock,
3833
+ participantId: 'test-agent-id',
3834
+ wrapUpRequired: true,
3835
+ agentsPendingWrapUp: ['other-agent-id'],
3836
+ interaction: {
3837
+ ...taskDataMock.interaction,
3838
+ owner: 'test-agent-id',
3839
+ participants: {},
3840
+ },
3841
+ });
3842
+
3843
+ expect(leftExcluded.wrapUpRequired).toBe(false);
3844
+ });
3845
+
3846
+ it('does not infer self-leave from a partial participant map when another participant is named', () => {
3847
+ const remaining = stampWrapUp(CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, {
3848
+ ...taskDataMock,
3849
+ participantId: 'other-agent-id',
3850
+ agentsPendingWrapUp: [],
3851
+ interaction: {
3852
+ ...taskDataMock.interaction,
3853
+ owner: 'test-agent-id',
3854
+ participants: {
3855
+ 'other-agent-id': {id: 'other-agent-id', pType: 'Agent', hasLeft: true},
3856
+ },
3857
+ },
3858
+ });
3859
+
3860
+ expect(remaining.wrapUpRequired).toBe(false);
3861
+
3862
+ class TimerTask extends Task {
3863
+ public accept() {
3864
+ return Promise.resolve({} as any);
3865
+ }
3866
+ }
3867
+ const remainingTask = new TimerTask(
3868
+ {},
3869
+ {...taskDataMock, wrapUpRequired: false},
3870
+ {channelType: 'voice', isEndTaskEnabled: true, isEndConsultEnabled: true},
3871
+ {
3872
+ wrapUpProps: {
3873
+ autoWrapup: true,
3874
+ autoWrapupInterval: 1000,
3875
+ wrapUpReasonList: [{id: 'code-1', name: 'Default', isDefault: true, isSystem: false}],
3876
+ },
3877
+ },
3878
+ 'test-agent-id'
3879
+ );
3880
+ try {
3881
+ remainingTask.updateTaskData(remaining);
3882
+ expect(remainingTask.autoWrapup).toBeUndefined();
3883
+ } finally {
3884
+ remainingTask.cancelAutoWrapupTimer();
3885
+ }
3886
+ });
3887
+
3888
+ it('does not stamp wrapUpRequired when a consulted non-owner leaves with empty pending', () => {
3889
+ const consulted = stampWrapUp(CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, {
3890
+ ...taskDataMock,
3891
+ participantId: 'test-agent-id',
3892
+ isConsulted: true,
3893
+ agentsPendingWrapUp: [],
3894
+ interaction: {
3895
+ ...taskDataMock.interaction,
3896
+ owner: 'other-agent-id',
3897
+ participants: {},
3898
+ },
3899
+ });
3900
+ const consultedFromTask = stampWrapUp(
3901
+ CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
3902
+ {
3903
+ ...taskDataMock,
3904
+ participantId: 'test-agent-id',
3905
+ agentsPendingWrapUp: [],
3906
+ interaction: {
3907
+ ...taskDataMock.interaction,
3908
+ participants: {},
3909
+ },
3910
+ },
3911
+ 'test-agent-id',
3912
+ {
3913
+ data: {
3914
+ isConsulted: true,
3915
+ interaction: {owner: 'other-agent-id'},
3916
+ },
3917
+ }
3918
+ );
3919
+
3920
+ expect(consulted.wrapUpRequired).toBe(false);
3921
+ expect(consultedFromTask.wrapUpRequired).toBe(false);
3922
+ });
3923
+
3924
+ it('stamps wrapUpRequired when EP-DN omits participantId and previous task included self', () => {
3925
+ const epdn = stampWrapUp(
3926
+ CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
3927
+ {
3928
+ ...taskDataMock,
3929
+ agentsPendingWrapUp: [],
3930
+ interaction: {
3931
+ ...taskDataMock.interaction,
3932
+ owner: 'test-agent-id',
3933
+ participants: {},
3934
+ },
3935
+ },
3936
+ 'test-agent-id',
3937
+ {
3938
+ data: {
3939
+ interaction: {
3940
+ participants: {
3941
+ 'test-agent-id': {id: 'test-agent-id', pType: 'Agent', hasLeft: false},
3942
+ },
3943
+ },
3944
+ },
3945
+ }
3946
+ );
3947
+
3948
+ expect(epdn.wrapUpRequired).toBe(true);
3949
+ });
3950
+
3951
+ it('does not stamp wrapUpRequired on PARTICIPANT_LEFT_CONFERENCE when the actor ignores PARTICIPANT_LEAVE', () => {
3952
+ const selfLeave = {
3953
+ ...taskDataMock,
3954
+ participantId: 'test-agent-id',
3955
+ agentsPendingWrapUp: [],
3956
+ interaction: {
3957
+ ...taskDataMock.interaction,
3958
+ owner: 'test-agent-id',
3959
+ participants: {},
3960
+ },
3961
+ };
3962
+ const snapshotTask = (state: TaskState) => ({
3963
+ stateMachineService: {getSnapshot: () => ({value: state})},
3964
+ });
3965
+
3966
+ const holdInitiating = stampWrapUp(
3967
+ CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
3968
+ selfLeave,
3969
+ 'test-agent-id',
3970
+ snapshotTask(TaskState.HOLD_INITIATING)
3971
+ );
3972
+ const connected = stampWrapUp(
3973
+ CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
3974
+ selfLeave,
3975
+ 'test-agent-id',
3976
+ snapshotTask(TaskState.CONNECTED)
3977
+ );
3978
+ const confInitiating = stampWrapUp(
3979
+ CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
3980
+ selfLeave,
3981
+ 'test-agent-id',
3982
+ snapshotTask(TaskState.CONF_INITIATING)
3983
+ );
3984
+ const conferencing = stampWrapUp(
3985
+ CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
3986
+ selfLeave,
3987
+ 'test-agent-id',
3988
+ snapshotTask(TaskState.CONFERENCING)
3989
+ );
3990
+
3991
+ expect(holdInitiating).toBe(selfLeave);
3992
+ expect(holdInitiating.wrapUpRequired).toBeUndefined();
3993
+ expect(connected.wrapUpRequired).toBeUndefined();
3994
+ expect(confInitiating.wrapUpRequired).toBeUndefined();
3995
+ expect(conferencing.wrapUpRequired).toBe(true);
3996
+
3997
+ class TimerTask extends Task {
3998
+ public accept() {
3999
+ return Promise.resolve({} as any);
4000
+ }
4001
+ }
4002
+ const holdTask = new TimerTask(
4003
+ {},
4004
+ {...taskDataMock, wrapUpRequired: false},
4005
+ {channelType: 'voice', isEndTaskEnabled: true, isEndConsultEnabled: true},
4006
+ {
4007
+ wrapUpProps: {
4008
+ autoWrapup: true,
4009
+ autoWrapupInterval: 1000,
4010
+ wrapUpReasonList: [{id: 'code-1', name: 'Default', isDefault: true, isSystem: false}],
4011
+ },
4012
+ },
4013
+ 'test-agent-id'
4014
+ );
4015
+ try {
4016
+ holdTask.updateTaskData(holdInitiating);
4017
+ expect(holdTask.autoWrapup).toBeUndefined();
4018
+ } finally {
4019
+ holdTask.cancelAutoWrapupTimer();
4020
+ }
4021
+ });
4022
+
4023
+ it('stamps wrapUpRequired on AGENT_CONFERENCE_TRANSFERRED for the initiator only', () => {
4024
+ const initiator = stampWrapUp(CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED, {
4025
+ ...taskDataMock,
4026
+ consultingAgentId: 'test-agent-id',
4027
+ });
4028
+ const observer = stampWrapUp(CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED, {
4029
+ ...taskDataMock,
4030
+ consultingAgentId: 'other-agent-id',
4031
+ owner: 'test-agent-id',
4032
+ });
4033
+
4034
+ expect(initiator.wrapUpRequired).toBe(true);
4035
+ expect(observer.wrapUpRequired).toBe(false);
4036
+ });
4037
+
4038
+ it('does not let transfer initiator override a nonempty pending list that excludes self', () => {
4039
+ const consultingInitiator = stampWrapUp(CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED, {
4040
+ ...taskDataMock,
4041
+ wrapUpRequired: true,
4042
+ agentsPendingWrapUp: ['other-agent-id'],
4043
+ consultingAgentId: 'test-agent-id',
4044
+ });
4045
+ const flaggedInitiator = stampWrapUp(
4046
+ CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED,
4047
+ {
4048
+ ...taskDataMock,
4049
+ wrapUpRequired: true,
4050
+ agentsPendingWrapUp: ['other-agent-id'],
4051
+ },
4052
+ 'test-agent-id',
4053
+ {
4054
+ stateMachineService: {
4055
+ getSnapshot: () => ({context: {transferConferenceRequested: true}}),
4056
+ },
4057
+ }
4058
+ );
4059
+
4060
+ expect(consultingInitiator.wrapUpRequired).toBe(false);
4061
+ expect(flaggedInitiator.wrapUpRequired).toBe(false);
4062
+ });
4063
+
4064
+ it('honors transferConferenceRequested when pending is empty and consultingAgentId is omitted', () => {
4065
+ const flagged = stampWrapUp(
4066
+ CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED,
4067
+ {...taskDataMock},
4068
+ 'test-agent-id',
4069
+ {
4070
+ stateMachineService: {
4071
+ getSnapshot: () => ({context: {transferConferenceRequested: true}}),
4072
+ },
4073
+ }
4074
+ );
4075
+ const unflagged = stampWrapUp(CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED, {...taskDataMock});
4076
+
4077
+ expect(flagged.wrapUpRequired).toBe(true);
4078
+ expect(unflagged.wrapUpRequired).toBe(false);
4079
+ });
4080
+
4081
+ it('does not stamp wrapUpRequired when a nonempty pending list excludes this agent', () => {
4082
+ const conflictingPayload = {
4083
+ ...taskDataMock,
4084
+ wrapUpRequired: true,
4085
+ agentsPendingWrapUp: ['other-agent-id'],
4086
+ };
4087
+
4088
+ const conferenceEnded = stampWrapUp(
4089
+ CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED,
4090
+ conflictingPayload
4091
+ );
4092
+ const conferenceTransferred = stampWrapUp(CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED, {
4093
+ ...conflictingPayload,
4094
+ consultingAgentId: 'other-agent-id',
4095
+ });
4096
+ const remainingOnLeave = stampWrapUp(CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, {
4097
+ ...conflictingPayload,
4098
+ participantId: 'other-agent-id',
4099
+ interaction: {
4100
+ ...taskDataMock.interaction,
4101
+ owner: 'test-agent-id',
4102
+ participants: {
4103
+ 'test-agent-id': {id: 'test-agent-id', pType: 'Agent', hasLeft: false},
4104
+ },
4105
+ },
4106
+ });
4107
+
4108
+ expect(conferenceEnded.wrapUpRequired).toBe(false);
4109
+ expect(conferenceTransferred.wrapUpRequired).toBe(false);
4110
+ expect(remainingOnLeave.wrapUpRequired).toBe(false);
4111
+ });
4112
+
4113
+ it('stamps wrapUpRequired when pending includes this agent or pending is empty', () => {
4114
+ const pendingIncludesSelf = stampWrapUp(CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED, {
4115
+ ...taskDataMock,
4116
+ wrapUpRequired: true,
4117
+ agentsPendingWrapUp: ['test-agent-id'],
4118
+ });
4119
+ const emptyPendingFallback = stampWrapUp(CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED, {
4120
+ ...taskDataMock,
4121
+ wrapUpRequired: true,
4122
+ agentsPendingWrapUp: [],
4123
+ });
4124
+ const missingPendingFallback = stampWrapUp(CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED, {
4125
+ ...taskDataMock,
4126
+ wrapUpRequired: true,
4127
+ });
4128
+
4129
+ expect(pendingIncludesSelf.wrapUpRequired).toBe(true);
4130
+ expect(emptyPendingFallback.wrapUpRequired).toBe(true);
4131
+ expect(missingPendingFallback.wrapUpRequired).toBe(true);
4132
+ });
4133
+
3719
4134
  it('sends mapped events to the task state machine service', () => {
3720
4135
  const payload = {...taskDataMock, type: CC_EVENTS.AGENT_CONTACT_ASSIGNED};
3721
4136
  const task = taskManager.getTask(taskId);