@tellescope/sdk 1.250.1 → 1.250.2
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/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/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/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +149 -141
- 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/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/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/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +149 -141
- 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/tests/api_tests/enduser_cross_access_isolation.test.ts +26 -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/tests.ts +5 -1
- 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
|
+
}
|
package/src/tests/tests.ts
CHANGED
|
@@ -78,6 +78,7 @@ import { eom_procedure_codes_tests } from "./api_tests/eom_procedure_codes.test"
|
|
|
78
78
|
import { cross_org_api_key_tests } from "./api_tests/cross_org_api_key.test";
|
|
79
79
|
import { custom_dashboards_tests } from "./api_tests/custom_dashboards.test";
|
|
80
80
|
import { message_assignment_trigger_tests } from "./api_tests/message_assignment_trigger.test";
|
|
81
|
+
import { outbound_chat_sent_trigger_tests } from "./api_tests/outbound_chat_sent_trigger.test";
|
|
81
82
|
import { time_tracks_tests, time_tracks_historical_tests, time_tracks_correction_tests, time_tracks_review_tests, time_tracks_lock_tests, time_tracks_edge_case_tests } from "./api_tests/time_tracks.test";
|
|
82
83
|
import { monthly_availability_restrictions_tests } from "./api_tests/monthly_availability_restrictions.test";
|
|
83
84
|
import { calendar_event_limits_tests } from "./api_tests/calendar_event_limits.test";
|
|
@@ -95,6 +96,7 @@ import { load_team_chat_tests } from "./api_tests/load_team_chat.test";
|
|
|
95
96
|
import { form_started_trigger_tests } from "./api_tests/form_started_trigger.test";
|
|
96
97
|
import { form_submitted_trigger_tests } from "./api_tests/form_submitted_trigger.test";
|
|
97
98
|
import { medication_added_trigger_tests } from "./api_tests/medication_added_trigger.test";
|
|
99
|
+
import { conditional_logic_medication_unit_tests } from "./unit_tests/conditional_logic_medication.test";
|
|
98
100
|
import { elation_user_id_tests } from "./api_tests/elation_user_id.test";
|
|
99
101
|
import { organization_settings_duplicates_tests } from "./api_tests/organization_settings_duplicates.test";
|
|
100
102
|
import { calendar_events_bulk_update_tests } from "./api_tests/calendar_events_bulk_update.test";
|
|
@@ -14303,10 +14305,13 @@ const ip_address_form_tests = async () => {
|
|
|
14303
14305
|
|
|
14304
14306
|
|
|
14305
14307
|
await enduser_conditional_logic_tests()
|
|
14308
|
+
await conditional_logic_medication_unit_tests()
|
|
14306
14309
|
await replace_enduser_template_values_tests()
|
|
14307
14310
|
await replace_form_field_template_values_tests()
|
|
14308
14311
|
await mfa_tests()
|
|
14309
14312
|
await setup_tests(sdk, sdkNonAdmin)
|
|
14313
|
+
await outbound_chat_sent_trigger_tests({ sdk })
|
|
14314
|
+
await automation_trigger_tests()
|
|
14310
14315
|
await enduser_cross_access_isolation_tests({ sdk, sdkNonAdmin })
|
|
14311
14316
|
await eom_procedure_codes_tests({ sdk, sdkNonAdmin })
|
|
14312
14317
|
await cross_org_api_key_tests({ sdk, sdkNonAdmin })
|
|
@@ -14317,7 +14322,6 @@ const ip_address_form_tests = async () => {
|
|
|
14317
14322
|
await form_submitted_trigger_tests({ sdk, sdkNonAdmin })
|
|
14318
14323
|
await date_string_validation_tests({ sdk, sdkNonAdmin })
|
|
14319
14324
|
await openloop_webhooks_tests({ sdk, sdkNonAdmin })
|
|
14320
|
-
await automation_trigger_tests()
|
|
14321
14325
|
await integrations_redacted_tests({ sdk, sdkNonAdmin })
|
|
14322
14326
|
await mdb_sort_tests({ sdk, sdkNonAdmin })
|
|
14323
14327
|
await search_tests()
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
evaluate_conditional_logic_for_medication_title,
|
|
3
|
+
evaluate_string_field_comparison,
|
|
4
|
+
} from "@tellescope/utilities"
|
|
5
|
+
import { CompoundFilter } from "@tellescope/types-models"
|
|
6
|
+
|
|
7
|
+
let failures = 0
|
|
8
|
+
let passed = 0
|
|
9
|
+
|
|
10
|
+
const assert = (name: string, actual: boolean, expected: boolean) => {
|
|
11
|
+
if (actual === expected) {
|
|
12
|
+
passed++
|
|
13
|
+
console.log(` ✓ ${name}`)
|
|
14
|
+
} else {
|
|
15
|
+
failures++
|
|
16
|
+
console.error(` ✗ ${name} — expected ${expected}, got ${actual}`)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const run_string_comparison_tests = () => {
|
|
21
|
+
console.log("\n[evaluate_string_field_comparison]")
|
|
22
|
+
|
|
23
|
+
// Plain-string equals (implicit operator)
|
|
24
|
+
assert("equals plain string match", evaluate_string_field_comparison('Aspirin', 'Aspirin'), true)
|
|
25
|
+
assert("equals plain string mismatch", evaluate_string_field_comparison('Aspirin', 'Lisinopril'), false)
|
|
26
|
+
assert("equals empty string against empty title", evaluate_string_field_comparison('', ''), true)
|
|
27
|
+
assert("equals empty string against undefined title", evaluate_string_field_comparison(undefined, ''), true)
|
|
28
|
+
|
|
29
|
+
// $ne
|
|
30
|
+
assert("$ne matches when different", evaluate_string_field_comparison('Aspirin', { $ne: 'Lisinopril' }), true)
|
|
31
|
+
assert("$ne fails when equal", evaluate_string_field_comparison('Aspirin', { $ne: 'Aspirin' }), false)
|
|
32
|
+
|
|
33
|
+
// $contains (case sensitive — consistent with enduser/form-response evaluators)
|
|
34
|
+
assert("$contains substring match", evaluate_string_field_comparison('Semaglutide GLP-1', { $contains: 'GLP' }), true)
|
|
35
|
+
assert("$contains case sensitive miss", evaluate_string_field_comparison('Lisinopril 10MG', { $contains: 'mg' }), false)
|
|
36
|
+
assert("$contains case sensitive match", evaluate_string_field_comparison('Lisinopril 10mg', { $contains: 'mg' }), true)
|
|
37
|
+
assert("$contains no match", evaluate_string_field_comparison('Aspirin', { $contains: 'GLP' }), false)
|
|
38
|
+
assert("$contains empty string is always true", evaluate_string_field_comparison('Aspirin', { $contains: '' }), true)
|
|
39
|
+
|
|
40
|
+
// $doesNotContain
|
|
41
|
+
assert("$doesNotContain mismatch true", evaluate_string_field_comparison('Aspirin', { $doesNotContain: 'GLP' }), true)
|
|
42
|
+
assert("$doesNotContain match false", evaluate_string_field_comparison('Semaglutide', { $doesNotContain: 'Sema' }), false)
|
|
43
|
+
assert("$doesNotContain case sensitive (different case = no match)", evaluate_string_field_comparison('Placebo', { $doesNotContain: 'PLACEBO' }), true)
|
|
44
|
+
|
|
45
|
+
// $exists
|
|
46
|
+
assert("$exists:true on present", evaluate_string_field_comparison('Aspirin', { $exists: true }), true)
|
|
47
|
+
assert("$exists:true on empty", evaluate_string_field_comparison('', { $exists: true }), false)
|
|
48
|
+
assert("$exists:true on undefined", evaluate_string_field_comparison(undefined, { $exists: true }), false)
|
|
49
|
+
assert("$exists:false on present", evaluate_string_field_comparison('Aspirin', { $exists: false }), false)
|
|
50
|
+
assert("$exists:false on undefined", evaluate_string_field_comparison(undefined, { $exists: false }), true)
|
|
51
|
+
|
|
52
|
+
// null operator → treated as "no value set"
|
|
53
|
+
assert("null operator on undefined title", evaluate_string_field_comparison(undefined, null), true)
|
|
54
|
+
assert("null operator on present title", evaluate_string_field_comparison('Aspirin', null), false)
|
|
55
|
+
|
|
56
|
+
// Unknown operator → returns true (don't suppress triggers on bad data)
|
|
57
|
+
assert("unknown operator returns true", evaluate_string_field_comparison('Aspirin', { $weirdOp: 'x' } as any), true)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const run_conditional_logic_tests = () => {
|
|
61
|
+
console.log("\n[evaluate_conditional_logic_for_medication_title]")
|
|
62
|
+
|
|
63
|
+
// Single condition (case-sensitive)
|
|
64
|
+
const c1: CompoundFilter<'title'> = { condition: { title: { $contains: 'GLP' } } }
|
|
65
|
+
assert("single $contains true", evaluate_conditional_logic_for_medication_title('Semaglutide GLP-1', c1), true)
|
|
66
|
+
assert("single $contains false", evaluate_conditional_logic_for_medication_title('Aspirin', c1), false)
|
|
67
|
+
assert("single $contains case-sensitive miss", evaluate_conditional_logic_for_medication_title('Semaglutide glp-1', c1), false)
|
|
68
|
+
|
|
69
|
+
// Plain string default-equals
|
|
70
|
+
const c2: CompoundFilter<'title'> = { condition: { title: 'Aspirin' } }
|
|
71
|
+
assert("plain equals true", evaluate_conditional_logic_for_medication_title('Aspirin', c2), true)
|
|
72
|
+
assert("plain equals false", evaluate_conditional_logic_for_medication_title('Lisinopril', c2), false)
|
|
73
|
+
|
|
74
|
+
// $and (both true)
|
|
75
|
+
const cAnd: CompoundFilter<'title'> = {
|
|
76
|
+
$and: [
|
|
77
|
+
{ condition: { title: { $contains: 'mg' } } },
|
|
78
|
+
{ condition: { title: { $doesNotContain: 'Placebo' } } },
|
|
79
|
+
],
|
|
80
|
+
}
|
|
81
|
+
assert("$and both pass", evaluate_conditional_logic_for_medication_title('Lisinopril 10mg', cAnd), true)
|
|
82
|
+
assert("$and first fails", evaluate_conditional_logic_for_medication_title('Lisinopril', cAnd), false)
|
|
83
|
+
assert("$and second fails", evaluate_conditional_logic_for_medication_title('Placebo 5mg', cAnd), false)
|
|
84
|
+
|
|
85
|
+
// $or
|
|
86
|
+
const cOr: CompoundFilter<'title'> = {
|
|
87
|
+
$or: [
|
|
88
|
+
{ condition: { title: 'Aspirin' } },
|
|
89
|
+
{ condition: { title: { $contains: 'pril' } } },
|
|
90
|
+
],
|
|
91
|
+
}
|
|
92
|
+
assert("$or first matches", evaluate_conditional_logic_for_medication_title('Aspirin', cOr), true)
|
|
93
|
+
assert("$or second matches", evaluate_conditional_logic_for_medication_title('Lisinopril', cOr), true)
|
|
94
|
+
assert("$or neither matches", evaluate_conditional_logic_for_medication_title('Metformin', cOr), false)
|
|
95
|
+
|
|
96
|
+
// Mixed compound: $and containing $or — the canonical "compound" case
|
|
97
|
+
const cMixed: CompoundFilter<'title'> = {
|
|
98
|
+
$and: [
|
|
99
|
+
{ condition: { title: { $contains: 'mg' } } },
|
|
100
|
+
{
|
|
101
|
+
$or: [
|
|
102
|
+
{ condition: { title: { $contains: 'Lisin' } } },
|
|
103
|
+
{ condition: { title: { $contains: 'Metform' } } },
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
}
|
|
108
|
+
assert("$and+$or first or-branch matches", evaluate_conditional_logic_for_medication_title('Lisinopril 10mg', cMixed), true)
|
|
109
|
+
assert("$and+$or second or-branch matches", evaluate_conditional_logic_for_medication_title('Metformin 500mg', cMixed), true)
|
|
110
|
+
assert("$and+$or and-branch fails", evaluate_conditional_logic_for_medication_title('Lisinopril', cMixed), false)
|
|
111
|
+
assert("$and+$or or-branch fails", evaluate_conditional_logic_for_medication_title('Aspirin 81mg', cMixed), false)
|
|
112
|
+
|
|
113
|
+
// Empty / no-op
|
|
114
|
+
assert("empty conditions returns true", evaluate_conditional_logic_for_medication_title('Aspirin', {}), true)
|
|
115
|
+
|
|
116
|
+
// Unknown operator inside condition → returns true (safe)
|
|
117
|
+
const cUnknown: CompoundFilter<'title'> = { condition: { title: { $regex: 'foo' } as any } }
|
|
118
|
+
assert("unknown operator is permissive", evaluate_conditional_logic_for_medication_title('Aspirin', cUnknown), true)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const run_all = () => {
|
|
122
|
+
console.log("Running conditional_logic_medication unit tests")
|
|
123
|
+
run_string_comparison_tests()
|
|
124
|
+
run_conditional_logic_tests()
|
|
125
|
+
console.log(`\nResults: ${passed} passed, ${failures} failed`)
|
|
126
|
+
if (failures > 0) process.exit(1)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (require.main === module) {
|
|
130
|
+
run_all()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { run_all as conditional_logic_medication_unit_tests }
|
package/test_generated.pdf
CHANGED
|
Binary file
|