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