@tellescope/sdk 0.0.52 → 0.0.55

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.
@@ -9,6 +9,7 @@ import {
9
9
  UserDisplayInfo,
10
10
  } from "@tellescope/types-client"
11
11
  import {
12
+ AutomationAction,
12
13
  ModelName,
13
14
  } from "@tellescope/types-models"
14
15
 
@@ -46,6 +47,8 @@ import {
46
47
  } from "@tellescope/utilities"
47
48
 
48
49
 
50
+ const UniquenessViolationMessage = 'Uniqueness Violation'
51
+
49
52
  const host = process.env.TEST_URL || 'http://localhost:8080'
50
53
  const [email, password] = [process.env.TEST_EMAIL, process.env.TEST_PASSWORD]
51
54
  const [email2, password2] = [process.env.TEST_EMAIL_2, process.env.TEST_PASSWORD_2]
@@ -91,6 +94,7 @@ const passOnVoid = { shouldError: false, onResult: voidResult }
91
94
  // const isNull = (d: any) => d === null
92
95
 
93
96
  const setup_tests = async () => {
97
+ log_header("Setup")
94
98
  await async_test('test_online', sdk.test_online, { expectedResult: 'API V1 Online' })
95
99
  await async_test('test_authenticated', sdk.test_authenticated, { expectedResult: 'Authenticated!' })
96
100
 
@@ -119,6 +123,7 @@ const setup_tests = async () => {
119
123
  }
120
124
 
121
125
  const multi_tenant_tests = async () => {
126
+ log_header("Multi Tenant")
122
127
  const e2 = await sdkOther.api.endusers.createOne({ email: "hi@tellescope.com" }).catch(console.error)
123
128
  const e1 = await sdk.api.endusers.createOne({ email: "hi@tellescope.com" }).catch(console.error)
124
129
  if (!e1) process.exit()
@@ -165,6 +170,7 @@ const multi_tenant_tests = async () => {
165
170
  }
166
171
 
167
172
  const threadKeyTests = async () => {
173
+ log_header("threadKey")
168
174
  const enduser = await sdk.api.endusers.createOne({ email: 'threadkeytests@tellescope.com' })
169
175
  const [e1, e2, e3] = await Promise.all([
170
176
  sdk.api.engagement_events.createOne({ enduserId: enduser.id, type: 'Type 1', significance: 5 }),
@@ -192,19 +198,12 @@ const threadKeyTests = async () => {
192
198
  assert(es.find(e => e.id === e5.id) !== undefined, 'threadKey got duplicates', 'threadKey no duplicates (key 2, new)')
193
199
  assert(es.find(e => e.id === e6.id) !== undefined, 'threadKey got duplicates', 'threadKey no duplicates (key 3, new)')
194
200
 
195
- await Promise.all([ // cleanup
196
- sdk.api.endusers.deleteOne(enduser.id),
197
- sdk.api.engagement_events.deleteOne(e1.id),
198
- sdk.api.engagement_events.deleteOne(e2.id),
199
- sdk.api.engagement_events.deleteOne(e3.id),
200
- sdk.api.engagement_events.deleteOne(e4.id),
201
- sdk.api.engagement_events.deleteOne(e5.id),
202
- sdk.api.engagement_events.deleteOne(e6.id),
203
- ])
201
+ // cleanup
202
+ await sdk.api.endusers.deleteOne(enduser.id) // cleans up automatin events too
204
203
  }
205
204
 
206
205
  const badInputTests = async () => {
207
- log_header("Bad Input Tests")
206
+ log_header("Bad Input")
208
207
  await async_test(
209
208
  `_-prefixed fields are not allowed`,
210
209
  () => sdk.api.endusers.createOne({ email: 'failure@tellescope.com', fields: { "_notallowed": 'hello' } }),
@@ -218,6 +217,7 @@ const badInputTests = async () => {
218
217
  }
219
218
 
220
219
  const filterTests = async () => {
220
+ log_header("Filter Tests")
221
221
  const enduser = await sdk.api.endusers.createOne({ email: 'filtertests@tellescope.com', fname: 'test', fields: { field1: 'value1', field2: 'value2' } })
222
222
  const otherEnduser = await sdk.api.endusers.createOne({ email: 'other@tellescope.com' })
223
223
  await async_test(
@@ -287,6 +287,7 @@ const filterTests = async () => {
287
287
  }
288
288
 
289
289
  const updatesTests = async () => {
290
+ log_header("Updates Tests")
290
291
  const enduser = await sdk.api.endusers.createOne({ email: 'test@tellescope.com', phone: '+15555555555' })
291
292
  await sdk.api.endusers.updateOne(enduser.id, { phone: '+15555555552' }) // update to new phone number
292
293
  await sdk.api.endusers.updateOne(enduser.id, { phone: '+15555555552' }) // update to same phone number
@@ -301,7 +302,7 @@ const updatesTests = async () => {
301
302
  }
302
303
 
303
304
  const generateEnduserAuthTests = async () => {
304
- log_header("Generated Enduser authToken Tests")
305
+ log_header("Generated Enduser authToken")
305
306
  const externalId = '1029f9v9sjd0as'
306
307
  const e = await sdk.api.endusers.createOne({ email: 'generated@tellescope.com', phone: '+15555555555', externalId })
307
308
 
@@ -628,6 +629,18 @@ const journey_tests = async (queries=sdk.api.journeys) => {
628
629
  const journey = await sdk.api.journeys.createOne({ title: 'Test Journey' })
629
630
  const journey2 = await sdk.api.journeys.createOne({ title: 'Test Journey 2' })
630
631
 
632
+ await sdk.api.journeys.updateOne(journey.id, {
633
+ states: [
634
+ { name: 'Delete Me 1', priority: 'N/A' },
635
+ { name: 'Delete Me 2', priority: 'N/A' },
636
+ ]
637
+ })
638
+ const updated = (await sdk.api.journeys.delete_states({ id: journey.id, states: ['Delete Me 1', 'Delete Me 2']})).updated
639
+ assert(!!updated.id && updated.states.length === 1 && updated.states[0].name === 'New', 'delete states fail on returned update', 'delete states returns updated value')
640
+
641
+ const fetchAfterDeletion = await sdk.api.journeys.getOne(journey.id)
642
+ assert(fetchAfterDeletion.states.length === 1 && fetchAfterDeletion.states[0].name === 'New', 'delete states fail', 'delete states worked')
643
+
631
644
  assert(journey.defaultState === 'New', 'defaultState not set on create', 'journey-create - defaultState initialized')
632
645
  assert(journey.states[0].name === 'New', 'defaultState not set on create', 'journey-create - states initialized')
633
646
 
@@ -750,7 +763,7 @@ const tasks_tests = async (queries=sdk.api.tasks) => {
750
763
  assert(!!t.enduserId, 'enduserId not assigned to task', 'enduserId exists for created task')
751
764
 
752
765
  await sdk.api.endusers.deleteOne(e.id)
753
- await wait(undefined, 25) // allow dependency updates to fire in background
766
+ await wait(undefined, 100) // allow dependency updates to fire in background (there are a lot for endusers)
754
767
  await async_test(
755
768
  `get-task - enduserId unset on enduser deletion`,
756
769
  () => queries.getOne(t.id),
@@ -1029,6 +1042,7 @@ const chat_tests = async() => {
1029
1042
  const chat2Null = await sdk.api.chats.createOne({ roomId: roomNull.id, message: "Hello...", replyId: chatNull.id })
1030
1043
 
1031
1044
  await sdk.api.chats.deleteOne(chatNull.id)
1045
+ await wait(undefined, 250)
1032
1046
  await async_test(
1033
1047
  `get-chat (setNull working)`,
1034
1048
  () => sdk.api.chats.getOne(chat2Null.id),
@@ -1037,6 +1051,7 @@ const chat_tests = async() => {
1037
1051
  }
1038
1052
 
1039
1053
  const enduserAccessTests = async () => {
1054
+ log_header("Enduser Access")
1040
1055
  const email = 'enduser@tellescope.com'
1041
1056
  const password = 'testpassword'
1042
1057
 
@@ -1134,7 +1149,7 @@ const enduserAccessTests = async () => {
1134
1149
  await async_test(
1135
1150
  `enduser cannot create ticket for another enduser`,
1136
1151
  () => enduserSDK.api.tickets.createOne({ enduserId: sdk.userInfo.id, title: "Error on Creation" }),
1137
- { shouldError: true, onError: e => e.message === "enduserId does not match creator id for enduser session" }
1152
+ { shouldError: true, onError: e => !!e.message }
1138
1153
  )
1139
1154
  await async_test(
1140
1155
  `enduser-access default, no access constraints, matching enduserId`,
@@ -1185,6 +1200,7 @@ const files_tests = async () => {
1185
1200
  }
1186
1201
 
1187
1202
  const enduser_session_tests = async () => {
1203
+ log_header("Enduser Session")
1188
1204
  const email = 'enduser@tellescope.com'
1189
1205
  const password = 'testpassword'
1190
1206
 
@@ -1303,6 +1319,112 @@ const calendar_events_tests = async () => {
1303
1319
  await sdk.api.endusers.deleteOne(enduser.id)
1304
1320
  }
1305
1321
 
1322
+ const automation_events_tests = async () => {
1323
+ log_header("Automation Events")
1324
+ const state1 = "State 1", state2 = "State 2";
1325
+ const journey = await sdk.api.journeys.createOne({
1326
+ title: "Automations Test",
1327
+ defaultState: state1,
1328
+ states: [
1329
+ { name: state1, priority: 'N/A' },
1330
+ { name: state2, priority: 'N/A' },
1331
+ ]
1332
+ })
1333
+
1334
+ await async_test(
1335
+ `enterState cannot match updateStateForJourney`,
1336
+ () => sdk.api.event_automations.createOne({
1337
+ journeyId: journey.id,
1338
+ event: {
1339
+ type: "enterState",
1340
+ info: { state: state1, journeyId: journey.id }
1341
+ },
1342
+ action: {
1343
+ type: 'updateStateForJourney',
1344
+ info: { state: state1, journeyId: journey.id },
1345
+ },
1346
+ }),
1347
+ { shouldError: true, onError: e => e.message === 'updateStateForJourney cannot have the same journey and state as the enterState event' }
1348
+ )
1349
+ await async_test(
1350
+ `leaveState cannot match updateStateForJourney`,
1351
+ () => sdk.api.event_automations.createOne({
1352
+ journeyId: journey.id,
1353
+ event: {
1354
+ type: "leaveState",
1355
+ info: { state: state1, journeyId: journey.id }
1356
+ },
1357
+ action: {
1358
+ type: 'updateStateForJourney',
1359
+ info: { state: state1, journeyId: journey.id },
1360
+ },
1361
+ }),
1362
+ { shouldError: true, onError: e => e.message === 'updateStateForJourney cannot have the same journey and state as the leaveState event' }
1363
+ )
1364
+
1365
+ const testAction: AutomationAction = {
1366
+ type: 'sendWebhook',
1367
+ info: { message: 'test' }
1368
+ }
1369
+ const a1 = await sdk.api.event_automations.createOne({
1370
+ journeyId: journey.id,
1371
+ event: {
1372
+ type: "enterState",
1373
+ info: { state: state1, journeyId: journey.id }
1374
+ },
1375
+ action: testAction,
1376
+ })
1377
+ const a2 = await sdk.api.event_automations.createOne({
1378
+ journeyId: journey.id,
1379
+ event: {
1380
+ type: "leaveState",
1381
+ info: { state: state1, journeyId: journey.id }
1382
+ },
1383
+ action: testAction,
1384
+ })
1385
+ const a3 = await sdk.api.event_automations.createOne({
1386
+ journeyId: journey.id,
1387
+ event: {
1388
+ type: "enterState",
1389
+ info: { state: state2, journeyId: journey.id }
1390
+ },
1391
+ action: testAction,
1392
+ })
1393
+
1394
+ await async_test(
1395
+ `Cannot insert duplicate event/action pair`,
1396
+ () => sdk.api.event_automations.createOne({
1397
+ journeyId: journey.id,
1398
+ event: {
1399
+ type: "enterState",
1400
+ info: { state: state2, journeyId: journey.id }
1401
+ },
1402
+ action: testAction,
1403
+ }),
1404
+ { shouldError: true, onError: e => e.message === UniquenessViolationMessage }
1405
+ )
1406
+
1407
+ // trigger a1 on create
1408
+ const enduser = await sdk.api.endusers.createOne({
1409
+ email: "automations@tellescope.com",
1410
+ journeys: { [journey.id]: journey.defaultState }
1411
+ })
1412
+
1413
+ // trigger a2 and a3 by leaving state 1 an going to state 2
1414
+ await sdk.api.endusers.updateOne(enduser.id, { journeys: { [journey.id]: state2 } })
1415
+
1416
+ await async_test(
1417
+ `Automation events triggered correctly`,
1418
+ // () => sdk.api.automation_endusers.getSome(),
1419
+ () => sdk.api.automation_endusers.getSome({ filter: { enduserId: enduser.id }}),
1420
+ { onResult: es => es && es.length === 3 && es.filter(a => a.automationId === "ONE_TIME").length === 3 }
1421
+ )
1422
+
1423
+ // cleanup
1424
+ await sdk.api.journeys.deleteOne(journey.id) // automation events deleted as side effect
1425
+ await sdk.api.endusers.deleteOne(enduser.id)
1426
+ }
1427
+
1306
1428
  const tests: { [K in keyof ClientModelForName]: () => void } = {
1307
1429
  chats: chat_tests,
1308
1430
  endusers: enduser_tests,
@@ -1322,7 +1444,10 @@ const tests: { [K in keyof ClientModelForName]: () => void } = {
1322
1444
  forms: () => {},
1323
1445
  form_responses: () => {},
1324
1446
  calendar_events: calendar_events_tests,
1325
- webhooks: () => {},
1447
+ webhooks: () => {}, // tested separately
1448
+ event_automations: automation_events_tests,
1449
+ sequence_automations: () => {},
1450
+ automation_endusers: () => {},
1326
1451
  };
1327
1452
 
1328
1453
  (async () => {
@@ -1345,7 +1470,7 @@ const tests: { [K in keyof ClientModelForName]: () => void } = {
1345
1470
  } catch(err) {
1346
1471
  console.error("Failed during custom test")
1347
1472
  console.error(err)
1348
- process.exit()
1473
+ process.exit(1)
1349
1474
  }
1350
1475
 
1351
1476
 
@@ -1370,6 +1495,7 @@ const tests: { [K in keyof ClientModelForName]: () => void } = {
1370
1495
  } catch(err) {
1371
1496
  console.error("Error running test:")
1372
1497
  console.error(err)
1498
+ process.exit(1)
1373
1499
  }
1374
1500
  }
1375
1501
 
@@ -18,6 +18,7 @@ import {
18
18
  WebhookRecord,
19
19
  WebhookCall,
20
20
  CUDSubscription,
21
+ AutomationAction,
21
22
  } from "@tellescope/types-models"
22
23
 
23
24
  import { Session } from "../sdk"
@@ -28,7 +29,7 @@ const [email2, password2] = [process.env.TEST_EMAIL_2, process.env.TEST_PASSWORD
28
29
  const [nonAdminEmail, nonAdminPassword] = [process.env.NON_ADMIN_EMAIL, process.env.NON_ADMIN_PASSWORD]
29
30
  if (!(email && password && email2 && password2 && nonAdminEmail && nonAdminPassword)) {
30
31
  console.error("Set TEST_EMAIL and TEST_PASSWORD")
31
- process.exit()
32
+ process.exit(1)
32
33
  }
33
34
 
34
35
 
@@ -46,8 +47,12 @@ const webhookURL = `http://127.0.0.1:${PORT}${webhookEndpoint}`
46
47
 
47
48
  const sha256 = (s: string) => crypto.createHash('sha256').update(s).digest('hex')
48
49
 
49
- const verify_integrity = (records: WebhookRecord[], timestamp: string, integrity: string,) => (
50
- sha256(records.map(r => r.id).join('') + timestamp + TEST_SECRET) === integrity
50
+ const verify_integrity = (type: string, message: string, records: WebhookRecord[], timestamp: string, integrity: string,) => (
51
+ sha256(
52
+ type === "automation"
53
+ ? message + timestamp + TEST_SECRET
54
+ : records.map(r => r.id).join('') + timestamp + TEST_SECRET
55
+ ) === integrity
51
56
  )
52
57
 
53
58
  const handledEvents: WebhookCall[] = []
@@ -55,9 +60,9 @@ app.post(webhookEndpoint, (req, res) => {
55
60
  const body = req.body as WebhookCall
56
61
  // console.log('got hook', body.records, body.timestamp, body.integrity)
57
62
 
58
- if (!verify_integrity(body.records, body.timestamp, body.integrity)) {
63
+ if (!verify_integrity(body.type, body.message ?? '', body.records, body.timestamp, body.integrity)) {
59
64
  console.error("Integrity check failed for request", JSON.stringify(body, null, 2))
60
- process.exit()
65
+ process.exit(1)
61
66
  }
62
67
 
63
68
  handledEvents.push(req.body)
@@ -133,6 +138,54 @@ const meetings_tests = async (isSubscribed: boolean) => {
133
138
  )
134
139
  }
135
140
 
141
+ const test_automation_webhooks = async () => {
142
+ log_header("Automation Events")
143
+ const state1 = "State 1", state2 = "State 2";
144
+ const testMessage = 'Test webhook from automation'
145
+ const journey = await sdk.api.journeys.createOne({
146
+ title: "Automations Test",
147
+ defaultState: state1,
148
+ states: [
149
+ { name: state1, priority: 'N/A' },
150
+ { name: state2, priority: 'N/A' },
151
+ ]
152
+ })
153
+
154
+ const testAction: AutomationAction = {
155
+ type: 'sendWebhook',
156
+ info: { message: testMessage }
157
+ }
158
+ const a1 = await sdk.api.event_automations.createOne({
159
+ journeyId: journey.id,
160
+ event: {
161
+ type: "enterState",
162
+ info: { state: state1, journeyId: journey.id }
163
+ },
164
+ action: testAction,
165
+ })
166
+
167
+ // trigger a1 on create
168
+ const enduser = await sdk.api.endusers.createOne({
169
+ email: "automations@tellescope.com",
170
+ journeys: { [journey.id]: journey.defaultState }
171
+ })
172
+
173
+ // wait long enough for automation to process and send webhook
174
+ await wait(undefined, 2000)
175
+
176
+ await check_next_webhook(
177
+ ({ message }) => message === testMessage,
178
+ 'Automation webhook error',
179
+ 'Automation webhook received',
180
+ true
181
+ )
182
+
183
+
184
+ // cleanup
185
+ await sdk.api.journeys.deleteOne(journey.id) // automation events deleted as side effect
186
+ await sdk.api.endusers.deleteOne(enduser.id)
187
+ }
188
+
136
189
  const tests: { [K in WebhookSupportedModel]: (isSubscribed: boolean) => Promise<void> } = {
137
190
  chats: chats_tests,
138
191
  meetings: meetings_tests,
@@ -187,6 +240,7 @@ const run_tests = async () => {
187
240
  )
188
241
 
189
242
  log_header("Webhooks Tests with Subscriptions")
243
+ await test_automation_webhooks()
190
244
  for (const t in tests) {
191
245
  await tests[t as keyof typeof tests](true)
192
246
  }