@app-connect/core 1.7.8 → 1.7.11
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 +44 -21
- package/handlers/auth.js +97 -69
- package/handlers/calldown.js +10 -4
- package/handlers/contact.js +45 -112
- package/handlers/disposition.js +4 -142
- package/handlers/log.js +174 -259
- package/handlers/user.js +19 -6
- package/index.js +310 -122
- 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 -12
- 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 +90 -0
- package/mcp/tools/collectAuthInfo.js +86 -0
- package/mcp/tools/createCallLog.js +299 -0
- package/mcp/tools/createMessageLog.js +283 -0
- package/mcp/tools/doAuth.js +185 -0
- package/mcp/tools/findContactByName.js +87 -0
- package/mcp/tools/findContactByPhone.js +96 -0
- package/mcp/tools/getCallLog.js +98 -0
- package/mcp/tools/getHelp.js +39 -0
- package/mcp/tools/getPublicConnectors.js +46 -0
- package/mcp/tools/index.js +58 -0
- package/mcp/tools/logout.js +63 -0
- package/mcp/tools/rcGetCallLogs.js +73 -0
- package/mcp/tools/setConnector.js +64 -0
- package/mcp/tools/updateCallLog.js +122 -0
- package/models/accountDataModel.js +34 -0
- package/models/cacheModel.js +3 -0
- package/package.json +6 -4
- package/releaseNotes.json +36 -0
- package/test/connector/registry.test.js +145 -0
- package/test/handlers/admin.test.js +583 -0
- package/test/handlers/auth.test.js +355 -0
- package/test/handlers/contact.test.js +852 -0
- package/test/handlers/log.test.js +872 -0
- package/test/lib/callLogComposer.test.js +1231 -0
- package/test/lib/debugTracer.test.js +328 -0
- package/test/lib/logger.test.js +206 -0
- package/test/lib/oauth.test.js +359 -0
- package/test/lib/ringcentral.test.js +473 -0
- package/test/lib/sharedSMSComposer.test.js +1084 -0
- package/test/lib/util.test.js +282 -0
- package/test/mcp/tools/collectAuthInfo.test.js +192 -0
- package/test/mcp/tools/createCallLog.test.js +412 -0
- package/test/mcp/tools/createMessageLog.test.js +580 -0
- package/test/mcp/tools/doAuth.test.js +363 -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/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
- package/test/models/accountDataModel.test.js +98 -0
- package/test/models/dynamo/connectorSchema.test.js +189 -0
- package/test/models/models.test.js +539 -0
- package/test/setup.js +176 -176
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const logout = require('../../../mcp/tools/logout');
|
|
2
|
+
const jwt = require('../../../lib/jwt');
|
|
3
|
+
const { UserModel } = require('../../../models/userModel');
|
|
4
|
+
const connectorRegistry = require('../../../connector/registry');
|
|
5
|
+
|
|
6
|
+
// Mock dependencies
|
|
7
|
+
jest.mock('../../../lib/jwt');
|
|
8
|
+
jest.mock('../../../models/userModel');
|
|
9
|
+
jest.mock('../../../connector/registry');
|
|
10
|
+
|
|
11
|
+
describe('MCP Tool: logout', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('tool definition', () => {
|
|
17
|
+
test('should have correct tool definition', () => {
|
|
18
|
+
expect(logout.definition).toBeDefined();
|
|
19
|
+
expect(logout.definition.name).toBe('logout');
|
|
20
|
+
expect(logout.definition.description).toContain('Logout');
|
|
21
|
+
expect(logout.definition.inputSchema).toBeDefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('should have jwtToken property', () => {
|
|
25
|
+
expect(logout.definition.inputSchema.properties).toHaveProperty('jwtToken');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('execute', () => {
|
|
30
|
+
test('should logout user successfully', async () => {
|
|
31
|
+
// Arrange
|
|
32
|
+
const mockUser = {
|
|
33
|
+
id: 'test-user-id',
|
|
34
|
+
platform: 'testCRM',
|
|
35
|
+
accessToken: 'test-access-token'
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const mockConnector = {
|
|
39
|
+
unAuthorize: jest.fn().mockResolvedValue({
|
|
40
|
+
returnMessage: {
|
|
41
|
+
messageType: 'success',
|
|
42
|
+
message: 'Logged out successfully'
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
jwt.decodeJwt.mockReturnValue({
|
|
48
|
+
id: 'test-user-id',
|
|
49
|
+
platform: 'testCRM'
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
53
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
54
|
+
|
|
55
|
+
// Act
|
|
56
|
+
const result = await logout.execute({
|
|
57
|
+
jwtToken: 'mock-jwt-token'
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Assert
|
|
61
|
+
expect(result).toEqual({
|
|
62
|
+
success: true,
|
|
63
|
+
data: {
|
|
64
|
+
message: expect.stringContaining('IMPORTANT')
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
expect(jwt.decodeJwt).toHaveBeenCalledWith('mock-jwt-token');
|
|
68
|
+
expect(UserModel.findByPk).toHaveBeenCalledWith('test-user-id');
|
|
69
|
+
expect(connectorRegistry.getConnector).toHaveBeenCalledWith('testCRM');
|
|
70
|
+
expect(mockConnector.unAuthorize).toHaveBeenCalledWith({
|
|
71
|
+
user: mockUser
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('should return error when user not found', async () => {
|
|
76
|
+
// Arrange
|
|
77
|
+
jwt.decodeJwt.mockReturnValue({
|
|
78
|
+
id: 'non-existent-user',
|
|
79
|
+
platform: 'testCRM'
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
UserModel.findByPk.mockResolvedValue(null);
|
|
83
|
+
|
|
84
|
+
// Act
|
|
85
|
+
const result = await logout.execute({
|
|
86
|
+
jwtToken: 'mock-jwt-token'
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Assert
|
|
90
|
+
expect(result).toEqual({
|
|
91
|
+
success: false,
|
|
92
|
+
error: 'User not found',
|
|
93
|
+
errorDetails: 'User not found'
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('should handle logout errors gracefully', async () => {
|
|
98
|
+
// Arrange
|
|
99
|
+
const mockUser = {
|
|
100
|
+
id: 'test-user-id',
|
|
101
|
+
platform: 'testCRM'
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const mockConnector = {
|
|
105
|
+
unAuthorize: jest.fn().mockRejectedValue(
|
|
106
|
+
new Error('Logout API failed')
|
|
107
|
+
)
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
jwt.decodeJwt.mockReturnValue({
|
|
111
|
+
id: 'test-user-id',
|
|
112
|
+
platform: 'testCRM'
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
116
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
117
|
+
|
|
118
|
+
// Act
|
|
119
|
+
const result = await logout.execute({
|
|
120
|
+
jwtToken: 'mock-jwt-token'
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Assert
|
|
124
|
+
expect(result.success).toBe(false);
|
|
125
|
+
expect(result.error).toBe('Logout API failed');
|
|
126
|
+
expect(result.errorDetails).toBeDefined();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('should handle invalid JWT token', async () => {
|
|
130
|
+
// Arrange
|
|
131
|
+
jwt.decodeJwt.mockReturnValue(null);
|
|
132
|
+
|
|
133
|
+
// Act
|
|
134
|
+
const result = await logout.execute({
|
|
135
|
+
jwtToken: 'invalid-token'
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Assert
|
|
139
|
+
expect(result.success).toBe(false);
|
|
140
|
+
expect(result.error).toBeDefined();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('should handle missing platform connector', async () => {
|
|
144
|
+
// Arrange
|
|
145
|
+
const mockUser = {
|
|
146
|
+
id: 'test-user-id',
|
|
147
|
+
platform: 'unknownCRM'
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
jwt.decodeJwt.mockReturnValue({
|
|
151
|
+
id: 'test-user-id',
|
|
152
|
+
platform: 'unknownCRM'
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
156
|
+
connectorRegistry.getConnector.mockReturnValue(null);
|
|
157
|
+
|
|
158
|
+
// Act
|
|
159
|
+
const result = await logout.execute({
|
|
160
|
+
jwtToken: 'mock-jwt-token'
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Assert
|
|
164
|
+
expect(result.success).toBe(false);
|
|
165
|
+
expect(result.error).toBeDefined();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
const setConnector = require('../../../mcp/tools/setConnector');
|
|
2
|
+
const developerPortal = require('../../../connector/developerPortal');
|
|
3
|
+
|
|
4
|
+
// Mock the developerPortal module
|
|
5
|
+
jest.mock('../../../connector/developerPortal');
|
|
6
|
+
|
|
7
|
+
describe('MCP Tool: setConnector', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
jest.clearAllMocks();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('tool definition', () => {
|
|
13
|
+
test('should have correct tool definition', () => {
|
|
14
|
+
expect(setConnector.definition).toBeDefined();
|
|
15
|
+
expect(setConnector.definition.name).toBe('setConnector');
|
|
16
|
+
expect(setConnector.definition.description).toContain('Auth flow step.2');
|
|
17
|
+
expect(setConnector.definition.inputSchema).toBeDefined();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('should require connectorDisplayName parameter', () => {
|
|
21
|
+
expect(setConnector.definition.inputSchema.required).toContain('connectorDisplayName');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('execute', () => {
|
|
26
|
+
test('should set connector successfully', async () => {
|
|
27
|
+
// Arrange
|
|
28
|
+
const mockPublicConnectors = [
|
|
29
|
+
{ id: '1', name: 'salesforce', displayName: 'Salesforce', status: 'public' }
|
|
30
|
+
];
|
|
31
|
+
const mockPrivateConnectors = [];
|
|
32
|
+
const mockManifest = {
|
|
33
|
+
platforms: {
|
|
34
|
+
salesforce: {
|
|
35
|
+
name: 'salesforce',
|
|
36
|
+
auth: { type: 'oauth' },
|
|
37
|
+
environment: { type: 'fixed' }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
developerPortal.getPublicConnectorList.mockResolvedValue({
|
|
43
|
+
connectors: mockPublicConnectors
|
|
44
|
+
});
|
|
45
|
+
developerPortal.getPrivateConnectorList.mockResolvedValue({
|
|
46
|
+
privateConnectors: mockPrivateConnectors
|
|
47
|
+
});
|
|
48
|
+
developerPortal.getConnectorManifest.mockResolvedValue(mockManifest);
|
|
49
|
+
|
|
50
|
+
// Act
|
|
51
|
+
const result = await setConnector.execute({
|
|
52
|
+
connectorDisplayName: 'Salesforce'
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Assert
|
|
56
|
+
expect(result).toEqual({
|
|
57
|
+
success: true,
|
|
58
|
+
data: {
|
|
59
|
+
connectorManifest: mockManifest,
|
|
60
|
+
connectorDisplayName: 'Salesforce',
|
|
61
|
+
connectorName: 'salesforce',
|
|
62
|
+
message: expect.stringContaining('IMPORTANT')
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
expect(developerPortal.getConnectorManifest).toHaveBeenCalledWith({
|
|
66
|
+
connectorId: '1',
|
|
67
|
+
isPrivate: false
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('should handle private connector', async () => {
|
|
72
|
+
// Arrange
|
|
73
|
+
const mockPublicConnectors = [];
|
|
74
|
+
const mockPrivateConnectors = [
|
|
75
|
+
{ id: '2', name: 'custom-crm', displayName: 'Custom CRM', status: 'private' }
|
|
76
|
+
];
|
|
77
|
+
const mockManifest = {
|
|
78
|
+
platforms: {
|
|
79
|
+
'custom-crm': {
|
|
80
|
+
name: 'custom-crm',
|
|
81
|
+
auth: { type: 'apiKey' }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
developerPortal.getPublicConnectorList.mockResolvedValue({
|
|
87
|
+
connectors: mockPublicConnectors
|
|
88
|
+
});
|
|
89
|
+
developerPortal.getPrivateConnectorList.mockResolvedValue({
|
|
90
|
+
privateConnectors: mockPrivateConnectors
|
|
91
|
+
});
|
|
92
|
+
developerPortal.getConnectorManifest.mockResolvedValue(mockManifest);
|
|
93
|
+
|
|
94
|
+
// Act
|
|
95
|
+
const result = await setConnector.execute({
|
|
96
|
+
connectorDisplayName: 'Custom CRM'
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Assert
|
|
100
|
+
expect(result.success).toBe(true);
|
|
101
|
+
expect(result.data.connectorName).toBe('custom-crm');
|
|
102
|
+
expect(developerPortal.getConnectorManifest).toHaveBeenCalledWith({
|
|
103
|
+
connectorId: '2',
|
|
104
|
+
isPrivate: true
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('should return error when connector manifest not found', async () => {
|
|
109
|
+
// Arrange
|
|
110
|
+
const mockPublicConnectors = [
|
|
111
|
+
{ id: '1', name: 'salesforce', displayName: 'Salesforce', status: 'public' }
|
|
112
|
+
];
|
|
113
|
+
const mockPrivateConnectors = [];
|
|
114
|
+
|
|
115
|
+
developerPortal.getPublicConnectorList.mockResolvedValue({
|
|
116
|
+
connectors: mockPublicConnectors
|
|
117
|
+
});
|
|
118
|
+
developerPortal.getPrivateConnectorList.mockResolvedValue({
|
|
119
|
+
privateConnectors: mockPrivateConnectors
|
|
120
|
+
});
|
|
121
|
+
developerPortal.getConnectorManifest.mockResolvedValue(null);
|
|
122
|
+
|
|
123
|
+
// Act
|
|
124
|
+
const result = await setConnector.execute({
|
|
125
|
+
connectorDisplayName: 'Salesforce'
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Assert
|
|
129
|
+
expect(result.success).toBe(false);
|
|
130
|
+
expect(result.error).toContain('Connector manifest not found');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('should return error when connector not found in list', async () => {
|
|
134
|
+
// Arrange
|
|
135
|
+
const mockPublicConnectors = [
|
|
136
|
+
{ id: '1', name: 'salesforce', displayName: 'Salesforce', status: 'public' }
|
|
137
|
+
];
|
|
138
|
+
const mockPrivateConnectors = [];
|
|
139
|
+
|
|
140
|
+
developerPortal.getPublicConnectorList.mockResolvedValue({
|
|
141
|
+
connectors: mockPublicConnectors
|
|
142
|
+
});
|
|
143
|
+
developerPortal.getPrivateConnectorList.mockResolvedValue({
|
|
144
|
+
privateConnectors: mockPrivateConnectors
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Act
|
|
148
|
+
const result = await setConnector.execute({
|
|
149
|
+
connectorDisplayName: 'NonExistentCRM'
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Assert
|
|
153
|
+
expect(result.success).toBe(false);
|
|
154
|
+
expect(result.error).toBeDefined();
|
|
155
|
+
expect(result.errorDetails).toBeDefined();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('should handle API errors gracefully', async () => {
|
|
159
|
+
// Arrange
|
|
160
|
+
const errorMessage = 'API connection failed';
|
|
161
|
+
developerPortal.getPublicConnectorList.mockRejectedValue(
|
|
162
|
+
new Error(errorMessage)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// Act
|
|
166
|
+
const result = await setConnector.execute({
|
|
167
|
+
connectorDisplayName: 'Salesforce'
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Assert
|
|
171
|
+
expect(result.success).toBe(false);
|
|
172
|
+
expect(result.error).toBe(errorMessage);
|
|
173
|
+
expect(result.errorDetails).toBeDefined();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
const updateCallLog = require('../../../mcp/tools/updateCallLog');
|
|
2
|
+
const jwt = require('../../../lib/jwt');
|
|
3
|
+
const connectorRegistry = require('../../../connector/registry');
|
|
4
|
+
const logCore = require('../../../handlers/log');
|
|
5
|
+
const util = require('../../../lib/util');
|
|
6
|
+
|
|
7
|
+
// Mock dependencies
|
|
8
|
+
jest.mock('../../../lib/jwt');
|
|
9
|
+
jest.mock('../../../connector/registry');
|
|
10
|
+
jest.mock('../../../handlers/log');
|
|
11
|
+
jest.mock('../../../lib/util');
|
|
12
|
+
|
|
13
|
+
describe('MCP Tool: updateCallLog', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
process.env.HASH_KEY = 'test-hash-key';
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('tool definition', () => {
|
|
20
|
+
test('should have correct tool definition', () => {
|
|
21
|
+
expect(updateCallLog.definition).toBeDefined();
|
|
22
|
+
expect(updateCallLog.definition.name).toBe('updateCallLog');
|
|
23
|
+
expect(updateCallLog.definition.description).toContain('REQUIRES AUTHENTICATION');
|
|
24
|
+
expect(updateCallLog.definition.inputSchema).toBeDefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should require jwtToken and incomingData parameters', () => {
|
|
28
|
+
expect(updateCallLog.definition.inputSchema.required).toContain('jwtToken');
|
|
29
|
+
expect(updateCallLog.definition.inputSchema.required).toContain('incomingData');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should require sessionId in incomingData', () => {
|
|
33
|
+
const incomingDataSchema = updateCallLog.definition.inputSchema.properties.incomingData;
|
|
34
|
+
expect(incomingDataSchema.required).toContain('sessionId');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('execute', () => {
|
|
39
|
+
test('should update call log successfully', async () => {
|
|
40
|
+
// Arrange
|
|
41
|
+
const mockIncomingData = {
|
|
42
|
+
sessionId: 'session-123',
|
|
43
|
+
note: 'Updated call note',
|
|
44
|
+
accountId: 'rc-account-123'
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
jwt.decodeJwt.mockReturnValue({
|
|
48
|
+
id: 'user-123',
|
|
49
|
+
platform: 'testCRM'
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const mockConnector = {
|
|
53
|
+
updateCallLog: jest.fn()
|
|
54
|
+
};
|
|
55
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
56
|
+
|
|
57
|
+
util.getHashValue.mockReturnValue('hashed-account-id');
|
|
58
|
+
|
|
59
|
+
logCore.updateCallLog.mockResolvedValue({
|
|
60
|
+
successful: true,
|
|
61
|
+
logId: 'crm-log-123',
|
|
62
|
+
updatedNote: 'Updated call note',
|
|
63
|
+
returnMessage: { message: 'Call log updated successfully' }
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Act
|
|
67
|
+
const result = await updateCallLog.execute({
|
|
68
|
+
jwtToken: 'mock-jwt-token',
|
|
69
|
+
incomingData: mockIncomingData
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Assert
|
|
73
|
+
expect(result).toEqual({
|
|
74
|
+
success: true,
|
|
75
|
+
data: {
|
|
76
|
+
logId: 'crm-log-123',
|
|
77
|
+
updatedNote: 'Updated call note',
|
|
78
|
+
message: 'Call log updated successfully'
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
expect(jwt.decodeJwt).toHaveBeenCalledWith('mock-jwt-token');
|
|
82
|
+
expect(util.getHashValue).toHaveBeenCalledWith('rc-account-123', 'test-hash-key');
|
|
83
|
+
expect(logCore.updateCallLog).toHaveBeenCalledWith({
|
|
84
|
+
platform: 'testCRM',
|
|
85
|
+
userId: 'user-123',
|
|
86
|
+
incomingData: mockIncomingData,
|
|
87
|
+
hashedAccountId: 'hashed-account-id',
|
|
88
|
+
isFromSSCL: false
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('should update call log with additional submission', async () => {
|
|
93
|
+
// Arrange
|
|
94
|
+
const mockIncomingData = {
|
|
95
|
+
sessionId: 'session-456',
|
|
96
|
+
note: 'Updated note with additional data',
|
|
97
|
+
additionalSubmission: {
|
|
98
|
+
dealId: 'deal-123',
|
|
99
|
+
customField: 'custom-value'
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
jwt.decodeJwt.mockReturnValue({
|
|
104
|
+
id: 'user-123',
|
|
105
|
+
platform: 'testCRM'
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const mockConnector = {
|
|
109
|
+
updateCallLog: jest.fn()
|
|
110
|
+
};
|
|
111
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
112
|
+
|
|
113
|
+
logCore.updateCallLog.mockResolvedValue({
|
|
114
|
+
successful: true,
|
|
115
|
+
logId: 'crm-log-456',
|
|
116
|
+
updatedNote: 'Updated note with additional data'
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Act
|
|
120
|
+
const result = await updateCallLog.execute({
|
|
121
|
+
jwtToken: 'mock-jwt-token',
|
|
122
|
+
incomingData: mockIncomingData
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Assert
|
|
126
|
+
expect(result.success).toBe(true);
|
|
127
|
+
expect(result.data.logId).toBe('crm-log-456');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('should update call log without accountId', async () => {
|
|
131
|
+
// Arrange
|
|
132
|
+
const mockIncomingData = {
|
|
133
|
+
sessionId: 'session-789',
|
|
134
|
+
note: 'Simple update'
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
jwt.decodeJwt.mockReturnValue({
|
|
138
|
+
id: 'user-123',
|
|
139
|
+
platform: 'testCRM'
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const mockConnector = {
|
|
143
|
+
updateCallLog: jest.fn()
|
|
144
|
+
};
|
|
145
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
146
|
+
|
|
147
|
+
logCore.updateCallLog.mockResolvedValue({
|
|
148
|
+
successful: true,
|
|
149
|
+
logId: 'crm-log-789',
|
|
150
|
+
updatedNote: 'Simple update'
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Act
|
|
154
|
+
const result = await updateCallLog.execute({
|
|
155
|
+
jwtToken: 'mock-jwt-token',
|
|
156
|
+
incomingData: mockIncomingData
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Assert
|
|
160
|
+
expect(result.success).toBe(true);
|
|
161
|
+
expect(util.getHashValue).not.toHaveBeenCalled();
|
|
162
|
+
expect(logCore.updateCallLog).toHaveBeenCalledWith({
|
|
163
|
+
platform: 'testCRM',
|
|
164
|
+
userId: 'user-123',
|
|
165
|
+
incomingData: mockIncomingData,
|
|
166
|
+
hashedAccountId: undefined,
|
|
167
|
+
isFromSSCL: false
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('should return error when JWT is invalid', async () => {
|
|
172
|
+
// Arrange
|
|
173
|
+
const mockIncomingData = {
|
|
174
|
+
sessionId: 'session-123',
|
|
175
|
+
note: 'Test note'
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
jwt.decodeJwt.mockReturnValue({
|
|
179
|
+
platform: 'testCRM'
|
|
180
|
+
// id is missing
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Act
|
|
184
|
+
const result = await updateCallLog.execute({
|
|
185
|
+
jwtToken: 'invalid-token',
|
|
186
|
+
incomingData: mockIncomingData
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Assert
|
|
190
|
+
expect(result.success).toBe(false);
|
|
191
|
+
expect(result.error).toContain('Invalid JWT token');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('should return error when platform connector not found', async () => {
|
|
195
|
+
// Arrange
|
|
196
|
+
const mockIncomingData = {
|
|
197
|
+
sessionId: 'session-123',
|
|
198
|
+
note: 'Test note'
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
jwt.decodeJwt.mockReturnValue({
|
|
202
|
+
id: 'user-123',
|
|
203
|
+
platform: 'unknownCRM'
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
connectorRegistry.getConnector.mockReturnValue(null);
|
|
207
|
+
|
|
208
|
+
// Act
|
|
209
|
+
const result = await updateCallLog.execute({
|
|
210
|
+
jwtToken: 'mock-jwt-token',
|
|
211
|
+
incomingData: mockIncomingData
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Assert
|
|
215
|
+
expect(result.success).toBe(false);
|
|
216
|
+
expect(result.error).toContain('Platform connector not found');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('should return error when updateCallLog is not implemented', async () => {
|
|
220
|
+
// Arrange
|
|
221
|
+
const mockIncomingData = {
|
|
222
|
+
sessionId: 'session-123',
|
|
223
|
+
note: 'Test note'
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
jwt.decodeJwt.mockReturnValue({
|
|
227
|
+
id: 'user-123',
|
|
228
|
+
platform: 'testCRM'
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const mockConnector = {}; // No updateCallLog method
|
|
232
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
233
|
+
|
|
234
|
+
// Act
|
|
235
|
+
const result = await updateCallLog.execute({
|
|
236
|
+
jwtToken: 'mock-jwt-token',
|
|
237
|
+
incomingData: mockIncomingData
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Assert
|
|
241
|
+
expect(result.success).toBe(false);
|
|
242
|
+
expect(result.error).toContain('not implemented');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('should return error when update fails', async () => {
|
|
246
|
+
// Arrange
|
|
247
|
+
const mockIncomingData = {
|
|
248
|
+
sessionId: 'session-999',
|
|
249
|
+
note: 'Test note'
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
jwt.decodeJwt.mockReturnValue({
|
|
253
|
+
id: 'user-123',
|
|
254
|
+
platform: 'testCRM'
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const mockConnector = {
|
|
258
|
+
updateCallLog: jest.fn()
|
|
259
|
+
};
|
|
260
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
261
|
+
|
|
262
|
+
logCore.updateCallLog.mockResolvedValue({
|
|
263
|
+
successful: false,
|
|
264
|
+
returnMessage: { message: 'Log not found in CRM' }
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Act
|
|
268
|
+
const result = await updateCallLog.execute({
|
|
269
|
+
jwtToken: 'mock-jwt-token',
|
|
270
|
+
incomingData: mockIncomingData
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Assert
|
|
274
|
+
expect(result.success).toBe(false);
|
|
275
|
+
expect(result.error).toBe('Log not found in CRM');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('should handle unexpected errors gracefully', async () => {
|
|
279
|
+
// Arrange
|
|
280
|
+
const mockIncomingData = {
|
|
281
|
+
sessionId: 'session-error',
|
|
282
|
+
note: 'Test note'
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
jwt.decodeJwt.mockReturnValue({
|
|
286
|
+
id: 'user-123',
|
|
287
|
+
platform: 'testCRM'
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const mockConnector = {
|
|
291
|
+
updateCallLog: jest.fn()
|
|
292
|
+
};
|
|
293
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
294
|
+
|
|
295
|
+
logCore.updateCallLog.mockRejectedValue(
|
|
296
|
+
new Error('API timeout')
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
// Act
|
|
300
|
+
const result = await updateCallLog.execute({
|
|
301
|
+
jwtToken: 'mock-jwt-token',
|
|
302
|
+
incomingData: mockIncomingData
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Assert
|
|
306
|
+
expect(result.success).toBe(false);
|
|
307
|
+
expect(result.error).toBe('API timeout');
|
|
308
|
+
expect(result.errorDetails).toBeDefined();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('should update call log with empty note', async () => {
|
|
312
|
+
// Arrange
|
|
313
|
+
const mockIncomingData = {
|
|
314
|
+
sessionId: 'session-empty-note',
|
|
315
|
+
note: ''
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
jwt.decodeJwt.mockReturnValue({
|
|
319
|
+
id: 'user-123',
|
|
320
|
+
platform: 'testCRM'
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const mockConnector = {
|
|
324
|
+
updateCallLog: jest.fn()
|
|
325
|
+
};
|
|
326
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
327
|
+
|
|
328
|
+
logCore.updateCallLog.mockResolvedValue({
|
|
329
|
+
successful: true,
|
|
330
|
+
logId: 'crm-log-empty',
|
|
331
|
+
updatedNote: ''
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Act
|
|
335
|
+
const result = await updateCallLog.execute({
|
|
336
|
+
jwtToken: 'mock-jwt-token',
|
|
337
|
+
incomingData: mockIncomingData
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Assert
|
|
341
|
+
expect(result.success).toBe(true);
|
|
342
|
+
expect(result.data.updatedNote).toBe('');
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|