@app-connect/core 1.7.37 → 1.7.38

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 (286) hide show
  1. package/README.md +17 -16
  2. package/dist/connector/developerPortal.d.ts +6 -0
  3. package/{connector → dist/connector}/developerPortal.js +29 -12
  4. package/dist/connector/mock.d.ts +1 -0
  5. package/{connector → dist/connector}/mock.js +33 -17
  6. package/dist/connector/proxy/engine.d.ts +10 -0
  7. package/dist/connector/proxy/engine.js +222 -0
  8. package/dist/connector/proxy/index.d.ts +5 -0
  9. package/dist/connector/proxy/index.js +511 -0
  10. package/dist/connector/registry.d.ts +1 -0
  11. package/dist/connector/registry.js +243 -0
  12. package/dist/handlers/admin.d.ts +1 -0
  13. package/{handlers → dist/handlers}/admin.js +137 -78
  14. package/dist/handlers/appointment.d.ts +9 -0
  15. package/{handlers → dist/handlers}/appointment.js +58 -29
  16. package/dist/handlers/auth.d.ts +9 -0
  17. package/{handlers → dist/handlers}/auth.js +68 -40
  18. package/dist/handlers/calldown.d.ts +10 -0
  19. package/dist/handlers/calldown.js +134 -0
  20. package/dist/handlers/contact.d.ts +1 -0
  21. package/{handlers → dist/handlers}/contact.js +61 -39
  22. package/dist/handlers/disposition.d.ts +1 -0
  23. package/{handlers → dist/handlers}/disposition.js +20 -10
  24. package/dist/handlers/log.d.ts +1 -0
  25. package/{handlers → dist/handlers}/log.js +91 -124
  26. package/dist/handlers/managedAuth.d.ts +1 -0
  27. package/{handlers → dist/handlers}/managedAuth.js +134 -61
  28. package/dist/handlers/managedOAuth.d.ts +13 -0
  29. package/{handlers → dist/handlers}/managedOAuth.js +102 -30
  30. package/dist/handlers/plugin.d.ts +1 -0
  31. package/{handlers → dist/handlers}/plugin.js +59 -27
  32. package/dist/handlers/user.d.ts +1 -0
  33. package/{handlers → dist/handlers}/user.js +45 -27
  34. package/dist/index.d.ts +1 -0
  35. package/{index.js → dist/index.js} +538 -449
  36. package/dist/jest.config.d.ts +1 -0
  37. package/dist/jest.config.js +65 -0
  38. package/dist/lib/analytics.d.ts +4 -0
  39. package/{lib → dist/lib}/analytics.js +8 -6
  40. package/dist/lib/authSession.d.ts +5 -0
  41. package/{lib → dist/lib}/authSession.js +15 -43
  42. package/dist/lib/cacheCleanup.d.ts +4 -0
  43. package/{lib → dist/lib}/cacheCleanup.js +5 -17
  44. package/dist/lib/callLogComposer.d.ts +109 -0
  45. package/{lib → dist/lib}/callLogComposer.js +157 -197
  46. package/dist/lib/callLogLookup.d.ts +6 -0
  47. package/{lib → dist/lib}/callLogLookup.js +12 -34
  48. package/dist/lib/constants.d.ts +6 -0
  49. package/dist/lib/constants.js +9 -0
  50. package/dist/lib/debugTracer.d.ts +17 -0
  51. package/{lib → dist/lib}/debugTracer.js +7 -83
  52. package/dist/lib/encode.d.ts +3 -0
  53. package/dist/lib/encode.js +50 -0
  54. package/dist/lib/errorHandler.d.ts +7 -0
  55. package/dist/lib/errorHandler.js +161 -0
  56. package/dist/lib/generalErrorMessage.d.ts +7 -0
  57. package/{lib → dist/lib}/generalErrorMessage.js +6 -6
  58. package/dist/lib/jwt.d.ts +4 -0
  59. package/dist/lib/jwt.js +18 -0
  60. package/dist/lib/logger.d.ts +24 -0
  61. package/dist/lib/logger.js +126 -0
  62. package/dist/lib/migrateCallLogsSchema.d.ts +32 -0
  63. package/{lib → dist/lib}/migrateCallLogsSchema.js +75 -160
  64. package/dist/lib/oauth.d.ts +4 -0
  65. package/{lib → dist/lib}/oauth.js +157 -160
  66. package/dist/lib/ringcentral.d.ts +24 -0
  67. package/dist/lib/ringcentral.js +226 -0
  68. package/dist/lib/s3ErrorLogReport.d.ts +4 -0
  69. package/{lib → dist/lib}/s3ErrorLogReport.js +11 -23
  70. package/dist/lib/sharedSMSComposer.d.ts +7 -0
  71. package/{lib → dist/lib}/sharedSMSComposer.js +21 -107
  72. package/dist/lib/util.d.ts +7 -0
  73. package/{lib → dist/lib}/util.js +9 -20
  74. package/dist/mcp/lib/oauthError.d.ts +1 -0
  75. package/dist/mcp/lib/oauthError.js +25 -0
  76. package/dist/mcp/lib/validator.d.ts +1 -0
  77. package/{mcp → dist/mcp}/lib/validator.js +16 -20
  78. package/dist/mcp/lib/widgetSessionToken.d.ts +1 -0
  79. package/dist/mcp/lib/widgetSessionToken.js +46 -0
  80. package/dist/mcp/mcpHandler.d.ts +8 -0
  81. package/{mcp → dist/mcp}/mcpHandler.js +98 -88
  82. package/dist/mcp/tools/cancelAppointment.d.ts +1 -0
  83. package/{mcp → dist/mcp}/tools/cancelAppointment.js +7 -16
  84. package/dist/mcp/tools/checkAuthStatus.d.ts +1 -0
  85. package/{mcp → dist/mcp}/tools/checkAuthStatus.js +21 -14
  86. package/dist/mcp/tools/confirmAppointment.d.ts +1 -0
  87. package/{mcp → dist/mcp}/tools/confirmAppointment.js +7 -16
  88. package/dist/mcp/tools/createAppointment.d.ts +1 -0
  89. package/{mcp → dist/mcp}/tools/createAppointment.js +7 -19
  90. package/dist/mcp/tools/createCallLog.d.ts +1 -0
  91. package/{mcp → dist/mcp}/tools/createCallLog.js +18 -29
  92. package/dist/mcp/tools/createContact.d.ts +1 -0
  93. package/{mcp → dist/mcp}/tools/createContact.js +8 -20
  94. package/dist/mcp/tools/createMessageLog.d.ts +1 -0
  95. package/{mcp → dist/mcp}/tools/createMessageLog.js +14 -26
  96. package/dist/mcp/tools/doAuth.d.ts +1 -0
  97. package/{mcp → dist/mcp}/tools/doAuth.js +17 -13
  98. package/dist/mcp/tools/findContactByName.d.ts +1 -0
  99. package/{mcp → dist/mcp}/tools/findContactByName.js +9 -15
  100. package/dist/mcp/tools/findContactByPhone.d.ts +1 -0
  101. package/{mcp → dist/mcp}/tools/findContactByPhone.js +8 -15
  102. package/dist/mcp/tools/getCallLog.d.ts +1 -0
  103. package/{mcp → dist/mcp}/tools/getCallLog.js +12 -21
  104. package/dist/mcp/tools/getGoogleFilePicker.d.ts +1 -0
  105. package/{mcp → dist/mcp}/tools/getGoogleFilePicker.js +9 -18
  106. package/dist/mcp/tools/getHelp.d.ts +1 -0
  107. package/{mcp → dist/mcp}/tools/getHelp.js +5 -6
  108. package/dist/mcp/tools/getPublicConnectors.d.ts +1 -0
  109. package/{mcp → dist/mcp}/tools/getPublicConnectors.js +19 -18
  110. package/dist/mcp/tools/getSessionInfo.d.ts +1 -0
  111. package/{mcp → dist/mcp}/tools/getSessionInfo.js +10 -16
  112. package/dist/mcp/tools/index.d.ts +8 -0
  113. package/dist/mcp/tools/index.js +50 -0
  114. package/dist/mcp/tools/listAppointments.d.ts +1 -0
  115. package/{mcp → dist/mcp}/tools/listAppointments.js +7 -19
  116. package/dist/mcp/tools/logout.d.ts +1 -0
  117. package/{mcp → dist/mcp}/tools/logout.js +16 -14
  118. package/dist/mcp/tools/rcGetCallLogs.d.ts +1 -0
  119. package/{mcp → dist/mcp}/tools/rcGetCallLogs.js +10 -8
  120. package/dist/mcp/tools/updateAppointment.d.ts +1 -0
  121. package/{mcp → dist/mcp}/tools/updateAppointment.js +17 -22
  122. package/dist/mcp/tools/updateCallLog.d.ts +1 -0
  123. package/{mcp → dist/mcp}/tools/updateCallLog.js +16 -26
  124. package/dist/models/accountDataModel.d.ts +3 -0
  125. package/dist/models/accountDataModel.js +39 -0
  126. package/dist/models/adminConfigModel.d.ts +2 -0
  127. package/dist/models/adminConfigModel.js +39 -0
  128. package/dist/models/cacheModel.d.ts +2 -0
  129. package/{models → dist/models}/cacheModel.js +7 -3
  130. package/dist/models/callDownListModel.d.ts +2 -0
  131. package/{models → dist/models}/callDownListModel.js +8 -4
  132. package/dist/models/callLogModel.d.ts +2 -0
  133. package/dist/models/callLogModel.js +43 -0
  134. package/dist/models/dynamo/connectorSchema.d.ts +2 -0
  135. package/dist/models/dynamo/connectorSchema.js +141 -0
  136. package/dist/models/dynamo/lockSchema.d.ts +2 -0
  137. package/{models → dist/models}/dynamo/lockSchema.js +5 -7
  138. package/dist/models/dynamo/noteCacheSchema.d.ts +2 -0
  139. package/{models → dist/models}/dynamo/noteCacheSchema.js +4 -6
  140. package/dist/models/llmSessionModel.d.ts +2 -0
  141. package/dist/models/llmSessionModel.js +21 -0
  142. package/dist/models/messageLogModel.d.ts +2 -0
  143. package/{models → dist/models}/messageLogModel.js +8 -4
  144. package/dist/models/sequelize.d.ts +2 -0
  145. package/dist/models/sequelize.js +68 -0
  146. package/dist/models/userModel.d.ts +2 -0
  147. package/dist/models/userModel.js +49 -0
  148. package/dist/package.json +100 -0
  149. package/dist/releaseNotes.json +1222 -0
  150. package/dist/tsconfig.json +14 -0
  151. package/dist/types/admin.d.ts +141 -0
  152. package/dist/types/analytics.d.ts +14 -0
  153. package/dist/types/appointment.d.ts +50 -0
  154. package/dist/types/auth.d.ts +137 -0
  155. package/dist/types/authSession.d.ts +35 -0
  156. package/dist/types/cacheCleanup.d.ts +12 -0
  157. package/dist/types/calldown.d.ts +54 -0
  158. package/dist/types/common.d.ts +46 -0
  159. package/dist/types/connector.d.ts +165 -0
  160. package/dist/types/contact.d.ts +125 -0
  161. package/dist/types/errorHandler.d.ts +60 -0
  162. package/dist/types/index.d.ts +76 -0
  163. package/dist/types/json.d.ts +9 -0
  164. package/dist/types/jwt.d.ts +8 -0
  165. package/dist/types/logging.d.ts +224 -0
  166. package/dist/types/managedAuth.d.ts +114 -0
  167. package/dist/types/managedOAuth.d.ts +59 -0
  168. package/dist/types/manifest.d.ts +120 -0
  169. package/dist/types/migration.d.ts +15 -0
  170. package/dist/types/plugin.d.ts +74 -0
  171. package/dist/types/ringcentral.d.ts +61 -0
  172. package/dist/types/s3ErrorLogReport.d.ts +16 -0
  173. package/dist/types/sharedSMSComposer.d.ts +105 -0
  174. package/dist/types/user.d.ts +46 -0
  175. package/dist/types/util.d.ts +7 -0
  176. package/package.json +34 -6
  177. package/releaseNotes.json +8 -0
  178. package/.env.test +0 -5
  179. package/connector/proxy/engine.js +0 -165
  180. package/connector/proxy/index.js +0 -500
  181. package/connector/registry.js +0 -252
  182. package/docs/README.md +0 -50
  183. package/docs/architecture.md +0 -94
  184. package/docs/connectors.md +0 -116
  185. package/docs/handlers.md +0 -128
  186. package/docs/libraries.md +0 -98
  187. package/docs/models.md +0 -146
  188. package/docs/routes.md +0 -119
  189. package/docs/tests.md +0 -73
  190. package/handlers/calldown.js +0 -99
  191. package/jest.config.js +0 -57
  192. package/lib/constants.js +0 -9
  193. package/lib/encode.js +0 -30
  194. package/lib/errorHandler.js +0 -218
  195. package/lib/jwt.js +0 -18
  196. package/lib/logger.js +0 -190
  197. package/lib/ringcentral.js +0 -267
  198. package/mcp/README.md +0 -420
  199. package/mcp/tools/index.js +0 -51
  200. package/mcp/ui/App/App.tsx +0 -358
  201. package/mcp/ui/App/components/AuthInfoForm.tsx +0 -113
  202. package/mcp/ui/App/components/AuthSuccess.tsx +0 -22
  203. package/mcp/ui/App/components/ConnectorList.tsx +0 -82
  204. package/mcp/ui/App/components/DebugPanel.tsx +0 -43
  205. package/mcp/ui/App/components/OAuthConnect.tsx +0 -270
  206. package/mcp/ui/App/lib/callTool.ts +0 -130
  207. package/mcp/ui/App/lib/debugLog.ts +0 -41
  208. package/mcp/ui/App/lib/developerPortal.ts +0 -111
  209. package/mcp/ui/App/main.css +0 -6
  210. package/mcp/ui/App/root.tsx +0 -13
  211. package/mcp/ui/dist/index.html +0 -53
  212. package/mcp/ui/vite.config.ts +0 -16
  213. package/models/accountDataModel.js +0 -34
  214. package/models/adminConfigModel.js +0 -35
  215. package/models/callLogModel.js +0 -39
  216. package/models/dynamo/connectorSchema.js +0 -146
  217. package/models/llmSessionModel.js +0 -17
  218. package/models/sequelize.js +0 -17
  219. package/models/userModel.js +0 -45
  220. package/test/connector/developerPortal.test.js +0 -166
  221. package/test/connector/mock.test.js +0 -131
  222. package/test/connector/proxy/engine.test.js +0 -211
  223. package/test/connector/proxy/index.test.js +0 -525
  224. package/test/connector/proxy/sample.json +0 -162
  225. package/test/connector/registry.test.js +0 -416
  226. package/test/handlers/admin.test.js +0 -960
  227. package/test/handlers/appointment.test.js +0 -260
  228. package/test/handlers/auth.test.js +0 -1096
  229. package/test/handlers/calldown.test.js +0 -310
  230. package/test/handlers/contact.test.js +0 -1014
  231. package/test/handlers/disposition.test.js +0 -396
  232. package/test/handlers/log.test.js +0 -1847
  233. package/test/handlers/managedAuth.test.js +0 -458
  234. package/test/handlers/managedOAuth.test.js +0 -262
  235. package/test/handlers/plugin.test.js +0 -429
  236. package/test/handlers/user.test.js +0 -381
  237. package/test/index.test.js +0 -301
  238. package/test/lib/analytics.test.js +0 -146
  239. package/test/lib/authSession.test.js +0 -173
  240. package/test/lib/cacheCleanup.test.js +0 -127
  241. package/test/lib/callLogComposer.test.js +0 -1231
  242. package/test/lib/debugTracer.test.js +0 -328
  243. package/test/lib/encode.test.js +0 -59
  244. package/test/lib/errorHandler.test.js +0 -246
  245. package/test/lib/generalErrorMessage.test.js +0 -82
  246. package/test/lib/jwt.test.js +0 -176
  247. package/test/lib/logger.test.js +0 -206
  248. package/test/lib/oauth.test.js +0 -359
  249. package/test/lib/ringcentral.test.js +0 -467
  250. package/test/lib/s3ErrorLogReport.test.js +0 -187
  251. package/test/lib/sharedSMSComposer.test.js +0 -1084
  252. package/test/lib/util.test.js +0 -329
  253. package/test/mcp/mcpHandler.test.js +0 -73
  254. package/test/mcp/mcpHandlerMore.test.js +0 -384
  255. package/test/mcp/tools/appointmentTools.test.js +0 -362
  256. package/test/mcp/tools/checkAuthStatus.test.js +0 -83
  257. package/test/mcp/tools/createCallLog.test.js +0 -436
  258. package/test/mcp/tools/createContact.test.js +0 -58
  259. package/test/mcp/tools/createMessageLog.test.js +0 -595
  260. package/test/mcp/tools/doAuth.test.js +0 -113
  261. package/test/mcp/tools/findContactByName.test.js +0 -275
  262. package/test/mcp/tools/findContactByPhone.test.js +0 -296
  263. package/test/mcp/tools/getCallLog.test.js +0 -298
  264. package/test/mcp/tools/getGoogleFilePicker.test.js +0 -281
  265. package/test/mcp/tools/getPublicConnectors.test.js +0 -107
  266. package/test/mcp/tools/getSessionInfo.test.js +0 -127
  267. package/test/mcp/tools/logout.test.js +0 -233
  268. package/test/mcp/tools/rcGetCallLogs.test.js +0 -56
  269. package/test/mcp/tools/updateCallLog.test.js +0 -360
  270. package/test/models/accountDataModel.test.js +0 -98
  271. package/test/models/callDownListModel.test.js +0 -125
  272. package/test/models/dynamo/connectorSchema.test.js +0 -189
  273. package/test/models/dynamo/lockSchema.test.js +0 -37
  274. package/test/models/dynamo/noteCacheSchema.test.js +0 -45
  275. package/test/models/llmSessionModel.test.js +0 -91
  276. package/test/models/models.test.js +0 -660
  277. package/test/routes/calldownRoutes.test.js +0 -224
  278. package/test/routes/coreRouterBroadRoutes.test.js +0 -855
  279. package/test/routes/dispositionRoutes.test.js +0 -192
  280. package/test/routes/managedAuthRoutes.test.js +0 -386
  281. package/test/routes/pluginRoutes.test.js +0 -262
  282. package/test/setup.js +0 -178
  283. /package/{mcp → dist/mcp}/ui/index.html +0 -0
  284. /package/{mcp → dist/mcp}/ui/package-lock.json +0 -0
  285. /package/{mcp → dist/mcp}/ui/package.json +0 -0
  286. /package/{mcp → dist/mcp}/ui/tsconfig.json +0 -0
@@ -1,1847 +0,0 @@
1
- // Use in-memory SQLite for isolated model tests
2
- jest.mock('../../models/sequelize', () => {
3
- const { Sequelize } = require('sequelize');
4
- return {
5
- sequelize: new Sequelize({
6
- dialect: 'sqlite',
7
- storage: ':memory:',
8
- logging: false,
9
- }),
10
- };
11
- });
12
-
13
- jest.mock('../../connector/registry');
14
- jest.mock('../../lib/oauth');
15
- jest.mock('../../lib/callLogComposer');
16
- jest.mock('../../models/dynamo/noteCacheSchema', () => ({
17
- NoteCache: {
18
- get: jest.fn(),
19
- create: jest.fn()
20
- }
21
- }));
22
- jest.mock('../../models/dynamo/connectorSchema', () => ({
23
- Connector: {
24
- getProxyConfig: jest.fn()
25
- }
26
- }));
27
- jest.mock('axios');
28
-
29
- const logHandler = require('../../handlers/log');
30
- const { CallLogModel } = require('../../models/callLogModel');
31
- const { MessageLogModel } = require('../../models/messageLogModel');
32
- const { UserModel } = require('../../models/userModel');
33
- const { AccountDataModel } = require('../../models/accountDataModel');
34
- const { CacheModel } = require('../../models/cacheModel');
35
- const connectorRegistry = require('../../connector/registry');
36
- const oauth = require('../../lib/oauth');
37
- const { composeCallLog } = require('../../lib/callLogComposer');
38
- const { NoteCache } = require('../../models/dynamo/noteCacheSchema');
39
- const axios = require('axios');
40
- const { sequelize } = require('../../models/sequelize');
41
- const { getHashValue } = require('../../lib/util');
42
-
43
- describe('Log Handler', () => {
44
- beforeAll(async () => {
45
- await CallLogModel.sync({ force: true });
46
- await MessageLogModel.sync({ force: true });
47
- await UserModel.sync({ force: true });
48
- await AccountDataModel.sync({ force: true });
49
- await CacheModel.sync({ force: true });
50
- });
51
-
52
- afterEach(async () => {
53
- await CallLogModel.destroy({ where: {} });
54
- await MessageLogModel.destroy({ where: {} });
55
- await UserModel.destroy({ where: {} });
56
- await AccountDataModel.destroy({ where: {} });
57
- await CacheModel.destroy({ where: {} });
58
- jest.clearAllMocks();
59
- });
60
-
61
- afterAll(async () => {
62
- await sequelize.close();
63
- });
64
-
65
- describe('createCallLog', () => {
66
- const mockUser = {
67
- id: 'test-user-id',
68
- platform: 'testCRM',
69
- accessToken: 'test-access-token',
70
- rcAccountId: '12345',
71
- platformAdditionalInfo: {}
72
- };
73
-
74
- const mockIncomingData = {
75
- logInfo: {
76
- sessionId: 'session-123',
77
- telephonySessionId: 'tel-session-123',
78
- id: 'call-id-123',
79
- direction: 'Outbound',
80
- startTime: new Date().toISOString(),
81
- duration: 120,
82
- result: 'Completed',
83
- from: { phoneNumber: '+1234567890' },
84
- to: { phoneNumber: '+0987654321' },
85
- recording: { link: 'https://recording.link' }
86
- },
87
- contactId: 'contact-123',
88
- contactType: 'Contact',
89
- contactName: 'Test Contact',
90
- note: 'Test note',
91
- aiNote: '',
92
- transcript: '',
93
- additionalSubmission: {}
94
- };
95
-
96
- test('should return warning when call log already exists for session', async () => {
97
- // Arrange
98
- await CallLogModel.create({
99
- id: 'existing-log',
100
- sessionId: 'session-123',
101
- platform: 'testCRM',
102
- thirdPartyLogId: 'third-party-123',
103
- userId: 'test-user-id'
104
- });
105
-
106
- // Act
107
- const result = await logHandler.createCallLog({
108
- platform: 'testCRM',
109
- userId: 'test-user-id',
110
- incomingData: mockIncomingData,
111
- hashedAccountId: 'hashed-123',
112
- isFromSSCL: false
113
- });
114
-
115
- // Assert
116
- expect(result.successful).toBe(false);
117
- expect(result.returnMessage.messageType).toBe('warning');
118
- expect(result.returnMessage.message).toContain('Existing log for session');
119
- });
120
-
121
- test('should allow same session when extensionNumber is different', async () => {
122
- // Arrange
123
- await UserModel.create(mockUser);
124
- await CallLogModel.create({
125
- id: 'tel-session-123',
126
- sessionId: 'session-123',
127
- extensionNumber: '101',
128
- platform: 'testCRM',
129
- thirdPartyLogId: 'third-party-101',
130
- userId: 'test-user-id'
131
- });
132
-
133
- const mockConnector = {
134
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
135
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
136
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
137
- createCallLog: jest.fn().mockResolvedValue({
138
- logId: 'new-log-102',
139
- returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
140
- })
141
- };
142
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
143
- composeCallLog.mockReturnValue('Composed log details');
144
-
145
- const incomingData = {
146
- ...mockIncomingData,
147
- logInfo: {
148
- ...mockIncomingData.logInfo,
149
- extensionNumber: '102'
150
- }
151
- };
152
-
153
- // Act
154
- const result = await logHandler.createCallLog({
155
- platform: 'testCRM',
156
- userId: 'test-user-id',
157
- incomingData,
158
- hashedAccountId: 'hashed-123',
159
- isFromSSCL: false
160
- });
161
-
162
- // Assert
163
- expect(result.successful).toBe(true);
164
- const savedLog = await CallLogModel.findOne({
165
- where: {
166
- sessionId: 'session-123',
167
- extensionNumber: '102'
168
- }
169
- });
170
- expect(savedLog).not.toBeNull();
171
- expect(savedLog.thirdPartyLogId).toBe('new-log-102');
172
- });
173
-
174
- test('should block duplicate client log when SSCL already logged same hashed extension', async () => {
175
- // Arrange
176
- const hashedExtensionId = getHashValue('rc-ext-1', 'test-hash-key');
177
- await CallLogModel.create({
178
- id: 'tel-session-123',
179
- sessionId: 'session-123',
180
- extensionNumber: '',
181
- hashedExtensionId,
182
- platform: 'testCRM',
183
- thirdPartyLogId: 'sscl-log',
184
- userId: 'test-user-id'
185
- });
186
-
187
- const incomingData = {
188
- ...mockIncomingData,
189
- extensionNumber: '101',
190
- hashedExtensionId
191
- };
192
-
193
- // Act
194
- const result = await logHandler.createCallLog({
195
- platform: 'testCRM',
196
- userId: 'test-user-id',
197
- incomingData,
198
- hashedAccountId: 'hashed-123',
199
- isFromSSCL: false
200
- });
201
-
202
- // Assert
203
- expect(result.successful).toBe(false);
204
- expect(result.returnMessage.message).toContain('Existing log for session');
205
- });
206
-
207
- test('should derive hashed extension id from SSCL raw rcExtensionId when saving', async () => {
208
- // Arrange
209
- process.env.HASH_KEY = 'test-hash-key';
210
- await UserModel.create(mockUser);
211
-
212
- const mockConnector = {
213
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
214
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
215
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
216
- createCallLog: jest.fn().mockResolvedValue({
217
- logId: 'new-sscl-log',
218
- returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
219
- })
220
- };
221
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
222
- composeCallLog.mockReturnValue('Composed log details');
223
-
224
- const incomingData = {
225
- ...mockIncomingData,
226
- rcExtensionId: 'rc-ext-1'
227
- };
228
-
229
- // Act
230
- const result = await logHandler.createCallLog({
231
- platform: 'testCRM',
232
- userId: 'test-user-id',
233
- incomingData,
234
- hashedAccountId: 'hashed-123',
235
- isFromSSCL: true
236
- });
237
-
238
- // Assert
239
- expect(result.successful).toBe(true);
240
- const savedLog = await CallLogModel.findOne({
241
- where: {
242
- sessionId: 'session-123',
243
- hashedExtensionId: getHashValue('rc-ext-1', 'test-hash-key')
244
- }
245
- });
246
- expect(savedLog).not.toBeNull();
247
- expect(savedLog.thirdPartyLogId).toBe('new-sscl-log');
248
- });
249
-
250
- test('should return warning when user not found', async () => {
251
- // Act
252
- const result = await logHandler.createCallLog({
253
- platform: 'testCRM',
254
- userId: 'non-existent-user',
255
- incomingData: mockIncomingData,
256
- hashedAccountId: 'hashed-123',
257
- isFromSSCL: false
258
- });
259
-
260
- // Assert
261
- expect(result.successful).toBe(false);
262
- expect(result.returnMessage.message).toBe('User not found');
263
- });
264
-
265
- test('should return warning when user has no access token', async () => {
266
- // Arrange
267
- await UserModel.create({
268
- id: 'test-user-id',
269
- platform: 'testCRM',
270
- accessToken: null
271
- });
272
-
273
- // Act
274
- const result = await logHandler.createCallLog({
275
- platform: 'testCRM',
276
- userId: 'test-user-id',
277
- incomingData: mockIncomingData,
278
- hashedAccountId: 'hashed-123',
279
- isFromSSCL: false
280
- });
281
-
282
- // Assert
283
- expect(result.successful).toBe(false);
284
- expect(result.returnMessage.message).toBe('User not found');
285
- });
286
-
287
- test('should return warning when contact not found', async () => {
288
- // Arrange
289
- await UserModel.create(mockUser);
290
-
291
- const mockConnector = {
292
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
293
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
294
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
295
- createCallLog: jest.fn()
296
- };
297
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
298
- composeCallLog.mockReturnValue('Composed log details');
299
-
300
- const incomingDataNoContact = {
301
- ...mockIncomingData,
302
- contactId: null
303
- };
304
-
305
- // Act
306
- const result = await logHandler.createCallLog({
307
- platform: 'testCRM',
308
- userId: 'test-user-id',
309
- incomingData: incomingDataNoContact,
310
- hashedAccountId: 'hashed-123',
311
- isFromSSCL: false
312
- });
313
-
314
- // Assert
315
- expect(result.successful).toBe(false);
316
- expect(result.returnMessage.message).toContain('Contact not found for number');
317
- });
318
-
319
- test('should successfully create call log with apiKey auth', async () => {
320
- // Arrange
321
- await UserModel.create(mockUser);
322
-
323
- const mockConnector = {
324
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
325
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
326
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
327
- createCallLog: jest.fn().mockResolvedValue({
328
- logId: 'new-log-123',
329
- returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
330
- })
331
- };
332
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
333
- composeCallLog.mockReturnValue('Composed log details');
334
-
335
- // Act
336
- const result = await logHandler.createCallLog({
337
- platform: 'testCRM',
338
- userId: 'test-user-id',
339
- incomingData: mockIncomingData,
340
- hashedAccountId: 'hashed-123',
341
- isFromSSCL: false
342
- });
343
-
344
- // Assert
345
- expect(result.successful).toBe(true);
346
- expect(result.logId).toBe('new-log-123');
347
- expect(mockConnector.getBasicAuth).toHaveBeenCalledWith({ apiKey: 'test-access-token' });
348
- expect(mockConnector.createCallLog).toHaveBeenCalled();
349
-
350
- // Verify call log was saved to database
351
- const savedLog = await CallLogModel.findOne({ where: { sessionId: 'session-123' } });
352
- expect(savedLog).not.toBeNull();
353
- expect(savedLog.thirdPartyLogId).toBe('new-log-123');
354
- });
355
-
356
- test('should call plugin with Bearer auth and without query jwt token', async () => {
357
- await UserModel.create(mockUser);
358
- await AccountDataModel.create({
359
- rcAccountId: mockUser.rcAccountId,
360
- platformName: 'testPlugin',
361
- dataKey: 'pluginData',
362
- data: {
363
- name: 'plugin.sample',
364
- supportedLogTypes: ['call'],
365
- isAsync: false,
366
- endpointUrl: 'https://plugins.example.com/plugin/testPlugin',
367
- jwtToken: 'plugin-jwt-token'
368
- }
369
- });
370
-
371
- const mockConnector = {
372
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
373
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
374
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
375
- createCallLog: jest.fn().mockResolvedValue({
376
- logId: 'new-log-123',
377
- returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
378
- })
379
- };
380
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
381
- composeCallLog.mockReturnValue('Composed log details');
382
-
383
- axios.post.mockResolvedValue({
384
- data: {
385
- ...mockIncomingData,
386
- note: 'updated by plugin'
387
- },
388
- headers: {
389
- 'x-refreshed-jwt-token': 'refreshed-plugin-jwt'
390
- }
391
- });
392
-
393
- const result = await logHandler.createCallLog({
394
- platform: 'testCRM',
395
- userId: 'test-user-id',
396
- incomingData: mockIncomingData,
397
- hashedAccountId: 'hashed-123',
398
- isFromSSCL: false
399
- });
400
-
401
- expect(result.successful).toBe(true);
402
- expect(axios.post).toHaveBeenCalledWith(
403
- 'https://plugins.example.com/plugin/testPlugin',
404
- { data: mockIncomingData, config: null, hashedExtensionId: null },
405
- {
406
- headers: {
407
- Authorization: 'Bearer plugin-jwt-token'
408
- }
409
- }
410
- );
411
- });
412
-
413
- test('should create async task cache and pass task details to async call plugin after log creation', async () => {
414
- const originalAppServer = process.env.APP_SERVER;
415
- process.env.APP_SERVER = 'https://app.example.com';
416
- try {
417
- await UserModel.create(mockUser);
418
- await AccountDataModel.create({
419
- rcAccountId: mockUser.rcAccountId,
420
- platformName: 'asyncPlugin',
421
- dataKey: 'pluginData',
422
- data: {
423
- name: 'plugin.async',
424
- supportedLogTypes: ['call'],
425
- isAsync: true,
426
- endpointUrl: 'https://plugins.example.com/plugin/asyncPlugin',
427
- tokenSyncUrl: 'https://plugins.example.com/plugin/asyncPlugin/token',
428
- jwtToken: 'plugin-jwt-token'
429
- }
430
- });
431
-
432
- const mockConnector = {
433
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
434
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
435
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
436
- createCallLog: jest.fn().mockResolvedValue({
437
- logId: 'new-log-async',
438
- returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
439
- })
440
- };
441
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
442
- composeCallLog.mockReturnValue('Composed log details');
443
- axios.post.mockImplementation((url) => {
444
- if (url === 'https://plugins.example.com/plugin/asyncPlugin/token') {
445
- return Promise.resolve({
446
- headers: {
447
- 'x-refreshed-jwt-token': 'synced-plugin-jwt'
448
- }
449
- });
450
- }
451
- return Promise.resolve({ data: { accepted: true }, headers: {} });
452
- });
453
-
454
- const result = await logHandler.createCallLog({
455
- platform: 'testCRM',
456
- userId: 'test-user-id',
457
- incomingData: mockIncomingData,
458
- hashedAccountId: 'hashed-123',
459
- isFromSSCL: false
460
- });
461
-
462
- expect(result.successful).toBe(true);
463
- const cache = await CacheModel.findOne({
464
- where: {
465
- cacheKey: 'asyncPluginTask-asyncPlugin'
466
- }
467
- });
468
- expect(cache).not.toBeNull();
469
- expect(cache.status).toBe('pending');
470
- expect(cache.userId).toBe('test-user-id');
471
- expect(cache.data.pluginId).toBe('asyncPlugin');
472
- expect(cache.data.sessionId).toBe('session-123');
473
- expect(cache.data.thirdPartyLogId).toBe('new-log-async');
474
- expect(cache.expiry.getTime() - Date.now()).toBeGreaterThan(6 * 24 * 60 * 60 * 1000);
475
-
476
- const pluginCall = axios.post.mock.calls.find(([url]) => url === 'https://plugins.example.com/plugin/asyncPlugin');
477
- expect(pluginCall).toBeTruthy();
478
- expect(pluginCall[1]).toEqual({
479
- data: mockIncomingData,
480
- config: null,
481
- asyncTaskId: cache.id,
482
- callbackUrl: `https://app.example.com/plugin/async-callback/${cache.id}`,
483
- hashedExtensionId: null
484
- });
485
- expect(pluginCall[2]).toEqual({
486
- headers: {
487
- Authorization: 'Bearer synced-plugin-jwt'
488
- }
489
- });
490
- } finally {
491
- if (originalAppServer) {
492
- process.env.APP_SERVER = originalAppServer;
493
- } else {
494
- delete process.env.APP_SERVER;
495
- }
496
- }
497
- });
498
-
499
- test('should mark async plugin task failed when token sync URL is missing', async () => {
500
- await UserModel.create(mockUser);
501
- await AccountDataModel.create({
502
- rcAccountId: mockUser.rcAccountId,
503
- platformName: 'asyncPlugin',
504
- dataKey: 'pluginData',
505
- data: {
506
- name: 'plugin.async',
507
- supportedLogTypes: ['call'],
508
- isAsync: true,
509
- endpointUrl: 'https://plugins.example.com/plugin/asyncPlugin',
510
- jwtToken: 'plugin-jwt-token'
511
- }
512
- });
513
-
514
- const mockConnector = {
515
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
516
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
517
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
518
- createCallLog: jest.fn().mockResolvedValue({
519
- logId: 'new-log-async-failed',
520
- returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
521
- })
522
- };
523
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
524
- composeCallLog.mockReturnValue('Composed log details');
525
-
526
- const result = await logHandler.createCallLog({
527
- platform: 'testCRM',
528
- userId: 'test-user-id',
529
- incomingData: mockIncomingData,
530
- hashedAccountId: 'hashed-123',
531
- isFromSSCL: false
532
- });
533
-
534
- expect(result.successful).toBe(true);
535
- const cache = await CacheModel.findOne({
536
- where: {
537
- cacheKey: 'asyncPluginTask-asyncPlugin'
538
- }
539
- });
540
- expect(cache).not.toBeNull();
541
- expect(cache.status).toBe('failed');
542
- expect(cache.data.message).toBe('Plugin token sync URL is not set');
543
- expect(axios.post).not.toHaveBeenCalled();
544
- });
545
-
546
- test('should successfully create call log with oauth auth', async () => {
547
- // Arrange
548
- const oauthUser = { ...mockUser };
549
- await UserModel.create(oauthUser);
550
-
551
- const mockConnector = {
552
- getAuthType: jest.fn().mockResolvedValue('oauth'),
553
- getOauthInfo: jest.fn().mockResolvedValue({
554
- clientId: 'client-id',
555
- clientSecret: 'client-secret',
556
- accessTokenUri: 'https://token.url',
557
- authorizationUri: 'https://auth.url'
558
- }),
559
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
560
- createCallLog: jest.fn().mockResolvedValue({
561
- logId: 'oauth-log-123',
562
- returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
563
- })
564
- };
565
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
566
-
567
- const mockOAuthApp = {};
568
- oauth.getOAuthApp.mockReturnValue(mockOAuthApp);
569
- oauth.checkAndRefreshAccessToken.mockResolvedValue({
570
- ...oauthUser,
571
- accessToken: 'refreshed-token'
572
- });
573
- composeCallLog.mockReturnValue('Composed log details');
574
-
575
- // Act
576
- const result = await logHandler.createCallLog({
577
- platform: 'testCRM',
578
- userId: 'test-user-id',
579
- incomingData: mockIncomingData,
580
- hashedAccountId: 'hashed-123',
581
- isFromSSCL: false
582
- });
583
-
584
- // Assert
585
- expect(result.successful).toBe(true);
586
- expect(result.logId).toBe('oauth-log-123');
587
- expect(oauth.checkAndRefreshAccessToken).toHaveBeenCalled();
588
- });
589
-
590
- test('should use cached note when USE_CACHE is enabled and isFromSSCL', async () => {
591
- // Arrange
592
- process.env.USE_CACHE = 'true';
593
- await UserModel.create(mockUser);
594
-
595
- NoteCache.get.mockResolvedValue({ note: 'Cached note' });
596
-
597
- const mockConnector = {
598
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
599
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
600
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
601
- createCallLog: jest.fn().mockResolvedValue({
602
- logId: 'cached-log-123',
603
- returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
604
- })
605
- };
606
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
607
- composeCallLog.mockReturnValue('Composed log details');
608
-
609
- // Act
610
- const result = await logHandler.createCallLog({
611
- platform: 'testCRM',
612
- userId: 'test-user-id',
613
- incomingData: mockIncomingData,
614
- hashedAccountId: 'hashed-123',
615
- isFromSSCL: true
616
- });
617
-
618
- // Assert
619
- expect(result.successful).toBe(true);
620
- expect(NoteCache.get).toHaveBeenCalledWith({ sessionId: 'session-123' });
621
-
622
- // Clean up
623
- delete process.env.USE_CACHE;
624
- });
625
- });
626
-
627
- describe('getCallLog', () => {
628
- test('should return error when user not found', async () => {
629
- // Act
630
- const result = await logHandler.getCallLog({
631
- userId: 'non-existent-user',
632
- sessionIds: 'session-1,session-2',
633
- platform: 'testCRM',
634
- requireDetails: false
635
- });
636
-
637
- // Assert
638
- expect(result.successful).toBe(false);
639
- expect(result.message).toBe('Contact not found');
640
- });
641
-
642
- test('should return error when no session IDs provided', async () => {
643
- // Arrange
644
- await UserModel.create({
645
- id: 'test-user-id',
646
- platform: 'testCRM',
647
- accessToken: 'test-token'
648
- });
649
-
650
- // Act
651
- const result = await logHandler.getCallLog({
652
- userId: 'test-user-id',
653
- sessionIds: null,
654
- platform: 'testCRM',
655
- requireDetails: false
656
- });
657
-
658
- // Assert
659
- expect(result.successful).toBe(false);
660
- expect(result.message).toBe('No session IDs provided');
661
- });
662
-
663
- test('should return matched logs without details', async () => {
664
- // Arrange
665
- await UserModel.create({
666
- id: 'test-user-id',
667
- platform: 'testCRM',
668
- accessToken: 'test-token'
669
- });
670
-
671
- await CallLogModel.create({
672
- id: 'call-1',
673
- sessionId: 'session-1',
674
- platform: 'testCRM',
675
- thirdPartyLogId: 'log-1',
676
- userId: 'test-user-id'
677
- });
678
-
679
- // Act
680
- const result = await logHandler.getCallLog({
681
- userId: 'test-user-id',
682
- sessionIds: 'session-1,session-2',
683
- platform: 'testCRM',
684
- requireDetails: false
685
- });
686
-
687
- // Assert
688
- expect(result.successful).toBe(true);
689
- expect(result.logs).toHaveLength(2);
690
- expect(result.logs[0]).toEqual({
691
- sessionId: 'session-1',
692
- matched: true,
693
- logId: 'log-1'
694
- });
695
- expect(result.logs[1]).toEqual({
696
- sessionId: 'session-2',
697
- matched: false
698
- });
699
- });
700
-
701
- test('should filter matched logs by extensionNumber when provided', async () => {
702
- // Arrange
703
- await UserModel.create({
704
- id: 'test-user-id',
705
- platform: 'testCRM',
706
- accessToken: 'test-token'
707
- });
708
-
709
- await CallLogModel.create({
710
- id: 'call-1',
711
- sessionId: 'session-1',
712
- extensionNumber: '101',
713
- platform: 'testCRM',
714
- thirdPartyLogId: 'log-101',
715
- userId: 'test-user-id'
716
- });
717
- await CallLogModel.create({
718
- id: 'call-1',
719
- sessionId: 'session-1',
720
- extensionNumber: '102',
721
- platform: 'testCRM',
722
- thirdPartyLogId: 'log-102',
723
- userId: 'test-user-id'
724
- });
725
-
726
- // Act
727
- const result = await logHandler.getCallLog({
728
- userId: 'test-user-id',
729
- sessionIds: 'session-1',
730
- extensionNumber: '102',
731
- platform: 'testCRM',
732
- requireDetails: false
733
- });
734
-
735
- // Assert
736
- expect(result.successful).toBe(true);
737
- expect(result.logs).toEqual([{
738
- sessionId: 'session-1',
739
- matched: true,
740
- logId: 'log-102'
741
- }]);
742
- });
743
-
744
- test('should match SSCL log by hashedExtensionId when extensionNumber differs', async () => {
745
- // Arrange
746
- await UserModel.create({
747
- id: 'test-user-id',
748
- platform: 'testCRM',
749
- accessToken: 'test-token'
750
- });
751
-
752
- await CallLogModel.create({
753
- id: 'call-1',
754
- sessionId: 'session-1',
755
- extensionNumber: '',
756
- hashedExtensionId: 'hashed-ext-1',
757
- platform: 'testCRM',
758
- thirdPartyLogId: 'sscl-log',
759
- userId: 'test-user-id'
760
- });
761
-
762
- // Act
763
- const result = await logHandler.getCallLog({
764
- userId: 'test-user-id',
765
- sessionIds: 'session-1',
766
- extensionNumber: '101',
767
- hashedExtensionId: 'hashed-ext-1',
768
- platform: 'testCRM',
769
- requireDetails: false
770
- });
771
-
772
- // Assert
773
- expect(result.successful).toBe(true);
774
- expect(result.logs).toEqual([{
775
- sessionId: 'session-1',
776
- matched: true,
777
- logId: 'sscl-log'
778
- }]);
779
- });
780
-
781
- test('should keep session-only lookup backward compatible when extensionNumber is empty', async () => {
782
- // Arrange
783
- await UserModel.create({
784
- id: 'test-user-id',
785
- platform: 'testCRM',
786
- accessToken: 'test-token'
787
- });
788
-
789
- await CallLogModel.create({
790
- id: 'call-1',
791
- sessionId: 'session-1',
792
- extensionNumber: '',
793
- platform: 'testCRM',
794
- thirdPartyLogId: 'legacy-log',
795
- userId: 'test-user-id'
796
- });
797
- await CallLogModel.create({
798
- id: 'call-1',
799
- sessionId: 'session-1',
800
- extensionNumber: '102',
801
- platform: 'testCRM',
802
- thirdPartyLogId: 'extension-log',
803
- userId: 'test-user-id'
804
- });
805
-
806
- // Act
807
- const result = await logHandler.getCallLog({
808
- userId: 'test-user-id',
809
- sessionIds: 'session-1',
810
- platform: 'testCRM',
811
- requireDetails: false
812
- });
813
-
814
- // Assert
815
- expect(result.successful).toBe(true);
816
- expect(result.logs).toEqual([{
817
- sessionId: 'session-1',
818
- matched: true,
819
- logId: 'legacy-log'
820
- }]);
821
- });
822
-
823
- test('should return matched logs with details when requireDetails is true', async () => {
824
- // Arrange
825
- await UserModel.create({
826
- id: 'test-user-id',
827
- platform: 'testCRM',
828
- accessToken: 'test-token',
829
- platformAdditionalInfo: {}
830
- });
831
-
832
- await CallLogModel.create({
833
- id: 'call-1',
834
- sessionId: 'session-1',
835
- platform: 'testCRM',
836
- thirdPartyLogId: 'log-1',
837
- userId: 'test-user-id',
838
- contactId: 'contact-1'
839
- });
840
-
841
- const mockConnector = {
842
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
843
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
844
- getCallLog: jest.fn().mockResolvedValue({
845
- callLogInfo: { subject: 'Test Call', note: 'Test note' },
846
- returnMessage: { message: 'Success', messageType: 'success' }
847
- })
848
- };
849
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
850
-
851
- // Act
852
- const result = await logHandler.getCallLog({
853
- userId: 'test-user-id',
854
- sessionIds: 'session-1',
855
- platform: 'testCRM',
856
- requireDetails: true
857
- });
858
-
859
- // Assert
860
- expect(result.successful).toBe(true);
861
- expect(result.logs).toHaveLength(1);
862
- expect(result.logs[0].matched).toBe(true);
863
- expect(result.logs[0].logData).toEqual({ subject: 'Test Call', note: 'Test note' });
864
- expect(mockConnector.getCallLog).toHaveBeenCalled();
865
- });
866
-
867
- test('should limit session IDs to 5', async () => {
868
- // Arrange
869
- await UserModel.create({
870
- id: 'test-user-id',
871
- platform: 'testCRM',
872
- accessToken: 'test-token'
873
- });
874
-
875
- const sessionIds = 'session-1,session-2,session-3,session-4,session-5,session-6,session-7';
876
-
877
- // Act
878
- const result = await logHandler.getCallLog({
879
- userId: 'test-user-id',
880
- sessionIds,
881
- platform: 'testCRM',
882
- requireDetails: false
883
- });
884
-
885
- // Assert
886
- expect(result.successful).toBe(true);
887
- expect(result.logs).toHaveLength(5);
888
- });
889
-
890
- test('should skip session ID 0', async () => {
891
- // Arrange
892
- await UserModel.create({
893
- id: 'test-user-id',
894
- platform: 'testCRM',
895
- accessToken: 'test-token',
896
- platformAdditionalInfo: {}
897
- });
898
-
899
- const mockConnector = {
900
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
901
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
902
- getCallLog: jest.fn()
903
- };
904
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
905
-
906
- // Act
907
- const result = await logHandler.getCallLog({
908
- userId: 'test-user-id',
909
- sessionIds: '0,session-1',
910
- platform: 'testCRM',
911
- requireDetails: true
912
- });
913
-
914
- // Assert
915
- expect(result.successful).toBe(true);
916
- expect(result.logs[0]).toEqual({ sessionId: '0', matched: false });
917
- });
918
- });
919
-
920
- describe('updateCallLog', () => {
921
- test('should return unsuccessful when no existing call log found', async () => {
922
- // Act
923
- const result = await logHandler.updateCallLog({
924
- platform: 'testCRM',
925
- userId: 'test-user-id',
926
- incomingData: { sessionId: 'non-existent-session' },
927
- hashedAccountId: 'hashed-123',
928
- isFromSSCL: false
929
- });
930
-
931
- // Assert
932
- expect(result.successful).toBe(false);
933
- });
934
-
935
- test('should return error when user not found for update', async () => {
936
- // Arrange
937
- await CallLogModel.create({
938
- id: 'call-1',
939
- sessionId: 'session-1',
940
- platform: 'testCRM',
941
- thirdPartyLogId: 'log-1',
942
- userId: 'test-user-id'
943
- });
944
-
945
- // Act
946
- const result = await logHandler.updateCallLog({
947
- platform: 'testCRM',
948
- userId: 'non-existent-user',
949
- incomingData: { sessionId: 'session-1' },
950
- hashedAccountId: 'hashed-123',
951
- isFromSSCL: false
952
- });
953
-
954
- // Assert
955
- expect(result.successful).toBe(false);
956
- expect(result.message).toBe('Contact not found');
957
- });
958
-
959
- test('should successfully update call log', async () => {
960
- // Arrange
961
- await UserModel.create({
962
- id: 'test-user-id',
963
- platform: 'testCRM',
964
- accessToken: 'test-token',
965
- platformAdditionalInfo: {}
966
- });
967
-
968
- await CallLogModel.create({
969
- id: 'call-1',
970
- sessionId: 'session-1',
971
- platform: 'testCRM',
972
- thirdPartyLogId: 'log-1',
973
- userId: 'test-user-id',
974
- contactId: 'contact-1'
975
- });
976
-
977
- const mockConnector = {
978
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
979
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
980
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
981
- getCallLog: jest.fn().mockResolvedValue({
982
- callLogInfo: { fullBody: 'Existing body', note: 'Existing note' }
983
- }),
984
- updateCallLog: jest.fn().mockResolvedValue({
985
- updatedNote: 'Updated note',
986
- returnMessage: { message: 'Updated', messageType: 'success', ttl: 2000 }
987
- })
988
- };
989
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
990
- composeCallLog.mockReturnValue('Updated composed log');
991
-
992
- const incomingData = {
993
- sessionId: 'session-1',
994
- note: 'Updated note',
995
- subject: 'Updated subject',
996
- startTime: new Date().toISOString(),
997
- duration: 180,
998
- result: 'Completed'
999
- };
1000
-
1001
- // Act
1002
- const result = await logHandler.updateCallLog({
1003
- platform: 'testCRM',
1004
- userId: 'test-user-id',
1005
- incomingData,
1006
- hashedAccountId: 'hashed-123',
1007
- isFromSSCL: false
1008
- });
1009
-
1010
- // Assert
1011
- expect(result.successful).toBe(true);
1012
- expect(result.logId).toBe('log-1');
1013
- expect(result.updatedNote).toBe('Updated note');
1014
- expect(mockConnector.updateCallLog).toHaveBeenCalled();
1015
- });
1016
- });
1017
-
1018
- describe('createMessageLog', () => {
1019
- test('should return warning when no messages to log', async () => {
1020
- // Act
1021
- const result = await logHandler.createMessageLog({
1022
- platform: 'testCRM',
1023
- userId: 'test-user-id',
1024
- incomingData: {
1025
- logInfo: { messages: [] }
1026
- }
1027
- });
1028
-
1029
- // Assert
1030
- expect(result.successful).toBe(false);
1031
- expect(result.returnMessage.message).toBe('No message to log.');
1032
- });
1033
-
1034
- test('should return warning when user not found', async () => {
1035
- // Act
1036
- const result = await logHandler.createMessageLog({
1037
- platform: 'testCRM',
1038
- userId: 'non-existent-user',
1039
- incomingData: {
1040
- logInfo: {
1041
- messages: [{ id: 'msg-1', subject: 'Test', creationTime: new Date() }],
1042
- correspondents: [{ phoneNumber: '+1234567890' }]
1043
- }
1044
- }
1045
- });
1046
-
1047
- // Assert
1048
- expect(result.successful).toBe(false);
1049
- expect(result.returnMessage.message).toBe('Contact not found');
1050
- });
1051
-
1052
- test('should return warning when contact not found', async () => {
1053
- // Arrange
1054
- await UserModel.create({
1055
- id: 'test-user-id',
1056
- platform: 'testCRM',
1057
- accessToken: 'test-token',
1058
- platformAdditionalInfo: {}
1059
- });
1060
-
1061
- const mockConnector = {
1062
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
1063
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded')
1064
- };
1065
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
1066
-
1067
- // Act
1068
- const result = await logHandler.createMessageLog({
1069
- platform: 'testCRM',
1070
- userId: 'test-user-id',
1071
- incomingData: {
1072
- logInfo: {
1073
- messages: [{ id: 'msg-1', subject: 'Test', creationTime: new Date() }],
1074
- correspondents: [{ phoneNumber: '+1234567890' }]
1075
- },
1076
- contactId: null
1077
- }
1078
- });
1079
-
1080
- // Assert
1081
- expect(result.successful).toBe(false);
1082
- expect(result.returnMessage.message).toContain('Contact not found for number');
1083
- });
1084
-
1085
- test('should successfully create message log', async () => {
1086
- // Arrange
1087
- await UserModel.create({
1088
- id: 'test-user-id',
1089
- platform: 'testCRM',
1090
- accessToken: 'test-token',
1091
- platformAdditionalInfo: {}
1092
- });
1093
-
1094
- const mockConnector = {
1095
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
1096
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1097
- createMessageLog: jest.fn().mockResolvedValue({
1098
- logId: 'msg-log-123',
1099
- returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
1100
- }),
1101
- updateMessageLog: jest.fn()
1102
- };
1103
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
1104
-
1105
- const incomingData = {
1106
- logInfo: {
1107
- messages: [{ id: 'msg-1', subject: 'Test SMS', direction: 'Outbound', creationTime: new Date() }],
1108
- correspondents: [{ phoneNumber: '+1234567890' }],
1109
- conversationId: 'conv-123',
1110
- conversationLogId: 'conv-log-123'
1111
- },
1112
- contactId: 'contact-123',
1113
- contactType: 'Contact',
1114
- contactName: 'Test Contact',
1115
- additionalSubmission: {}
1116
- };
1117
-
1118
- // Act
1119
- const result = await logHandler.createMessageLog({
1120
- platform: 'testCRM',
1121
- userId: 'test-user-id',
1122
- incomingData
1123
- });
1124
-
1125
- // Assert
1126
- expect(result.successful).toBe(true);
1127
- expect(result.logIds).toContain('msg-1');
1128
- expect(mockConnector.createMessageLog).toHaveBeenCalled();
1129
-
1130
- // Verify message log was saved
1131
- const savedLog = await MessageLogModel.findOne({ where: { id: 'msg-1' } });
1132
- expect(savedLog).not.toBeNull();
1133
- expect(savedLog.thirdPartyLogId).toBe('msg-log-123');
1134
- });
1135
-
1136
- test('should skip already logged messages', async () => {
1137
- // Arrange
1138
- await UserModel.create({
1139
- id: 'test-user-id',
1140
- platform: 'testCRM',
1141
- accessToken: 'test-token',
1142
- platformAdditionalInfo: {}
1143
- });
1144
-
1145
- // Create existing message log with same conversationLogId
1146
- await MessageLogModel.create({
1147
- id: 'msg-1',
1148
- platform: 'testCRM',
1149
- conversationId: 'conv-123',
1150
- conversationLogId: 'conv-log-123',
1151
- thirdPartyLogId: 'existing-log',
1152
- userId: 'test-user-id'
1153
- });
1154
-
1155
- const mockConnector = {
1156
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
1157
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1158
- createMessageLog: jest.fn().mockResolvedValue({
1159
- logId: 'msg-log-new',
1160
- returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
1161
- }),
1162
- updateMessageLog: jest.fn().mockResolvedValue({
1163
- returnMessage: { message: 'Message updated', messageType: 'success', ttl: 2000 }
1164
- })
1165
- };
1166
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
1167
-
1168
- const incomingData = {
1169
- logInfo: {
1170
- messages: [
1171
- { id: 'msg-1', subject: 'Already logged', direction: 'Outbound', creationTime: new Date() },
1172
- { id: 'msg-2', subject: 'New message', direction: 'Outbound', creationTime: new Date() }
1173
- ],
1174
- correspondents: [{ phoneNumber: '+1234567890' }],
1175
- conversationId: 'conv-123',
1176
- conversationLogId: 'conv-log-123' // Same conversationLogId as existing record
1177
- },
1178
- contactId: 'contact-123',
1179
- contactType: 'Contact',
1180
- additionalSubmission: {}
1181
- };
1182
-
1183
- // Act
1184
- const result = await logHandler.createMessageLog({
1185
- platform: 'testCRM',
1186
- userId: 'test-user-id',
1187
- incomingData
1188
- });
1189
-
1190
- // Assert
1191
- expect(result.successful).toBe(true);
1192
- // msg-1 is skipped (already logged), msg-2 uses updateMessageLog because same conversationLogId exists
1193
- expect(mockConnector.createMessageLog).toHaveBeenCalledTimes(0);
1194
- expect(mockConnector.updateMessageLog).toHaveBeenCalledTimes(1);
1195
- });
1196
-
1197
- test('should handle group SMS with contactId suffix for message IDs', async () => {
1198
- // Arrange - group SMS has multiple correspondents
1199
- await UserModel.create({
1200
- id: 'test-user-id',
1201
- platform: 'testCRM',
1202
- accessToken: 'test-token',
1203
- rcAccountId: 'rc-account-123',
1204
- platformAdditionalInfo: {}
1205
- });
1206
-
1207
- const mockConnector = {
1208
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
1209
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1210
- createMessageLog: jest.fn().mockResolvedValue({
1211
- logId: 'msg-log-group-123',
1212
- returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
1213
- }),
1214
- updateMessageLog: jest.fn()
1215
- };
1216
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
1217
-
1218
- const incomingData = {
1219
- logInfo: {
1220
- messages: [{ id: 'msg-group-1', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
1221
- correspondents: [
1222
- { phoneNumber: '+1234567890' },
1223
- { phoneNumber: '+0987654321' }
1224
- ],
1225
- conversationId: 'conv-group-123',
1226
- conversationLogId: 'conv-log-group-123'
1227
- },
1228
- contactId: 'contact-456',
1229
- contactType: 'Contact',
1230
- contactName: 'Primary Contact',
1231
- additionalSubmission: {}
1232
- };
1233
-
1234
- // Act
1235
- const result = await logHandler.createMessageLog({
1236
- platform: 'testCRM',
1237
- userId: 'test-user-id',
1238
- incomingData
1239
- });
1240
-
1241
- // Assert
1242
- expect(result.successful).toBe(true);
1243
- expect(result.logIds).toContain('msg-group-1-contact-456');
1244
- const savedLog = await MessageLogModel.findOne({ where: { id: 'msg-group-1-contact-456' } });
1245
- expect(savedLog).not.toBeNull();
1246
- expect(savedLog.thirdPartyLogId).toBe('msg-log-group-123');
1247
- });
1248
-
1249
- test('should pass correspondents to createMessageLog when group SMS has different contact names', async () => {
1250
- // Arrange - correspondent in cache with different name
1251
- await UserModel.create({
1252
- id: 'test-user-id',
1253
- platform: 'testCRM',
1254
- accessToken: 'test-token',
1255
- rcAccountId: 'rc-account-123',
1256
- platformAdditionalInfo: {}
1257
- });
1258
-
1259
- await AccountDataModel.create({
1260
- rcAccountId: 'rc-account-123',
1261
- platformName: 'testCRM',
1262
- dataKey: 'contact-+0987654321',
1263
- data: [{ name: 'Other Contact', id: 'contact-789' }]
1264
- });
1265
-
1266
- const mockConnector = {
1267
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
1268
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1269
- createMessageLog: jest.fn().mockResolvedValue({
1270
- logId: 'msg-log-correspondents',
1271
- returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
1272
- }),
1273
- updateMessageLog: jest.fn()
1274
- };
1275
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
1276
-
1277
- const incomingData = {
1278
- logInfo: {
1279
- messages: [{ id: 'msg-correspondents', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
1280
- correspondents: [
1281
- { phoneNumber: '+1234567890' },
1282
- { phoneNumber: '+0987654321' }
1283
- ],
1284
- conversationId: 'conv-correspondents',
1285
- conversationLogId: 'conv-log-correspondents'
1286
- },
1287
- contactId: 'contact-456',
1288
- contactType: 'Contact',
1289
- contactName: 'Primary Contact',
1290
- additionalSubmission: {}
1291
- };
1292
-
1293
- // Act
1294
- await logHandler.createMessageLog({
1295
- platform: 'testCRM',
1296
- userId: 'test-user-id',
1297
- incomingData
1298
- });
1299
-
1300
- // Assert - createMessageLog should receive correspondents with different name
1301
- expect(mockConnector.createMessageLog).toHaveBeenCalledWith(
1302
- expect.objectContaining({
1303
- correspondents: [[{ name: 'Other Contact', id: 'contact-789' }]]
1304
- })
1305
- );
1306
- });
1307
-
1308
- test('should not add correspondent when name matches contactName in group SMS', async () => {
1309
- // Arrange - correspondent in cache with same name as contactName
1310
- await UserModel.create({
1311
- id: 'test-user-id',
1312
- platform: 'testCRM',
1313
- accessToken: 'test-token',
1314
- rcAccountId: 'rc-account-123',
1315
- platformAdditionalInfo: {}
1316
- });
1317
-
1318
- await AccountDataModel.create({
1319
- rcAccountId: 'rc-account-123',
1320
- platformName: 'testCRM',
1321
- dataKey: 'contact-+0987654321',
1322
- data: [{ name: 'Primary Contact', id: 'contact-789' }]
1323
- });
1324
-
1325
- const mockConnector = {
1326
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
1327
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1328
- createMessageLog: jest.fn().mockResolvedValue({
1329
- logId: 'msg-log-same-name',
1330
- returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
1331
- }),
1332
- updateMessageLog: jest.fn()
1333
- };
1334
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
1335
-
1336
- const incomingData = {
1337
- logInfo: {
1338
- messages: [{ id: 'msg-same-name', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
1339
- correspondents: [
1340
- { phoneNumber: '+1234567890' },
1341
- { phoneNumber: '+0987654321' }
1342
- ],
1343
- conversationId: 'conv-same-name',
1344
- conversationLogId: 'conv-log-same-name'
1345
- },
1346
- contactId: 'contact-456',
1347
- contactType: 'Contact',
1348
- contactName: 'Primary Contact',
1349
- additionalSubmission: {}
1350
- };
1351
-
1352
- // Act
1353
- await logHandler.createMessageLog({
1354
- platform: 'testCRM',
1355
- userId: 'test-user-id',
1356
- incomingData
1357
- });
1358
-
1359
- // Assert - correspondents should be empty when names match
1360
- expect(mockConnector.createMessageLog).toHaveBeenCalledWith(
1361
- expect.objectContaining({
1362
- correspondents: []
1363
- })
1364
- );
1365
- });
1366
-
1367
- test('should use suffixed conversationLogId and conversationId for group SMS', async () => {
1368
- // Arrange
1369
- await UserModel.create({
1370
- id: 'test-user-id',
1371
- platform: 'testCRM',
1372
- accessToken: 'test-token',
1373
- rcAccountId: 'rc-account-123',
1374
- platformAdditionalInfo: {}
1375
- });
1376
-
1377
- const mockConnector = {
1378
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
1379
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1380
- createMessageLog: jest.fn().mockResolvedValue({
1381
- logId: 'msg-log-suffix',
1382
- returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
1383
- }),
1384
- updateMessageLog: jest.fn()
1385
- };
1386
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
1387
-
1388
- const incomingData = {
1389
- logInfo: {
1390
- messages: [{ id: 'msg-suffix', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
1391
- correspondents: [
1392
- { phoneNumber: '+1234567890' },
1393
- { phoneNumber: '+0987654321' }
1394
- ],
1395
- conversationId: 'conv-original',
1396
- conversationLogId: 'conv-log-original'
1397
- },
1398
- contactId: 'contact-999',
1399
- contactType: 'Contact',
1400
- contactName: 'Test Contact',
1401
- additionalSubmission: {}
1402
- };
1403
-
1404
- // Act
1405
- const result = await logHandler.createMessageLog({
1406
- platform: 'testCRM',
1407
- userId: 'test-user-id',
1408
- incomingData
1409
- });
1410
-
1411
- // Assert - message log saved with suffixed conversationLogId
1412
- expect(result.successful).toBe(true);
1413
- const savedLog = await MessageLogModel.findOne({ where: { id: 'msg-suffix-contact-999' } });
1414
- expect(savedLog).not.toBeNull();
1415
- expect(savedLog.conversationLogId).toBe('conv-log-original-contact-999');
1416
- expect(savedLog.conversationId).toBe('conv-original-contact-999');
1417
- });
1418
- });
1419
-
1420
- describe('saveNoteCache', () => {
1421
- test('should successfully save note cache', async () => {
1422
- // Arrange
1423
- NoteCache.create.mockResolvedValue({ sessionId: 'session-123', note: 'Test note' });
1424
-
1425
- // Act
1426
- const result = await logHandler.saveNoteCache({
1427
- sessionId: 'session-123',
1428
- note: 'Test note'
1429
- });
1430
-
1431
- // Assert
1432
- expect(result.successful).toBe(true);
1433
- expect(result.returnMessage).toBe('Note cache saved');
1434
- expect(NoteCache.create).toHaveBeenCalled();
1435
- });
1436
-
1437
- test('should handle errors when saving note cache', async () => {
1438
- // Arrange
1439
- NoteCache.create.mockRejectedValue(new Error('DynamoDB error'));
1440
-
1441
- // Act
1442
- const result = await logHandler.saveNoteCache({
1443
- sessionId: 'session-123',
1444
- note: 'Test note'
1445
- });
1446
-
1447
- // Assert
1448
- expect(result.successful).toBe(false);
1449
- expect(result.returnMessage.message).toBe('Error performing saveNoteCache');
1450
- expect(result.returnMessage.messageType).toBe('warning');
1451
- });
1452
- });
1453
-
1454
- describe('handleAsyncPluginCallback', () => {
1455
- test('should return not found when callback task cache is missing', async () => {
1456
- const result = await logHandler.handleAsyncPluginCallback({
1457
- taskId: 'missing-task',
1458
- body: {
1459
- successful: true,
1460
- note: 'Callback note'
1461
- }
1462
- });
1463
-
1464
- expect(result).toEqual({
1465
- statusCode: 404,
1466
- body: { successful: false, message: 'Async task not found' }
1467
- });
1468
- });
1469
-
1470
- test('should append callback note to call log and remove task cache on success', async () => {
1471
- await UserModel.create({
1472
- id: 'test-user-id',
1473
- platform: 'testCRM',
1474
- accessToken: 'test-token',
1475
- platformAdditionalInfo: {}
1476
- });
1477
- await CallLogModel.create({
1478
- id: 'call-1',
1479
- sessionId: 'session-1',
1480
- extensionNumber: '',
1481
- platform: 'testCRM',
1482
- thirdPartyLogId: 'log-1',
1483
- userId: 'test-user-id',
1484
- contactId: 'contact-1'
1485
- });
1486
- await CacheModel.create({
1487
- id: 'task-1',
1488
- status: 'pending',
1489
- userId: 'test-user-id',
1490
- cacheKey: 'asyncPluginTask-testPlugin',
1491
- expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
1492
- data: {
1493
- pluginId: 'testPlugin',
1494
- platform: 'testCRM',
1495
- userId: 'test-user-id',
1496
- sessionId: 'session-1',
1497
- extensionNumber: '',
1498
- incomingData: {
1499
- logInfo: {
1500
- sessionId: 'session-1',
1501
- startTime: '2026-06-12T10:00:00.000Z',
1502
- duration: 120,
1503
- result: 'Completed',
1504
- direction: 'Outbound',
1505
- from: { phoneNumber: '+1234567890' },
1506
- to: { phoneNumber: '+1987654321' }
1507
- },
1508
- note: 'Original note'
1509
- },
1510
- hashedAccountId: 'hashed-123',
1511
- isFromSSCL: false
1512
- }
1513
- });
1514
-
1515
- const mockConnector = {
1516
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
1517
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1518
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
1519
- getCallLog: jest.fn().mockResolvedValue({
1520
- callLogInfo: {
1521
- fullBody: '- Note: Existing note\n- Summary: Existing summary',
1522
- note: 'Existing note',
1523
- fullLogResponse: { id: 'log-1' }
1524
- }
1525
- }),
1526
- updateCallLog: jest.fn().mockResolvedValue({
1527
- updatedNote: 'Existing note\n\nCallback note'
1528
- })
1529
- };
1530
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
1531
- composeCallLog.mockReturnValue('Updated composed log');
1532
-
1533
- const result = await logHandler.handleAsyncPluginCallback({
1534
- taskId: 'task-1',
1535
- body: {
1536
- successful: true,
1537
- message: 'Done',
1538
- note: 'Callback note'
1539
- }
1540
- });
1541
-
1542
- expect(result).toEqual({
1543
- statusCode: 200,
1544
- body: { successful: true }
1545
- });
1546
- expect(composeCallLog).toHaveBeenCalledWith(expect.objectContaining({
1547
- existingBody: '- Note: Existing note\n- Summary: Existing summary',
1548
- note: 'Existing note\n\nCallback note'
1549
- }));
1550
- expect(mockConnector.updateCallLog).toHaveBeenCalledWith(expect.objectContaining({
1551
- note: 'Existing note\n\nCallback note',
1552
- composedLogDetails: 'Updated composed log',
1553
- existingCallLogDetails: { id: 'log-1' }
1554
- }));
1555
- expect(await CacheModel.findByPk('task-1')).toBeNull();
1556
- });
1557
-
1558
- test('should mark task cache failed when callback reports failure', async () => {
1559
- await CacheModel.create({
1560
- id: 'task-failed',
1561
- status: 'pending',
1562
- userId: 'test-user-id',
1563
- cacheKey: 'asyncPluginTask-testPlugin',
1564
- expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
1565
- data: {
1566
- pluginId: 'testPlugin',
1567
- platform: 'testCRM',
1568
- userId: 'test-user-id',
1569
- sessionId: 'session-1'
1570
- }
1571
- });
1572
-
1573
- const result = await logHandler.handleAsyncPluginCallback({
1574
- taskId: 'task-failed',
1575
- body: {
1576
- successful: false,
1577
- message: 'Plugin failed',
1578
- note: 'Ignored note'
1579
- }
1580
- });
1581
-
1582
- expect(result).toEqual({
1583
- statusCode: 200,
1584
- body: { successful: true }
1585
- });
1586
- const cache = await CacheModel.findByPk('task-failed');
1587
- expect(cache.status).toBe('failed');
1588
- expect(cache.data.message).toBe('Plugin failed');
1589
- });
1590
-
1591
- test('should destroy expired task cache and return not found', async () => {
1592
- await CacheModel.create({
1593
- id: 'task-expired',
1594
- status: 'pending',
1595
- userId: 'test-user-id',
1596
- cacheKey: 'asyncPluginTask-testPlugin',
1597
- expiry: new Date(Date.now() - 1000),
1598
- data: {
1599
- pluginId: 'testPlugin',
1600
- platform: 'testCRM',
1601
- userId: 'test-user-id',
1602
- sessionId: 'session-1'
1603
- }
1604
- });
1605
-
1606
- const result = await logHandler.handleAsyncPluginCallback({
1607
- taskId: 'task-expired',
1608
- body: {
1609
- successful: true,
1610
- note: 'Late callback note'
1611
- }
1612
- });
1613
-
1614
- expect(result).toEqual({
1615
- statusCode: 404,
1616
- body: { successful: false, message: 'Async task not found' }
1617
- });
1618
- expect(await CacheModel.findByPk('task-expired')).toBeNull();
1619
- expect(connectorRegistry.getConnector).not.toHaveBeenCalled();
1620
- });
1621
-
1622
- test('should mark task failed when callback cannot find the original call log', async () => {
1623
- await CacheModel.create({
1624
- id: 'task-missing-log',
1625
- status: 'pending',
1626
- userId: 'test-user-id',
1627
- cacheKey: 'asyncPluginTask-testPlugin',
1628
- expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
1629
- data: {
1630
- pluginId: 'testPlugin',
1631
- platform: 'testCRM',
1632
- userId: 'test-user-id',
1633
- sessionId: 'missing-session',
1634
- extensionNumber: '',
1635
- incomingData: {
1636
- logInfo: {
1637
- sessionId: 'missing-session'
1638
- }
1639
- }
1640
- }
1641
- });
1642
-
1643
- const result = await logHandler.handleAsyncPluginCallback({
1644
- taskId: 'task-missing-log',
1645
- body: {
1646
- successful: true,
1647
- note: 'Callback note'
1648
- }
1649
- });
1650
-
1651
- expect(result).toEqual({
1652
- statusCode: 500,
1653
- body: { successful: false, message: 'Call log not found for async plugin task' }
1654
- });
1655
- const cache = await CacheModel.findByPk('task-missing-log');
1656
- expect(cache.status).toBe('failed');
1657
- expect(cache.data.message).toBe('Call log not found for async plugin task');
1658
- expect(connectorRegistry.getConnector).not.toHaveBeenCalled();
1659
- });
1660
-
1661
- test('should mark task failed when callback note cannot be written to CRM', async () => {
1662
- await UserModel.create({
1663
- id: 'test-user-id',
1664
- platform: 'testCRM',
1665
- accessToken: 'test-token',
1666
- platformAdditionalInfo: {}
1667
- });
1668
- await CallLogModel.create({
1669
- id: 'call-1',
1670
- sessionId: 'session-1',
1671
- extensionNumber: '',
1672
- platform: 'testCRM',
1673
- thirdPartyLogId: 'log-1',
1674
- userId: 'test-user-id',
1675
- contactId: 'contact-1'
1676
- });
1677
- await CacheModel.create({
1678
- id: 'task-update-failed',
1679
- status: 'pending',
1680
- userId: 'test-user-id',
1681
- cacheKey: 'asyncPluginTask-testPlugin',
1682
- expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
1683
- data: {
1684
- pluginId: 'testPlugin',
1685
- platform: 'testCRM',
1686
- userId: 'test-user-id',
1687
- sessionId: 'session-1',
1688
- extensionNumber: '',
1689
- incomingData: {
1690
- logInfo: {
1691
- sessionId: 'session-1',
1692
- startTime: '2026-06-12T10:00:00.000Z',
1693
- duration: 120,
1694
- result: 'Completed',
1695
- direction: 'Outbound',
1696
- from: { phoneNumber: '+1234567890' },
1697
- to: { phoneNumber: '+1987654321' }
1698
- },
1699
- note: 'Original note'
1700
- },
1701
- hashedAccountId: 'hashed-123',
1702
- isFromSSCL: false
1703
- }
1704
- });
1705
- const mockConnector = {
1706
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
1707
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1708
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
1709
- getCallLog: jest.fn().mockResolvedValue({
1710
- callLogInfo: {
1711
- fullBody: 'Existing body',
1712
- note: 'Existing note',
1713
- fullLogResponse: { id: 'log-1' }
1714
- }
1715
- }),
1716
- updateCallLog: jest.fn().mockRejectedValue(new Error('CRM update failed'))
1717
- };
1718
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
1719
- composeCallLog.mockReturnValue('Updated composed log');
1720
-
1721
- const result = await logHandler.handleAsyncPluginCallback({
1722
- taskId: 'task-update-failed',
1723
- body: {
1724
- successful: true,
1725
- note: 'Callback note'
1726
- }
1727
- });
1728
-
1729
- expect(result).toEqual({
1730
- statusCode: 500,
1731
- body: { successful: false, message: 'CRM update failed' }
1732
- });
1733
- const cache = await CacheModel.findByPk('task-update-failed');
1734
- expect(cache).not.toBeNull();
1735
- expect(cache.status).toBe('failed');
1736
- expect(cache.data.message).toBe('CRM update failed');
1737
- expect(mockConnector.updateCallLog).toHaveBeenCalled();
1738
- });
1739
- test('should reject callback without successful boolean', async () => {
1740
- const result = await logHandler.handleAsyncPluginCallback({
1741
- taskId: 'task-1',
1742
- body: {
1743
- message: 'Missing status'
1744
- }
1745
- });
1746
-
1747
- expect(result).toEqual({
1748
- statusCode: 400,
1749
- body: { successful: false, message: 'successful is required' }
1750
- });
1751
- });
1752
- });
1753
-
1754
- describe('Error Handling', () => {
1755
- test('should handle 429 rate limit error in createCallLog', async () => {
1756
- // Arrange
1757
- await UserModel.create({
1758
- id: 'test-user-id',
1759
- platform: 'testCRM',
1760
- accessToken: 'test-token',
1761
- platformAdditionalInfo: {}
1762
- });
1763
-
1764
- const mockConnector = {
1765
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
1766
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1767
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
1768
- createCallLog: jest.fn().mockRejectedValue({
1769
- response: { status: 429 }
1770
- })
1771
- };
1772
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
1773
- composeCallLog.mockReturnValue('Composed log');
1774
-
1775
- const incomingData = {
1776
- logInfo: {
1777
- sessionId: 'session-rate-limit',
1778
- telephonySessionId: 'tel-session',
1779
- direction: 'Outbound',
1780
- from: { phoneNumber: '+1234567890' },
1781
- to: { phoneNumber: '+0987654321' }
1782
- },
1783
- contactId: 'contact-123',
1784
- note: 'Test'
1785
- };
1786
-
1787
- // Act
1788
- const result = await logHandler.createCallLog({
1789
- platform: 'testCRM',
1790
- userId: 'test-user-id',
1791
- incomingData,
1792
- hashedAccountId: 'hashed-123',
1793
- isFromSSCL: false
1794
- });
1795
-
1796
- // Assert
1797
- expect(result.successful).toBe(false);
1798
- expect(result.returnMessage.messageType).toBe('warning');
1799
- });
1800
-
1801
- test('should handle 401 authorization error in createCallLog', async () => {
1802
- // Arrange
1803
- await UserModel.create({
1804
- id: 'test-user-id',
1805
- platform: 'testCRM',
1806
- accessToken: 'test-token',
1807
- platformAdditionalInfo: {}
1808
- });
1809
-
1810
- const mockConnector = {
1811
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
1812
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1813
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
1814
- createCallLog: jest.fn().mockRejectedValue({
1815
- response: { status: 401 }
1816
- })
1817
- };
1818
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
1819
- composeCallLog.mockReturnValue('Composed log');
1820
-
1821
- const incomingData = {
1822
- logInfo: {
1823
- sessionId: 'session-auth-error',
1824
- telephonySessionId: 'tel-session',
1825
- direction: 'Outbound',
1826
- from: { phoneNumber: '+1234567890' },
1827
- to: { phoneNumber: '+0987654321' }
1828
- },
1829
- contactId: 'contact-123',
1830
- note: 'Test'
1831
- };
1832
-
1833
- // Act
1834
- const result = await logHandler.createCallLog({
1835
- platform: 'testCRM',
1836
- userId: 'test-user-id',
1837
- incomingData,
1838
- hashedAccountId: 'hashed-123',
1839
- isFromSSCL: false
1840
- });
1841
-
1842
- // Assert
1843
- expect(result.successful).toBe(false);
1844
- expect(result.extraDataTracking.statusCode).toBe(401);
1845
- });
1846
- });
1847
- });