@webex/contact-center 3.12.0-task-refactor.10 → 3.12.0-task-refactor.12

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 (110) hide show
  1. package/dist/cc.js +345 -82
  2. package/dist/cc.js.map +1 -1
  3. package/dist/config.js +6 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/constants.js +3 -0
  6. package/dist/constants.js.map +1 -1
  7. package/dist/metrics/behavioral-events.js +39 -0
  8. package/dist/metrics/behavioral-events.js.map +1 -1
  9. package/dist/metrics/constants.js +8 -1
  10. package/dist/metrics/constants.js.map +1 -1
  11. package/dist/services/ApiAiAssistant.js +3 -0
  12. package/dist/services/ApiAiAssistant.js.map +1 -1
  13. package/dist/services/config/Util.js +1 -1
  14. package/dist/services/config/Util.js.map +1 -1
  15. package/dist/services/config/types.js +10 -0
  16. package/dist/services/config/types.js.map +1 -1
  17. package/dist/services/constants.js +1 -0
  18. package/dist/services/constants.js.map +1 -1
  19. package/dist/services/core/Err.js.map +1 -1
  20. package/dist/services/core/Utils.js +57 -6
  21. package/dist/services/core/Utils.js.map +1 -1
  22. package/dist/services/core/aqm-reqs.js +92 -17
  23. package/dist/services/core/aqm-reqs.js.map +1 -1
  24. package/dist/services/core/websocket/WebSocketManager.js +24 -3
  25. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  26. package/dist/services/index.js +1 -0
  27. package/dist/services/index.js.map +1 -1
  28. package/dist/services/task/Task.js +16 -4
  29. package/dist/services/task/Task.js.map +1 -1
  30. package/dist/services/task/TaskManager.js +158 -6
  31. package/dist/services/task/TaskManager.js.map +1 -1
  32. package/dist/services/task/TaskUtils.js +39 -11
  33. package/dist/services/task/TaskUtils.js.map +1 -1
  34. package/dist/services/task/constants.js +7 -1
  35. package/dist/services/task/constants.js.map +1 -1
  36. package/dist/services/task/dialer.js +129 -0
  37. package/dist/services/task/dialer.js.map +1 -1
  38. package/dist/services/task/state-machine/TaskStateMachine.js +30 -3
  39. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
  40. package/dist/services/task/state-machine/actions.js +4 -0
  41. package/dist/services/task/state-machine/actions.js.map +1 -1
  42. package/dist/services/task/state-machine/constants.js +3 -0
  43. package/dist/services/task/state-machine/constants.js.map +1 -1
  44. package/dist/services/task/state-machine/guards.js +23 -0
  45. package/dist/services/task/state-machine/guards.js.map +1 -1
  46. package/dist/services/task/state-machine/types.js.map +1 -1
  47. package/dist/services/task/state-machine/uiControlsComputer.js +1 -1
  48. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
  49. package/dist/services/task/types.js +69 -0
  50. package/dist/services/task/types.js.map +1 -1
  51. package/dist/types/cc.d.ts +93 -2
  52. package/dist/types/config.d.ts +6 -0
  53. package/dist/types/constants.d.ts +3 -0
  54. package/dist/types/metrics/constants.d.ts +6 -0
  55. package/dist/types/services/config/types.d.ts +22 -1
  56. package/dist/types/services/core/Err.d.ts +6 -0
  57. package/dist/types/services/core/Utils.d.ts +20 -2
  58. package/dist/types/services/core/aqm-reqs.d.ts +49 -0
  59. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  60. package/dist/types/services/core/websocket/connection-service.d.ts +1 -0
  61. package/dist/types/services/task/Task.d.ts +1 -0
  62. package/dist/types/services/task/TaskUtils.d.ts +13 -0
  63. package/dist/types/services/task/constants.d.ts +6 -0
  64. package/dist/types/services/task/dialer.d.ts +45 -0
  65. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +36 -8
  66. package/dist/types/services/task/state-machine/constants.d.ts +3 -0
  67. package/dist/types/services/task/state-machine/guards.d.ts +2 -0
  68. package/dist/types/services/task/state-machine/types.d.ts +11 -0
  69. package/dist/types/services/task/types.d.ts +85 -1
  70. package/dist/types/types.d.ts +2 -0
  71. package/dist/types.js.map +1 -1
  72. package/dist/webex.js +1 -1
  73. package/package.json +2 -2
  74. package/src/cc.ts +452 -102
  75. package/src/config.ts +6 -0
  76. package/src/constants.ts +3 -0
  77. package/src/metrics/behavioral-events.ts +42 -0
  78. package/src/metrics/constants.ts +9 -1
  79. package/src/services/ApiAiAssistant.ts +3 -0
  80. package/src/services/config/Util.ts +1 -1
  81. package/src/services/config/types.ts +12 -1
  82. package/src/services/constants.ts +1 -0
  83. package/src/services/core/Err.ts +3 -0
  84. package/src/services/core/Utils.ts +68 -5
  85. package/src/services/core/aqm-reqs.ts +100 -22
  86. package/src/services/core/websocket/WebSocketManager.ts +23 -2
  87. package/src/services/index.ts +1 -0
  88. package/src/services/task/Task.ts +22 -4
  89. package/src/services/task/TaskManager.ts +175 -7
  90. package/src/services/task/TaskUtils.ts +33 -1
  91. package/src/services/task/constants.ts +6 -0
  92. package/src/services/task/dialer.ts +136 -1
  93. package/src/services/task/state-machine/TaskStateMachine.ts +37 -5
  94. package/src/services/task/state-machine/actions.ts +4 -0
  95. package/src/services/task/state-machine/constants.ts +3 -0
  96. package/src/services/task/state-machine/guards.ts +30 -0
  97. package/src/services/task/state-machine/types.ts +14 -1
  98. package/src/services/task/state-machine/uiControlsComputer.ts +1 -1
  99. package/src/services/task/types.ts +91 -0
  100. package/src/types.ts +6 -1
  101. package/test/unit/spec/cc.ts +432 -26
  102. package/test/unit/spec/services/config/index.ts +30 -2
  103. package/test/unit/spec/services/core/Utils.ts +367 -1
  104. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +90 -1
  105. package/test/unit/spec/services/core/websocket/connection-service.ts +4 -5
  106. package/test/unit/spec/services/task/TaskManager.ts +2334 -145
  107. package/test/unit/spec/services/task/dialer.ts +372 -96
  108. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +47 -0
  109. package/umd/contact-center.min.js +2 -2
  110. package/umd/contact-center.min.js.map +1 -1
@@ -746,7 +746,7 @@ describe('AgentConfigService', () => {
746
746
  agentProfileId: 'profile123',
747
747
  siteId: 'site789',
748
748
  dbId: 'db123',
749
- defaultDialledNumber: '1234567890',
749
+ deafultDialledNumber: '1234567890',
750
750
  id: 'user001',
751
751
  teamIds: ['team1', 'team2'],
752
752
  };
@@ -775,6 +775,17 @@ describe('AgentConfigService', () => {
775
775
 
776
776
  const mockDialPlanData = [];
777
777
 
778
+ const mockTeamData = [
779
+ {id: 'team1', name: 'Support Team'},
780
+ {id: 'team2', name: 'Sales Team'},
781
+ ];
782
+
783
+ const mockOrgInfo = {
784
+ tenantId: 'tenant123',
785
+ timezone: 'GMT',
786
+ environment: 'produs1',
787
+ };
788
+
778
789
  const mockOrgSettings = {
779
790
  campaignManagerEnabled: true,
780
791
  webRtcEnabled: true,
@@ -798,6 +809,9 @@ describe('AgentConfigService', () => {
798
809
  {id: 'aux1', type: 'WRAP_UP_CODE', name: 'Wrap Up Code 1', isDefault: true},
799
810
  {id: 'aux2', type: 'IDLE_CODE', name: 'Idle Code 1', isDefault: true},
800
811
  ];
812
+ const mockAIFeatureFlags = {
813
+ data: [{realtimeTranscripts: {enable: true}}],
814
+ };
801
815
 
802
816
  const parseAgentConfigsSpy = jest.spyOn(util, 'parseAgentConfigs');
803
817
  agentConfigService.getUserUsingCI = jest.fn().mockResolvedValue(mockUserConfig);
@@ -861,7 +875,7 @@ describe('AgentConfigService', () => {
861
875
  skillProfileId: 'skillProfile456',
862
876
  siteId: 'site789',
863
877
  dbId: 'db123',
864
- defaultDialledNumber: '1234567890',
878
+ deafultDialledNumber: '1234567890',
865
879
  id: 'user001',
866
880
  teamIds: ['team1', 'team2'],
867
881
  };
@@ -895,6 +909,17 @@ describe('AgentConfigService', () => {
895
909
  {id: 'dialPlan2', name: 'Plan 2'},
896
910
  ];
897
911
 
912
+ const mockTeamData = [
913
+ {id: 'team1', name: 'Support Team'},
914
+ {id: 'team2', name: 'Sales Team'},
915
+ ];
916
+
917
+ const mockOrgInfo = {
918
+ tenantId: 'tenant123',
919
+ timezone: 'GMT',
920
+ environment: 'produs1',
921
+ };
922
+
898
923
  const mockOrgSettings = {
899
924
  campaignManagerEnabled: true,
900
925
  webRtcEnabled: true,
@@ -919,6 +944,9 @@ describe('AgentConfigService', () => {
919
944
  {id: 'aux1', type: 'WRAP_UP_CODE', name: 'Wrap Up Code 1'},
920
945
  {id: 'aux2', type: 'IDLE_CODE', name: 'Idle Code 1'},
921
946
  ];
947
+ const mockAIFeatureFlags = {
948
+ data: [{realtimeTranscripts: {enable: true}}],
949
+ };
922
950
 
923
951
  const parseAgentConfigsSpy = jest.spyOn(util, 'parseAgentConfigs');
924
952
  agentConfigService.getUserUsingCI = jest.fn().mockResolvedValue(mockUserConfig);
@@ -11,6 +11,7 @@ jest.mock('../../../../../src/logger-proxy', () => ({
11
11
  log: jest.fn(),
12
12
  error: jest.fn(),
13
13
  info: jest.fn(),
14
+ warn: jest.fn(),
14
15
  initialize: jest.fn(),
15
16
  },
16
17
  }));
@@ -244,7 +245,7 @@ describe('Utils', () => {
244
245
  const result = Utils.getStationLoginErrorData(failure, LoginOption.AGENT_DN);
245
246
  expect(result).toEqual({
246
247
  message:
247
- 'Enter a valid US dial number. For help, reach out to your administrator or support team.',
248
+ 'Enter a valid dial number. For help, reach out to your administrator or support team.',
248
249
  fieldName: LoginOption.AGENT_DN,
249
250
  });
250
251
  });
@@ -670,4 +671,369 @@ describe('Utils', () => {
670
671
  expect(underscoreResult.data.destinationType).toBe('entryPoint');
671
672
  });
672
673
  });
674
+
675
+ describe('getDestAgentIdForCBT', () => {
676
+ it('should return destination agent ID for CBT scenario', () => {
677
+ const interaction: any = {
678
+ participants: {
679
+ 'agent-uuid-123': {
680
+ type: 'Agent',
681
+ pType: 'dn',
682
+ dn: '5551234567',
683
+ id: 'agent-uuid-123',
684
+ },
685
+ 'customer-1': {
686
+ type: 'Customer',
687
+ pType: 'Customer',
688
+ id: 'customer-1',
689
+ },
690
+ },
691
+ };
692
+ const consultingAgent = '5551234567'; // Phone number, not in participants as key
693
+
694
+ const result = Utils.getDestAgentIdForCBT(interaction, consultingAgent);
695
+ expect(result).toBe('agent-uuid-123');
696
+ });
697
+
698
+ it('should return empty string when consultingAgent is in participants (non-CBT)', () => {
699
+ const interaction: any = {
700
+ participants: {
701
+ 'agent-123': {
702
+ type: 'Agent',
703
+ pType: 'Agent',
704
+ id: 'agent-123',
705
+ },
706
+ },
707
+ };
708
+ const consultingAgent = 'agent-123'; // Exists as key in participants
709
+
710
+ const result = Utils.getDestAgentIdForCBT(interaction, consultingAgent);
711
+ expect(result).toBe('');
712
+ });
713
+
714
+ it('should return empty string when no matching dial number found', () => {
715
+ const interaction: any = {
716
+ participants: {
717
+ 'agent-uuid-123': {
718
+ type: 'Agent',
719
+ pType: 'dn',
720
+ dn: '5559999999',
721
+ id: 'agent-uuid-123',
722
+ },
723
+ },
724
+ };
725
+ const consultingAgent = '5551234567'; // Different number
726
+
727
+ const result = Utils.getDestAgentIdForCBT(interaction, consultingAgent);
728
+ expect(result).toBe('');
729
+ });
730
+
731
+ it('should return empty string when consultingAgent is empty', () => {
732
+ const interaction: any = {
733
+ participants: {
734
+ 'agent-uuid-123': {
735
+ type: 'Agent',
736
+ pType: 'dn',
737
+ dn: '5551234567',
738
+ },
739
+ },
740
+ };
741
+
742
+ const result = Utils.getDestAgentIdForCBT(interaction, '');
743
+ expect(result).toBe('');
744
+ });
745
+
746
+ it('should match only when participant type is dial number and type is Agent', () => {
747
+ const interaction: any = {
748
+ participants: {
749
+ 'participant-1': {
750
+ type: 'Customer',
751
+ pType: 'dn',
752
+ dn: '5551234567',
753
+ },
754
+ 'participant-2': {
755
+ type: 'Agent',
756
+ pType: 'Agent',
757
+ dn: '5551234567',
758
+ },
759
+ 'participant-3': {
760
+ type: 'Agent',
761
+ pType: 'dn',
762
+ dn: '5551234567',
763
+ id: 'correct-agent',
764
+ },
765
+ },
766
+ };
767
+
768
+ const result = Utils.getDestAgentIdForCBT(interaction, '5551234567');
769
+ expect(result).toBe('participant-3');
770
+ });
771
+
772
+ it('should handle case-insensitive participant type comparison', () => {
773
+ const interaction: any = {
774
+ participants: {
775
+ 'agent-uuid': {
776
+ type: 'Agent',
777
+ pType: 'DN', // Uppercase (dial number)
778
+ dn: '5551234567',
779
+ },
780
+ },
781
+ };
782
+
783
+ const result = Utils.getDestAgentIdForCBT(interaction, '5551234567');
784
+ expect(result).toBe('agent-uuid');
785
+ });
786
+ });
787
+
788
+ describe('calculateDestAgentId', () => {
789
+ const currentAgentId = 'agent-123';
790
+
791
+ it('should return destAgentIdCBT when found', () => {
792
+ const interaction: any = {
793
+ media: {
794
+ consult: {
795
+ mType: 'consult',
796
+ participants: [currentAgentId, '5551234567'],
797
+ },
798
+ },
799
+ participants: {
800
+ 'agent-uuid-456': {
801
+ type: 'Agent',
802
+ pType: 'dn',
803
+ dn: '5551234567',
804
+ id: 'agent-uuid-456',
805
+ },
806
+ },
807
+ };
808
+
809
+ const result = Utils.calculateDestAgentId(interaction, currentAgentId);
810
+ expect(result).toBe('agent-uuid-456');
811
+ });
812
+
813
+ it('should return participant id for regular agent when not CBT', () => {
814
+ const consultedAgentId = 'agent-456';
815
+ const interaction: any = {
816
+ media: {
817
+ consult: {
818
+ mType: 'consult',
819
+ participants: [currentAgentId, consultedAgentId],
820
+ },
821
+ },
822
+ participants: {
823
+ [consultedAgentId]: {
824
+ type: 'Agent',
825
+ id: consultedAgentId,
826
+ },
827
+ },
828
+ };
829
+
830
+ const result = Utils.calculateDestAgentId(interaction, currentAgentId);
831
+ expect(result).toBe(consultedAgentId);
832
+ });
833
+
834
+ it('should return epId for EpDn type participants', () => {
835
+ const consultedAgentId = 'epdn-456';
836
+ const interaction: any = {
837
+ media: {
838
+ consult: {
839
+ mType: 'consult',
840
+ participants: [currentAgentId, consultedAgentId],
841
+ },
842
+ },
843
+ participants: {
844
+ [consultedAgentId]: {
845
+ type: 'EpDn',
846
+ id: consultedAgentId,
847
+ epId: 'entry-point-id-789',
848
+ },
849
+ },
850
+ };
851
+
852
+ const result = Utils.calculateDestAgentId(interaction, currentAgentId);
853
+ expect(result).toBe('entry-point-id-789');
854
+ });
855
+
856
+ it('should return undefined when no consulting agent found', () => {
857
+ const interaction: any = {
858
+ media: {
859
+ mainCall: {
860
+ mType: 'mainCall',
861
+ participants: [currentAgentId],
862
+ },
863
+ },
864
+ participants: {},
865
+ };
866
+
867
+ const result = Utils.calculateDestAgentId(interaction, currentAgentId);
868
+ expect(result).toBeUndefined();
869
+ });
870
+
871
+ it('should handle CBT scenario when phone number is not a direct participant key', () => {
872
+ const interaction: any = {
873
+ media: {
874
+ consult: {
875
+ mType: 'consult',
876
+ participants: [currentAgentId, '5551234567'], // Phone number in media
877
+ },
878
+ },
879
+ participants: {
880
+ // Note: '5551234567' is NOT a key - this is CBT
881
+ 'agent-uuid-cbt': {
882
+ type: 'Agent',
883
+ pType: 'dn',
884
+ dn: '5551234567', // Found by matching DN
885
+ id: 'agent-uuid-cbt',
886
+ },
887
+ },
888
+ };
889
+
890
+ const result = Utils.calculateDestAgentId(interaction, currentAgentId);
891
+ expect(result).toBe('agent-uuid-cbt'); // Returns the CBT agent
892
+ });
893
+ });
894
+
895
+ describe('isValidDialNumber', () => {
896
+ const anyFormatEntry = {
897
+ name: 'Any Format',
898
+ prefix: '',
899
+ regex: '([0-9a-zA-Z]+[-._])*[0-9a-zA-Z]+',
900
+ strippedChars: '( )-',
901
+ };
902
+
903
+ const usOnlyEntry = {
904
+ name: 'US',
905
+ prefix: '1',
906
+ regex: '1[0-9]{3}[2-9][0-9]{6}([,]{1,10}[0-9]+){0,1}',
907
+ strippedChars: '( )-',
908
+ };
909
+
910
+ describe('with multiple dial plan entries (Any Format + US)', () => {
911
+ const dialPlanEntries = [anyFormatEntry, usOnlyEntry];
912
+
913
+ it('should return true for a valid US phone number', () => {
914
+ const result = Utils.isValidDialNumber('12223334567', dialPlanEntries);
915
+ expect(result).toBe(true);
916
+ });
917
+
918
+ it('should return true for a UK phone number', () => {
919
+ const result = Utils.isValidDialNumber('+442030484377', dialPlanEntries);
920
+ expect(result).toBe(true);
921
+ });
922
+
923
+ it('should return true for a European number', () => {
924
+ const result = Utils.isValidDialNumber('6955577166', dialPlanEntries);
925
+ expect(result).toBe(true);
926
+ });
927
+ });
928
+
929
+ describe('with US-only dial plan entry', () => {
930
+ const dialPlanEntries = [usOnlyEntry];
931
+
932
+ it('should return true for a valid US phone number', () => {
933
+ const result = Utils.isValidDialNumber('12223334567', dialPlanEntries);
934
+ expect(result).toBe(true);
935
+ });
936
+
937
+ it('should return false for a UK phone number', () => {
938
+ const result = Utils.isValidDialNumber('+442030484377', dialPlanEntries);
939
+ expect(result).toBe(false);
940
+ });
941
+
942
+ it('should return false for an invalid US number format', () => {
943
+ const result = Utils.isValidDialNumber('1234567890', dialPlanEntries);
944
+ expect(result).toBe(false);
945
+ });
946
+ });
947
+
948
+ describe('with empty dial plan entries (defers to server)', () => {
949
+ it('should return true for any dial number', () => {
950
+ expect(Utils.isValidDialNumber('12223334567', [])).toBe(true);
951
+ });
952
+
953
+ it('should return true for a UK phone number', () => {
954
+ expect(Utils.isValidDialNumber('+442030484377', [])).toBe(true);
955
+ });
956
+
957
+ it('should return true for a European number', () => {
958
+ expect(Utils.isValidDialNumber('6955577166', [])).toBe(true);
959
+ });
960
+ });
961
+
962
+ describe('strippedChars handling', () => {
963
+ it('should strip characters before regex matching', () => {
964
+ const strictEntry = {
965
+ name: 'Digits Only',
966
+ prefix: '',
967
+ regex: '^[0-9]{10,15}$',
968
+ strippedChars: '( )-+',
969
+ };
970
+ const result = Utils.isValidDialNumber('+44 (203) 048-4377', [strictEntry]);
971
+ expect(result).toBe(true);
972
+ });
973
+
974
+ it('should handle entries with no strippedChars', () => {
975
+ const noStripEntry = {
976
+ name: 'No Strip',
977
+ prefix: '',
978
+ regex: '^[0-9]+$',
979
+ strippedChars: '',
980
+ };
981
+ expect(Utils.isValidDialNumber('12345', [noStripEntry])).toBe(true);
982
+ expect(Utils.isValidDialNumber('+12345', [noStripEntry])).toBe(false);
983
+ });
984
+ });
985
+
986
+ describe('empty or undefined dial number', () => {
987
+ it('should return false and log warning for undefined dial number', () => {
988
+ const result = Utils.isValidDialNumber(undefined as any, [anyFormatEntry]);
989
+ expect(result).toBe(false);
990
+ expect(LoggerProxy.warn).toHaveBeenCalledWith(
991
+ 'Dial number is empty or undefined.',
992
+ expect.objectContaining({module: 'Utils', method: 'isValidDialNumber'})
993
+ );
994
+ });
995
+
996
+ it('should return false and log warning for empty string dial number', () => {
997
+ const result = Utils.isValidDialNumber('', [anyFormatEntry]);
998
+ expect(result).toBe(false);
999
+ expect(LoggerProxy.warn).toHaveBeenCalledWith(
1000
+ 'Dial number is empty or undefined.',
1001
+ expect.objectContaining({module: 'Utils', method: 'isValidDialNumber'})
1002
+ );
1003
+ });
1004
+ });
1005
+
1006
+ describe('invalid regex handling', () => {
1007
+ it('should return false and log warning for invalid regex pattern', () => {
1008
+ const badEntry = {
1009
+ name: 'Bad Regex',
1010
+ prefix: '',
1011
+ regex: '[invalid(',
1012
+ strippedChars: '',
1013
+ };
1014
+ const result = Utils.isValidDialNumber('12345', [badEntry]);
1015
+ expect(result).toBe(false);
1016
+ expect(LoggerProxy.warn).toHaveBeenCalledWith(
1017
+ expect.stringContaining('Failed to validate dial number against entry "Bad Regex"'),
1018
+ expect.objectContaining({module: 'Utils', method: 'isValidDialNumber'})
1019
+ );
1020
+ });
1021
+ });
1022
+ });
1023
+
1024
+ describe('stripDialPlanChars', () => {
1025
+ it('should remove specified characters from input', () => {
1026
+ expect(Utils.stripDialPlanChars('+44 (203) 048-4377', '( )-+')).toBe('442030484377');
1027
+ });
1028
+
1029
+ it('should return input unchanged when strippedChars is empty', () => {
1030
+ expect(Utils.stripDialPlanChars('+442030484377', '')).toBe('+442030484377');
1031
+ });
1032
+
1033
+ it('should return input unchanged when strippedChars is null/undefined', () => {
1034
+ expect(Utils.stripDialPlanChars('12345', null as any)).toBe('12345');
1035
+ expect(Utils.stripDialPlanChars('12345', undefined as any)).toBe('12345');
1036
+ });
1037
+ });
1038
+
673
1039
  });
@@ -78,6 +78,14 @@ describe('WebSocketManager', () => {
78
78
 
79
79
  mockWebex = {
80
80
  request: jest.fn(),
81
+ credentials: {
82
+ getOrgId: jest.fn().mockReturnValue('test-org-id'),
83
+ },
84
+ internal: {
85
+ services: {
86
+ isIntegrationEnvironment: jest.fn().mockReturnValue(true), // INT environment by default
87
+ },
88
+ },
81
89
  } as unknown as WebexSDK;
82
90
 
83
91
  mockWorker = {
@@ -111,13 +119,15 @@ describe('WebSocketManager', () => {
111
119
  expect(webSocketManager).toBeDefined();
112
120
  });
113
121
 
114
- it('should register and connect to WebSocket', async () => {
122
+ it('should register and connect to WebSocket with X-ORGANIZATION-ID header for INT environment', async () => {
115
123
  const subscribeResponse = {
116
124
  body: {
117
125
  webSocketUrl: 'wss://fake-url',
118
126
  },
119
127
  };
120
128
 
129
+ // Mock INT environment (services.isIntegrationEnvironment returns true)
130
+ (mockWebex.internal.services.isIntegrationEnvironment as jest.Mock).mockReturnValue(true);
121
131
  (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
122
132
 
123
133
  await webSocketManager.initWebSocket({body: fakeSubscribeRequest, resource: SUBSCRIBE_API});
@@ -127,6 +137,7 @@ describe('WebSocketManager', () => {
127
137
  resource: SUBSCRIBE_API,
128
138
  method: 'POST',
129
139
  body: fakeSubscribeRequest,
140
+ headers: {'X-ORGANIZATION-ID': 'test-org-id'},
130
141
  });
131
142
  });
132
143
 
@@ -152,6 +163,84 @@ describe('WebSocketManager', () => {
152
163
  });
153
164
  });
154
165
 
166
+ it('should register and connect to WebSocket without X-ORGANIZATION-ID header for production environment', async () => {
167
+ const subscribeResponse = {
168
+ body: {
169
+ webSocketUrl: 'wss://fake-url',
170
+ },
171
+ };
172
+ // Mock production environment (services.isIntegrationEnvironment returns false)
173
+ (mockWebex.internal.services.isIntegrationEnvironment as jest.Mock).mockReturnValue(false);
174
+ (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
175
+
176
+ // Create new WebSocketManager instance with production mock
177
+ webSocketManager = new WebSocketManager({ webex: mockWebex });
178
+
179
+ setTimeout(() => {
180
+ MockWebSocket.inst.onopen();
181
+ MockWebSocket.inst.onmessage({ data: JSON.stringify({ type: "Welcome" }) });
182
+ }, 1);
183
+
184
+ await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
185
+
186
+ expect(mockWebex.request).toHaveBeenCalledWith({
187
+ service: WCC_API_GATEWAY,
188
+ resource: SUBSCRIBE_API,
189
+ method: 'POST',
190
+ body: fakeSubscribeRequest,
191
+ headers: undefined,
192
+ });
193
+ });
194
+
195
+ it('should not send X-ORGANIZATION-ID header when services.isIntegrationEnvironment is not available', async () => {
196
+ const subscribeResponse = {
197
+ body: {
198
+ webSocketUrl: 'wss://fake-url',
199
+ },
200
+ };
201
+
202
+ // Mock services not available (defaults to production behavior)
203
+ (mockWebex as any).internal = undefined;
204
+ (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
205
+
206
+ webSocketManager = new WebSocketManager({ webex: mockWebex });
207
+
208
+ setTimeout(() => {
209
+ MockWebSocket.inst.onopen();
210
+ MockWebSocket.inst.onmessage({ data: JSON.stringify({ type: "Welcome" }) });
211
+ }, 1);
212
+
213
+ await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
214
+
215
+ expect(mockWebex.request).toHaveBeenCalledWith({
216
+ service: WCC_API_GATEWAY,
217
+ resource: SUBSCRIBE_API,
218
+ method: 'POST',
219
+ body: fakeSubscribeRequest,
220
+ headers: undefined,
221
+ });
222
+ });
223
+
224
+ it('should log error and throw when register API fails in initWebSocket', async () => {
225
+ const error = new Error('Register API failed');
226
+
227
+ (mockWebex.request as jest.Mock).mockRejectedValueOnce(error);
228
+
229
+ await expect(
230
+ webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API })
231
+ ).rejects.toThrow(error);
232
+
233
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
234
+ `Register API Failed, Request to RoutingNotifs websocket registration API failed ${error}`,
235
+ { module: WEB_SOCKET_MANAGER_FILE, method: 'register' }
236
+ );
237
+
238
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
239
+ `[WebSocketStatus] | Error in registering Websocket ${error}`,
240
+ { module: WEB_SOCKET_MANAGER_FILE, method: 'initWebSocket' }
241
+ );
242
+ });
243
+
155
244
  it('should close WebSocket connection', async () => {
156
245
  const subscribeResponse = {
157
246
  body: {
@@ -3,7 +3,9 @@ import {WebSocketManager} from '../../../../../../src/services/core/websocket/We
3
3
  import {SubscribeRequest} from '../../../../../../src/types';
4
4
  import LoggerProxy from '../../../../../../src/logger-proxy';
5
5
  import {CONNECTIVITY_CHECK_INTERVAL} from '../../../../../../src/services/core/constants';
6
- import {CONNECTION_SERVICE_FILE} from '../../../../../../src/constants';
6
+ import { CONNECTION_SERVICE_FILE } from '../../../../../../src/constants';
7
+ import {SUBSCRIBE_API} from '../../../../../../src/services/constants';
8
+
7
9
 
8
10
  jest.mock('../../../../../../src/services/core/websocket/WebSocketManager');
9
11
  jest.mock('../../../../../../src/logger-proxy', () => ({
@@ -109,10 +111,7 @@ describe('ConnectionService', () => {
109
111
  'event=socketConnectionRetry | Trying to reconnect to websocket',
110
112
  {module: CONNECTION_SERVICE_FILE, method: 'handleSocketClose'}
111
113
  );
112
- expect(mockWebSocketManager.initWebSocket).toHaveBeenCalledWith({
113
- body: mockSubscribeRequest,
114
- resource: 'v1/notification/subscribe',
115
- });
114
+ expect(mockWebSocketManager.initWebSocket).toHaveBeenCalledWith({body: mockSubscribeRequest, resource: SUBSCRIBE_API});
116
115
  });
117
116
 
118
117
  describe('ConnectionService onPing', () => {