@app-connect/core 1.7.25 → 1.7.26

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 (137) hide show
  1. package/.env.test +5 -5
  2. package/README.md +441 -441
  3. package/connector/developerPortal.js +31 -31
  4. package/connector/mock.js +84 -77
  5. package/connector/proxy/engine.js +164 -164
  6. package/connector/proxy/index.js +500 -500
  7. package/connector/registry.js +252 -252
  8. package/docs/README.md +50 -50
  9. package/docs/architecture.md +93 -93
  10. package/docs/connectors.md +116 -116
  11. package/docs/handlers.md +125 -125
  12. package/docs/libraries.md +101 -101
  13. package/docs/models.md +144 -144
  14. package/docs/routes.md +115 -115
  15. package/docs/tests.md +73 -73
  16. package/handlers/admin.js +523 -523
  17. package/handlers/appointment.js +193 -0
  18. package/handlers/auth.js +296 -296
  19. package/handlers/calldown.js +99 -99
  20. package/handlers/contact.js +280 -280
  21. package/handlers/disposition.js +82 -80
  22. package/handlers/log.js +984 -973
  23. package/handlers/managedAuth.js +446 -446
  24. package/handlers/plugin.js +208 -208
  25. package/handlers/user.js +142 -142
  26. package/index.js +3140 -2652
  27. package/jest.config.js +56 -56
  28. package/lib/analytics.js +54 -54
  29. package/lib/authSession.js +109 -109
  30. package/lib/cacheCleanup.js +21 -0
  31. package/lib/callLogComposer.js +898 -898
  32. package/lib/callLogLookup.js +34 -0
  33. package/lib/constants.js +8 -8
  34. package/lib/debugTracer.js +177 -177
  35. package/lib/encode.js +30 -30
  36. package/lib/errorHandler.js +218 -206
  37. package/lib/generalErrorMessage.js +41 -41
  38. package/lib/jwt.js +18 -18
  39. package/lib/logger.js +190 -190
  40. package/lib/migrateCallLogsSchema.js +116 -0
  41. package/lib/ringcentral.js +266 -266
  42. package/lib/s3ErrorLogReport.js +65 -65
  43. package/lib/sharedSMSComposer.js +471 -471
  44. package/lib/util.js +67 -67
  45. package/mcp/README.md +412 -395
  46. package/mcp/lib/validator.js +91 -91
  47. package/mcp/mcpHandler.js +425 -425
  48. package/mcp/tools/cancelAppointment.js +101 -0
  49. package/mcp/tools/checkAuthStatus.js +105 -105
  50. package/mcp/tools/confirmAppointment.js +101 -0
  51. package/mcp/tools/createAppointment.js +157 -0
  52. package/mcp/tools/createCallLog.js +327 -316
  53. package/mcp/tools/createContact.js +117 -117
  54. package/mcp/tools/createMessageLog.js +287 -287
  55. package/mcp/tools/doAuth.js +60 -60
  56. package/mcp/tools/findContactByName.js +93 -93
  57. package/mcp/tools/findContactByPhone.js +101 -101
  58. package/mcp/tools/getCallLog.js +111 -102
  59. package/mcp/tools/getGoogleFilePicker.js +99 -99
  60. package/mcp/tools/getHelp.js +43 -43
  61. package/mcp/tools/getPublicConnectors.js +94 -94
  62. package/mcp/tools/getSessionInfo.js +90 -90
  63. package/mcp/tools/index.js +51 -41
  64. package/mcp/tools/listAppointments.js +163 -0
  65. package/mcp/tools/logout.js +96 -96
  66. package/mcp/tools/rcGetCallLogs.js +65 -65
  67. package/mcp/tools/updateAppointment.js +154 -0
  68. package/mcp/tools/updateCallLog.js +130 -126
  69. package/mcp/ui/App/App.tsx +358 -358
  70. package/mcp/ui/App/components/AuthInfoForm.tsx +113 -113
  71. package/mcp/ui/App/components/AuthSuccess.tsx +22 -22
  72. package/mcp/ui/App/components/ConnectorList.tsx +82 -82
  73. package/mcp/ui/App/components/DebugPanel.tsx +43 -43
  74. package/mcp/ui/App/components/OAuthConnect.tsx +270 -270
  75. package/mcp/ui/App/lib/callTool.ts +130 -130
  76. package/mcp/ui/App/lib/debugLog.ts +41 -41
  77. package/mcp/ui/App/lib/developerPortal.ts +111 -111
  78. package/mcp/ui/App/main.css +5 -5
  79. package/mcp/ui/App/root.tsx +13 -13
  80. package/mcp/ui/index.html +13 -13
  81. package/mcp/ui/package-lock.json +6356 -6356
  82. package/mcp/ui/package.json +25 -25
  83. package/mcp/ui/tsconfig.json +26 -26
  84. package/mcp/ui/vite.config.ts +16 -16
  85. package/models/accountDataModel.js +33 -33
  86. package/models/adminConfigModel.js +35 -35
  87. package/models/cacheModel.js +30 -26
  88. package/models/callDownListModel.js +34 -34
  89. package/models/callLogModel.js +33 -27
  90. package/models/dynamo/connectorSchema.js +146 -146
  91. package/models/dynamo/lockSchema.js +24 -24
  92. package/models/dynamo/noteCacheSchema.js +29 -29
  93. package/models/llmSessionModel.js +17 -17
  94. package/models/messageLogModel.js +25 -25
  95. package/models/sequelize.js +16 -16
  96. package/models/userModel.js +45 -45
  97. package/package.json +1 -1
  98. package/releaseNotes.json +1093 -1081
  99. package/test/connector/proxy/engine.test.js +126 -126
  100. package/test/connector/proxy/index.test.js +279 -279
  101. package/test/connector/proxy/sample.json +161 -161
  102. package/test/connector/registry.test.js +415 -415
  103. package/test/handlers/admin.test.js +616 -616
  104. package/test/handlers/auth.test.js +1018 -1018
  105. package/test/handlers/contact.test.js +1014 -1014
  106. package/test/handlers/log.test.js +1298 -1160
  107. package/test/handlers/managedAuth.test.js +457 -457
  108. package/test/handlers/plugin.test.js +380 -380
  109. package/test/index.test.js +105 -105
  110. package/test/lib/cacheCleanup.test.js +42 -0
  111. package/test/lib/callLogComposer.test.js +1231 -1231
  112. package/test/lib/debugTracer.test.js +328 -328
  113. package/test/lib/jwt.test.js +176 -176
  114. package/test/lib/logger.test.js +206 -206
  115. package/test/lib/oauth.test.js +359 -359
  116. package/test/lib/ringcentral.test.js +467 -467
  117. package/test/lib/sharedSMSComposer.test.js +1084 -1084
  118. package/test/lib/util.test.js +329 -329
  119. package/test/mcp/tools/checkAuthStatus.test.js +83 -83
  120. package/test/mcp/tools/createCallLog.test.js +436 -436
  121. package/test/mcp/tools/createContact.test.js +58 -58
  122. package/test/mcp/tools/createMessageLog.test.js +595 -595
  123. package/test/mcp/tools/doAuth.test.js +113 -113
  124. package/test/mcp/tools/findContactByName.test.js +275 -275
  125. package/test/mcp/tools/findContactByPhone.test.js +296 -296
  126. package/test/mcp/tools/getCallLog.test.js +298 -298
  127. package/test/mcp/tools/getGoogleFilePicker.test.js +281 -281
  128. package/test/mcp/tools/getPublicConnectors.test.js +107 -107
  129. package/test/mcp/tools/getSessionInfo.test.js +127 -127
  130. package/test/mcp/tools/logout.test.js +233 -233
  131. package/test/mcp/tools/rcGetCallLogs.test.js +56 -56
  132. package/test/mcp/tools/updateCallLog.test.js +360 -360
  133. package/test/models/accountDataModel.test.js +98 -98
  134. package/test/models/dynamo/connectorSchema.test.js +189 -189
  135. package/test/models/models.test.js +568 -539
  136. package/test/routes/managedAuthRoutes.test.js +104 -104
  137. package/test/setup.js +178 -178
@@ -1,1160 +1,1298 @@
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 connectorRegistry = require('../../connector/registry');
35
- const oauth = require('../../lib/oauth');
36
- const { composeCallLog } = require('../../lib/callLogComposer');
37
- const { NoteCache } = require('../../models/dynamo/noteCacheSchema');
38
- const axios = require('axios');
39
- const { sequelize } = require('../../models/sequelize');
40
-
41
- describe('Log Handler', () => {
42
- beforeAll(async () => {
43
- await CallLogModel.sync({ force: true });
44
- await MessageLogModel.sync({ force: true });
45
- await UserModel.sync({ force: true });
46
- await AccountDataModel.sync({ force: true });
47
- });
48
-
49
- afterEach(async () => {
50
- await CallLogModel.destroy({ where: {} });
51
- await MessageLogModel.destroy({ where: {} });
52
- await UserModel.destroy({ where: {} });
53
- await AccountDataModel.destroy({ where: {} });
54
- jest.clearAllMocks();
55
- });
56
-
57
- afterAll(async () => {
58
- await sequelize.close();
59
- });
60
-
61
- describe('createCallLog', () => {
62
- const mockUser = {
63
- id: 'test-user-id',
64
- platform: 'testCRM',
65
- accessToken: 'test-access-token',
66
- rcAccountId: '12345',
67
- platformAdditionalInfo: {}
68
- };
69
-
70
- const mockIncomingData = {
71
- logInfo: {
72
- sessionId: 'session-123',
73
- telephonySessionId: 'tel-session-123',
74
- id: 'call-id-123',
75
- direction: 'Outbound',
76
- startTime: new Date().toISOString(),
77
- duration: 120,
78
- result: 'Completed',
79
- from: { phoneNumber: '+1234567890' },
80
- to: { phoneNumber: '+0987654321' },
81
- recording: { link: 'https://recording.link' }
82
- },
83
- contactId: 'contact-123',
84
- contactType: 'Contact',
85
- contactName: 'Test Contact',
86
- note: 'Test note',
87
- aiNote: '',
88
- transcript: '',
89
- additionalSubmission: {}
90
- };
91
-
92
- test('should return warning when call log already exists for session', async () => {
93
- // Arrange
94
- await CallLogModel.create({
95
- id: 'existing-log',
96
- sessionId: 'session-123',
97
- platform: 'testCRM',
98
- thirdPartyLogId: 'third-party-123',
99
- userId: 'test-user-id'
100
- });
101
-
102
- // Act
103
- const result = await logHandler.createCallLog({
104
- platform: 'testCRM',
105
- userId: 'test-user-id',
106
- incomingData: mockIncomingData,
107
- hashedAccountId: 'hashed-123',
108
- isFromSSCL: false
109
- });
110
-
111
- // Assert
112
- expect(result.successful).toBe(false);
113
- expect(result.returnMessage.messageType).toBe('warning');
114
- expect(result.returnMessage.message).toContain('Existing log for session');
115
- });
116
-
117
- test('should return warning when user not found', async () => {
118
- // Act
119
- const result = await logHandler.createCallLog({
120
- platform: 'testCRM',
121
- userId: 'non-existent-user',
122
- incomingData: mockIncomingData,
123
- hashedAccountId: 'hashed-123',
124
- isFromSSCL: false
125
- });
126
-
127
- // Assert
128
- expect(result.successful).toBe(false);
129
- expect(result.returnMessage.message).toBe('User not found');
130
- });
131
-
132
- test('should return warning when user has no access token', async () => {
133
- // Arrange
134
- await UserModel.create({
135
- id: 'test-user-id',
136
- platform: 'testCRM',
137
- accessToken: null
138
- });
139
-
140
- // Act
141
- const result = await logHandler.createCallLog({
142
- platform: 'testCRM',
143
- userId: 'test-user-id',
144
- incomingData: mockIncomingData,
145
- hashedAccountId: 'hashed-123',
146
- isFromSSCL: false
147
- });
148
-
149
- // Assert
150
- expect(result.successful).toBe(false);
151
- expect(result.returnMessage.message).toBe('User not found');
152
- });
153
-
154
- test('should return warning when contact not found', async () => {
155
- // Arrange
156
- await UserModel.create(mockUser);
157
-
158
- const mockConnector = {
159
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
160
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
161
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
162
- createCallLog: jest.fn()
163
- };
164
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
165
- composeCallLog.mockReturnValue('Composed log details');
166
-
167
- const incomingDataNoContact = {
168
- ...mockIncomingData,
169
- contactId: null
170
- };
171
-
172
- // Act
173
- const result = await logHandler.createCallLog({
174
- platform: 'testCRM',
175
- userId: 'test-user-id',
176
- incomingData: incomingDataNoContact,
177
- hashedAccountId: 'hashed-123',
178
- isFromSSCL: false
179
- });
180
-
181
- // Assert
182
- expect(result.successful).toBe(false);
183
- expect(result.returnMessage.message).toContain('Contact not found for number');
184
- });
185
-
186
- test('should successfully create call log with apiKey auth', async () => {
187
- // Arrange
188
- await UserModel.create(mockUser);
189
-
190
- const mockConnector = {
191
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
192
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
193
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
194
- createCallLog: jest.fn().mockResolvedValue({
195
- logId: 'new-log-123',
196
- returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
197
- })
198
- };
199
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
200
- composeCallLog.mockReturnValue('Composed log details');
201
-
202
- // Act
203
- const result = await logHandler.createCallLog({
204
- platform: 'testCRM',
205
- userId: 'test-user-id',
206
- incomingData: mockIncomingData,
207
- hashedAccountId: 'hashed-123',
208
- isFromSSCL: false
209
- });
210
-
211
- // Assert
212
- expect(result.successful).toBe(true);
213
- expect(result.logId).toBe('new-log-123');
214
- expect(mockConnector.getBasicAuth).toHaveBeenCalledWith({ apiKey: 'test-access-token' });
215
- expect(mockConnector.createCallLog).toHaveBeenCalled();
216
-
217
- // Verify call log was saved to database
218
- const savedLog = await CallLogModel.findOne({ where: { sessionId: 'session-123' } });
219
- expect(savedLog).not.toBeNull();
220
- expect(savedLog.thirdPartyLogId).toBe('new-log-123');
221
- });
222
-
223
- test('should call plugin with Bearer auth and without query jwt token', async () => {
224
- await UserModel.create(mockUser);
225
- await AccountDataModel.create({
226
- rcAccountId: mockUser.rcAccountId,
227
- platformName: 'testPlugin',
228
- dataKey: 'pluginData',
229
- data: {
230
- name: 'plugin.sample',
231
- supportedLogTypes: ['call'],
232
- isAsync: false,
233
- endpointUrl: 'https://plugins.example.com/plugin/testPlugin',
234
- jwtToken: 'plugin-jwt-token'
235
- }
236
- });
237
-
238
- const mockConnector = {
239
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
240
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
241
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
242
- createCallLog: jest.fn().mockResolvedValue({
243
- logId: 'new-log-123',
244
- returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
245
- })
246
- };
247
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
248
- composeCallLog.mockReturnValue('Composed log details');
249
-
250
- axios.post.mockResolvedValue({
251
- data: {
252
- ...mockIncomingData,
253
- note: 'updated by plugin'
254
- },
255
- headers: {
256
- 'x-refreshed-jwt-token': 'refreshed-plugin-jwt'
257
- }
258
- });
259
-
260
- const result = await logHandler.createCallLog({
261
- platform: 'testCRM',
262
- userId: 'test-user-id',
263
- incomingData: mockIncomingData,
264
- hashedAccountId: 'hashed-123',
265
- isFromSSCL: false
266
- });
267
-
268
- expect(result.successful).toBe(true);
269
- expect(axios.post).toHaveBeenCalledWith(
270
- 'https://plugins.example.com/plugin/testPlugin',
271
- { data: mockIncomingData, config: null },
272
- {
273
- headers: {
274
- Authorization: 'Bearer plugin-jwt-token'
275
- }
276
- }
277
- );
278
- });
279
-
280
- test('should successfully create call log with oauth auth', async () => {
281
- // Arrange
282
- const oauthUser = { ...mockUser };
283
- await UserModel.create(oauthUser);
284
-
285
- const mockConnector = {
286
- getAuthType: jest.fn().mockResolvedValue('oauth'),
287
- getOauthInfo: jest.fn().mockResolvedValue({
288
- clientId: 'client-id',
289
- clientSecret: 'client-secret',
290
- accessTokenUri: 'https://token.url',
291
- authorizationUri: 'https://auth.url'
292
- }),
293
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
294
- createCallLog: jest.fn().mockResolvedValue({
295
- logId: 'oauth-log-123',
296
- returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
297
- })
298
- };
299
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
300
-
301
- const mockOAuthApp = {};
302
- oauth.getOAuthApp.mockReturnValue(mockOAuthApp);
303
- oauth.checkAndRefreshAccessToken.mockResolvedValue({
304
- ...oauthUser,
305
- accessToken: 'refreshed-token'
306
- });
307
- composeCallLog.mockReturnValue('Composed log details');
308
-
309
- // Act
310
- const result = await logHandler.createCallLog({
311
- platform: 'testCRM',
312
- userId: 'test-user-id',
313
- incomingData: mockIncomingData,
314
- hashedAccountId: 'hashed-123',
315
- isFromSSCL: false
316
- });
317
-
318
- // Assert
319
- expect(result.successful).toBe(true);
320
- expect(result.logId).toBe('oauth-log-123');
321
- expect(oauth.checkAndRefreshAccessToken).toHaveBeenCalled();
322
- });
323
-
324
- test('should use cached note when USE_CACHE is enabled and isFromSSCL', async () => {
325
- // Arrange
326
- process.env.USE_CACHE = 'true';
327
- await UserModel.create(mockUser);
328
-
329
- NoteCache.get.mockResolvedValue({ note: 'Cached note' });
330
-
331
- const mockConnector = {
332
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
333
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
334
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
335
- createCallLog: jest.fn().mockResolvedValue({
336
- logId: 'cached-log-123',
337
- returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
338
- })
339
- };
340
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
341
- composeCallLog.mockReturnValue('Composed log details');
342
-
343
- // Act
344
- const result = await logHandler.createCallLog({
345
- platform: 'testCRM',
346
- userId: 'test-user-id',
347
- incomingData: mockIncomingData,
348
- hashedAccountId: 'hashed-123',
349
- isFromSSCL: true
350
- });
351
-
352
- // Assert
353
- expect(result.successful).toBe(true);
354
- expect(NoteCache.get).toHaveBeenCalledWith({ sessionId: 'session-123' });
355
-
356
- // Clean up
357
- delete process.env.USE_CACHE;
358
- });
359
- });
360
-
361
- describe('getCallLog', () => {
362
- test('should return error when user not found', async () => {
363
- // Act
364
- const result = await logHandler.getCallLog({
365
- userId: 'non-existent-user',
366
- sessionIds: 'session-1,session-2',
367
- platform: 'testCRM',
368
- requireDetails: false
369
- });
370
-
371
- // Assert
372
- expect(result.successful).toBe(false);
373
- expect(result.message).toBe('Contact not found');
374
- });
375
-
376
- test('should return error when no session IDs provided', async () => {
377
- // Arrange
378
- await UserModel.create({
379
- id: 'test-user-id',
380
- platform: 'testCRM',
381
- accessToken: 'test-token'
382
- });
383
-
384
- // Act
385
- const result = await logHandler.getCallLog({
386
- userId: 'test-user-id',
387
- sessionIds: null,
388
- platform: 'testCRM',
389
- requireDetails: false
390
- });
391
-
392
- // Assert
393
- expect(result.successful).toBe(false);
394
- expect(result.message).toBe('No session IDs provided');
395
- });
396
-
397
- test('should return matched logs without details', async () => {
398
- // Arrange
399
- await UserModel.create({
400
- id: 'test-user-id',
401
- platform: 'testCRM',
402
- accessToken: 'test-token'
403
- });
404
-
405
- await CallLogModel.create({
406
- id: 'call-1',
407
- sessionId: 'session-1',
408
- platform: 'testCRM',
409
- thirdPartyLogId: 'log-1',
410
- userId: 'test-user-id'
411
- });
412
-
413
- // Act
414
- const result = await logHandler.getCallLog({
415
- userId: 'test-user-id',
416
- sessionIds: 'session-1,session-2',
417
- platform: 'testCRM',
418
- requireDetails: false
419
- });
420
-
421
- // Assert
422
- expect(result.successful).toBe(true);
423
- expect(result.logs).toHaveLength(2);
424
- expect(result.logs[0]).toEqual({
425
- sessionId: 'session-1',
426
- matched: true,
427
- logId: 'log-1'
428
- });
429
- expect(result.logs[1]).toEqual({
430
- sessionId: 'session-2',
431
- matched: false
432
- });
433
- });
434
-
435
- test('should return matched logs with details when requireDetails is true', async () => {
436
- // Arrange
437
- await UserModel.create({
438
- id: 'test-user-id',
439
- platform: 'testCRM',
440
- accessToken: 'test-token',
441
- platformAdditionalInfo: {}
442
- });
443
-
444
- await CallLogModel.create({
445
- id: 'call-1',
446
- sessionId: 'session-1',
447
- platform: 'testCRM',
448
- thirdPartyLogId: 'log-1',
449
- userId: 'test-user-id',
450
- contactId: 'contact-1'
451
- });
452
-
453
- const mockConnector = {
454
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
455
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
456
- getCallLog: jest.fn().mockResolvedValue({
457
- callLogInfo: { subject: 'Test Call', note: 'Test note' },
458
- returnMessage: { message: 'Success', messageType: 'success' }
459
- })
460
- };
461
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
462
-
463
- // Act
464
- const result = await logHandler.getCallLog({
465
- userId: 'test-user-id',
466
- sessionIds: 'session-1',
467
- platform: 'testCRM',
468
- requireDetails: true
469
- });
470
-
471
- // Assert
472
- expect(result.successful).toBe(true);
473
- expect(result.logs).toHaveLength(1);
474
- expect(result.logs[0].matched).toBe(true);
475
- expect(result.logs[0].logData).toEqual({ subject: 'Test Call', note: 'Test note' });
476
- expect(mockConnector.getCallLog).toHaveBeenCalled();
477
- });
478
-
479
- test('should limit session IDs to 5', async () => {
480
- // Arrange
481
- await UserModel.create({
482
- id: 'test-user-id',
483
- platform: 'testCRM',
484
- accessToken: 'test-token'
485
- });
486
-
487
- const sessionIds = 'session-1,session-2,session-3,session-4,session-5,session-6,session-7';
488
-
489
- // Act
490
- const result = await logHandler.getCallLog({
491
- userId: 'test-user-id',
492
- sessionIds,
493
- platform: 'testCRM',
494
- requireDetails: false
495
- });
496
-
497
- // Assert
498
- expect(result.successful).toBe(true);
499
- expect(result.logs).toHaveLength(5);
500
- });
501
-
502
- test('should skip session ID 0', async () => {
503
- // Arrange
504
- await UserModel.create({
505
- id: 'test-user-id',
506
- platform: 'testCRM',
507
- accessToken: 'test-token',
508
- platformAdditionalInfo: {}
509
- });
510
-
511
- const mockConnector = {
512
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
513
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
514
- getCallLog: jest.fn()
515
- };
516
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
517
-
518
- // Act
519
- const result = await logHandler.getCallLog({
520
- userId: 'test-user-id',
521
- sessionIds: '0,session-1',
522
- platform: 'testCRM',
523
- requireDetails: true
524
- });
525
-
526
- // Assert
527
- expect(result.successful).toBe(true);
528
- expect(result.logs[0]).toEqual({ sessionId: '0', matched: false });
529
- });
530
- });
531
-
532
- describe('updateCallLog', () => {
533
- test('should return unsuccessful when no existing call log found', async () => {
534
- // Act
535
- const result = await logHandler.updateCallLog({
536
- platform: 'testCRM',
537
- userId: 'test-user-id',
538
- incomingData: { sessionId: 'non-existent-session' },
539
- hashedAccountId: 'hashed-123',
540
- isFromSSCL: false
541
- });
542
-
543
- // Assert
544
- expect(result.successful).toBe(false);
545
- });
546
-
547
- test('should return error when user not found for update', async () => {
548
- // Arrange
549
- await CallLogModel.create({
550
- id: 'call-1',
551
- sessionId: 'session-1',
552
- platform: 'testCRM',
553
- thirdPartyLogId: 'log-1',
554
- userId: 'test-user-id'
555
- });
556
-
557
- // Act
558
- const result = await logHandler.updateCallLog({
559
- platform: 'testCRM',
560
- userId: 'non-existent-user',
561
- incomingData: { sessionId: 'session-1' },
562
- hashedAccountId: 'hashed-123',
563
- isFromSSCL: false
564
- });
565
-
566
- // Assert
567
- expect(result.successful).toBe(false);
568
- expect(result.message).toBe('Contact not found');
569
- });
570
-
571
- test('should successfully update call log', async () => {
572
- // Arrange
573
- await UserModel.create({
574
- id: 'test-user-id',
575
- platform: 'testCRM',
576
- accessToken: 'test-token',
577
- platformAdditionalInfo: {}
578
- });
579
-
580
- await CallLogModel.create({
581
- id: 'call-1',
582
- sessionId: 'session-1',
583
- platform: 'testCRM',
584
- thirdPartyLogId: 'log-1',
585
- userId: 'test-user-id',
586
- contactId: 'contact-1'
587
- });
588
-
589
- const mockConnector = {
590
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
591
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
592
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
593
- getCallLog: jest.fn().mockResolvedValue({
594
- callLogInfo: { fullBody: 'Existing body', note: 'Existing note' }
595
- }),
596
- updateCallLog: jest.fn().mockResolvedValue({
597
- updatedNote: 'Updated note',
598
- returnMessage: { message: 'Updated', messageType: 'success', ttl: 2000 }
599
- })
600
- };
601
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
602
- composeCallLog.mockReturnValue('Updated composed log');
603
-
604
- const incomingData = {
605
- sessionId: 'session-1',
606
- note: 'Updated note',
607
- subject: 'Updated subject',
608
- startTime: new Date().toISOString(),
609
- duration: 180,
610
- result: 'Completed'
611
- };
612
-
613
- // Act
614
- const result = await logHandler.updateCallLog({
615
- platform: 'testCRM',
616
- userId: 'test-user-id',
617
- incomingData,
618
- hashedAccountId: 'hashed-123',
619
- isFromSSCL: false
620
- });
621
-
622
- // Assert
623
- expect(result.successful).toBe(true);
624
- expect(result.logId).toBe('log-1');
625
- expect(result.updatedNote).toBe('Updated note');
626
- expect(mockConnector.updateCallLog).toHaveBeenCalled();
627
- });
628
- });
629
-
630
- describe('createMessageLog', () => {
631
- test('should return warning when no messages to log', async () => {
632
- // Act
633
- const result = await logHandler.createMessageLog({
634
- platform: 'testCRM',
635
- userId: 'test-user-id',
636
- incomingData: {
637
- logInfo: { messages: [] }
638
- }
639
- });
640
-
641
- // Assert
642
- expect(result.successful).toBe(false);
643
- expect(result.returnMessage.message).toBe('No message to log.');
644
- });
645
-
646
- test('should return warning when user not found', async () => {
647
- // Act
648
- const result = await logHandler.createMessageLog({
649
- platform: 'testCRM',
650
- userId: 'non-existent-user',
651
- incomingData: {
652
- logInfo: {
653
- messages: [{ id: 'msg-1', subject: 'Test', creationTime: new Date() }],
654
- correspondents: [{ phoneNumber: '+1234567890' }]
655
- }
656
- }
657
- });
658
-
659
- // Assert
660
- expect(result.successful).toBe(false);
661
- expect(result.returnMessage.message).toBe('Contact not found');
662
- });
663
-
664
- test('should return warning when contact not found', async () => {
665
- // Arrange
666
- await UserModel.create({
667
- id: 'test-user-id',
668
- platform: 'testCRM',
669
- accessToken: 'test-token',
670
- platformAdditionalInfo: {}
671
- });
672
-
673
- const mockConnector = {
674
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
675
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded')
676
- };
677
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
678
-
679
- // Act
680
- const result = await logHandler.createMessageLog({
681
- platform: 'testCRM',
682
- userId: 'test-user-id',
683
- incomingData: {
684
- logInfo: {
685
- messages: [{ id: 'msg-1', subject: 'Test', creationTime: new Date() }],
686
- correspondents: [{ phoneNumber: '+1234567890' }]
687
- },
688
- contactId: null
689
- }
690
- });
691
-
692
- // Assert
693
- expect(result.successful).toBe(false);
694
- expect(result.returnMessage.message).toContain('Contact not found for number');
695
- });
696
-
697
- test('should successfully create message log', async () => {
698
- // Arrange
699
- await UserModel.create({
700
- id: 'test-user-id',
701
- platform: 'testCRM',
702
- accessToken: 'test-token',
703
- platformAdditionalInfo: {}
704
- });
705
-
706
- const mockConnector = {
707
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
708
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
709
- createMessageLog: jest.fn().mockResolvedValue({
710
- logId: 'msg-log-123',
711
- returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
712
- }),
713
- updateMessageLog: jest.fn()
714
- };
715
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
716
-
717
- const incomingData = {
718
- logInfo: {
719
- messages: [{ id: 'msg-1', subject: 'Test SMS', direction: 'Outbound', creationTime: new Date() }],
720
- correspondents: [{ phoneNumber: '+1234567890' }],
721
- conversationId: 'conv-123',
722
- conversationLogId: 'conv-log-123'
723
- },
724
- contactId: 'contact-123',
725
- contactType: 'Contact',
726
- contactName: 'Test Contact',
727
- additionalSubmission: {}
728
- };
729
-
730
- // Act
731
- const result = await logHandler.createMessageLog({
732
- platform: 'testCRM',
733
- userId: 'test-user-id',
734
- incomingData
735
- });
736
-
737
- // Assert
738
- expect(result.successful).toBe(true);
739
- expect(result.logIds).toContain('msg-1');
740
- expect(mockConnector.createMessageLog).toHaveBeenCalled();
741
-
742
- // Verify message log was saved
743
- const savedLog = await MessageLogModel.findOne({ where: { id: 'msg-1' } });
744
- expect(savedLog).not.toBeNull();
745
- expect(savedLog.thirdPartyLogId).toBe('msg-log-123');
746
- });
747
-
748
- test('should skip already logged messages', async () => {
749
- // Arrange
750
- await UserModel.create({
751
- id: 'test-user-id',
752
- platform: 'testCRM',
753
- accessToken: 'test-token',
754
- platformAdditionalInfo: {}
755
- });
756
-
757
- // Create existing message log with same conversationLogId
758
- await MessageLogModel.create({
759
- id: 'msg-1',
760
- platform: 'testCRM',
761
- conversationId: 'conv-123',
762
- conversationLogId: 'conv-log-123',
763
- thirdPartyLogId: 'existing-log',
764
- userId: 'test-user-id'
765
- });
766
-
767
- const mockConnector = {
768
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
769
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
770
- createMessageLog: jest.fn().mockResolvedValue({
771
- logId: 'msg-log-new',
772
- returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
773
- }),
774
- updateMessageLog: jest.fn().mockResolvedValue({
775
- returnMessage: { message: 'Message updated', messageType: 'success', ttl: 2000 }
776
- })
777
- };
778
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
779
-
780
- const incomingData = {
781
- logInfo: {
782
- messages: [
783
- { id: 'msg-1', subject: 'Already logged', direction: 'Outbound', creationTime: new Date() },
784
- { id: 'msg-2', subject: 'New message', direction: 'Outbound', creationTime: new Date() }
785
- ],
786
- correspondents: [{ phoneNumber: '+1234567890' }],
787
- conversationId: 'conv-123',
788
- conversationLogId: 'conv-log-123' // Same conversationLogId as existing record
789
- },
790
- contactId: 'contact-123',
791
- contactType: 'Contact',
792
- additionalSubmission: {}
793
- };
794
-
795
- // Act
796
- const result = await logHandler.createMessageLog({
797
- platform: 'testCRM',
798
- userId: 'test-user-id',
799
- incomingData
800
- });
801
-
802
- // Assert
803
- expect(result.successful).toBe(true);
804
- // msg-1 is skipped (already logged), msg-2 uses updateMessageLog because same conversationLogId exists
805
- expect(mockConnector.createMessageLog).toHaveBeenCalledTimes(0);
806
- expect(mockConnector.updateMessageLog).toHaveBeenCalledTimes(1);
807
- });
808
-
809
- test('should handle group SMS with contactId suffix for message IDs', async () => {
810
- // Arrange - group SMS has multiple correspondents
811
- await UserModel.create({
812
- id: 'test-user-id',
813
- platform: 'testCRM',
814
- accessToken: 'test-token',
815
- rcAccountId: 'rc-account-123',
816
- platformAdditionalInfo: {}
817
- });
818
-
819
- const mockConnector = {
820
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
821
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
822
- createMessageLog: jest.fn().mockResolvedValue({
823
- logId: 'msg-log-group-123',
824
- returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
825
- }),
826
- updateMessageLog: jest.fn()
827
- };
828
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
829
-
830
- const incomingData = {
831
- logInfo: {
832
- messages: [{ id: 'msg-group-1', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
833
- correspondents: [
834
- { phoneNumber: '+1234567890' },
835
- { phoneNumber: '+0987654321' }
836
- ],
837
- conversationId: 'conv-group-123',
838
- conversationLogId: 'conv-log-group-123'
839
- },
840
- contactId: 'contact-456',
841
- contactType: 'Contact',
842
- contactName: 'Primary Contact',
843
- additionalSubmission: {}
844
- };
845
-
846
- // Act
847
- const result = await logHandler.createMessageLog({
848
- platform: 'testCRM',
849
- userId: 'test-user-id',
850
- incomingData
851
- });
852
-
853
- // Assert
854
- expect(result.successful).toBe(true);
855
- expect(result.logIds).toContain('msg-group-1-contact-456');
856
- const savedLog = await MessageLogModel.findOne({ where: { id: 'msg-group-1-contact-456' } });
857
- expect(savedLog).not.toBeNull();
858
- expect(savedLog.thirdPartyLogId).toBe('msg-log-group-123');
859
- });
860
-
861
- test('should pass correspondents to createMessageLog when group SMS has different contact names', async () => {
862
- // Arrange - correspondent in cache with different name
863
- await UserModel.create({
864
- id: 'test-user-id',
865
- platform: 'testCRM',
866
- accessToken: 'test-token',
867
- rcAccountId: 'rc-account-123',
868
- platformAdditionalInfo: {}
869
- });
870
-
871
- await AccountDataModel.create({
872
- rcAccountId: 'rc-account-123',
873
- platformName: 'testCRM',
874
- dataKey: 'contact-+0987654321',
875
- data: [{ name: 'Other Contact', id: 'contact-789' }]
876
- });
877
-
878
- const mockConnector = {
879
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
880
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
881
- createMessageLog: jest.fn().mockResolvedValue({
882
- logId: 'msg-log-correspondents',
883
- returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
884
- }),
885
- updateMessageLog: jest.fn()
886
- };
887
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
888
-
889
- const incomingData = {
890
- logInfo: {
891
- messages: [{ id: 'msg-correspondents', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
892
- correspondents: [
893
- { phoneNumber: '+1234567890' },
894
- { phoneNumber: '+0987654321' }
895
- ],
896
- conversationId: 'conv-correspondents',
897
- conversationLogId: 'conv-log-correspondents'
898
- },
899
- contactId: 'contact-456',
900
- contactType: 'Contact',
901
- contactName: 'Primary Contact',
902
- additionalSubmission: {}
903
- };
904
-
905
- // Act
906
- await logHandler.createMessageLog({
907
- platform: 'testCRM',
908
- userId: 'test-user-id',
909
- incomingData
910
- });
911
-
912
- // Assert - createMessageLog should receive correspondents with different name
913
- expect(mockConnector.createMessageLog).toHaveBeenCalledWith(
914
- expect.objectContaining({
915
- correspondents: [[{ name: 'Other Contact', id: 'contact-789' }]]
916
- })
917
- );
918
- });
919
-
920
- test('should not add correspondent when name matches contactName in group SMS', async () => {
921
- // Arrange - correspondent in cache with same name as contactName
922
- await UserModel.create({
923
- id: 'test-user-id',
924
- platform: 'testCRM',
925
- accessToken: 'test-token',
926
- rcAccountId: 'rc-account-123',
927
- platformAdditionalInfo: {}
928
- });
929
-
930
- await AccountDataModel.create({
931
- rcAccountId: 'rc-account-123',
932
- platformName: 'testCRM',
933
- dataKey: 'contact-+0987654321',
934
- data: [{ name: 'Primary Contact', id: 'contact-789' }]
935
- });
936
-
937
- const mockConnector = {
938
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
939
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
940
- createMessageLog: jest.fn().mockResolvedValue({
941
- logId: 'msg-log-same-name',
942
- returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
943
- }),
944
- updateMessageLog: jest.fn()
945
- };
946
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
947
-
948
- const incomingData = {
949
- logInfo: {
950
- messages: [{ id: 'msg-same-name', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
951
- correspondents: [
952
- { phoneNumber: '+1234567890' },
953
- { phoneNumber: '+0987654321' }
954
- ],
955
- conversationId: 'conv-same-name',
956
- conversationLogId: 'conv-log-same-name'
957
- },
958
- contactId: 'contact-456',
959
- contactType: 'Contact',
960
- contactName: 'Primary Contact',
961
- additionalSubmission: {}
962
- };
963
-
964
- // Act
965
- await logHandler.createMessageLog({
966
- platform: 'testCRM',
967
- userId: 'test-user-id',
968
- incomingData
969
- });
970
-
971
- // Assert - correspondents should be empty when names match
972
- expect(mockConnector.createMessageLog).toHaveBeenCalledWith(
973
- expect.objectContaining({
974
- correspondents: []
975
- })
976
- );
977
- });
978
-
979
- test('should use suffixed conversationLogId and conversationId for group SMS', async () => {
980
- // Arrange
981
- await UserModel.create({
982
- id: 'test-user-id',
983
- platform: 'testCRM',
984
- accessToken: 'test-token',
985
- rcAccountId: 'rc-account-123',
986
- platformAdditionalInfo: {}
987
- });
988
-
989
- const mockConnector = {
990
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
991
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
992
- createMessageLog: jest.fn().mockResolvedValue({
993
- logId: 'msg-log-suffix',
994
- returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
995
- }),
996
- updateMessageLog: jest.fn()
997
- };
998
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
999
-
1000
- const incomingData = {
1001
- logInfo: {
1002
- messages: [{ id: 'msg-suffix', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
1003
- correspondents: [
1004
- { phoneNumber: '+1234567890' },
1005
- { phoneNumber: '+0987654321' }
1006
- ],
1007
- conversationId: 'conv-original',
1008
- conversationLogId: 'conv-log-original'
1009
- },
1010
- contactId: 'contact-999',
1011
- contactType: 'Contact',
1012
- contactName: 'Test Contact',
1013
- additionalSubmission: {}
1014
- };
1015
-
1016
- // Act
1017
- const result = await logHandler.createMessageLog({
1018
- platform: 'testCRM',
1019
- userId: 'test-user-id',
1020
- incomingData
1021
- });
1022
-
1023
- // Assert - message log saved with suffixed conversationLogId
1024
- expect(result.successful).toBe(true);
1025
- const savedLog = await MessageLogModel.findOne({ where: { id: 'msg-suffix-contact-999' } });
1026
- expect(savedLog).not.toBeNull();
1027
- expect(savedLog.conversationLogId).toBe('conv-log-original-contact-999');
1028
- expect(savedLog.conversationId).toBe('conv-original-contact-999');
1029
- });
1030
- });
1031
-
1032
- describe('saveNoteCache', () => {
1033
- test('should successfully save note cache', async () => {
1034
- // Arrange
1035
- NoteCache.create.mockResolvedValue({ sessionId: 'session-123', note: 'Test note' });
1036
-
1037
- // Act
1038
- const result = await logHandler.saveNoteCache({
1039
- sessionId: 'session-123',
1040
- note: 'Test note'
1041
- });
1042
-
1043
- // Assert
1044
- expect(result.successful).toBe(true);
1045
- expect(result.returnMessage).toBe('Note cache saved');
1046
- expect(NoteCache.create).toHaveBeenCalled();
1047
- });
1048
-
1049
- test('should handle errors when saving note cache', async () => {
1050
- // Arrange
1051
- NoteCache.create.mockRejectedValue(new Error('DynamoDB error'));
1052
-
1053
- // Act
1054
- const result = await logHandler.saveNoteCache({
1055
- sessionId: 'session-123',
1056
- note: 'Test note'
1057
- });
1058
-
1059
- // Assert
1060
- expect(result.successful).toBe(false);
1061
- expect(result.returnMessage.message).toBe('Error performing saveNoteCache');
1062
- expect(result.returnMessage.messageType).toBe('warning');
1063
- });
1064
- });
1065
-
1066
- describe('Error Handling', () => {
1067
- test('should handle 429 rate limit error in createCallLog', async () => {
1068
- // Arrange
1069
- await UserModel.create({
1070
- id: 'test-user-id',
1071
- platform: 'testCRM',
1072
- accessToken: 'test-token',
1073
- platformAdditionalInfo: {}
1074
- });
1075
-
1076
- const mockConnector = {
1077
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
1078
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1079
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
1080
- createCallLog: jest.fn().mockRejectedValue({
1081
- response: { status: 429 }
1082
- })
1083
- };
1084
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
1085
- composeCallLog.mockReturnValue('Composed log');
1086
-
1087
- const incomingData = {
1088
- logInfo: {
1089
- sessionId: 'session-rate-limit',
1090
- telephonySessionId: 'tel-session',
1091
- direction: 'Outbound',
1092
- from: { phoneNumber: '+1234567890' },
1093
- to: { phoneNumber: '+0987654321' }
1094
- },
1095
- contactId: 'contact-123',
1096
- note: 'Test'
1097
- };
1098
-
1099
- // Act
1100
- const result = await logHandler.createCallLog({
1101
- platform: 'testCRM',
1102
- userId: 'test-user-id',
1103
- incomingData,
1104
- hashedAccountId: 'hashed-123',
1105
- isFromSSCL: false
1106
- });
1107
-
1108
- // Assert
1109
- expect(result.successful).toBe(false);
1110
- expect(result.returnMessage.messageType).toBe('warning');
1111
- });
1112
-
1113
- test('should handle 401 authorization error in createCallLog', async () => {
1114
- // Arrange
1115
- await UserModel.create({
1116
- id: 'test-user-id',
1117
- platform: 'testCRM',
1118
- accessToken: 'test-token',
1119
- platformAdditionalInfo: {}
1120
- });
1121
-
1122
- const mockConnector = {
1123
- getAuthType: jest.fn().mockResolvedValue('apiKey'),
1124
- getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1125
- getLogFormatType: jest.fn().mockReturnValue('text/plain'),
1126
- createCallLog: jest.fn().mockRejectedValue({
1127
- response: { status: 401 }
1128
- })
1129
- };
1130
- connectorRegistry.getConnector.mockReturnValue(mockConnector);
1131
- composeCallLog.mockReturnValue('Composed log');
1132
-
1133
- const incomingData = {
1134
- logInfo: {
1135
- sessionId: 'session-auth-error',
1136
- telephonySessionId: 'tel-session',
1137
- direction: 'Outbound',
1138
- from: { phoneNumber: '+1234567890' },
1139
- to: { phoneNumber: '+0987654321' }
1140
- },
1141
- contactId: 'contact-123',
1142
- note: 'Test'
1143
- };
1144
-
1145
- // Act
1146
- const result = await logHandler.createCallLog({
1147
- platform: 'testCRM',
1148
- userId: 'test-user-id',
1149
- incomingData,
1150
- hashedAccountId: 'hashed-123',
1151
- isFromSSCL: false
1152
- });
1153
-
1154
- // Assert
1155
- expect(result.successful).toBe(false);
1156
- expect(result.extraDataTracking.statusCode).toBe(401);
1157
- });
1158
- });
1159
- });
1160
-
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 connectorRegistry = require('../../connector/registry');
35
+ const oauth = require('../../lib/oauth');
36
+ const { composeCallLog } = require('../../lib/callLogComposer');
37
+ const { NoteCache } = require('../../models/dynamo/noteCacheSchema');
38
+ const axios = require('axios');
39
+ const { sequelize } = require('../../models/sequelize');
40
+
41
+ describe('Log Handler', () => {
42
+ beforeAll(async () => {
43
+ await CallLogModel.sync({ force: true });
44
+ await MessageLogModel.sync({ force: true });
45
+ await UserModel.sync({ force: true });
46
+ await AccountDataModel.sync({ force: true });
47
+ });
48
+
49
+ afterEach(async () => {
50
+ await CallLogModel.destroy({ where: {} });
51
+ await MessageLogModel.destroy({ where: {} });
52
+ await UserModel.destroy({ where: {} });
53
+ await AccountDataModel.destroy({ where: {} });
54
+ jest.clearAllMocks();
55
+ });
56
+
57
+ afterAll(async () => {
58
+ await sequelize.close();
59
+ });
60
+
61
+ describe('createCallLog', () => {
62
+ const mockUser = {
63
+ id: 'test-user-id',
64
+ platform: 'testCRM',
65
+ accessToken: 'test-access-token',
66
+ rcAccountId: '12345',
67
+ platformAdditionalInfo: {}
68
+ };
69
+
70
+ const mockIncomingData = {
71
+ logInfo: {
72
+ sessionId: 'session-123',
73
+ telephonySessionId: 'tel-session-123',
74
+ id: 'call-id-123',
75
+ direction: 'Outbound',
76
+ startTime: new Date().toISOString(),
77
+ duration: 120,
78
+ result: 'Completed',
79
+ from: { phoneNumber: '+1234567890' },
80
+ to: { phoneNumber: '+0987654321' },
81
+ recording: { link: 'https://recording.link' }
82
+ },
83
+ contactId: 'contact-123',
84
+ contactType: 'Contact',
85
+ contactName: 'Test Contact',
86
+ note: 'Test note',
87
+ aiNote: '',
88
+ transcript: '',
89
+ additionalSubmission: {}
90
+ };
91
+
92
+ test('should return warning when call log already exists for session', async () => {
93
+ // Arrange
94
+ await CallLogModel.create({
95
+ id: 'existing-log',
96
+ sessionId: 'session-123',
97
+ platform: 'testCRM',
98
+ thirdPartyLogId: 'third-party-123',
99
+ userId: 'test-user-id'
100
+ });
101
+
102
+ // Act
103
+ const result = await logHandler.createCallLog({
104
+ platform: 'testCRM',
105
+ userId: 'test-user-id',
106
+ incomingData: mockIncomingData,
107
+ hashedAccountId: 'hashed-123',
108
+ isFromSSCL: false
109
+ });
110
+
111
+ // Assert
112
+ expect(result.successful).toBe(false);
113
+ expect(result.returnMessage.messageType).toBe('warning');
114
+ expect(result.returnMessage.message).toContain('Existing log for session');
115
+ });
116
+
117
+ test('should allow same session when extensionNumber is different', async () => {
118
+ // Arrange
119
+ await UserModel.create(mockUser);
120
+ await CallLogModel.create({
121
+ id: 'tel-session-123',
122
+ sessionId: 'session-123',
123
+ extensionNumber: '101',
124
+ platform: 'testCRM',
125
+ thirdPartyLogId: 'third-party-101',
126
+ userId: 'test-user-id'
127
+ });
128
+
129
+ const mockConnector = {
130
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
131
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
132
+ getLogFormatType: jest.fn().mockReturnValue('text/plain'),
133
+ createCallLog: jest.fn().mockResolvedValue({
134
+ logId: 'new-log-102',
135
+ returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
136
+ })
137
+ };
138
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
139
+ composeCallLog.mockReturnValue('Composed log details');
140
+
141
+ const incomingData = {
142
+ ...mockIncomingData,
143
+ logInfo: {
144
+ ...mockIncomingData.logInfo,
145
+ extensionNumber: '102'
146
+ }
147
+ };
148
+
149
+ // Act
150
+ const result = await logHandler.createCallLog({
151
+ platform: 'testCRM',
152
+ userId: 'test-user-id',
153
+ incomingData,
154
+ hashedAccountId: 'hashed-123',
155
+ isFromSSCL: false
156
+ });
157
+
158
+ // Assert
159
+ expect(result.successful).toBe(true);
160
+ const savedLog = await CallLogModel.findOne({
161
+ where: {
162
+ sessionId: 'session-123',
163
+ extensionNumber: '102'
164
+ }
165
+ });
166
+ expect(savedLog).not.toBeNull();
167
+ expect(savedLog.thirdPartyLogId).toBe('new-log-102');
168
+ });
169
+
170
+ test('should return warning when user not found', async () => {
171
+ // Act
172
+ const result = await logHandler.createCallLog({
173
+ platform: 'testCRM',
174
+ userId: 'non-existent-user',
175
+ incomingData: mockIncomingData,
176
+ hashedAccountId: 'hashed-123',
177
+ isFromSSCL: false
178
+ });
179
+
180
+ // Assert
181
+ expect(result.successful).toBe(false);
182
+ expect(result.returnMessage.message).toBe('User not found');
183
+ });
184
+
185
+ test('should return warning when user has no access token', async () => {
186
+ // Arrange
187
+ await UserModel.create({
188
+ id: 'test-user-id',
189
+ platform: 'testCRM',
190
+ accessToken: null
191
+ });
192
+
193
+ // Act
194
+ const result = await logHandler.createCallLog({
195
+ platform: 'testCRM',
196
+ userId: 'test-user-id',
197
+ incomingData: mockIncomingData,
198
+ hashedAccountId: 'hashed-123',
199
+ isFromSSCL: false
200
+ });
201
+
202
+ // Assert
203
+ expect(result.successful).toBe(false);
204
+ expect(result.returnMessage.message).toBe('User not found');
205
+ });
206
+
207
+ test('should return warning when contact not found', async () => {
208
+ // Arrange
209
+ await UserModel.create(mockUser);
210
+
211
+ const mockConnector = {
212
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
213
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
214
+ getLogFormatType: jest.fn().mockReturnValue('text/plain'),
215
+ createCallLog: jest.fn()
216
+ };
217
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
218
+ composeCallLog.mockReturnValue('Composed log details');
219
+
220
+ const incomingDataNoContact = {
221
+ ...mockIncomingData,
222
+ contactId: null
223
+ };
224
+
225
+ // Act
226
+ const result = await logHandler.createCallLog({
227
+ platform: 'testCRM',
228
+ userId: 'test-user-id',
229
+ incomingData: incomingDataNoContact,
230
+ hashedAccountId: 'hashed-123',
231
+ isFromSSCL: false
232
+ });
233
+
234
+ // Assert
235
+ expect(result.successful).toBe(false);
236
+ expect(result.returnMessage.message).toContain('Contact not found for number');
237
+ });
238
+
239
+ test('should successfully create call log with apiKey auth', async () => {
240
+ // Arrange
241
+ await UserModel.create(mockUser);
242
+
243
+ const mockConnector = {
244
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
245
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
246
+ getLogFormatType: jest.fn().mockReturnValue('text/plain'),
247
+ createCallLog: jest.fn().mockResolvedValue({
248
+ logId: 'new-log-123',
249
+ returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
250
+ })
251
+ };
252
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
253
+ composeCallLog.mockReturnValue('Composed log details');
254
+
255
+ // Act
256
+ const result = await logHandler.createCallLog({
257
+ platform: 'testCRM',
258
+ userId: 'test-user-id',
259
+ incomingData: mockIncomingData,
260
+ hashedAccountId: 'hashed-123',
261
+ isFromSSCL: false
262
+ });
263
+
264
+ // Assert
265
+ expect(result.successful).toBe(true);
266
+ expect(result.logId).toBe('new-log-123');
267
+ expect(mockConnector.getBasicAuth).toHaveBeenCalledWith({ apiKey: 'test-access-token' });
268
+ expect(mockConnector.createCallLog).toHaveBeenCalled();
269
+
270
+ // Verify call log was saved to database
271
+ const savedLog = await CallLogModel.findOne({ where: { sessionId: 'session-123' } });
272
+ expect(savedLog).not.toBeNull();
273
+ expect(savedLog.thirdPartyLogId).toBe('new-log-123');
274
+ });
275
+
276
+ test('should call plugin with Bearer auth and without query jwt token', async () => {
277
+ await UserModel.create(mockUser);
278
+ await AccountDataModel.create({
279
+ rcAccountId: mockUser.rcAccountId,
280
+ platformName: 'testPlugin',
281
+ dataKey: 'pluginData',
282
+ data: {
283
+ name: 'plugin.sample',
284
+ supportedLogTypes: ['call'],
285
+ isAsync: false,
286
+ endpointUrl: 'https://plugins.example.com/plugin/testPlugin',
287
+ jwtToken: 'plugin-jwt-token'
288
+ }
289
+ });
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().mockResolvedValue({
296
+ logId: 'new-log-123',
297
+ returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
298
+ })
299
+ };
300
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
301
+ composeCallLog.mockReturnValue('Composed log details');
302
+
303
+ axios.post.mockResolvedValue({
304
+ data: {
305
+ ...mockIncomingData,
306
+ note: 'updated by plugin'
307
+ },
308
+ headers: {
309
+ 'x-refreshed-jwt-token': 'refreshed-plugin-jwt'
310
+ }
311
+ });
312
+
313
+ const result = await logHandler.createCallLog({
314
+ platform: 'testCRM',
315
+ userId: 'test-user-id',
316
+ incomingData: mockIncomingData,
317
+ hashedAccountId: 'hashed-123',
318
+ isFromSSCL: false
319
+ });
320
+
321
+ expect(result.successful).toBe(true);
322
+ expect(axios.post).toHaveBeenCalledWith(
323
+ 'https://plugins.example.com/plugin/testPlugin',
324
+ { data: mockIncomingData, config: null },
325
+ {
326
+ headers: {
327
+ Authorization: 'Bearer plugin-jwt-token'
328
+ }
329
+ }
330
+ );
331
+ });
332
+
333
+ test('should successfully create call log with oauth auth', async () => {
334
+ // Arrange
335
+ const oauthUser = { ...mockUser };
336
+ await UserModel.create(oauthUser);
337
+
338
+ const mockConnector = {
339
+ getAuthType: jest.fn().mockResolvedValue('oauth'),
340
+ getOauthInfo: jest.fn().mockResolvedValue({
341
+ clientId: 'client-id',
342
+ clientSecret: 'client-secret',
343
+ accessTokenUri: 'https://token.url',
344
+ authorizationUri: 'https://auth.url'
345
+ }),
346
+ getLogFormatType: jest.fn().mockReturnValue('text/plain'),
347
+ createCallLog: jest.fn().mockResolvedValue({
348
+ logId: 'oauth-log-123',
349
+ returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
350
+ })
351
+ };
352
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
353
+
354
+ const mockOAuthApp = {};
355
+ oauth.getOAuthApp.mockReturnValue(mockOAuthApp);
356
+ oauth.checkAndRefreshAccessToken.mockResolvedValue({
357
+ ...oauthUser,
358
+ accessToken: 'refreshed-token'
359
+ });
360
+ composeCallLog.mockReturnValue('Composed log details');
361
+
362
+ // Act
363
+ const result = await logHandler.createCallLog({
364
+ platform: 'testCRM',
365
+ userId: 'test-user-id',
366
+ incomingData: mockIncomingData,
367
+ hashedAccountId: 'hashed-123',
368
+ isFromSSCL: false
369
+ });
370
+
371
+ // Assert
372
+ expect(result.successful).toBe(true);
373
+ expect(result.logId).toBe('oauth-log-123');
374
+ expect(oauth.checkAndRefreshAccessToken).toHaveBeenCalled();
375
+ });
376
+
377
+ test('should use cached note when USE_CACHE is enabled and isFromSSCL', async () => {
378
+ // Arrange
379
+ process.env.USE_CACHE = 'true';
380
+ await UserModel.create(mockUser);
381
+
382
+ NoteCache.get.mockResolvedValue({ note: 'Cached note' });
383
+
384
+ const mockConnector = {
385
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
386
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
387
+ getLogFormatType: jest.fn().mockReturnValue('text/plain'),
388
+ createCallLog: jest.fn().mockResolvedValue({
389
+ logId: 'cached-log-123',
390
+ returnMessage: { message: 'Call logged', messageType: 'success', ttl: 2000 }
391
+ })
392
+ };
393
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
394
+ composeCallLog.mockReturnValue('Composed log details');
395
+
396
+ // Act
397
+ const result = await logHandler.createCallLog({
398
+ platform: 'testCRM',
399
+ userId: 'test-user-id',
400
+ incomingData: mockIncomingData,
401
+ hashedAccountId: 'hashed-123',
402
+ isFromSSCL: true
403
+ });
404
+
405
+ // Assert
406
+ expect(result.successful).toBe(true);
407
+ expect(NoteCache.get).toHaveBeenCalledWith({ sessionId: 'session-123' });
408
+
409
+ // Clean up
410
+ delete process.env.USE_CACHE;
411
+ });
412
+ });
413
+
414
+ describe('getCallLog', () => {
415
+ test('should return error when user not found', async () => {
416
+ // Act
417
+ const result = await logHandler.getCallLog({
418
+ userId: 'non-existent-user',
419
+ sessionIds: 'session-1,session-2',
420
+ platform: 'testCRM',
421
+ requireDetails: false
422
+ });
423
+
424
+ // Assert
425
+ expect(result.successful).toBe(false);
426
+ expect(result.message).toBe('Contact not found');
427
+ });
428
+
429
+ test('should return error when no session IDs provided', async () => {
430
+ // Arrange
431
+ await UserModel.create({
432
+ id: 'test-user-id',
433
+ platform: 'testCRM',
434
+ accessToken: 'test-token'
435
+ });
436
+
437
+ // Act
438
+ const result = await logHandler.getCallLog({
439
+ userId: 'test-user-id',
440
+ sessionIds: null,
441
+ platform: 'testCRM',
442
+ requireDetails: false
443
+ });
444
+
445
+ // Assert
446
+ expect(result.successful).toBe(false);
447
+ expect(result.message).toBe('No session IDs provided');
448
+ });
449
+
450
+ test('should return matched logs without details', async () => {
451
+ // Arrange
452
+ await UserModel.create({
453
+ id: 'test-user-id',
454
+ platform: 'testCRM',
455
+ accessToken: 'test-token'
456
+ });
457
+
458
+ await CallLogModel.create({
459
+ id: 'call-1',
460
+ sessionId: 'session-1',
461
+ platform: 'testCRM',
462
+ thirdPartyLogId: 'log-1',
463
+ userId: 'test-user-id'
464
+ });
465
+
466
+ // Act
467
+ const result = await logHandler.getCallLog({
468
+ userId: 'test-user-id',
469
+ sessionIds: 'session-1,session-2',
470
+ platform: 'testCRM',
471
+ requireDetails: false
472
+ });
473
+
474
+ // Assert
475
+ expect(result.successful).toBe(true);
476
+ expect(result.logs).toHaveLength(2);
477
+ expect(result.logs[0]).toEqual({
478
+ sessionId: 'session-1',
479
+ matched: true,
480
+ logId: 'log-1'
481
+ });
482
+ expect(result.logs[1]).toEqual({
483
+ sessionId: 'session-2',
484
+ matched: false
485
+ });
486
+ });
487
+
488
+ test('should filter matched logs by extensionNumber when provided', async () => {
489
+ // Arrange
490
+ await UserModel.create({
491
+ id: 'test-user-id',
492
+ platform: 'testCRM',
493
+ accessToken: 'test-token'
494
+ });
495
+
496
+ await CallLogModel.create({
497
+ id: 'call-1',
498
+ sessionId: 'session-1',
499
+ extensionNumber: '101',
500
+ platform: 'testCRM',
501
+ thirdPartyLogId: 'log-101',
502
+ userId: 'test-user-id'
503
+ });
504
+ await CallLogModel.create({
505
+ id: 'call-1',
506
+ sessionId: 'session-1',
507
+ extensionNumber: '102',
508
+ platform: 'testCRM',
509
+ thirdPartyLogId: 'log-102',
510
+ userId: 'test-user-id'
511
+ });
512
+
513
+ // Act
514
+ const result = await logHandler.getCallLog({
515
+ userId: 'test-user-id',
516
+ sessionIds: 'session-1',
517
+ extensionNumber: '102',
518
+ platform: 'testCRM',
519
+ requireDetails: false
520
+ });
521
+
522
+ // Assert
523
+ expect(result.successful).toBe(true);
524
+ expect(result.logs).toEqual([{
525
+ sessionId: 'session-1',
526
+ matched: true,
527
+ logId: 'log-102'
528
+ }]);
529
+ });
530
+
531
+ test('should keep session-only lookup backward compatible when extensionNumber is empty', async () => {
532
+ // Arrange
533
+ await UserModel.create({
534
+ id: 'test-user-id',
535
+ platform: 'testCRM',
536
+ accessToken: 'test-token'
537
+ });
538
+
539
+ await CallLogModel.create({
540
+ id: 'call-1',
541
+ sessionId: 'session-1',
542
+ extensionNumber: '',
543
+ platform: 'testCRM',
544
+ thirdPartyLogId: 'legacy-log',
545
+ userId: 'test-user-id'
546
+ });
547
+ await CallLogModel.create({
548
+ id: 'call-1',
549
+ sessionId: 'session-1',
550
+ extensionNumber: '102',
551
+ platform: 'testCRM',
552
+ thirdPartyLogId: 'extension-log',
553
+ userId: 'test-user-id'
554
+ });
555
+
556
+ // Act
557
+ const result = await logHandler.getCallLog({
558
+ userId: 'test-user-id',
559
+ sessionIds: 'session-1',
560
+ platform: 'testCRM',
561
+ requireDetails: false
562
+ });
563
+
564
+ // Assert
565
+ expect(result.successful).toBe(true);
566
+ expect(result.logs).toEqual([{
567
+ sessionId: 'session-1',
568
+ matched: true,
569
+ logId: 'legacy-log'
570
+ }]);
571
+ });
572
+
573
+ test('should return matched logs with details when requireDetails is true', async () => {
574
+ // Arrange
575
+ await UserModel.create({
576
+ id: 'test-user-id',
577
+ platform: 'testCRM',
578
+ accessToken: 'test-token',
579
+ platformAdditionalInfo: {}
580
+ });
581
+
582
+ await CallLogModel.create({
583
+ id: 'call-1',
584
+ sessionId: 'session-1',
585
+ platform: 'testCRM',
586
+ thirdPartyLogId: 'log-1',
587
+ userId: 'test-user-id',
588
+ contactId: 'contact-1'
589
+ });
590
+
591
+ const mockConnector = {
592
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
593
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
594
+ getCallLog: jest.fn().mockResolvedValue({
595
+ callLogInfo: { subject: 'Test Call', note: 'Test note' },
596
+ returnMessage: { message: 'Success', messageType: 'success' }
597
+ })
598
+ };
599
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
600
+
601
+ // Act
602
+ const result = await logHandler.getCallLog({
603
+ userId: 'test-user-id',
604
+ sessionIds: 'session-1',
605
+ platform: 'testCRM',
606
+ requireDetails: true
607
+ });
608
+
609
+ // Assert
610
+ expect(result.successful).toBe(true);
611
+ expect(result.logs).toHaveLength(1);
612
+ expect(result.logs[0].matched).toBe(true);
613
+ expect(result.logs[0].logData).toEqual({ subject: 'Test Call', note: 'Test note' });
614
+ expect(mockConnector.getCallLog).toHaveBeenCalled();
615
+ });
616
+
617
+ test('should limit session IDs to 5', async () => {
618
+ // Arrange
619
+ await UserModel.create({
620
+ id: 'test-user-id',
621
+ platform: 'testCRM',
622
+ accessToken: 'test-token'
623
+ });
624
+
625
+ const sessionIds = 'session-1,session-2,session-3,session-4,session-5,session-6,session-7';
626
+
627
+ // Act
628
+ const result = await logHandler.getCallLog({
629
+ userId: 'test-user-id',
630
+ sessionIds,
631
+ platform: 'testCRM',
632
+ requireDetails: false
633
+ });
634
+
635
+ // Assert
636
+ expect(result.successful).toBe(true);
637
+ expect(result.logs).toHaveLength(5);
638
+ });
639
+
640
+ test('should skip session ID 0', async () => {
641
+ // Arrange
642
+ await UserModel.create({
643
+ id: 'test-user-id',
644
+ platform: 'testCRM',
645
+ accessToken: 'test-token',
646
+ platformAdditionalInfo: {}
647
+ });
648
+
649
+ const mockConnector = {
650
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
651
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
652
+ getCallLog: jest.fn()
653
+ };
654
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
655
+
656
+ // Act
657
+ const result = await logHandler.getCallLog({
658
+ userId: 'test-user-id',
659
+ sessionIds: '0,session-1',
660
+ platform: 'testCRM',
661
+ requireDetails: true
662
+ });
663
+
664
+ // Assert
665
+ expect(result.successful).toBe(true);
666
+ expect(result.logs[0]).toEqual({ sessionId: '0', matched: false });
667
+ });
668
+ });
669
+
670
+ describe('updateCallLog', () => {
671
+ test('should return unsuccessful when no existing call log found', async () => {
672
+ // Act
673
+ const result = await logHandler.updateCallLog({
674
+ platform: 'testCRM',
675
+ userId: 'test-user-id',
676
+ incomingData: { sessionId: 'non-existent-session' },
677
+ hashedAccountId: 'hashed-123',
678
+ isFromSSCL: false
679
+ });
680
+
681
+ // Assert
682
+ expect(result.successful).toBe(false);
683
+ });
684
+
685
+ test('should return error when user not found for update', async () => {
686
+ // Arrange
687
+ await CallLogModel.create({
688
+ id: 'call-1',
689
+ sessionId: 'session-1',
690
+ platform: 'testCRM',
691
+ thirdPartyLogId: 'log-1',
692
+ userId: 'test-user-id'
693
+ });
694
+
695
+ // Act
696
+ const result = await logHandler.updateCallLog({
697
+ platform: 'testCRM',
698
+ userId: 'non-existent-user',
699
+ incomingData: { sessionId: 'session-1' },
700
+ hashedAccountId: 'hashed-123',
701
+ isFromSSCL: false
702
+ });
703
+
704
+ // Assert
705
+ expect(result.successful).toBe(false);
706
+ expect(result.message).toBe('Contact not found');
707
+ });
708
+
709
+ test('should successfully update call log', async () => {
710
+ // Arrange
711
+ await UserModel.create({
712
+ id: 'test-user-id',
713
+ platform: 'testCRM',
714
+ accessToken: 'test-token',
715
+ platformAdditionalInfo: {}
716
+ });
717
+
718
+ await CallLogModel.create({
719
+ id: 'call-1',
720
+ sessionId: 'session-1',
721
+ platform: 'testCRM',
722
+ thirdPartyLogId: 'log-1',
723
+ userId: 'test-user-id',
724
+ contactId: 'contact-1'
725
+ });
726
+
727
+ const mockConnector = {
728
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
729
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
730
+ getLogFormatType: jest.fn().mockReturnValue('text/plain'),
731
+ getCallLog: jest.fn().mockResolvedValue({
732
+ callLogInfo: { fullBody: 'Existing body', note: 'Existing note' }
733
+ }),
734
+ updateCallLog: jest.fn().mockResolvedValue({
735
+ updatedNote: 'Updated note',
736
+ returnMessage: { message: 'Updated', messageType: 'success', ttl: 2000 }
737
+ })
738
+ };
739
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
740
+ composeCallLog.mockReturnValue('Updated composed log');
741
+
742
+ const incomingData = {
743
+ sessionId: 'session-1',
744
+ note: 'Updated note',
745
+ subject: 'Updated subject',
746
+ startTime: new Date().toISOString(),
747
+ duration: 180,
748
+ result: 'Completed'
749
+ };
750
+
751
+ // Act
752
+ const result = await logHandler.updateCallLog({
753
+ platform: 'testCRM',
754
+ userId: 'test-user-id',
755
+ incomingData,
756
+ hashedAccountId: 'hashed-123',
757
+ isFromSSCL: false
758
+ });
759
+
760
+ // Assert
761
+ expect(result.successful).toBe(true);
762
+ expect(result.logId).toBe('log-1');
763
+ expect(result.updatedNote).toBe('Updated note');
764
+ expect(mockConnector.updateCallLog).toHaveBeenCalled();
765
+ });
766
+ });
767
+
768
+ describe('createMessageLog', () => {
769
+ test('should return warning when no messages to log', async () => {
770
+ // Act
771
+ const result = await logHandler.createMessageLog({
772
+ platform: 'testCRM',
773
+ userId: 'test-user-id',
774
+ incomingData: {
775
+ logInfo: { messages: [] }
776
+ }
777
+ });
778
+
779
+ // Assert
780
+ expect(result.successful).toBe(false);
781
+ expect(result.returnMessage.message).toBe('No message to log.');
782
+ });
783
+
784
+ test('should return warning when user not found', async () => {
785
+ // Act
786
+ const result = await logHandler.createMessageLog({
787
+ platform: 'testCRM',
788
+ userId: 'non-existent-user',
789
+ incomingData: {
790
+ logInfo: {
791
+ messages: [{ id: 'msg-1', subject: 'Test', creationTime: new Date() }],
792
+ correspondents: [{ phoneNumber: '+1234567890' }]
793
+ }
794
+ }
795
+ });
796
+
797
+ // Assert
798
+ expect(result.successful).toBe(false);
799
+ expect(result.returnMessage.message).toBe('Contact not found');
800
+ });
801
+
802
+ test('should return warning when contact not found', async () => {
803
+ // Arrange
804
+ await UserModel.create({
805
+ id: 'test-user-id',
806
+ platform: 'testCRM',
807
+ accessToken: 'test-token',
808
+ platformAdditionalInfo: {}
809
+ });
810
+
811
+ const mockConnector = {
812
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
813
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded')
814
+ };
815
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
816
+
817
+ // Act
818
+ const result = await logHandler.createMessageLog({
819
+ platform: 'testCRM',
820
+ userId: 'test-user-id',
821
+ incomingData: {
822
+ logInfo: {
823
+ messages: [{ id: 'msg-1', subject: 'Test', creationTime: new Date() }],
824
+ correspondents: [{ phoneNumber: '+1234567890' }]
825
+ },
826
+ contactId: null
827
+ }
828
+ });
829
+
830
+ // Assert
831
+ expect(result.successful).toBe(false);
832
+ expect(result.returnMessage.message).toContain('Contact not found for number');
833
+ });
834
+
835
+ test('should successfully create message log', async () => {
836
+ // Arrange
837
+ await UserModel.create({
838
+ id: 'test-user-id',
839
+ platform: 'testCRM',
840
+ accessToken: 'test-token',
841
+ platformAdditionalInfo: {}
842
+ });
843
+
844
+ const mockConnector = {
845
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
846
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
847
+ createMessageLog: jest.fn().mockResolvedValue({
848
+ logId: 'msg-log-123',
849
+ returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
850
+ }),
851
+ updateMessageLog: jest.fn()
852
+ };
853
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
854
+
855
+ const incomingData = {
856
+ logInfo: {
857
+ messages: [{ id: 'msg-1', subject: 'Test SMS', direction: 'Outbound', creationTime: new Date() }],
858
+ correspondents: [{ phoneNumber: '+1234567890' }],
859
+ conversationId: 'conv-123',
860
+ conversationLogId: 'conv-log-123'
861
+ },
862
+ contactId: 'contact-123',
863
+ contactType: 'Contact',
864
+ contactName: 'Test Contact',
865
+ additionalSubmission: {}
866
+ };
867
+
868
+ // Act
869
+ const result = await logHandler.createMessageLog({
870
+ platform: 'testCRM',
871
+ userId: 'test-user-id',
872
+ incomingData
873
+ });
874
+
875
+ // Assert
876
+ expect(result.successful).toBe(true);
877
+ expect(result.logIds).toContain('msg-1');
878
+ expect(mockConnector.createMessageLog).toHaveBeenCalled();
879
+
880
+ // Verify message log was saved
881
+ const savedLog = await MessageLogModel.findOne({ where: { id: 'msg-1' } });
882
+ expect(savedLog).not.toBeNull();
883
+ expect(savedLog.thirdPartyLogId).toBe('msg-log-123');
884
+ });
885
+
886
+ test('should skip already logged messages', async () => {
887
+ // Arrange
888
+ await UserModel.create({
889
+ id: 'test-user-id',
890
+ platform: 'testCRM',
891
+ accessToken: 'test-token',
892
+ platformAdditionalInfo: {}
893
+ });
894
+
895
+ // Create existing message log with same conversationLogId
896
+ await MessageLogModel.create({
897
+ id: 'msg-1',
898
+ platform: 'testCRM',
899
+ conversationId: 'conv-123',
900
+ conversationLogId: 'conv-log-123',
901
+ thirdPartyLogId: 'existing-log',
902
+ userId: 'test-user-id'
903
+ });
904
+
905
+ const mockConnector = {
906
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
907
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
908
+ createMessageLog: jest.fn().mockResolvedValue({
909
+ logId: 'msg-log-new',
910
+ returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
911
+ }),
912
+ updateMessageLog: jest.fn().mockResolvedValue({
913
+ returnMessage: { message: 'Message updated', messageType: 'success', ttl: 2000 }
914
+ })
915
+ };
916
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
917
+
918
+ const incomingData = {
919
+ logInfo: {
920
+ messages: [
921
+ { id: 'msg-1', subject: 'Already logged', direction: 'Outbound', creationTime: new Date() },
922
+ { id: 'msg-2', subject: 'New message', direction: 'Outbound', creationTime: new Date() }
923
+ ],
924
+ correspondents: [{ phoneNumber: '+1234567890' }],
925
+ conversationId: 'conv-123',
926
+ conversationLogId: 'conv-log-123' // Same conversationLogId as existing record
927
+ },
928
+ contactId: 'contact-123',
929
+ contactType: 'Contact',
930
+ additionalSubmission: {}
931
+ };
932
+
933
+ // Act
934
+ const result = await logHandler.createMessageLog({
935
+ platform: 'testCRM',
936
+ userId: 'test-user-id',
937
+ incomingData
938
+ });
939
+
940
+ // Assert
941
+ expect(result.successful).toBe(true);
942
+ // msg-1 is skipped (already logged), msg-2 uses updateMessageLog because same conversationLogId exists
943
+ expect(mockConnector.createMessageLog).toHaveBeenCalledTimes(0);
944
+ expect(mockConnector.updateMessageLog).toHaveBeenCalledTimes(1);
945
+ });
946
+
947
+ test('should handle group SMS with contactId suffix for message IDs', async () => {
948
+ // Arrange - group SMS has multiple correspondents
949
+ await UserModel.create({
950
+ id: 'test-user-id',
951
+ platform: 'testCRM',
952
+ accessToken: 'test-token',
953
+ rcAccountId: 'rc-account-123',
954
+ platformAdditionalInfo: {}
955
+ });
956
+
957
+ const mockConnector = {
958
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
959
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
960
+ createMessageLog: jest.fn().mockResolvedValue({
961
+ logId: 'msg-log-group-123',
962
+ returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
963
+ }),
964
+ updateMessageLog: jest.fn()
965
+ };
966
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
967
+
968
+ const incomingData = {
969
+ logInfo: {
970
+ messages: [{ id: 'msg-group-1', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
971
+ correspondents: [
972
+ { phoneNumber: '+1234567890' },
973
+ { phoneNumber: '+0987654321' }
974
+ ],
975
+ conversationId: 'conv-group-123',
976
+ conversationLogId: 'conv-log-group-123'
977
+ },
978
+ contactId: 'contact-456',
979
+ contactType: 'Contact',
980
+ contactName: 'Primary Contact',
981
+ additionalSubmission: {}
982
+ };
983
+
984
+ // Act
985
+ const result = await logHandler.createMessageLog({
986
+ platform: 'testCRM',
987
+ userId: 'test-user-id',
988
+ incomingData
989
+ });
990
+
991
+ // Assert
992
+ expect(result.successful).toBe(true);
993
+ expect(result.logIds).toContain('msg-group-1-contact-456');
994
+ const savedLog = await MessageLogModel.findOne({ where: { id: 'msg-group-1-contact-456' } });
995
+ expect(savedLog).not.toBeNull();
996
+ expect(savedLog.thirdPartyLogId).toBe('msg-log-group-123');
997
+ });
998
+
999
+ test('should pass correspondents to createMessageLog when group SMS has different contact names', async () => {
1000
+ // Arrange - correspondent in cache with different name
1001
+ await UserModel.create({
1002
+ id: 'test-user-id',
1003
+ platform: 'testCRM',
1004
+ accessToken: 'test-token',
1005
+ rcAccountId: 'rc-account-123',
1006
+ platformAdditionalInfo: {}
1007
+ });
1008
+
1009
+ await AccountDataModel.create({
1010
+ rcAccountId: 'rc-account-123',
1011
+ platformName: 'testCRM',
1012
+ dataKey: 'contact-+0987654321',
1013
+ data: [{ name: 'Other Contact', id: 'contact-789' }]
1014
+ });
1015
+
1016
+ const mockConnector = {
1017
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
1018
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1019
+ createMessageLog: jest.fn().mockResolvedValue({
1020
+ logId: 'msg-log-correspondents',
1021
+ returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
1022
+ }),
1023
+ updateMessageLog: jest.fn()
1024
+ };
1025
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
1026
+
1027
+ const incomingData = {
1028
+ logInfo: {
1029
+ messages: [{ id: 'msg-correspondents', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
1030
+ correspondents: [
1031
+ { phoneNumber: '+1234567890' },
1032
+ { phoneNumber: '+0987654321' }
1033
+ ],
1034
+ conversationId: 'conv-correspondents',
1035
+ conversationLogId: 'conv-log-correspondents'
1036
+ },
1037
+ contactId: 'contact-456',
1038
+ contactType: 'Contact',
1039
+ contactName: 'Primary Contact',
1040
+ additionalSubmission: {}
1041
+ };
1042
+
1043
+ // Act
1044
+ await logHandler.createMessageLog({
1045
+ platform: 'testCRM',
1046
+ userId: 'test-user-id',
1047
+ incomingData
1048
+ });
1049
+
1050
+ // Assert - createMessageLog should receive correspondents with different name
1051
+ expect(mockConnector.createMessageLog).toHaveBeenCalledWith(
1052
+ expect.objectContaining({
1053
+ correspondents: [[{ name: 'Other Contact', id: 'contact-789' }]]
1054
+ })
1055
+ );
1056
+ });
1057
+
1058
+ test('should not add correspondent when name matches contactName in group SMS', async () => {
1059
+ // Arrange - correspondent in cache with same name as contactName
1060
+ await UserModel.create({
1061
+ id: 'test-user-id',
1062
+ platform: 'testCRM',
1063
+ accessToken: 'test-token',
1064
+ rcAccountId: 'rc-account-123',
1065
+ platformAdditionalInfo: {}
1066
+ });
1067
+
1068
+ await AccountDataModel.create({
1069
+ rcAccountId: 'rc-account-123',
1070
+ platformName: 'testCRM',
1071
+ dataKey: 'contact-+0987654321',
1072
+ data: [{ name: 'Primary Contact', id: 'contact-789' }]
1073
+ });
1074
+
1075
+ const mockConnector = {
1076
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
1077
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1078
+ createMessageLog: jest.fn().mockResolvedValue({
1079
+ logId: 'msg-log-same-name',
1080
+ returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
1081
+ }),
1082
+ updateMessageLog: jest.fn()
1083
+ };
1084
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
1085
+
1086
+ const incomingData = {
1087
+ logInfo: {
1088
+ messages: [{ id: 'msg-same-name', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
1089
+ correspondents: [
1090
+ { phoneNumber: '+1234567890' },
1091
+ { phoneNumber: '+0987654321' }
1092
+ ],
1093
+ conversationId: 'conv-same-name',
1094
+ conversationLogId: 'conv-log-same-name'
1095
+ },
1096
+ contactId: 'contact-456',
1097
+ contactType: 'Contact',
1098
+ contactName: 'Primary Contact',
1099
+ additionalSubmission: {}
1100
+ };
1101
+
1102
+ // Act
1103
+ await logHandler.createMessageLog({
1104
+ platform: 'testCRM',
1105
+ userId: 'test-user-id',
1106
+ incomingData
1107
+ });
1108
+
1109
+ // Assert - correspondents should be empty when names match
1110
+ expect(mockConnector.createMessageLog).toHaveBeenCalledWith(
1111
+ expect.objectContaining({
1112
+ correspondents: []
1113
+ })
1114
+ );
1115
+ });
1116
+
1117
+ test('should use suffixed conversationLogId and conversationId for group SMS', async () => {
1118
+ // Arrange
1119
+ await UserModel.create({
1120
+ id: 'test-user-id',
1121
+ platform: 'testCRM',
1122
+ accessToken: 'test-token',
1123
+ rcAccountId: 'rc-account-123',
1124
+ platformAdditionalInfo: {}
1125
+ });
1126
+
1127
+ const mockConnector = {
1128
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
1129
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1130
+ createMessageLog: jest.fn().mockResolvedValue({
1131
+ logId: 'msg-log-suffix',
1132
+ returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
1133
+ }),
1134
+ updateMessageLog: jest.fn()
1135
+ };
1136
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
1137
+
1138
+ const incomingData = {
1139
+ logInfo: {
1140
+ messages: [{ id: 'msg-suffix', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
1141
+ correspondents: [
1142
+ { phoneNumber: '+1234567890' },
1143
+ { phoneNumber: '+0987654321' }
1144
+ ],
1145
+ conversationId: 'conv-original',
1146
+ conversationLogId: 'conv-log-original'
1147
+ },
1148
+ contactId: 'contact-999',
1149
+ contactType: 'Contact',
1150
+ contactName: 'Test Contact',
1151
+ additionalSubmission: {}
1152
+ };
1153
+
1154
+ // Act
1155
+ const result = await logHandler.createMessageLog({
1156
+ platform: 'testCRM',
1157
+ userId: 'test-user-id',
1158
+ incomingData
1159
+ });
1160
+
1161
+ // Assert - message log saved with suffixed conversationLogId
1162
+ expect(result.successful).toBe(true);
1163
+ const savedLog = await MessageLogModel.findOne({ where: { id: 'msg-suffix-contact-999' } });
1164
+ expect(savedLog).not.toBeNull();
1165
+ expect(savedLog.conversationLogId).toBe('conv-log-original-contact-999');
1166
+ expect(savedLog.conversationId).toBe('conv-original-contact-999');
1167
+ });
1168
+ });
1169
+
1170
+ describe('saveNoteCache', () => {
1171
+ test('should successfully save note cache', async () => {
1172
+ // Arrange
1173
+ NoteCache.create.mockResolvedValue({ sessionId: 'session-123', note: 'Test note' });
1174
+
1175
+ // Act
1176
+ const result = await logHandler.saveNoteCache({
1177
+ sessionId: 'session-123',
1178
+ note: 'Test note'
1179
+ });
1180
+
1181
+ // Assert
1182
+ expect(result.successful).toBe(true);
1183
+ expect(result.returnMessage).toBe('Note cache saved');
1184
+ expect(NoteCache.create).toHaveBeenCalled();
1185
+ });
1186
+
1187
+ test('should handle errors when saving note cache', async () => {
1188
+ // Arrange
1189
+ NoteCache.create.mockRejectedValue(new Error('DynamoDB error'));
1190
+
1191
+ // Act
1192
+ const result = await logHandler.saveNoteCache({
1193
+ sessionId: 'session-123',
1194
+ note: 'Test note'
1195
+ });
1196
+
1197
+ // Assert
1198
+ expect(result.successful).toBe(false);
1199
+ expect(result.returnMessage.message).toBe('Error performing saveNoteCache');
1200
+ expect(result.returnMessage.messageType).toBe('warning');
1201
+ });
1202
+ });
1203
+
1204
+ describe('Error Handling', () => {
1205
+ test('should handle 429 rate limit error in createCallLog', async () => {
1206
+ // Arrange
1207
+ await UserModel.create({
1208
+ id: 'test-user-id',
1209
+ platform: 'testCRM',
1210
+ accessToken: 'test-token',
1211
+ platformAdditionalInfo: {}
1212
+ });
1213
+
1214
+ const mockConnector = {
1215
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
1216
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1217
+ getLogFormatType: jest.fn().mockReturnValue('text/plain'),
1218
+ createCallLog: jest.fn().mockRejectedValue({
1219
+ response: { status: 429 }
1220
+ })
1221
+ };
1222
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
1223
+ composeCallLog.mockReturnValue('Composed log');
1224
+
1225
+ const incomingData = {
1226
+ logInfo: {
1227
+ sessionId: 'session-rate-limit',
1228
+ telephonySessionId: 'tel-session',
1229
+ direction: 'Outbound',
1230
+ from: { phoneNumber: '+1234567890' },
1231
+ to: { phoneNumber: '+0987654321' }
1232
+ },
1233
+ contactId: 'contact-123',
1234
+ note: 'Test'
1235
+ };
1236
+
1237
+ // Act
1238
+ const result = await logHandler.createCallLog({
1239
+ platform: 'testCRM',
1240
+ userId: 'test-user-id',
1241
+ incomingData,
1242
+ hashedAccountId: 'hashed-123',
1243
+ isFromSSCL: false
1244
+ });
1245
+
1246
+ // Assert
1247
+ expect(result.successful).toBe(false);
1248
+ expect(result.returnMessage.messageType).toBe('warning');
1249
+ });
1250
+
1251
+ test('should handle 401 authorization error in createCallLog', async () => {
1252
+ // Arrange
1253
+ await UserModel.create({
1254
+ id: 'test-user-id',
1255
+ platform: 'testCRM',
1256
+ accessToken: 'test-token',
1257
+ platformAdditionalInfo: {}
1258
+ });
1259
+
1260
+ const mockConnector = {
1261
+ getAuthType: jest.fn().mockResolvedValue('apiKey'),
1262
+ getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
1263
+ getLogFormatType: jest.fn().mockReturnValue('text/plain'),
1264
+ createCallLog: jest.fn().mockRejectedValue({
1265
+ response: { status: 401 }
1266
+ })
1267
+ };
1268
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
1269
+ composeCallLog.mockReturnValue('Composed log');
1270
+
1271
+ const incomingData = {
1272
+ logInfo: {
1273
+ sessionId: 'session-auth-error',
1274
+ telephonySessionId: 'tel-session',
1275
+ direction: 'Outbound',
1276
+ from: { phoneNumber: '+1234567890' },
1277
+ to: { phoneNumber: '+0987654321' }
1278
+ },
1279
+ contactId: 'contact-123',
1280
+ note: 'Test'
1281
+ };
1282
+
1283
+ // Act
1284
+ const result = await logHandler.createCallLog({
1285
+ platform: 'testCRM',
1286
+ userId: 'test-user-id',
1287
+ incomingData,
1288
+ hashedAccountId: 'hashed-123',
1289
+ isFromSSCL: false
1290
+ });
1291
+
1292
+ // Assert
1293
+ expect(result.successful).toBe(false);
1294
+ expect(result.extraDataTracking.statusCode).toBe(401);
1295
+ });
1296
+ });
1297
+ });
1298
+