@app-connect/core 1.7.10 → 1.7.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/connector/developerPortal.js +43 -0
- package/connector/proxy/index.js +10 -3
- package/connector/registry.js +8 -6
- package/handlers/admin.js +135 -22
- package/handlers/auth.js +89 -67
- package/handlers/calldown.js +10 -4
- package/handlers/contact.js +4 -104
- package/handlers/disposition.js +7 -145
- package/handlers/log.js +174 -258
- package/handlers/user.js +19 -6
- package/index.js +280 -47
- package/lib/analytics.js +3 -1
- package/lib/authSession.js +68 -0
- package/lib/callLogComposer.js +498 -420
- package/lib/errorHandler.js +206 -0
- package/lib/jwt.js +2 -0
- package/lib/logger.js +190 -0
- package/lib/oauth.js +21 -10
- package/lib/ringcentral.js +2 -10
- package/lib/sharedSMSComposer.js +471 -0
- package/mcp/SupportedPlatforms.md +12 -0
- package/mcp/lib/validator.js +91 -0
- package/mcp/mcpHandler.js +166 -0
- package/mcp/tools/checkAuthStatus.js +110 -0
- package/mcp/tools/collectAuthInfo.js +91 -0
- package/mcp/tools/createCallLog.js +308 -0
- package/mcp/tools/createContact.js +117 -0
- package/mcp/tools/createMessageLog.js +283 -0
- package/mcp/tools/doAuth.js +190 -0
- package/mcp/tools/findContactByName.js +92 -0
- package/mcp/tools/findContactByPhone.js +101 -0
- package/mcp/tools/getCallLog.js +98 -0
- package/mcp/tools/getGoogleFilePicker.js +103 -0
- package/mcp/tools/getHelp.js +44 -0
- package/mcp/tools/getPublicConnectors.js +53 -0
- package/mcp/tools/index.js +64 -0
- package/mcp/tools/logout.js +68 -0
- package/mcp/tools/rcGetCallLogs.js +78 -0
- package/mcp/tools/setConnector.js +69 -0
- package/mcp/tools/updateCallLog.js +122 -0
- package/models/cacheModel.js +3 -0
- package/package.json +71 -70
- package/releaseNotes.json +24 -0
- package/test/handlers/log.test.js +11 -4
- package/test/lib/logger.test.js +206 -0
- package/test/lib/ringcentral.test.js +0 -6
- package/test/lib/sharedSMSComposer.test.js +1084 -0
- package/test/mcp/tools/collectAuthInfo.test.js +234 -0
- package/test/mcp/tools/createCallLog.test.js +425 -0
- package/test/mcp/tools/createMessageLog.test.js +580 -0
- package/test/mcp/tools/doAuth.test.js +376 -0
- package/test/mcp/tools/findContactByName.test.js +263 -0
- package/test/mcp/tools/findContactByPhone.test.js +284 -0
- package/test/mcp/tools/getCallLog.test.js +286 -0
- package/test/mcp/tools/getGoogleFilePicker.test.js +281 -0
- package/test/mcp/tools/getPublicConnectors.test.js +128 -0
- package/test/mcp/tools/logout.test.js +169 -0
- package/test/mcp/tools/setConnector.test.js +177 -0
- package/test/mcp/tools/updateCallLog.test.js +346 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
const collectAuthInfo = require('../../../mcp/tools/collectAuthInfo');
|
|
2
|
+
|
|
3
|
+
describe('MCP Tool: collectAuthInfo', () => {
|
|
4
|
+
describe('tool definition', () => {
|
|
5
|
+
test('should have correct tool definition', () => {
|
|
6
|
+
expect(collectAuthInfo.definition).toBeDefined();
|
|
7
|
+
expect(collectAuthInfo.definition.name).toBe('collectAuthInfo');
|
|
8
|
+
expect(collectAuthInfo.definition.description).toContain('Auth flow step.3');
|
|
9
|
+
expect(collectAuthInfo.definition.inputSchema).toBeDefined();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('should require connectorManifest and connectorName parameters', () => {
|
|
13
|
+
expect(collectAuthInfo.definition.inputSchema.required).toContain('connectorManifest');
|
|
14
|
+
expect(collectAuthInfo.definition.inputSchema.required).toContain('connectorName');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('execute', () => {
|
|
19
|
+
test('should handle selectable environment type', async () => {
|
|
20
|
+
// Arrange
|
|
21
|
+
const mockManifest = {
|
|
22
|
+
platforms: {
|
|
23
|
+
salesforce: {
|
|
24
|
+
name: 'salesforce',
|
|
25
|
+
auth: {
|
|
26
|
+
type: 'oauth',
|
|
27
|
+
oauth: {
|
|
28
|
+
authUrl: 'https://login.salesforce.com/services/oauth2/authorize',
|
|
29
|
+
clientId: 'test-client-id'
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
environment: {
|
|
33
|
+
type: 'selectable',
|
|
34
|
+
selections: [
|
|
35
|
+
{ name: 'Production', const: 'https://login.salesforce.com' },
|
|
36
|
+
{ name: 'Sandbox', const: 'https://test.salesforce.com' }
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Act
|
|
44
|
+
const result = await collectAuthInfo.execute({
|
|
45
|
+
connectorManifest: mockManifest,
|
|
46
|
+
connectorName: 'salesforce',
|
|
47
|
+
selection: 'Production'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Assert
|
|
51
|
+
expect(result).toEqual({
|
|
52
|
+
success: true,
|
|
53
|
+
data: {
|
|
54
|
+
hostname: 'login.salesforce.com',
|
|
55
|
+
message: expect.stringContaining('IMPORTANT')
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('should handle dynamic environment type', async () => {
|
|
61
|
+
// Arrange
|
|
62
|
+
const mockManifest = {
|
|
63
|
+
platforms: {
|
|
64
|
+
netsuite: {
|
|
65
|
+
name: 'netsuite',
|
|
66
|
+
auth: {
|
|
67
|
+
type: 'oauth',
|
|
68
|
+
oauth: {
|
|
69
|
+
authUrl: 'https://system.netsuite.com/app/login/oauth2/authorize.nl',
|
|
70
|
+
clientId: 'test-client-id'
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
environment: {
|
|
74
|
+
type: 'dynamic'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Act
|
|
81
|
+
const result = await collectAuthInfo.execute({
|
|
82
|
+
connectorManifest: mockManifest,
|
|
83
|
+
connectorName: 'netsuite',
|
|
84
|
+
hostname: 'https://1234567.app.netsuite.com'
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Assert
|
|
88
|
+
expect(result).toEqual({
|
|
89
|
+
success: true,
|
|
90
|
+
data: {
|
|
91
|
+
hostname: '1234567.app.netsuite.com',
|
|
92
|
+
message: expect.stringContaining('IMPORTANT')
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('should handle sandbox selection', async () => {
|
|
98
|
+
// Arrange
|
|
99
|
+
const mockManifest = {
|
|
100
|
+
platforms: {
|
|
101
|
+
salesforce: {
|
|
102
|
+
name: 'salesforce',
|
|
103
|
+
auth: {
|
|
104
|
+
type: 'oauth',
|
|
105
|
+
oauth: {
|
|
106
|
+
authUrl: 'https://login.salesforce.com/services/oauth2/authorize',
|
|
107
|
+
clientId: 'test-client-id'
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
environment: {
|
|
111
|
+
type: 'selectable',
|
|
112
|
+
selections: [
|
|
113
|
+
{ name: 'Production', const: 'https://login.salesforce.com' },
|
|
114
|
+
{ name: 'Sandbox', const: 'https://test.salesforce.com' }
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Act
|
|
122
|
+
const result = await collectAuthInfo.execute({
|
|
123
|
+
connectorManifest: mockManifest,
|
|
124
|
+
connectorName: 'salesforce',
|
|
125
|
+
selection: 'Sandbox'
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Assert
|
|
129
|
+
expect(result.success).toBe(true);
|
|
130
|
+
expect(result.data.hostname).toBe('test.salesforce.com');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('should handle invalid hostname URL', async () => {
|
|
134
|
+
// Arrange
|
|
135
|
+
const mockManifest = {
|
|
136
|
+
platforms: {
|
|
137
|
+
netsuite: {
|
|
138
|
+
name: 'netsuite',
|
|
139
|
+
auth: {
|
|
140
|
+
type: 'oauth',
|
|
141
|
+
oauth: {
|
|
142
|
+
authUrl: 'https://system.netsuite.com/app/login/oauth2/authorize.nl',
|
|
143
|
+
clientId: 'test-client-id'
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
environment: {
|
|
147
|
+
type: 'dynamic'
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Act
|
|
154
|
+
const result = await collectAuthInfo.execute({
|
|
155
|
+
connectorManifest: mockManifest,
|
|
156
|
+
connectorName: 'netsuite',
|
|
157
|
+
hostname: 'invalid-url'
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Assert
|
|
161
|
+
expect(result.success).toBe(false);
|
|
162
|
+
expect(result.error).toBeDefined();
|
|
163
|
+
expect(result.errorDetails).toBeDefined();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('should handle missing selection for selectable type', async () => {
|
|
167
|
+
// Arrange
|
|
168
|
+
const mockManifest = {
|
|
169
|
+
platforms: {
|
|
170
|
+
salesforce: {
|
|
171
|
+
name: 'salesforce',
|
|
172
|
+
auth: {
|
|
173
|
+
type: 'oauth',
|
|
174
|
+
oauth: {
|
|
175
|
+
authUrl: 'https://login.salesforce.com/services/oauth2/authorize',
|
|
176
|
+
clientId: 'test-client-id'
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
environment: {
|
|
180
|
+
type: 'selectable',
|
|
181
|
+
selections: [
|
|
182
|
+
{ name: 'Production', const: 'https://login.salesforce.com' }
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Act
|
|
190
|
+
const result = await collectAuthInfo.execute({
|
|
191
|
+
connectorManifest: mockManifest,
|
|
192
|
+
connectorName: 'salesforce',
|
|
193
|
+
selection: 'NonExistent'
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Assert
|
|
197
|
+
expect(result.success).toBe(false);
|
|
198
|
+
expect(result.error).toBeDefined();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('should handle missing hostname for dynamic type', async () => {
|
|
202
|
+
// Arrange
|
|
203
|
+
const mockManifest = {
|
|
204
|
+
platforms: {
|
|
205
|
+
netsuite: {
|
|
206
|
+
name: 'netsuite',
|
|
207
|
+
auth: {
|
|
208
|
+
type: 'oauth',
|
|
209
|
+
oauth: {
|
|
210
|
+
authUrl: 'https://system.netsuite.com/app/login/oauth2/authorize.nl',
|
|
211
|
+
clientId: 'test-client-id'
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
environment: {
|
|
215
|
+
type: 'dynamic'
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Act
|
|
222
|
+
const result = await collectAuthInfo.execute({
|
|
223
|
+
connectorManifest: mockManifest,
|
|
224
|
+
connectorName: 'netsuite'
|
|
225
|
+
// hostname is missing
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Assert
|
|
229
|
+
expect(result.success).toBe(false);
|
|
230
|
+
expect(result.error).toBeDefined();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
const createCallLog = require('../../../mcp/tools/createCallLog');
|
|
2
|
+
const jwt = require('../../../lib/jwt');
|
|
3
|
+
const connectorRegistry = require('../../../connector/registry');
|
|
4
|
+
const logCore = require('../../../handlers/log');
|
|
5
|
+
const contactCore = require('../../../handlers/contact');
|
|
6
|
+
const util = require('../../../lib/util');
|
|
7
|
+
const { CallLogModel } = require('../../../models/callLogModel');
|
|
8
|
+
|
|
9
|
+
// Mock dependencies
|
|
10
|
+
jest.mock('../../../lib/jwt');
|
|
11
|
+
jest.mock('../../../connector/registry');
|
|
12
|
+
jest.mock('../../../handlers/log');
|
|
13
|
+
jest.mock('../../../handlers/contact');
|
|
14
|
+
jest.mock('../../../lib/util');
|
|
15
|
+
jest.mock('../../../models/callLogModel');
|
|
16
|
+
|
|
17
|
+
describe('MCP Tool: createCallLog', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
jest.clearAllMocks();
|
|
20
|
+
process.env.HASH_KEY = 'test-hash-key';
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('tool definition', () => {
|
|
24
|
+
test('should have correct tool definition', () => {
|
|
25
|
+
expect(createCallLog.definition).toBeDefined();
|
|
26
|
+
expect(createCallLog.definition.name).toBe('createCallLog');
|
|
27
|
+
expect(createCallLog.definition.description).toContain('REQUIRES AUTHENTICATION');
|
|
28
|
+
expect(createCallLog.definition.inputSchema).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('should require jwtToken and incomingData parameters', () => {
|
|
32
|
+
expect(createCallLog.definition.inputSchema.required).toContain('jwtToken');
|
|
33
|
+
expect(createCallLog.definition.inputSchema.required).toContain('incomingData');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('should have detailed inputSchema for incomingData', () => {
|
|
37
|
+
const incomingDataSchema = createCallLog.definition.inputSchema.properties.incomingData;
|
|
38
|
+
expect(incomingDataSchema.properties).toHaveProperty('logInfo');
|
|
39
|
+
expect(incomingDataSchema.properties).toHaveProperty('note');
|
|
40
|
+
expect(incomingDataSchema.required).toContain('logInfo');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('execute', () => {
|
|
45
|
+
test('should create call log successfully', async () => {
|
|
46
|
+
// Arrange
|
|
47
|
+
const mockIncomingData = {
|
|
48
|
+
logInfo: {
|
|
49
|
+
id: 'rc-call-123',
|
|
50
|
+
sessionId: 'session-123',
|
|
51
|
+
direction: 'Inbound',
|
|
52
|
+
startTime: '2024-01-01T10:00:00Z',
|
|
53
|
+
duration: 120,
|
|
54
|
+
from: { phoneNumber: '+1234567890', name: 'John Doe' },
|
|
55
|
+
to: { phoneNumber: '+0987654321', name: 'Company' },
|
|
56
|
+
accountId: 'rc-account-123'
|
|
57
|
+
},
|
|
58
|
+
contactName: 'John Doe',
|
|
59
|
+
contactType: 'Contact',
|
|
60
|
+
note: 'Test call note'
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
jwt.decodeJwt.mockReturnValue({
|
|
64
|
+
id: 'user-123',
|
|
65
|
+
platform: 'testCRM'
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const mockConnector = {
|
|
69
|
+
createCallLog: jest.fn()
|
|
70
|
+
};
|
|
71
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
72
|
+
|
|
73
|
+
CallLogModel.findOne.mockResolvedValue(null); // No existing log
|
|
74
|
+
|
|
75
|
+
util.getHashValue.mockReturnValue('hashed-account-id');
|
|
76
|
+
|
|
77
|
+
// Mock contactCore.findContact to return a contact
|
|
78
|
+
contactCore.findContact.mockResolvedValue({
|
|
79
|
+
successful: true,
|
|
80
|
+
contact: [{ id: 'contact-123', isNewContact: false }]
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
logCore.createCallLog.mockResolvedValue({
|
|
84
|
+
successful: true,
|
|
85
|
+
logId: 'crm-log-123',
|
|
86
|
+
returnMessage: { message: 'Call logged successfully' }
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Act
|
|
90
|
+
const result = await createCallLog.execute({
|
|
91
|
+
jwtToken: 'mock-jwt-token',
|
|
92
|
+
incomingData: mockIncomingData
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Assert
|
|
96
|
+
expect(result).toEqual({
|
|
97
|
+
success: true,
|
|
98
|
+
data: {
|
|
99
|
+
logId: 'crm-log-123',
|
|
100
|
+
message: 'Call logged successfully'
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
expect(jwt.decodeJwt).toHaveBeenCalledWith('mock-jwt-token');
|
|
104
|
+
expect(CallLogModel.findOne).toHaveBeenCalledWith({
|
|
105
|
+
where: { sessionId: 'session-123' }
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('should create call log with AI note and transcript', async () => {
|
|
110
|
+
// Arrange
|
|
111
|
+
const mockIncomingData = {
|
|
112
|
+
logInfo: {
|
|
113
|
+
id: 'rc-call-456',
|
|
114
|
+
sessionId: 'session-456',
|
|
115
|
+
direction: 'Outbound',
|
|
116
|
+
startTime: '2024-01-01T11:00:00Z',
|
|
117
|
+
duration: 300,
|
|
118
|
+
from: { phoneNumber: '+0987654321' },
|
|
119
|
+
to: { phoneNumber: '+1234567890' }
|
|
120
|
+
},
|
|
121
|
+
aiNote: 'AI generated summary of the call',
|
|
122
|
+
transcript: 'Full call transcript text'
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
jwt.decodeJwt.mockReturnValue({
|
|
126
|
+
id: 'user-123',
|
|
127
|
+
platform: 'testCRM'
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const mockConnector = {
|
|
131
|
+
createCallLog: jest.fn()
|
|
132
|
+
};
|
|
133
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
134
|
+
|
|
135
|
+
CallLogModel.findOne.mockResolvedValue(null);
|
|
136
|
+
|
|
137
|
+
// Mock contactCore.findContact to return a contact
|
|
138
|
+
contactCore.findContact.mockResolvedValue({
|
|
139
|
+
successful: true,
|
|
140
|
+
contact: [{ id: 'contact-456', isNewContact: false }]
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
logCore.createCallLog.mockResolvedValue({
|
|
144
|
+
successful: true,
|
|
145
|
+
logId: 'crm-log-456'
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Act
|
|
149
|
+
const result = await createCallLog.execute({
|
|
150
|
+
jwtToken: 'mock-jwt-token',
|
|
151
|
+
incomingData: mockIncomingData
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Assert
|
|
155
|
+
expect(result.success).toBe(true);
|
|
156
|
+
expect(result.data.logId).toBe('crm-log-456');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('should create call log with additional submission', async () => {
|
|
160
|
+
// Arrange
|
|
161
|
+
const mockIncomingData = {
|
|
162
|
+
logInfo: {
|
|
163
|
+
id: 'rc-call-789',
|
|
164
|
+
sessionId: 'session-789',
|
|
165
|
+
direction: 'Inbound',
|
|
166
|
+
startTime: '2024-01-01T12:00:00Z',
|
|
167
|
+
duration: 60,
|
|
168
|
+
from: { phoneNumber: '+1234567890' },
|
|
169
|
+
to: { phoneNumber: '+0987654321' }
|
|
170
|
+
},
|
|
171
|
+
additionalSubmission: {
|
|
172
|
+
isAssignedToUser: true,
|
|
173
|
+
adminAssignedUserToken: 'admin-jwt-token',
|
|
174
|
+
adminAssignedUserRcId: 'rc-ext-101'
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
jwt.decodeJwt.mockReturnValue({
|
|
179
|
+
id: 'user-123',
|
|
180
|
+
platform: 'testCRM'
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const mockConnector = {
|
|
184
|
+
createCallLog: jest.fn()
|
|
185
|
+
};
|
|
186
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
187
|
+
|
|
188
|
+
CallLogModel.findOne.mockResolvedValue(null);
|
|
189
|
+
|
|
190
|
+
// Mock contactCore.findContact to return a contact
|
|
191
|
+
contactCore.findContact.mockResolvedValue({
|
|
192
|
+
successful: true,
|
|
193
|
+
contact: [{ id: 'contact-789', isNewContact: false }]
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
logCore.createCallLog.mockResolvedValue({
|
|
197
|
+
successful: true,
|
|
198
|
+
logId: 'crm-log-789'
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Act
|
|
202
|
+
const result = await createCallLog.execute({
|
|
203
|
+
jwtToken: 'mock-jwt-token',
|
|
204
|
+
incomingData: mockIncomingData
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Assert
|
|
208
|
+
expect(result.success).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('should return error when call log already exists', async () => {
|
|
212
|
+
// Arrange
|
|
213
|
+
const mockIncomingData = {
|
|
214
|
+
logInfo: {
|
|
215
|
+
sessionId: 'existing-session'
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
CallLogModel.findOne.mockResolvedValue({
|
|
220
|
+
id: 'existing-log',
|
|
221
|
+
sessionId: 'existing-session'
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Act
|
|
225
|
+
const result = await createCallLog.execute({
|
|
226
|
+
jwtToken: 'mock-jwt-token',
|
|
227
|
+
incomingData: mockIncomingData
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Assert
|
|
231
|
+
expect(result.success).toBe(false);
|
|
232
|
+
expect(result.error).toContain('already exists');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('should return error when jwtToken is missing', async () => {
|
|
236
|
+
// Act
|
|
237
|
+
const result = await createCallLog.execute({
|
|
238
|
+
incomingData: { logInfo: {} }
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Assert
|
|
242
|
+
expect(result.success).toBe(false);
|
|
243
|
+
expect(result.error).toContain('authorize CRM platform');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('should return error when incomingData is missing', async () => {
|
|
247
|
+
// Act
|
|
248
|
+
const result = await createCallLog.execute({
|
|
249
|
+
jwtToken: 'mock-jwt-token'
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Assert
|
|
253
|
+
expect(result.success).toBe(false);
|
|
254
|
+
expect(result.error).toContain('Incoming data must be provided');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('should return error when logInfo is missing', async () => {
|
|
258
|
+
// Act
|
|
259
|
+
const result = await createCallLog.execute({
|
|
260
|
+
jwtToken: 'mock-jwt-token',
|
|
261
|
+
incomingData: { contactId: 'contact-123' }
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Assert
|
|
265
|
+
expect(result.success).toBe(false);
|
|
266
|
+
expect(result.error).toContain('logInfo is required');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('should return error when JWT is invalid', async () => {
|
|
270
|
+
// Arrange
|
|
271
|
+
const mockIncomingData = {
|
|
272
|
+
logInfo: {
|
|
273
|
+
sessionId: 'session-123'
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
CallLogModel.findOne.mockResolvedValue(null);
|
|
278
|
+
|
|
279
|
+
jwt.decodeJwt.mockReturnValue({
|
|
280
|
+
platform: 'testCRM'
|
|
281
|
+
// id is missing
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Act
|
|
285
|
+
const result = await createCallLog.execute({
|
|
286
|
+
jwtToken: 'invalid-token',
|
|
287
|
+
incomingData: mockIncomingData
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Assert
|
|
291
|
+
expect(result.success).toBe(false);
|
|
292
|
+
expect(result.error).toContain('Invalid JWT token');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test('should return error when platform connector not found', async () => {
|
|
296
|
+
// Arrange
|
|
297
|
+
const mockIncomingData = {
|
|
298
|
+
logInfo: {
|
|
299
|
+
sessionId: 'session-123'
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
CallLogModel.findOne.mockResolvedValue(null);
|
|
304
|
+
|
|
305
|
+
jwt.decodeJwt.mockReturnValue({
|
|
306
|
+
id: 'user-123',
|
|
307
|
+
platform: 'unknownCRM'
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
connectorRegistry.getConnector.mockReturnValue(null);
|
|
311
|
+
|
|
312
|
+
// Act
|
|
313
|
+
const result = await createCallLog.execute({
|
|
314
|
+
jwtToken: 'mock-jwt-token',
|
|
315
|
+
incomingData: mockIncomingData
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Assert
|
|
319
|
+
expect(result.success).toBe(false);
|
|
320
|
+
expect(result.error).toContain('Platform connector not found');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test('should return error when createCallLog is not implemented', async () => {
|
|
324
|
+
// Arrange
|
|
325
|
+
const mockIncomingData = {
|
|
326
|
+
logInfo: {
|
|
327
|
+
sessionId: 'session-123'
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
CallLogModel.findOne.mockResolvedValue(null);
|
|
332
|
+
|
|
333
|
+
jwt.decodeJwt.mockReturnValue({
|
|
334
|
+
id: 'user-123',
|
|
335
|
+
platform: 'testCRM'
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const mockConnector = {}; // No createCallLog method
|
|
339
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
340
|
+
|
|
341
|
+
// Act
|
|
342
|
+
const result = await createCallLog.execute({
|
|
343
|
+
jwtToken: 'mock-jwt-token',
|
|
344
|
+
incomingData: mockIncomingData
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Assert
|
|
348
|
+
expect(result.success).toBe(false);
|
|
349
|
+
expect(result.error).toContain('not implemented');
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('should return error when creation fails', async () => {
|
|
353
|
+
// Arrange
|
|
354
|
+
const mockIncomingData = {
|
|
355
|
+
logInfo: {
|
|
356
|
+
id: 'rc-call-999',
|
|
357
|
+
sessionId: 'session-999',
|
|
358
|
+
direction: 'Inbound',
|
|
359
|
+
startTime: '2024-01-01T13:00:00Z',
|
|
360
|
+
duration: 45,
|
|
361
|
+
from: { phoneNumber: '+1234567890' },
|
|
362
|
+
to: { phoneNumber: '+0987654321' }
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
jwt.decodeJwt.mockReturnValue({
|
|
367
|
+
id: 'user-123',
|
|
368
|
+
platform: 'testCRM'
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const mockConnector = {
|
|
372
|
+
createCallLog: jest.fn()
|
|
373
|
+
};
|
|
374
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
375
|
+
|
|
376
|
+
CallLogModel.findOne.mockResolvedValue(null);
|
|
377
|
+
|
|
378
|
+
// Mock contactCore.findContact to return a contact
|
|
379
|
+
contactCore.findContact.mockResolvedValue({
|
|
380
|
+
successful: true,
|
|
381
|
+
contact: [{ id: 'contact-999', isNewContact: false }]
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
logCore.createCallLog.mockResolvedValue({
|
|
385
|
+
successful: false,
|
|
386
|
+
returnMessage: { message: 'Failed to create log in CRM' }
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Act
|
|
390
|
+
const result = await createCallLog.execute({
|
|
391
|
+
jwtToken: 'mock-jwt-token',
|
|
392
|
+
incomingData: mockIncomingData
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Assert
|
|
396
|
+
expect(result.success).toBe(false);
|
|
397
|
+
expect(result.error).toBe('Failed to create log in CRM');
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test('should handle unexpected errors gracefully', async () => {
|
|
401
|
+
// Arrange
|
|
402
|
+
const mockIncomingData = {
|
|
403
|
+
logInfo: {
|
|
404
|
+
sessionId: 'session-error'
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
CallLogModel.findOne.mockRejectedValue(
|
|
409
|
+
new Error('Database connection failed')
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
// Act
|
|
413
|
+
const result = await createCallLog.execute({
|
|
414
|
+
jwtToken: 'mock-jwt-token',
|
|
415
|
+
incomingData: mockIncomingData
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// Assert
|
|
419
|
+
expect(result.success).toBe(false);
|
|
420
|
+
expect(result.error).toBe('Database connection failed');
|
|
421
|
+
expect(result.errorDetails).toBeDefined();
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|