@app-connect/core 1.7.8 → 1.7.10

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.
@@ -0,0 +1,1231 @@
1
+ const {
2
+ composeCallLog,
3
+ upsertCallAgentNote,
4
+ upsertCallSessionId,
5
+ upsertRingCentralUserName,
6
+ upsertRingCentralNumberAndExtension,
7
+ upsertCallSubject,
8
+ upsertContactPhoneNumber,
9
+ upsertCallDateTime,
10
+ upsertCallDuration,
11
+ upsertCallResult,
12
+ upsertCallRecording,
13
+ upsertAiNote,
14
+ upsertTranscript,
15
+ upsertLegs,
16
+ upsertRingSenseTranscript,
17
+ upsertRingSenseSummary,
18
+ upsertRingSenseAIScore,
19
+ upsertRingSenseBulletedSummary,
20
+ upsertRingSenseLink,
21
+ } = require('../../lib/callLogComposer');
22
+ const { LOG_DETAILS_FORMAT_TYPE } = require('../../lib/constants');
23
+
24
+ describe('callLogComposer', () => {
25
+ describe('composeCallLog', () => {
26
+ const baseParams = {
27
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT,
28
+ existingBody: '',
29
+ callLog: {
30
+ sessionId: 'session-123',
31
+ direction: 'Outbound',
32
+ startTime: '2024-01-15T10:30:00Z',
33
+ duration: 120,
34
+ result: 'Completed',
35
+ from: { phoneNumber: '+1234567890', name: 'John Doe' },
36
+ to: { phoneNumber: '+0987654321', name: 'Jane Smith' }
37
+ },
38
+ contactInfo: { phoneNumber: '+0987654321' },
39
+ user: { userSettings: {}, timezoneOffset: '+00:00' },
40
+ note: 'Test note',
41
+ subject: 'Test Call',
42
+ startTime: '2024-01-15T10:30:00Z',
43
+ duration: 120,
44
+ result: 'Completed'
45
+ };
46
+
47
+ test('should compose call log with default settings (plain text)', async () => {
48
+ const result = await composeCallLog(baseParams);
49
+
50
+ expect(result).toContain('- Note: Test note');
51
+ expect(result).toContain('- Summary: Test Call');
52
+ expect(result).toContain('- Duration:');
53
+ expect(result).toContain('- Result: Completed');
54
+ });
55
+
56
+ test('should compose call log in HTML format', async () => {
57
+ const result = await composeCallLog({
58
+ ...baseParams,
59
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
60
+ });
61
+
62
+ expect(result).toContain('<b>Agent notes</b>');
63
+ expect(result).toContain('<b>Summary</b>');
64
+ expect(result).toContain('<b>Duration</b>');
65
+ expect(result).toContain('<b>Result</b>');
66
+ });
67
+
68
+ test('should compose call log in Markdown format', async () => {
69
+ const result = await composeCallLog({
70
+ ...baseParams,
71
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
72
+ });
73
+
74
+ expect(result).toContain('## Agent notes');
75
+ expect(result).toContain('**Summary**:');
76
+ expect(result).toContain('**Duration**:');
77
+ expect(result).toContain('**Result**:');
78
+ });
79
+
80
+ test('should respect user settings to disable fields', async () => {
81
+ const result = await composeCallLog({
82
+ ...baseParams,
83
+ user: {
84
+ userSettings: {
85
+ addCallLogNote: { value: false },
86
+ addCallLogSubject: { value: false },
87
+ addCallLogDuration: { value: false }
88
+ }
89
+ }
90
+ });
91
+
92
+ expect(result).not.toContain('Note:');
93
+ expect(result).not.toContain('Summary:');
94
+ expect(result).not.toContain('Duration:');
95
+ expect(result).toContain('Result: Completed');
96
+ });
97
+
98
+ test('should add session ID when enabled', async () => {
99
+ const result = await composeCallLog({
100
+ ...baseParams,
101
+ user: {
102
+ userSettings: { addCallSessionId: { value: true } },
103
+ timezoneOffset: '+00:00'
104
+ }
105
+ });
106
+
107
+ expect(result).toContain('Session Id: session-123');
108
+ });
109
+
110
+ test('should add recording link when provided', async () => {
111
+ const result = await composeCallLog({
112
+ ...baseParams,
113
+ recordingLink: 'https://recording.example.com/123'
114
+ });
115
+
116
+ expect(result).toContain('Call recording link: https://recording.example.com/123');
117
+ });
118
+
119
+ test('should add AI note when provided', async () => {
120
+ const result = await composeCallLog({
121
+ ...baseParams,
122
+ aiNote: 'AI generated summary of the call'
123
+ });
124
+
125
+ expect(result).toContain('AI Note');
126
+ expect(result).toContain('AI generated summary of the call');
127
+ });
128
+
129
+ test('should add transcript when provided', async () => {
130
+ const result = await composeCallLog({
131
+ ...baseParams,
132
+ transcript: 'Hello, this is a test transcript.'
133
+ });
134
+
135
+ expect(result).toContain('Transcript');
136
+ expect(result).toContain('Hello, this is a test transcript.');
137
+ });
138
+
139
+ test('should add RingSense data when provided', async () => {
140
+ const result = await composeCallLog({
141
+ ...baseParams,
142
+ ringSenseTranscript: 'RS Transcript',
143
+ ringSenseSummary: 'RS Summary',
144
+ ringSenseAIScore: '85',
145
+ ringSenseBulletedSummary: '- Point 1\n- Point 2',
146
+ ringSenseLink: 'https://ringsense.example.com/123'
147
+ });
148
+
149
+ expect(result).toContain('RingSense transcript');
150
+ expect(result).toContain('RingSense summary');
151
+ expect(result).toContain('Call score: 85');
152
+ expect(result).toContain('RingSense recording link');
153
+ });
154
+
155
+ test('should add call legs when provided', async () => {
156
+ const result = await composeCallLog({
157
+ ...baseParams,
158
+ callLog: {
159
+ ...baseParams.callLog,
160
+ legs: [
161
+ { direction: 'Outbound', from: { phoneNumber: '+1234567890' }, to: { phoneNumber: '+0987654321' }, duration: 60 }
162
+ ]
163
+ }
164
+ });
165
+
166
+ expect(result).toContain('Call journey');
167
+ });
168
+
169
+ test('should handle RingCentral user name setting', async () => {
170
+ // For Outbound calls, it picks from.name
171
+ const result = await composeCallLog({
172
+ ...baseParams,
173
+ user: {
174
+ userSettings: { addRingCentralUserName: { value: true } },
175
+ timezoneOffset: '+00:00'
176
+ }
177
+ });
178
+
179
+ // For Outbound, uses from.name which is John Doe
180
+ expect(result).toContain('RingCentral user name: John Doe');
181
+ });
182
+
183
+ test('should handle RingCentral number and extension setting', async () => {
184
+ const result = await composeCallLog({
185
+ ...baseParams,
186
+ user: {
187
+ userSettings: { addRingCentralNumber: { value: true } },
188
+ timezoneOffset: '+00:00'
189
+ },
190
+ callLog: {
191
+ ...baseParams.callLog,
192
+ from: { phoneNumber: '+1234567890', extensionNumber: '101' }
193
+ }
194
+ });
195
+
196
+ expect(result).toContain('RingCentral number and extension');
197
+ });
198
+ });
199
+
200
+ describe('upsertCallAgentNote', () => {
201
+ test('should add note to empty body (plain text)', () => {
202
+ const result = upsertCallAgentNote({
203
+ body: '',
204
+ note: 'Test note',
205
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
206
+ });
207
+
208
+ expect(result).toBe('- Note: Test note\n');
209
+ });
210
+
211
+ test('should replace existing note (plain text)', () => {
212
+ const body = '- Note: Old note\n- Duration: 2 minutes';
213
+ const result = upsertCallAgentNote({
214
+ body,
215
+ note: 'New note',
216
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
217
+ });
218
+
219
+ expect(result).toContain('- Note: New note');
220
+ expect(result).not.toContain('Old note');
221
+ });
222
+
223
+ test('should add note in HTML format', () => {
224
+ const result = upsertCallAgentNote({
225
+ body: '',
226
+ note: 'Test note',
227
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
228
+ });
229
+
230
+ expect(result).toContain('<b>Agent notes</b>');
231
+ expect(result).toContain('Test note');
232
+ expect(result).toContain('<b>Call details</b>');
233
+ });
234
+
235
+ test('should add note in Markdown format', () => {
236
+ const result = upsertCallAgentNote({
237
+ body: '',
238
+ note: 'Test note',
239
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
240
+ });
241
+
242
+ expect(result).toContain('## Agent notes');
243
+ expect(result).toContain('Test note');
244
+ expect(result).toContain('## Call details');
245
+ });
246
+
247
+ test('should return body unchanged if note is empty', () => {
248
+ const body = 'Existing content';
249
+ const result = upsertCallAgentNote({
250
+ body,
251
+ note: '',
252
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
253
+ });
254
+
255
+ expect(result).toBe(body);
256
+ });
257
+ });
258
+
259
+ describe('upsertCallSessionId', () => {
260
+ test('should add session ID to empty body (plain text)', () => {
261
+ const result = upsertCallSessionId({
262
+ body: '',
263
+ id: 'session-123',
264
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
265
+ });
266
+
267
+ expect(result).toBe('- Session Id: session-123\n');
268
+ });
269
+
270
+ test('should add session ID in HTML format', () => {
271
+ const result = upsertCallSessionId({
272
+ body: '',
273
+ id: 'session-123',
274
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
275
+ });
276
+
277
+ expect(result).toContain('<b>Session Id</b>: session-123');
278
+ });
279
+
280
+ test('should add session ID in Markdown format', () => {
281
+ const result = upsertCallSessionId({
282
+ body: '',
283
+ id: 'session-123',
284
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
285
+ });
286
+
287
+ expect(result).toContain('**Session Id**: session-123');
288
+ });
289
+
290
+ test('should return body unchanged if id is empty', () => {
291
+ const body = 'Existing content';
292
+ const result = upsertCallSessionId({
293
+ body,
294
+ id: '',
295
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
296
+ });
297
+
298
+ expect(result).toBe(body);
299
+ });
300
+ });
301
+
302
+ describe('upsertRingCentralUserName', () => {
303
+ test('should add user name to empty body', () => {
304
+ const result = upsertRingCentralUserName({
305
+ body: '',
306
+ userName: 'John Doe',
307
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
308
+ });
309
+
310
+ expect(result).toContain('RingCentral user name: John Doe');
311
+ });
312
+
313
+ test('should replace pending placeholder', () => {
314
+ const body = '- RingCentral user name: (pending...)\n';
315
+ const result = upsertRingCentralUserName({
316
+ body,
317
+ userName: 'John Doe',
318
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
319
+ });
320
+
321
+ expect(result).toContain('RingCentral user name: John Doe');
322
+ expect(result).not.toContain('(pending...)');
323
+ });
324
+
325
+ test('should not replace existing non-pending value', () => {
326
+ const body = '- RingCentral user name: Jane Smith\n';
327
+ const result = upsertRingCentralUserName({
328
+ body,
329
+ userName: 'John Doe',
330
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
331
+ });
332
+
333
+ expect(result).toContain('Jane Smith');
334
+ expect(result).not.toContain('John Doe');
335
+ });
336
+
337
+ test('should add user name in HTML format', () => {
338
+ const result = upsertRingCentralUserName({
339
+ body: '',
340
+ userName: 'John Doe',
341
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
342
+ });
343
+
344
+ expect(result).toContain('<b>RingCentral user name</b>: John Doe');
345
+ });
346
+
347
+ test('should add user name in Markdown format', () => {
348
+ const result = upsertRingCentralUserName({
349
+ body: '',
350
+ userName: 'John Doe',
351
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
352
+ });
353
+
354
+ expect(result).toContain('**RingCentral user name**: John Doe');
355
+ });
356
+
357
+ test('should replace pending placeholder in Markdown format', () => {
358
+ const body = '**RingCentral user name**: (pending...)\n';
359
+ const result = upsertRingCentralUserName({
360
+ body,
361
+ userName: 'John Doe',
362
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
363
+ });
364
+
365
+ expect(result).toContain('**RingCentral user name**: John Doe');
366
+ expect(result).not.toContain('(pending...)');
367
+ });
368
+ });
369
+
370
+ describe('upsertRingCentralNumberAndExtension', () => {
371
+ test('should add number and extension to empty body (plain text)', () => {
372
+ const result = upsertRingCentralNumberAndExtension({
373
+ body: '',
374
+ number: '+1234567890',
375
+ extension: '101',
376
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
377
+ });
378
+
379
+ expect(result).toContain('RingCentral number and extension: +1234567890 101');
380
+ });
381
+
382
+ test('should add in HTML format', () => {
383
+ const result = upsertRingCentralNumberAndExtension({
384
+ body: '',
385
+ number: '+1234567890',
386
+ extension: '101',
387
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
388
+ });
389
+
390
+ expect(result).toContain('<b>RingCentral number and extension</b>: +1234567890 101');
391
+ });
392
+
393
+ test('should add in Markdown format', () => {
394
+ const result = upsertRingCentralNumberAndExtension({
395
+ body: '',
396
+ number: '+1234567890',
397
+ extension: '101',
398
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
399
+ });
400
+
401
+ expect(result).toContain('**RingCentral number and extension**: +1234567890 101');
402
+ });
403
+
404
+ test('should replace existing value in plain text', () => {
405
+ const body = '- RingCentral number and extension: +9999999999 999\n';
406
+ const result = upsertRingCentralNumberAndExtension({
407
+ body,
408
+ number: '+1234567890',
409
+ extension: '101',
410
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
411
+ });
412
+
413
+ expect(result).toContain('RingCentral number and extension: +1234567890 101');
414
+ expect(result).not.toContain('+9999999999');
415
+ });
416
+
417
+ test('should handle number without extension', () => {
418
+ const result = upsertRingCentralNumberAndExtension({
419
+ body: '',
420
+ number: '+1234567890',
421
+ extension: '',
422
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
423
+ });
424
+
425
+ expect(result).toContain('RingCentral number and extension: +1234567890');
426
+ });
427
+
428
+ test('should return body unchanged if number and extension are empty', () => {
429
+ const body = 'Existing content';
430
+ const result = upsertRingCentralNumberAndExtension({
431
+ body,
432
+ number: '',
433
+ extension: '',
434
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
435
+ });
436
+
437
+ expect(result).toBe(body);
438
+ });
439
+ });
440
+
441
+ describe('upsertCallSubject', () => {
442
+ test('should add subject to empty body', () => {
443
+ const result = upsertCallSubject({
444
+ body: '',
445
+ subject: 'Call with Client',
446
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
447
+ });
448
+
449
+ expect(result).toBe('- Summary: Call with Client\n');
450
+ });
451
+
452
+ test('should replace existing subject', () => {
453
+ const body = '- Summary: Old subject\n- Duration: 5 minutes';
454
+ const result = upsertCallSubject({
455
+ body,
456
+ subject: 'New subject',
457
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
458
+ });
459
+
460
+ expect(result).toContain('- Summary: New subject');
461
+ expect(result).not.toContain('Old subject');
462
+ });
463
+
464
+ test('should add subject in HTML format', () => {
465
+ const result = upsertCallSubject({
466
+ body: '',
467
+ subject: 'Test Subject',
468
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
469
+ });
470
+
471
+ expect(result).toContain('<b>Summary</b>: Test Subject');
472
+ });
473
+
474
+ test('should add subject in Markdown format', () => {
475
+ const result = upsertCallSubject({
476
+ body: '',
477
+ subject: 'Test Subject',
478
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
479
+ });
480
+
481
+ expect(result).toContain('**Summary**: Test Subject');
482
+ });
483
+
484
+ test('should replace existing subject in Markdown format', () => {
485
+ const body = '**Summary**: Old subject\n**Duration**: 5 minutes';
486
+ const result = upsertCallSubject({
487
+ body,
488
+ subject: 'New subject',
489
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
490
+ });
491
+
492
+ expect(result).toContain('**Summary**: New subject');
493
+ expect(result).not.toContain('Old subject');
494
+ });
495
+ });
496
+
497
+ describe('upsertContactPhoneNumber', () => {
498
+ test('should add phone number for outbound call', () => {
499
+ const result = upsertContactPhoneNumber({
500
+ body: '',
501
+ phoneNumber: '+1234567890',
502
+ direction: 'Outbound',
503
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
504
+ });
505
+
506
+ expect(result).toContain('Contact Number: +1234567890');
507
+ });
508
+
509
+ test('should add phone number for inbound call', () => {
510
+ const result = upsertContactPhoneNumber({
511
+ body: '',
512
+ phoneNumber: '+1234567890',
513
+ direction: 'Inbound',
514
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
515
+ });
516
+
517
+ expect(result).toContain('Contact Number: +1234567890');
518
+ });
519
+
520
+ test('should add phone number in HTML format with direction label', () => {
521
+ const result = upsertContactPhoneNumber({
522
+ body: '',
523
+ phoneNumber: '+1234567890',
524
+ direction: 'Outbound',
525
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
526
+ });
527
+
528
+ expect(result).toContain('<b>Recipient phone number</b>: +1234567890');
529
+ });
530
+
531
+ test('should add phone number in Markdown format', () => {
532
+ const result = upsertContactPhoneNumber({
533
+ body: '',
534
+ phoneNumber: '+1234567890',
535
+ direction: 'Outbound',
536
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
537
+ });
538
+
539
+ expect(result).toContain('**Contact Number**: +1234567890');
540
+ });
541
+ });
542
+
543
+ describe('upsertCallDateTime', () => {
544
+ test('should add formatted date/time', () => {
545
+ const result = upsertCallDateTime({
546
+ body: '',
547
+ startTime: '2024-01-15T10:30:00Z',
548
+ timezoneOffset: '+00:00',
549
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT,
550
+ logDateFormat: 'YYYY-MM-DD hh:mm:ss A'
551
+ });
552
+
553
+ expect(result).toContain('Date/Time:');
554
+ expect(result).toContain('2024-01-15');
555
+ });
556
+
557
+ test('should apply timezone offset', () => {
558
+ const result = upsertCallDateTime({
559
+ body: '',
560
+ startTime: '2024-01-15T10:30:00Z',
561
+ timezoneOffset: '+05:00',
562
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT,
563
+ logDateFormat: 'YYYY-MM-DD HH:mm'
564
+ });
565
+
566
+ expect(result).toContain('Date/Time:');
567
+ expect(result).toContain('15:30'); // 10:30 + 5 hours
568
+ });
569
+
570
+ test('should add date/time in HTML format', () => {
571
+ const result = upsertCallDateTime({
572
+ body: '',
573
+ startTime: '2024-01-15T10:30:00Z',
574
+ timezoneOffset: '+00:00',
575
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
576
+ });
577
+
578
+ expect(result).toContain('<b>Date/time</b>:');
579
+ });
580
+
581
+ test('should add date/time in Markdown format', () => {
582
+ const result = upsertCallDateTime({
583
+ body: '',
584
+ startTime: '2024-01-15T10:30:00Z',
585
+ timezoneOffset: '+00:00',
586
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN,
587
+ logDateFormat: 'YYYY-MM-DD hh:mm:ss A'
588
+ });
589
+
590
+ expect(result).toContain('**Date/Time**:');
591
+ expect(result).toContain('2024-01-15');
592
+ });
593
+
594
+ test('should replace existing date/time in Markdown format', () => {
595
+ const body = '**Date/Time**: 2024-01-01 09:00:00 AM\n';
596
+ const result = upsertCallDateTime({
597
+ body,
598
+ startTime: '2024-01-15T10:30:00Z',
599
+ timezoneOffset: '+00:00',
600
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN,
601
+ logDateFormat: 'YYYY-MM-DD HH:mm'
602
+ });
603
+
604
+ expect(result).toContain('**Date/Time**: 2024-01-15');
605
+ expect(result).not.toContain('2024-01-01');
606
+ });
607
+ });
608
+
609
+ describe('upsertCallDuration', () => {
610
+ test('should add formatted duration', () => {
611
+ const result = upsertCallDuration({
612
+ body: '',
613
+ duration: 120,
614
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
615
+ });
616
+
617
+ expect(result).toContain('Duration: 2 minutes');
618
+ });
619
+
620
+ test('should handle hours, minutes, seconds', () => {
621
+ const result = upsertCallDuration({
622
+ body: '',
623
+ duration: 3661, // 1 hour, 1 minute, 1 second
624
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
625
+ });
626
+
627
+ expect(result).toContain('1 hour');
628
+ expect(result).toContain('1 minute');
629
+ expect(result).toContain('1 second');
630
+ });
631
+
632
+ test('should add duration in HTML format', () => {
633
+ const result = upsertCallDuration({
634
+ body: '',
635
+ duration: 60,
636
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
637
+ });
638
+
639
+ expect(result).toContain('<b>Duration</b>:');
640
+ });
641
+
642
+ test('should handle 0 duration', () => {
643
+ const result = upsertCallDuration({
644
+ body: '',
645
+ duration: 0,
646
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
647
+ });
648
+
649
+ expect(result).toContain('Duration: 0 seconds');
650
+ });
651
+
652
+ test('should add duration in Markdown format', () => {
653
+ const result = upsertCallDuration({
654
+ body: '',
655
+ duration: 120,
656
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
657
+ });
658
+
659
+ expect(result).toContain('**Duration**: 2 minutes');
660
+ });
661
+
662
+ test('should replace existing duration in Markdown format', () => {
663
+ const body = '**Duration**: 1 minute\n';
664
+ const result = upsertCallDuration({
665
+ body,
666
+ duration: 180,
667
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
668
+ });
669
+
670
+ expect(result).toContain('**Duration**: 3 minutes');
671
+ expect(result).not.toContain('1 minute');
672
+ });
673
+ });
674
+
675
+ describe('upsertCallResult', () => {
676
+ test('should add call result', () => {
677
+ const result = upsertCallResult({
678
+ body: '',
679
+ result: 'Completed',
680
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
681
+ });
682
+
683
+ expect(result).toContain('Result: Completed');
684
+ });
685
+
686
+ test('should replace existing result', () => {
687
+ const body = '- Result: Pending\n';
688
+ const result = upsertCallResult({
689
+ body,
690
+ result: 'Completed',
691
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
692
+ });
693
+
694
+ expect(result).toContain('Result: Completed');
695
+ expect(result).not.toContain('Pending');
696
+ });
697
+
698
+ test('should add result in HTML format', () => {
699
+ const result = upsertCallResult({
700
+ body: '',
701
+ result: 'Missed',
702
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
703
+ });
704
+
705
+ expect(result).toContain('<b>Result</b>: Missed');
706
+ });
707
+
708
+ test('should add result in Markdown format', () => {
709
+ const result = upsertCallResult({
710
+ body: '',
711
+ result: 'Completed',
712
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
713
+ });
714
+
715
+ expect(result).toContain('**Result**: Completed');
716
+ });
717
+
718
+ test('should replace existing result in Markdown format', () => {
719
+ const body = '**Result**: Pending\n';
720
+ const result = upsertCallResult({
721
+ body,
722
+ result: 'Completed',
723
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
724
+ });
725
+
726
+ expect(result).toContain('**Result**: Completed');
727
+ expect(result).not.toContain('Pending');
728
+ });
729
+ });
730
+
731
+ describe('upsertCallRecording', () => {
732
+ test('should add recording link', () => {
733
+ const result = upsertCallRecording({
734
+ body: '',
735
+ recordingLink: 'https://recording.example.com/123',
736
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
737
+ });
738
+
739
+ expect(result).toContain('Call recording link: https://recording.example.com/123');
740
+ });
741
+
742
+ test('should add recording link in HTML format with anchor', () => {
743
+ const result = upsertCallRecording({
744
+ body: '',
745
+ recordingLink: 'https://recording.example.com/123',
746
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
747
+ });
748
+
749
+ expect(result).toContain('<a target="_blank" href="https://recording.example.com/123">open</a>');
750
+ });
751
+
752
+ test('should show pending for non-http link', () => {
753
+ const result = upsertCallRecording({
754
+ body: '',
755
+ recordingLink: 'pending',
756
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
757
+ });
758
+
759
+ expect(result).toContain('(pending...)');
760
+ });
761
+
762
+ test('should add recording link in Markdown format', () => {
763
+ const result = upsertCallRecording({
764
+ body: '',
765
+ recordingLink: 'https://recording.example.com/123',
766
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
767
+ });
768
+
769
+ expect(result).toContain('**Call recording link**: https://recording.example.com/123');
770
+ });
771
+
772
+ test('should replace existing recording link in Markdown format', () => {
773
+ const body = '**Call recording link**: https://old-link.com\n';
774
+ const result = upsertCallRecording({
775
+ body,
776
+ recordingLink: 'https://new-link.com/456',
777
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
778
+ });
779
+
780
+ expect(result).toContain('**Call recording link**: https://new-link.com/456');
781
+ expect(result).not.toContain('old-link');
782
+ });
783
+ });
784
+
785
+ describe('upsertAiNote', () => {
786
+ test('should add AI note', () => {
787
+ const result = upsertAiNote({
788
+ body: '',
789
+ aiNote: 'AI generated summary',
790
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
791
+ });
792
+
793
+ expect(result).toContain('AI Note');
794
+ expect(result).toContain('AI generated summary');
795
+ expect(result).toContain('--- END');
796
+ });
797
+
798
+ test('should add AI note in HTML format', () => {
799
+ const result = upsertAiNote({
800
+ body: '',
801
+ aiNote: 'AI summary\nWith multiple lines',
802
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
803
+ });
804
+
805
+ expect(result).toContain('<b>AI Note</b>');
806
+ expect(result).toContain('<br>');
807
+ });
808
+
809
+ test('should replace existing AI note', () => {
810
+ const body = '- AI Note:\nOld note\n--- END\n';
811
+ const result = upsertAiNote({
812
+ body,
813
+ aiNote: 'New AI note',
814
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
815
+ });
816
+
817
+ expect(result).toContain('New AI note');
818
+ expect(result).not.toContain('Old note');
819
+ });
820
+
821
+ test('should add AI note in Markdown format', () => {
822
+ const result = upsertAiNote({
823
+ body: '',
824
+ aiNote: 'AI generated summary',
825
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
826
+ });
827
+
828
+ expect(result).toContain('### AI Note');
829
+ expect(result).toContain('AI generated summary');
830
+ });
831
+
832
+ test('should replace existing AI note in Markdown format', () => {
833
+ const body = '### AI Note\nOld AI note\n';
834
+ const result = upsertAiNote({
835
+ body,
836
+ aiNote: 'New AI note',
837
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
838
+ });
839
+
840
+ expect(result).toContain('New AI note');
841
+ });
842
+ });
843
+
844
+ describe('upsertTranscript', () => {
845
+ test('should add transcript', () => {
846
+ const result = upsertTranscript({
847
+ body: '',
848
+ transcript: 'Hello, this is a transcript.',
849
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
850
+ });
851
+
852
+ expect(result).toContain('Transcript');
853
+ expect(result).toContain('Hello, this is a transcript.');
854
+ expect(result).toContain('--- END');
855
+ });
856
+
857
+ test('should add transcript in HTML format', () => {
858
+ const result = upsertTranscript({
859
+ body: '',
860
+ transcript: 'Line 1\nLine 2',
861
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
862
+ });
863
+
864
+ expect(result).toContain('<b>Transcript</b>');
865
+ expect(result).toContain('<br>');
866
+ });
867
+
868
+ test('should add transcript in Markdown format', () => {
869
+ const result = upsertTranscript({
870
+ body: '',
871
+ transcript: 'Hello, this is a transcript.',
872
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
873
+ });
874
+
875
+ expect(result).toContain('### Transcript');
876
+ expect(result).toContain('Hello, this is a transcript.');
877
+ });
878
+
879
+ test('should replace existing transcript in Markdown format', () => {
880
+ const body = '### Transcript\nOld transcript\n';
881
+ const result = upsertTranscript({
882
+ body,
883
+ transcript: 'New transcript content',
884
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
885
+ });
886
+
887
+ expect(result).toContain('New transcript content');
888
+ });
889
+ });
890
+
891
+ describe('upsertLegs', () => {
892
+ test('should add call legs (outbound)', () => {
893
+ const legs = [
894
+ {
895
+ direction: 'Outbound',
896
+ from: { phoneNumber: '+1234567890', name: 'John' },
897
+ to: { phoneNumber: '+0987654321' },
898
+ duration: 60
899
+ }
900
+ ];
901
+
902
+ const result = upsertLegs({
903
+ body: '',
904
+ legs,
905
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
906
+ });
907
+
908
+ expect(result).toContain('Call journey');
909
+ expect(result).toContain('Made call from');
910
+ });
911
+
912
+ test('should add call legs (inbound)', () => {
913
+ const legs = [
914
+ {
915
+ direction: 'Inbound',
916
+ from: { phoneNumber: '+1234567890' },
917
+ to: { phoneNumber: '+0987654321', name: 'Support' },
918
+ duration: 60
919
+ }
920
+ ];
921
+
922
+ const result = upsertLegs({
923
+ body: '',
924
+ legs,
925
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
926
+ });
927
+
928
+ expect(result).toContain('Received call at');
929
+ });
930
+
931
+ test('should handle transferred calls', () => {
932
+ const legs = [
933
+ {
934
+ direction: 'Inbound',
935
+ from: { phoneNumber: '+1234567890' },
936
+ to: { phoneNumber: '+0987654321' },
937
+ duration: 30
938
+ },
939
+ {
940
+ direction: 'Outbound',
941
+ from: { phoneNumber: '+0987654321', extensionNumber: '101' },
942
+ to: { phoneNumber: '+1111111111' },
943
+ duration: 45
944
+ }
945
+ ];
946
+
947
+ const result = upsertLegs({
948
+ body: '',
949
+ legs,
950
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
951
+ });
952
+
953
+ expect(result).toContain('Transferred to');
954
+ });
955
+
956
+ test('should return body unchanged for empty legs', () => {
957
+ const result = upsertLegs({
958
+ body: 'existing',
959
+ legs: [],
960
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
961
+ });
962
+
963
+ expect(result).toBe('existing');
964
+ });
965
+
966
+ test('should add call legs in HTML format', () => {
967
+ const legs = [
968
+ {
969
+ direction: 'Outbound',
970
+ from: { phoneNumber: '+1234567890', name: 'John' },
971
+ to: { phoneNumber: '+0987654321' },
972
+ duration: 60
973
+ }
974
+ ];
975
+
976
+ const result = upsertLegs({
977
+ body: '',
978
+ legs,
979
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
980
+ });
981
+
982
+ expect(result).toContain('<b>Call journey</b>');
983
+ expect(result).toContain('Made call from');
984
+ });
985
+
986
+ test('should add call legs in Markdown format', () => {
987
+ const legs = [
988
+ {
989
+ direction: 'Outbound',
990
+ from: { phoneNumber: '+1234567890', name: 'John' },
991
+ to: { phoneNumber: '+0987654321' },
992
+ duration: 60
993
+ }
994
+ ];
995
+
996
+ const result = upsertLegs({
997
+ body: '',
998
+ legs,
999
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
1000
+ });
1001
+
1002
+ expect(result).toContain('### Call journey');
1003
+ expect(result).toContain('Made call from');
1004
+ });
1005
+
1006
+ test('should replace existing legs in Markdown format', () => {
1007
+ const body = '### Call journey\nOld journey info\n';
1008
+ const legs = [
1009
+ {
1010
+ direction: 'Inbound',
1011
+ from: { phoneNumber: '+1234567890' },
1012
+ to: { phoneNumber: '+0987654321', name: 'Support' },
1013
+ duration: 120
1014
+ }
1015
+ ];
1016
+
1017
+ const result = upsertLegs({
1018
+ body,
1019
+ legs,
1020
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
1021
+ });
1022
+
1023
+ expect(result).toContain('### Call journey');
1024
+ expect(result).toContain('Received call at');
1025
+ });
1026
+ });
1027
+
1028
+ describe('RingSense Functions', () => {
1029
+ describe('upsertRingSenseTranscript', () => {
1030
+ test('should add RingSense transcript', () => {
1031
+ const result = upsertRingSenseTranscript({
1032
+ body: '',
1033
+ transcript: 'RS transcript content',
1034
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
1035
+ });
1036
+
1037
+ expect(result).toContain('RingSense transcript');
1038
+ expect(result).toContain('RS transcript content');
1039
+ });
1040
+
1041
+ test('should add in HTML format', () => {
1042
+ const result = upsertRingSenseTranscript({
1043
+ body: '',
1044
+ transcript: 'RS transcript',
1045
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
1046
+ });
1047
+
1048
+ expect(result).toContain('<b>RingSense transcript</b>');
1049
+ });
1050
+
1051
+ test('should add in Markdown format', () => {
1052
+ const result = upsertRingSenseTranscript({
1053
+ body: '',
1054
+ transcript: 'RS transcript content',
1055
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
1056
+ });
1057
+
1058
+ expect(result).toContain('### RingSense transcript');
1059
+ expect(result).toContain('RS transcript content');
1060
+ });
1061
+ });
1062
+
1063
+ describe('upsertRingSenseSummary', () => {
1064
+ test('should add RingSense summary', () => {
1065
+ const result = upsertRingSenseSummary({
1066
+ body: '',
1067
+ summary: 'RS summary content',
1068
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
1069
+ });
1070
+
1071
+ expect(result).toContain('RingSense summary');
1072
+ expect(result).toContain('RS summary content');
1073
+ });
1074
+
1075
+ test('should add in HTML format', () => {
1076
+ const result = upsertRingSenseSummary({
1077
+ body: '',
1078
+ summary: 'RS summary',
1079
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
1080
+ });
1081
+
1082
+ expect(result).toContain('<b>RingSense summary</b>');
1083
+ });
1084
+
1085
+ test('should add in Markdown format', () => {
1086
+ const result = upsertRingSenseSummary({
1087
+ body: '',
1088
+ summary: 'RS summary content',
1089
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
1090
+ });
1091
+
1092
+ expect(result).toContain('### RingSense summary');
1093
+ expect(result).toContain('RS summary content');
1094
+ });
1095
+ });
1096
+
1097
+ describe('upsertRingSenseAIScore', () => {
1098
+ test('should add RingSense AI score', () => {
1099
+ const result = upsertRingSenseAIScore({
1100
+ body: '',
1101
+ score: '85',
1102
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
1103
+ });
1104
+
1105
+ expect(result).toContain('Call score: 85');
1106
+ });
1107
+
1108
+ test('should add in HTML format', () => {
1109
+ const result = upsertRingSenseAIScore({
1110
+ body: '',
1111
+ score: '90',
1112
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
1113
+ });
1114
+
1115
+ expect(result).toContain('<b>Call score</b>: 90');
1116
+ });
1117
+
1118
+ test('should add in Markdown format', () => {
1119
+ const result = upsertRingSenseAIScore({
1120
+ body: '',
1121
+ score: '85',
1122
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
1123
+ });
1124
+
1125
+ expect(result).toContain('**Call score**: 85');
1126
+ });
1127
+ });
1128
+
1129
+ describe('upsertRingSenseBulletedSummary', () => {
1130
+ test('should add RingSense bulleted summary', () => {
1131
+ const result = upsertRingSenseBulletedSummary({
1132
+ body: '',
1133
+ summary: '- Point 1\n- Point 2',
1134
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
1135
+ });
1136
+
1137
+ expect(result).toContain('RingSense bulleted summary');
1138
+ expect(result).toContain('- Point 1');
1139
+ });
1140
+
1141
+ test('should add in HTML format', () => {
1142
+ const result = upsertRingSenseBulletedSummary({
1143
+ body: '',
1144
+ summary: '- Item 1\n- Item 2',
1145
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
1146
+ });
1147
+
1148
+ expect(result).toContain('<b>RingSense bulleted summary</b>');
1149
+ });
1150
+
1151
+ test('should add in Markdown format', () => {
1152
+ const result = upsertRingSenseBulletedSummary({
1153
+ body: '',
1154
+ summary: '- Point 1\n- Point 2',
1155
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
1156
+ });
1157
+
1158
+ expect(result).toContain('### RingSense bulleted summary');
1159
+ expect(result).toContain('- Point 1');
1160
+ });
1161
+ });
1162
+
1163
+ describe('upsertRingSenseLink', () => {
1164
+ test('should add RingSense recording link', () => {
1165
+ const result = upsertRingSenseLink({
1166
+ body: '',
1167
+ link: 'https://ringsense.example.com/123',
1168
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT
1169
+ });
1170
+
1171
+ expect(result).toContain('RingSense recording link: https://ringsense.example.com/123');
1172
+ });
1173
+
1174
+ test('should add in HTML format with anchor', () => {
1175
+ const result = upsertRingSenseLink({
1176
+ body: '',
1177
+ link: 'https://ringsense.example.com/123',
1178
+ logFormat: LOG_DETAILS_FORMAT_TYPE.HTML
1179
+ });
1180
+
1181
+ expect(result).toContain('<a target="_blank" href="https://ringsense.example.com/123">open</a>');
1182
+ });
1183
+
1184
+ test('should add in Markdown format', () => {
1185
+ const result = upsertRingSenseLink({
1186
+ body: '',
1187
+ link: 'https://ringsense.example.com/123',
1188
+ logFormat: LOG_DETAILS_FORMAT_TYPE.MARKDOWN
1189
+ });
1190
+
1191
+ expect(result).toContain('**RingSense recording link**: https://ringsense.example.com/123');
1192
+ });
1193
+ });
1194
+ });
1195
+
1196
+ describe('Edge Cases', () => {
1197
+ test('should handle null user settings', async () => {
1198
+ const result = await composeCallLog({
1199
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT,
1200
+ callLog: { direction: 'Outbound', startTime: new Date() },
1201
+ user: { userSettings: null },
1202
+ note: 'Test',
1203
+ duration: 60
1204
+ });
1205
+
1206
+ expect(result).toContain('Note: Test');
1207
+ expect(result).toContain('Duration:');
1208
+ });
1209
+
1210
+ test('should handle undefined user settings', async () => {
1211
+ const result = await composeCallLog({
1212
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT,
1213
+ callLog: { direction: 'Outbound' },
1214
+ user: {},
1215
+ result: 'Completed'
1216
+ });
1217
+
1218
+ expect(result).toContain('Result: Completed');
1219
+ });
1220
+
1221
+ test('should return empty string for empty params', async () => {
1222
+ const result = await composeCallLog({
1223
+ logFormat: LOG_DETAILS_FORMAT_TYPE.PLAIN_TEXT,
1224
+ user: {}
1225
+ });
1226
+
1227
+ expect(result).toBe('');
1228
+ });
1229
+ });
1230
+ });
1231
+