@app-connect/core 1.7.24 → 1.7.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.test +5 -5
- package/README.md +441 -441
- package/connector/developerPortal.js +31 -42
- package/connector/mock.js +84 -77
- package/connector/proxy/engine.js +164 -163
- package/connector/proxy/index.js +500 -500
- package/connector/registry.js +252 -252
- package/docs/README.md +50 -50
- package/docs/architecture.md +93 -93
- package/docs/connectors.md +116 -117
- package/docs/handlers.md +125 -125
- package/docs/libraries.md +101 -101
- package/docs/models.md +144 -144
- package/docs/routes.md +115 -115
- package/docs/tests.md +73 -73
- package/handlers/admin.js +523 -523
- package/handlers/appointment.js +193 -0
- package/handlers/auth.js +296 -296
- package/handlers/calldown.js +99 -99
- package/handlers/contact.js +280 -280
- package/handlers/disposition.js +82 -80
- package/handlers/log.js +984 -973
- package/handlers/managedAuth.js +446 -446
- package/handlers/plugin.js +208 -208
- package/handlers/user.js +142 -142
- package/index.js +3140 -2652
- package/jest.config.js +56 -56
- package/lib/analytics.js +54 -54
- package/lib/authSession.js +109 -109
- package/lib/cacheCleanup.js +21 -0
- package/lib/callLogComposer.js +898 -898
- package/lib/callLogLookup.js +34 -0
- package/lib/constants.js +8 -8
- package/lib/debugTracer.js +177 -177
- package/lib/encode.js +30 -30
- package/lib/errorHandler.js +218 -206
- package/lib/generalErrorMessage.js +41 -41
- package/lib/jwt.js +18 -18
- package/lib/logger.js +190 -190
- package/lib/migrateCallLogsSchema.js +116 -0
- package/lib/ringcentral.js +266 -266
- package/lib/s3ErrorLogReport.js +65 -65
- package/lib/sharedSMSComposer.js +471 -471
- package/lib/util.js +67 -67
- package/mcp/README.md +412 -395
- package/mcp/lib/validator.js +91 -91
- package/mcp/mcpHandler.js +425 -425
- package/mcp/tools/cancelAppointment.js +101 -0
- package/mcp/tools/checkAuthStatus.js +105 -105
- package/mcp/tools/confirmAppointment.js +101 -0
- package/mcp/tools/createAppointment.js +157 -0
- package/mcp/tools/createCallLog.js +327 -316
- package/mcp/tools/createContact.js +117 -117
- package/mcp/tools/createMessageLog.js +287 -287
- package/mcp/tools/doAuth.js +60 -60
- package/mcp/tools/findContactByName.js +93 -93
- package/mcp/tools/findContactByPhone.js +101 -101
- package/mcp/tools/getCallLog.js +111 -102
- package/mcp/tools/getGoogleFilePicker.js +99 -99
- package/mcp/tools/getHelp.js +43 -43
- package/mcp/tools/getPublicConnectors.js +94 -94
- package/mcp/tools/getSessionInfo.js +90 -90
- package/mcp/tools/index.js +51 -41
- package/mcp/tools/listAppointments.js +163 -0
- package/mcp/tools/logout.js +96 -96
- package/mcp/tools/rcGetCallLogs.js +65 -65
- package/mcp/tools/updateAppointment.js +154 -0
- package/mcp/tools/updateCallLog.js +130 -126
- package/mcp/ui/App/App.tsx +358 -358
- package/mcp/ui/App/components/AuthInfoForm.tsx +113 -113
- package/mcp/ui/App/components/AuthSuccess.tsx +22 -22
- package/mcp/ui/App/components/ConnectorList.tsx +82 -82
- package/mcp/ui/App/components/DebugPanel.tsx +43 -43
- package/mcp/ui/App/components/OAuthConnect.tsx +270 -270
- package/mcp/ui/App/lib/callTool.ts +130 -130
- package/mcp/ui/App/lib/debugLog.ts +41 -41
- package/mcp/ui/App/lib/developerPortal.ts +111 -111
- package/mcp/ui/App/main.css +5 -5
- package/mcp/ui/App/root.tsx +13 -13
- package/mcp/ui/index.html +13 -13
- package/mcp/ui/package-lock.json +6356 -6356
- package/mcp/ui/package.json +25 -25
- package/mcp/ui/tsconfig.json +26 -26
- package/mcp/ui/vite.config.ts +16 -16
- package/models/accountDataModel.js +33 -33
- package/models/adminConfigModel.js +35 -35
- package/models/cacheModel.js +30 -26
- package/models/callDownListModel.js +34 -34
- package/models/callLogModel.js +33 -27
- package/models/dynamo/connectorSchema.js +146 -146
- package/models/dynamo/lockSchema.js +24 -24
- package/models/dynamo/noteCacheSchema.js +29 -29
- package/models/llmSessionModel.js +17 -17
- package/models/messageLogModel.js +25 -25
- package/models/sequelize.js +16 -16
- package/models/userModel.js +45 -45
- package/package.json +72 -72
- package/releaseNotes.json +1093 -1073
- package/test/connector/proxy/engine.test.js +126 -93
- package/test/connector/proxy/index.test.js +279 -279
- package/test/connector/proxy/sample.json +161 -161
- package/test/connector/registry.test.js +415 -415
- package/test/handlers/admin.test.js +616 -616
- package/test/handlers/auth.test.js +1018 -1015
- package/test/handlers/contact.test.js +1014 -1014
- package/test/handlers/log.test.js +1298 -1160
- package/test/handlers/managedAuth.test.js +458 -458
- package/test/handlers/plugin.test.js +380 -380
- package/test/index.test.js +105 -105
- package/test/lib/cacheCleanup.test.js +42 -0
- package/test/lib/callLogComposer.test.js +1231 -1231
- package/test/lib/debugTracer.test.js +328 -328
- package/test/lib/jwt.test.js +176 -176
- package/test/lib/logger.test.js +206 -206
- package/test/lib/oauth.test.js +359 -359
- package/test/lib/ringcentral.test.js +467 -467
- package/test/lib/sharedSMSComposer.test.js +1084 -1084
- package/test/lib/util.test.js +329 -329
- package/test/mcp/tools/checkAuthStatus.test.js +83 -82
- package/test/mcp/tools/createCallLog.test.js +436 -436
- package/test/mcp/tools/createContact.test.js +58 -58
- package/test/mcp/tools/createMessageLog.test.js +595 -595
- package/test/mcp/tools/doAuth.test.js +113 -113
- package/test/mcp/tools/findContactByName.test.js +275 -275
- package/test/mcp/tools/findContactByPhone.test.js +296 -296
- package/test/mcp/tools/getCallLog.test.js +298 -298
- package/test/mcp/tools/getGoogleFilePicker.test.js +281 -281
- package/test/mcp/tools/getPublicConnectors.test.js +107 -107
- package/test/mcp/tools/getSessionInfo.test.js +127 -127
- package/test/mcp/tools/logout.test.js +233 -233
- package/test/mcp/tools/rcGetCallLogs.test.js +56 -56
- package/test/mcp/tools/updateCallLog.test.js +360 -360
- package/test/models/accountDataModel.test.js +98 -98
- package/test/models/dynamo/connectorSchema.test.js +189 -189
- package/test/models/models.test.js +568 -539
- package/test/routes/managedAuthRoutes.test.js +104 -129
- package/test/setup.js +178 -178
|
@@ -1,233 +1,233 @@
|
|
|
1
|
-
const logout = require('../../../mcp/tools/logout');
|
|
2
|
-
const jwt = require('../../../lib/jwt');
|
|
3
|
-
const { UserModel } = require('../../../models/userModel');
|
|
4
|
-
const { LlmSessionModel } = require('../../../models/llmSessionModel');
|
|
5
|
-
const { CacheModel } = require('../../../models/cacheModel');
|
|
6
|
-
const connectorRegistry = require('../../../connector/registry');
|
|
7
|
-
|
|
8
|
-
// Mock dependencies
|
|
9
|
-
jest.mock('../../../lib/jwt');
|
|
10
|
-
jest.mock('../../../models/userModel');
|
|
11
|
-
jest.mock('../../../models/llmSessionModel');
|
|
12
|
-
jest.mock('../../../models/cacheModel');
|
|
13
|
-
jest.mock('../../../connector/registry');
|
|
14
|
-
|
|
15
|
-
describe('MCP Tool: logout', () => {
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
jest.clearAllMocks();
|
|
18
|
-
LlmSessionModel.destroy.mockResolvedValue(1);
|
|
19
|
-
CacheModel.destroy.mockResolvedValue(1);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe('tool definition', () => {
|
|
23
|
-
test('should have correct tool definition', () => {
|
|
24
|
-
expect(logout.definition).toBeDefined();
|
|
25
|
-
expect(logout.definition.name).toBe('logout');
|
|
26
|
-
expect(logout.definition.description).toContain('Logout');
|
|
27
|
-
expect(logout.definition.inputSchema).toBeDefined();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('should have empty properties (jwtToken is server-injected, not in schema)', () => {
|
|
31
|
-
expect(logout.definition.inputSchema.properties).toEqual({});
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('execute', () => {
|
|
36
|
-
test('should logout user successfully', async () => {
|
|
37
|
-
// Arrange
|
|
38
|
-
const mockUser = {
|
|
39
|
-
id: 'test-user-id',
|
|
40
|
-
platform: 'testCRM',
|
|
41
|
-
accessToken: 'test-access-token'
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const mockConnector = {
|
|
45
|
-
unAuthorize: jest.fn().mockResolvedValue({
|
|
46
|
-
returnMessage: {
|
|
47
|
-
messageType: 'success',
|
|
48
|
-
message: 'Logged out successfully'
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
jwt.decodeJwt.mockReturnValue({
|
|
54
|
-
id: 'test-user-id',
|
|
55
|
-
platform: 'testCRM'
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
59
|
-
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
60
|
-
|
|
61
|
-
// Act
|
|
62
|
-
const result = await logout.execute({
|
|
63
|
-
jwtToken: 'mock-jwt-token'
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// Assert
|
|
67
|
-
expect(result).toEqual({
|
|
68
|
-
success: true,
|
|
69
|
-
data: {
|
|
70
|
-
message: expect.stringContaining('IMPORTANT')
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
expect(jwt.decodeJwt).toHaveBeenCalledWith('mock-jwt-token');
|
|
74
|
-
expect(LlmSessionModel.destroy).toHaveBeenCalledWith({ where: { id: 'test-user-id' } });
|
|
75
|
-
expect(UserModel.findByPk).toHaveBeenCalledWith('test-user-id');
|
|
76
|
-
expect(connectorRegistry.getConnector).toHaveBeenCalledWith('testCRM');
|
|
77
|
-
expect(mockConnector.unAuthorize).toHaveBeenCalledWith({
|
|
78
|
-
user: mockUser
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('should return error when user not found', async () => {
|
|
83
|
-
// Arrange
|
|
84
|
-
jwt.decodeJwt.mockReturnValue({
|
|
85
|
-
id: 'non-existent-user',
|
|
86
|
-
platform: 'testCRM'
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
UserModel.findByPk.mockResolvedValue(null);
|
|
90
|
-
|
|
91
|
-
// Act
|
|
92
|
-
const result = await logout.execute({
|
|
93
|
-
jwtToken: 'mock-jwt-token'
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Assert
|
|
97
|
-
expect(result).toEqual({
|
|
98
|
-
success: false,
|
|
99
|
-
error: 'User not found',
|
|
100
|
-
errorDetails: 'User not found'
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('should treat CRM unAuthorize failure as non-fatal and still succeed', async () => {
|
|
105
|
-
// Arrange
|
|
106
|
-
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
107
|
-
const mockUser = {
|
|
108
|
-
id: 'test-user-id',
|
|
109
|
-
platform: 'testCRM'
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const mockConnector = {
|
|
113
|
-
unAuthorize: jest.fn().mockRejectedValue(
|
|
114
|
-
new Error('Logout API failed')
|
|
115
|
-
)
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
jwt.decodeJwt.mockReturnValue({
|
|
119
|
-
id: 'test-user-id',
|
|
120
|
-
platform: 'testCRM'
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
124
|
-
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
125
|
-
|
|
126
|
-
// Act
|
|
127
|
-
const result = await logout.execute({
|
|
128
|
-
jwtToken: 'mock-jwt-token'
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Assert — local session is cleared; CRM revoke errors are logged only
|
|
132
|
-
expect(result.success).toBe(true);
|
|
133
|
-
expect(result.data.message).toContain('IMPORTANT');
|
|
134
|
-
expect(mockConnector.unAuthorize).toHaveBeenCalled();
|
|
135
|
-
expect(consoleSpy).toHaveBeenCalled();
|
|
136
|
-
consoleSpy.mockRestore();
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('should handle invalid JWT token', async () => {
|
|
140
|
-
// Arrange
|
|
141
|
-
jwt.decodeJwt.mockReturnValue(null);
|
|
142
|
-
|
|
143
|
-
// Act
|
|
144
|
-
const result = await logout.execute({
|
|
145
|
-
jwtToken: 'invalid-token'
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// Assert
|
|
149
|
-
expect(result.success).toBe(false);
|
|
150
|
-
expect(result.error).toBeDefined();
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test('should succeed when platform connector is missing (unAuthorize skipped)', async () => {
|
|
154
|
-
// Arrange
|
|
155
|
-
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
156
|
-
const mockUser = {
|
|
157
|
-
id: 'test-user-id',
|
|
158
|
-
platform: 'unknownCRM'
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
jwt.decodeJwt.mockReturnValue({
|
|
162
|
-
id: 'test-user-id',
|
|
163
|
-
platform: 'unknownCRM'
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
167
|
-
connectorRegistry.getConnector.mockReturnValue(null);
|
|
168
|
-
|
|
169
|
-
// Act
|
|
170
|
-
const result = await logout.execute({
|
|
171
|
-
jwtToken: 'mock-jwt-token'
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// Assert — null connector throws on unAuthorize; error is caught, logout still succeeds locally
|
|
175
|
-
expect(result.success).toBe(true);
|
|
176
|
-
expect(result.data.message).toContain('IMPORTANT');
|
|
177
|
-
expect(consoleSpy).toHaveBeenCalled();
|
|
178
|
-
consoleSpy.mockRestore();
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test('should clear both CRM user and RC extension session rows when rcExtensionId is provided', async () => {
|
|
182
|
-
const mockUser = {
|
|
183
|
-
id: 'test-user-id',
|
|
184
|
-
platform: 'testCRM'
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
jwt.decodeJwt.mockReturnValue({
|
|
188
|
-
id: 'test-user-id',
|
|
189
|
-
platform: 'testCRM'
|
|
190
|
-
});
|
|
191
|
-
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
192
|
-
connectorRegistry.getConnector.mockReturnValue({
|
|
193
|
-
unAuthorize: jest.fn().mockResolvedValue({})
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const result = await logout.execute({
|
|
197
|
-
jwtToken: 'mock-jwt-token',
|
|
198
|
-
rcExtensionId: 'rc-ext-123'
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
expect(result.success).toBe(true);
|
|
202
|
-
expect(LlmSessionModel.destroy).toHaveBeenNthCalledWith(1, { where: { id: 'test-user-id' } });
|
|
203
|
-
expect(LlmSessionModel.destroy).toHaveBeenNthCalledWith(2, { where: { id: 'rc-ext-123' } });
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test('should clear the resolved rcExtension cache for the current OpenAI session on logout', async () => {
|
|
207
|
-
const mockUser = {
|
|
208
|
-
id: 'test-user-id',
|
|
209
|
-
platform: 'testCRM'
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
jwt.decodeJwt.mockReturnValue({
|
|
213
|
-
id: 'test-user-id',
|
|
214
|
-
platform: 'testCRM'
|
|
215
|
-
});
|
|
216
|
-
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
217
|
-
connectorRegistry.getConnector.mockReturnValue({
|
|
218
|
-
unAuthorize: jest.fn().mockResolvedValue({})
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
const result = await logout.execute({
|
|
222
|
-
jwtToken: 'mock-jwt-token',
|
|
223
|
-
openaiSessionId: 'oa-session-123'
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
expect(result.success).toBe(true);
|
|
227
|
-
expect(CacheModel.destroy).toHaveBeenCalledWith({
|
|
228
|
-
where: { id: 'oa-session-123-rcExtensionId' }
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
|
|
1
|
+
const logout = require('../../../mcp/tools/logout');
|
|
2
|
+
const jwt = require('../../../lib/jwt');
|
|
3
|
+
const { UserModel } = require('../../../models/userModel');
|
|
4
|
+
const { LlmSessionModel } = require('../../../models/llmSessionModel');
|
|
5
|
+
const { CacheModel } = require('../../../models/cacheModel');
|
|
6
|
+
const connectorRegistry = require('../../../connector/registry');
|
|
7
|
+
|
|
8
|
+
// Mock dependencies
|
|
9
|
+
jest.mock('../../../lib/jwt');
|
|
10
|
+
jest.mock('../../../models/userModel');
|
|
11
|
+
jest.mock('../../../models/llmSessionModel');
|
|
12
|
+
jest.mock('../../../models/cacheModel');
|
|
13
|
+
jest.mock('../../../connector/registry');
|
|
14
|
+
|
|
15
|
+
describe('MCP Tool: logout', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
LlmSessionModel.destroy.mockResolvedValue(1);
|
|
19
|
+
CacheModel.destroy.mockResolvedValue(1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('tool definition', () => {
|
|
23
|
+
test('should have correct tool definition', () => {
|
|
24
|
+
expect(logout.definition).toBeDefined();
|
|
25
|
+
expect(logout.definition.name).toBe('logout');
|
|
26
|
+
expect(logout.definition.description).toContain('Logout');
|
|
27
|
+
expect(logout.definition.inputSchema).toBeDefined();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should have empty properties (jwtToken is server-injected, not in schema)', () => {
|
|
31
|
+
expect(logout.definition.inputSchema.properties).toEqual({});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('execute', () => {
|
|
36
|
+
test('should logout user successfully', async () => {
|
|
37
|
+
// Arrange
|
|
38
|
+
const mockUser = {
|
|
39
|
+
id: 'test-user-id',
|
|
40
|
+
platform: 'testCRM',
|
|
41
|
+
accessToken: 'test-access-token'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const mockConnector = {
|
|
45
|
+
unAuthorize: jest.fn().mockResolvedValue({
|
|
46
|
+
returnMessage: {
|
|
47
|
+
messageType: 'success',
|
|
48
|
+
message: 'Logged out successfully'
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
jwt.decodeJwt.mockReturnValue({
|
|
54
|
+
id: 'test-user-id',
|
|
55
|
+
platform: 'testCRM'
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
59
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
60
|
+
|
|
61
|
+
// Act
|
|
62
|
+
const result = await logout.execute({
|
|
63
|
+
jwtToken: 'mock-jwt-token'
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Assert
|
|
67
|
+
expect(result).toEqual({
|
|
68
|
+
success: true,
|
|
69
|
+
data: {
|
|
70
|
+
message: expect.stringContaining('IMPORTANT')
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
expect(jwt.decodeJwt).toHaveBeenCalledWith('mock-jwt-token');
|
|
74
|
+
expect(LlmSessionModel.destroy).toHaveBeenCalledWith({ where: { id: 'test-user-id' } });
|
|
75
|
+
expect(UserModel.findByPk).toHaveBeenCalledWith('test-user-id');
|
|
76
|
+
expect(connectorRegistry.getConnector).toHaveBeenCalledWith('testCRM');
|
|
77
|
+
expect(mockConnector.unAuthorize).toHaveBeenCalledWith({
|
|
78
|
+
user: mockUser
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('should return error when user not found', async () => {
|
|
83
|
+
// Arrange
|
|
84
|
+
jwt.decodeJwt.mockReturnValue({
|
|
85
|
+
id: 'non-existent-user',
|
|
86
|
+
platform: 'testCRM'
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
UserModel.findByPk.mockResolvedValue(null);
|
|
90
|
+
|
|
91
|
+
// Act
|
|
92
|
+
const result = await logout.execute({
|
|
93
|
+
jwtToken: 'mock-jwt-token'
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Assert
|
|
97
|
+
expect(result).toEqual({
|
|
98
|
+
success: false,
|
|
99
|
+
error: 'User not found',
|
|
100
|
+
errorDetails: 'User not found'
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('should treat CRM unAuthorize failure as non-fatal and still succeed', async () => {
|
|
105
|
+
// Arrange
|
|
106
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
107
|
+
const mockUser = {
|
|
108
|
+
id: 'test-user-id',
|
|
109
|
+
platform: 'testCRM'
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const mockConnector = {
|
|
113
|
+
unAuthorize: jest.fn().mockRejectedValue(
|
|
114
|
+
new Error('Logout API failed')
|
|
115
|
+
)
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
jwt.decodeJwt.mockReturnValue({
|
|
119
|
+
id: 'test-user-id',
|
|
120
|
+
platform: 'testCRM'
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
124
|
+
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
125
|
+
|
|
126
|
+
// Act
|
|
127
|
+
const result = await logout.execute({
|
|
128
|
+
jwtToken: 'mock-jwt-token'
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Assert — local session is cleared; CRM revoke errors are logged only
|
|
132
|
+
expect(result.success).toBe(true);
|
|
133
|
+
expect(result.data.message).toContain('IMPORTANT');
|
|
134
|
+
expect(mockConnector.unAuthorize).toHaveBeenCalled();
|
|
135
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
136
|
+
consoleSpy.mockRestore();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('should handle invalid JWT token', async () => {
|
|
140
|
+
// Arrange
|
|
141
|
+
jwt.decodeJwt.mockReturnValue(null);
|
|
142
|
+
|
|
143
|
+
// Act
|
|
144
|
+
const result = await logout.execute({
|
|
145
|
+
jwtToken: 'invalid-token'
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Assert
|
|
149
|
+
expect(result.success).toBe(false);
|
|
150
|
+
expect(result.error).toBeDefined();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('should succeed when platform connector is missing (unAuthorize skipped)', async () => {
|
|
154
|
+
// Arrange
|
|
155
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
156
|
+
const mockUser = {
|
|
157
|
+
id: 'test-user-id',
|
|
158
|
+
platform: 'unknownCRM'
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
jwt.decodeJwt.mockReturnValue({
|
|
162
|
+
id: 'test-user-id',
|
|
163
|
+
platform: 'unknownCRM'
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
167
|
+
connectorRegistry.getConnector.mockReturnValue(null);
|
|
168
|
+
|
|
169
|
+
// Act
|
|
170
|
+
const result = await logout.execute({
|
|
171
|
+
jwtToken: 'mock-jwt-token'
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Assert — null connector throws on unAuthorize; error is caught, logout still succeeds locally
|
|
175
|
+
expect(result.success).toBe(true);
|
|
176
|
+
expect(result.data.message).toContain('IMPORTANT');
|
|
177
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
178
|
+
consoleSpy.mockRestore();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('should clear both CRM user and RC extension session rows when rcExtensionId is provided', async () => {
|
|
182
|
+
const mockUser = {
|
|
183
|
+
id: 'test-user-id',
|
|
184
|
+
platform: 'testCRM'
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
jwt.decodeJwt.mockReturnValue({
|
|
188
|
+
id: 'test-user-id',
|
|
189
|
+
platform: 'testCRM'
|
|
190
|
+
});
|
|
191
|
+
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
192
|
+
connectorRegistry.getConnector.mockReturnValue({
|
|
193
|
+
unAuthorize: jest.fn().mockResolvedValue({})
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const result = await logout.execute({
|
|
197
|
+
jwtToken: 'mock-jwt-token',
|
|
198
|
+
rcExtensionId: 'rc-ext-123'
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(result.success).toBe(true);
|
|
202
|
+
expect(LlmSessionModel.destroy).toHaveBeenNthCalledWith(1, { where: { id: 'test-user-id' } });
|
|
203
|
+
expect(LlmSessionModel.destroy).toHaveBeenNthCalledWith(2, { where: { id: 'rc-ext-123' } });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('should clear the resolved rcExtension cache for the current OpenAI session on logout', async () => {
|
|
207
|
+
const mockUser = {
|
|
208
|
+
id: 'test-user-id',
|
|
209
|
+
platform: 'testCRM'
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
jwt.decodeJwt.mockReturnValue({
|
|
213
|
+
id: 'test-user-id',
|
|
214
|
+
platform: 'testCRM'
|
|
215
|
+
});
|
|
216
|
+
UserModel.findByPk.mockResolvedValue(mockUser);
|
|
217
|
+
connectorRegistry.getConnector.mockReturnValue({
|
|
218
|
+
unAuthorize: jest.fn().mockResolvedValue({})
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const result = await logout.execute({
|
|
222
|
+
jwtToken: 'mock-jwt-token',
|
|
223
|
+
openaiSessionId: 'oa-session-123'
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(result.success).toBe(true);
|
|
227
|
+
expect(CacheModel.destroy).toHaveBeenCalledWith({
|
|
228
|
+
where: { id: 'oa-session-123-rcExtensionId' }
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
const rcGetCallLogs = require('../../../mcp/tools/rcGetCallLogs');
|
|
2
|
-
const jwt = require('../../../lib/jwt');
|
|
3
|
-
const { RingCentral } = require('../../../lib/ringcentral');
|
|
4
|
-
|
|
5
|
-
jest.mock('../../../lib/jwt');
|
|
6
|
-
jest.mock('../../../lib/ringcentral', () => ({
|
|
7
|
-
RingCentral: jest.fn()
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
describe('MCP Tool: rcGetCallLogs', () => {
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
jest.clearAllMocks();
|
|
13
|
-
process.env.RINGCENTRAL_SERVER = 'https://platform.ringcentral.com';
|
|
14
|
-
process.env.RINGCENTRAL_CLIENT_ID = 'client-id';
|
|
15
|
-
process.env.RINGCENTRAL_CLIENT_SECRET = 'client-secret';
|
|
16
|
-
process.env.APP_SERVER = 'https://app.example.com';
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test('should have correct tool definition', () => {
|
|
20
|
-
expect(rcGetCallLogs.definition).toBeDefined();
|
|
21
|
-
expect(rcGetCallLogs.definition.name).toBe('rcGetCallLogs');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('should return call logs successfully', async () => {
|
|
25
|
-
jwt.decodeJwt.mockReturnValue({ id: 'user-123' });
|
|
26
|
-
const getCallLogData = jest.fn().mockResolvedValue({ records: [{ id: '1' }] });
|
|
27
|
-
RingCentral.mockImplementation(() => ({ getCallLogData }));
|
|
28
|
-
|
|
29
|
-
const result = await rcGetCallLogs.execute({
|
|
30
|
-
jwtToken: 'mock-jwt',
|
|
31
|
-
rcAccessToken: 'rc-token',
|
|
32
|
-
timeFrom: '2026-04-01T00:00:00.000Z',
|
|
33
|
-
timeTo: '2026-04-02T00:00:00.000Z'
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
expect(result).toEqual({ records: [{ id: '1' }] });
|
|
37
|
-
expect(getCallLogData).toHaveBeenCalledWith({
|
|
38
|
-
token: { access_token: 'rc-token', token_type: 'Bearer' },
|
|
39
|
-
timeFrom: '2026-04-01T00:00:00.000Z',
|
|
40
|
-
timeTo: '2026-04-02T00:00:00.000Z'
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('should return error when decodeJwt returns null', async () => {
|
|
45
|
-
jwt.decodeJwt.mockReturnValue(null);
|
|
46
|
-
|
|
47
|
-
const result = await rcGetCallLogs.execute({
|
|
48
|
-
jwtToken: 'bad-jwt',
|
|
49
|
-
rcAccessToken: 'rc-token'
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
expect(result.success).toBe(false);
|
|
53
|
-
expect(result.error).toContain('Invalid JWT token');
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
1
|
+
const rcGetCallLogs = require('../../../mcp/tools/rcGetCallLogs');
|
|
2
|
+
const jwt = require('../../../lib/jwt');
|
|
3
|
+
const { RingCentral } = require('../../../lib/ringcentral');
|
|
4
|
+
|
|
5
|
+
jest.mock('../../../lib/jwt');
|
|
6
|
+
jest.mock('../../../lib/ringcentral', () => ({
|
|
7
|
+
RingCentral: jest.fn()
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
describe('MCP Tool: rcGetCallLogs', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
process.env.RINGCENTRAL_SERVER = 'https://platform.ringcentral.com';
|
|
14
|
+
process.env.RINGCENTRAL_CLIENT_ID = 'client-id';
|
|
15
|
+
process.env.RINGCENTRAL_CLIENT_SECRET = 'client-secret';
|
|
16
|
+
process.env.APP_SERVER = 'https://app.example.com';
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('should have correct tool definition', () => {
|
|
20
|
+
expect(rcGetCallLogs.definition).toBeDefined();
|
|
21
|
+
expect(rcGetCallLogs.definition.name).toBe('rcGetCallLogs');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('should return call logs successfully', async () => {
|
|
25
|
+
jwt.decodeJwt.mockReturnValue({ id: 'user-123' });
|
|
26
|
+
const getCallLogData = jest.fn().mockResolvedValue({ records: [{ id: '1' }] });
|
|
27
|
+
RingCentral.mockImplementation(() => ({ getCallLogData }));
|
|
28
|
+
|
|
29
|
+
const result = await rcGetCallLogs.execute({
|
|
30
|
+
jwtToken: 'mock-jwt',
|
|
31
|
+
rcAccessToken: 'rc-token',
|
|
32
|
+
timeFrom: '2026-04-01T00:00:00.000Z',
|
|
33
|
+
timeTo: '2026-04-02T00:00:00.000Z'
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(result).toEqual({ records: [{ id: '1' }] });
|
|
37
|
+
expect(getCallLogData).toHaveBeenCalledWith({
|
|
38
|
+
token: { access_token: 'rc-token', token_type: 'Bearer' },
|
|
39
|
+
timeFrom: '2026-04-01T00:00:00.000Z',
|
|
40
|
+
timeTo: '2026-04-02T00:00:00.000Z'
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('should return error when decodeJwt returns null', async () => {
|
|
45
|
+
jwt.decodeJwt.mockReturnValue(null);
|
|
46
|
+
|
|
47
|
+
const result = await rcGetCallLogs.execute({
|
|
48
|
+
jwtToken: 'bad-jwt',
|
|
49
|
+
rcAccessToken: 'rc-token'
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(result.success).toBe(false);
|
|
53
|
+
expect(result.error).toContain('Invalid JWT token');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|