@app-connect/core 1.7.25 → 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 -31
- package/connector/mock.js +84 -77
- package/connector/proxy/engine.js +164 -164
- 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 -116
- 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 +1 -1
- package/releaseNotes.json +1093 -1081
- package/test/connector/proxy/engine.test.js +126 -126
- 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 -1018
- package/test/handlers/contact.test.js +1014 -1014
- package/test/handlers/log.test.js +1298 -1160
- package/test/handlers/managedAuth.test.js +457 -457
- 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 -83
- 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 -104
- package/test/setup.js +178 -178
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
const getPublicConnectors = require('../../../mcp/tools/getPublicConnectors');
|
|
2
|
-
const axios = require('axios');
|
|
3
|
-
|
|
4
|
-
jest.mock('axios');
|
|
5
|
-
|
|
6
|
-
describe('MCP Tool: getPublicConnectors', () => {
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
jest.clearAllMocks();
|
|
9
|
-
process.env.APP_SERVER = 'https://test-server.com';
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
describe('tool definition', () => {
|
|
13
|
-
test('should have correct tool definition', () => {
|
|
14
|
-
expect(getPublicConnectors.definition).toBeDefined();
|
|
15
|
-
expect(getPublicConnectors.definition.name).toBe('getPublicConnectors');
|
|
16
|
-
expect(getPublicConnectors.definition.description).toContain('connectors');
|
|
17
|
-
expect(getPublicConnectors.definition.inputSchema).toBeDefined();
|
|
18
|
-
expect(getPublicConnectors.definition.inputSchema.type).toBe('object');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test('should have no required parameters', () => {
|
|
22
|
-
expect(getPublicConnectors.definition.inputSchema.required).toEqual([]);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('execute', () => {
|
|
27
|
-
test('should return structuredContent with server URL when no rcAccessToken', async () => {
|
|
28
|
-
// Act
|
|
29
|
-
const result = await getPublicConnectors.execute({});
|
|
30
|
-
|
|
31
|
-
// Assert
|
|
32
|
-
expect(result).toEqual({
|
|
33
|
-
structuredContent: {
|
|
34
|
-
serverUrl: 'https://test-server.com',
|
|
35
|
-
rcExtensionId: null,
|
|
36
|
-
rcAccountId: null,
|
|
37
|
-
openaiSessionId: null,
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
expect(axios.get).not.toHaveBeenCalled();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('should resolve RC account and extension IDs when rcAccessToken provided', async () => {
|
|
44
|
-
// Arrange
|
|
45
|
-
axios.get.mockResolvedValue({
|
|
46
|
-
data: { id: 'ext-456', account: { id: 'acc-789' } }
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Act
|
|
50
|
-
const result = await getPublicConnectors.execute({
|
|
51
|
-
rcAccessToken: 'valid-rc-token',
|
|
52
|
-
openaiSessionId: 'session-abc'
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Assert
|
|
56
|
-
expect(result).toEqual({
|
|
57
|
-
structuredContent: {
|
|
58
|
-
serverUrl: 'https://test-server.com',
|
|
59
|
-
rcExtensionId: 'ext-456',
|
|
60
|
-
rcAccountId: 'acc-789',
|
|
61
|
-
openaiSessionId: 'session-abc',
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
expect(axios.get).toHaveBeenCalledWith(
|
|
65
|
-
'https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~',
|
|
66
|
-
{ headers: { Authorization: 'Bearer valid-rc-token' } }
|
|
67
|
-
);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('should return null RC IDs and continue when RC API call fails', async () => {
|
|
71
|
-
// Arrange — RC API failure is non-fatal: widget only shows public connectors
|
|
72
|
-
axios.get.mockRejectedValue(new Error('RC API unavailable'));
|
|
73
|
-
|
|
74
|
-
// Act
|
|
75
|
-
const result = await getPublicConnectors.execute({ rcAccessToken: 'bad-token' });
|
|
76
|
-
|
|
77
|
-
// Assert — still returns structuredContent, just without RC IDs
|
|
78
|
-
expect(result).toEqual({
|
|
79
|
-
structuredContent: {
|
|
80
|
-
serverUrl: 'https://test-server.com',
|
|
81
|
-
rcExtensionId: null,
|
|
82
|
-
rcAccountId: null,
|
|
83
|
-
openaiSessionId: null,
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('should include openaiSessionId when provided', async () => {
|
|
89
|
-
// Act
|
|
90
|
-
const result = await getPublicConnectors.execute({ openaiSessionId: 'my-session' });
|
|
91
|
-
|
|
92
|
-
// Assert
|
|
93
|
-
expect(result.structuredContent.openaiSessionId).toBe('my-session');
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('should use default server URL when APP_SERVER is not set', async () => {
|
|
97
|
-
// Arrange
|
|
98
|
-
delete process.env.APP_SERVER;
|
|
99
|
-
|
|
100
|
-
// Act
|
|
101
|
-
const result = await getPublicConnectors.execute({});
|
|
102
|
-
|
|
103
|
-
// Assert
|
|
104
|
-
expect(result.structuredContent.serverUrl).toBe('https://localhost:6066');
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
});
|
|
1
|
+
const getPublicConnectors = require('../../../mcp/tools/getPublicConnectors');
|
|
2
|
+
const axios = require('axios');
|
|
3
|
+
|
|
4
|
+
jest.mock('axios');
|
|
5
|
+
|
|
6
|
+
describe('MCP Tool: getPublicConnectors', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.clearAllMocks();
|
|
9
|
+
process.env.APP_SERVER = 'https://test-server.com';
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('tool definition', () => {
|
|
13
|
+
test('should have correct tool definition', () => {
|
|
14
|
+
expect(getPublicConnectors.definition).toBeDefined();
|
|
15
|
+
expect(getPublicConnectors.definition.name).toBe('getPublicConnectors');
|
|
16
|
+
expect(getPublicConnectors.definition.description).toContain('connectors');
|
|
17
|
+
expect(getPublicConnectors.definition.inputSchema).toBeDefined();
|
|
18
|
+
expect(getPublicConnectors.definition.inputSchema.type).toBe('object');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('should have no required parameters', () => {
|
|
22
|
+
expect(getPublicConnectors.definition.inputSchema.required).toEqual([]);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('execute', () => {
|
|
27
|
+
test('should return structuredContent with server URL when no rcAccessToken', async () => {
|
|
28
|
+
// Act
|
|
29
|
+
const result = await getPublicConnectors.execute({});
|
|
30
|
+
|
|
31
|
+
// Assert
|
|
32
|
+
expect(result).toEqual({
|
|
33
|
+
structuredContent: {
|
|
34
|
+
serverUrl: 'https://test-server.com',
|
|
35
|
+
rcExtensionId: null,
|
|
36
|
+
rcAccountId: null,
|
|
37
|
+
openaiSessionId: null,
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
expect(axios.get).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('should resolve RC account and extension IDs when rcAccessToken provided', async () => {
|
|
44
|
+
// Arrange
|
|
45
|
+
axios.get.mockResolvedValue({
|
|
46
|
+
data: { id: 'ext-456', account: { id: 'acc-789' } }
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Act
|
|
50
|
+
const result = await getPublicConnectors.execute({
|
|
51
|
+
rcAccessToken: 'valid-rc-token',
|
|
52
|
+
openaiSessionId: 'session-abc'
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Assert
|
|
56
|
+
expect(result).toEqual({
|
|
57
|
+
structuredContent: {
|
|
58
|
+
serverUrl: 'https://test-server.com',
|
|
59
|
+
rcExtensionId: 'ext-456',
|
|
60
|
+
rcAccountId: 'acc-789',
|
|
61
|
+
openaiSessionId: 'session-abc',
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
expect(axios.get).toHaveBeenCalledWith(
|
|
65
|
+
'https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~',
|
|
66
|
+
{ headers: { Authorization: 'Bearer valid-rc-token' } }
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('should return null RC IDs and continue when RC API call fails', async () => {
|
|
71
|
+
// Arrange — RC API failure is non-fatal: widget only shows public connectors
|
|
72
|
+
axios.get.mockRejectedValue(new Error('RC API unavailable'));
|
|
73
|
+
|
|
74
|
+
// Act
|
|
75
|
+
const result = await getPublicConnectors.execute({ rcAccessToken: 'bad-token' });
|
|
76
|
+
|
|
77
|
+
// Assert — still returns structuredContent, just without RC IDs
|
|
78
|
+
expect(result).toEqual({
|
|
79
|
+
structuredContent: {
|
|
80
|
+
serverUrl: 'https://test-server.com',
|
|
81
|
+
rcExtensionId: null,
|
|
82
|
+
rcAccountId: null,
|
|
83
|
+
openaiSessionId: null,
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('should include openaiSessionId when provided', async () => {
|
|
89
|
+
// Act
|
|
90
|
+
const result = await getPublicConnectors.execute({ openaiSessionId: 'my-session' });
|
|
91
|
+
|
|
92
|
+
// Assert
|
|
93
|
+
expect(result.structuredContent.openaiSessionId).toBe('my-session');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('should use default server URL when APP_SERVER is not set', async () => {
|
|
97
|
+
// Arrange
|
|
98
|
+
delete process.env.APP_SERVER;
|
|
99
|
+
|
|
100
|
+
// Act
|
|
101
|
+
const result = await getPublicConnectors.execute({});
|
|
102
|
+
|
|
103
|
+
// Assert
|
|
104
|
+
expect(result.structuredContent.serverUrl).toBe('https://localhost:6066');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -1,127 +1,127 @@
|
|
|
1
|
-
const getSessionInfo = require('../../../mcp/tools/getSessionInfo');
|
|
2
|
-
const jwt = require('../../../lib/jwt');
|
|
3
|
-
const { UserModel } = require('../../../models/userModel');
|
|
4
|
-
const { RingCentral } = require('../../../lib/ringcentral');
|
|
5
|
-
|
|
6
|
-
jest.mock('../../../lib/jwt');
|
|
7
|
-
jest.mock('../../../models/userModel');
|
|
8
|
-
jest.mock('../../../lib/ringcentral');
|
|
9
|
-
|
|
10
|
-
describe('MCP Tool: getSessionInfo', () => {
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
jest.clearAllMocks();
|
|
13
|
-
RingCentral.mockImplementation(() => ({
|
|
14
|
-
getExtensionInfo: jest.fn().mockResolvedValue({ name: 'Demo Extension' })
|
|
15
|
-
}));
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe('tool definition', () => {
|
|
19
|
-
test('should have correct tool definition', () => {
|
|
20
|
-
expect(getSessionInfo.definition).toBeDefined();
|
|
21
|
-
expect(getSessionInfo.definition.name).toBe('getSessionInfo');
|
|
22
|
-
expect(getSessionInfo.definition.description).toContain('session info');
|
|
23
|
-
expect(getSessionInfo.definition.inputSchema).toBeDefined();
|
|
24
|
-
expect(getSessionInfo.definition.inputSchema.properties).toEqual({});
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe('execute', () => {
|
|
29
|
-
test('should return unauthenticated session info when no CRM jwtToken exists', async () => {
|
|
30
|
-
const result = await getSessionInfo.execute({
|
|
31
|
-
openaiSessionId: 'session-123',
|
|
32
|
-
rcExtensionId: 'ext-456',
|
|
33
|
-
rcAccessToken: 'rc-token'
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
expect(result).toEqual({
|
|
37
|
-
success: true,
|
|
38
|
-
data: {
|
|
39
|
-
openaiSessionId: 'session-123',
|
|
40
|
-
dataToShow: {
|
|
41
|
-
isCrmAuthenticated: false,
|
|
42
|
-
ringcentral: {
|
|
43
|
-
extensionId: 'ext-456',
|
|
44
|
-
name: 'Demo Extension',
|
|
45
|
-
},
|
|
46
|
-
crm: {
|
|
47
|
-
userId: null,
|
|
48
|
-
platform: null,
|
|
49
|
-
hostname: null
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
expect(jwt.decodeJwt).not.toHaveBeenCalled();
|
|
55
|
-
expect(UserModel.findByPk).not.toHaveBeenCalled();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('should return connected CRM session info when jwtToken resolves to a saved user', async () => {
|
|
59
|
-
jwt.decodeJwt.mockReturnValue({
|
|
60
|
-
id: 'crm-user-1',
|
|
61
|
-
platform: 'clio'
|
|
62
|
-
});
|
|
63
|
-
UserModel.findByPk.mockResolvedValue({
|
|
64
|
-
id: 'crm-user-1',
|
|
65
|
-
platform: 'clio',
|
|
66
|
-
hostname: 'app.clio.com',
|
|
67
|
-
accessToken: 'crm-access-token',
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const result = await getSessionInfo.execute({
|
|
71
|
-
openaiSessionId: 'session-123',
|
|
72
|
-
rcExtensionId: 'ext-456',
|
|
73
|
-
rcAccessToken: 'rc-token',
|
|
74
|
-
jwtToken: 'jwt-token'
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
expect(jwt.decodeJwt).toHaveBeenCalledWith('jwt-token');
|
|
78
|
-
expect(UserModel.findByPk).toHaveBeenCalledWith('crm-user-1');
|
|
79
|
-
expect(result).toEqual({
|
|
80
|
-
success: true,
|
|
81
|
-
data: {
|
|
82
|
-
openaiSessionId: 'session-123',
|
|
83
|
-
dataToShow: {
|
|
84
|
-
isCrmAuthenticated: true,
|
|
85
|
-
ringcentral: {
|
|
86
|
-
extensionId: 'ext-456',
|
|
87
|
-
name: 'Demo Extension',
|
|
88
|
-
},
|
|
89
|
-
crm: {
|
|
90
|
-
userId: 'crm-user-1',
|
|
91
|
-
platform: 'clio',
|
|
92
|
-
hostname: 'app.clio.com'
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('should report not authenticated when jwtToken is invalid', async () => {
|
|
100
|
-
jwt.decodeJwt.mockReturnValue(null);
|
|
101
|
-
|
|
102
|
-
const result = await getSessionInfo.execute({
|
|
103
|
-
jwtToken: 'bad-token'
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
expect(result).toEqual({
|
|
107
|
-
success: true,
|
|
108
|
-
data: {
|
|
109
|
-
openaiSessionId: null,
|
|
110
|
-
dataToShow: {
|
|
111
|
-
isCrmAuthenticated: false,
|
|
112
|
-
ringcentral: {
|
|
113
|
-
extensionId: null,
|
|
114
|
-
name: null,
|
|
115
|
-
},
|
|
116
|
-
crm: {
|
|
117
|
-
userId: null,
|
|
118
|
-
platform: null,
|
|
119
|
-
hostname: null
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
expect(UserModel.findByPk).not.toHaveBeenCalled();
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
});
|
|
1
|
+
const getSessionInfo = require('../../../mcp/tools/getSessionInfo');
|
|
2
|
+
const jwt = require('../../../lib/jwt');
|
|
3
|
+
const { UserModel } = require('../../../models/userModel');
|
|
4
|
+
const { RingCentral } = require('../../../lib/ringcentral');
|
|
5
|
+
|
|
6
|
+
jest.mock('../../../lib/jwt');
|
|
7
|
+
jest.mock('../../../models/userModel');
|
|
8
|
+
jest.mock('../../../lib/ringcentral');
|
|
9
|
+
|
|
10
|
+
describe('MCP Tool: getSessionInfo', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
RingCentral.mockImplementation(() => ({
|
|
14
|
+
getExtensionInfo: jest.fn().mockResolvedValue({ name: 'Demo Extension' })
|
|
15
|
+
}));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('tool definition', () => {
|
|
19
|
+
test('should have correct tool definition', () => {
|
|
20
|
+
expect(getSessionInfo.definition).toBeDefined();
|
|
21
|
+
expect(getSessionInfo.definition.name).toBe('getSessionInfo');
|
|
22
|
+
expect(getSessionInfo.definition.description).toContain('session info');
|
|
23
|
+
expect(getSessionInfo.definition.inputSchema).toBeDefined();
|
|
24
|
+
expect(getSessionInfo.definition.inputSchema.properties).toEqual({});
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('execute', () => {
|
|
29
|
+
test('should return unauthenticated session info when no CRM jwtToken exists', async () => {
|
|
30
|
+
const result = await getSessionInfo.execute({
|
|
31
|
+
openaiSessionId: 'session-123',
|
|
32
|
+
rcExtensionId: 'ext-456',
|
|
33
|
+
rcAccessToken: 'rc-token'
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(result).toEqual({
|
|
37
|
+
success: true,
|
|
38
|
+
data: {
|
|
39
|
+
openaiSessionId: 'session-123',
|
|
40
|
+
dataToShow: {
|
|
41
|
+
isCrmAuthenticated: false,
|
|
42
|
+
ringcentral: {
|
|
43
|
+
extensionId: 'ext-456',
|
|
44
|
+
name: 'Demo Extension',
|
|
45
|
+
},
|
|
46
|
+
crm: {
|
|
47
|
+
userId: null,
|
|
48
|
+
platform: null,
|
|
49
|
+
hostname: null
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
expect(jwt.decodeJwt).not.toHaveBeenCalled();
|
|
55
|
+
expect(UserModel.findByPk).not.toHaveBeenCalled();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('should return connected CRM session info when jwtToken resolves to a saved user', async () => {
|
|
59
|
+
jwt.decodeJwt.mockReturnValue({
|
|
60
|
+
id: 'crm-user-1',
|
|
61
|
+
platform: 'clio'
|
|
62
|
+
});
|
|
63
|
+
UserModel.findByPk.mockResolvedValue({
|
|
64
|
+
id: 'crm-user-1',
|
|
65
|
+
platform: 'clio',
|
|
66
|
+
hostname: 'app.clio.com',
|
|
67
|
+
accessToken: 'crm-access-token',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const result = await getSessionInfo.execute({
|
|
71
|
+
openaiSessionId: 'session-123',
|
|
72
|
+
rcExtensionId: 'ext-456',
|
|
73
|
+
rcAccessToken: 'rc-token',
|
|
74
|
+
jwtToken: 'jwt-token'
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(jwt.decodeJwt).toHaveBeenCalledWith('jwt-token');
|
|
78
|
+
expect(UserModel.findByPk).toHaveBeenCalledWith('crm-user-1');
|
|
79
|
+
expect(result).toEqual({
|
|
80
|
+
success: true,
|
|
81
|
+
data: {
|
|
82
|
+
openaiSessionId: 'session-123',
|
|
83
|
+
dataToShow: {
|
|
84
|
+
isCrmAuthenticated: true,
|
|
85
|
+
ringcentral: {
|
|
86
|
+
extensionId: 'ext-456',
|
|
87
|
+
name: 'Demo Extension',
|
|
88
|
+
},
|
|
89
|
+
crm: {
|
|
90
|
+
userId: 'crm-user-1',
|
|
91
|
+
platform: 'clio',
|
|
92
|
+
hostname: 'app.clio.com'
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('should report not authenticated when jwtToken is invalid', async () => {
|
|
100
|
+
jwt.decodeJwt.mockReturnValue(null);
|
|
101
|
+
|
|
102
|
+
const result = await getSessionInfo.execute({
|
|
103
|
+
jwtToken: 'bad-token'
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(result).toEqual({
|
|
107
|
+
success: true,
|
|
108
|
+
data: {
|
|
109
|
+
openaiSessionId: null,
|
|
110
|
+
dataToShow: {
|
|
111
|
+
isCrmAuthenticated: false,
|
|
112
|
+
ringcentral: {
|
|
113
|
+
extensionId: null,
|
|
114
|
+
name: null,
|
|
115
|
+
},
|
|
116
|
+
crm: {
|
|
117
|
+
userId: null,
|
|
118
|
+
platform: null,
|
|
119
|
+
hostname: null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
expect(UserModel.findByPk).not.toHaveBeenCalled();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|