@app-connect/core 1.7.16 → 1.7.17
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.
- package/handlers/admin.js +23 -1
- package/handlers/contact.js +33 -0
- package/handlers/disposition.js +11 -0
- package/handlers/log.js +82 -7
- package/index.js +108 -55
- package/lib/oauth.js +22 -15
- package/package.json +1 -1
- package/releaseNotes.json +16 -0
- package/test/handlers/log.test.js +225 -0
- package/test/setup.js +176 -176
package/handlers/admin.js
CHANGED
|
@@ -109,7 +109,7 @@ async function getAdminReport({ rcAccountId, timezone, timeFrom, timeTo, groupBy
|
|
|
109
109
|
var callLogStats = [];
|
|
110
110
|
var itemKeys = [];
|
|
111
111
|
for (const record of callsAggregationData.data.records) {
|
|
112
|
-
if(!record?.info?.name){
|
|
112
|
+
if (!record?.info?.name) {
|
|
113
113
|
continue;
|
|
114
114
|
}
|
|
115
115
|
itemKeys.push(record.info.name);
|
|
@@ -238,6 +238,17 @@ async function getUserMapping({ user, hashedRcAccountId, rcExtensionList }) {
|
|
|
238
238
|
const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname, proxyId, proxyConfig })));
|
|
239
239
|
// eslint-disable-next-line no-param-reassign
|
|
240
240
|
user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
|
|
241
|
+
if (!user) {
|
|
242
|
+
return {
|
|
243
|
+
successful: false,
|
|
244
|
+
returnMessage: {
|
|
245
|
+
message: `User session expired. Please connect again.`,
|
|
246
|
+
messageType: 'warning',
|
|
247
|
+
ttl: 5000
|
|
248
|
+
},
|
|
249
|
+
isRevokeUserSession: true
|
|
250
|
+
}
|
|
251
|
+
}
|
|
241
252
|
authHeader = `Bearer ${user.accessToken}`;
|
|
242
253
|
break;
|
|
243
254
|
case 'apiKey':
|
|
@@ -399,6 +410,17 @@ async function reinitializeUserMapping({ user, hashedRcAccountId, rcExtensionLis
|
|
|
399
410
|
const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname, proxyId, proxyConfig })));
|
|
400
411
|
// eslint-disable-next-line no-param-reassign
|
|
401
412
|
user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
|
|
413
|
+
if (!user) {
|
|
414
|
+
return {
|
|
415
|
+
successful: false,
|
|
416
|
+
returnMessage: {
|
|
417
|
+
message: `User session expired. Please connect again.`,
|
|
418
|
+
messageType: 'warning',
|
|
419
|
+
ttl: 5000
|
|
420
|
+
},
|
|
421
|
+
isRevokeUserSession: true
|
|
422
|
+
}
|
|
423
|
+
}
|
|
402
424
|
authHeader = `Bearer ${user.accessToken}`;
|
|
403
425
|
break;
|
|
404
426
|
case 'apiKey':
|
package/handlers/contact.js
CHANGED
|
@@ -56,6 +56,17 @@ async function findContact({ platform, userId, phoneNumber, overridingFormat, is
|
|
|
56
56
|
case 'oauth':
|
|
57
57
|
const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname, proxyId, proxyConfig })));
|
|
58
58
|
user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
|
|
59
|
+
if (!user) {
|
|
60
|
+
return {
|
|
61
|
+
successful: false,
|
|
62
|
+
returnMessage: {
|
|
63
|
+
message: `User session expired. Please connect again.`,
|
|
64
|
+
messageType: 'warning',
|
|
65
|
+
ttl: 5000
|
|
66
|
+
},
|
|
67
|
+
isRevokeUserSession: true
|
|
68
|
+
}
|
|
69
|
+
}
|
|
59
70
|
authHeader = `Bearer ${user.accessToken}`;
|
|
60
71
|
tracer?.trace('handler.findContact:oauthAuth', { authHeader });
|
|
61
72
|
break;
|
|
@@ -153,6 +164,17 @@ async function createContact({ platform, userId, phoneNumber, newContactName, ne
|
|
|
153
164
|
case 'oauth':
|
|
154
165
|
const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname, proxyId, proxyConfig })));
|
|
155
166
|
user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
|
|
167
|
+
if (!user) {
|
|
168
|
+
return {
|
|
169
|
+
successful: false,
|
|
170
|
+
returnMessage: {
|
|
171
|
+
message: `User session expired. Please connect again.`,
|
|
172
|
+
messageType: 'warning',
|
|
173
|
+
ttl: 5000
|
|
174
|
+
},
|
|
175
|
+
isRevokeUserSession: true
|
|
176
|
+
}
|
|
177
|
+
}
|
|
156
178
|
authHeader = `Bearer ${user.accessToken}`;
|
|
157
179
|
break;
|
|
158
180
|
case 'apiKey':
|
|
@@ -202,6 +224,17 @@ async function findContactWithName({ platform, userId, name }) {
|
|
|
202
224
|
case 'oauth':
|
|
203
225
|
const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname, proxyId, proxyConfig })));
|
|
204
226
|
user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
|
|
227
|
+
if (!user) {
|
|
228
|
+
return {
|
|
229
|
+
successful: false,
|
|
230
|
+
returnMessage: {
|
|
231
|
+
message: `User session expired. Please connect again.`,
|
|
232
|
+
messageType: 'warning',
|
|
233
|
+
ttl: 5000
|
|
234
|
+
},
|
|
235
|
+
isRevokeUserSession: true
|
|
236
|
+
}
|
|
237
|
+
}
|
|
205
238
|
authHeader = `Bearer ${user.accessToken}`;
|
|
206
239
|
break;
|
|
207
240
|
case 'apiKey':
|
package/handlers/disposition.js
CHANGED
|
@@ -45,6 +45,17 @@ async function upsertCallDisposition({ platform, userId, sessionId, dispositions
|
|
|
45
45
|
case 'oauth':
|
|
46
46
|
const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname, proxyId, proxyConfig })));
|
|
47
47
|
user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
|
|
48
|
+
if (!user) {
|
|
49
|
+
return {
|
|
50
|
+
successful: false,
|
|
51
|
+
returnMessage: {
|
|
52
|
+
message: `User session expired. Please connect again.`,
|
|
53
|
+
messageType: 'warning',
|
|
54
|
+
ttl: 5000
|
|
55
|
+
},
|
|
56
|
+
isRevokeUserSession: true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
48
59
|
authHeader = `Bearer ${user.accessToken}`;
|
|
49
60
|
break;
|
|
50
61
|
case 'apiKey':
|
package/handlers/log.js
CHANGED
|
@@ -13,6 +13,7 @@ const moment = require('moment');
|
|
|
13
13
|
const { getMediaReaderLinkByPlatformMediaLink } = require('../lib/util');
|
|
14
14
|
const logger = require('../lib/logger');
|
|
15
15
|
const { handleApiError, handleDatabaseError } = require('../lib/errorHandler');
|
|
16
|
+
const { AccountDataModel } = require('../models/accountDataModel');
|
|
16
17
|
|
|
17
18
|
async function createCallLog({ platform, userId, incomingData, hashedAccountId, isFromSSCL }) {
|
|
18
19
|
try {
|
|
@@ -77,6 +78,17 @@ async function createCallLog({ platform, userId, incomingData, hashedAccountId,
|
|
|
77
78
|
case 'oauth':
|
|
78
79
|
const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname, proxyId, proxyConfig })));
|
|
79
80
|
user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
|
|
81
|
+
if (!user) {
|
|
82
|
+
return {
|
|
83
|
+
successful: false,
|
|
84
|
+
returnMessage: {
|
|
85
|
+
message: `User session expired. Please connect again.`,
|
|
86
|
+
messageType: 'warning',
|
|
87
|
+
ttl: 5000
|
|
88
|
+
},
|
|
89
|
+
isRevokeUserSession: true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
80
92
|
authHeader = `Bearer ${user.accessToken}`;
|
|
81
93
|
break;
|
|
82
94
|
case 'apiKey':
|
|
@@ -206,6 +218,17 @@ async function getCallLog({ userId, sessionIds, platform, requireDetails }) {
|
|
|
206
218
|
case 'oauth':
|
|
207
219
|
const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname, proxyId, proxyConfig })));
|
|
208
220
|
user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
|
|
221
|
+
if (!user) {
|
|
222
|
+
return {
|
|
223
|
+
successful: false,
|
|
224
|
+
returnMessage: {
|
|
225
|
+
message: `User session expired. Please connect again.`,
|
|
226
|
+
messageType: 'warning',
|
|
227
|
+
ttl: 5000
|
|
228
|
+
},
|
|
229
|
+
isRevokeUserSession: true
|
|
230
|
+
}
|
|
231
|
+
}
|
|
209
232
|
authHeader = `Bearer ${user.accessToken}`;
|
|
210
233
|
break;
|
|
211
234
|
case 'apiKey':
|
|
@@ -292,6 +315,17 @@ async function updateCallLog({ platform, userId, incomingData, hashedAccountId,
|
|
|
292
315
|
case 'oauth':
|
|
293
316
|
const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname, proxyId, proxyConfig })));
|
|
294
317
|
user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
|
|
318
|
+
if (!user) {
|
|
319
|
+
return {
|
|
320
|
+
successful: false,
|
|
321
|
+
returnMessage: {
|
|
322
|
+
message: `User session expired. Please connect again.`,
|
|
323
|
+
messageType: 'warning',
|
|
324
|
+
ttl: 5000
|
|
325
|
+
},
|
|
326
|
+
isRevokeUserSession: true
|
|
327
|
+
}
|
|
328
|
+
}
|
|
295
329
|
authHeader = `Bearer ${user.accessToken}`;
|
|
296
330
|
break;
|
|
297
331
|
case 'apiKey':
|
|
@@ -382,7 +416,7 @@ async function updateCallLog({ platform, userId, incomingData, hashedAccountId,
|
|
|
382
416
|
isFromSSCL,
|
|
383
417
|
proxyConfig,
|
|
384
418
|
});
|
|
385
|
-
if(!extraDataTracking){
|
|
419
|
+
if (!extraDataTracking) {
|
|
386
420
|
extraDataTracking = {};
|
|
387
421
|
}
|
|
388
422
|
extraDataTracking.withSmartNoteLog = !!incomingData.aiNote;
|
|
@@ -442,6 +476,17 @@ async function createMessageLog({ platform, userId, incomingData }) {
|
|
|
442
476
|
case 'oauth':
|
|
443
477
|
const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname, proxyId, proxyConfig })));
|
|
444
478
|
user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
|
|
479
|
+
if (!user) {
|
|
480
|
+
return {
|
|
481
|
+
successful: false,
|
|
482
|
+
returnMessage: {
|
|
483
|
+
message: `User session expired. Please connect again.`,
|
|
484
|
+
messageType: 'warning',
|
|
485
|
+
ttl: 5000
|
|
486
|
+
},
|
|
487
|
+
isRevokeUserSession: true
|
|
488
|
+
}
|
|
489
|
+
}
|
|
445
490
|
authHeader = `Bearer ${user.accessToken}`;
|
|
446
491
|
break;
|
|
447
492
|
case 'apiKey':
|
|
@@ -467,12 +512,33 @@ async function createMessageLog({ platform, userId, incomingData }) {
|
|
|
467
512
|
type: incomingData.contactType ?? "",
|
|
468
513
|
name: incomingData.contactName ?? ""
|
|
469
514
|
};
|
|
515
|
+
const isGroupSMS = incomingData.logInfo.correspondents.length > 1;
|
|
470
516
|
// For shared SMS
|
|
471
517
|
const assigneeName = incomingData.logInfo.assignee?.name;
|
|
472
518
|
const ownerName = incomingData.logInfo.owner?.name;
|
|
473
519
|
const isSharedSMS = !!ownerName;
|
|
474
520
|
|
|
475
|
-
|
|
521
|
+
let messageIds = [];
|
|
522
|
+
const correspondents = [];
|
|
523
|
+
if (isGroupSMS) {
|
|
524
|
+
messageIds = incomingData.logInfo.messages.map(m => { return { id: m.id.toString() + `-${incomingData.contactId}` }; });
|
|
525
|
+
for (var i = 0; i < incomingData.logInfo.correspondents.length; i++) {
|
|
526
|
+
// find cached contact by composite key; findByPk expects raw PK values, so use where clause
|
|
527
|
+
const correspondentContactInfo = await AccountDataModel.findOne({
|
|
528
|
+
where: {
|
|
529
|
+
rcAccountId: user.rcAccountId,
|
|
530
|
+
platformName: platform,
|
|
531
|
+
dataKey: `contact-${incomingData.logInfo.correspondents[i].phoneNumber}`
|
|
532
|
+
}
|
|
533
|
+
})
|
|
534
|
+
if (correspondentContactInfo && correspondentContactInfo.data[0]?.name != incomingData.contactName) {
|
|
535
|
+
correspondents.push(correspondentContactInfo.data);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
messageIds = incomingData.logInfo.messages.map(m => { return { id: m.id.toString() }; });
|
|
541
|
+
}
|
|
476
542
|
let existingMessages = null;
|
|
477
543
|
try {
|
|
478
544
|
existingMessages = await MessageLogModel.findAll({
|
|
@@ -520,9 +586,18 @@ async function createMessageLog({ platform, userId, incomingData }) {
|
|
|
520
586
|
}
|
|
521
587
|
// Case: normal SMS
|
|
522
588
|
else {
|
|
589
|
+
if (isGroupSMS) {
|
|
590
|
+
// eslint-disable-next-line no-param-reassign
|
|
591
|
+
incomingData.logInfo.conversationLogId = incomingData.logInfo.conversationLogId + `-${incomingData.contactId}`;
|
|
592
|
+
// eslint-disable-next-line no-param-reassign
|
|
593
|
+
incomingData.logInfo.conversationId = incomingData.logInfo.conversationId + `-${incomingData.contactId}`;
|
|
594
|
+
}
|
|
523
595
|
// reverse the order of messages to log the oldest message first
|
|
524
596
|
const reversedMessages = incomingData.logInfo.messages.reverse();
|
|
525
597
|
for (const message of reversedMessages) {
|
|
598
|
+
if (isGroupSMS) {
|
|
599
|
+
message.id = message.id.toString() + `-${incomingData.contactId}`;
|
|
600
|
+
}
|
|
526
601
|
if (existingIds.includes(message.id.toString())) {
|
|
527
602
|
continue;
|
|
528
603
|
}
|
|
@@ -532,14 +607,14 @@ async function createMessageLog({ platform, userId, incomingData }) {
|
|
|
532
607
|
}
|
|
533
608
|
let faxDocLink = null;
|
|
534
609
|
let faxDownloadLink = null;
|
|
535
|
-
if (message.attachments && message.attachments.some(a => a.type === 'RenderedDocument')) {
|
|
610
|
+
if (message.attachments && message.attachments.some(a => a.type === 'RenderedDocument') && incomingData.logInfo.rcAccessToken) {
|
|
536
611
|
faxDocLink = message.attachments.find(a => a.type === 'RenderedDocument').link;
|
|
537
612
|
faxDownloadLink = message.attachments.find(a => a.type === 'RenderedDocument').uri + `?access_token=${incomingData.logInfo.rcAccessToken}`
|
|
538
613
|
}
|
|
539
614
|
let imageLink = null;
|
|
540
615
|
let imageDownloadLink = null;
|
|
541
616
|
let imageContentType = null;
|
|
542
|
-
if (message.attachments && message.attachments.some(a => a.type === 'MmsAttachment' && a.contentType.startsWith('image/'))) {
|
|
617
|
+
if (message.attachments && message.attachments.some(a => a.type === 'MmsAttachment' && a.contentType.startsWith('image/')) && incomingData.logInfo.rcAccessToken) {
|
|
543
618
|
const imageAttachment = message.attachments.find(a => a.type === 'MmsAttachment' && a.contentType.startsWith('image/'));
|
|
544
619
|
if (imageAttachment) {
|
|
545
620
|
imageLink = getMediaReaderLinkByPlatformMediaLink(imageAttachment?.uri);
|
|
@@ -563,15 +638,15 @@ async function createMessageLog({ platform, userId, incomingData }) {
|
|
|
563
638
|
conversationLogId: incomingData.logInfo.conversationLogId
|
|
564
639
|
}
|
|
565
640
|
});
|
|
566
|
-
|
|
641
|
+
let crmLogId = ''
|
|
567
642
|
if (existingSameDateMessageLog) {
|
|
568
|
-
const updateMessageResult = await platformModule.updateMessageLog({ user, contactInfo, assigneeName, ownerName, existingMessageLog: existingSameDateMessageLog, message, authHeader, additionalSubmission, imageLink, videoLink, proxyConfig });
|
|
643
|
+
const updateMessageResult = await platformModule.updateMessageLog({ user, contactInfo, assigneeName, ownerName, existingMessageLog: existingSameDateMessageLog, message, authHeader, additionalSubmission, imageLink, imageDownloadLink, imageContentType, videoLink, proxyConfig });
|
|
569
644
|
crmLogId = existingSameDateMessageLog.thirdPartyLogId;
|
|
570
645
|
returnMessage = updateMessageResult?.returnMessage;
|
|
571
646
|
extraDataTracking = updateMessageResult.extraDataTracking;
|
|
572
647
|
}
|
|
573
648
|
else {
|
|
574
|
-
const createMessageLogResult = await platformModule.createMessageLog({ user, contactInfo, assigneeName, ownerName, authHeader, message, additionalSubmission, recordingLink, faxDocLink, faxDownloadLink, imageLink, imageDownloadLink, imageContentType, videoLink, proxyConfig });
|
|
649
|
+
const createMessageLogResult = await platformModule.createMessageLog({ user, contactInfo, correspondents, assigneeName, ownerName, authHeader, message, additionalSubmission, recordingLink, faxDocLink, faxDownloadLink, imageLink, imageDownloadLink, imageContentType, videoLink, proxyConfig });
|
|
575
650
|
crmLogId = createMessageLogResult.logId;
|
|
576
651
|
returnMessage = createMessageLogResult?.returnMessage;
|
|
577
652
|
extraDataTracking = createMessageLogResult.extraDataTracking;
|
package/index.js
CHANGED
|
@@ -333,7 +333,7 @@ function createCoreRouter() {
|
|
|
333
333
|
}
|
|
334
334
|
else {
|
|
335
335
|
tracer?.trace('setAdminSettings:adminValidationFailed', {});
|
|
336
|
-
res.status(
|
|
336
|
+
res.status(403).send(tracer ? tracer.wrapResponse('Admin validation failed') : 'Admin validation failed');
|
|
337
337
|
success = false;
|
|
338
338
|
}
|
|
339
339
|
}
|
|
@@ -395,7 +395,7 @@ function createCoreRouter() {
|
|
|
395
395
|
}
|
|
396
396
|
else {
|
|
397
397
|
tracer?.trace('getAdminSettings:adminValidationFailed', {});
|
|
398
|
-
res.status(
|
|
398
|
+
res.status(403).send(tracer ? tracer.wrapResponse('Admin validation failed') : 'Admin validation failed');
|
|
399
399
|
success = true;
|
|
400
400
|
}
|
|
401
401
|
}
|
|
@@ -447,12 +447,18 @@ function createCoreRouter() {
|
|
|
447
447
|
const hashedRcAccountId = util.getHashValue(rcAccountId, process.env.HASH_KEY);
|
|
448
448
|
if (isValidated) {
|
|
449
449
|
const userMapping = await adminCore.getUserMapping({ user, hashedRcAccountId, rcExtensionList: req.body.rcExtensionList });
|
|
450
|
-
|
|
451
|
-
|
|
450
|
+
if (userMapping?.isRevokeUserSession) {
|
|
451
|
+
res.status(401).send(tracer ? tracer.wrapResponse(userMapping) : userMapping);
|
|
452
|
+
success = false;
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
res.status(200).send(tracer ? tracer.wrapResponse(userMapping) : userMapping);
|
|
456
|
+
success = true;
|
|
457
|
+
}
|
|
452
458
|
}
|
|
453
459
|
else {
|
|
454
460
|
tracer?.trace('getUserMapping:adminValidationFailed', {});
|
|
455
|
-
res.status(
|
|
461
|
+
res.status(403).send(tracer ? tracer.wrapResponse('Admin validation failed') : 'Admin validation failed');
|
|
456
462
|
success = true;
|
|
457
463
|
}
|
|
458
464
|
}
|
|
@@ -504,12 +510,18 @@ function createCoreRouter() {
|
|
|
504
510
|
const hashedRcAccountId = util.getHashValue(rcAccountId, process.env.HASH_KEY);
|
|
505
511
|
if (isValidated) {
|
|
506
512
|
const userMapping = await adminCore.reinitializeUserMapping({ user, hashedRcAccountId, rcExtensionList: req.body.rcExtensionList });
|
|
507
|
-
|
|
508
|
-
|
|
513
|
+
if (userMapping?.isRevokeUserSession) {
|
|
514
|
+
res.status(401).send(tracer ? tracer.wrapResponse(userMapping) : userMapping);
|
|
515
|
+
success = false;
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
res.status(200).send(tracer ? tracer.wrapResponse(userMapping) : userMapping);
|
|
519
|
+
success = true;
|
|
520
|
+
}
|
|
509
521
|
}
|
|
510
522
|
else {
|
|
511
523
|
tracer?.trace('reinitializeUserMapping:adminValidationFailed', {});
|
|
512
|
-
res.status(
|
|
524
|
+
res.status(403).send(tracer ? tracer.wrapResponse('Admin validation failed') : 'Admin validation failed');
|
|
513
525
|
success = true;
|
|
514
526
|
}
|
|
515
527
|
}
|
|
@@ -825,8 +837,7 @@ function createCoreRouter() {
|
|
|
825
837
|
const stateParams = new URLSearchParams(state ? decodeURIComponent(state) : '');
|
|
826
838
|
platformName = stateParams.get('platform');
|
|
827
839
|
// backward compatibility
|
|
828
|
-
if(!platformName)
|
|
829
|
-
{
|
|
840
|
+
if (!platformName) {
|
|
830
841
|
platformName = req.query.callbackUri?.split('platform=')[1] ?? state.split('platform=')[1];
|
|
831
842
|
}
|
|
832
843
|
// Extract mcp auth sessionId if present
|
|
@@ -870,7 +881,7 @@ function createCoreRouter() {
|
|
|
870
881
|
res.status(200).send("Authentication successful. Please go back to AI Agent and confirm it.");
|
|
871
882
|
success = true;
|
|
872
883
|
}
|
|
873
|
-
else {
|
|
884
|
+
else {
|
|
874
885
|
res.status(200).send(tracer ? tracer.wrapResponse({ jwtToken, name: userInfo.name, returnMessage }) : { jwtToken, name: userInfo.name, returnMessage });
|
|
875
886
|
success = true;
|
|
876
887
|
}
|
|
@@ -1057,7 +1068,7 @@ function createCoreRouter() {
|
|
|
1057
1068
|
}
|
|
1058
1069
|
const { id: userId, platform } = decodedToken;
|
|
1059
1070
|
platformName = platform;
|
|
1060
|
-
const { successful, returnMessage, contact, extraDataTracking } = await contactCore.findContact({
|
|
1071
|
+
const { successful, returnMessage, contact, extraDataTracking, isRevokeUserSession } = await contactCore.findContact({
|
|
1061
1072
|
platform,
|
|
1062
1073
|
userId,
|
|
1063
1074
|
phoneNumber: req.query.phoneNumber.replace(' ', '+'),
|
|
@@ -1066,15 +1077,21 @@ function createCoreRouter() {
|
|
|
1066
1077
|
tracer,
|
|
1067
1078
|
isForceRefreshAccountData: req.query?.isForceRefreshAccountData === 'true'
|
|
1068
1079
|
});
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
const nonNewContact = contact?.filter(c => !c.isNewContact) ?? [];
|
|
1073
|
-
resultCount = nonNewContact.length;
|
|
1080
|
+
if (isRevokeUserSession) {
|
|
1081
|
+
res.status(401).send(tracer ? tracer.wrapResponse({ successful, returnMessage }) : { successful, returnMessage });
|
|
1082
|
+
success = false;
|
|
1074
1083
|
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1084
|
+
else {
|
|
1085
|
+
tracer?.trace('findContact:result', { successful, returnMessage, contact });
|
|
1086
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ successful, returnMessage, contact }) : { successful, returnMessage, contact });
|
|
1087
|
+
if (successful) {
|
|
1088
|
+
const nonNewContact = contact?.filter(c => !c.isNewContact) ?? [];
|
|
1089
|
+
resultCount = nonNewContact.length;
|
|
1090
|
+
}
|
|
1091
|
+
success = successful;
|
|
1092
|
+
if (extraDataTracking) {
|
|
1093
|
+
extraData = extraDataTracking;
|
|
1094
|
+
}
|
|
1078
1095
|
}
|
|
1079
1096
|
}
|
|
1080
1097
|
else {
|
|
@@ -1128,11 +1145,17 @@ function createCoreRouter() {
|
|
|
1128
1145
|
}
|
|
1129
1146
|
const { id: userId, platform } = decodedToken;
|
|
1130
1147
|
platformName = platform;
|
|
1131
|
-
const { successful, returnMessage, contact, extraDataTracking } = await contactCore.createContact({ platform, userId, phoneNumber: req.body.phoneNumber, newContactName: req.body.newContactName, newContactType: req.body.newContactType, additionalSubmission: req.body.additionalSubmission });
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1148
|
+
const { successful, returnMessage, contact, extraDataTracking, isRevokeUserSession } = await contactCore.createContact({ platform, userId, phoneNumber: req.body.phoneNumber, newContactName: req.body.newContactName, newContactType: req.body.newContactType, additionalSubmission: req.body.additionalSubmission });
|
|
1149
|
+
if (isRevokeUserSession) {
|
|
1150
|
+
res.status(401).send(tracer ? tracer.wrapResponse({ successful, returnMessage }) : { successful, returnMessage });
|
|
1151
|
+
success = false;
|
|
1152
|
+
}
|
|
1153
|
+
else {
|
|
1154
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ successful, returnMessage, contact }) : { successful, returnMessage, contact });
|
|
1155
|
+
success = true;
|
|
1156
|
+
if (extraDataTracking) {
|
|
1157
|
+
extraData = extraDataTracking;
|
|
1158
|
+
}
|
|
1136
1159
|
}
|
|
1137
1160
|
}
|
|
1138
1161
|
else {
|
|
@@ -1236,13 +1259,19 @@ function createCoreRouter() {
|
|
|
1236
1259
|
}
|
|
1237
1260
|
const { id: userId, platform } = decodedToken;
|
|
1238
1261
|
platformName = platform;
|
|
1239
|
-
const { successful, logs, returnMessage, extraDataTracking } = await logCore.getCallLog({ userId, sessionIds: req.query.sessionIds, platform, requireDetails: req.query.requireDetails === 'true' });
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1262
|
+
const { successful, logs, returnMessage, extraDataTracking, isRevokeUserSession } = await logCore.getCallLog({ userId, sessionIds: req.query.sessionIds, platform, requireDetails: req.query.requireDetails === 'true' });
|
|
1263
|
+
if (isRevokeUserSession) {
|
|
1264
|
+
res.status(401).send(tracer ? tracer.wrapResponse({ successful, returnMessage }) : { successful, returnMessage });
|
|
1265
|
+
success = false;
|
|
1266
|
+
}
|
|
1267
|
+
else {
|
|
1268
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ successful, logs, returnMessage }) : { successful, logs, returnMessage });
|
|
1269
|
+
success = true;
|
|
1270
|
+
if (extraDataTracking) {
|
|
1271
|
+
extraData = extraDataTracking;
|
|
1272
|
+
}
|
|
1273
|
+
extraData.requireDetails = req.query.requireDetails === 'true';
|
|
1244
1274
|
}
|
|
1245
|
-
extraData.requireDetails = req.query.requireDetails === 'true';
|
|
1246
1275
|
}
|
|
1247
1276
|
else {
|
|
1248
1277
|
tracer?.trace('getCallLog:noToken', {});
|
|
@@ -1294,12 +1323,18 @@ function createCoreRouter() {
|
|
|
1294
1323
|
}
|
|
1295
1324
|
const { id: userId, platform } = decodedToken;
|
|
1296
1325
|
platformName = platform;
|
|
1297
|
-
const { successful, logId, returnMessage, extraDataTracking } = await logCore.createCallLog({ platform, userId, incomingData: req.body, hashedAccountId: hashedAccountId ?? util.getHashValue(req.body.logInfo?.accountId, process.env.HASH_KEY), isFromSSCL: userAgent === 'SSCL' });
|
|
1298
|
-
if (
|
|
1299
|
-
|
|
1326
|
+
const { successful, logId, returnMessage, extraDataTracking, isRevokeUserSession } = await logCore.createCallLog({ platform, userId, incomingData: req.body, hashedAccountId: hashedAccountId ?? util.getHashValue(req.body.logInfo?.accountId, process.env.HASH_KEY), isFromSSCL: userAgent === 'SSCL' });
|
|
1327
|
+
if (isRevokeUserSession) {
|
|
1328
|
+
res.status(401).send(tracer ? tracer.wrapResponse({ successful, returnMessage }) : { successful, returnMessage });
|
|
1329
|
+
success = false;
|
|
1330
|
+
}
|
|
1331
|
+
else {
|
|
1332
|
+
if (extraDataTracking) {
|
|
1333
|
+
extraData = extraDataTracking;
|
|
1334
|
+
}
|
|
1335
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ successful, logId, returnMessage }) : { successful, logId, returnMessage });
|
|
1336
|
+
success = true;
|
|
1300
1337
|
}
|
|
1301
|
-
res.status(200).send(tracer ? tracer.wrapResponse({ successful, logId, returnMessage }) : { successful, logId, returnMessage });
|
|
1302
|
-
success = true;
|
|
1303
1338
|
}
|
|
1304
1339
|
else {
|
|
1305
1340
|
tracer?.trace('createCallLog:noToken', {});
|
|
@@ -1407,18 +1442,24 @@ function createCoreRouter() {
|
|
|
1407
1442
|
res.status(400).send(tracer ? tracer.wrapResponse('Please go to Settings and authorize CRM platform') : 'Please go to Settings and authorize CRM platform');
|
|
1408
1443
|
return;
|
|
1409
1444
|
}
|
|
1410
|
-
const { successful, returnMessage, extraDataTracking } = await dispositionCore.upsertCallDisposition({
|
|
1445
|
+
const { successful, returnMessage, extraDataTracking, isRevokeUserSession } = await dispositionCore.upsertCallDisposition({
|
|
1411
1446
|
platform,
|
|
1412
1447
|
userId,
|
|
1413
1448
|
sessionId: req.body.sessionId,
|
|
1414
1449
|
dispositions: req.body.dispositions,
|
|
1415
1450
|
additionalSubmission: req.body.additionalSubmission
|
|
1416
1451
|
});
|
|
1417
|
-
if (
|
|
1418
|
-
|
|
1452
|
+
if (isRevokeUserSession) {
|
|
1453
|
+
res.status(401).send(tracer ? tracer.wrapResponse({ successful, returnMessage }) : { successful, returnMessage });
|
|
1454
|
+
success = false;
|
|
1455
|
+
}
|
|
1456
|
+
else {
|
|
1457
|
+
if (extraDataTracking) {
|
|
1458
|
+
extraData = extraDataTracking;
|
|
1459
|
+
}
|
|
1460
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ successful, returnMessage }) : { successful, returnMessage });
|
|
1461
|
+
success = true;
|
|
1419
1462
|
}
|
|
1420
|
-
res.status(200).send(tracer ? tracer.wrapResponse({ successful, returnMessage }) : { successful, returnMessage });
|
|
1421
|
-
success = true;
|
|
1422
1463
|
}
|
|
1423
1464
|
else {
|
|
1424
1465
|
tracer?.trace('upsertCallDisposition:noToken', {});
|
|
@@ -1471,12 +1512,18 @@ function createCoreRouter() {
|
|
|
1471
1512
|
}
|
|
1472
1513
|
const { id: userId, platform } = decodedToken;
|
|
1473
1514
|
platformName = platform;
|
|
1474
|
-
const { successful, returnMessage, logIds, extraDataTracking } = await logCore.createMessageLog({ platform, userId, incomingData: req.body });
|
|
1475
|
-
if (
|
|
1476
|
-
|
|
1515
|
+
const { successful, returnMessage, logIds, extraDataTracking, isRevokeUserSession } = await logCore.createMessageLog({ platform, userId, incomingData: req.body });
|
|
1516
|
+
if (isRevokeUserSession) {
|
|
1517
|
+
res.status(401).send(tracer ? tracer.wrapResponse({ successful, returnMessage }) : { successful, returnMessage });
|
|
1518
|
+
success = false;
|
|
1519
|
+
}
|
|
1520
|
+
else {
|
|
1521
|
+
if (extraDataTracking) {
|
|
1522
|
+
extraData = extraDataTracking;
|
|
1523
|
+
}
|
|
1524
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ successful, returnMessage, logIds }) : { successful, returnMessage, logIds });
|
|
1525
|
+
success = true;
|
|
1477
1526
|
}
|
|
1478
|
-
res.status(200).send(tracer ? tracer.wrapResponse({ successful, returnMessage, logIds }) : { successful, returnMessage, logIds });
|
|
1479
|
-
success = true;
|
|
1480
1527
|
}
|
|
1481
1528
|
else {
|
|
1482
1529
|
tracer?.trace('createMessageLog:noToken', {});
|
|
@@ -1707,9 +1754,15 @@ function createCoreRouter() {
|
|
|
1707
1754
|
if (jwtToken) {
|
|
1708
1755
|
const { id: userId, platform } = jwt.decodeJwt(jwtToken);
|
|
1709
1756
|
platformName = platform;
|
|
1710
|
-
const { successful, returnMessage, contact } = await contactCore.findContactWithName({ platform, userId, name: req.query.name });
|
|
1711
|
-
|
|
1712
|
-
|
|
1757
|
+
const { successful, returnMessage, contact, isRevokeUserSession } = await contactCore.findContactWithName({ platform, userId, name: req.query.name });
|
|
1758
|
+
if (isRevokeUserSession) {
|
|
1759
|
+
res.status(401).send(tracer ? tracer.wrapResponse({ successful, returnMessage }) : { successful, returnMessage });
|
|
1760
|
+
success = false;
|
|
1761
|
+
}
|
|
1762
|
+
else {
|
|
1763
|
+
res.status(200).send(tracer ? tracer.wrapResponse({ successful, returnMessage, contact }) : { successful, returnMessage, contact });
|
|
1764
|
+
success = successful;
|
|
1765
|
+
}
|
|
1713
1766
|
}
|
|
1714
1767
|
else {
|
|
1715
1768
|
tracer?.trace('contactSearchByName:noToken', {});
|
|
@@ -1896,7 +1949,7 @@ function createCoreRouter() {
|
|
|
1896
1949
|
res.status(200).send(mockUser ? 'Mock user registered' : 'Mock user already existed');
|
|
1897
1950
|
}
|
|
1898
1951
|
else {
|
|
1899
|
-
res.status(
|
|
1952
|
+
res.status(403).send('Unauthorized');
|
|
1900
1953
|
}
|
|
1901
1954
|
});
|
|
1902
1955
|
router.delete('/deleteMockUser', async function (req, res) {
|
|
@@ -1906,7 +1959,7 @@ function createCoreRouter() {
|
|
|
1906
1959
|
res.status(200).send(foundAndDeleted ? 'Mock user deleted' : 'Mock user not found');
|
|
1907
1960
|
}
|
|
1908
1961
|
else {
|
|
1909
|
-
res.status(
|
|
1962
|
+
res.status(403).send('Unauthorized');
|
|
1910
1963
|
}
|
|
1911
1964
|
});
|
|
1912
1965
|
router.get('/mockCallLog', async function (req, res) {
|
|
@@ -1916,7 +1969,7 @@ function createCoreRouter() {
|
|
|
1916
1969
|
res.status(200).send(callLogs);
|
|
1917
1970
|
}
|
|
1918
1971
|
else {
|
|
1919
|
-
res.status(
|
|
1972
|
+
res.status(403).send('Unauthorized');
|
|
1920
1973
|
}
|
|
1921
1974
|
});
|
|
1922
1975
|
router.post('/mockCallLog', async function (req, res) {
|
|
@@ -1926,7 +1979,7 @@ function createCoreRouter() {
|
|
|
1926
1979
|
res.status(200).send('Mock call log created');
|
|
1927
1980
|
}
|
|
1928
1981
|
else {
|
|
1929
|
-
res.status(
|
|
1982
|
+
res.status(403).send('Unauthorized');
|
|
1930
1983
|
}
|
|
1931
1984
|
});
|
|
1932
1985
|
router.delete('/mockCallLog', async function (req, res) {
|
|
@@ -1936,7 +1989,7 @@ function createCoreRouter() {
|
|
|
1936
1989
|
res.status(200).send('Mock call logs cleaned up');
|
|
1937
1990
|
}
|
|
1938
1991
|
else {
|
|
1939
|
-
res.status(
|
|
1992
|
+
res.status(403).send('Unauthorized');
|
|
1940
1993
|
}
|
|
1941
1994
|
});
|
|
1942
1995
|
}
|
|
@@ -1944,7 +1997,7 @@ function createCoreRouter() {
|
|
|
1944
1997
|
router.get('/.well-known/openai-apps-challenge', (req, res) => {
|
|
1945
1998
|
res.send(process.env.CHATGPT_VERIFICATION_CODE);
|
|
1946
1999
|
});
|
|
1947
|
-
|
|
2000
|
+
|
|
1948
2001
|
// --- METADATA ENDPOINT 1: Resource Metadata ---
|
|
1949
2002
|
// Tells the client "I am protected" and "Here is who protects me"
|
|
1950
2003
|
router.get('/.well-known/oauth-protected-resource', (req, res) => {
|
package/lib/oauth.js
CHANGED
|
@@ -101,41 +101,48 @@ async function checkAndRefreshAccessToken(oauthApp, user, tokenLockTimeout = 20)
|
|
|
101
101
|
try {
|
|
102
102
|
const startRefreshTime = moment();
|
|
103
103
|
const token = oauthApp.createToken(user.accessToken, user.refreshToken);
|
|
104
|
-
|
|
104
|
+
logger.info('token refreshing...')
|
|
105
105
|
const { accessToken, refreshToken, expires } = await token.refresh();
|
|
106
106
|
user.accessToken = accessToken;
|
|
107
107
|
user.refreshToken = refreshToken;
|
|
108
108
|
user.tokenExpiry = expires;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
try {
|
|
110
|
+
await user.save();
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
return handleDatabaseError(error, 'Error saving user');
|
|
114
|
+
}
|
|
115
115
|
if (newLock) {
|
|
116
116
|
const deletionStartTime = moment();
|
|
117
117
|
await newLock.delete();
|
|
118
118
|
const deletionEndTime = moment();
|
|
119
|
-
|
|
119
|
+
logger.info(`lock deleted in ${deletionEndTime.diff(deletionStartTime)}ms`)
|
|
120
120
|
}
|
|
121
121
|
const endRefreshTime = moment();
|
|
122
|
-
|
|
122
|
+
logger.info(`token refreshing finished in ${endRefreshTime.diff(startRefreshTime)}ms`)
|
|
123
123
|
}
|
|
124
124
|
catch (e) {
|
|
125
125
|
console.log('token refreshing failed', e.stack)
|
|
126
126
|
if (newLock) {
|
|
127
127
|
await newLock.delete();
|
|
128
128
|
}
|
|
129
|
+
return null;
|
|
129
130
|
}
|
|
130
131
|
}
|
|
131
132
|
// case: run withou token refresh lock
|
|
132
133
|
else {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
try {
|
|
135
|
+
logger.info('token refreshing...')
|
|
136
|
+
const token = oauthApp.createToken(user.accessToken, user.refreshToken);
|
|
137
|
+
const { accessToken, refreshToken, expires } = await token.refresh();
|
|
138
|
+
user.accessToken = accessToken;
|
|
139
|
+
user.refreshToken = refreshToken;
|
|
140
|
+
user.tokenExpiry = expires;
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
console.log('token refreshing failed', e.stack)
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
139
146
|
try {
|
|
140
147
|
await user.save();
|
|
141
148
|
}
|
package/package.json
CHANGED
package/releaseNotes.json
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
+
"1.7.17": {
|
|
3
|
+
"global": [
|
|
4
|
+
{
|
|
5
|
+
"type": "New",
|
|
6
|
+
"description": "Support for Group SMS logging"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"type": "Better",
|
|
10
|
+
"description": "User session will log be revoked is token refresh fails"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"type": "Fix",
|
|
14
|
+
"description": "Contact call pop issue"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
},
|
|
2
18
|
"1.7.16": {
|
|
3
19
|
"global": [
|
|
4
20
|
{
|
|
@@ -29,6 +29,7 @@ const logHandler = require('../../handlers/log');
|
|
|
29
29
|
const { CallLogModel } = require('../../models/callLogModel');
|
|
30
30
|
const { MessageLogModel } = require('../../models/messageLogModel');
|
|
31
31
|
const { UserModel } = require('../../models/userModel');
|
|
32
|
+
const { AccountDataModel } = require('../../models/accountDataModel');
|
|
32
33
|
const connectorRegistry = require('../../connector/registry');
|
|
33
34
|
const oauth = require('../../lib/oauth');
|
|
34
35
|
const { composeCallLog } = require('../../lib/callLogComposer');
|
|
@@ -40,12 +41,14 @@ describe('Log Handler', () => {
|
|
|
40
41
|
await CallLogModel.sync({ force: true });
|
|
41
42
|
await MessageLogModel.sync({ force: true });
|
|
42
43
|
await UserModel.sync({ force: true });
|
|
44
|
+
await AccountDataModel.sync({ force: true });
|
|
43
45
|
});
|
|
44
46
|
|
|
45
47
|
afterEach(async () => {
|
|
46
48
|
await CallLogModel.destroy({ where: {} });
|
|
47
49
|
await MessageLogModel.destroy({ where: {} });
|
|
48
50
|
await UserModel.destroy({ where: {} });
|
|
51
|
+
await AccountDataModel.destroy({ where: {} });
|
|
49
52
|
jest.clearAllMocks();
|
|
50
53
|
});
|
|
51
54
|
|
|
@@ -742,6 +745,228 @@ describe('Log Handler', () => {
|
|
|
742
745
|
expect(mockConnector.createMessageLog).toHaveBeenCalledTimes(0);
|
|
743
746
|
expect(mockConnector.updateMessageLog).toHaveBeenCalledTimes(1);
|
|
744
747
|
});
|
|
748
|
+
|
|
749
|
+
test('should handle group SMS with contactId suffix for message IDs', async () => {
|
|
750
|
+
// Arrange - group SMS has multiple correspondents
|
|
751
|
+
await UserModel.create({
|
|
752
|
+
id: 'test-user-id',
|
|
753
|
+
platform: 'testCRM',
|
|
754
|
+
accessToken: 'test-token',
|
|
755
|
+
rcAccountId: 'rc-account-123',
|
|
756
|
+
platformAdditionalInfo: {}
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
const mockConnector = {
|
|
760
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
761
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
762
|
+
createMessageLog: jest.fn().mockResolvedValue({
|
|
763
|
+
logId: 'msg-log-group-123',
|
|
764
|
+
returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
|
|
765
|
+
}),
|
|
766
|
+
updateMessageLog: jest.fn()
|
|
767
|
+
};
|
|
768
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
769
|
+
|
|
770
|
+
const incomingData = {
|
|
771
|
+
logInfo: {
|
|
772
|
+
messages: [{ id: 'msg-group-1', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
|
|
773
|
+
correspondents: [
|
|
774
|
+
{ phoneNumber: '+1234567890' },
|
|
775
|
+
{ phoneNumber: '+0987654321' }
|
|
776
|
+
],
|
|
777
|
+
conversationId: 'conv-group-123',
|
|
778
|
+
conversationLogId: 'conv-log-group-123'
|
|
779
|
+
},
|
|
780
|
+
contactId: 'contact-456',
|
|
781
|
+
contactType: 'Contact',
|
|
782
|
+
contactName: 'Primary Contact',
|
|
783
|
+
additionalSubmission: {}
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
// Act
|
|
787
|
+
const result = await logHandler.createMessageLog({
|
|
788
|
+
platform: 'testCRM',
|
|
789
|
+
userId: 'test-user-id',
|
|
790
|
+
incomingData
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
// Assert
|
|
794
|
+
expect(result.successful).toBe(true);
|
|
795
|
+
expect(result.logIds).toContain('msg-group-1-contact-456');
|
|
796
|
+
const savedLog = await MessageLogModel.findOne({ where: { id: 'msg-group-1-contact-456' } });
|
|
797
|
+
expect(savedLog).not.toBeNull();
|
|
798
|
+
expect(savedLog.thirdPartyLogId).toBe('msg-log-group-123');
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
test('should pass correspondents to createMessageLog when group SMS has different contact names', async () => {
|
|
802
|
+
// Arrange - correspondent in cache with different name
|
|
803
|
+
await UserModel.create({
|
|
804
|
+
id: 'test-user-id',
|
|
805
|
+
platform: 'testCRM',
|
|
806
|
+
accessToken: 'test-token',
|
|
807
|
+
rcAccountId: 'rc-account-123',
|
|
808
|
+
platformAdditionalInfo: {}
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
await AccountDataModel.create({
|
|
812
|
+
rcAccountId: 'rc-account-123',
|
|
813
|
+
platformName: 'testCRM',
|
|
814
|
+
dataKey: 'contact-+0987654321',
|
|
815
|
+
data: [{ name: 'Other Contact', id: 'contact-789' }]
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
const mockConnector = {
|
|
819
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
820
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
821
|
+
createMessageLog: jest.fn().mockResolvedValue({
|
|
822
|
+
logId: 'msg-log-correspondents',
|
|
823
|
+
returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
|
|
824
|
+
}),
|
|
825
|
+
updateMessageLog: jest.fn()
|
|
826
|
+
};
|
|
827
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
828
|
+
|
|
829
|
+
const incomingData = {
|
|
830
|
+
logInfo: {
|
|
831
|
+
messages: [{ id: 'msg-correspondents', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
|
|
832
|
+
correspondents: [
|
|
833
|
+
{ phoneNumber: '+1234567890' },
|
|
834
|
+
{ phoneNumber: '+0987654321' }
|
|
835
|
+
],
|
|
836
|
+
conversationId: 'conv-correspondents',
|
|
837
|
+
conversationLogId: 'conv-log-correspondents'
|
|
838
|
+
},
|
|
839
|
+
contactId: 'contact-456',
|
|
840
|
+
contactType: 'Contact',
|
|
841
|
+
contactName: 'Primary Contact',
|
|
842
|
+
additionalSubmission: {}
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
// Act
|
|
846
|
+
await logHandler.createMessageLog({
|
|
847
|
+
platform: 'testCRM',
|
|
848
|
+
userId: 'test-user-id',
|
|
849
|
+
incomingData
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
// Assert - createMessageLog should receive correspondents with different name
|
|
853
|
+
expect(mockConnector.createMessageLog).toHaveBeenCalledWith(
|
|
854
|
+
expect.objectContaining({
|
|
855
|
+
correspondents: [[{ name: 'Other Contact', id: 'contact-789' }]]
|
|
856
|
+
})
|
|
857
|
+
);
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
test('should not add correspondent when name matches contactName in group SMS', async () => {
|
|
861
|
+
// Arrange - correspondent in cache with same name as contactName
|
|
862
|
+
await UserModel.create({
|
|
863
|
+
id: 'test-user-id',
|
|
864
|
+
platform: 'testCRM',
|
|
865
|
+
accessToken: 'test-token',
|
|
866
|
+
rcAccountId: 'rc-account-123',
|
|
867
|
+
platformAdditionalInfo: {}
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
await AccountDataModel.create({
|
|
871
|
+
rcAccountId: 'rc-account-123',
|
|
872
|
+
platformName: 'testCRM',
|
|
873
|
+
dataKey: 'contact-+0987654321',
|
|
874
|
+
data: [{ name: 'Primary Contact', id: 'contact-789' }]
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
const mockConnector = {
|
|
878
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
879
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
880
|
+
createMessageLog: jest.fn().mockResolvedValue({
|
|
881
|
+
logId: 'msg-log-same-name',
|
|
882
|
+
returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
|
|
883
|
+
}),
|
|
884
|
+
updateMessageLog: jest.fn()
|
|
885
|
+
};
|
|
886
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
887
|
+
|
|
888
|
+
const incomingData = {
|
|
889
|
+
logInfo: {
|
|
890
|
+
messages: [{ id: 'msg-same-name', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
|
|
891
|
+
correspondents: [
|
|
892
|
+
{ phoneNumber: '+1234567890' },
|
|
893
|
+
{ phoneNumber: '+0987654321' }
|
|
894
|
+
],
|
|
895
|
+
conversationId: 'conv-same-name',
|
|
896
|
+
conversationLogId: 'conv-log-same-name'
|
|
897
|
+
},
|
|
898
|
+
contactId: 'contact-456',
|
|
899
|
+
contactType: 'Contact',
|
|
900
|
+
contactName: 'Primary Contact',
|
|
901
|
+
additionalSubmission: {}
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
// Act
|
|
905
|
+
await logHandler.createMessageLog({
|
|
906
|
+
platform: 'testCRM',
|
|
907
|
+
userId: 'test-user-id',
|
|
908
|
+
incomingData
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
// Assert - correspondents should be empty when names match
|
|
912
|
+
expect(mockConnector.createMessageLog).toHaveBeenCalledWith(
|
|
913
|
+
expect.objectContaining({
|
|
914
|
+
correspondents: []
|
|
915
|
+
})
|
|
916
|
+
);
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
test('should use suffixed conversationLogId and conversationId for group SMS', async () => {
|
|
920
|
+
// Arrange
|
|
921
|
+
await UserModel.create({
|
|
922
|
+
id: 'test-user-id',
|
|
923
|
+
platform: 'testCRM',
|
|
924
|
+
accessToken: 'test-token',
|
|
925
|
+
rcAccountId: 'rc-account-123',
|
|
926
|
+
platformAdditionalInfo: {}
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
const mockConnector = {
|
|
930
|
+
getAuthType: jest.fn().mockResolvedValue('apiKey'),
|
|
931
|
+
getBasicAuth: jest.fn().mockReturnValue('base64-encoded'),
|
|
932
|
+
createMessageLog: jest.fn().mockResolvedValue({
|
|
933
|
+
logId: 'msg-log-suffix',
|
|
934
|
+
returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
|
|
935
|
+
}),
|
|
936
|
+
updateMessageLog: jest.fn()
|
|
937
|
+
};
|
|
938
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
939
|
+
|
|
940
|
+
const incomingData = {
|
|
941
|
+
logInfo: {
|
|
942
|
+
messages: [{ id: 'msg-suffix', subject: 'Group SMS', direction: 'Outbound', creationTime: new Date() }],
|
|
943
|
+
correspondents: [
|
|
944
|
+
{ phoneNumber: '+1234567890' },
|
|
945
|
+
{ phoneNumber: '+0987654321' }
|
|
946
|
+
],
|
|
947
|
+
conversationId: 'conv-original',
|
|
948
|
+
conversationLogId: 'conv-log-original'
|
|
949
|
+
},
|
|
950
|
+
contactId: 'contact-999',
|
|
951
|
+
contactType: 'Contact',
|
|
952
|
+
contactName: 'Test Contact',
|
|
953
|
+
additionalSubmission: {}
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
// Act
|
|
957
|
+
const result = await logHandler.createMessageLog({
|
|
958
|
+
platform: 'testCRM',
|
|
959
|
+
userId: 'test-user-id',
|
|
960
|
+
incomingData
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
// Assert - message log saved with suffixed conversationLogId
|
|
964
|
+
expect(result.successful).toBe(true);
|
|
965
|
+
const savedLog = await MessageLogModel.findOne({ where: { id: 'msg-suffix-contact-999' } });
|
|
966
|
+
expect(savedLog).not.toBeNull();
|
|
967
|
+
expect(savedLog.conversationLogId).toBe('conv-log-original-contact-999');
|
|
968
|
+
expect(savedLog.conversationId).toBe('conv-original-contact-999');
|
|
969
|
+
});
|
|
745
970
|
});
|
|
746
971
|
|
|
747
972
|
describe('saveNoteCache', () => {
|
package/test/setup.js
CHANGED
|
@@ -1,176 +1,176 @@
|
|
|
1
|
-
// Test setup for @app-connect/core package
|
|
2
|
-
const path = require('path');
|
|
3
|
-
require('dotenv').config({ path: path.resolve(__dirname, '../.env.test') });
|
|
4
|
-
|
|
5
|
-
// Set test timeout
|
|
6
|
-
jest.setTimeout(30000);
|
|
7
|
-
|
|
8
|
-
// Mock console methods to reduce noise in tests
|
|
9
|
-
global.console = {
|
|
10
|
-
...console,
|
|
11
|
-
log: jest.fn(),
|
|
12
|
-
debug: jest.fn(),
|
|
13
|
-
info: jest.fn(),
|
|
14
|
-
warn: jest.fn(),
|
|
15
|
-
error: jest.fn(),
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// Setup database models for testing
|
|
19
|
-
beforeAll(async () => {
|
|
20
|
-
try {
|
|
21
|
-
// Set up test database URL if not provided
|
|
22
|
-
if (!process.env.DATABASE_URL) {
|
|
23
|
-
process.env.DATABASE_URL = 'sqlite::memory:';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Import models
|
|
27
|
-
const { CallLogModel } = require('../models/callLogModel');
|
|
28
|
-
const { MessageLogModel } = require('../models/messageLogModel');
|
|
29
|
-
const { UserModel } = require('../models/userModel');
|
|
30
|
-
const { CacheModel } = require('../models/cacheModel');
|
|
31
|
-
const { AdminConfigModel } = require('../models/adminConfigModel');
|
|
32
|
-
|
|
33
|
-
// Sync database models
|
|
34
|
-
await CallLogModel.sync({ force: true });
|
|
35
|
-
await MessageLogModel.sync({ force: true });
|
|
36
|
-
await UserModel.sync({ force: true });
|
|
37
|
-
await CacheModel.sync({ force: true });
|
|
38
|
-
await AdminConfigModel.sync({ force: true });
|
|
39
|
-
|
|
40
|
-
console.log('Database models synced for testing');
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error('Error setting up test database:', error);
|
|
43
|
-
// Don't fail the setup, some tests might not need database
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Clean up after all tests
|
|
48
|
-
afterAll(async () => {
|
|
49
|
-
try {
|
|
50
|
-
// Close database connections
|
|
51
|
-
const { sequelize } = require('../models/sequelize');
|
|
52
|
-
if (sequelize) {
|
|
53
|
-
await sequelize.close();
|
|
54
|
-
}
|
|
55
|
-
} catch (error) {
|
|
56
|
-
console.error('Error closing database connection:', error);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Global test utilities
|
|
61
|
-
global.testUtils = {
|
|
62
|
-
// Helper to create mock user
|
|
63
|
-
createMockUser: (overrides = {}) => ({
|
|
64
|
-
id: 'test-user-id',
|
|
65
|
-
platform: 'testCRM',
|
|
66
|
-
accessToken: 'test-access-token',
|
|
67
|
-
refreshToken: 'test-refresh-token',
|
|
68
|
-
tokenExpiry: new Date(Date.now() + 3600000), // 1 hour from now
|
|
69
|
-
platformUserInfo: {
|
|
70
|
-
id: 'test-platform-user-id',
|
|
71
|
-
name: 'Test User',
|
|
72
|
-
timezoneName: 'America/Los_Angeles',
|
|
73
|
-
timezoneOffset: 0,
|
|
74
|
-
platformAdditionalInfo: {}
|
|
75
|
-
},
|
|
76
|
-
...overrides
|
|
77
|
-
}),
|
|
78
|
-
|
|
79
|
-
// Helper to create mock call log
|
|
80
|
-
createMockCallLog: (overrides = {}) => ({
|
|
81
|
-
id: 'test-call-log-id',
|
|
82
|
-
userId: 'test-user-id',
|
|
83
|
-
platform: 'testCRM',
|
|
84
|
-
thirdPartyLogId: 'test-third-party-id',
|
|
85
|
-
contactId: 'test-contact-id',
|
|
86
|
-
contactType: 'Contact',
|
|
87
|
-
phoneNumber: '+1234567890',
|
|
88
|
-
callDirection: 'Inbound',
|
|
89
|
-
callResult: 'Answered',
|
|
90
|
-
callDuration: 120,
|
|
91
|
-
callStartTime: new Date(),
|
|
92
|
-
callEndTime: new Date(Date.now() + 120000),
|
|
93
|
-
recordingLink: 'https://example.com/recording.mp3',
|
|
94
|
-
subject: 'Test Call',
|
|
95
|
-
note: 'Test call note',
|
|
96
|
-
...overrides
|
|
97
|
-
}),
|
|
98
|
-
|
|
99
|
-
// Helper to create mock contact
|
|
100
|
-
createMockContact: (overrides = {}) => ({
|
|
101
|
-
id: 'test-contact-id',
|
|
102
|
-
name: 'Test Contact',
|
|
103
|
-
type: 'Contact',
|
|
104
|
-
phone: '+1234567890',
|
|
105
|
-
additionalInfo: null,
|
|
106
|
-
...overrides
|
|
107
|
-
}),
|
|
108
|
-
|
|
109
|
-
// Helper to reset connector registry
|
|
110
|
-
resetConnectorRegistry: () => {
|
|
111
|
-
const connectorRegistry = require('../connector/registry');
|
|
112
|
-
connectorRegistry.connectors.clear();
|
|
113
|
-
connectorRegistry.manifests.clear();
|
|
114
|
-
connectorRegistry.platformInterfaces.clear();
|
|
115
|
-
connectorRegistry.releaseNotes = {};
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
// Helper to create mock connector
|
|
119
|
-
createMockConnector: (overrides = {}) => ({
|
|
120
|
-
getAuthType: jest.fn().mockReturnValue('apiKey'),
|
|
121
|
-
getUserInfo: jest.fn().mockResolvedValue({
|
|
122
|
-
successful: true,
|
|
123
|
-
platformUserInfo: {
|
|
124
|
-
id: 'test-user-id',
|
|
125
|
-
name: 'Test User',
|
|
126
|
-
timezoneName: 'America/Los_Angeles',
|
|
127
|
-
timezoneOffset: 0,
|
|
128
|
-
platformAdditionalInfo: {}
|
|
129
|
-
}
|
|
130
|
-
}),
|
|
131
|
-
createCallLog: jest.fn().mockResolvedValue({
|
|
132
|
-
logId: 'test-log-id',
|
|
133
|
-
returnMessage: {
|
|
134
|
-
message: 'Call logged successfully',
|
|
135
|
-
messageType: 'success',
|
|
136
|
-
ttl: 2000
|
|
137
|
-
}
|
|
138
|
-
}),
|
|
139
|
-
updateCallLog: jest.fn().mockResolvedValue({
|
|
140
|
-
updatedNote: 'Call log updated',
|
|
141
|
-
returnMessage: {
|
|
142
|
-
message: 'Call log updated successfully',
|
|
143
|
-
messageType: 'success',
|
|
144
|
-
ttl: 2000
|
|
145
|
-
}
|
|
146
|
-
}),
|
|
147
|
-
unAuthorize: jest.fn().mockResolvedValue({
|
|
148
|
-
returnMessage: {
|
|
149
|
-
messageType: 'success',
|
|
150
|
-
message: 'Logged out successfully',
|
|
151
|
-
ttl: 1000
|
|
152
|
-
}
|
|
153
|
-
}),
|
|
154
|
-
findContact: jest.fn().mockResolvedValue([
|
|
155
|
-
{
|
|
156
|
-
id: 'test-contact-id',
|
|
157
|
-
name: 'Test Contact',
|
|
158
|
-
type: 'Contact',
|
|
159
|
-
phone: '+1234567890',
|
|
160
|
-
additionalInfo: null
|
|
161
|
-
}
|
|
162
|
-
]),
|
|
163
|
-
createContact: jest.fn().mockResolvedValue({
|
|
164
|
-
contactInfo: {
|
|
165
|
-
id: 'new-contact-id',
|
|
166
|
-
name: 'New Contact'
|
|
167
|
-
},
|
|
168
|
-
returnMessage: {
|
|
169
|
-
message: 'Contact created successfully',
|
|
170
|
-
messageType: 'success',
|
|
171
|
-
ttl: 2000
|
|
172
|
-
}
|
|
173
|
-
}),
|
|
174
|
-
...overrides
|
|
175
|
-
})
|
|
176
|
-
};
|
|
1
|
+
// Test setup for @app-connect/core package
|
|
2
|
+
const path = require('path');
|
|
3
|
+
require('dotenv').config({ path: path.resolve(__dirname, '../.env.test') });
|
|
4
|
+
|
|
5
|
+
// Set test timeout
|
|
6
|
+
jest.setTimeout(30000);
|
|
7
|
+
|
|
8
|
+
// Mock console methods to reduce noise in tests
|
|
9
|
+
global.console = {
|
|
10
|
+
...console,
|
|
11
|
+
log: jest.fn(),
|
|
12
|
+
debug: jest.fn(),
|
|
13
|
+
info: jest.fn(),
|
|
14
|
+
warn: jest.fn(),
|
|
15
|
+
error: jest.fn(),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Setup database models for testing
|
|
19
|
+
beforeAll(async () => {
|
|
20
|
+
try {
|
|
21
|
+
// Set up test database URL if not provided
|
|
22
|
+
if (!process.env.DATABASE_URL) {
|
|
23
|
+
process.env.DATABASE_URL = 'sqlite::memory:';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Import models
|
|
27
|
+
const { CallLogModel } = require('../models/callLogModel');
|
|
28
|
+
const { MessageLogModel } = require('../models/messageLogModel');
|
|
29
|
+
const { UserModel } = require('../models/userModel');
|
|
30
|
+
const { CacheModel } = require('../models/cacheModel');
|
|
31
|
+
const { AdminConfigModel } = require('../models/adminConfigModel');
|
|
32
|
+
|
|
33
|
+
// Sync database models
|
|
34
|
+
await CallLogModel.sync({ force: true });
|
|
35
|
+
await MessageLogModel.sync({ force: true });
|
|
36
|
+
await UserModel.sync({ force: true });
|
|
37
|
+
await CacheModel.sync({ force: true });
|
|
38
|
+
await AdminConfigModel.sync({ force: true });
|
|
39
|
+
|
|
40
|
+
console.log('Database models synced for testing');
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('Error setting up test database:', error);
|
|
43
|
+
// Don't fail the setup, some tests might not need database
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Clean up after all tests
|
|
48
|
+
afterAll(async () => {
|
|
49
|
+
try {
|
|
50
|
+
// Close database connections
|
|
51
|
+
const { sequelize } = require('../models/sequelize');
|
|
52
|
+
if (sequelize) {
|
|
53
|
+
await sequelize.close();
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error('Error closing database connection:', error);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Global test utilities
|
|
61
|
+
global.testUtils = {
|
|
62
|
+
// Helper to create mock user
|
|
63
|
+
createMockUser: (overrides = {}) => ({
|
|
64
|
+
id: 'test-user-id',
|
|
65
|
+
platform: 'testCRM',
|
|
66
|
+
accessToken: 'test-access-token',
|
|
67
|
+
refreshToken: 'test-refresh-token',
|
|
68
|
+
tokenExpiry: new Date(Date.now() + 3600000), // 1 hour from now
|
|
69
|
+
platformUserInfo: {
|
|
70
|
+
id: 'test-platform-user-id',
|
|
71
|
+
name: 'Test User',
|
|
72
|
+
timezoneName: 'America/Los_Angeles',
|
|
73
|
+
timezoneOffset: 0,
|
|
74
|
+
platformAdditionalInfo: {}
|
|
75
|
+
},
|
|
76
|
+
...overrides
|
|
77
|
+
}),
|
|
78
|
+
|
|
79
|
+
// Helper to create mock call log
|
|
80
|
+
createMockCallLog: (overrides = {}) => ({
|
|
81
|
+
id: 'test-call-log-id',
|
|
82
|
+
userId: 'test-user-id',
|
|
83
|
+
platform: 'testCRM',
|
|
84
|
+
thirdPartyLogId: 'test-third-party-id',
|
|
85
|
+
contactId: 'test-contact-id',
|
|
86
|
+
contactType: 'Contact',
|
|
87
|
+
phoneNumber: '+1234567890',
|
|
88
|
+
callDirection: 'Inbound',
|
|
89
|
+
callResult: 'Answered',
|
|
90
|
+
callDuration: 120,
|
|
91
|
+
callStartTime: new Date(),
|
|
92
|
+
callEndTime: new Date(Date.now() + 120000),
|
|
93
|
+
recordingLink: 'https://example.com/recording.mp3',
|
|
94
|
+
subject: 'Test Call',
|
|
95
|
+
note: 'Test call note',
|
|
96
|
+
...overrides
|
|
97
|
+
}),
|
|
98
|
+
|
|
99
|
+
// Helper to create mock contact
|
|
100
|
+
createMockContact: (overrides = {}) => ({
|
|
101
|
+
id: 'test-contact-id',
|
|
102
|
+
name: 'Test Contact',
|
|
103
|
+
type: 'Contact',
|
|
104
|
+
phone: '+1234567890',
|
|
105
|
+
additionalInfo: null,
|
|
106
|
+
...overrides
|
|
107
|
+
}),
|
|
108
|
+
|
|
109
|
+
// Helper to reset connector registry
|
|
110
|
+
resetConnectorRegistry: () => {
|
|
111
|
+
const connectorRegistry = require('../connector/registry');
|
|
112
|
+
connectorRegistry.connectors.clear();
|
|
113
|
+
connectorRegistry.manifests.clear();
|
|
114
|
+
connectorRegistry.platformInterfaces.clear();
|
|
115
|
+
connectorRegistry.releaseNotes = {};
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// Helper to create mock connector
|
|
119
|
+
createMockConnector: (overrides = {}) => ({
|
|
120
|
+
getAuthType: jest.fn().mockReturnValue('apiKey'),
|
|
121
|
+
getUserInfo: jest.fn().mockResolvedValue({
|
|
122
|
+
successful: true,
|
|
123
|
+
platformUserInfo: {
|
|
124
|
+
id: 'test-user-id',
|
|
125
|
+
name: 'Test User',
|
|
126
|
+
timezoneName: 'America/Los_Angeles',
|
|
127
|
+
timezoneOffset: 0,
|
|
128
|
+
platformAdditionalInfo: {}
|
|
129
|
+
}
|
|
130
|
+
}),
|
|
131
|
+
createCallLog: jest.fn().mockResolvedValue({
|
|
132
|
+
logId: 'test-log-id',
|
|
133
|
+
returnMessage: {
|
|
134
|
+
message: 'Call logged successfully',
|
|
135
|
+
messageType: 'success',
|
|
136
|
+
ttl: 2000
|
|
137
|
+
}
|
|
138
|
+
}),
|
|
139
|
+
updateCallLog: jest.fn().mockResolvedValue({
|
|
140
|
+
updatedNote: 'Call log updated',
|
|
141
|
+
returnMessage: {
|
|
142
|
+
message: 'Call log updated successfully',
|
|
143
|
+
messageType: 'success',
|
|
144
|
+
ttl: 2000
|
|
145
|
+
}
|
|
146
|
+
}),
|
|
147
|
+
unAuthorize: jest.fn().mockResolvedValue({
|
|
148
|
+
returnMessage: {
|
|
149
|
+
messageType: 'success',
|
|
150
|
+
message: 'Logged out successfully',
|
|
151
|
+
ttl: 1000
|
|
152
|
+
}
|
|
153
|
+
}),
|
|
154
|
+
findContact: jest.fn().mockResolvedValue([
|
|
155
|
+
{
|
|
156
|
+
id: 'test-contact-id',
|
|
157
|
+
name: 'Test Contact',
|
|
158
|
+
type: 'Contact',
|
|
159
|
+
phone: '+1234567890',
|
|
160
|
+
additionalInfo: null
|
|
161
|
+
}
|
|
162
|
+
]),
|
|
163
|
+
createContact: jest.fn().mockResolvedValue({
|
|
164
|
+
contactInfo: {
|
|
165
|
+
id: 'new-contact-id',
|
|
166
|
+
name: 'New Contact'
|
|
167
|
+
},
|
|
168
|
+
returnMessage: {
|
|
169
|
+
message: 'Contact created successfully',
|
|
170
|
+
messageType: 'success',
|
|
171
|
+
ttl: 2000
|
|
172
|
+
}
|
|
173
|
+
}),
|
|
174
|
+
...overrides
|
|
175
|
+
})
|
|
176
|
+
};
|