@app-connect/core 1.7.35 → 1.7.37
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/mock.js +9 -4
- package/connector/proxy/engine.js +3 -2
- package/connector/proxy/index.js +2 -2
- package/docs/architecture.md +1 -0
- package/docs/handlers.md +1 -1
- package/docs/models.md +2 -0
- package/docs/routes.md +1 -1
- package/handlers/disposition.js +3 -2
- package/handlers/log.js +16 -6
- package/handlers/plugin.js +13 -6
- package/index.js +19 -4
- package/lib/callLogLookup.js +82 -9
- package/lib/migrateCallLogsSchema.js +315 -7
- package/models/callLogModel.js +6 -0
- package/package.json +1 -1
- package/releaseNotes.json +20 -0
- package/test/connector/developerPortal.test.js +166 -0
- package/test/connector/mock.test.js +131 -0
- package/test/connector/proxy/engine.test.js +85 -0
- package/test/connector/proxy/index.test.js +246 -0
- package/test/connector/proxy/sample.json +1 -0
- package/test/handlers/admin.test.js +344 -0
- package/test/handlers/appointment.test.js +260 -0
- package/test/handlers/calldown.test.js +310 -0
- package/test/handlers/disposition.test.js +396 -0
- package/test/handlers/log.test.js +324 -1
- package/test/handlers/managedOAuth.test.js +262 -0
- package/test/handlers/plugin.test.js +305 -0
- package/test/handlers/user.test.js +381 -0
- package/test/index.test.js +102 -0
- package/test/lib/analytics.test.js +146 -0
- package/test/lib/authSession.test.js +173 -0
- package/test/lib/encode.test.js +59 -0
- package/test/lib/errorHandler.test.js +246 -0
- package/test/lib/generalErrorMessage.test.js +82 -0
- package/test/lib/s3ErrorLogReport.test.js +187 -0
- package/test/mcp/mcpHandlerMore.test.js +384 -0
- package/test/mcp/tools/appointmentTools.test.js +362 -0
- package/test/models/callDownListModel.test.js +125 -0
- package/test/models/dynamo/lockSchema.test.js +37 -0
- package/test/models/dynamo/noteCacheSchema.test.js +45 -0
- package/test/models/llmSessionModel.test.js +91 -0
- package/test/models/models.test.js +92 -0
- package/test/routes/calldownRoutes.test.js +224 -0
- package/test/routes/coreRouterBroadRoutes.test.js +855 -0
- package/test/routes/dispositionRoutes.test.js +192 -0
- package/test/routes/managedAuthRoutes.test.js +151 -0
- package/test/routes/pluginRoutes.test.js +262 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
jest.mock('shortid', () => ({
|
|
2
|
+
generate: jest.fn(() => 'mock-log-id'),
|
|
3
|
+
}));
|
|
4
|
+
|
|
5
|
+
const mockConnector = require('../../connector/mock');
|
|
6
|
+
const { UserModel } = require('../../models/userModel');
|
|
7
|
+
const { CallLogModel } = require('../../models/callLogModel');
|
|
8
|
+
|
|
9
|
+
describe('mock connector', () => {
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
await CallLogModel.destroy({ where: {} });
|
|
12
|
+
await UserModel.destroy({ where: {} });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('creates and deletes the mock user idempotently', async () => {
|
|
16
|
+
const firstUser = await mockConnector.createUser();
|
|
17
|
+
const secondUser = await mockConnector.createUser();
|
|
18
|
+
|
|
19
|
+
expect(firstUser.id).toBe('mockUser');
|
|
20
|
+
expect(secondUser.id).toBe('mockUser');
|
|
21
|
+
await expect(UserModel.count({ where: { id: 'mockUser' } })).resolves.toBe(1);
|
|
22
|
+
|
|
23
|
+
await expect(mockConnector.deleteUser()).resolves.toBe(true);
|
|
24
|
+
await expect(mockConnector.deleteUser()).resolves.toBe(false);
|
|
25
|
+
await expect(UserModel.findByPk('mockUser')).resolves.toBeNull();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('creates a mock call log only once for the same session identity', async () => {
|
|
29
|
+
await mockConnector.createCallLog({
|
|
30
|
+
sessionId: 'session-1',
|
|
31
|
+
extensionNumber: 101,
|
|
32
|
+
hashedExtensionId: 'hash-101',
|
|
33
|
+
});
|
|
34
|
+
await mockConnector.createCallLog({
|
|
35
|
+
sessionId: 'session-1',
|
|
36
|
+
extensionNumber: '101',
|
|
37
|
+
hashedExtensionId: 'hash-101',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const records = await CallLogModel.findAll({
|
|
41
|
+
where: {
|
|
42
|
+
sessionId: 'session-1',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
expect(records).toHaveLength(1);
|
|
46
|
+
expect(records[0]).toMatchObject({
|
|
47
|
+
id: 'mock-log-id',
|
|
48
|
+
sessionId: 'session-1',
|
|
49
|
+
extensionNumber: '101',
|
|
50
|
+
hashedExtensionId: 'hash-101',
|
|
51
|
+
userId: 'mockUser',
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('returns matched and unmatched call logs in requested session order', async () => {
|
|
56
|
+
await mockConnector.createCallLog({
|
|
57
|
+
sessionId: 'session-1',
|
|
58
|
+
extensionNumber: '101',
|
|
59
|
+
hashedExtensionId: 'hash-101',
|
|
60
|
+
});
|
|
61
|
+
await mockConnector.createCallLog({
|
|
62
|
+
sessionId: 'session-3',
|
|
63
|
+
extensionNumber: '101',
|
|
64
|
+
hashedExtensionId: 'hash-101',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await expect(mockConnector.getCallLog({
|
|
68
|
+
sessionIds: 'session-1,session-2,session-3',
|
|
69
|
+
extensionNumber: '101',
|
|
70
|
+
hashedExtensionId: 'hash-101',
|
|
71
|
+
})).resolves.toEqual([
|
|
72
|
+
{ sessionId: 'session-1', matched: true, logId: 'mockThirdPartyLogId' },
|
|
73
|
+
{ sessionId: 'session-2', matched: false },
|
|
74
|
+
{ sessionId: 'session-3', matched: true, logId: 'mockThirdPartyLogId' },
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('falls back to legacy extension matching when hashed extension id does not match', async () => {
|
|
79
|
+
await CallLogModel.create({
|
|
80
|
+
id: 'legacy-log-id',
|
|
81
|
+
sessionId: 'session-legacy',
|
|
82
|
+
extensionNumber: '101',
|
|
83
|
+
hashedExtensionId: '',
|
|
84
|
+
userId: 'mockUser',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await expect(mockConnector.getCallLog({
|
|
88
|
+
sessionIds: 'session-legacy',
|
|
89
|
+
extensionNumber: '101',
|
|
90
|
+
hashedExtensionId: 'new-hash-101',
|
|
91
|
+
})).resolves.toEqual([
|
|
92
|
+
{ sessionId: 'session-legacy', matched: true, logId: 'mockThirdPartyLogId' },
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('cleanUpMockLogs removes only mock user call logs', async () => {
|
|
97
|
+
await CallLogModel.bulkCreate([
|
|
98
|
+
{
|
|
99
|
+
id: 'mock-log',
|
|
100
|
+
sessionId: 'mock-session',
|
|
101
|
+
extensionNumber: '',
|
|
102
|
+
hashedExtensionId: '',
|
|
103
|
+
userId: 'mockUser',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'real-log',
|
|
107
|
+
sessionId: 'real-session',
|
|
108
|
+
extensionNumber: '',
|
|
109
|
+
hashedExtensionId: '',
|
|
110
|
+
userId: 'realUser',
|
|
111
|
+
},
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
await mockConnector.cleanUpMockLogs();
|
|
115
|
+
|
|
116
|
+
await expect(CallLogModel.findOne({
|
|
117
|
+
where: {
|
|
118
|
+
userId: 'mockUser',
|
|
119
|
+
},
|
|
120
|
+
})).resolves.toBeNull();
|
|
121
|
+
const realLog = await CallLogModel.findOne({
|
|
122
|
+
where: {
|
|
123
|
+
userId: 'realUser',
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
expect(realLog).toMatchObject({
|
|
127
|
+
id: 'real-log',
|
|
128
|
+
sessionId: 'real-session',
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
renderDeep,
|
|
9
9
|
joinUrl,
|
|
10
10
|
performRequest,
|
|
11
|
+
mapFindContactResponse,
|
|
11
12
|
} = require('../../../connector/proxy/engine');
|
|
12
13
|
|
|
13
14
|
describe('proxy engine utilities', () => {
|
|
@@ -121,6 +122,90 @@ describe('proxy engine utilities', () => {
|
|
|
121
122
|
const args = axios.mock.calls[0][0];
|
|
122
123
|
expect(args.headers.Authorization).toBe(`Basic ${Buffer.from('login-key').toString('base64')}`);
|
|
123
124
|
});
|
|
125
|
+
|
|
126
|
+
test('mapFindContactResponse maps contact created date', () => {
|
|
127
|
+
const config = {
|
|
128
|
+
operations: {
|
|
129
|
+
findContact: {
|
|
130
|
+
responseMapping: {
|
|
131
|
+
listPath: 'body.contacts',
|
|
132
|
+
item: {
|
|
133
|
+
idPath: 'id',
|
|
134
|
+
namePath: 'name',
|
|
135
|
+
createdDatePath: 'created_at',
|
|
136
|
+
mostRecentActivityDatePath: 'updated_at'
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const response = {
|
|
143
|
+
data: {
|
|
144
|
+
contacts: [
|
|
145
|
+
{
|
|
146
|
+
id: 'c1',
|
|
147
|
+
name: 'Alice',
|
|
148
|
+
created_at: '2024-01-01T00:00:00Z',
|
|
149
|
+
updated_at: '2024-02-01T00:00:00Z'
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
expect(mapFindContactResponse({ config, response })).toEqual([
|
|
156
|
+
expect.objectContaining({
|
|
157
|
+
id: 'c1',
|
|
158
|
+
name: 'Alice',
|
|
159
|
+
createdDate: '2024-01-01T00:00:00Z',
|
|
160
|
+
mostRecentActivityDate: '2024-02-01T00:00:00Z'
|
|
161
|
+
})
|
|
162
|
+
]);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('mapFindContactResponse can map findContactWithName responses', () => {
|
|
166
|
+
const config = {
|
|
167
|
+
operations: {
|
|
168
|
+
findContact: {
|
|
169
|
+
responseMapping: {
|
|
170
|
+
listPath: 'body.phoneMatches',
|
|
171
|
+
item: {
|
|
172
|
+
idPath: 'id',
|
|
173
|
+
namePath: 'name'
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
findContactWithName: {
|
|
178
|
+
responseMapping: {
|
|
179
|
+
listPath: 'body.nameMatches',
|
|
180
|
+
item: {
|
|
181
|
+
idPath: 'personId',
|
|
182
|
+
namePath: 'displayName',
|
|
183
|
+
createdDatePath: 'created'
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const response = {
|
|
190
|
+
data: {
|
|
191
|
+
nameMatches: [
|
|
192
|
+
{
|
|
193
|
+
personId: 'p1',
|
|
194
|
+
displayName: 'Jane Smith',
|
|
195
|
+
created: '2023-05-01T00:00:00Z'
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
expect(mapFindContactResponse({ config, response, opName: 'findContactWithName' })).toEqual([
|
|
202
|
+
expect.objectContaining({
|
|
203
|
+
id: 'p1',
|
|
204
|
+
name: 'Jane Smith',
|
|
205
|
+
createdDate: '2023-05-01T00:00:00Z'
|
|
206
|
+
})
|
|
207
|
+
]);
|
|
208
|
+
});
|
|
124
209
|
});
|
|
125
210
|
|
|
126
211
|
|
|
@@ -126,6 +126,86 @@ describe('proxy connector - more coverage', () => {
|
|
|
126
126
|
expect(token).toBe(Buffer.from('abc:').toString('base64'));
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
+
test('getOauthInfo maps configured OAuth settings and falls back when config loading fails', async () => {
|
|
130
|
+
const oauthConfig = {
|
|
131
|
+
auth: {
|
|
132
|
+
type: 'oauth',
|
|
133
|
+
clientId: 'client-id',
|
|
134
|
+
clientSecret: 'client-secret',
|
|
135
|
+
tokenUrl: 'https://auth.example.com/token',
|
|
136
|
+
redirectUri: 'https://app.example.com/callback'
|
|
137
|
+
},
|
|
138
|
+
operations: {}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
await expect(proxy.getOauthInfo({
|
|
142
|
+
proxyConfig: oauthConfig,
|
|
143
|
+
tokenUrl: 'https://override.example.com/token'
|
|
144
|
+
})).resolves.toEqual({
|
|
145
|
+
clientId: 'client-id',
|
|
146
|
+
clientSecret: 'client-secret',
|
|
147
|
+
accessTokenUri: 'https://override.example.com/token',
|
|
148
|
+
redirectUri: 'https://app.example.com/callback'
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
Connector.getProxyConfig.mockRejectedValueOnce(new Error('config unavailable'));
|
|
152
|
+
await expect(proxy.getOauthInfo({ proxyId: 'missing-config' })).resolves.toEqual({});
|
|
153
|
+
await expect(proxy.getAuthType({ proxyId: '' })).resolves.toBe('apiKey');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('getUserInfo returns a warning when the proxy config has no getUserInfo operation', async () => {
|
|
157
|
+
await expect(proxy.getUserInfo({
|
|
158
|
+
proxyConfig: { operations: {} },
|
|
159
|
+
platform: 'minimal'
|
|
160
|
+
})).resolves.toMatchObject({
|
|
161
|
+
successful: false,
|
|
162
|
+
returnMessage: {
|
|
163
|
+
messageType: 'warning',
|
|
164
|
+
message: 'Could not load user information. The platform does not support getUserInfo operation.'
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('getUserList maps configured list responses and returns empty when unsupported', async () => {
|
|
170
|
+
const userListConfig = {
|
|
171
|
+
requestDefaults: { baseUrl: 'https://api.example.com' },
|
|
172
|
+
operations: {
|
|
173
|
+
getUserList: {
|
|
174
|
+
method: 'GET',
|
|
175
|
+
url: '/users',
|
|
176
|
+
responseMapping: {
|
|
177
|
+
listPath: 'body.records',
|
|
178
|
+
idPath: 'user.id',
|
|
179
|
+
namePath: 'profile.name',
|
|
180
|
+
emailPath: 'email'
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
axios.mockResolvedValueOnce({
|
|
186
|
+
data: {
|
|
187
|
+
records: [
|
|
188
|
+
{ user: { id: 'u1' }, profile: { name: 'Ada' }, email: 'ada@example.com' },
|
|
189
|
+
{ user: { id: 'u2' }, profile: { name: 'Grace' }, email: 'grace@example.com' }
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
await expect(proxy.getUserList({
|
|
195
|
+
user: { platformAdditionalInfo: { proxyId: 'p1' } },
|
|
196
|
+
authHeader: 'Bearer token',
|
|
197
|
+
proxyConfig: userListConfig
|
|
198
|
+
})).resolves.toEqual([
|
|
199
|
+
{ id: 'u1', name: 'Ada', email: 'ada@example.com' },
|
|
200
|
+
{ id: 'u2', name: 'Grace', email: 'grace@example.com' }
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
await expect(proxy.getUserList({
|
|
204
|
+
user: { platformAdditionalInfo: { proxyId: 'p1' } },
|
|
205
|
+
proxyConfig: { operations: {} }
|
|
206
|
+
})).resolves.toEqual([]);
|
|
207
|
+
});
|
|
208
|
+
|
|
129
209
|
test('getUserInfo maps id/name/message/platformAdditionalInfo', async () => {
|
|
130
210
|
const cfg = JSON.parse(JSON.stringify(sampleConfig));
|
|
131
211
|
delete cfg.auth; // ensure provided authHeader is used
|
|
@@ -165,6 +245,37 @@ describe('proxy connector - more coverage', () => {
|
|
|
165
245
|
expect(out.matchedContactInfo[0].id).toBe('c1');
|
|
166
246
|
});
|
|
167
247
|
|
|
248
|
+
test('contact operations return supported empty responses when omitted from manifest', async () => {
|
|
249
|
+
const proxyConfig = { operations: {} };
|
|
250
|
+
const user = { accessToken: 't', platformAdditionalInfo: { proxyId: 'p1' } };
|
|
251
|
+
|
|
252
|
+
await expect(proxy.findContact({
|
|
253
|
+
user,
|
|
254
|
+
authHeader: 'x',
|
|
255
|
+
phoneNumber: '+1',
|
|
256
|
+
proxyConfig
|
|
257
|
+
})).resolves.toEqual({ successful: true, matchedContactInfo: [] });
|
|
258
|
+
|
|
259
|
+
await expect(proxy.findContactWithName({
|
|
260
|
+
user,
|
|
261
|
+
authHeader: 'x',
|
|
262
|
+
name: 'Ada',
|
|
263
|
+
proxyConfig
|
|
264
|
+
})).resolves.toEqual({ successful: true, matchedContactInfo: [] });
|
|
265
|
+
|
|
266
|
+
await expect(proxy.createContact({
|
|
267
|
+
user,
|
|
268
|
+
authHeader: 'x',
|
|
269
|
+
phoneNumber: '+1',
|
|
270
|
+
newContactName: 'Ada',
|
|
271
|
+
newContactType: 'Contact',
|
|
272
|
+
proxyConfig
|
|
273
|
+
})).resolves.toEqual({
|
|
274
|
+
contactInfo: null,
|
|
275
|
+
returnMessage: { message: 'Not supported', messageType: 'warning', ttl: 2000 }
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
168
279
|
test('createContact maps object response', async () => {
|
|
169
280
|
Connector.getProxyConfig.mockResolvedValue(sampleConfig);
|
|
170
281
|
axios.mockResolvedValue({ data: { id: 'c2', name: 'Bob', type: 'Lead' } });
|
|
@@ -182,6 +293,70 @@ describe('proxy connector - more coverage', () => {
|
|
|
182
293
|
expect(res.returnMessage.messageType).toBe('success');
|
|
183
294
|
});
|
|
184
295
|
|
|
296
|
+
test('call and message log operations return Not supported when omitted from manifest', async () => {
|
|
297
|
+
const proxyConfig = { operations: {} };
|
|
298
|
+
const user = { accessToken: 't', platformAdditionalInfo: { proxyId: 'p1' } };
|
|
299
|
+
const commonCall = {
|
|
300
|
+
user,
|
|
301
|
+
contactInfo: { id: 'c1', name: 'Ada' },
|
|
302
|
+
authHeader: 'x',
|
|
303
|
+
proxyConfig
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
await expect(proxy.createCallLog({
|
|
307
|
+
...commonCall,
|
|
308
|
+
callLog: { direction: 'Inbound', startTime: Date.now(), duration: 30 },
|
|
309
|
+
note: ''
|
|
310
|
+
})).resolves.toEqual({
|
|
311
|
+
logId: undefined,
|
|
312
|
+
returnMessage: { message: 'Not supported', messageType: 'warning', ttl: 2000 }
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
await expect(proxy.getCallLog({
|
|
316
|
+
user,
|
|
317
|
+
callLogId: 'L1',
|
|
318
|
+
contactId: 'c1',
|
|
319
|
+
authHeader: 'x',
|
|
320
|
+
proxyConfig
|
|
321
|
+
})).resolves.toEqual({
|
|
322
|
+
callLogInfo: null,
|
|
323
|
+
returnMessage: { message: 'Not supported', messageType: 'warning', ttl: 2000 }
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
await expect(proxy.updateCallLog({
|
|
327
|
+
user,
|
|
328
|
+
existingCallLog: { thirdPartyLogId: 'L1' },
|
|
329
|
+
authHeader: 'x',
|
|
330
|
+
startTime: Date.now(),
|
|
331
|
+
duration: 30,
|
|
332
|
+
proxyConfig
|
|
333
|
+
})).resolves.toEqual({
|
|
334
|
+
returnMessage: { message: 'Not supported', messageType: 'warning', ttl: 2000 }
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
await expect(proxy.createMessageLog({
|
|
338
|
+
user,
|
|
339
|
+
contactInfo: { id: 'c1', name: 'Ada' },
|
|
340
|
+
authHeader: 'x',
|
|
341
|
+
message: { creationTime: Date.now() },
|
|
342
|
+
proxyConfig
|
|
343
|
+
})).resolves.toEqual({
|
|
344
|
+
logId: undefined,
|
|
345
|
+
returnMessage: { message: 'Not supported', messageType: 'warning', ttl: 2000 }
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
await expect(proxy.updateMessageLog({
|
|
349
|
+
user,
|
|
350
|
+
contactInfo: { id: 'c1', name: 'Ada' },
|
|
351
|
+
existingMessageLog: { thirdPartyLogId: 'M1' },
|
|
352
|
+
message: { creationTime: Date.now() },
|
|
353
|
+
authHeader: 'x',
|
|
354
|
+
proxyConfig
|
|
355
|
+
})).resolves.toEqual({
|
|
356
|
+
returnMessage: { message: 'Not supported', messageType: 'warning', ttl: 2000 }
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
185
360
|
test('createMessageLog maps idPath and updateMessageLog returns success', async () => {
|
|
186
361
|
Connector.getProxyConfig.mockResolvedValue(sampleConfig);
|
|
187
362
|
axios.mockResolvedValueOnce({ data: { activity: { id: 'M1' } } });
|
|
@@ -227,6 +402,40 @@ describe('proxy connector - more coverage', () => {
|
|
|
227
402
|
expect(res.returnMessage.message).toMatch(/Not supported/);
|
|
228
403
|
});
|
|
229
404
|
|
|
405
|
+
test('upsertCallDisposition performs configured operation when supported', async () => {
|
|
406
|
+
const dispositionConfig = {
|
|
407
|
+
requestDefaults: { baseUrl: 'https://api.example.com' },
|
|
408
|
+
operations: {
|
|
409
|
+
upsertCallDisposition: {
|
|
410
|
+
method: 'POST',
|
|
411
|
+
url: '/activities/{{thirdPartyLogId}}/disposition',
|
|
412
|
+
body: {
|
|
413
|
+
result: '{{dispositions.0.value}}'
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
Connector.getProxyConfig.mockResolvedValue(dispositionConfig);
|
|
419
|
+
axios.mockResolvedValueOnce({ data: { ok: true } });
|
|
420
|
+
|
|
421
|
+
const res = await proxy.upsertCallDisposition({
|
|
422
|
+
user: { accessToken: 't', platformAdditionalInfo: { proxyId: 'p1' } },
|
|
423
|
+
existingCallLog: { thirdPartyLogId: 'L1' },
|
|
424
|
+
authHeader: 'x',
|
|
425
|
+
dispositions: [{ value: 'Connected' }]
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
expect(res).toEqual({
|
|
429
|
+
logId: 'L1',
|
|
430
|
+
returnMessage: { message: 'Disposition updated', messageType: 'success', ttl: 2000 }
|
|
431
|
+
});
|
|
432
|
+
expect(axios.mock.calls[0][0]).toMatchObject({
|
|
433
|
+
method: 'POST',
|
|
434
|
+
url: 'https://api.example.com/activities/L1/disposition',
|
|
435
|
+
data: { result: 'Connected' }
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
230
439
|
test('getLicenseStatus maps values with custom config', async () => {
|
|
231
440
|
const licenseConfig = {
|
|
232
441
|
requestDefaults: { baseUrl: 'https://api.example.com' },
|
|
@@ -252,6 +461,23 @@ describe('proxy connector - more coverage', () => {
|
|
|
252
461
|
expect(s.licenseStatusDescription).toBe('All good');
|
|
253
462
|
});
|
|
254
463
|
|
|
464
|
+
test('getLicenseStatus handles missing users and missing license operations', async () => {
|
|
465
|
+
UserModel.findByPk.mockResolvedValueOnce(null);
|
|
466
|
+
await expect(proxy.getLicenseStatus({ userId: 'missing', platform: 'x' })).resolves.toEqual({
|
|
467
|
+
isLicenseValid: false,
|
|
468
|
+
licenseStatus: 'Invalid (User not found)',
|
|
469
|
+
licenseStatusDescription: ''
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
UserModel.findByPk.mockResolvedValueOnce({ id: 'u1', accessToken: 't', platformAdditionalInfo: { proxyId: 'p1' } });
|
|
473
|
+
Connector.getProxyConfig.mockResolvedValueOnce({ operations: {} });
|
|
474
|
+
await expect(proxy.getLicenseStatus({ userId: 'u1', platform: 'x' })).resolves.toEqual({
|
|
475
|
+
isLicenseValid: true,
|
|
476
|
+
licenseStatus: 'Basic',
|
|
477
|
+
licenseStatusDescription: ''
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
255
481
|
test('unAuthorize without custom op clears tokens and saves user', async () => {
|
|
256
482
|
Connector.getProxyConfig.mockResolvedValue(sampleConfig); // no unAuthorize op
|
|
257
483
|
const user = { accessToken: 't', refreshToken: 'r', save: jest.fn(), platformAdditionalInfo: { proxyId: 'p1' } };
|
|
@@ -275,5 +501,25 @@ describe('proxy connector - more coverage', () => {
|
|
|
275
501
|
expect(out.returnMessage.message).toMatch(/Logged out/);
|
|
276
502
|
expect(user.accessToken).toBe('');
|
|
277
503
|
});
|
|
504
|
+
|
|
505
|
+
test('unAuthorize returns a database warning when saving cleared tokens fails', async () => {
|
|
506
|
+
Connector.getProxyConfig.mockResolvedValue(sampleConfig);
|
|
507
|
+
const user = {
|
|
508
|
+
accessToken: 't',
|
|
509
|
+
refreshToken: 'r',
|
|
510
|
+
save: jest.fn().mockRejectedValue(new Error('save failed')),
|
|
511
|
+
platformAdditionalInfo: { proxyId: 'p1' }
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
await expect(proxy.unAuthorize({ user })).resolves.toMatchObject({
|
|
515
|
+
successful: false,
|
|
516
|
+
returnMessage: {
|
|
517
|
+
message: 'Database operation failed',
|
|
518
|
+
messageType: 'warning'
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
expect(user.accessToken).toBe('');
|
|
522
|
+
expect(user.refreshToken).toBe('');
|
|
523
|
+
});
|
|
278
524
|
});
|
|
279
525
|
|