@tellescope/sdk 1.250.1 → 1.251.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.
- package/lib/cjs/sdk.d.ts +9 -0
- package/lib/cjs/sdk.d.ts.map +1 -1
- package/lib/cjs/sdk.js +3 -0
- package/lib/cjs/sdk.js.map +1 -1
- package/lib/cjs/tests/api_tests/account_switcher.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/account_switcher.test.js +1700 -306
- package/lib/cjs/tests/api_tests/account_switcher.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/enduser_cross_access_isolation.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/enduser_cross_access_isolation.test.js +28 -15
- package/lib/cjs/tests/api_tests/enduser_cross_access_isolation.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/enduser_login.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/enduser_login.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/enduser_login.test.js +315 -0
- package/lib/cjs/tests/api_tests/enduser_login.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/medication_added_trigger.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/medication_added_trigger.test.js +556 -105
- package/lib/cjs/tests/api_tests/medication_added_trigger.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/outbound_chat_sent_trigger.test.d.ts +7 -0
- package/lib/cjs/tests/api_tests/outbound_chat_sent_trigger.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/outbound_chat_sent_trigger.test.js +436 -0
- package/lib/cjs/tests/api_tests/outbound_chat_sent_trigger.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.js +370 -0
- package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/set_fields_order_templates.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/set_fields_order_templates.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/set_fields_order_templates.test.js +373 -0
- package/lib/cjs/tests/api_tests/set_fields_order_templates.test.js.map +1 -0
- package/lib/cjs/tests/setup.d.ts.map +1 -1
- package/lib/cjs/tests/setup.js +47 -32
- package/lib/cjs/tests/setup.js.map +1 -1
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +190 -161
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/cjs/tests/unit_tests/conditional_logic_medication.test.d.ts +3 -0
- package/lib/cjs/tests/unit_tests/conditional_logic_medication.test.d.ts.map +1 -0
- package/lib/cjs/tests/unit_tests/conditional_logic_medication.test.js +114 -0
- package/lib/cjs/tests/unit_tests/conditional_logic_medication.test.js.map +1 -0
- package/lib/esm/sdk.d.ts +9 -0
- package/lib/esm/sdk.d.ts.map +1 -1
- package/lib/esm/sdk.js +3 -0
- package/lib/esm/sdk.js.map +1 -1
- package/lib/esm/tests/api_tests/account_switcher.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/account_switcher.test.js +1702 -305
- package/lib/esm/tests/api_tests/account_switcher.test.js.map +1 -1
- package/lib/esm/tests/api_tests/enduser_cross_access_isolation.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/enduser_cross_access_isolation.test.js +28 -15
- package/lib/esm/tests/api_tests/enduser_cross_access_isolation.test.js.map +1 -1
- package/lib/esm/tests/api_tests/enduser_login.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/enduser_login.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/enduser_login.test.js +308 -0
- package/lib/esm/tests/api_tests/enduser_login.test.js.map +1 -0
- package/lib/esm/tests/api_tests/enduser_login_phi_disclosure.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/enduser_login_phi_disclosure.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/enduser_login_phi_disclosure.test.js +268 -0
- package/lib/esm/tests/api_tests/enduser_login_phi_disclosure.test.js.map +1 -0
- package/lib/esm/tests/api_tests/medication_added_trigger.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/medication_added_trigger.test.js +556 -105
- package/lib/esm/tests/api_tests/medication_added_trigger.test.js.map +1 -1
- package/lib/esm/tests/api_tests/outbound_chat_sent_trigger.test.d.ts +7 -0
- package/lib/esm/tests/api_tests/outbound_chat_sent_trigger.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/outbound_chat_sent_trigger.test.js +432 -0
- package/lib/esm/tests/api_tests/outbound_chat_sent_trigger.test.js.map +1 -0
- package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.js +366 -0
- package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.js.map +1 -0
- package/lib/esm/tests/api_tests/set_fields_order_templates.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/set_fields_order_templates.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/set_fields_order_templates.test.js +369 -0
- package/lib/esm/tests/api_tests/set_fields_order_templates.test.js.map +1 -0
- package/lib/esm/tests/setup.d.ts.map +1 -1
- package/lib/esm/tests/setup.js +47 -32
- package/lib/esm/tests/setup.js.map +1 -1
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +190 -161
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/esm/tests/unit_tests/conditional_logic_medication.test.d.ts +3 -0
- package/lib/esm/tests/unit_tests/conditional_logic_medication.test.d.ts.map +1 -0
- package/lib/esm/tests/unit_tests/conditional_logic_medication.test.js +111 -0
- package/lib/esm/tests/unit_tests/conditional_logic_medication.test.js.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/sdk.ts +12 -0
- package/src/tests/api_tests/account_switcher.test.ts +1283 -0
- package/src/tests/api_tests/enduser_cross_access_isolation.test.ts +26 -0
- package/src/tests/api_tests/enduser_login.test.ts +215 -0
- package/src/tests/api_tests/medication_added_trigger.test.ts +345 -4
- package/src/tests/api_tests/outbound_chat_sent_trigger.test.ts +339 -0
- package/src/tests/api_tests/push_forms_to_portal_group_completion.test.ts +198 -0
- package/src/tests/api_tests/set_fields_order_templates.test.ts +258 -0
- package/src/tests/setup.ts +8 -1
- package/src/tests/tests.ts +23 -6
- package/src/tests/unit_tests/conditional_logic_medication.test.ts +133 -0
- package/test_generated.pdf +0 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import { Session, EnduserSession } from "../../sdk"
|
|
4
|
+
import {
|
|
5
|
+
log_header,
|
|
6
|
+
wait,
|
|
7
|
+
} from "@tellescope/testing"
|
|
8
|
+
import { setup_tests } from "../setup"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tests for the "Outbound Chat Sent" automation trigger event.
|
|
12
|
+
*
|
|
13
|
+
* Covered cases (one shared trigger, one shared TAG):
|
|
14
|
+
* 1. Single-enduser room, inbound (enduser session) → no trigger, no recentOutboundChatAt
|
|
15
|
+
* 2. Single-enduser room, outbound (user session) → trigger fires, recentOutboundChatAt set + recent
|
|
16
|
+
* 3. Multi-enduser room, inbound (one enduser's session) → neither enduser tagged or stamped
|
|
17
|
+
* 4. Multi-enduser room, outbound (user session) → BOTH endusers tagged + stamped
|
|
18
|
+
* 5. User session, senderId = enduser.id (backfill case) → treated as inbound: no trigger, no stamp
|
|
19
|
+
*
|
|
20
|
+
* Chats are NOT explicitly deleted — they cascade-delete from chat_rooms.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const isRecent = (value: Date | string | undefined, sinceMs: number): boolean => {
|
|
24
|
+
if (!value) return false
|
|
25
|
+
const t = new Date(value).valueOf()
|
|
26
|
+
return t >= sinceMs - 1000 && t <= Date.now() + 1000
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const outbound_chat_sent_trigger_tests = async ({ sdk }: { sdk: Session }) => {
|
|
30
|
+
log_header("Outbound Chat Sent Trigger Tests")
|
|
31
|
+
|
|
32
|
+
const host = process.env.API_URL || 'http://localhost:8080'
|
|
33
|
+
const TAG = `outbound-chat-sent-${Date.now()}`
|
|
34
|
+
|
|
35
|
+
let endSolo: { id: string } | undefined
|
|
36
|
+
let endA: { id: string } | undefined
|
|
37
|
+
let endB: { id: string } | undefined
|
|
38
|
+
let endBackfill: { id: string } | undefined
|
|
39
|
+
let endAutoreply: { id: string } | undefined
|
|
40
|
+
|
|
41
|
+
let trigger: { id: string } | undefined
|
|
42
|
+
let roomSolo: { id: string } | undefined
|
|
43
|
+
let roomGroup: { id: string } | undefined
|
|
44
|
+
let roomBackfill: { id: string } | undefined
|
|
45
|
+
let roomAutoreply: { id: string } | undefined
|
|
46
|
+
|
|
47
|
+
let settingsModified = false
|
|
48
|
+
let originalOutOfOfficeHours: any[] = []
|
|
49
|
+
let originalAutoReplyEnabled = false
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// ── Endusers ──────────────────────────────────────────────────────
|
|
53
|
+
endSolo = await sdk.api.endusers.createOne({ fname: 'Solo', lname: 'Patient' })
|
|
54
|
+
endA = await sdk.api.endusers.createOne({ fname: 'GroupA', lname: 'Patient' })
|
|
55
|
+
endB = await sdk.api.endusers.createOne({ fname: 'GroupB', lname: 'Patient' })
|
|
56
|
+
endBackfill = await sdk.api.endusers.createOne({ fname: 'Backfill', lname: 'Patient' })
|
|
57
|
+
console.log(`Created endusers: solo=${endSolo.id}, A=${endA.id}, B=${endB.id}, backfill=${endBackfill.id}`)
|
|
58
|
+
|
|
59
|
+
// ── Enduser sessions for inbound sends ────────────────────────────
|
|
60
|
+
const { authToken: tokenSolo } = await sdk.api.endusers.generate_auth_token({ id: endSolo.id })
|
|
61
|
+
const endSoloSDK = new EnduserSession({ host, authToken: tokenSolo, businessId: sdk.userInfo.businessId })
|
|
62
|
+
|
|
63
|
+
const { authToken: tokenA } = await sdk.api.endusers.generate_auth_token({ id: endA.id })
|
|
64
|
+
const endASDK = new EnduserSession({ host, authToken: tokenA, businessId: sdk.userInfo.businessId })
|
|
65
|
+
|
|
66
|
+
// ── Trigger ───────────────────────────────────────────────────────
|
|
67
|
+
trigger = await sdk.api.automation_triggers.createOne({
|
|
68
|
+
title: `Outbound Chat Sent Test ${Date.now()}`,
|
|
69
|
+
status: 'Active',
|
|
70
|
+
event: { type: 'Outbound Chat Sent', info: {} },
|
|
71
|
+
action: { type: 'Add Tags', info: { tags: [TAG] } },
|
|
72
|
+
})
|
|
73
|
+
console.log(`Created trigger: ${trigger.id}`)
|
|
74
|
+
|
|
75
|
+
// ── Rooms ─────────────────────────────────────────────────────────
|
|
76
|
+
roomSolo = await sdk.api.chat_rooms.createOne({
|
|
77
|
+
userIds: [sdk.userInfo.id],
|
|
78
|
+
enduserIds: [endSolo.id],
|
|
79
|
+
})
|
|
80
|
+
roomGroup = await sdk.api.chat_rooms.createOne({
|
|
81
|
+
userIds: [sdk.userInfo.id],
|
|
82
|
+
enduserIds: [endA.id, endB.id],
|
|
83
|
+
})
|
|
84
|
+
roomBackfill = await sdk.api.chat_rooms.createOne({
|
|
85
|
+
userIds: [sdk.userInfo.id],
|
|
86
|
+
enduserIds: [endBackfill.id],
|
|
87
|
+
})
|
|
88
|
+
console.log(`Created rooms: solo=${roomSolo.id}, group=${roomGroup.id}, backfill=${roomBackfill.id}`)
|
|
89
|
+
|
|
90
|
+
// ═════════════════════════════════════════════════════════════════
|
|
91
|
+
// Case 1: Single-enduser inbound (enduser session)
|
|
92
|
+
// ═════════════════════════════════════════════════════════════════
|
|
93
|
+
await endSoloSDK.api.chats.createOne({
|
|
94
|
+
roomId: roomSolo.id,
|
|
95
|
+
message: 'solo inbound from enduser',
|
|
96
|
+
})
|
|
97
|
+
await wait(undefined, 2000)
|
|
98
|
+
|
|
99
|
+
const soloAfterInbound = await sdk.api.endusers.getOne(endSolo.id)
|
|
100
|
+
const c1_notTagged = !soloAfterInbound.tags?.includes(TAG)
|
|
101
|
+
const c1_noStamp = !soloAfterInbound.recentOutboundChatAt
|
|
102
|
+
console.log(c1_notTagged
|
|
103
|
+
? `✅ [1] Solo inbound did NOT fire trigger`
|
|
104
|
+
: `❌ [1] Solo inbound fired trigger. tags=${JSON.stringify(soloAfterInbound.tags)}`)
|
|
105
|
+
console.log(c1_noStamp
|
|
106
|
+
? `✅ [1] Solo inbound did NOT set recentOutboundChatAt`
|
|
107
|
+
: `❌ [1] Solo inbound set recentOutboundChatAt=${soloAfterInbound.recentOutboundChatAt}`)
|
|
108
|
+
|
|
109
|
+
// ═════════════════════════════════════════════════════════════════
|
|
110
|
+
// Case 2: Single-enduser outbound (user session)
|
|
111
|
+
// ═════════════════════════════════════════════════════════════════
|
|
112
|
+
const c2_sendStart = Date.now()
|
|
113
|
+
await sdk.api.chats.createOne({
|
|
114
|
+
roomId: roomSolo.id,
|
|
115
|
+
message: 'solo outbound from user',
|
|
116
|
+
senderId: sdk.userInfo.id,
|
|
117
|
+
})
|
|
118
|
+
await wait(undefined, 2000)
|
|
119
|
+
|
|
120
|
+
const soloAfterOutbound = await sdk.api.endusers.getOne(endSolo.id)
|
|
121
|
+
const c2_tagged = !!soloAfterOutbound.tags?.includes(TAG)
|
|
122
|
+
const c2_stamped = isRecent(soloAfterOutbound.recentOutboundChatAt, c2_sendStart)
|
|
123
|
+
console.log(c2_tagged
|
|
124
|
+
? `✅ [2] Solo outbound fired trigger`
|
|
125
|
+
: `❌ [2] Solo outbound did NOT fire trigger. tags=${JSON.stringify(soloAfterOutbound.tags)}`)
|
|
126
|
+
console.log(c2_stamped
|
|
127
|
+
? `✅ [2] Solo outbound set recentOutboundChatAt=${soloAfterOutbound.recentOutboundChatAt}`
|
|
128
|
+
: `❌ [2] Solo outbound did not set a recent recentOutboundChatAt. got=${soloAfterOutbound.recentOutboundChatAt}`)
|
|
129
|
+
|
|
130
|
+
// ═════════════════════════════════════════════════════════════════
|
|
131
|
+
// Case 3: Multi-enduser inbound (endA sends in [A, B] room)
|
|
132
|
+
// ═════════════════════════════════════════════════════════════════
|
|
133
|
+
await endASDK.api.chats.createOne({
|
|
134
|
+
roomId: roomGroup.id,
|
|
135
|
+
message: 'group inbound from endA',
|
|
136
|
+
})
|
|
137
|
+
await wait(undefined, 2000)
|
|
138
|
+
|
|
139
|
+
const aAfterInbound = await sdk.api.endusers.getOne(endA.id)
|
|
140
|
+
const bAfterInbound = await sdk.api.endusers.getOne(endB.id)
|
|
141
|
+
const c3_aNotTagged = !aAfterInbound.tags?.includes(TAG)
|
|
142
|
+
const c3_bNotTagged = !bAfterInbound.tags?.includes(TAG)
|
|
143
|
+
const c3_neitherTagged = c3_aNotTagged && c3_bNotTagged
|
|
144
|
+
const c3_neitherStamped = !aAfterInbound.recentOutboundChatAt && !bAfterInbound.recentOutboundChatAt
|
|
145
|
+
console.log(c3_neitherTagged
|
|
146
|
+
? `✅ [3] Group inbound did NOT fire trigger on either enduser`
|
|
147
|
+
: `❌ [3] Group inbound fired trigger. A tags=${JSON.stringify(aAfterInbound.tags)} B tags=${JSON.stringify(bAfterInbound.tags)}`)
|
|
148
|
+
console.log(c3_neitherStamped
|
|
149
|
+
? `✅ [3] Group inbound did NOT set recentOutboundChatAt on either enduser`
|
|
150
|
+
: `❌ [3] Group inbound stamped recentOutboundChatAt. A=${aAfterInbound.recentOutboundChatAt} B=${bAfterInbound.recentOutboundChatAt}`)
|
|
151
|
+
|
|
152
|
+
// ═════════════════════════════════════════════════════════════════
|
|
153
|
+
// Case 4: Multi-enduser outbound (user sends in [A, B] room)
|
|
154
|
+
// ═════════════════════════════════════════════════════════════════
|
|
155
|
+
const c4_sendStart = Date.now()
|
|
156
|
+
await sdk.api.chats.createOne({
|
|
157
|
+
roomId: roomGroup.id,
|
|
158
|
+
message: 'group outbound from user',
|
|
159
|
+
senderId: sdk.userInfo.id,
|
|
160
|
+
})
|
|
161
|
+
await wait(undefined, 2000)
|
|
162
|
+
|
|
163
|
+
const aAfterOutbound = await sdk.api.endusers.getOne(endA.id)
|
|
164
|
+
const bAfterOutbound = await sdk.api.endusers.getOne(endB.id)
|
|
165
|
+
const c4_bothTagged = !!aAfterOutbound.tags?.includes(TAG) && !!bAfterOutbound.tags?.includes(TAG)
|
|
166
|
+
const c4_bothStamped = isRecent(aAfterOutbound.recentOutboundChatAt, c4_sendStart)
|
|
167
|
+
&& isRecent(bAfterOutbound.recentOutboundChatAt, c4_sendStart)
|
|
168
|
+
console.log(c4_bothTagged
|
|
169
|
+
? `✅ [4] Group outbound fired trigger on BOTH endusers`
|
|
170
|
+
: `❌ [4] Group outbound did not tag both. A tags=${JSON.stringify(aAfterOutbound.tags)} B tags=${JSON.stringify(bAfterOutbound.tags)}`)
|
|
171
|
+
console.log(c4_bothStamped
|
|
172
|
+
? `✅ [4] Group outbound set recentOutboundChatAt on BOTH endusers`
|
|
173
|
+
: `❌ [4] Group outbound did not stamp both. A=${aAfterOutbound.recentOutboundChatAt} B=${bAfterOutbound.recentOutboundChatAt}`)
|
|
174
|
+
|
|
175
|
+
// ═════════════════════════════════════════════════════════════════
|
|
176
|
+
// Case 5: User session, senderId = enduser.id (backfill)
|
|
177
|
+
// ═════════════════════════════════════════════════════════════════
|
|
178
|
+
await sdk.api.chats.createOne({
|
|
179
|
+
roomId: roomBackfill.id,
|
|
180
|
+
message: 'backfilled inbound (user session, enduser senderId)',
|
|
181
|
+
senderId: endBackfill.id,
|
|
182
|
+
})
|
|
183
|
+
await wait(undefined, 2000)
|
|
184
|
+
|
|
185
|
+
const backfillAfter = await sdk.api.endusers.getOne(endBackfill.id)
|
|
186
|
+
const c5_notTagged = !backfillAfter.tags?.includes(TAG)
|
|
187
|
+
const c5_noStamp = !backfillAfter.recentOutboundChatAt
|
|
188
|
+
console.log(c5_notTagged
|
|
189
|
+
? `✅ [5] Backfill (user-session + enduser senderId) did NOT fire trigger`
|
|
190
|
+
: `❌ [5] Backfill fired trigger. tags=${JSON.stringify(backfillAfter.tags)}`)
|
|
191
|
+
console.log(c5_noStamp
|
|
192
|
+
? `✅ [5] Backfill did NOT set recentOutboundChatAt`
|
|
193
|
+
: `❌ [5] Backfill set recentOutboundChatAt=${backfillAfter.recentOutboundChatAt}`)
|
|
194
|
+
|
|
195
|
+
// ═════════════════════════════════════════════════════════════════
|
|
196
|
+
// Case 6: Autoreply — enduser inbound triggers an org autoreply.
|
|
197
|
+
// The autoreply chat (isAutoreply: true) MUST NOT fire the trigger
|
|
198
|
+
// or stamp recentOutboundChatAt.
|
|
199
|
+
//
|
|
200
|
+
// Setup requires enabling org autoreply with an OOO window covering
|
|
201
|
+
// "now" (is_out_of_office returns false on empty config).
|
|
202
|
+
// ═════════════════════════════════════════════════════════════════
|
|
203
|
+
const originalOrg = await sdk.api.organizations.getOne(sdk.userInfo.businessId)
|
|
204
|
+
originalOutOfOfficeHours = originalOrg.outOfOfficeHours ?? []
|
|
205
|
+
originalAutoReplyEnabled = !!originalOrg.settings?.endusers?.autoReplyEnabled
|
|
206
|
+
|
|
207
|
+
settingsModified = true
|
|
208
|
+
await sdk.api.organizations.updateOne(sdk.userInfo.businessId, {
|
|
209
|
+
outOfOfficeHours: [{
|
|
210
|
+
from: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
|
|
211
|
+
to: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
|
212
|
+
autoreplyText: 'OOO autoreply (test)',
|
|
213
|
+
}] as any,
|
|
214
|
+
settings: { endusers: { autoReplyEnabled: true } } as any,
|
|
215
|
+
}, { replaceObjectFields: true })
|
|
216
|
+
|
|
217
|
+
endAutoreply = await sdk.api.endusers.createOne({ fname: 'Autoreply', lname: 'Patient' })
|
|
218
|
+
const { authToken: tokenAR } = await sdk.api.endusers.generate_auth_token({ id: endAutoreply.id })
|
|
219
|
+
const endAutoreplySDK = new EnduserSession({ host, authToken: tokenAR, businessId: sdk.userInfo.businessId })
|
|
220
|
+
|
|
221
|
+
roomAutoreply = await sdk.api.chat_rooms.createOne({
|
|
222
|
+
userIds: [sdk.userInfo.id],
|
|
223
|
+
enduserIds: [endAutoreply.id],
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
await endAutoreplySDK.api.chats.createOne({
|
|
227
|
+
roomId: roomAutoreply.id,
|
|
228
|
+
message: 'trigger an autoreply',
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
// Autoreply path involves an extra org/user lookup + insert before
|
|
232
|
+
// handle_chat_create re-runs against the autoreply chat.
|
|
233
|
+
await wait(undefined, 4000)
|
|
234
|
+
|
|
235
|
+
const arAfter = await sdk.api.endusers.getOne(endAutoreply.id)
|
|
236
|
+
const roomChats = await sdk.api.chats.getSome({ filter: { roomId: roomAutoreply.id } })
|
|
237
|
+
const sanity_autoreplySent = roomChats.some(c => (c as any).isAutoreply === true)
|
|
238
|
+
|
|
239
|
+
const c6_notTagged = !arAfter.tags?.includes(TAG)
|
|
240
|
+
const c6_noStamp = !arAfter.recentOutboundChatAt
|
|
241
|
+
|
|
242
|
+
console.log(sanity_autoreplySent
|
|
243
|
+
? `✅ [6] Autoreply chat was sent (sanity check)`
|
|
244
|
+
: `❌ [6] No autoreply chat found in room — test setup invalid. Cannot validate trigger skip.`)
|
|
245
|
+
console.log(c6_notTagged
|
|
246
|
+
? `✅ [6] Autoreply did NOT fire trigger`
|
|
247
|
+
: `❌ [6] Autoreply fired trigger. tags=${JSON.stringify(arAfter.tags)}`)
|
|
248
|
+
console.log(c6_noStamp
|
|
249
|
+
? `✅ [6] Autoreply did NOT set recentOutboundChatAt`
|
|
250
|
+
: `❌ [6] Autoreply set recentOutboundChatAt=${arAfter.recentOutboundChatAt}`)
|
|
251
|
+
|
|
252
|
+
// ── Summary ───────────────────────────────────────────────────────
|
|
253
|
+
console.log(`\n=== Outbound Chat Sent Trigger Test Results ===`)
|
|
254
|
+
console.log(`[1] Solo inbound — no trigger: ${c1_notTagged ? '✅' : '❌'}`)
|
|
255
|
+
console.log(`[1] Solo inbound — no stamp: ${c1_noStamp ? '✅' : '❌'}`)
|
|
256
|
+
console.log(`[2] Solo outbound — trigger: ${c2_tagged ? '✅' : '❌'}`)
|
|
257
|
+
console.log(`[2] Solo outbound — stamp: ${c2_stamped ? '✅' : '❌'}`)
|
|
258
|
+
console.log(`[3] Group inbound — no trigger (both): ${c3_neitherTagged ? '✅' : '❌'}`)
|
|
259
|
+
console.log(`[3] Group inbound — no stamp (both): ${c3_neitherStamped ? '✅' : '❌'}`)
|
|
260
|
+
console.log(`[4] Group outbound — trigger (both): ${c4_bothTagged ? '✅' : '❌'}`)
|
|
261
|
+
console.log(`[4] Group outbound — stamp (both): ${c4_bothStamped ? '✅' : '❌'}`)
|
|
262
|
+
console.log(`[5] Backfill — no trigger: ${c5_notTagged ? '✅' : '❌'}`)
|
|
263
|
+
console.log(`[5] Backfill — no stamp: ${c5_noStamp ? '✅' : '❌'}`)
|
|
264
|
+
console.log(`[6] Autoreply — sanity sent: ${sanity_autoreplySent ? '✅' : '❌'}`)
|
|
265
|
+
console.log(`[6] Autoreply — no trigger: ${c6_notTagged ? '✅' : '❌'}`)
|
|
266
|
+
console.log(`[6] Autoreply — no stamp: ${c6_noStamp ? '✅' : '❌'}`)
|
|
267
|
+
|
|
268
|
+
const allPassed =
|
|
269
|
+
c1_notTagged && c1_noStamp
|
|
270
|
+
&& c2_tagged && c2_stamped
|
|
271
|
+
&& c3_neitherTagged && c3_neitherStamped
|
|
272
|
+
&& c4_bothTagged && c4_bothStamped
|
|
273
|
+
&& c5_notTagged && c5_noStamp
|
|
274
|
+
&& sanity_autoreplySent && c6_notTagged && c6_noStamp
|
|
275
|
+
|
|
276
|
+
if (!allPassed) {
|
|
277
|
+
throw new Error('Outbound Chat Sent trigger tests failed')
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return { success: true }
|
|
281
|
+
} finally {
|
|
282
|
+
try {
|
|
283
|
+
// Restore org settings FIRST (sequentially, before deletes), so a
|
|
284
|
+
// failure here doesn't leave autoReplyEnabled=true polluting other tests.
|
|
285
|
+
if (settingsModified) {
|
|
286
|
+
try {
|
|
287
|
+
await sdk.api.organizations.updateOne(sdk.userInfo.businessId, {
|
|
288
|
+
outOfOfficeHours: originalOutOfOfficeHours as any,
|
|
289
|
+
settings: { endusers: { autoReplyEnabled: originalAutoReplyEnabled } } as any,
|
|
290
|
+
}, { replaceObjectFields: true })
|
|
291
|
+
console.log(`Restored org settings (autoReplyEnabled=${originalAutoReplyEnabled}, outOfOfficeHours.length=${originalOutOfOfficeHours.length})`)
|
|
292
|
+
} catch (err) {
|
|
293
|
+
console.error(`❌ Failed to restore org settings — manual cleanup may be required: ${err}`)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const cleanups: Promise<any>[] = []
|
|
298
|
+
|
|
299
|
+
if (trigger?.id) cleanups.push(sdk.api.automation_triggers.deleteOne(trigger.id).catch(() => {}))
|
|
300
|
+
if (roomSolo?.id) cleanups.push(sdk.api.chat_rooms.deleteOne(roomSolo.id).catch(() => {}))
|
|
301
|
+
if (roomGroup?.id) cleanups.push(sdk.api.chat_rooms.deleteOne(roomGroup.id).catch(() => {}))
|
|
302
|
+
if (roomBackfill?.id) cleanups.push(sdk.api.chat_rooms.deleteOne(roomBackfill.id).catch(() => {}))
|
|
303
|
+
if (roomAutoreply?.id) cleanups.push(sdk.api.chat_rooms.deleteOne(roomAutoreply.id).catch(() => {}))
|
|
304
|
+
if (endSolo?.id) cleanups.push(sdk.api.endusers.deleteOne(endSolo.id).catch(() => {}))
|
|
305
|
+
if (endA?.id) cleanups.push(sdk.api.endusers.deleteOne(endA.id).catch(() => {}))
|
|
306
|
+
if (endB?.id) cleanups.push(sdk.api.endusers.deleteOne(endB.id).catch(() => {}))
|
|
307
|
+
if (endBackfill?.id) cleanups.push(sdk.api.endusers.deleteOne(endBackfill.id).catch(() => {}))
|
|
308
|
+
if (endAutoreply?.id) cleanups.push(sdk.api.endusers.deleteOne(endAutoreply.id).catch(() => {}))
|
|
309
|
+
|
|
310
|
+
await Promise.all(cleanups)
|
|
311
|
+
console.log(`Outbound Chat Sent trigger test cleanup completed`)
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error(`Cleanup error: ${error}`)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Allow running this test file independently
|
|
319
|
+
if (require.main === module) {
|
|
320
|
+
const host = process.env.API_URL || 'http://localhost:8080'
|
|
321
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
322
|
+
const sdk = new Session({ host })
|
|
323
|
+
const sdkNonAdmin = new Session({ host })
|
|
324
|
+
|
|
325
|
+
const runTests = async () => {
|
|
326
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
327
|
+
await outbound_chat_sent_trigger_tests({ sdk })
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
runTests()
|
|
331
|
+
.then(() => {
|
|
332
|
+
console.log("✅ Outbound Chat Sent trigger tests completed successfully")
|
|
333
|
+
process.exit(0)
|
|
334
|
+
})
|
|
335
|
+
.catch((error) => {
|
|
336
|
+
console.error("❌ Outbound Chat Sent trigger tests failed:", error)
|
|
337
|
+
process.exit(1)
|
|
338
|
+
})
|
|
339
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import { Session } from "../../sdk"
|
|
4
|
+
import { log_header, wait, async_test } from "@tellescope/testing"
|
|
5
|
+
import { Enduser } from "@tellescope/types-client"
|
|
6
|
+
import { setup_tests } from "../setup"
|
|
7
|
+
|
|
8
|
+
const host = process.env.API_URL || "http://localhost:8080"
|
|
9
|
+
|
|
10
|
+
const pollFor = async <T>(
|
|
11
|
+
fetchFn: () => Promise<T | undefined>,
|
|
12
|
+
evaluateFn: (result: T | undefined) => result is T,
|
|
13
|
+
description: string,
|
|
14
|
+
intervalMs = 500,
|
|
15
|
+
maxIterations = 30,
|
|
16
|
+
): Promise<T> => {
|
|
17
|
+
let lastResult: T | undefined
|
|
18
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
19
|
+
await wait(undefined, intervalMs)
|
|
20
|
+
lastResult = await fetchFn()
|
|
21
|
+
if (evaluateFn(lastResult)) return lastResult
|
|
22
|
+
}
|
|
23
|
+
throw new Error(`Polling timeout: ${description} - waited ${maxIterations * intervalMs}ms`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const push_forms_to_portal_group_completion_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
27
|
+
log_header("Push Forms To Portal - Form Group Completed Trigger Tests")
|
|
28
|
+
|
|
29
|
+
const createdEnduserIds: string[] = []
|
|
30
|
+
const createdJourneyIds: string[] = []
|
|
31
|
+
const createdFormIds: string[] = []
|
|
32
|
+
const createdFormGroupIds: string[] = []
|
|
33
|
+
const createdTriggerIds: string[] = []
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// 1. Create two forms, each with a single text field
|
|
37
|
+
const formA = await sdk.api.forms.createOne({ title: 'Push To Portal Form A' })
|
|
38
|
+
createdFormIds.push(formA.id)
|
|
39
|
+
const fieldA = await sdk.api.form_fields.createOne({
|
|
40
|
+
formId: formA.id,
|
|
41
|
+
type: 'string',
|
|
42
|
+
title: 'FieldA',
|
|
43
|
+
previousFields: [{ type: 'root', info: {} }],
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const formB = await sdk.api.forms.createOne({ title: 'Push To Portal Form B' })
|
|
47
|
+
createdFormIds.push(formB.id)
|
|
48
|
+
const fieldB = await sdk.api.form_fields.createOne({
|
|
49
|
+
formId: formB.id,
|
|
50
|
+
type: 'string',
|
|
51
|
+
title: 'FieldB',
|
|
52
|
+
previousFields: [{ type: 'root', info: {} }],
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// 2. Create a form group containing both forms
|
|
56
|
+
const formGroup = await sdk.api.form_groups.createOne({
|
|
57
|
+
title: 'Push To Portal Test Group',
|
|
58
|
+
formIds: [formA.id, formB.id],
|
|
59
|
+
})
|
|
60
|
+
createdFormGroupIds.push(formGroup.id)
|
|
61
|
+
|
|
62
|
+
// 3. Configure trigger with event.info.groupId = the real formGroupId
|
|
63
|
+
const trigger = await sdk.api.automation_triggers.createOne({
|
|
64
|
+
event: { type: 'Form Group Completed', info: { groupId: formGroup.id } },
|
|
65
|
+
action: { type: 'Add Tags', info: { tags: ['form-group-completed-push'] } },
|
|
66
|
+
status: 'Active',
|
|
67
|
+
title: 'Form Group Completed - Push to Portal',
|
|
68
|
+
})
|
|
69
|
+
createdTriggerIds.push(trigger.id)
|
|
70
|
+
|
|
71
|
+
// 4. Create journey with a pushFormsToPortal step referencing the form group
|
|
72
|
+
const journey = await sdk.api.journeys.createOne({
|
|
73
|
+
title: 'Push To Portal Trigger Journey',
|
|
74
|
+
})
|
|
75
|
+
createdJourneyIds.push(journey.id)
|
|
76
|
+
|
|
77
|
+
const pushStep = await sdk.api.automation_steps.createOne({
|
|
78
|
+
journeyId: journey.id,
|
|
79
|
+
action: { type: 'pushFormsToPortal', info: { formGroupIds: [formGroup.id] } },
|
|
80
|
+
events: [{ type: 'onJourneyStart', info: {} }],
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// 5. Create enduser and add to journey
|
|
84
|
+
const enduser = await sdk.api.endusers.createOne({ fname: 'PushPortal', lname: 'Tester' })
|
|
85
|
+
createdEnduserIds.push(enduser.id)
|
|
86
|
+
|
|
87
|
+
await sdk.api.endusers.add_to_journey({
|
|
88
|
+
enduserIds: [enduser.id],
|
|
89
|
+
journeyId: journey.id,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// 6. Poll for the worker to create the push-to-portal form_responses
|
|
93
|
+
const pushedResponses = await pollFor(
|
|
94
|
+
async () => {
|
|
95
|
+
const responses = await sdk.api.form_responses.getSome({
|
|
96
|
+
filter: { enduserId: enduser.id },
|
|
97
|
+
})
|
|
98
|
+
const pushed = responses.filter(r => !!r.pushedToPortalAt)
|
|
99
|
+
return pushed.length >= 2 ? pushed : undefined
|
|
100
|
+
},
|
|
101
|
+
(result): result is any[] => Array.isArray(result) && result.length >= 2,
|
|
102
|
+
'pushed-to-portal form_responses to be created by worker',
|
|
103
|
+
500,
|
|
104
|
+
40,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
// 7. Assert worker behavior: groupId === automationStepId and pushedToPortalAt is set
|
|
108
|
+
for (const fr of pushedResponses) {
|
|
109
|
+
if (!fr.pushedToPortalAt) {
|
|
110
|
+
throw new Error(`Expected pushedToPortalAt to be set on form_response ${fr.id}`)
|
|
111
|
+
}
|
|
112
|
+
if (fr.groupId !== pushStep.id) {
|
|
113
|
+
throw new Error(`Expected form_response.groupId (${fr.groupId}) to equal automation step id (${pushStep.id})`)
|
|
114
|
+
}
|
|
115
|
+
if (fr.automationStepId !== pushStep.id) {
|
|
116
|
+
throw new Error(`Expected form_response.automationStepId (${fr.automationStepId}) to equal automation step id (${pushStep.id})`)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await async_test(
|
|
121
|
+
"Worker writes groupId === automationStepId and pushedToPortalAt set",
|
|
122
|
+
async () => true,
|
|
123
|
+
{ onResult: r => r === true },
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
// 8. Submit every form_response on behalf of the enduser
|
|
127
|
+
// Identify which form_response corresponds to formA / formB via formId
|
|
128
|
+
for (const fr of pushedResponses) {
|
|
129
|
+
const isFormA = fr.formId === formA.id
|
|
130
|
+
const targetFieldId = isFormA ? fieldA.id : fieldB.id
|
|
131
|
+
const targetFieldTitle = isFormA ? 'FieldA' : 'FieldB'
|
|
132
|
+
await sdk.api.form_responses.submit_form_response({
|
|
133
|
+
accessCode: fr.accessCode as string,
|
|
134
|
+
responses: [{
|
|
135
|
+
fieldId: targetFieldId,
|
|
136
|
+
fieldTitle: targetFieldTitle,
|
|
137
|
+
answer: { type: 'string', value: 'pushed-portal-answer' },
|
|
138
|
+
}],
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 9. Poll for the trigger's side-effect (tag on enduser)
|
|
143
|
+
await pollFor(
|
|
144
|
+
async () => {
|
|
145
|
+
const e = await sdk.api.endusers.getOne(enduser.id)
|
|
146
|
+
return e.tags?.includes('form-group-completed-push') ? e : undefined
|
|
147
|
+
},
|
|
148
|
+
(result): result is Enduser => !!result,
|
|
149
|
+
'Form Group Completed trigger to apply tag after push-to-portal submissions',
|
|
150
|
+
500,
|
|
151
|
+
30,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
await async_test(
|
|
155
|
+
"Form Group Completed trigger fires for push-to-portal completion",
|
|
156
|
+
() => sdk.api.endusers.getOne(enduser.id),
|
|
157
|
+
{ onResult: (e: Enduser) => !!e.tags?.includes('form-group-completed-push') },
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
} finally {
|
|
161
|
+
for (const id of createdTriggerIds) {
|
|
162
|
+
try { await sdk.api.automation_triggers.deleteOne(id) } catch (e) { /* ignore */ }
|
|
163
|
+
}
|
|
164
|
+
for (const id of createdEnduserIds) {
|
|
165
|
+
try { await sdk.api.endusers.deleteOne(id) } catch (e) { /* ignore */ }
|
|
166
|
+
}
|
|
167
|
+
for (const id of createdJourneyIds) {
|
|
168
|
+
try { await sdk.api.journeys.deleteOne(id) } catch (e) { /* ignore */ }
|
|
169
|
+
}
|
|
170
|
+
for (const id of createdFormGroupIds) {
|
|
171
|
+
try { await sdk.api.form_groups.deleteOne(id) } catch (e) { /* ignore */ }
|
|
172
|
+
}
|
|
173
|
+
for (const id of createdFormIds) {
|
|
174
|
+
try { await sdk.api.forms.deleteOne(id) } catch (e) { /* ignore */ }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (require.main === module) {
|
|
180
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
181
|
+
const sdk = new Session({ host })
|
|
182
|
+
const sdkNonAdmin = new Session({ host })
|
|
183
|
+
|
|
184
|
+
const runTests = async () => {
|
|
185
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
186
|
+
await push_forms_to_portal_group_completion_tests({ sdk, sdkNonAdmin })
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
runTests()
|
|
190
|
+
.then(() => {
|
|
191
|
+
console.log("✅ Push forms to portal group completion test suite completed successfully")
|
|
192
|
+
process.exit(0)
|
|
193
|
+
})
|
|
194
|
+
.catch((error) => {
|
|
195
|
+
console.error("❌ Push forms to portal group completion test suite failed:", error)
|
|
196
|
+
process.exit(1)
|
|
197
|
+
})
|
|
198
|
+
}
|