@webex/contact-center 3.12.0-llmrefactor.13 → 3.12.0-llmrefactor.14

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.
Files changed (68) hide show
  1. package/.sdd/manifest.json +3 -2
  2. package/ai-docs/CONTRACTS.md +7 -1
  3. package/ai-docs/contact-center-spec.md +7 -7
  4. package/ai-docs/features/contact-owner-change-refactor.md +78 -0
  5. package/dist/cc.js +39 -7
  6. package/dist/cc.js.map +1 -1
  7. package/dist/metrics/behavioral-events.js +6 -0
  8. package/dist/metrics/behavioral-events.js.map +1 -1
  9. package/dist/metrics/constants.js +1 -0
  10. package/dist/metrics/constants.js.map +1 -1
  11. package/dist/services/AnswerCallOnWebexService.js +4 -4
  12. package/dist/services/AnswerCallOnWebexService.js.map +1 -1
  13. package/dist/services/WebexCrossClientService.js +5 -2
  14. package/dist/services/WebexCrossClientService.js.map +1 -1
  15. package/dist/services/WxAppTelephonyMercurySync.js +6 -3
  16. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -1
  17. package/dist/services/config/types.js.map +1 -1
  18. package/dist/services/task/TaskFactory.js +1 -0
  19. package/dist/services/task/TaskFactory.js.map +1 -1
  20. package/dist/services/task/TaskManager.js +50 -58
  21. package/dist/services/task/TaskManager.js.map +1 -1
  22. package/dist/services/task/types.js.map +1 -1
  23. package/dist/services/task/voice/Voice.js +63 -44
  24. package/dist/services/task/voice/Voice.js.map +1 -1
  25. package/dist/services/task/voice/wxAppOfferObservability.js +145 -0
  26. package/dist/services/task/voice/wxAppOfferObservability.js.map +1 -0
  27. package/dist/services/task/voice/wxAppVoiceMethods.js +52 -11
  28. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -1
  29. package/dist/services/wxAppDiagnosticLogging.js +50 -13
  30. package/dist/services/wxAppDiagnosticLogging.js.map +1 -1
  31. package/dist/types/metrics/constants.d.ts +1 -0
  32. package/dist/types/services/config/types.d.ts +2 -0
  33. package/dist/types/services/task/types.d.ts +2 -0
  34. package/dist/types/services/task/voice/Voice.d.ts +15 -2
  35. package/dist/types/services/task/voice/wxAppOfferObservability.d.ts +39 -0
  36. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +19 -6
  37. package/dist/types/services/wxAppDiagnosticLogging.d.ts +12 -0
  38. package/dist/types.js.map +1 -1
  39. package/dist/webex.js +1 -1
  40. package/package.json +2 -2
  41. package/src/cc.ts +43 -7
  42. package/src/metrics/ai-docs/metrics-spec.md +44 -2
  43. package/src/metrics/behavioral-events.ts +6 -0
  44. package/src/metrics/constants.ts +1 -0
  45. package/src/services/AnswerCallOnWebexService.ts +4 -4
  46. package/src/services/WebexCrossClientService.ts +7 -2
  47. package/src/services/WxAppTelephonyMercurySync.ts +3 -3
  48. package/src/services/config/types.ts +2 -0
  49. package/src/services/task/TaskFactory.ts +1 -0
  50. package/src/services/task/TaskManager.ts +85 -87
  51. package/src/services/task/ai-docs/task-spec.md +7 -3
  52. package/src/services/task/types.ts +16 -0
  53. package/src/services/task/voice/Voice.ts +74 -52
  54. package/src/services/task/voice/wxAppOfferObservability.ts +204 -0
  55. package/src/services/task/voice/wxAppVoiceMethods.ts +83 -24
  56. package/src/services/wxAppDiagnosticLogging.ts +135 -23
  57. package/src/types.ts +2 -0
  58. package/test/unit/spec/cc.ts +201 -19
  59. package/test/unit/spec/services/WebexCrossClientService.ts +45 -3
  60. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +1 -1
  61. package/test/unit/spec/services/task/TaskManager.ts +214 -3
  62. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +33 -0
  63. package/test/unit/spec/services/task/voice/Voice.ts +10 -51
  64. package/test/unit/spec/services/task/voice/wxAppOfferObservability.ts +361 -0
  65. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +121 -5
  66. package/test/unit/spec/services/wxAppDiagnosticLogging.ts +44 -8
  67. package/umd/contact-center.min.js +2 -2
  68. package/umd/contact-center.min.js.map +1 -1
@@ -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();
@@ -3626,6 +3812,15 @@ describe('TaskManager', () => {
3626
3812
  expect(taskManager.taskCollection['task-alias']).toBeUndefined();
3627
3813
  });
3628
3814
 
3815
+ it('disposes wxApp offer observability when removing a task from the collection', () => {
3816
+ const disposeWxAppOfferObservability = jest.fn();
3817
+ task.disposeWxAppOfferObservability = disposeWxAppOfferObservability;
3818
+
3819
+ (taskManager as any).removeTaskFromCollection(task);
3820
+
3821
+ expect(disposeWxAppOfferObservability).toHaveBeenCalledTimes(1);
3822
+ });
3823
+
3629
3824
  it('replaces an EP-DN child task with a hydrated main task on CONTACT_MERGED', () => {
3630
3825
  const childTaskId = 'ep-dn-child-interaction-id';
3631
3826
  const childTask = createMockTask({
@@ -4186,4 +4381,20 @@ describe('TaskManager', () => {
4186
4381
  expect(taskTwo.applyWxAppMuteStateFromSync).toHaveBeenCalledWith('call-1', true);
4187
4382
  });
4188
4383
  });
4384
+
4385
+ describe('refreshWxAppOfferObservabilityForAllTasks', () => {
4386
+ it('invokes refresh on collected tasks that implement it', () => {
4387
+ const taskOne = {refreshWxAppOfferObservability: jest.fn()};
4388
+ const taskTwo = {};
4389
+
4390
+ taskManager['taskCollection'] = {
4391
+ [taskId]: taskOne,
4392
+ 'task-2': taskTwo,
4393
+ };
4394
+
4395
+ taskManager.refreshWxAppOfferObservabilityForAllTasks();
4396
+
4397
+ expect(taskOne.refreshWxAppOfferObservability).toHaveBeenCalledTimes(1);
4398
+ });
4399
+ });
4189
4400
  });
@@ -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({
@@ -1006,69 +1006,28 @@ describe('Voice Task', () => {
1006
1006
  });
1007
1007
  });
1008
1008
 
1009
- describe('wxApp offer decision logging', () => {
1010
- const wxAppParticipant = {
1011
- deviceType: 'wxApp',
1012
- deviceId: 'device-id-1',
1013
- deviceCallId: 'call-id-1',
1014
- };
1015
-
1016
- const makeWxAppOfferTaskData = () =>
1017
- createBaseData({
1018
- agentId: 'agent-1',
1019
- interaction: {
1020
- participants: {
1021
- 'agent-1': {id: 'agent-1', ...wxAppParticipant},
1022
- },
1023
- } as any,
1024
- });
1025
-
1026
- it('logs evolving accept reasons through the normal wxApp accept flow', () => {
1027
- const logSpy = jest.spyOn(wxAppDiagnosticLogging, 'logWxAppOfferDecision');
1028
- const taskData = makeWxAppOfferTaskData();
1029
- const voice = new Voice(dummyContact, taskData, {enableWxBetterTogether: true});
1030
-
1031
- voice.stateMachineService?.send({type: TaskEvent.TASK_INCOMING, taskData});
1032
-
1033
- expect(logSpy).toHaveBeenCalledWith(
1034
- expect.objectContaining({acceptReason: 'wxApp_offer_ready'})
1035
- );
1036
-
1037
- logSpy.mockClear();
1038
- voice['setWxAppAcceptInFlight'](true);
1039
-
1040
- expect(logSpy).toHaveBeenCalledWith(
1041
- expect.objectContaining({acceptReason: 'wxApp_accept_in_flight'})
1042
- );
1043
-
1044
- logSpy.mockClear();
1045
- voice['setWxAppAcceptInFlight'](false);
1046
- voice['setWxAppAnswerPending'](true);
1047
-
1048
- expect(logSpy).toHaveBeenCalledWith(
1049
- expect.objectContaining({acceptReason: 'wxApp_answer_pending'})
1050
- );
1051
- });
1052
-
1053
- it('logs browser_webrtc_offer when voice variant is WebRTC on a non-wxApp inbound offer', () => {
1009
+ describe('wxApp offer observability integration', () => {
1010
+ it('delegates OFFERED ui control updates to wxApp offer observability', () => {
1054
1011
  const logSpy = jest.spyOn(wxAppDiagnosticLogging, 'logWxAppOfferDecision');
1055
1012
  const taskData = createBaseData({
1056
1013
  agentId: 'agent-1',
1057
1014
  interaction: {
1058
1015
  participants: {
1059
- 'agent-1': {id: 'agent-1', deviceType: 'phone', deviceId: 'device-id-1'},
1016
+ 'agent-1': {
1017
+ id: 'agent-1',
1018
+ deviceType: 'wxApp',
1019
+ deviceId: 'device-id-1',
1020
+ deviceCallId: 'call-id-1',
1021
+ },
1060
1022
  },
1061
1023
  } as any,
1062
1024
  });
1063
- const voice = new Voice(dummyContact, taskData, {
1064
- enableWxBetterTogether: true,
1065
- voiceVariant: VOICE_VARIANT.WEBRTC,
1066
- });
1025
+ const voice = new Voice(dummyContact, taskData, {enableWxBetterTogether: true});
1067
1026
 
1068
1027
  voice.stateMachineService?.send({type: TaskEvent.TASK_INCOMING, taskData});
1069
1028
 
1070
1029
  expect(logSpy).toHaveBeenCalledWith(
1071
- expect.objectContaining({acceptReason: 'browser_webrtc_offer'})
1030
+ expect.objectContaining({acceptReason: 'wxApp_offer_ready'})
1072
1031
  );
1073
1032
  });
1074
1033
  });