@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,122 @@
|
|
|
1
|
+
const jwt = require('../../lib/jwt');
|
|
2
|
+
const connectorRegistry = require('../../connector/registry');
|
|
3
|
+
const logCore = require('../../handlers/log');
|
|
4
|
+
const util = require('../../lib/util');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* MCP Tool: Update Call Log
|
|
8
|
+
*
|
|
9
|
+
* This tool updates an existing call log in the CRM platform.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const toolDefinition = {
|
|
13
|
+
name: 'updateCallLog',
|
|
14
|
+
description: '⚠️ REQUIRES AUTHENTICATION: User must first authenticate using the "auth" tool to obtain a JWT token before using this tool. | Update an existing call log in the CRM platform. Returns the updated log ID if successful.',
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
jwtToken: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'JWT token containing userId and platform information. If user does not have this, direct them to use the "auth" tool first.'
|
|
21
|
+
},
|
|
22
|
+
incomingData: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
description: 'Call log update data including sessionId, note, additionalSubmission, etc.',
|
|
25
|
+
properties: {
|
|
26
|
+
sessionId: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'Session ID of the call log to update'
|
|
29
|
+
},
|
|
30
|
+
note: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'Updated call note/description'
|
|
33
|
+
},
|
|
34
|
+
additionalSubmission: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
description: 'Additional platform-specific fields to update'
|
|
37
|
+
},
|
|
38
|
+
accountId: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
description: 'RingCentral account ID'
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
required: ['sessionId']
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
required: ['jwtToken', 'incomingData']
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Execute the updateCallLog tool
|
|
52
|
+
* @param {Object} args - The tool arguments
|
|
53
|
+
* @param {string} args.jwtToken - JWT token with user and platform info
|
|
54
|
+
* @param {Object} args.incomingData - Call log update data
|
|
55
|
+
* @returns {Object} Result object with updated log ID
|
|
56
|
+
*/
|
|
57
|
+
async function execute(args) {
|
|
58
|
+
try {
|
|
59
|
+
const { jwtToken, incomingData } = args;
|
|
60
|
+
|
|
61
|
+
// Decode JWT to get userId and platform
|
|
62
|
+
const { id: userId, platform } = jwt.decodeJwt(jwtToken);
|
|
63
|
+
|
|
64
|
+
if (!userId) {
|
|
65
|
+
throw new Error('Invalid JWT token: userId not found');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Get the platform connector module
|
|
69
|
+
const platformModule = connectorRegistry.getConnector(platform);
|
|
70
|
+
|
|
71
|
+
if (!platformModule) {
|
|
72
|
+
throw new Error(`Platform connector not found for: ${platform}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check if updateCallLog is implemented
|
|
76
|
+
if (!platformModule.updateCallLog) {
|
|
77
|
+
throw new Error(`updateCallLog is not implemented for platform: ${platform}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Calculate hashed account ID
|
|
81
|
+
const hashedAccountId = incomingData.accountId
|
|
82
|
+
? util.getHashValue(incomingData.accountId, process.env.HASH_KEY)
|
|
83
|
+
: undefined;
|
|
84
|
+
|
|
85
|
+
// Call the updateCallLog method
|
|
86
|
+
const { successful, logId, updatedNote, returnMessage } = await logCore.updateCallLog({
|
|
87
|
+
platform,
|
|
88
|
+
userId,
|
|
89
|
+
incomingData,
|
|
90
|
+
hashedAccountId,
|
|
91
|
+
isFromSSCL: false
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (successful) {
|
|
95
|
+
return {
|
|
96
|
+
success: true,
|
|
97
|
+
data: {
|
|
98
|
+
logId,
|
|
99
|
+
updatedNote,
|
|
100
|
+
message: returnMessage?.message || 'Call log updated successfully'
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
error: returnMessage?.message || 'Failed to update call log',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
error: error.message || 'Unknown error occurred',
|
|
115
|
+
errorDetails: error.stack
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
exports.definition = toolDefinition;
|
|
121
|
+
exports.execute = execute;
|
|
122
|
+
|
package/models/cacheModel.js
CHANGED
package/package.json
CHANGED
|
@@ -1,70 +1,71 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@app-connect/core",
|
|
3
|
-
"version": "1.7.
|
|
4
|
-
"description": "RingCentral App Connect Core",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/ringcentral/rc-unified-crm-extension.git"
|
|
9
|
-
},
|
|
10
|
-
"keywords": [
|
|
11
|
-
"RingCentral",
|
|
12
|
-
"App Connect"
|
|
13
|
-
],
|
|
14
|
-
"author": "RingCentral Labs",
|
|
15
|
-
"license": "MIT",
|
|
16
|
-
"peerDependencies": {
|
|
17
|
-
"axios": "^1.12.2",
|
|
18
|
-
"express": "^4.22.1",
|
|
19
|
-
"moment": "^2.29.4",
|
|
20
|
-
"moment-timezone": "^0.5.39",
|
|
21
|
-
"pg": "^8.8.0",
|
|
22
|
-
"sequelize": "^6.29.0"
|
|
23
|
-
},
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"@aws-sdk/client-dynamodb": "^3.751.0",
|
|
26
|
-
"@
|
|
27
|
-
"@aws-sdk/s3
|
|
28
|
-
"
|
|
29
|
-
"body-parser
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"test
|
|
44
|
-
"test:
|
|
45
|
-
"test:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"@
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"moment
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@app-connect/core",
|
|
3
|
+
"version": "1.7.12",
|
|
4
|
+
"description": "RingCentral App Connect Core",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/ringcentral/rc-unified-crm-extension.git"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"RingCentral",
|
|
12
|
+
"App Connect"
|
|
13
|
+
],
|
|
14
|
+
"author": "RingCentral Labs",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"axios": "^1.12.2",
|
|
18
|
+
"express": "^4.22.1",
|
|
19
|
+
"moment": "^2.29.4",
|
|
20
|
+
"moment-timezone": "^0.5.39",
|
|
21
|
+
"pg": "^8.8.0",
|
|
22
|
+
"sequelize": "^6.29.0"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@aws-sdk/client-dynamodb": "^3.751.0",
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.21.1",
|
|
27
|
+
"@aws-sdk/client-s3": "^3.947.0",
|
|
28
|
+
"@aws-sdk/s3-request-presigner": "^3.947.0",
|
|
29
|
+
"body-parser": "^1.20.4",
|
|
30
|
+
"body-parser-xml": "^2.0.5",
|
|
31
|
+
"client-oauth2": "^4.3.3",
|
|
32
|
+
"cors": "^2.8.5",
|
|
33
|
+
"country-state-city": "^3.2.1",
|
|
34
|
+
"dotenv": "^16.0.3",
|
|
35
|
+
"dynamoose": "^4.0.3",
|
|
36
|
+
"jsonwebtoken": "^9.0.0",
|
|
37
|
+
"mixpanel": "^0.18.0",
|
|
38
|
+
"shortid": "^2.2.17",
|
|
39
|
+
"tz-lookup": "^6.1.25",
|
|
40
|
+
"ua-parser-js": "^1.0.38"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"test": "jest",
|
|
44
|
+
"test:watch": "jest --watch",
|
|
45
|
+
"test:coverage": "jest --coverage",
|
|
46
|
+
"test:ci": "jest --ci --coverage --watchAll=false"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@eslint/js": "^9.22.0",
|
|
50
|
+
"@octokit/rest": "^19.0.5",
|
|
51
|
+
"axios": "^1.12.2",
|
|
52
|
+
"eslint": "^9.22.0",
|
|
53
|
+
"express": "^4.22.1",
|
|
54
|
+
"globals": "^16.0.0",
|
|
55
|
+
"jest": "^29.3.1",
|
|
56
|
+
"moment": "^2.29.4",
|
|
57
|
+
"moment-timezone": "^0.5.39",
|
|
58
|
+
"nock": "^13.2.9",
|
|
59
|
+
"pg": "^8.8.0",
|
|
60
|
+
"sequelize": "^6.29.0",
|
|
61
|
+
"sqlite3": "^5.1.2",
|
|
62
|
+
"supertest": "^6.3.1"
|
|
63
|
+
},
|
|
64
|
+
"overrides": {
|
|
65
|
+
"js-object-utilities": "2.2.1"
|
|
66
|
+
},
|
|
67
|
+
"bugs": {
|
|
68
|
+
"url": "https://github.com/ringcentral/rc-unified-crm-extension/issues"
|
|
69
|
+
},
|
|
70
|
+
"homepage": "https://github.com/ringcentral/rc-unified-crm-extension#readme"
|
|
71
|
+
}
|
package/releaseNotes.json
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
{
|
|
2
|
+
"1.7.12": {
|
|
3
|
+
"global": [
|
|
4
|
+
{
|
|
5
|
+
"type": "New",
|
|
6
|
+
"description": "A button on Developer settings page to re-initialize user mapping"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"type": "Better",
|
|
10
|
+
"description": "Click-to-SMS button is disabled if RingCentral SMS service is not activated"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"1.7.11": {
|
|
15
|
+
"global": [
|
|
16
|
+
{
|
|
17
|
+
"type": "New",
|
|
18
|
+
"description": "Shared-SMS logging"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"type": "New",
|
|
22
|
+
"description": "MCP tools"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
},
|
|
2
26
|
"1.7.10": {
|
|
3
27
|
"global": [
|
|
4
28
|
{
|
|
@@ -691,10 +691,12 @@ describe('Log Handler', () => {
|
|
|
691
691
|
platformAdditionalInfo: {}
|
|
692
692
|
});
|
|
693
693
|
|
|
694
|
+
// Create existing message log with same conversationLogId
|
|
694
695
|
await MessageLogModel.create({
|
|
695
696
|
id: 'msg-1',
|
|
696
697
|
platform: 'testCRM',
|
|
697
698
|
conversationId: 'conv-123',
|
|
699
|
+
conversationLogId: 'conv-log-123',
|
|
698
700
|
thirdPartyLogId: 'existing-log',
|
|
699
701
|
userId: 'test-user-id'
|
|
700
702
|
});
|
|
@@ -705,6 +707,9 @@ describe('Log Handler', () => {
|
|
|
705
707
|
createMessageLog: jest.fn().mockResolvedValue({
|
|
706
708
|
logId: 'msg-log-new',
|
|
707
709
|
returnMessage: { message: 'Message logged', messageType: 'success', ttl: 2000 }
|
|
710
|
+
}),
|
|
711
|
+
updateMessageLog: jest.fn().mockResolvedValue({
|
|
712
|
+
returnMessage: { message: 'Message updated', messageType: 'success', ttl: 2000 }
|
|
708
713
|
})
|
|
709
714
|
};
|
|
710
715
|
connectorRegistry.getConnector.mockReturnValue(mockConnector);
|
|
@@ -717,7 +722,7 @@ describe('Log Handler', () => {
|
|
|
717
722
|
],
|
|
718
723
|
correspondents: [{ phoneNumber: '+1234567890' }],
|
|
719
724
|
conversationId: 'conv-123',
|
|
720
|
-
conversationLogId: '
|
|
725
|
+
conversationLogId: 'conv-log-123' // Same conversationLogId as existing record
|
|
721
726
|
},
|
|
722
727
|
contactId: 'contact-123',
|
|
723
728
|
contactType: 'Contact',
|
|
@@ -733,8 +738,9 @@ describe('Log Handler', () => {
|
|
|
733
738
|
|
|
734
739
|
// Assert
|
|
735
740
|
expect(result.successful).toBe(true);
|
|
736
|
-
//
|
|
737
|
-
expect(mockConnector.createMessageLog).toHaveBeenCalledTimes(
|
|
741
|
+
// msg-1 is skipped (already logged), msg-2 uses updateMessageLog because same conversationLogId exists
|
|
742
|
+
expect(mockConnector.createMessageLog).toHaveBeenCalledTimes(0);
|
|
743
|
+
expect(mockConnector.updateMessageLog).toHaveBeenCalledTimes(1);
|
|
738
744
|
});
|
|
739
745
|
});
|
|
740
746
|
|
|
@@ -767,7 +773,8 @@ describe('Log Handler', () => {
|
|
|
767
773
|
|
|
768
774
|
// Assert
|
|
769
775
|
expect(result.successful).toBe(false);
|
|
770
|
-
expect(result.returnMessage).toBe('Error
|
|
776
|
+
expect(result.returnMessage.message).toBe('Error performing saveNoteCache');
|
|
777
|
+
expect(result.returnMessage.messageType).toBe('warning');
|
|
771
778
|
});
|
|
772
779
|
});
|
|
773
780
|
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
const { Logger, LOG_LEVELS } = require('../../lib/logger');
|
|
2
|
+
|
|
3
|
+
describe('Logger', () => {
|
|
4
|
+
let originalEnv;
|
|
5
|
+
let consoleSpy;
|
|
6
|
+
let consoleErrorSpy;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
originalEnv = process.env.NODE_ENV;
|
|
10
|
+
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
11
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
process.env.NODE_ENV = originalEnv;
|
|
16
|
+
consoleSpy.mockRestore();
|
|
17
|
+
consoleErrorSpy.mockRestore();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('Log Levels', () => {
|
|
21
|
+
test('should only log messages at or above the configured level', () => {
|
|
22
|
+
const logger = new Logger({ level: 'WARN' });
|
|
23
|
+
|
|
24
|
+
logger.debug('debug message');
|
|
25
|
+
logger.info('info message');
|
|
26
|
+
logger.warn('warn message');
|
|
27
|
+
logger.error('error message');
|
|
28
|
+
|
|
29
|
+
// Only warn and error should be logged
|
|
30
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
31
|
+
expect(consoleErrorSpy).toHaveBeenCalledTimes(2);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('should log all messages when level is DEBUG', () => {
|
|
35
|
+
const logger = new Logger({ level: 'DEBUG' });
|
|
36
|
+
|
|
37
|
+
logger.debug('debug message');
|
|
38
|
+
logger.info('info message');
|
|
39
|
+
logger.warn('warn message');
|
|
40
|
+
logger.error('error message');
|
|
41
|
+
|
|
42
|
+
expect(consoleSpy).toHaveBeenCalledTimes(2); // debug and info
|
|
43
|
+
expect(consoleErrorSpy).toHaveBeenCalledTimes(2); // warn and error
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('Production vs Development', () => {
|
|
48
|
+
test('should output JSON in production', () => {
|
|
49
|
+
process.env.NODE_ENV = 'production';
|
|
50
|
+
const logger = new Logger({ level: 'INFO' });
|
|
51
|
+
|
|
52
|
+
logger.info('test message', { userId: '123' });
|
|
53
|
+
|
|
54
|
+
const logOutput = consoleSpy.mock.calls[0][0];
|
|
55
|
+
const parsed = JSON.parse(logOutput);
|
|
56
|
+
|
|
57
|
+
expect(parsed).toHaveProperty('timestamp');
|
|
58
|
+
expect(parsed).toHaveProperty('level', 'INFO');
|
|
59
|
+
expect(parsed).toHaveProperty('message', 'test message');
|
|
60
|
+
expect(parsed).toHaveProperty('userId', '123');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('should output human-readable format in development', () => {
|
|
64
|
+
process.env.NODE_ENV = 'development';
|
|
65
|
+
const logger = new Logger({ level: 'INFO' });
|
|
66
|
+
|
|
67
|
+
logger.info('test message', { userId: '123' });
|
|
68
|
+
|
|
69
|
+
const logOutput = consoleSpy.mock.calls[0][0];
|
|
70
|
+
expect(logOutput).toContain('[INFO]');
|
|
71
|
+
expect(logOutput).toContain('test message');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('Error Handling', () => {
|
|
76
|
+
test('should extract error information from Error objects', () => {
|
|
77
|
+
process.env.NODE_ENV = 'production';
|
|
78
|
+
const logger = new Logger({ level: 'ERROR' });
|
|
79
|
+
const error = new Error('Test error');
|
|
80
|
+
error.response = {
|
|
81
|
+
status: 500,
|
|
82
|
+
data: { message: 'Server error' },
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
logger.error('Operation failed', { error });
|
|
86
|
+
|
|
87
|
+
const logOutput = consoleErrorSpy.mock.calls[0][0];
|
|
88
|
+
const parsed = JSON.parse(logOutput);
|
|
89
|
+
|
|
90
|
+
expect(parsed).toHaveProperty('errorMessage', 'Test error');
|
|
91
|
+
expect(parsed).toHaveProperty('errorStack');
|
|
92
|
+
expect(parsed).toHaveProperty('errorStatus', 500);
|
|
93
|
+
expect(parsed).toHaveProperty('errorResponse');
|
|
94
|
+
expect(parsed).not.toHaveProperty('error'); // Should be removed
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('Child Logger', () => {
|
|
99
|
+
test('should create child logger with default context', () => {
|
|
100
|
+
process.env.NODE_ENV = 'production';
|
|
101
|
+
const logger = new Logger({ level: 'INFO' });
|
|
102
|
+
const childLogger = logger.child({ platform: 'clio', userId: '123' });
|
|
103
|
+
|
|
104
|
+
childLogger.info('test message', { operation: 'createLog' });
|
|
105
|
+
|
|
106
|
+
const logOutput = consoleSpy.mock.calls[0][0];
|
|
107
|
+
const parsed = JSON.parse(logOutput);
|
|
108
|
+
|
|
109
|
+
expect(parsed).toHaveProperty('platform', 'clio');
|
|
110
|
+
expect(parsed).toHaveProperty('userId', '123');
|
|
111
|
+
expect(parsed).toHaveProperty('operation', 'createLog');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('should support nested child loggers', () => {
|
|
115
|
+
process.env.NODE_ENV = 'production';
|
|
116
|
+
const logger = new Logger({ level: 'INFO' });
|
|
117
|
+
const child1 = logger.child({ platform: 'clio' });
|
|
118
|
+
const child2 = child1.child({ userId: '123' });
|
|
119
|
+
|
|
120
|
+
child2.info('test message');
|
|
121
|
+
|
|
122
|
+
const logOutput = consoleSpy.mock.calls[0][0];
|
|
123
|
+
const parsed = JSON.parse(logOutput);
|
|
124
|
+
|
|
125
|
+
expect(parsed).toHaveProperty('platform', 'clio');
|
|
126
|
+
expect(parsed).toHaveProperty('userId', '123');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('API Request Logging', () => {
|
|
131
|
+
test('should log successful API requests at DEBUG level', () => {
|
|
132
|
+
const logger = new Logger({ level: 'DEBUG' });
|
|
133
|
+
|
|
134
|
+
logger.logApiRequest({
|
|
135
|
+
method: 'GET',
|
|
136
|
+
url: 'https://api.example.com/users',
|
|
137
|
+
status: 200,
|
|
138
|
+
duration: 150,
|
|
139
|
+
platform: 'clio',
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(consoleSpy).toHaveBeenCalledTimes(1);
|
|
143
|
+
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('should log failed API requests at ERROR level', () => {
|
|
147
|
+
const logger = new Logger({ level: 'ERROR' });
|
|
148
|
+
const error = new Error('Request failed');
|
|
149
|
+
|
|
150
|
+
logger.logApiRequest({
|
|
151
|
+
method: 'POST',
|
|
152
|
+
url: 'https://api.example.com/logs',
|
|
153
|
+
status: 500,
|
|
154
|
+
duration: 300,
|
|
155
|
+
platform: 'clio',
|
|
156
|
+
error,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
|
160
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('should log 4xx errors at WARN level', () => {
|
|
164
|
+
const logger = new Logger({ level: 'WARN' });
|
|
165
|
+
|
|
166
|
+
logger.logApiRequest({
|
|
167
|
+
method: 'GET',
|
|
168
|
+
url: 'https://api.example.com/contact/999',
|
|
169
|
+
status: 404,
|
|
170
|
+
duration: 100,
|
|
171
|
+
platform: 'clio',
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('Database Query Logging', () => {
|
|
179
|
+
test('should log successful queries at DEBUG level', () => {
|
|
180
|
+
const logger = new Logger({ level: 'DEBUG' });
|
|
181
|
+
|
|
182
|
+
logger.logDatabaseQuery({
|
|
183
|
+
operation: 'SELECT',
|
|
184
|
+
table: 'users',
|
|
185
|
+
duration: 50,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(consoleSpy).toHaveBeenCalledTimes(1);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('should log failed queries at ERROR level', () => {
|
|
192
|
+
const logger = new Logger({ level: 'ERROR' });
|
|
193
|
+
const error = new Error('Constraint violation');
|
|
194
|
+
|
|
195
|
+
logger.logDatabaseQuery({
|
|
196
|
+
operation: 'INSERT',
|
|
197
|
+
table: 'call_logs',
|
|
198
|
+
duration: 20,
|
|
199
|
+
error,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
@@ -126,9 +126,6 @@ describe('ringcentral', () => {
|
|
|
126
126
|
expect(result.refresh_token).toBe('new-refresh-token');
|
|
127
127
|
expect(result.expire_time).toBeDefined();
|
|
128
128
|
expect(result.refresh_token_expire_time).toBeDefined();
|
|
129
|
-
// These should not be included
|
|
130
|
-
expect(result.scope).toBeUndefined();
|
|
131
|
-
expect(result.endpoint_id).toBeUndefined();
|
|
132
129
|
});
|
|
133
130
|
|
|
134
131
|
test('should throw error on failed token generation', async () => {
|
|
@@ -317,9 +314,6 @@ describe('ringcentral', () => {
|
|
|
317
314
|
}, token);
|
|
318
315
|
|
|
319
316
|
expect(result.id).toBe('sub-123');
|
|
320
|
-
// These should not be included in result
|
|
321
|
-
expect(result.uri).toBeUndefined();
|
|
322
|
-
expect(result.creationTime).toBeUndefined();
|
|
323
317
|
});
|
|
324
318
|
});
|
|
325
319
|
|