@webex/contact-center 3.12.0-next.129 → 3.12.0-next.130

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.
@@ -778,6 +778,7 @@ export default class TaskManager extends EventEmitter {
778
778
  const interactionId = message.data.interactionId;
779
779
  const reservationInteractionId = message.data.reservationInteractionId;
780
780
  let task = this.taskCollection[interactionId];
781
+ let ownerChangeResolvedByRelatedInteraction = false;
781
782
 
782
783
  // When a campaign preview contact is accepted, the assigned event may arrive
783
784
  // with a new interactionId while the task is stored under the original
@@ -792,7 +793,66 @@ export default class TaskManager extends EventEmitter {
792
793
  }
793
794
 
794
795
  if (!task && eventType === CC_EVENTS.CONTACT_OWNER_CHANGED) {
795
- task = this.findTaskForContactOwnerChange(message.data);
796
+ const incomingShowsCurrentAgentPromotion = Boolean(
797
+ this.agentId &&
798
+ message.data.interaction?.owner === this.agentId &&
799
+ TaskManager.isParticipantActiveOnMainInteraction(message.data, this.agentId)
800
+ );
801
+
802
+ task = this.findUniqueTaskByRelatedInteraction(
803
+ message.data,
804
+ (candidate) => {
805
+ const currentAgentId = this.agentId || candidate.data?.agentId;
806
+
807
+ return Boolean(
808
+ currentAgentId &&
809
+ (TaskManager.isParticipantActiveOnMainInteraction(candidate.data, currentAgentId) ||
810
+ incomingShowsCurrentAgentPromotion)
811
+ );
812
+ },
813
+ {includeCollectionKeys: true, includeMainCallMediaKeys: true}
814
+ );
815
+ ownerChangeResolvedByRelatedInteraction = Boolean(task);
816
+ }
817
+
818
+ let ownerChangeMainInteractionId: string | undefined;
819
+ if (task && eventType === CC_EVENTS.CONTACT_OWNER_CHANGED) {
820
+ const taskMainInteractionId = task.data?.interaction?.mainInteractionId;
821
+ const payloadMainInteractionId = message.data.interaction?.mainInteractionId;
822
+ const hasConflictingMainInteractionIds = Boolean(
823
+ taskMainInteractionId &&
824
+ payloadMainInteractionId &&
825
+ taskMainInteractionId !== payloadMainInteractionId
826
+ );
827
+ // A promoted child task can retain stale embedded IDs. Prefer the
828
+ // authoritative incoming main-call identity before local snapshot IDs.
829
+ const stableMainInteractionId =
830
+ payloadMainInteractionId ||
831
+ TaskManager.getMainCallMediaId(message.data.interaction) ||
832
+ TaskManager.getMainCallMediaId(task.data?.interaction) ||
833
+ taskMainInteractionId ||
834
+ message.data.interaction?.interactionId ||
835
+ task.data?.interaction?.interactionId ||
836
+ task.data?.interactionId;
837
+ const taskAtStableKey = stableMainInteractionId
838
+ ? this.taskCollection[stableMainInteractionId]
839
+ : undefined;
840
+
841
+ if (
842
+ !stableMainInteractionId ||
843
+ hasConflictingMainInteractionIds ||
844
+ (taskAtStableKey && taskAtStableKey !== task)
845
+ ) {
846
+ LoggerProxy.warn('Unable to safely correlate owner change to the main interaction task', {
847
+ module: TASK_MANAGER_FILE,
848
+ method: 'prepareEventContext',
849
+ interactionId:
850
+ payloadMainInteractionId || taskMainInteractionId || message.data.interactionId,
851
+ });
852
+ task = undefined;
853
+ } else if (ownerChangeResolvedByRelatedInteraction) {
854
+ ownerChangeMainInteractionId = stableMainInteractionId;
855
+ }
796
856
  }
797
857
 
798
858
  if (!task && MAIN_INTERACTION_CORRELATED_EVENTS.has(eventType)) {
@@ -821,8 +881,11 @@ export default class TaskManager extends EventEmitter {
821
881
  }
822
882
  : message.data;
823
883
 
824
- if (task && eventType === CC_EVENTS.CONTACT_OWNER_CHANGED) {
825
- adjustedPayload = TaskManager.preserveMainTaskIdentity(task, adjustedPayload);
884
+ if (task && ownerChangeMainInteractionId) {
885
+ adjustedPayload = {
886
+ ...adjustedPayload,
887
+ interactionId: ownerChangeMainInteractionId,
888
+ };
826
889
  }
827
890
 
828
891
  if (task && eventType === CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE) {
@@ -860,53 +923,6 @@ export default class TaskManager extends EventEmitter {
860
923
  };
861
924
  }
862
925
 
863
- /**
864
- * ContactOwnerChanged can identify the promoted agent's child interaction while
865
- * the surviving task is stored under the main interaction. Accept one related
866
- * task when its existing snapshot or the authoritative incoming promotion proves
867
- * current-agent main-call membership, while excluding non-promoted and ambiguous matches.
868
- */
869
- private findTaskForContactOwnerChange(payload: WebSocketPayload): ITask | undefined {
870
- const relatedCandidates = this.findRelatedTasks(payload, {
871
- includeCollectionKeys: true,
872
- includeMainCallMediaKeys: true,
873
- });
874
- const incomingShowsCurrentAgentPromotion =
875
- this.isCurrentAgentPromotedOnIncomingMainCall(payload);
876
- const eligibleCandidates = relatedCandidates.filter(
877
- (candidate) =>
878
- this.isCurrentAgentActiveOnMainInteraction(candidate.data) ||
879
- incomingShowsCurrentAgentPromotion
880
- );
881
-
882
- if (eligibleCandidates.length === 1) {
883
- return eligibleCandidates[0];
884
- }
885
-
886
- if (eligibleCandidates.length > 1) {
887
- LoggerProxy.warn('Unable to correlate task event to a unique main interaction task', {
888
- module: TASK_MANAGER_FILE,
889
- method: 'findTaskForContactOwnerChange',
890
- interactionId:
891
- payload.interaction?.mainInteractionId ||
892
- payload.interaction?.parentInteractionId ||
893
- payload.interaction?.callProcessingDetails?.parentInteractionId ||
894
- payload.interactionId,
895
- });
896
-
897
- return undefined;
898
- }
899
-
900
- return undefined;
901
- }
902
-
903
- private isCurrentAgentActiveOnMainInteraction(taskData: TaskData | undefined): boolean {
904
- const currentAgentId = this.agentId || taskData?.agentId;
905
- if (!currentAgentId) return false;
906
-
907
- return TaskManager.isParticipantActiveOnMainInteraction(taskData, currentAgentId);
908
- }
909
-
910
926
  private static isParticipantActiveOnMainInteraction(
911
927
  taskData: TaskData | WebSocketPayload | undefined,
912
928
  participantId: string
@@ -921,14 +937,6 @@ export default class TaskManager extends EventEmitter {
921
937
  );
922
938
  }
923
939
 
924
- private isCurrentAgentPromotedOnIncomingMainCall(payload: WebSocketPayload): boolean {
925
- return Boolean(
926
- this.agentId &&
927
- payload.interaction?.owner === this.agentId &&
928
- TaskManager.isParticipantActiveOnMainInteraction(payload, this.agentId)
929
- );
930
- }
931
-
932
940
  private static getMainCallMediaId(
933
941
  interaction: TaskData['interaction'] | undefined
934
942
  ): string | undefined {
@@ -960,31 +968,6 @@ export default class TaskManager extends EventEmitter {
960
968
  );
961
969
  }
962
970
 
963
- /** Keep a related owner-change event from re-keying the surviving main task to a child ID. */
964
- private static preserveMainTaskIdentity(
965
- task: ITask,
966
- payload: WebSocketPayload
967
- ): WebSocketPayload {
968
- const currentMainInteractionId = task.data?.interaction?.mainInteractionId;
969
- const payloadMainInteractionId = payload.interaction?.mainInteractionId;
970
- const currentMainMediaId = TaskManager.getMainCallMediaId(task.data?.interaction);
971
- const payloadMainMediaId = TaskManager.getMainCallMediaId(payload.interaction);
972
- const stableInteractionId =
973
- currentMainInteractionId ||
974
- payloadMainInteractionId ||
975
- currentMainMediaId ||
976
- payloadMainMediaId ||
977
- payload.interaction?.interactionId ||
978
- task.data?.interaction?.interactionId ||
979
- task.data?.interactionId;
980
-
981
- if (!stableInteractionId || stableInteractionId === payload.interactionId) {
982
- return payload;
983
- }
984
-
985
- return {...payload, interactionId: stableInteractionId};
986
- }
987
-
988
971
  /**
989
972
  * ParticipantLeftConference may arrive after an owner update while still carrying
990
973
  * the departed owner. Keep the confirmed owner only when the new roster proves that
@@ -1024,9 +1007,10 @@ export default class TaskManager extends EventEmitter {
1024
1007
  */
1025
1008
  private findUniqueTaskByRelatedInteraction(
1026
1009
  payload: WebSocketPayload,
1027
- candidateFilter: (task: ITask) => boolean = () => true
1010
+ candidateFilter: (task: ITask) => boolean = () => true,
1011
+ options: {includeCollectionKeys?: boolean; includeMainCallMediaKeys?: boolean} = {}
1028
1012
  ): ITask | undefined {
1029
- const candidates = this.findRelatedTasks(payload).filter(candidateFilter);
1013
+ const candidates = this.findRelatedTasks(payload, options).filter(candidateFilter);
1030
1014
 
1031
1015
  if (candidates.length === 1) {
1032
1016
  return candidates[0];
@@ -1060,7 +1044,9 @@ export default class TaskManager extends EventEmitter {
1060
1044
  payload.interaction?.parentInteractionId,
1061
1045
  payload.interaction?.callProcessingDetails?.parentInteractionId,
1062
1046
  ...(includeMainCallMediaKeys ? [TaskManager.getMainCallMediaId(payload.interaction)] : []),
1063
- ].filter((interactionId): interactionId is string => Boolean(interactionId))
1047
+ ].filter((relatedInteractionId): relatedInteractionId is string =>
1048
+ Boolean(relatedInteractionId)
1049
+ )
1064
1050
  );
1065
1051
  if (payloadInteractionIds.size === 0) return [];
1066
1052
 
@@ -1082,7 +1068,8 @@ export default class TaskManager extends EventEmitter {
1082
1068
  ];
1083
1069
 
1084
1070
  return candidateInteractionIds.some(
1085
- (interactionId) => Boolean(interactionId) && payloadInteractionIds.has(interactionId)
1071
+ (relatedInteractionId) =>
1072
+ Boolean(relatedInteractionId) && payloadInteractionIds.has(relatedInteractionId)
1086
1073
  );
1087
1074
  })
1088
1075
  .map(([, candidate]) => candidate)
@@ -924,15 +924,17 @@ guards. The state-machine transition table lives in
924
924
  Primary-Agent promotion remains backend-authoritative and follows the two-event
925
925
  desktop contract. `ContactOwnerChanged` updates the promoted Agent; TaskManager
926
926
  prefers an exact task, then one unique related task resolved through nested
927
- main/parent identifiers or the `mainCall` media-map identity. A related
928
- candidate is eligible when its current
927
+ main/parent identifiers, the task collection key, or the `mainCall` media-map
928
+ identity. A related candidate is eligible when its current
929
929
  snapshot contains the current Agent's participant entry with `hasLeft !== true`
930
930
  and membership on the `mType: mainCall` leg. The incoming snapshot can provide
931
931
  that evidence only when it also names the current Agent as `interaction.owner`;
932
932
  this permits an authoritative promotion payload to repair a stale child-keyed
933
933
  snapshot while still excluding consult-only tasks. The update keeps the
934
934
  surviving main interaction identity even when the notification names a
935
- promoted-Agent child interaction.
935
+ promoted-Agent child interaction. Conflicting main identifiers, ambiguous
936
+ matches, and a canonical key occupied by a distinct task are ignored rather
937
+ than replacing an existing task.
936
938
 
937
939
  If no related task exists, TaskManager recovers only the promoted current Agent
938
940
  from a non-terminal telephony `ContactOwnerChanged` payload that provides the same
@@ -2927,7 +2927,7 @@ describe('TaskManager', () => {
2927
2927
  expect(hydrateHandler).toHaveBeenCalledTimes(1);
2928
2928
  });
2929
2929
 
2930
- it('reuses a task stored under the incoming main-call media key', () => {
2930
+ it('reuses a task identified by the incoming main-call media key', () => {
2931
2931
  const mainMediaInteractionId = 'main-media-interaction-id';
2932
2932
  const promotedChildId = 'promoted-child-without-main-id';
2933
2933
  const task = installTask(
@@ -2958,6 +2958,41 @@ describe('TaskManager', () => {
2958
2958
  expect(hydrateHandler).toHaveBeenCalledWith(task);
2959
2959
  });
2960
2960
 
2961
+ it('reuses a stale task through its collection key and the incoming main-call media key', () => {
2962
+ const mainMediaInteractionId = 'collection-main-interaction-id';
2963
+ const staleChildId = 'stale-collection-child-id';
2964
+ const promotedChildId = 'promoted-collection-child-id';
2965
+ const staleTaskData = createConferenceTaskData(
2966
+ staleChildId,
2967
+ previousOwnerId,
2968
+ staleChildId
2969
+ );
2970
+ staleTaskData.interaction.media[staleChildId].mType = 'consult';
2971
+ const task = createMockTask(staleTaskData);
2972
+ taskManager.taskCollection = {[mainMediaInteractionId]: task};
2973
+ (taskManager as any).setupTaskListeners(task);
2974
+ const hydrateHandler = jest.fn();
2975
+ taskManager.on(TASK_EVENTS.TASK_HYDRATE, hydrateHandler);
2976
+ const ownerChangeData = createPromotedAgentOwnerChangeData(
2977
+ promotedChildId,
2978
+ mainMediaInteractionId
2979
+ );
2980
+ delete ownerChangeData.interaction.mainInteractionId;
2981
+
2982
+ webSocketManagerMock.emit('message', JSON.stringify({data: ownerChangeData}));
2983
+
2984
+ expectLastStateMachineEvent(task.sendStateMachineEvent, TaskEvent.CONTACT_OWNER_CHANGED);
2985
+ expect(task.data.interactionId).toBe(mainMediaInteractionId);
2986
+ expect(task.data.interaction.owner).toBe(currentAgentId);
2987
+ expect(TaskFactory.createTask).not.toHaveBeenCalled();
2988
+ expect(taskManager.taskCollection[mainMediaInteractionId]).toBe(task);
2989
+ expect(taskManager.taskCollection[staleChildId]).toBeUndefined();
2990
+ expect(taskManager.taskCollection[promotedChildId]).toBeUndefined();
2991
+ expect(Object.values(taskManager.taskCollection)).toEqual([task]);
2992
+ expect(task.listenerCount(TASK_EVENTS.TASK_HYDRATE)).toBe(1);
2993
+ expect(hydrateHandler).toHaveBeenCalledTimes(1);
2994
+ });
2995
+
2961
2996
  it('prefers the exact task for ContactOwnerChanged over related child tasks', () => {
2962
2997
  const exactTask = installTask(createConferenceTaskData(taskId, previousOwnerId));
2963
2998
  const childTaskId = 'related-child-id';
@@ -2980,6 +3015,102 @@ describe('TaskManager', () => {
2980
3015
  expect(childTask.sendStateMachineEvent).not.toHaveBeenCalled();
2981
3016
  });
2982
3017
 
3018
+ it('does not replace a distinct main task when an exact child task also exists', () => {
3019
+ const childTaskId = 'exact-child-id';
3020
+ const mainTask = installTask(createConferenceTaskData(taskId, previousOwnerId));
3021
+ const childTask = createMockTask(
3022
+ createConferenceTaskData(childTaskId, previousOwnerId, taskId)
3023
+ );
3024
+ taskManager.taskCollection[childTaskId] = childTask;
3025
+
3026
+ webSocketManagerMock.emit(
3027
+ 'message',
3028
+ JSON.stringify({
3029
+ data: {
3030
+ ...createConferenceTaskData(childTaskId, promotedOwnerId, taskId),
3031
+ type: CC_EVENTS.CONTACT_OWNER_CHANGED,
3032
+ },
3033
+ })
3034
+ );
3035
+
3036
+ expect(childTask.sendStateMachineEvent).not.toHaveBeenCalled();
3037
+ expect(mainTask.sendStateMachineEvent).not.toHaveBeenCalled();
3038
+ expect(taskManager.taskCollection[taskId]).toBe(mainTask);
3039
+ expect(taskManager.taskCollection[childTaskId]).toBe(childTask);
3040
+ });
3041
+
3042
+ it('ignores a related owner change with conflicting explicit main interaction IDs', () => {
3043
+ const childTaskId = 'conflicting-main-child-id';
3044
+ const existingMainId = 'existing-main-id';
3045
+ const incomingMainId = 'incoming-main-id';
3046
+ const sharedParentId = 'shared-parent-id';
3047
+ const taskData = createConferenceTaskData(
3048
+ childTaskId,
3049
+ previousOwnerId,
3050
+ existingMainId
3051
+ );
3052
+ taskData.interaction.parentInteractionId = sharedParentId;
3053
+ const task = installTask(taskData);
3054
+ const ownerChangeData = createConferenceTaskData(
3055
+ 'different-child-id',
3056
+ promotedOwnerId,
3057
+ incomingMainId
3058
+ );
3059
+ ownerChangeData.interaction.parentInteractionId = sharedParentId;
3060
+
3061
+ webSocketManagerMock.emit(
3062
+ 'message',
3063
+ JSON.stringify({
3064
+ data: {...ownerChangeData, type: CC_EVENTS.CONTACT_OWNER_CHANGED},
3065
+ })
3066
+ );
3067
+
3068
+ expect(task.sendStateMachineEvent).not.toHaveBeenCalled();
3069
+ expect(taskManager.taskCollection[childTaskId]).toBe(task);
3070
+ expect(taskManager.taskCollection[existingMainId]).toBeUndefined();
3071
+ expect(taskManager.taskCollection[incomingMainId]).toBeUndefined();
3072
+ expect(TaskFactory.createTask).not.toHaveBeenCalled();
3073
+ });
3074
+
3075
+ it('does not replace a distinct task already stored at the canonical main key', () => {
3076
+ const childTaskId = 'related-active-child-id';
3077
+ const canonicalMainId = 'occupied-canonical-main-id';
3078
+ const candidate = createMockTask(
3079
+ createConferenceTaskData(childTaskId, previousOwnerId, canonicalMainId)
3080
+ );
3081
+ const occupiedData = createConferenceTaskData(
3082
+ 'occupied-task-child-id',
3083
+ previousOwnerId,
3084
+ canonicalMainId
3085
+ );
3086
+ occupiedData.interaction.media[canonicalMainId].mType = 'consult';
3087
+ const occupiedTask = createMockTask(occupiedData);
3088
+ taskManager.taskCollection = {
3089
+ [childTaskId]: candidate,
3090
+ [canonicalMainId]: occupiedTask,
3091
+ };
3092
+
3093
+ webSocketManagerMock.emit(
3094
+ 'message',
3095
+ JSON.stringify({
3096
+ data: {
3097
+ ...createConferenceTaskData(
3098
+ 'incoming-related-child-id',
3099
+ promotedOwnerId,
3100
+ canonicalMainId
3101
+ ),
3102
+ type: CC_EVENTS.CONTACT_OWNER_CHANGED,
3103
+ },
3104
+ })
3105
+ );
3106
+
3107
+ expect(candidate.sendStateMachineEvent).not.toHaveBeenCalled();
3108
+ expect(occupiedTask.sendStateMachineEvent).not.toHaveBeenCalled();
3109
+ expect(taskManager.taskCollection[childTaskId]).toBe(candidate);
3110
+ expect(taskManager.taskCollection[canonicalMainId]).toBe(occupiedTask);
3111
+ expect(TaskFactory.createTask).not.toHaveBeenCalled();
3112
+ });
3113
+
2983
3114
  it('ignores a related ContactOwnerChanged task whose current agent is consult-only', () => {
2984
3115
  const childTaskId = 'consult-only-child-id';
2985
3116
  const data = createConferenceTaskData(childTaskId, previousOwnerId, taskId);
@@ -3066,7 +3197,43 @@ describe('TaskManager', () => {
3066
3197
  expect(secondTask.sendStateMachineEvent).not.toHaveBeenCalled();
3067
3198
  });
3068
3199
 
3069
- it('does not recover a new task when multiple stale related tasks become eligible from the payload', () => {
3200
+ it('ignores ambiguous main-call media-key matches without creating a task', () => {
3201
+ const mainMediaInteractionId = 'ambiguous-main-media-id';
3202
+ const createMediaRelatedTask = (interactionId: string) => {
3203
+ const data = createConferenceTaskData(interactionId, previousOwnerId, interactionId);
3204
+ data.interaction.media = {
3205
+ [mainMediaInteractionId]: {
3206
+ mediaResourceId: mainMediaInteractionId,
3207
+ mediaType: 'telephony',
3208
+ mType: 'mainCall',
3209
+ participants: [currentAgentId, previousOwnerId, promotedOwnerId],
3210
+ },
3211
+ };
3212
+
3213
+ return createMockTask(data);
3214
+ };
3215
+ const firstTask = createMediaRelatedTask('first-media-child-id');
3216
+ const secondTask = createMediaRelatedTask('second-media-child-id');
3217
+ taskManager.taskCollection = {
3218
+ 'first-media-child-id': firstTask,
3219
+ 'second-media-child-id': secondTask,
3220
+ };
3221
+ (TaskFactory.createTask as jest.Mock).mockClear();
3222
+ const ownerChangeData = createPromotedAgentOwnerChangeData(
3223
+ 'promoted-media-child-id',
3224
+ mainMediaInteractionId
3225
+ );
3226
+ delete ownerChangeData.interaction.mainInteractionId;
3227
+
3228
+ webSocketManagerMock.emit('message', JSON.stringify({data: ownerChangeData}));
3229
+
3230
+ expect(firstTask.sendStateMachineEvent).not.toHaveBeenCalled();
3231
+ expect(secondTask.sendStateMachineEvent).not.toHaveBeenCalled();
3232
+ expect(TaskFactory.createTask).not.toHaveBeenCalled();
3233
+ expect(Object.values(taskManager.taskCollection)).toEqual([firstTask, secondTask]);
3234
+ });
3235
+
3236
+ it('does not recover a new task when multiple stale related tasks exist', () => {
3070
3237
  const createStaleRelatedTask = (interactionId: string) => {
3071
3238
  const data = createConferenceTaskData(interactionId, previousOwnerId, taskId);
3072
3239
  data.interaction.media[taskId].participants = [previousOwnerId, promotedOwnerId];
@@ -3178,7 +3345,10 @@ describe('TaskManager', () => {
3178
3345
  expect.objectContaining({
3179
3346
  type: TaskEvent.HYDRATE,
3180
3347
  agentId: currentAgentId,
3181
- taskData: expect.objectContaining({interactionId: taskId}),
3348
+ taskData: expect.objectContaining({
3349
+ interactionId: taskId,
3350
+ interaction: expect.objectContaining({state: 'conference'}),
3351
+ }),
3182
3352
  })
3183
3353
  );
3184
3354
  expect(recoveredTask.sendStateMachineEvent).toHaveBeenNthCalledWith(
@@ -3192,6 +3362,7 @@ describe('TaskManager', () => {
3192
3362
  expect(taskManager.taskCollection['promoted-child-interaction-id']).toBeUndefined();
3193
3363
  expect(Object.values(taskManager.taskCollection)).toEqual([recoveredTask]);
3194
3364
  expect(recoveredTask.data.interaction.owner).toBe(currentAgentId);
3365
+ expect(recoveredTask.listenerCount(TASK_EVENTS.TASK_HYDRATE)).toBe(1);
3195
3366
  expect(hydrateHandler).toHaveBeenCalledTimes(1);
3196
3367
  expect(hydrateHandler).toHaveBeenCalledWith(recoveredTask);
3197
3368
  expect(incomingHandler).not.toHaveBeenCalled();
@@ -3217,6 +3388,21 @@ describe('TaskManager', () => {
3217
3388
  data.interaction.isTerminated = true;
3218
3389
  },
3219
3390
  ],
3391
+ [
3392
+ 'the interaction is not telephony',
3393
+ (data) => {
3394
+ data.interaction.mediaType = 'chat';
3395
+ },
3396
+ ],
3397
+ [
3398
+ 'the payload has no stable interaction identifier',
3399
+ (data) => {
3400
+ data.interactionId = undefined;
3401
+ data.interaction.interactionId = undefined;
3402
+ delete data.interaction.mainInteractionId;
3403
+ data.interaction.media = {};
3404
+ },
3405
+ ],
3220
3406
  ])('does not create a missing task when %s', (_description, mutatePayload) => {
3221
3407
  clearTasksAndFactoryHistory();
3222
3408
  const ownerChangeData = createPromotedAgentOwnerChangeData();
@@ -539,6 +539,39 @@ describe('Task state machine', () => {
539
539
  } as any,
540
540
  });
541
541
 
542
+ it('hydrates a recovered active conference from IDLE to CONFERENCING', () => {
543
+ const service = startMachine();
544
+ const taskData = createTaskData({
545
+ agentId: 'agent-1',
546
+ interactionId: 'interaction-1',
547
+ mediaResourceId: 'interaction-1',
548
+ interaction: {
549
+ state: 'conference',
550
+ interactionId: 'interaction-1',
551
+ mainInteractionId: 'interaction-1',
552
+ owner: 'agent-1',
553
+ participants: {
554
+ 'agent-1': {id: 'agent-1', pType: 'Agent', hasLeft: false},
555
+ 'agent-2': {id: 'agent-2', pType: 'Agent', hasLeft: false},
556
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false},
557
+ },
558
+ media: {
559
+ 'interaction-1': {
560
+ mediaResourceId: 'interaction-1',
561
+ mType: 'mainCall',
562
+ participants: ['agent-1', 'agent-2', 'customer-1'],
563
+ isHold: false,
564
+ },
565
+ },
566
+ } as any,
567
+ });
568
+
569
+ service.send({type: TaskEvent.HYDRATE, taskData});
570
+
571
+ expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
572
+ expect(service.getSnapshot().context.taskData).toEqual(taskData);
573
+ });
574
+
542
575
  it('boots from IDLE to CONSULTING on CONSULTING_ACTIVE for split-leg ordering', () => {
543
576
  const service = startMachine();
544
577
  const taskData = createTaskData({