@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.
Files changed (137) hide show
  1. package/.env.test +5 -5
  2. package/README.md +441 -441
  3. package/connector/developerPortal.js +31 -42
  4. package/connector/mock.js +84 -77
  5. package/connector/proxy/engine.js +164 -163
  6. package/connector/proxy/index.js +500 -500
  7. package/connector/registry.js +252 -252
  8. package/docs/README.md +50 -50
  9. package/docs/architecture.md +93 -93
  10. package/docs/connectors.md +116 -117
  11. package/docs/handlers.md +125 -125
  12. package/docs/libraries.md +101 -101
  13. package/docs/models.md +144 -144
  14. package/docs/routes.md +115 -115
  15. package/docs/tests.md +73 -73
  16. package/handlers/admin.js +523 -523
  17. package/handlers/appointment.js +193 -0
  18. package/handlers/auth.js +296 -296
  19. package/handlers/calldown.js +99 -99
  20. package/handlers/contact.js +280 -280
  21. package/handlers/disposition.js +82 -80
  22. package/handlers/log.js +984 -973
  23. package/handlers/managedAuth.js +446 -446
  24. package/handlers/plugin.js +208 -208
  25. package/handlers/user.js +142 -142
  26. package/index.js +3140 -2652
  27. package/jest.config.js +56 -56
  28. package/lib/analytics.js +54 -54
  29. package/lib/authSession.js +109 -109
  30. package/lib/cacheCleanup.js +21 -0
  31. package/lib/callLogComposer.js +898 -898
  32. package/lib/callLogLookup.js +34 -0
  33. package/lib/constants.js +8 -8
  34. package/lib/debugTracer.js +177 -177
  35. package/lib/encode.js +30 -30
  36. package/lib/errorHandler.js +218 -206
  37. package/lib/generalErrorMessage.js +41 -41
  38. package/lib/jwt.js +18 -18
  39. package/lib/logger.js +190 -190
  40. package/lib/migrateCallLogsSchema.js +116 -0
  41. package/lib/ringcentral.js +266 -266
  42. package/lib/s3ErrorLogReport.js +65 -65
  43. package/lib/sharedSMSComposer.js +471 -471
  44. package/lib/util.js +67 -67
  45. package/mcp/README.md +412 -395
  46. package/mcp/lib/validator.js +91 -91
  47. package/mcp/mcpHandler.js +425 -425
  48. package/mcp/tools/cancelAppointment.js +101 -0
  49. package/mcp/tools/checkAuthStatus.js +105 -105
  50. package/mcp/tools/confirmAppointment.js +101 -0
  51. package/mcp/tools/createAppointment.js +157 -0
  52. package/mcp/tools/createCallLog.js +327 -316
  53. package/mcp/tools/createContact.js +117 -117
  54. package/mcp/tools/createMessageLog.js +287 -287
  55. package/mcp/tools/doAuth.js +60 -60
  56. package/mcp/tools/findContactByName.js +93 -93
  57. package/mcp/tools/findContactByPhone.js +101 -101
  58. package/mcp/tools/getCallLog.js +111 -102
  59. package/mcp/tools/getGoogleFilePicker.js +99 -99
  60. package/mcp/tools/getHelp.js +43 -43
  61. package/mcp/tools/getPublicConnectors.js +94 -94
  62. package/mcp/tools/getSessionInfo.js +90 -90
  63. package/mcp/tools/index.js +51 -41
  64. package/mcp/tools/listAppointments.js +163 -0
  65. package/mcp/tools/logout.js +96 -96
  66. package/mcp/tools/rcGetCallLogs.js +65 -65
  67. package/mcp/tools/updateAppointment.js +154 -0
  68. package/mcp/tools/updateCallLog.js +130 -126
  69. package/mcp/ui/App/App.tsx +358 -358
  70. package/mcp/ui/App/components/AuthInfoForm.tsx +113 -113
  71. package/mcp/ui/App/components/AuthSuccess.tsx +22 -22
  72. package/mcp/ui/App/components/ConnectorList.tsx +82 -82
  73. package/mcp/ui/App/components/DebugPanel.tsx +43 -43
  74. package/mcp/ui/App/components/OAuthConnect.tsx +270 -270
  75. package/mcp/ui/App/lib/callTool.ts +130 -130
  76. package/mcp/ui/App/lib/debugLog.ts +41 -41
  77. package/mcp/ui/App/lib/developerPortal.ts +111 -111
  78. package/mcp/ui/App/main.css +5 -5
  79. package/mcp/ui/App/root.tsx +13 -13
  80. package/mcp/ui/index.html +13 -13
  81. package/mcp/ui/package-lock.json +6356 -6356
  82. package/mcp/ui/package.json +25 -25
  83. package/mcp/ui/tsconfig.json +26 -26
  84. package/mcp/ui/vite.config.ts +16 -16
  85. package/models/accountDataModel.js +33 -33
  86. package/models/adminConfigModel.js +35 -35
  87. package/models/cacheModel.js +30 -26
  88. package/models/callDownListModel.js +34 -34
  89. package/models/callLogModel.js +33 -27
  90. package/models/dynamo/connectorSchema.js +146 -146
  91. package/models/dynamo/lockSchema.js +24 -24
  92. package/models/dynamo/noteCacheSchema.js +29 -29
  93. package/models/llmSessionModel.js +17 -17
  94. package/models/messageLogModel.js +25 -25
  95. package/models/sequelize.js +16 -16
  96. package/models/userModel.js +45 -45
  97. package/package.json +72 -72
  98. package/releaseNotes.json +1093 -1073
  99. package/test/connector/proxy/engine.test.js +126 -93
  100. package/test/connector/proxy/index.test.js +279 -279
  101. package/test/connector/proxy/sample.json +161 -161
  102. package/test/connector/registry.test.js +415 -415
  103. package/test/handlers/admin.test.js +616 -616
  104. package/test/handlers/auth.test.js +1018 -1015
  105. package/test/handlers/contact.test.js +1014 -1014
  106. package/test/handlers/log.test.js +1298 -1160
  107. package/test/handlers/managedAuth.test.js +458 -458
  108. package/test/handlers/plugin.test.js +380 -380
  109. package/test/index.test.js +105 -105
  110. package/test/lib/cacheCleanup.test.js +42 -0
  111. package/test/lib/callLogComposer.test.js +1231 -1231
  112. package/test/lib/debugTracer.test.js +328 -328
  113. package/test/lib/jwt.test.js +176 -176
  114. package/test/lib/logger.test.js +206 -206
  115. package/test/lib/oauth.test.js +359 -359
  116. package/test/lib/ringcentral.test.js +467 -467
  117. package/test/lib/sharedSMSComposer.test.js +1084 -1084
  118. package/test/lib/util.test.js +329 -329
  119. package/test/mcp/tools/checkAuthStatus.test.js +83 -82
  120. package/test/mcp/tools/createCallLog.test.js +436 -436
  121. package/test/mcp/tools/createContact.test.js +58 -58
  122. package/test/mcp/tools/createMessageLog.test.js +595 -595
  123. package/test/mcp/tools/doAuth.test.js +113 -113
  124. package/test/mcp/tools/findContactByName.test.js +275 -275
  125. package/test/mcp/tools/findContactByPhone.test.js +296 -296
  126. package/test/mcp/tools/getCallLog.test.js +298 -298
  127. package/test/mcp/tools/getGoogleFilePicker.test.js +281 -281
  128. package/test/mcp/tools/getPublicConnectors.test.js +107 -107
  129. package/test/mcp/tools/getSessionInfo.test.js +127 -127
  130. package/test/mcp/tools/logout.test.js +233 -233
  131. package/test/mcp/tools/rcGetCallLogs.test.js +56 -56
  132. package/test/mcp/tools/updateCallLog.test.js +360 -360
  133. package/test/models/accountDataModel.test.js +98 -98
  134. package/test/models/dynamo/connectorSchema.test.js +189 -189
  135. package/test/models/models.test.js +568 -539
  136. package/test/routes/managedAuthRoutes.test.js +104 -129
  137. package/test/setup.js +178 -178
@@ -1,539 +1,568 @@
1
- // Use in-memory SQLite for isolated model tests
2
- jest.mock('../../models/sequelize', () => {
3
- const { Sequelize } = require('sequelize');
4
- return {
5
- sequelize: new Sequelize({
6
- dialect: 'sqlite',
7
- storage: ':memory:',
8
- logging: false,
9
- }),
10
- };
11
- });
12
-
13
- const { CallLogModel } = require('../../models/callLogModel');
14
- const { MessageLogModel } = require('../../models/messageLogModel');
15
- const { UserModel } = require('../../models/userModel');
16
- const { CacheModel } = require('../../models/cacheModel');
17
- const { AdminConfigModel } = require('../../models/adminConfigModel');
18
- const { sequelize } = require('../../models/sequelize');
19
-
20
- describe('Core Models', () => {
21
- beforeAll(async () => {
22
- await CallLogModel.sync({ force: true });
23
- await MessageLogModel.sync({ force: true });
24
- await UserModel.sync({ force: true });
25
- await CacheModel.sync({ force: true });
26
- await AdminConfigModel.sync({ force: true });
27
- });
28
-
29
- afterEach(async () => {
30
- await CallLogModel.destroy({ where: {} });
31
- await MessageLogModel.destroy({ where: {} });
32
- await UserModel.destroy({ where: {} });
33
- await CacheModel.destroy({ where: {} });
34
- await AdminConfigModel.destroy({ where: {} });
35
- });
36
-
37
- afterAll(async () => {
38
- await sequelize.close();
39
- });
40
-
41
- describe('UserModel', () => {
42
- test('should create user with required fields', async () => {
43
- // Arrange & Act
44
- const user = await UserModel.create({
45
- id: 'user-1',
46
- platform: 'testCRM',
47
- accessToken: 'token-123'
48
- });
49
-
50
- // Assert
51
- expect(user.id).toBe('user-1');
52
- expect(user.platform).toBe('testCRM');
53
- expect(user.accessToken).toBe('token-123');
54
- });
55
-
56
- test('should create user with all optional fields', async () => {
57
- // Arrange
58
- const tokenExpiry = new Date('2024-12-31');
59
-
60
- // Act
61
- const user = await UserModel.create({
62
- id: 'user-full',
63
- platform: 'testCRM',
64
- hostname: 'test.example.com',
65
- accessToken: 'token-123',
66
- refreshToken: 'refresh-123',
67
- tokenExpiry: tokenExpiry,
68
- rcAccountId: 'rc-account-123',
69
- timezoneOffset: '-08:00',
70
- timezoneName: 'America/Los_Angeles',
71
- platformAdditionalInfo: { customField: 'value' },
72
- userSettings: { autoLog: true }
73
- });
74
-
75
- // Assert
76
- expect(user.hostname).toBe('test.example.com');
77
- expect(user.refreshToken).toBe('refresh-123');
78
- expect(user.rcAccountId).toBe('rc-account-123');
79
- expect(user.platformAdditionalInfo).toEqual({ customField: 'value' });
80
- expect(user.userSettings).toEqual({ autoLog: true });
81
- });
82
-
83
- test('should find user by primary key', async () => {
84
- // Arrange
85
- await UserModel.create({
86
- id: 'user-find',
87
- platform: 'testCRM',
88
- accessToken: 'token'
89
- });
90
-
91
- // Act
92
- const user = await UserModel.findByPk('user-find');
93
-
94
- // Assert
95
- expect(user).not.toBeNull();
96
- expect(user.id).toBe('user-find');
97
- });
98
-
99
- test('should find user by platform and id', async () => {
100
- // Arrange
101
- await UserModel.create({
102
- id: 'user-platform',
103
- platform: 'testCRM',
104
- accessToken: 'token'
105
- });
106
-
107
- // Act
108
- const user = await UserModel.findOne({
109
- where: { id: 'user-platform', platform: 'testCRM' }
110
- });
111
-
112
- // Assert
113
- expect(user).not.toBeNull();
114
- expect(user.platform).toBe('testCRM');
115
- });
116
-
117
- test('should update user fields', async () => {
118
- // Arrange
119
- const user = await UserModel.create({
120
- id: 'user-update',
121
- platform: 'testCRM',
122
- accessToken: 'old-token'
123
- });
124
-
125
- // Act
126
- await user.update({ accessToken: 'new-token' });
127
-
128
- // Assert
129
- const updated = await UserModel.findByPk('user-update');
130
- expect(updated.accessToken).toBe('new-token');
131
- });
132
-
133
- test('should delete user', async () => {
134
- // Arrange
135
- await UserModel.create({
136
- id: 'user-delete',
137
- platform: 'testCRM',
138
- accessToken: 'token'
139
- });
140
-
141
- // Act
142
- await UserModel.destroy({ where: { id: 'user-delete' } });
143
-
144
- // Assert
145
- const user = await UserModel.findByPk('user-delete');
146
- expect(user).toBeNull();
147
- });
148
- });
149
-
150
- describe('CallLogModel', () => {
151
- test('should create call log with required fields', async () => {
152
- // Arrange & Act
153
- const log = await CallLogModel.create({
154
- id: 'call-1',
155
- sessionId: 'session-123',
156
- platform: 'testCRM',
157
- thirdPartyLogId: 'third-party-1',
158
- userId: 'user-1'
159
- });
160
-
161
- // Assert
162
- expect(log.id).toBe('call-1');
163
- expect(log.sessionId).toBe('session-123');
164
- expect(log.thirdPartyLogId).toBe('third-party-1');
165
- });
166
-
167
- test('should create call log with optional contactId', async () => {
168
- // Act
169
- const log = await CallLogModel.create({
170
- id: 'call-contact',
171
- sessionId: 'session-456',
172
- platform: 'testCRM',
173
- thirdPartyLogId: 'third-party-2',
174
- userId: 'user-1',
175
- contactId: 'contact-123'
176
- });
177
-
178
- // Assert
179
- expect(log.contactId).toBe('contact-123');
180
- });
181
-
182
- test('should find call logs by session ID', async () => {
183
- // Arrange
184
- await CallLogModel.create({
185
- id: 'call-session',
186
- sessionId: 'unique-session',
187
- platform: 'testCRM',
188
- thirdPartyLogId: 'third-party-3',
189
- userId: 'user-1'
190
- });
191
-
192
- // Act
193
- const log = await CallLogModel.findOne({
194
- where: { sessionId: 'unique-session' }
195
- });
196
-
197
- // Assert
198
- expect(log).not.toBeNull();
199
- expect(log.id).toBe('call-session');
200
- });
201
-
202
- test('should find multiple call logs by session IDs', async () => {
203
- // Arrange
204
- await CallLogModel.bulkCreate([
205
- { id: 'call-multi-1', sessionId: 'session-a', platform: 'testCRM', thirdPartyLogId: 'tp-1', userId: 'user-1' },
206
- { id: 'call-multi-2', sessionId: 'session-b', platform: 'testCRM', thirdPartyLogId: 'tp-2', userId: 'user-1' },
207
- { id: 'call-multi-3', sessionId: 'session-c', platform: 'testCRM', thirdPartyLogId: 'tp-3', userId: 'user-1' }
208
- ]);
209
-
210
- // Act
211
- const { Op } = require('sequelize');
212
- const logs = await CallLogModel.findAll({
213
- where: {
214
- sessionId: { [Op.in]: ['session-a', 'session-c'] }
215
- }
216
- });
217
-
218
- // Assert
219
- expect(logs).toHaveLength(2);
220
- });
221
- });
222
-
223
- describe('MessageLogModel', () => {
224
- test('should create message log with required fields', async () => {
225
- // Arrange & Act
226
- const log = await MessageLogModel.create({
227
- id: 'msg-1',
228
- platform: 'testCRM',
229
- conversationId: 'conv-123',
230
- thirdPartyLogId: 'third-party-msg-1',
231
- userId: 'user-1'
232
- });
233
-
234
- // Assert
235
- expect(log.id).toBe('msg-1');
236
- expect(log.conversationId).toBe('conv-123');
237
- });
238
-
239
- test('should create message log with conversationLogId', async () => {
240
- // Act
241
- const log = await MessageLogModel.create({
242
- id: 'msg-conv',
243
- platform: 'testCRM',
244
- conversationId: 'conv-456',
245
- conversationLogId: 'conv-log-456',
246
- thirdPartyLogId: 'third-party-msg-2',
247
- userId: 'user-1'
248
- });
249
-
250
- // Assert
251
- expect(log.conversationLogId).toBe('conv-log-456');
252
- });
253
-
254
- test('should find message logs by conversation ID', async () => {
255
- // Arrange
256
- await MessageLogModel.create({
257
- id: 'msg-find',
258
- platform: 'testCRM',
259
- conversationId: 'conv-find',
260
- thirdPartyLogId: 'third-party-find',
261
- userId: 'user-1'
262
- });
263
-
264
- // Act
265
- const log = await MessageLogModel.findOne({
266
- where: { conversationId: 'conv-find' }
267
- });
268
-
269
- // Assert
270
- expect(log).not.toBeNull();
271
- expect(log.id).toBe('msg-find');
272
- });
273
-
274
- test('should find message logs by conversationLogId', async () => {
275
- // Arrange
276
- await MessageLogModel.create({
277
- id: 'msg-conv-log',
278
- platform: 'testCRM',
279
- conversationId: 'conv-789',
280
- conversationLogId: 'conv-log-789',
281
- thirdPartyLogId: 'third-party-789',
282
- userId: 'user-1'
283
- });
284
-
285
- // Act
286
- const log = await MessageLogModel.findOne({
287
- where: { conversationLogId: 'conv-log-789' }
288
- });
289
-
290
- // Assert
291
- expect(log).not.toBeNull();
292
- expect(log.conversationLogId).toBe('conv-log-789');
293
- });
294
- });
295
-
296
- describe('CacheModel', () => {
297
- test('should create cache entry', async () => {
298
- // Arrange & Act
299
- const cache = await CacheModel.create({
300
- id: 'cache-1',
301
- userId: 'user-123',
302
- cacheKey: 'contacts',
303
- status: 'active'
304
- });
305
-
306
- // Assert
307
- expect(cache.id).toBe('cache-1');
308
- expect(cache.userId).toBe('user-123');
309
- expect(cache.cacheKey).toBe('contacts');
310
- expect(cache.status).toBe('active');
311
- });
312
-
313
- test('should update cache status', async () => {
314
- // Arrange
315
- const cache = await CacheModel.create({
316
- id: 'cache-update',
317
- userId: 'user-123',
318
- cacheKey: 'contacts',
319
- status: 'pending'
320
- });
321
-
322
- // Act
323
- await cache.update({ status: 'completed' });
324
-
325
- // Assert
326
- const updated = await CacheModel.findByPk('cache-update');
327
- expect(updated.status).toBe('completed');
328
- });
329
-
330
- test('should find cache by userId and cacheKey', async () => {
331
- // Arrange
332
- await CacheModel.create({
333
- id: 'cache-find',
334
- userId: 'user-find',
335
- cacheKey: 'contacts-cache',
336
- status: 'active'
337
- });
338
-
339
- // Act
340
- const cache = await CacheModel.findOne({
341
- where: { userId: 'user-find', cacheKey: 'contacts-cache' }
342
- });
343
-
344
- // Assert
345
- expect(cache).not.toBeNull();
346
- expect(cache.status).toBe('active');
347
- });
348
- });
349
-
350
- describe('AdminConfigModel', () => {
351
- test('should create admin config with basic settings', async () => {
352
- // Arrange & Act
353
- const config = await AdminConfigModel.create({
354
- id: 'admin-1',
355
- userSettings: { autoLogCalls: true, autoLogMessages: false }
356
- });
357
-
358
- // Assert
359
- expect(config.id).toBe('admin-1');
360
- expect(config.userSettings).toEqual({ autoLogCalls: true, autoLogMessages: false });
361
- });
362
-
363
- test('should create admin config with tokens', async () => {
364
- // Arrange
365
- const expiry = new Date('2024-12-31');
366
-
367
- // Act
368
- const config = await AdminConfigModel.create({
369
- id: 'admin-tokens',
370
- adminAccessToken: 'access-token',
371
- adminRefreshToken: 'refresh-token',
372
- adminTokenExpiry: expiry
373
- });
374
-
375
- // Assert
376
- expect(config.adminAccessToken).toBe('access-token');
377
- expect(config.adminRefreshToken).toBe('refresh-token');
378
- });
379
-
380
- test('should create admin config with user mappings', async () => {
381
- // Arrange
382
- const userMappings = [
383
- { crmUserId: 'crm-1', rcExtensionId: ['ext-1'] },
384
- { crmUserId: 'crm-2', rcExtensionId: ['ext-2', 'ext-3'] }
385
- ];
386
-
387
- // Act
388
- const config = await AdminConfigModel.create({
389
- id: 'admin-mappings',
390
- userMappings: userMappings
391
- });
392
-
393
- // Assert
394
- expect(config.userMappings).toEqual(userMappings);
395
- });
396
-
397
- test('should update admin config settings', async () => {
398
- // Arrange
399
- const config = await AdminConfigModel.create({
400
- id: 'admin-update',
401
- userSettings: { autoLog: false }
402
- });
403
-
404
- // Act
405
- await config.update({ userSettings: { autoLog: true } });
406
-
407
- // Assert
408
- const updated = await AdminConfigModel.findByPk('admin-update');
409
- expect(updated.userSettings).toEqual({ autoLog: true });
410
- });
411
-
412
- test('should find admin config by primary key', async () => {
413
- // Arrange
414
- await AdminConfigModel.create({
415
- id: 'admin-find',
416
- userSettings: { enabled: true }
417
- });
418
-
419
- // Act
420
- const config = await AdminConfigModel.findByPk('admin-find');
421
-
422
- // Assert
423
- expect(config).not.toBeNull();
424
- expect(config.id).toBe('admin-find');
425
- });
426
- });
427
-
428
- describe('Model Relationships and Edge Cases', () => {
429
- test('should handle null JSON fields', async () => {
430
- // Act
431
- const user = await UserModel.create({
432
- id: 'user-null-json',
433
- platform: 'testCRM',
434
- accessToken: 'token',
435
- platformAdditionalInfo: null,
436
- userSettings: null
437
- });
438
-
439
- // Assert
440
- expect(user.platformAdditionalInfo).toBeNull();
441
- expect(user.userSettings).toBeNull();
442
- });
443
-
444
- test('should handle empty JSON objects', async () => {
445
- // Act
446
- const user = await UserModel.create({
447
- id: 'user-empty-json',
448
- platform: 'testCRM',
449
- accessToken: 'token',
450
- platformAdditionalInfo: {},
451
- userSettings: {}
452
- });
453
-
454
- // Assert
455
- expect(user.platformAdditionalInfo).toEqual({});
456
- expect(user.userSettings).toEqual({});
457
- });
458
-
459
- test('should handle complex nested JSON', async () => {
460
- // Arrange
461
- const complexData = {
462
- level1: {
463
- level2: {
464
- level3: {
465
- value: 'deep',
466
- array: [1, 2, 3]
467
- }
468
- },
469
- items: [
470
- { id: 1, name: 'Item 1' },
471
- { id: 2, name: 'Item 2' }
472
- ]
473
- }
474
- };
475
-
476
- // Act
477
- const user = await UserModel.create({
478
- id: 'user-complex-json',
479
- platform: 'testCRM',
480
- accessToken: 'token',
481
- platformAdditionalInfo: complexData
482
- });
483
-
484
- // Assert
485
- expect(user.platformAdditionalInfo).toEqual(complexData);
486
- expect(user.platformAdditionalInfo.level1.level2.level3.value).toBe('deep');
487
- });
488
-
489
- test('should enforce unique primary key constraint', async () => {
490
- // Arrange
491
- await UserModel.create({
492
- id: 'unique-user',
493
- platform: 'testCRM',
494
- accessToken: 'token-1'
495
- });
496
-
497
- // Act & Assert
498
- await expect(
499
- UserModel.create({
500
- id: 'unique-user',
501
- platform: 'testCRM',
502
- accessToken: 'token-2'
503
- })
504
- ).rejects.toThrow();
505
- });
506
-
507
- test('should handle bulk create', async () => {
508
- // Arrange
509
- const users = [
510
- { id: 'bulk-1', platform: 'testCRM', accessToken: 'token-1' },
511
- { id: 'bulk-2', platform: 'testCRM', accessToken: 'token-2' },
512
- { id: 'bulk-3', platform: 'testCRM', accessToken: 'token-3' }
513
- ];
514
-
515
- // Act
516
- await UserModel.bulkCreate(users);
517
-
518
- // Assert
519
- const count = await UserModel.count({ where: { platform: 'testCRM' } });
520
- expect(count).toBe(3);
521
- });
522
-
523
- test('should handle bulk destroy', async () => {
524
- // Arrange
525
- await UserModel.bulkCreate([
526
- { id: 'destroy-1', platform: 'destroyTest', accessToken: 'token-1' },
527
- { id: 'destroy-2', platform: 'destroyTest', accessToken: 'token-2' }
528
- ]);
529
-
530
- // Act
531
- await UserModel.destroy({ where: { platform: 'destroyTest' } });
532
-
533
- // Assert
534
- const count = await UserModel.count({ where: { platform: 'destroyTest' } });
535
- expect(count).toBe(0);
536
- });
537
- });
538
- });
539
-
1
+ // Use in-memory SQLite for isolated model tests
2
+ jest.mock('../../models/sequelize', () => {
3
+ const { Sequelize } = require('sequelize');
4
+ return {
5
+ sequelize: new Sequelize({
6
+ dialect: 'sqlite',
7
+ storage: ':memory:',
8
+ logging: false,
9
+ }),
10
+ };
11
+ });
12
+
13
+ const { CallLogModel } = require('../../models/callLogModel');
14
+ const { MessageLogModel } = require('../../models/messageLogModel');
15
+ const { UserModel } = require('../../models/userModel');
16
+ const { CacheModel } = require('../../models/cacheModel');
17
+ const { AdminConfigModel } = require('../../models/adminConfigModel');
18
+ const { sequelize } = require('../../models/sequelize');
19
+
20
+ describe('Core Models', () => {
21
+ beforeAll(async () => {
22
+ await CallLogModel.sync({ force: true });
23
+ await MessageLogModel.sync({ force: true });
24
+ await UserModel.sync({ force: true });
25
+ await CacheModel.sync({ force: true });
26
+ await AdminConfigModel.sync({ force: true });
27
+ });
28
+
29
+ afterEach(async () => {
30
+ await CallLogModel.destroy({ where: {} });
31
+ await MessageLogModel.destroy({ where: {} });
32
+ await UserModel.destroy({ where: {} });
33
+ await CacheModel.destroy({ where: {} });
34
+ await AdminConfigModel.destroy({ where: {} });
35
+ });
36
+
37
+ afterAll(async () => {
38
+ await sequelize.close();
39
+ });
40
+
41
+ describe('UserModel', () => {
42
+ test('should create user with required fields', async () => {
43
+ // Arrange & Act
44
+ const user = await UserModel.create({
45
+ id: 'user-1',
46
+ platform: 'testCRM',
47
+ accessToken: 'token-123'
48
+ });
49
+
50
+ // Assert
51
+ expect(user.id).toBe('user-1');
52
+ expect(user.platform).toBe('testCRM');
53
+ expect(user.accessToken).toBe('token-123');
54
+ });
55
+
56
+ test('should create user with all optional fields', async () => {
57
+ // Arrange
58
+ const tokenExpiry = new Date('2024-12-31');
59
+
60
+ // Act
61
+ const user = await UserModel.create({
62
+ id: 'user-full',
63
+ platform: 'testCRM',
64
+ hostname: 'test.example.com',
65
+ accessToken: 'token-123',
66
+ refreshToken: 'refresh-123',
67
+ tokenExpiry: tokenExpiry,
68
+ rcAccountId: 'rc-account-123',
69
+ timezoneOffset: '-08:00',
70
+ timezoneName: 'America/Los_Angeles',
71
+ platformAdditionalInfo: { customField: 'value' },
72
+ userSettings: { autoLog: true }
73
+ });
74
+
75
+ // Assert
76
+ expect(user.hostname).toBe('test.example.com');
77
+ expect(user.refreshToken).toBe('refresh-123');
78
+ expect(user.rcAccountId).toBe('rc-account-123');
79
+ expect(user.platformAdditionalInfo).toEqual({ customField: 'value' });
80
+ expect(user.userSettings).toEqual({ autoLog: true });
81
+ });
82
+
83
+ test('should find user by primary key', async () => {
84
+ // Arrange
85
+ await UserModel.create({
86
+ id: 'user-find',
87
+ platform: 'testCRM',
88
+ accessToken: 'token'
89
+ });
90
+
91
+ // Act
92
+ const user = await UserModel.findByPk('user-find');
93
+
94
+ // Assert
95
+ expect(user).not.toBeNull();
96
+ expect(user.id).toBe('user-find');
97
+ });
98
+
99
+ test('should find user by platform and id', async () => {
100
+ // Arrange
101
+ await UserModel.create({
102
+ id: 'user-platform',
103
+ platform: 'testCRM',
104
+ accessToken: 'token'
105
+ });
106
+
107
+ // Act
108
+ const user = await UserModel.findOne({
109
+ where: { id: 'user-platform', platform: 'testCRM' }
110
+ });
111
+
112
+ // Assert
113
+ expect(user).not.toBeNull();
114
+ expect(user.platform).toBe('testCRM');
115
+ });
116
+
117
+ test('should update user fields', async () => {
118
+ // Arrange
119
+ const user = await UserModel.create({
120
+ id: 'user-update',
121
+ platform: 'testCRM',
122
+ accessToken: 'old-token'
123
+ });
124
+
125
+ // Act
126
+ await user.update({ accessToken: 'new-token' });
127
+
128
+ // Assert
129
+ const updated = await UserModel.findByPk('user-update');
130
+ expect(updated.accessToken).toBe('new-token');
131
+ });
132
+
133
+ test('should delete user', async () => {
134
+ // Arrange
135
+ await UserModel.create({
136
+ id: 'user-delete',
137
+ platform: 'testCRM',
138
+ accessToken: 'token'
139
+ });
140
+
141
+ // Act
142
+ await UserModel.destroy({ where: { id: 'user-delete' } });
143
+
144
+ // Assert
145
+ const user = await UserModel.findByPk('user-delete');
146
+ expect(user).toBeNull();
147
+ });
148
+ });
149
+
150
+ describe('CallLogModel', () => {
151
+ test('should create call log with required fields', async () => {
152
+ // Arrange & Act
153
+ const log = await CallLogModel.create({
154
+ id: 'call-1',
155
+ sessionId: 'session-123',
156
+ platform: 'testCRM',
157
+ thirdPartyLogId: 'third-party-1',
158
+ userId: 'user-1'
159
+ });
160
+
161
+ // Assert
162
+ expect(log.id).toBe('call-1');
163
+ expect(log.sessionId).toBe('session-123');
164
+ expect(log.thirdPartyLogId).toBe('third-party-1');
165
+ });
166
+
167
+ test('should create call log with optional contactId', async () => {
168
+ // Act
169
+ const log = await CallLogModel.create({
170
+ id: 'call-contact',
171
+ sessionId: 'session-456',
172
+ platform: 'testCRM',
173
+ thirdPartyLogId: 'third-party-2',
174
+ userId: 'user-1',
175
+ contactId: 'contact-123'
176
+ });
177
+
178
+ // Assert
179
+ expect(log.contactId).toBe('contact-123');
180
+ });
181
+
182
+ test('should use extensionNumber as part of call log primary key', async () => {
183
+ // Arrange & Act
184
+ await CallLogModel.create({
185
+ id: 'call-shared',
186
+ sessionId: 'session-shared',
187
+ extensionNumber: '101',
188
+ platform: 'testCRM',
189
+ thirdPartyLogId: 'third-party-101',
190
+ userId: 'user-1'
191
+ });
192
+ await CallLogModel.create({
193
+ id: 'call-shared',
194
+ sessionId: 'session-shared',
195
+ extensionNumber: '102',
196
+ platform: 'testCRM',
197
+ thirdPartyLogId: 'third-party-102',
198
+ userId: 'user-1'
199
+ });
200
+
201
+ // Assert
202
+ const logs = await CallLogModel.findAll({
203
+ where: {
204
+ id: 'call-shared',
205
+ sessionId: 'session-shared'
206
+ }
207
+ });
208
+ expect(logs).toHaveLength(2);
209
+ });
210
+
211
+ test('should find call logs by session ID', async () => {
212
+ // Arrange
213
+ await CallLogModel.create({
214
+ id: 'call-session',
215
+ sessionId: 'unique-session',
216
+ platform: 'testCRM',
217
+ thirdPartyLogId: 'third-party-3',
218
+ userId: 'user-1'
219
+ });
220
+
221
+ // Act
222
+ const log = await CallLogModel.findOne({
223
+ where: { sessionId: 'unique-session' }
224
+ });
225
+
226
+ // Assert
227
+ expect(log).not.toBeNull();
228
+ expect(log.id).toBe('call-session');
229
+ });
230
+
231
+ test('should find multiple call logs by session IDs', async () => {
232
+ // Arrange
233
+ await CallLogModel.bulkCreate([
234
+ { id: 'call-multi-1', sessionId: 'session-a', platform: 'testCRM', thirdPartyLogId: 'tp-1', userId: 'user-1' },
235
+ { id: 'call-multi-2', sessionId: 'session-b', platform: 'testCRM', thirdPartyLogId: 'tp-2', userId: 'user-1' },
236
+ { id: 'call-multi-3', sessionId: 'session-c', platform: 'testCRM', thirdPartyLogId: 'tp-3', userId: 'user-1' }
237
+ ]);
238
+
239
+ // Act
240
+ const { Op } = require('sequelize');
241
+ const logs = await CallLogModel.findAll({
242
+ where: {
243
+ sessionId: { [Op.in]: ['session-a', 'session-c'] }
244
+ }
245
+ });
246
+
247
+ // Assert
248
+ expect(logs).toHaveLength(2);
249
+ });
250
+ });
251
+
252
+ describe('MessageLogModel', () => {
253
+ test('should create message log with required fields', async () => {
254
+ // Arrange & Act
255
+ const log = await MessageLogModel.create({
256
+ id: 'msg-1',
257
+ platform: 'testCRM',
258
+ conversationId: 'conv-123',
259
+ thirdPartyLogId: 'third-party-msg-1',
260
+ userId: 'user-1'
261
+ });
262
+
263
+ // Assert
264
+ expect(log.id).toBe('msg-1');
265
+ expect(log.conversationId).toBe('conv-123');
266
+ });
267
+
268
+ test('should create message log with conversationLogId', async () => {
269
+ // Act
270
+ const log = await MessageLogModel.create({
271
+ id: 'msg-conv',
272
+ platform: 'testCRM',
273
+ conversationId: 'conv-456',
274
+ conversationLogId: 'conv-log-456',
275
+ thirdPartyLogId: 'third-party-msg-2',
276
+ userId: 'user-1'
277
+ });
278
+
279
+ // Assert
280
+ expect(log.conversationLogId).toBe('conv-log-456');
281
+ });
282
+
283
+ test('should find message logs by conversation ID', async () => {
284
+ // Arrange
285
+ await MessageLogModel.create({
286
+ id: 'msg-find',
287
+ platform: 'testCRM',
288
+ conversationId: 'conv-find',
289
+ thirdPartyLogId: 'third-party-find',
290
+ userId: 'user-1'
291
+ });
292
+
293
+ // Act
294
+ const log = await MessageLogModel.findOne({
295
+ where: { conversationId: 'conv-find' }
296
+ });
297
+
298
+ // Assert
299
+ expect(log).not.toBeNull();
300
+ expect(log.id).toBe('msg-find');
301
+ });
302
+
303
+ test('should find message logs by conversationLogId', async () => {
304
+ // Arrange
305
+ await MessageLogModel.create({
306
+ id: 'msg-conv-log',
307
+ platform: 'testCRM',
308
+ conversationId: 'conv-789',
309
+ conversationLogId: 'conv-log-789',
310
+ thirdPartyLogId: 'third-party-789',
311
+ userId: 'user-1'
312
+ });
313
+
314
+ // Act
315
+ const log = await MessageLogModel.findOne({
316
+ where: { conversationLogId: 'conv-log-789' }
317
+ });
318
+
319
+ // Assert
320
+ expect(log).not.toBeNull();
321
+ expect(log.conversationLogId).toBe('conv-log-789');
322
+ });
323
+ });
324
+
325
+ describe('CacheModel', () => {
326
+ test('should create cache entry', async () => {
327
+ // Arrange & Act
328
+ const cache = await CacheModel.create({
329
+ id: 'cache-1',
330
+ userId: 'user-123',
331
+ cacheKey: 'contacts',
332
+ status: 'active'
333
+ });
334
+
335
+ // Assert
336
+ expect(cache.id).toBe('cache-1');
337
+ expect(cache.userId).toBe('user-123');
338
+ expect(cache.cacheKey).toBe('contacts');
339
+ expect(cache.status).toBe('active');
340
+ });
341
+
342
+ test('should update cache status', async () => {
343
+ // Arrange
344
+ const cache = await CacheModel.create({
345
+ id: 'cache-update',
346
+ userId: 'user-123',
347
+ cacheKey: 'contacts',
348
+ status: 'pending'
349
+ });
350
+
351
+ // Act
352
+ await cache.update({ status: 'completed' });
353
+
354
+ // Assert
355
+ const updated = await CacheModel.findByPk('cache-update');
356
+ expect(updated.status).toBe('completed');
357
+ });
358
+
359
+ test('should find cache by userId and cacheKey', async () => {
360
+ // Arrange
361
+ await CacheModel.create({
362
+ id: 'cache-find',
363
+ userId: 'user-find',
364
+ cacheKey: 'contacts-cache',
365
+ status: 'active'
366
+ });
367
+
368
+ // Act
369
+ const cache = await CacheModel.findOne({
370
+ where: { userId: 'user-find', cacheKey: 'contacts-cache' }
371
+ });
372
+
373
+ // Assert
374
+ expect(cache).not.toBeNull();
375
+ expect(cache.status).toBe('active');
376
+ });
377
+ });
378
+
379
+ describe('AdminConfigModel', () => {
380
+ test('should create admin config with basic settings', async () => {
381
+ // Arrange & Act
382
+ const config = await AdminConfigModel.create({
383
+ id: 'admin-1',
384
+ userSettings: { autoLogCalls: true, autoLogMessages: false }
385
+ });
386
+
387
+ // Assert
388
+ expect(config.id).toBe('admin-1');
389
+ expect(config.userSettings).toEqual({ autoLogCalls: true, autoLogMessages: false });
390
+ });
391
+
392
+ test('should create admin config with tokens', async () => {
393
+ // Arrange
394
+ const expiry = new Date('2024-12-31');
395
+
396
+ // Act
397
+ const config = await AdminConfigModel.create({
398
+ id: 'admin-tokens',
399
+ adminAccessToken: 'access-token',
400
+ adminRefreshToken: 'refresh-token',
401
+ adminTokenExpiry: expiry
402
+ });
403
+
404
+ // Assert
405
+ expect(config.adminAccessToken).toBe('access-token');
406
+ expect(config.adminRefreshToken).toBe('refresh-token');
407
+ });
408
+
409
+ test('should create admin config with user mappings', async () => {
410
+ // Arrange
411
+ const userMappings = [
412
+ { crmUserId: 'crm-1', rcExtensionId: ['ext-1'] },
413
+ { crmUserId: 'crm-2', rcExtensionId: ['ext-2', 'ext-3'] }
414
+ ];
415
+
416
+ // Act
417
+ const config = await AdminConfigModel.create({
418
+ id: 'admin-mappings',
419
+ userMappings: userMappings
420
+ });
421
+
422
+ // Assert
423
+ expect(config.userMappings).toEqual(userMappings);
424
+ });
425
+
426
+ test('should update admin config settings', async () => {
427
+ // Arrange
428
+ const config = await AdminConfigModel.create({
429
+ id: 'admin-update',
430
+ userSettings: { autoLog: false }
431
+ });
432
+
433
+ // Act
434
+ await config.update({ userSettings: { autoLog: true } });
435
+
436
+ // Assert
437
+ const updated = await AdminConfigModel.findByPk('admin-update');
438
+ expect(updated.userSettings).toEqual({ autoLog: true });
439
+ });
440
+
441
+ test('should find admin config by primary key', async () => {
442
+ // Arrange
443
+ await AdminConfigModel.create({
444
+ id: 'admin-find',
445
+ userSettings: { enabled: true }
446
+ });
447
+
448
+ // Act
449
+ const config = await AdminConfigModel.findByPk('admin-find');
450
+
451
+ // Assert
452
+ expect(config).not.toBeNull();
453
+ expect(config.id).toBe('admin-find');
454
+ });
455
+ });
456
+
457
+ describe('Model Relationships and Edge Cases', () => {
458
+ test('should handle null JSON fields', async () => {
459
+ // Act
460
+ const user = await UserModel.create({
461
+ id: 'user-null-json',
462
+ platform: 'testCRM',
463
+ accessToken: 'token',
464
+ platformAdditionalInfo: null,
465
+ userSettings: null
466
+ });
467
+
468
+ // Assert
469
+ expect(user.platformAdditionalInfo).toBeNull();
470
+ expect(user.userSettings).toBeNull();
471
+ });
472
+
473
+ test('should handle empty JSON objects', async () => {
474
+ // Act
475
+ const user = await UserModel.create({
476
+ id: 'user-empty-json',
477
+ platform: 'testCRM',
478
+ accessToken: 'token',
479
+ platformAdditionalInfo: {},
480
+ userSettings: {}
481
+ });
482
+
483
+ // Assert
484
+ expect(user.platformAdditionalInfo).toEqual({});
485
+ expect(user.userSettings).toEqual({});
486
+ });
487
+
488
+ test('should handle complex nested JSON', async () => {
489
+ // Arrange
490
+ const complexData = {
491
+ level1: {
492
+ level2: {
493
+ level3: {
494
+ value: 'deep',
495
+ array: [1, 2, 3]
496
+ }
497
+ },
498
+ items: [
499
+ { id: 1, name: 'Item 1' },
500
+ { id: 2, name: 'Item 2' }
501
+ ]
502
+ }
503
+ };
504
+
505
+ // Act
506
+ const user = await UserModel.create({
507
+ id: 'user-complex-json',
508
+ platform: 'testCRM',
509
+ accessToken: 'token',
510
+ platformAdditionalInfo: complexData
511
+ });
512
+
513
+ // Assert
514
+ expect(user.platformAdditionalInfo).toEqual(complexData);
515
+ expect(user.platformAdditionalInfo.level1.level2.level3.value).toBe('deep');
516
+ });
517
+
518
+ test('should enforce unique primary key constraint', async () => {
519
+ // Arrange
520
+ await UserModel.create({
521
+ id: 'unique-user',
522
+ platform: 'testCRM',
523
+ accessToken: 'token-1'
524
+ });
525
+
526
+ // Act & Assert
527
+ await expect(
528
+ UserModel.create({
529
+ id: 'unique-user',
530
+ platform: 'testCRM',
531
+ accessToken: 'token-2'
532
+ })
533
+ ).rejects.toThrow();
534
+ });
535
+
536
+ test('should handle bulk create', async () => {
537
+ // Arrange
538
+ const users = [
539
+ { id: 'bulk-1', platform: 'testCRM', accessToken: 'token-1' },
540
+ { id: 'bulk-2', platform: 'testCRM', accessToken: 'token-2' },
541
+ { id: 'bulk-3', platform: 'testCRM', accessToken: 'token-3' }
542
+ ];
543
+
544
+ // Act
545
+ await UserModel.bulkCreate(users);
546
+
547
+ // Assert
548
+ const count = await UserModel.count({ where: { platform: 'testCRM' } });
549
+ expect(count).toBe(3);
550
+ });
551
+
552
+ test('should handle bulk destroy', async () => {
553
+ // Arrange
554
+ await UserModel.bulkCreate([
555
+ { id: 'destroy-1', platform: 'destroyTest', accessToken: 'token-1' },
556
+ { id: 'destroy-2', platform: 'destroyTest', accessToken: 'token-2' }
557
+ ]);
558
+
559
+ // Act
560
+ await UserModel.destroy({ where: { platform: 'destroyTest' } });
561
+
562
+ // Assert
563
+ const count = await UserModel.count({ where: { platform: 'destroyTest' } });
564
+ expect(count).toBe(0);
565
+ });
566
+ });
567
+ });
568
+