@tellescope/sdk 1.242.9 → 1.243.0

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 (64) hide show
  1. package/lib/cjs/enduser.d.ts +20 -0
  2. package/lib/cjs/enduser.d.ts.map +1 -1
  3. package/lib/cjs/sdk.d.ts +45 -0
  4. package/lib/cjs/sdk.d.ts.map +1 -1
  5. package/lib/cjs/sdk.js +2 -0
  6. package/lib/cjs/sdk.js.map +1 -1
  7. package/lib/cjs/tests/api_tests/concurrent_build_threads.test.d.ts +6 -0
  8. package/lib/cjs/tests/api_tests/concurrent_build_threads.test.d.ts.map +1 -0
  9. package/lib/cjs/tests/api_tests/concurrent_build_threads.test.js +169 -0
  10. package/lib/cjs/tests/api_tests/concurrent_build_threads.test.js.map +1 -0
  11. package/lib/cjs/tests/api_tests/custom_aggregation.test.d.ts.map +1 -1
  12. package/lib/cjs/tests/api_tests/custom_aggregation.test.js +109 -7
  13. package/lib/cjs/tests/api_tests/custom_aggregation.test.js.map +1 -1
  14. package/lib/cjs/tests/api_tests/custom_dashboards.test.d.ts +6 -0
  15. package/lib/cjs/tests/api_tests/custom_dashboards.test.d.ts.map +1 -0
  16. package/lib/cjs/tests/api_tests/custom_dashboards.test.js +304 -0
  17. package/lib/cjs/tests/api_tests/custom_dashboards.test.js.map +1 -0
  18. package/lib/cjs/tests/api_tests/inbox_thread_assignment_updates.test.d.ts.map +1 -1
  19. package/lib/cjs/tests/api_tests/inbox_thread_assignment_updates.test.js +655 -139
  20. package/lib/cjs/tests/api_tests/inbox_thread_assignment_updates.test.js.map +1 -1
  21. package/lib/cjs/tests/api_tests/no_access_permission_checks.test.d.ts +20 -0
  22. package/lib/cjs/tests/api_tests/no_access_permission_checks.test.d.ts.map +1 -0
  23. package/lib/cjs/tests/api_tests/no_access_permission_checks.test.js +481 -0
  24. package/lib/cjs/tests/api_tests/no_access_permission_checks.test.js.map +1 -0
  25. package/lib/cjs/tests/tests.d.ts.map +1 -1
  26. package/lib/cjs/tests/tests.js +125 -112
  27. package/lib/cjs/tests/tests.js.map +1 -1
  28. package/lib/esm/enduser.d.ts +20 -0
  29. package/lib/esm/enduser.d.ts.map +1 -1
  30. package/lib/esm/sdk.d.ts +45 -0
  31. package/lib/esm/sdk.d.ts.map +1 -1
  32. package/lib/esm/sdk.js +2 -0
  33. package/lib/esm/sdk.js.map +1 -1
  34. package/lib/esm/tests/api_tests/concurrent_build_threads.test.d.ts +6 -0
  35. package/lib/esm/tests/api_tests/concurrent_build_threads.test.d.ts.map +1 -0
  36. package/lib/esm/tests/api_tests/concurrent_build_threads.test.js +165 -0
  37. package/lib/esm/tests/api_tests/concurrent_build_threads.test.js.map +1 -0
  38. package/lib/esm/tests/api_tests/custom_aggregation.test.d.ts.map +1 -1
  39. package/lib/esm/tests/api_tests/custom_aggregation.test.js +110 -8
  40. package/lib/esm/tests/api_tests/custom_aggregation.test.js.map +1 -1
  41. package/lib/esm/tests/api_tests/custom_dashboards.test.d.ts +6 -0
  42. package/lib/esm/tests/api_tests/custom_dashboards.test.d.ts.map +1 -0
  43. package/lib/esm/tests/api_tests/custom_dashboards.test.js +300 -0
  44. package/lib/esm/tests/api_tests/custom_dashboards.test.js.map +1 -0
  45. package/lib/esm/tests/api_tests/inbox_thread_assignment_updates.test.d.ts.map +1 -1
  46. package/lib/esm/tests/api_tests/inbox_thread_assignment_updates.test.js +655 -139
  47. package/lib/esm/tests/api_tests/inbox_thread_assignment_updates.test.js.map +1 -1
  48. package/lib/esm/tests/api_tests/no_access_permission_checks.test.d.ts +20 -0
  49. package/lib/esm/tests/api_tests/no_access_permission_checks.test.d.ts.map +1 -0
  50. package/lib/esm/tests/api_tests/no_access_permission_checks.test.js +477 -0
  51. package/lib/esm/tests/api_tests/no_access_permission_checks.test.js.map +1 -0
  52. package/lib/esm/tests/tests.d.ts.map +1 -1
  53. package/lib/esm/tests/tests.js +125 -112
  54. package/lib/esm/tests/tests.js.map +1 -1
  55. package/lib/tsconfig.tsbuildinfo +1 -1
  56. package/package.json +10 -10
  57. package/src/sdk.ts +9 -1
  58. package/src/tests/api_tests/concurrent_build_threads.test.ts +103 -0
  59. package/src/tests/api_tests/custom_aggregation.test.ts +74 -0
  60. package/src/tests/api_tests/custom_dashboards.test.ts +258 -0
  61. package/src/tests/api_tests/inbox_thread_assignment_updates.test.ts +431 -1
  62. package/src/tests/api_tests/no_access_permission_checks.test.ts +365 -0
  63. package/src/tests/tests.ts +8 -1
  64. package/test_generated.pdf +0 -0
@@ -825,7 +825,437 @@ export const inbox_thread_assignment_updates_tests = async ({ sdk, sdkNonAdmin }
825
825
  sdk.api.inbox_threads.deleteOne(readByEmailTestThread!.id),
826
826
  ])
827
827
 
828
- console.log("🎉 All InboxThread assignment update tests passed!")
828
+ // Test 33: Outbound SMS with NARROW time range should NOT reset readBy
829
+ // This tests the production scenario where incremental builds only include recent messages
830
+ console.log("Testing outbound SMS with NARROW time range should NOT reset readBy...")
831
+
832
+ const narrowRangeTestSMS1 = await sdk.api.sms_messages.createOne({
833
+ message: "Inbound for narrow range test",
834
+ enduserId: testEnduser.id,
835
+ inbound: true,
836
+ phoneNumber: "+15555559999",
837
+ enduserPhoneNumber: "+15555559998",
838
+ logOnly: true,
839
+ })
840
+
841
+ // Build threads with wide range (includes inbound)
842
+ await sdk.api.inbox_threads.reset_threads()
843
+ await sdk.api.inbox_threads.build_threads({ from: new Date(Date.now() - 60000), to: new Date() })
844
+
845
+ const narrowRangeThreads = await sdk.api.inbox_threads.load_threads({})
846
+ const narrowRangeThread = narrowRangeThreads.threads.find(t =>
847
+ t.type === 'SMS' && t.phoneNumber === "+15555559999"
848
+ )
849
+ assert(!!narrowRangeThread, "Narrow range test thread should exist")
850
+
851
+ // Mark thread as read
852
+ await sdk.api.inbox_threads.updateOne(narrowRangeThread!.id, {
853
+ readBy: { [sdk.userInfo.id]: new Date() }
854
+ })
855
+
856
+ // Wait and capture a timestamp AFTER the inbound was created
857
+ await new Promise(resolve => setTimeout(resolve, 100))
858
+ const narrowRangeFrom = new Date()
859
+ await new Promise(resolve => setTimeout(resolve, 100))
860
+
861
+ // Create outbound message (after narrowRangeFrom)
862
+ const narrowRangeTestSMS2 = await sdk.api.sms_messages.createOne({
863
+ message: "Outbound for narrow range test",
864
+ enduserId: testEnduser.id,
865
+ inbound: false,
866
+ phoneNumber: "+15555559999",
867
+ enduserPhoneNumber: "+15555559998",
868
+ logOnly: true,
869
+ })
870
+
871
+ // Rebuild with NARROW range that excludes the original inbound
872
+ await sdk.api.inbox_threads.build_threads({ from: narrowRangeFrom, to: new Date() })
873
+
874
+ // readBy should still be preserved (not reset)
875
+ const threadAfterNarrowBuild = (await sdk.api.inbox_threads.load_threads({ ids: [narrowRangeThread!.id] })).threads[0]
876
+ assert(
877
+ !!threadAfterNarrowBuild.readBy?.[sdk.userInfo.id],
878
+ `readBy should remain set after outbound-only incremental build, got ${JSON.stringify(threadAfterNarrowBuild.readBy)}`
879
+ )
880
+
881
+ console.log("Outbound SMS with narrow time range does NOT reset readBy test passed")
882
+
883
+ // Cleanup narrow range test
884
+ await Promise.all([
885
+ sdk.api.sms_messages.deleteOne(narrowRangeTestSMS1.id),
886
+ sdk.api.sms_messages.deleteOne(narrowRangeTestSMS2.id),
887
+ sdk.api.inbox_threads.deleteOne(narrowRangeThread!.id),
888
+ ])
889
+
890
+ // Test 34: Outbound Email with NARROW time range should NOT reset readBy
891
+ // This tests the production scenario where incremental builds only include recent messages
892
+ console.log("Testing outbound Email with NARROW time range should NOT reset readBy...")
893
+
894
+ const narrowRangeTestEmail1 = await sdk.api.emails.createOne({
895
+ subject: "Inbound email for narrow range test",
896
+ textContent: "Test inbound email content",
897
+ enduserId: testEnduser.id,
898
+ inbound: true,
899
+ logOnly: true,
900
+ })
901
+
902
+ // Build threads with wide range (includes inbound)
903
+ await sdk.api.inbox_threads.reset_threads()
904
+ await sdk.api.inbox_threads.build_threads({ from: new Date(Date.now() - 60000), to: new Date() })
905
+
906
+ const narrowRangeEmailThreads = await sdk.api.inbox_threads.load_threads({})
907
+ const narrowRangeEmailThread = narrowRangeEmailThreads.threads.find(t =>
908
+ t.type === 'Email' && t.enduserIds.includes(testEnduser.id) && t.title === "Inbound email for narrow range test"
909
+ )
910
+ assert(!!narrowRangeEmailThread, "Narrow range test Email thread should exist")
911
+
912
+ // Mark thread as read
913
+ await sdk.api.inbox_threads.updateOne(narrowRangeEmailThread!.id, {
914
+ readBy: { [sdk.userInfo.id]: new Date() }
915
+ })
916
+
917
+ // Wait and capture a timestamp AFTER the inbound was created
918
+ await new Promise(resolve => setTimeout(resolve, 100))
919
+ const narrowRangeEmailFrom = new Date()
920
+ await new Promise(resolve => setTimeout(resolve, 100))
921
+
922
+ // Create outbound email (after narrowRangeEmailFrom)
923
+ const narrowRangeTestEmail2 = await sdk.api.emails.createOne({
924
+ subject: "Re: Inbound email for narrow range test",
925
+ textContent: "Outbound reply for narrow range test",
926
+ enduserId: testEnduser.id,
927
+ inbound: false,
928
+ logOnly: true,
929
+ })
930
+
931
+ // Rebuild with NARROW range that excludes the original inbound
932
+ await sdk.api.inbox_threads.build_threads({ from: narrowRangeEmailFrom, to: new Date() })
933
+
934
+ // readBy should still be preserved (not reset)
935
+ const emailThreadAfterNarrowBuild = (await sdk.api.inbox_threads.load_threads({ ids: [narrowRangeEmailThread!.id] })).threads[0]
936
+ assert(
937
+ !!emailThreadAfterNarrowBuild.readBy?.[sdk.userInfo.id],
938
+ `Email readBy should remain set after outbound-only incremental build, got ${JSON.stringify(emailThreadAfterNarrowBuild.readBy)}`
939
+ )
940
+
941
+ console.log("Outbound Email with narrow time range does NOT reset readBy test passed")
942
+
943
+ // Cleanup narrow range email test
944
+ await Promise.all([
945
+ sdk.api.emails.deleteOne(narrowRangeTestEmail1.id),
946
+ sdk.api.emails.deleteOne(narrowRangeTestEmail2.id),
947
+ sdk.api.inbox_threads.deleteOne(narrowRangeEmailThread!.id),
948
+ ])
949
+
950
+ // ========== Zendesk Thread Tests ==========
951
+ // Test 35: Basic Zendesk thread building
952
+ console.log("Testing Zendesk thread building...")
953
+
954
+ const zendeskTicketThread = await sdk.api.ticket_threads.createOne({
955
+ enduserId: testEnduser.id,
956
+ subject: "Test Zendesk Thread",
957
+ })
958
+
959
+ const zendeskTicketComment = await sdk.api.ticket_thread_comments.createOne({
960
+ ticketThreadId: zendeskTicketThread.id,
961
+ enduserId: testEnduser.id,
962
+ plaintext: "Test ticket comment",
963
+ html: "<p>Test ticket comment</p>",
964
+ public: true,
965
+ inbound: true,
966
+ })
967
+
968
+ await sdk.api.inbox_threads.reset_threads()
969
+ await sdk.api.inbox_threads.build_threads({ from: new Date(Date.now() - 60000), to: new Date() })
970
+
971
+ const zendeskThreads = await sdk.api.inbox_threads.load_threads({ mdbFilter: { type: 'Zendesk' } })
972
+ const zendeskThread = zendeskThreads.threads.find(t => t.threadId === zendeskTicketThread.id)
973
+
974
+ assert(!!zendeskThread, "Zendesk thread should exist")
975
+ assert(zendeskThread!.type === 'Zendesk', "Type should be Zendesk")
976
+ assert(zendeskThread!.title === "Test Zendesk Thread", "Title should match subject")
977
+ assert(zendeskThread!.preview.includes("Test ticket comment"), "Preview should match comment")
978
+ assert(zendeskThread!.enduserIds.includes(testEnduser.id), "Should have enduser")
979
+
980
+ console.log("Basic Zendesk thread building test passed")
981
+
982
+ // Test 36: Zendesk assignment updates
983
+ console.log("Testing Zendesk assignment updates...")
984
+
985
+ // Update comment assignment
986
+ await sdk.api.ticket_thread_comments.updateOne(zendeskTicketComment.id, {
987
+ assignedTo: [testUser.id]
988
+ }, { replaceObjectFields: true })
989
+
990
+ // Wait for side effects (the side effect should update the inbox thread directly)
991
+ await new Promise(resolve => setTimeout(resolve, 1500))
992
+
993
+ // Verify - NO rebuild needed, side effect should have handled it
994
+ const updatedZendeskThreads = await sdk.api.inbox_threads.load_threads({ ids: [zendeskThread!.id] })
995
+ const updatedZendeskThread = updatedZendeskThreads.threads[0]
996
+
997
+ assert(
998
+ JSON.stringify(updatedZendeskThread.assignedTo) === JSON.stringify([testUser.id]),
999
+ `Zendesk thread assignment should be updated from comment, got ${JSON.stringify(updatedZendeskThread.assignedTo)}`
1000
+ )
1001
+
1002
+ console.log("Zendesk assignment update test passed")
1003
+
1004
+ // Test 37: Zendesk outbound comment should NOT reset readBy
1005
+ console.log("Testing Zendesk outbound should NOT reset readBy...")
1006
+
1007
+ // Mark thread as read
1008
+ await sdk.api.inbox_threads.updateOne(zendeskThread!.id, {
1009
+ readBy: { [sdk.userInfo.id]: new Date() }
1010
+ })
1011
+
1012
+ // Verify marked as read
1013
+ const zendeskThreadAfterRead = (await sdk.api.inbox_threads.load_threads({ ids: [zendeskThread!.id] })).threads[0]
1014
+ assert(!!zendeskThreadAfterRead.readBy?.[sdk.userInfo.id], "Zendesk thread should be marked as read")
1015
+
1016
+ // Create outbound comment
1017
+ const zendeskOutboundComment = await sdk.api.ticket_thread_comments.createOne({
1018
+ ticketThreadId: zendeskTicketThread.id,
1019
+ enduserId: testEnduser.id,
1020
+ plaintext: "Outbound staff reply",
1021
+ html: "<p>Outbound staff reply</p>",
1022
+ public: true,
1023
+ inbound: false,
1024
+ userId: sdk.userInfo.id,
1025
+ })
1026
+
1027
+ // Rebuild threads
1028
+ await sdk.api.inbox_threads.build_threads({ from: new Date(Date.now() - 60000), to: new Date() })
1029
+
1030
+ // readBy should remain set
1031
+ const zendeskThreadAfterOutbound = (await sdk.api.inbox_threads.load_threads({ ids: [zendeskThread!.id] })).threads[0]
1032
+ assert(
1033
+ !!zendeskThreadAfterOutbound.readBy?.[sdk.userInfo.id],
1034
+ `readBy should remain set after outbound Zendesk comment, got ${JSON.stringify(zendeskThreadAfterOutbound.readBy)}`
1035
+ )
1036
+ assert(!!zendeskThreadAfterOutbound.outboundTimestamp, "outboundTimestamp should be set")
1037
+ assert(!!zendeskThreadAfterOutbound.outboundPreview?.includes("Outbound staff reply"), "outboundPreview should match")
1038
+
1039
+ console.log("Zendesk outbound does NOT reset readBy test passed")
1040
+
1041
+ // Test 38: New Zendesk inbound SHOULD clear readBy
1042
+ console.log("Testing new Zendesk inbound SHOULD clear readBy...")
1043
+
1044
+ await new Promise(resolve => setTimeout(resolve, 1100))
1045
+
1046
+ const zendeskNewInboundComment = await sdk.api.ticket_thread_comments.createOne({
1047
+ ticketThreadId: zendeskTicketThread.id,
1048
+ enduserId: testEnduser.id,
1049
+ plaintext: "New inbound from customer",
1050
+ html: "<p>New inbound from customer</p>",
1051
+ public: true,
1052
+ inbound: true,
1053
+ })
1054
+
1055
+ await sdk.api.inbox_threads.build_threads({ from: new Date(Date.now() - 60000), to: new Date() })
1056
+
1057
+ const zendeskThreadAfterNewInbound = (await sdk.api.inbox_threads.load_threads({ ids: [zendeskThread!.id] })).threads[0]
1058
+ assert(
1059
+ !zendeskThreadAfterNewInbound.readBy?.[sdk.userInfo.id],
1060
+ `readBy SHOULD be cleared after new inbound Zendesk comment, got ${JSON.stringify(zendeskThreadAfterNewInbound.readBy)}`
1061
+ )
1062
+
1063
+ console.log("New Zendesk inbound DOES clear readBy test passed")
1064
+
1065
+ // Test 39: Zendesk narrow range build preserves readBy
1066
+ console.log("Testing Zendesk narrow range build preserves readBy...")
1067
+
1068
+ // Reset and build fresh
1069
+ await sdk.api.inbox_threads.reset_threads()
1070
+ await sdk.api.inbox_threads.build_threads({ from: new Date(Date.now() - 60000), to: new Date() })
1071
+
1072
+ // Get fresh thread
1073
+ const freshZendeskThreads = await sdk.api.inbox_threads.load_threads({ mdbFilter: { type: 'Zendesk', threadId: zendeskTicketThread.id } })
1074
+ const freshZendeskThread = freshZendeskThreads.threads.find(t => t.threadId === zendeskTicketThread.id)
1075
+ assert(!!freshZendeskThread, "Fresh Zendesk thread should exist")
1076
+
1077
+ // Mark as read
1078
+ await sdk.api.inbox_threads.updateOne(freshZendeskThread!.id, {
1079
+ readBy: { [sdk.userInfo.id]: new Date() }
1080
+ })
1081
+
1082
+ // Wait and capture narrow range start
1083
+ await new Promise(resolve => setTimeout(resolve, 100))
1084
+ const zendeskNarrowRangeFrom = new Date()
1085
+ await new Promise(resolve => setTimeout(resolve, 100))
1086
+
1087
+ // Create outbound comment after narrowRangeFrom
1088
+ const zendeskNarrowRangeOutbound = await sdk.api.ticket_thread_comments.createOne({
1089
+ ticketThreadId: zendeskTicketThread.id,
1090
+ enduserId: testEnduser.id,
1091
+ plaintext: "Outbound in narrow range",
1092
+ html: "<p>Outbound in narrow range</p>",
1093
+ public: true,
1094
+ inbound: false,
1095
+ userId: sdk.userInfo.id,
1096
+ })
1097
+
1098
+ // Rebuild with narrow range (excludes original inbound)
1099
+ await sdk.api.inbox_threads.build_threads({ from: zendeskNarrowRangeFrom, to: new Date() })
1100
+
1101
+ // readBy should still be preserved
1102
+ const zendeskThreadAfterNarrowBuild = (await sdk.api.inbox_threads.load_threads({ ids: [freshZendeskThread!.id] })).threads[0]
1103
+ assert(
1104
+ !!zendeskThreadAfterNarrowBuild.readBy?.[sdk.userInfo.id],
1105
+ `readBy should remain set after outbound-only Zendesk incremental build, got ${JSON.stringify(zendeskThreadAfterNarrowBuild.readBy)}`
1106
+ )
1107
+
1108
+ console.log("Zendesk narrow range build preserves readBy test passed")
1109
+
1110
+ // Test 40: Zendesk channel filtering
1111
+ console.log("Testing Zendesk channel filtering...")
1112
+
1113
+ // Filter by Zendesk type
1114
+ const filteredZendeskThreads = await sdk.api.inbox_threads.load_threads({
1115
+ mdbFilter: { type: 'Zendesk' }
1116
+ })
1117
+
1118
+ assert(filteredZendeskThreads.threads.length >= 1, "Should find Zendesk threads")
1119
+ assert(filteredZendeskThreads.threads.every(t => t.type === 'Zendesk'), "All filtered threads should be Zendesk type")
1120
+
1121
+ // Filter by multiple types including Zendesk
1122
+ const multiTypeZendeskThreads = await sdk.api.inbox_threads.load_threads({
1123
+ mdbFilter: { type: { $in: ['Zendesk', 'SMS'] } }
1124
+ })
1125
+
1126
+ assert(
1127
+ multiTypeZendeskThreads.threads.every(t => t.type === 'Zendesk' || t.type === 'SMS'),
1128
+ "Multi-type filter should work with Zendesk"
1129
+ )
1130
+
1131
+ console.log("Zendesk channel filtering test passed")
1132
+
1133
+ // Test 41: Zendesk thread without comments is skipped
1134
+ console.log("Testing Zendesk thread without comments is skipped...")
1135
+
1136
+ const emptyZendeskTicketThread = await sdk.api.ticket_threads.createOne({
1137
+ enduserId: testEnduser.id,
1138
+ subject: "Empty Zendesk Thread",
1139
+ })
1140
+
1141
+ await sdk.api.inbox_threads.reset_threads()
1142
+ await sdk.api.inbox_threads.build_threads({ from: new Date(Date.now() - 60000), to: new Date() })
1143
+
1144
+ const allZendeskThreadsAfterEmpty = await sdk.api.inbox_threads.load_threads({ mdbFilter: { type: 'Zendesk' } })
1145
+ const emptyZendeskThread = allZendeskThreadsAfterEmpty.threads.find(t => t.threadId === emptyZendeskTicketThread.id)
1146
+
1147
+ assert(!emptyZendeskThread, "Zendesk thread without comments should NOT be built")
1148
+
1149
+ console.log("Zendesk thread without comments is skipped test passed")
1150
+
1151
+ // Test 42: Zendesk access control - verify access is blocked when user lacks ticket_threads permission
1152
+ console.log("Testing Zendesk access control (blocking)...")
1153
+
1154
+ // Create a new Zendesk thread for testing
1155
+ const accessControlTicketThread = await sdk.api.ticket_threads.createOne({
1156
+ enduserId: testEnduser.id,
1157
+ subject: "Access Control Test Thread",
1158
+ })
1159
+
1160
+ const accessControlComment = await sdk.api.ticket_thread_comments.createOne({
1161
+ ticketThreadId: accessControlTicketThread.id,
1162
+ enduserId: testEnduser.id,
1163
+ plaintext: "Access control test comment",
1164
+ html: "<p>Access control test comment</p>",
1165
+ public: true,
1166
+ inbound: true,
1167
+ })
1168
+
1169
+ await sdk.api.inbox_threads.reset_threads()
1170
+ await sdk.api.inbox_threads.build_threads({ from: new Date(Date.now() - 60000), to: new Date() })
1171
+
1172
+ // Verify admin can see the Zendesk thread
1173
+ const adminZendeskThreads = await sdk.api.inbox_threads.load_threads({
1174
+ mdbFilter: { type: 'Zendesk' }
1175
+ })
1176
+ assert(adminZendeskThreads.threads.length >= 1, "Admin should see Zendesk threads")
1177
+ const accessControlThread = adminZendeskThreads.threads.find(t => t.threadId === accessControlTicketThread.id)
1178
+ assert(!!accessControlThread, "Admin should see the access control test thread")
1179
+
1180
+ // Create a role with NO ticket_threads access
1181
+ const noTicketThreadsRole = await sdk.api.role_based_access_permissions.createOne({
1182
+ role: 'No Ticket Threads Access',
1183
+ permissions: {
1184
+ ticket_threads: { read: null, create: null, update: null, delete: null },
1185
+ // Give access to other inbox types so we can verify selective blocking
1186
+ emails: { read: 'Default', create: 'Default', update: 'Default', delete: 'Default' },
1187
+ sms_messages: { read: 'Default', create: 'Default', update: 'Default', delete: 'Default' },
1188
+ },
1189
+ })
1190
+
1191
+ // Create a test user for access control testing
1192
+ const accessControlTestEmail = 'zendesk.access.control.test@tellescope.com'
1193
+ const accessControlTestUser = (
1194
+ await sdk.api.users.getOne({ email: accessControlTestEmail }).catch(() => null)
1195
+ ) || (
1196
+ await sdk.api.users.createOne({ email: accessControlTestEmail, notificationEmailsDisabled: true, verifiedEmail: true })
1197
+ )
1198
+
1199
+ // Assign the restricted role to the test user
1200
+ await sdk.api.users.updateOne(accessControlTestUser.id, { roles: [noTicketThreadsRole.role] }, { replaceObjectFields: true })
1201
+ await new Promise(resolve => setTimeout(resolve, 2000)) // Wait for role change
1202
+
1203
+ // Create SDK session for the restricted user
1204
+ const sdkNoTicketAccess = new Session({
1205
+ host,
1206
+ authToken: (await sdk.api.users.generate_auth_token({ id: accessControlTestUser.id })).authToken,
1207
+ })
1208
+
1209
+ // User WITHOUT ticket_threads access should NOT see Zendesk threads
1210
+ const restrictedUserZendeskThreads = await sdkNoTicketAccess.api.inbox_threads.load_threads({
1211
+ mdbFilter: { type: 'Zendesk' }
1212
+ })
1213
+
1214
+ assert(
1215
+ restrictedUserZendeskThreads.threads.length === 0,
1216
+ `User without ticket_threads access should NOT see Zendesk threads, but found ${restrictedUserZendeskThreads.threads.length}`
1217
+ )
1218
+ console.log("Verified: User without ticket_threads access cannot see Zendesk threads")
1219
+
1220
+ // Verify the same user CAN still see other thread types they have access to (if any exist)
1221
+ // This confirms the filter is selective, not a blanket block
1222
+ const restrictedUserAllThreads = await sdkNoTicketAccess.api.inbox_threads.load_threads({})
1223
+ const restrictedUserHasZendesk = restrictedUserAllThreads.threads.some(t => t.type === 'Zendesk')
1224
+ assert(
1225
+ !restrictedUserHasZendesk,
1226
+ "User without ticket_threads access should not see ANY Zendesk threads even in unfiltered query"
1227
+ )
1228
+ console.log("Verified: Zendesk threads are filtered from all queries for restricted user")
1229
+
1230
+ // Cleanup access control test resources
1231
+ await Promise.all([
1232
+ sdk.api.ticket_thread_comments.deleteOne(accessControlComment.id).catch(() => {}),
1233
+ sdk.api.ticket_threads.deleteOne(accessControlTicketThread.id).catch(() => {}),
1234
+ sdk.api.users.deleteOne(accessControlTestUser.id).catch(() => {}),
1235
+ sdk.api.role_based_access_permissions.deleteOne(noTicketThreadsRole.id).catch(() => {}),
1236
+ ])
1237
+
1238
+ console.log("Zendesk access control (blocking) test passed")
1239
+
1240
+ // Cleanup Zendesk test resources
1241
+ await Promise.all([
1242
+ sdk.api.ticket_thread_comments.deleteOne(zendeskTicketComment.id).catch(() => {}),
1243
+ sdk.api.ticket_thread_comments.deleteOne(zendeskOutboundComment.id).catch(() => {}),
1244
+ sdk.api.ticket_thread_comments.deleteOne(zendeskNewInboundComment.id).catch(() => {}),
1245
+ sdk.api.ticket_thread_comments.deleteOne(zendeskNarrowRangeOutbound.id).catch(() => {}),
1246
+ sdk.api.ticket_threads.deleteOne(zendeskTicketThread.id).catch(() => {}),
1247
+ sdk.api.ticket_threads.deleteOne(emptyZendeskTicketThread.id).catch(() => {}),
1248
+ ])
1249
+
1250
+ // Delete any Zendesk inbox threads
1251
+ const remainingZendeskThreads = await sdk.api.inbox_threads.load_threads({ mdbFilter: { type: 'Zendesk' } })
1252
+ await Promise.all(
1253
+ remainingZendeskThreads.threads.map(t => sdk.api.inbox_threads.deleteOne(t.id).catch(() => {}))
1254
+ )
1255
+
1256
+ console.log("All Zendesk thread tests passed!")
1257
+
1258
+ console.log("All InboxThread assignment update tests passed!")
829
1259
 
830
1260
  } finally {
831
1261
  // Cleanup