@tellescope/sdk 1.237.2 → 1.237.4

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 (34) hide show
  1. package/lib/cjs/enduser.d.ts +1 -1
  2. package/lib/cjs/tests/api_tests/database_cascade_delete.test.d.ts +6 -0
  3. package/lib/cjs/tests/api_tests/database_cascade_delete.test.d.ts.map +1 -0
  4. package/lib/cjs/tests/api_tests/database_cascade_delete.test.js +298 -0
  5. package/lib/cjs/tests/api_tests/database_cascade_delete.test.js.map +1 -0
  6. package/lib/cjs/tests/api_tests/inbox_thread_assignment_updates.test.d.ts.map +1 -1
  7. package/lib/cjs/tests/api_tests/inbox_thread_assignment_updates.test.js +173 -11
  8. package/lib/cjs/tests/api_tests/inbox_thread_assignment_updates.test.js.map +1 -1
  9. package/lib/cjs/tests/api_tests/inbox_thread_status_preservation.test.d.ts +6 -0
  10. package/lib/cjs/tests/api_tests/inbox_thread_status_preservation.test.d.ts.map +1 -0
  11. package/lib/cjs/tests/tests.d.ts.map +1 -1
  12. package/lib/cjs/tests/tests.js +30 -3
  13. package/lib/cjs/tests/tests.js.map +1 -1
  14. package/lib/esm/enduser.d.ts +1 -1
  15. package/lib/esm/tests/api_tests/database_cascade_delete.test.d.ts +6 -0
  16. package/lib/esm/tests/api_tests/database_cascade_delete.test.d.ts.map +1 -0
  17. package/lib/esm/tests/api_tests/database_cascade_delete.test.js +294 -0
  18. package/lib/esm/tests/api_tests/database_cascade_delete.test.js.map +1 -0
  19. package/lib/esm/tests/api_tests/inbox_thread_assignment_updates.test.d.ts.map +1 -1
  20. package/lib/esm/tests/api_tests/inbox_thread_assignment_updates.test.js +173 -11
  21. package/lib/esm/tests/api_tests/inbox_thread_assignment_updates.test.js.map +1 -1
  22. package/lib/esm/tests/api_tests/inbox_thread_status_preservation.test.d.ts +6 -0
  23. package/lib/esm/tests/api_tests/inbox_thread_status_preservation.test.d.ts.map +1 -0
  24. package/lib/esm/tests/api_tests/inbox_thread_status_preservation.test.js +435 -0
  25. package/lib/esm/tests/api_tests/inbox_thread_status_preservation.test.js.map +1 -0
  26. package/lib/esm/tests/tests.d.ts.map +1 -1
  27. package/lib/esm/tests/tests.js +30 -3
  28. package/lib/esm/tests/tests.js.map +1 -1
  29. package/lib/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +10 -10
  31. package/src/tests/api_tests/database_cascade_delete.test.ts +193 -0
  32. package/src/tests/api_tests/inbox_thread_assignment_updates.test.ts +158 -0
  33. package/src/tests/tests.ts +23 -0
  34. package/test_generated.pdf +0 -0
@@ -0,0 +1,193 @@
1
+ require('source-map-support').install();
2
+
3
+ import { Session } from "../../sdk"
4
+ import {
5
+ async_test,
6
+ handleAnyError,
7
+ log_header,
8
+ wait,
9
+ } from "@tellescope/testing"
10
+ import { setup_tests } from "../setup"
11
+
12
+ const host = process.env.API_URL || 'http://localhost:8080' as const
13
+
14
+ // Main test function that can be called independently or from main test suite
15
+ export const database_cascade_delete_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
16
+ log_header("Database Cascade Delete Tests")
17
+
18
+ // Test 1: Create database and records, then delete database and verify records are cascade deleted
19
+ await async_test(
20
+ 'cascade delete - deleting database deletes all database_records',
21
+ async () => {
22
+ // Create a test database
23
+ const database = await sdk.api.databases.createOne({
24
+ title: `__Test__CascadeDelete_${Date.now()}`,
25
+ fields: [
26
+ { type: 'Text', label: "Name" },
27
+ { type: 'Number', label: "Age" },
28
+ ],
29
+ })
30
+
31
+ // Create multiple database records
32
+ const records = await sdk.api.database_records.createSome([
33
+ {
34
+ databaseId: database.id,
35
+ values: [
36
+ { type: 'Text', value: 'Alice', label: 'Name' },
37
+ { type: 'Number', value: 25, label: 'Age' },
38
+ ],
39
+ },
40
+ {
41
+ databaseId: database.id,
42
+ values: [
43
+ { type: 'Text', value: 'Bob', label: 'Name' },
44
+ { type: 'Number', value: 30, label: 'Age' },
45
+ ],
46
+ },
47
+ {
48
+ databaseId: database.id,
49
+ values: [
50
+ { type: 'Text', value: 'Charlie', label: 'Name' },
51
+ { type: 'Number', value: 35, label: 'Age' },
52
+ ],
53
+ },
54
+ ])
55
+
56
+ const recordIds = records.created.map(r => r.id)
57
+ console.log(`Created database ${database.id} with ${recordIds.length} records`)
58
+
59
+ // Verify records exist before deletion
60
+ const recordsBefore = await sdk.api.database_records.getSome({ filter: { databaseId: database.id } })
61
+ if (recordsBefore.length !== 3) {
62
+ throw new Error(`Expected 3 records before delete, got ${recordsBefore.length}`)
63
+ }
64
+ console.log(`Verified ${recordsBefore.length} records exist before deletion`)
65
+
66
+ // Delete the database
67
+ await sdk.api.databases.deleteOne(database.id)
68
+ console.log(`Deleted database ${database.id}`)
69
+
70
+ // Wait a moment for cascade delete to propagate
71
+ await wait(undefined, 500)
72
+
73
+ // Verify all records were cascade deleted
74
+ const recordsAfter = await sdk.api.database_records.getSome({ filter: { databaseId: database.id } })
75
+
76
+ if (recordsAfter.length !== 0) {
77
+ throw new Error(`Expected 0 records after cascade delete, got ${recordsAfter.length}`)
78
+ }
79
+ console.log(`✓ Verified all records were cascade deleted`)
80
+
81
+ // Also verify individual record fetches return 404
82
+ for (const recordId of recordIds) {
83
+ try {
84
+ await sdk.api.database_records.getOne(recordId)
85
+ throw new Error(`Record ${recordId} should have been deleted but was still found`)
86
+ } catch (err: any) {
87
+ if (!err.message?.includes('Could not find') && !err.message?.includes('404')) {
88
+ throw new Error(`Unexpected error fetching deleted record: ${err.message}`)
89
+ }
90
+ // Expected: record not found
91
+ }
92
+ }
93
+ console.log(`✓ Verified individual record fetches return not found`)
94
+
95
+ return true
96
+ },
97
+ { onResult: (result) => result === true }
98
+ )
99
+
100
+ // Test 2: Verify database with no records can be deleted without error
101
+ await async_test(
102
+ 'cascade delete - deleting empty database works',
103
+ async () => {
104
+ const database = await sdk.api.databases.createOne({
105
+ title: `__Test__EmptyDB_${Date.now()}`,
106
+ fields: [{ type: 'Text', label: "Field" }],
107
+ })
108
+
109
+ // Delete immediately without adding records
110
+ await sdk.api.databases.deleteOne(database.id)
111
+
112
+ // Verify database is gone
113
+ try {
114
+ await sdk.api.databases.getOne(database.id)
115
+ throw new Error('Database should have been deleted')
116
+ } catch (err: any) {
117
+ if (!err.message?.includes('Could not find') && !err.message?.includes('404')) {
118
+ throw new Error(`Unexpected error: ${err.message}`)
119
+ }
120
+ }
121
+
122
+ return true
123
+ },
124
+ { onResult: (result) => result === true }
125
+ )
126
+
127
+ // Test 3: Verify records from other databases are not affected
128
+ await async_test(
129
+ 'cascade delete - only affects records from deleted database',
130
+ async () => {
131
+ // Create two databases
132
+ const database1 = await sdk.api.databases.createOne({
133
+ title: `__Test__DB1_${Date.now()}`,
134
+ fields: [{ type: 'Text', label: "Value" }],
135
+ })
136
+ const database2 = await sdk.api.databases.createOne({
137
+ title: `__Test__DB2_${Date.now()}`,
138
+ fields: [{ type: 'Text', label: "Value" }],
139
+ })
140
+
141
+ // Create records in both databases
142
+ await sdk.api.database_records.createOne({
143
+ databaseId: database1.id,
144
+ values: [{ type: 'Text', value: 'DB1 Record', label: 'Value' }],
145
+ })
146
+ const db2Record = await sdk.api.database_records.createOne({
147
+ databaseId: database2.id,
148
+ values: [{ type: 'Text', value: 'DB2 Record', label: 'Value' }],
149
+ })
150
+
151
+ // Delete database1
152
+ await sdk.api.databases.deleteOne(database1.id)
153
+ await wait(undefined, 500)
154
+
155
+ // Verify database2's record still exists
156
+ const db2RecordAfter = await sdk.api.database_records.getOne(db2Record.id)
157
+ if (!db2RecordAfter) {
158
+ throw new Error('Database2 record should still exist')
159
+ }
160
+ console.log(`✓ Verified database2 record was not affected by database1 deletion`)
161
+
162
+ // Cleanup database2
163
+ await sdk.api.databases.deleteOne(database2.id)
164
+
165
+ return true
166
+ },
167
+ { onResult: (result) => result === true }
168
+ )
169
+
170
+ console.log("✅ All Database Cascade Delete tests passed!")
171
+ }
172
+
173
+ // Allow running this test file independently
174
+ if (require.main === module) {
175
+ console.log(`🌐 Using API URL: ${host}`)
176
+ const sdk = new Session({ host })
177
+ const sdkNonAdmin = new Session({ host })
178
+
179
+ const runTests = async () => {
180
+ await setup_tests(sdk, sdkNonAdmin)
181
+ await database_cascade_delete_tests({ sdk, sdkNonAdmin })
182
+ }
183
+
184
+ runTests()
185
+ .then(() => {
186
+ console.log("✅ Database cascade delete test suite completed successfully")
187
+ process.exit(0)
188
+ })
189
+ .catch((error) => {
190
+ console.error("❌ Database cascade delete test suite failed:", error)
191
+ process.exit(1)
192
+ })
193
+ }
@@ -503,6 +503,164 @@ export const inbox_thread_assignment_updates_tests = async ({ sdk, sdkNonAdmin }
503
503
  assert(emptyIdsResult.threads.length >= 3, 'Empty ids array should not filter (return all threads)')
504
504
  console.log("✅ ids filter empty array test passed")
505
505
 
506
+ // Test 24: sortBy parameter - default behavior (timestamp)
507
+ console.log("Testing sortBy parameter - default behavior...")
508
+
509
+ // Create threads with controlled timestamps for sort testing
510
+ const sortTestBaseTime = Date.now()
511
+ const sortThread1 = await sdk.api.inbox_threads.createOne({
512
+ ...defaultThreadFields,
513
+ type: "SMS",
514
+ title: "Sort Test Thread 1",
515
+ threadId: `sort-test-1-${timestamp}`,
516
+ phoneNumber: "+15555550001",
517
+ enduserPhoneNumber: "+15555550002",
518
+ timestamp: new Date(sortTestBaseTime - 3000), // oldest timestamp
519
+ outboundTimestamp: new Date(sortTestBaseTime), // newest outboundTimestamp
520
+ })
521
+
522
+ const sortThread2 = await sdk.api.inbox_threads.createOne({
523
+ ...defaultThreadFields,
524
+ type: "SMS",
525
+ title: "Sort Test Thread 2",
526
+ threadId: `sort-test-2-${timestamp}`,
527
+ phoneNumber: "+15555550003",
528
+ enduserPhoneNumber: "+15555550004",
529
+ timestamp: new Date(sortTestBaseTime - 1000), // newest timestamp
530
+ outboundTimestamp: new Date(sortTestBaseTime - 3000), // oldest outboundTimestamp
531
+ })
532
+
533
+ const sortThread3 = await sdk.api.inbox_threads.createOne({
534
+ ...defaultThreadFields,
535
+ type: "SMS",
536
+ title: "Sort Test Thread 3",
537
+ threadId: `sort-test-3-${timestamp}`,
538
+ phoneNumber: "+15555550005",
539
+ enduserPhoneNumber: "+15555550006",
540
+ timestamp: new Date(sortTestBaseTime - 2000), // middle timestamp
541
+ outboundTimestamp: new Date(sortTestBaseTime - 2000), // middle outboundTimestamp
542
+ })
543
+
544
+ // Test default sort (should be by timestamp descending)
545
+ const defaultSortResult = await sdk.api.inbox_threads.load_threads({
546
+ ids: [sortThread1.id, sortThread2.id, sortThread3.id]
547
+ })
548
+ assert(defaultSortResult.threads.length === 3, 'Should return 3 sort test threads')
549
+ assert(defaultSortResult.threads[0].id === sortThread2.id, 'Default sort: newest timestamp should be first')
550
+ assert(defaultSortResult.threads[1].id === sortThread3.id, 'Default sort: middle timestamp should be second')
551
+ assert(defaultSortResult.threads[2].id === sortThread1.id, 'Default sort: oldest timestamp should be last')
552
+ console.log("✅ sortBy default behavior test passed")
553
+
554
+ // Test 25: sortBy='timestamp' explicit
555
+ console.log("Testing sortBy='timestamp' explicit...")
556
+ const timestampSortResult = await sdk.api.inbox_threads.load_threads({
557
+ ids: [sortThread1.id, sortThread2.id, sortThread3.id],
558
+ sortBy: 'timestamp'
559
+ })
560
+ assert(timestampSortResult.threads[0].id === sortThread2.id, 'Explicit timestamp sort: newest should be first')
561
+ assert(timestampSortResult.threads[1].id === sortThread3.id, 'Explicit timestamp sort: middle should be second')
562
+ assert(timestampSortResult.threads[2].id === sortThread1.id, 'Explicit timestamp sort: oldest should be last')
563
+ console.log("✅ sortBy='timestamp' test passed")
564
+
565
+ // Test 26: sortBy='outboundTimestamp'
566
+ console.log("Testing sortBy='outboundTimestamp'...")
567
+ const outboundSortResult = await sdk.api.inbox_threads.load_threads({
568
+ ids: [sortThread1.id, sortThread2.id, sortThread3.id],
569
+ sortBy: 'outboundTimestamp'
570
+ })
571
+ assert(outboundSortResult.threads[0].id === sortThread1.id, 'OutboundTimestamp sort: newest outbound should be first')
572
+ assert(outboundSortResult.threads[1].id === sortThread3.id, 'OutboundTimestamp sort: middle outbound should be second')
573
+ assert(outboundSortResult.threads[2].id === sortThread2.id, 'OutboundTimestamp sort: oldest outbound should be last')
574
+ console.log("✅ sortBy='outboundTimestamp' test passed")
575
+
576
+ // Cleanup sort test threads
577
+ await Promise.all([
578
+ sdk.api.inbox_threads.deleteOne(sortThread1.id),
579
+ sdk.api.inbox_threads.deleteOne(sortThread2.id),
580
+ sdk.api.inbox_threads.deleteOne(sortThread3.id),
581
+ ])
582
+
583
+ // ========== InboxStatus Preservation Tests ==========
584
+ // These tests verify that outbound messages do NOT reset inboxStatus
585
+
586
+ // Test 27: Outbound SMS should NOT reset inboxStatus
587
+ console.log("Testing outbound SMS should NOT reset inboxStatus...")
588
+
589
+ const statusTestSMS1 = await sdk.api.sms_messages.createOne({
590
+ message: "Inbound test message for status test",
591
+ enduserId: testEnduser.id,
592
+ inbound: true,
593
+ phoneNumber: "+15555559999",
594
+ enduserPhoneNumber: "+15555559876",
595
+ logOnly: true,
596
+ })
597
+
598
+ // Build threads using reset_threads + build_threads pattern
599
+ const statusTestFrom = new Date(Date.now() - 60000)
600
+ await sdk.api.inbox_threads.reset_threads()
601
+ await sdk.api.inbox_threads.build_threads({ from: statusTestFrom, to: new Date() })
602
+
603
+ const statusTestThreads = await sdk.api.inbox_threads.load_threads({})
604
+ const statusTestThread = statusTestThreads.threads.find(t =>
605
+ t.type === 'SMS' && t.enduserIds.includes(testEnduser.id) && t.phoneNumber === "+15555559999"
606
+ )
607
+ assert(!!statusTestThread, "Status test SMS thread should be created")
608
+ assert(statusTestThread!.inboxStatus === 'New', `Initial status should be 'New', got '${statusTestThread!.inboxStatus}'`)
609
+
610
+ // Update thread status to "Resolved"
611
+ await sdk.api.inbox_threads.updateOne(statusTestThread!.id, { inboxStatus: "Resolved" })
612
+
613
+ // Create outbound SMS (should NOT reset status)
614
+ const statusTestSMS2 = await sdk.api.sms_messages.createOne({
615
+ message: "Outbound reply - should not reset status",
616
+ enduserId: testEnduser.id,
617
+ inbound: false,
618
+ phoneNumber: "+15555559999",
619
+ enduserPhoneNumber: "+15555559876",
620
+ logOnly: true,
621
+ })
622
+
623
+ // Rebuild threads - status should remain "Resolved"
624
+ await sdk.api.inbox_threads.build_threads({ from: statusTestFrom, to: new Date() })
625
+
626
+ const threadAfterOutbound = (await sdk.api.inbox_threads.load_threads({ ids: [statusTestThread!.id] })).threads[0]
627
+ assert(threadAfterOutbound.inboxStatus === 'Resolved', `Status should remain 'Resolved' after outbound message, got '${threadAfterOutbound.inboxStatus}'`)
628
+ assert(!!threadAfterOutbound.outboundTimestamp, "outboundTimestamp should be set after outbound message")
629
+
630
+ console.log("✅ Outbound SMS does NOT reset inboxStatus test passed")
631
+
632
+ // Test 28: New inbound SMS SHOULD update inboxStatus
633
+ console.log("Testing new inbound SMS SHOULD update inboxStatus...")
634
+
635
+ // Wait to ensure ObjectId timestamps are in different seconds (MongoDB ObjectIds have second-level precision)
636
+ await new Promise(resolve => setTimeout(resolve, 1100))
637
+
638
+ const statusTestSMS3 = await sdk.api.sms_messages.createOne({
639
+ message: "New inbound - should update status",
640
+ enduserId: testEnduser.id,
641
+ inbound: true,
642
+ phoneNumber: "+15555559999",
643
+ enduserPhoneNumber: "+15555559876",
644
+ inboxStatus: "New",
645
+ logOnly: true,
646
+ })
647
+
648
+ // Rebuild threads - status SHOULD be updated from new inbound
649
+ await sdk.api.inbox_threads.build_threads({ from: statusTestFrom, to: new Date() })
650
+
651
+ const threadAfterNewInbound = (await sdk.api.inbox_threads.load_threads({ ids: [statusTestThread!.id] })).threads[0]
652
+ assert(threadAfterNewInbound.inboxStatus === 'New', `Status SHOULD be 'New' after new inbound message, got '${threadAfterNewInbound.inboxStatus}'`)
653
+
654
+ console.log("✅ New inbound SMS DOES update inboxStatus test passed")
655
+
656
+ // Cleanup status preservation test resources
657
+ await Promise.all([
658
+ sdk.api.sms_messages.deleteOne(statusTestSMS1.id),
659
+ sdk.api.sms_messages.deleteOne(statusTestSMS2.id),
660
+ sdk.api.sms_messages.deleteOne(statusTestSMS3.id),
661
+ sdk.api.inbox_threads.deleteOne(statusTestThread!.id),
662
+ ])
663
+
506
664
  console.log("🎉 All InboxThread assignment update tests passed!")
507
665
 
508
666
  } finally {
@@ -77,6 +77,7 @@ import { custom_aggregation_tests } from "./api_tests/custom_aggregation.test";
77
77
  import { bulk_assignment_tests } from "./api_tests/bulk_assignment.test";
78
78
  import { managed_content_enduser_access_tests } from "./api_tests/managed_content_enduser_access.test";
79
79
  import { auto_merge_form_submission_tests } from "./api_tests/auto_merge_form_submission.test";
80
+ import { database_cascade_delete_tests } from "./api_tests/database_cascade_delete.test";
80
81
 
81
82
  const UniquenessViolationMessage = 'Uniqueness Violation'
82
83
 
@@ -5835,6 +5836,9 @@ export const databases_tests = async () => {
5835
5836
  sdk.api.databases.deleteOne(database.id),
5836
5837
  sdk.api.databases.deleteOne(databaseNoRead.id),
5837
5838
  ])
5839
+
5840
+ // Run cascade delete tests
5841
+ await database_cascade_delete_tests({ sdk, sdkNonAdmin })
5838
5842
  }
5839
5843
 
5840
5844
  export const filter_by_date_tests = async () => {
@@ -12364,6 +12368,25 @@ const replace_enduser_template_values_tests = async () => {
12364
12368
  const d = Date.now()
12365
12369
  assert(replace_enduser_template_values(d as any, enduser) === d as any, 'fail non-string', 'non-string')
12366
12370
 
12371
+ // Test height/weight subfields with values present
12372
+ const enduserWithVitals = await sdk.api.endusers.createOne({
12373
+ fname: "Vitals",
12374
+ height: { value: 72, unit: 'inches' },
12375
+ weight: { value: 180, unit: 'lbs' }
12376
+ })
12377
+
12378
+ assert(replace_enduser_template_values('{{enduser.height.value}}', enduserWithVitals) === '72', 'fail height.value', 'height.value')
12379
+ assert(replace_enduser_template_values('{{enduser.height.unit}}', enduserWithVitals) === 'inches', 'fail height.unit', 'height.unit')
12380
+ assert(replace_enduser_template_values('{{enduser.weight.value}}', enduserWithVitals) === '180', 'fail weight.value', 'weight.value')
12381
+ assert(replace_enduser_template_values('{{enduser.weight.unit}}', enduserWithVitals) === 'lbs', 'fail weight.unit', 'weight.unit')
12382
+
12383
+ // Test undefined safeguards - enduser without height/weight should return empty string
12384
+ assert(replace_enduser_template_values('{{enduser.height.value}}', enduser) === '', 'fail undefined height.value', 'undefined height.value')
12385
+ assert(replace_enduser_template_values('{{enduser.height.unit}}', enduser) === '', 'fail undefined height.unit', 'undefined height.unit')
12386
+ assert(replace_enduser_template_values('{{enduser.weight.value}}', enduser) === '', 'fail undefined weight.value', 'undefined weight.value')
12387
+ assert(replace_enduser_template_values('{{enduser.weight.unit}}', enduser) === '', 'fail undefined weight.unit', 'undefined weight.unit')
12388
+
12389
+ await sdk.api.endusers.deleteOne(enduserWithVitals.id)
12367
12390
  await sdk.api.endusers.deleteOne(enduser.id)
12368
12391
  }
12369
12392
 
Binary file