@tellescope/sdk 1.256.6 → 1.256.8
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/enduser.d.ts +1 -1
- package/lib/cjs/enduser.d.ts.map +1 -1
- package/lib/cjs/enduser.js +1 -0
- package/lib/cjs/enduser.js.map +1 -1
- package/lib/cjs/tests/api_tests/calendar_event_create_and_bulk_triggers.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/calendar_event_create_and_bulk_triggers.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/calendar_event_create_and_bulk_triggers.test.js +505 -0
- package/lib/cjs/tests/api_tests/calendar_event_create_and_bulk_triggers.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/configurations_public_read.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/configurations_public_read.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/configurations_public_read.test.js +173 -0
- package/lib/cjs/tests/api_tests/configurations_public_read.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/form_started_trigger.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/form_started_trigger.test.js +87 -24
- package/lib/cjs/tests/api_tests/form_started_trigger.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/form_translations.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/form_translations.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/form_translations.test.js +259 -0
- package/lib/cjs/tests/api_tests/form_translations.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/group_event_attendee_self_update.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/group_event_attendee_self_update.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/group_event_attendee_self_update.test.js +293 -0
- package/lib/cjs/tests/api_tests/group_event_attendee_self_update.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/group_event_attendee_status_triggers.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/group_event_attendee_status_triggers.test.js +340 -124
- package/lib/cjs/tests/api_tests/group_event_attendee_status_triggers.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/mdi_trigger_refill.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/mdi_trigger_refill.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/mdi_trigger_refill.test.js +277 -0
- package/lib/cjs/tests/api_tests/mdi_trigger_refill.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/purchase_made_trigger.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/purchase_made_trigger.test.js +262 -78
- package/lib/cjs/tests/api_tests/purchase_made_trigger.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.d.ts +31 -0
- package/lib/cjs/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.js +315 -0
- package/lib/cjs/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/public_endpoint_auth_field_lock.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/security/public_endpoint_auth_field_lock.test.js +206 -198
- package/lib/cjs/tests/api_tests/security/public_endpoint_auth_field_lock.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.js +343 -80
- package/lib/cjs/tests/api_tests/stripe_webhook_triggers.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/welle_partner_token.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/welle_partner_token.test.js +74 -35
- package/lib/cjs/tests/api_tests/welle_partner_token.test.js.map +1 -1
- package/lib/cjs/tests/stripe_webhook_helpers.d.ts +134 -0
- package/lib/cjs/tests/stripe_webhook_helpers.d.ts.map +1 -1
- package/lib/cjs/tests/stripe_webhook_helpers.js +126 -1
- package/lib/cjs/tests/stripe_webhook_helpers.js.map +1 -1
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +242 -212
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/esm/enduser.d.ts +2 -1
- package/lib/esm/enduser.d.ts.map +1 -1
- package/lib/esm/enduser.js +1 -0
- package/lib/esm/enduser.js.map +1 -1
- package/lib/esm/sdk.d.ts +3 -2
- package/lib/esm/sdk.d.ts.map +1 -1
- package/lib/esm/session.d.ts +1 -0
- package/lib/esm/session.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/calendar_event_create_and_bulk_triggers.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/calendar_event_create_and_bulk_triggers.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/calendar_event_create_and_bulk_triggers.test.js +501 -0
- package/lib/esm/tests/api_tests/calendar_event_create_and_bulk_triggers.test.js.map +1 -0
- package/lib/esm/tests/api_tests/configurations_public_read.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/configurations_public_read.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/configurations_public_read.test.js +169 -0
- package/lib/esm/tests/api_tests/configurations_public_read.test.js.map +1 -0
- package/lib/esm/tests/api_tests/form_started_trigger.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/form_started_trigger.test.js +87 -24
- package/lib/esm/tests/api_tests/form_started_trigger.test.js.map +1 -1
- package/lib/esm/tests/api_tests/form_translations.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/form_translations.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/form_translations.test.js +255 -0
- package/lib/esm/tests/api_tests/form_translations.test.js.map +1 -0
- package/lib/esm/tests/api_tests/group_event_attendee_self_update.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/group_event_attendee_self_update.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/group_event_attendee_self_update.test.js +289 -0
- package/lib/esm/tests/api_tests/group_event_attendee_self_update.test.js.map +1 -0
- package/lib/esm/tests/api_tests/group_event_attendee_status_triggers.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/group_event_attendee_status_triggers.test.js +340 -124
- package/lib/esm/tests/api_tests/group_event_attendee_status_triggers.test.js.map +1 -1
- package/lib/esm/tests/api_tests/mdi_trigger_refill.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/mdi_trigger_refill.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/mdi_trigger_refill.test.js +273 -0
- package/lib/esm/tests/api_tests/mdi_trigger_refill.test.js.map +1 -0
- package/lib/esm/tests/api_tests/purchase_made_trigger.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/purchase_made_trigger.test.js +262 -78
- package/lib/esm/tests/api_tests/purchase_made_trigger.test.js.map +1 -1
- package/lib/esm/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.d.ts +31 -0
- package/lib/esm/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.js +311 -0
- package/lib/esm/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/public_endpoint_auth_field_lock.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/security/public_endpoint_auth_field_lock.test.js +206 -198
- package/lib/esm/tests/api_tests/security/public_endpoint_auth_field_lock.test.js.map +1 -1
- package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.js +344 -81
- package/lib/esm/tests/api_tests/stripe_webhook_triggers.test.js.map +1 -1
- package/lib/esm/tests/api_tests/welle_partner_token.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/welle_partner_token.test.js +74 -35
- package/lib/esm/tests/api_tests/welle_partner_token.test.js.map +1 -1
- package/lib/esm/tests/stripe_webhook_helpers.d.ts +134 -0
- package/lib/esm/tests/stripe_webhook_helpers.d.ts.map +1 -1
- package/lib/esm/tests/stripe_webhook_helpers.js +119 -0
- package/lib/esm/tests/stripe_webhook_helpers.js.map +1 -1
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +242 -212
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/enduser.ts +4 -2
- package/src/tests/api_tests/calendar_event_create_and_bulk_triggers.test.ts +293 -0
- package/src/tests/api_tests/configurations_public_read.test.ts +116 -0
- package/src/tests/api_tests/form_started_trigger.test.ts +49 -1
- package/src/tests/api_tests/form_translations.test.ts +205 -0
- package/src/tests/api_tests/group_event_attendee_self_update.test.ts +161 -0
- package/src/tests/api_tests/group_event_attendee_status_triggers.test.ts +135 -0
- package/src/tests/api_tests/mdi_trigger_refill.test.ts +184 -0
- package/src/tests/api_tests/purchase_made_trigger.test.ts +137 -0
- package/src/tests/api_tests/security/F-0006-develop-health-benefit-verification-rbac.test.ts +204 -0
- package/src/tests/api_tests/security/public_endpoint_auth_field_lock.test.ts +16 -6
- package/src/tests/api_tests/stripe_webhook_triggers.test.ts +221 -0
- package/src/tests/api_tests/welle_partner_token.test.ts +49 -6
- package/src/tests/stripe_webhook_helpers.ts +116 -0
- package/src/tests/tests.ts +29 -12
- package/test_generated.pdf +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import { Session } from "../../sdk"
|
|
4
|
+
import {
|
|
5
|
+
async_test,
|
|
6
|
+
log_header,
|
|
7
|
+
passOnAnyResult,
|
|
8
|
+
} from "@tellescope/testing"
|
|
9
|
+
import { MD_INTEGRATIONS_TITLE } from "@tellescope/constants"
|
|
10
|
+
import { setup_tests } from "../setup"
|
|
11
|
+
|
|
12
|
+
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
13
|
+
|
|
14
|
+
// MDI Trigger Refill journey action tests.
|
|
15
|
+
//
|
|
16
|
+
// Validator-acceptance plus handler-guard coverage: verifies that automation steps configured
|
|
17
|
+
// with the new mdiTriggerRefill action pass validation (and invalid shapes are rejected), and
|
|
18
|
+
// exercises the process endpoint's guard branches that return BEFORE any live MDI call (missing
|
|
19
|
+
// integration, missing patient reference) — no sandbox credentials needed. The happy path
|
|
20
|
+
// (fetch cases -> find scheduled orders -> submit) requires an MDI sandbox with a
|
|
21
|
+
// Direct-to-Pharmacy refill sequence and is covered by manual QA.
|
|
22
|
+
export const mdi_trigger_refill_tests = async ({ sdk, sdkNonAdmin }: { sdk: Session, sdkNonAdmin: Session }) => {
|
|
23
|
+
log_header("MDI Trigger Refill Journey Action Tests")
|
|
24
|
+
|
|
25
|
+
let journeyId: string | undefined
|
|
26
|
+
const stepIds: string[] = []
|
|
27
|
+
const automatedActionIds: string[] = []
|
|
28
|
+
let enduserId: string | undefined
|
|
29
|
+
let createdMDIIntegrationId: string | undefined
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const journey = await sdk.api.journeys.createOne({ title: 'MDI Trigger Refill Test Journey' })
|
|
33
|
+
journeyId = journey.id
|
|
34
|
+
|
|
35
|
+
// Full config accepted
|
|
36
|
+
await async_test(
|
|
37
|
+
"mdiTriggerRefill step accepted (onlySubmitWhenDue)",
|
|
38
|
+
() => sdk.api.automation_steps.createOne({
|
|
39
|
+
journeyId: journey.id,
|
|
40
|
+
events: [{ type: 'onJourneyStart', info: {} }],
|
|
41
|
+
action: {
|
|
42
|
+
type: 'mdiTriggerRefill',
|
|
43
|
+
info: { onlySubmitWhenDue: true },
|
|
44
|
+
},
|
|
45
|
+
}).then(s => { stepIds.push(s.id); return s }),
|
|
46
|
+
passOnAnyResult,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
// Empty info accepted (all fields optional)
|
|
50
|
+
await async_test(
|
|
51
|
+
"mdiTriggerRefill step accepted (empty info)",
|
|
52
|
+
() => sdk.api.automation_steps.createOne({
|
|
53
|
+
journeyId: journey.id,
|
|
54
|
+
events: [{ type: 'onJourneyStart', info: {} }],
|
|
55
|
+
action: {
|
|
56
|
+
type: 'mdiTriggerRefill',
|
|
57
|
+
info: {},
|
|
58
|
+
},
|
|
59
|
+
}).then(s => { stepIds.push(s.id); return s }),
|
|
60
|
+
passOnAnyResult,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
// Wrong type for onlySubmitWhenDue rejected
|
|
64
|
+
await async_test(
|
|
65
|
+
"mdiTriggerRefill step rejected (non-boolean onlySubmitWhenDue)",
|
|
66
|
+
() => sdk.api.automation_steps.createOne({
|
|
67
|
+
journeyId: journey.id,
|
|
68
|
+
events: [{ type: 'onJourneyStart', info: {} }],
|
|
69
|
+
action: {
|
|
70
|
+
type: 'mdiTriggerRefill',
|
|
71
|
+
info: { onlySubmitWhenDue: 'yes' },
|
|
72
|
+
} as any,
|
|
73
|
+
}),
|
|
74
|
+
{ shouldError: true, onError: () => true },
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
// Unexpected info fields are stripped by objectValidator, not rejected — assert they never
|
|
78
|
+
// reach the stored config (the step itself is accepted)
|
|
79
|
+
await async_test(
|
|
80
|
+
"mdiTriggerRefill step strips unexpected info field",
|
|
81
|
+
() => sdk.api.automation_steps.createOne({
|
|
82
|
+
journeyId: journey.id,
|
|
83
|
+
events: [{ type: 'onJourneyStart', info: {} }],
|
|
84
|
+
action: {
|
|
85
|
+
type: 'mdiTriggerRefill',
|
|
86
|
+
info: { onlySubmitWhenDue: false, formId: 'not-allowed' },
|
|
87
|
+
} as any,
|
|
88
|
+
}).then(s => { stepIds.push(s.id); return s }),
|
|
89
|
+
{
|
|
90
|
+
onResult: s => (
|
|
91
|
+
(s.action.info as any).onlySubmitWhenDue === false
|
|
92
|
+
&& (s.action.info as any).formId === undefined
|
|
93
|
+
),
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
// ── Handler guards (process endpoint) ────────────────────────────────────────
|
|
98
|
+
const enduser = await sdk.api.endusers.createOne({ fname: 'MDI', lname: 'RefillPatient' })
|
|
99
|
+
enduserId = enduser.id
|
|
100
|
+
|
|
101
|
+
// processAfter is set in the future so no background worker picks these up before the explicit call
|
|
102
|
+
const createAutomatedAction = () => sdk.api.automated_actions.createOne({
|
|
103
|
+
journeyId: journey!.id,
|
|
104
|
+
automationStepId: stepIds[0],
|
|
105
|
+
enduserId: enduser.id,
|
|
106
|
+
event: { type: 'onJourneyStart', info: {} },
|
|
107
|
+
action: { type: 'mdiTriggerRefill', info: {} },
|
|
108
|
+
status: 'active',
|
|
109
|
+
processAfter: new Date().getTime() + 60 * 60 * 1000,
|
|
110
|
+
} as any).then(a => { automatedActionIds.push(a.id); return a })
|
|
111
|
+
|
|
112
|
+
let mdiIntegrationExists = false
|
|
113
|
+
try {
|
|
114
|
+
const existing = await sdk.api.integrations.load_redacted({})
|
|
115
|
+
mdiIntegrationExists = !!existing.integrations.find((i: any) => i.title === MD_INTEGRATIONS_TITLE)
|
|
116
|
+
} catch { /* load not permitted — assume absent */ }
|
|
117
|
+
|
|
118
|
+
if (!mdiIntegrationExists) {
|
|
119
|
+
const noIntegrationAction = await createAutomatedAction()
|
|
120
|
+
await async_test(
|
|
121
|
+
"mdiTriggerRefill fails cleanly without an MDI integration",
|
|
122
|
+
() => sdk.api.automated_actions.process({ automatedActionId: noIntegrationAction.id }),
|
|
123
|
+
{ onResult: (r: any) => r?.success === false && /MD Integrations integration not found/i.test(r?.error || '') },
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const created = await sdk.api.integrations.createOne({
|
|
128
|
+
title: MD_INTEGRATIONS_TITLE,
|
|
129
|
+
authentication: {
|
|
130
|
+
type: 'oauth2',
|
|
131
|
+
info: { access_token: 'test-access-token', refresh_token: 'test-refresh-token', scope: '', token_type: 'Bearer', expiry_date: new Date().getTime() },
|
|
132
|
+
},
|
|
133
|
+
} as any)
|
|
134
|
+
createdMDIIntegrationId = created.id
|
|
135
|
+
mdiIntegrationExists = true
|
|
136
|
+
} catch (e) {
|
|
137
|
+
console.log("Could not create an MDI integration; skipping patient-reference guard test:", e)
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
console.log("⏭️ Skipping mdiTriggerRefill missing-integration guard test (an MDI integration already exists)")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (mdiIntegrationExists) {
|
|
144
|
+
// enduser has no MD Integrations reference — the guard returns before any outbound MDI call
|
|
145
|
+
const noReferenceAction = await createAutomatedAction()
|
|
146
|
+
await async_test(
|
|
147
|
+
"mdiTriggerRefill fails cleanly for an enduser with no MDI patient reference",
|
|
148
|
+
() => sdk.api.automated_actions.process({ automatedActionId: noReferenceAction.id }),
|
|
149
|
+
{ onResult: (r: any) => r?.success === false && /no MD Integrations patient reference/i.test(r?.error || '') },
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
} finally {
|
|
153
|
+
for (const id of automatedActionIds) {
|
|
154
|
+
await sdk.api.automated_actions.deleteOne(id).catch(console.error)
|
|
155
|
+
}
|
|
156
|
+
if (enduserId) await sdk.api.endusers.deleteOne(enduserId).catch(console.error)
|
|
157
|
+
if (createdMDIIntegrationId) await sdk.api.integrations.deleteOne(createdMDIIntegrationId).catch(console.error)
|
|
158
|
+
for (const id of stepIds) {
|
|
159
|
+
await sdk.api.automation_steps.deleteOne(id).catch(console.error)
|
|
160
|
+
}
|
|
161
|
+
if (journeyId) await sdk.api.journeys.deleteOne(journeyId).catch(console.error)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (require.main === module) {
|
|
166
|
+
console.log(`Using API URL: ${host}`)
|
|
167
|
+
const sdk = new Session({ host })
|
|
168
|
+
const sdkNonAdmin = new Session({ host })
|
|
169
|
+
|
|
170
|
+
const runTests = async () => {
|
|
171
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
172
|
+
await mdi_trigger_refill_tests({ sdk, sdkNonAdmin })
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
runTests()
|
|
176
|
+
.then(() => {
|
|
177
|
+
console.log("✅ MDI trigger refill test suite completed successfully")
|
|
178
|
+
process.exit(0)
|
|
179
|
+
})
|
|
180
|
+
.catch((error) => {
|
|
181
|
+
console.error("❌ MDI trigger refill test suite failed:", error)
|
|
182
|
+
process.exit(1)
|
|
183
|
+
})
|
|
184
|
+
}
|
|
@@ -111,6 +111,37 @@ export const purchase_made_trigger_tests = async ({ sdk, sdkNonAdmin } : { sdk:
|
|
|
111
111
|
const enduser13 = await sdk.api.endusers.createOne({})
|
|
112
112
|
const enduser14 = await sdk.api.endusers.createOne({})
|
|
113
113
|
|
|
114
|
+
// couponCodes filter triggers
|
|
115
|
+
const t10 = await sdk.api.automation_triggers.createOne({
|
|
116
|
+
event: { type: 'Purchase Made', info: { couponCodes: ['SUMMER20'] } },
|
|
117
|
+
action: { type: 'Add Tags', info: { tags: ['Purchase-Coupon-Summer20'] }},
|
|
118
|
+
status: 'Active',
|
|
119
|
+
title: "Purchase - Coupon SUMMER20"
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// Empty couponCodes array should behave like no filter
|
|
123
|
+
const t11 = await sdk.api.automation_triggers.createOne({
|
|
124
|
+
event: { type: 'Purchase Made', info: { couponCodes: [] } },
|
|
125
|
+
action: { type: 'Add Tags', info: { tags: ['Purchase-Coupon-Empty'] }},
|
|
126
|
+
status: 'Active',
|
|
127
|
+
title: "Purchase - Empty couponCodes Array"
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// Combined filter: title AND coupon must both match
|
|
131
|
+
const t12 = await sdk.api.automation_triggers.createOne({
|
|
132
|
+
event: { type: 'Purchase Made', info: { titles: ['Premium Course Package'], couponCodes: ['SUMMER20'] } },
|
|
133
|
+
action: { type: 'Add Tags', info: { tags: ['Purchase-Title-And-Coupon'] }},
|
|
134
|
+
status: 'Active',
|
|
135
|
+
title: "Purchase - Title and Coupon"
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const enduser15 = await sdk.api.endusers.createOne({})
|
|
139
|
+
const enduser16 = await sdk.api.endusers.createOne({})
|
|
140
|
+
const enduser17 = await sdk.api.endusers.createOne({})
|
|
141
|
+
const enduser18 = await sdk.api.endusers.createOne({})
|
|
142
|
+
const enduser19 = await sdk.api.endusers.createOne({})
|
|
143
|
+
const enduser20 = await sdk.api.endusers.createOne({})
|
|
144
|
+
|
|
114
145
|
// Test 1: Purchase without specific filters (should trigger t1 for any purchase)
|
|
115
146
|
const purchase1 = await sdk.api.purchases.createOne({
|
|
116
147
|
enduserId: enduser1.id,
|
|
@@ -321,6 +352,103 @@ export const purchase_made_trigger_tests = async ({ sdk, sdkNonAdmin } : { sdk:
|
|
|
321
352
|
{ onResult: (e: Enduser) => !!e.tags?.includes('Purchase-Any') && !e.tags?.includes('Purchase-No-Title-Test') }
|
|
322
353
|
)
|
|
323
354
|
|
|
355
|
+
// Test 15: exact coupon code match fires the coupon-scoped trigger
|
|
356
|
+
const purchase15 = await sdk.api.purchases.createOne({
|
|
357
|
+
enduserId: enduser15.id,
|
|
358
|
+
title: 'Coupon Test Purchase',
|
|
359
|
+
cost: { amount: 9999, currency: 'USD' },
|
|
360
|
+
processor: 'Stripe',
|
|
361
|
+
couponCodes: ['SUMMER20'],
|
|
362
|
+
})
|
|
363
|
+
await wait(undefined, 500)
|
|
364
|
+
await async_test(
|
|
365
|
+
"Purchase Made - Coupon Code Match (exact)",
|
|
366
|
+
() => sdk.api.endusers.getOne(enduser15.id),
|
|
367
|
+
{ onResult: (e: Enduser) => !!e.tags?.includes('Purchase-Coupon-Summer20') && !!e.tags?.includes('Purchase-Coupon-Empty') }
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
// Test 16: coupon match is case-sensitive — Stripe returns the canonical merchant-defined
|
|
371
|
+
// code, so a differently-cased value must NOT fire the coupon-scoped trigger
|
|
372
|
+
const purchase16 = await sdk.api.purchases.createOne({
|
|
373
|
+
enduserId: enduser16.id,
|
|
374
|
+
title: 'Coupon Test Purchase',
|
|
375
|
+
cost: { amount: 9999, currency: 'USD' },
|
|
376
|
+
processor: 'Stripe',
|
|
377
|
+
couponCodes: ['summer20'],
|
|
378
|
+
})
|
|
379
|
+
await wait(undefined, 500)
|
|
380
|
+
await async_test(
|
|
381
|
+
"Purchase Made - Coupon Code Case Mismatch (case sensitive, 'summer20' vs 'SUMMER20')",
|
|
382
|
+
() => sdk.api.endusers.getOne(enduser16.id),
|
|
383
|
+
{ onResult: (e: Enduser) => !!e.tags?.includes('Purchase-Any') && !e.tags?.includes('Purchase-Coupon-Summer20') }
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
// Test 17: a different coupon code does NOT fire the coupon-scoped trigger
|
|
387
|
+
const purchase17 = await sdk.api.purchases.createOne({
|
|
388
|
+
enduserId: enduser17.id,
|
|
389
|
+
title: 'Coupon Test Purchase',
|
|
390
|
+
cost: { amount: 9999, currency: 'USD' },
|
|
391
|
+
processor: 'Stripe',
|
|
392
|
+
couponCodes: ['WINTER10'],
|
|
393
|
+
})
|
|
394
|
+
await wait(undefined, 500)
|
|
395
|
+
await async_test(
|
|
396
|
+
"Purchase Made - Non-matching Coupon Code",
|
|
397
|
+
() => sdk.api.endusers.getOne(enduser17.id),
|
|
398
|
+
{ onResult: (e: Enduser) => !!e.tags?.includes('Purchase-Any') && !e.tags?.includes('Purchase-Coupon-Summer20') }
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
// Test 18: a purchase with no couponCodes never matches a coupon-scoped trigger, but
|
|
402
|
+
// unscoped and empty-array coupon triggers still fire (backward compat)
|
|
403
|
+
const purchase18 = await sdk.api.purchases.createOne({
|
|
404
|
+
enduserId: enduser18.id,
|
|
405
|
+
title: 'Coupon Test Purchase',
|
|
406
|
+
cost: { amount: 9999, currency: 'USD' },
|
|
407
|
+
processor: 'Stripe',
|
|
408
|
+
})
|
|
409
|
+
await wait(undefined, 500)
|
|
410
|
+
await async_test(
|
|
411
|
+
"Purchase Made - No Coupon Data (backward compat)",
|
|
412
|
+
() => sdk.api.endusers.getOne(enduser18.id),
|
|
413
|
+
{ onResult: (e: Enduser) => !!e.tags?.includes('Purchase-Any') && !!e.tags?.includes('Purchase-Coupon-Empty') && !e.tags?.includes('Purchase-Coupon-Summer20') }
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
// Test 19: combined titles + couponCodes filter fires when both match
|
|
417
|
+
const purchase19 = await sdk.api.purchases.createOne({
|
|
418
|
+
enduserId: enduser19.id,
|
|
419
|
+
title: 'Premium Course Package',
|
|
420
|
+
cost: { amount: 9999, currency: 'USD' },
|
|
421
|
+
processor: 'Stripe',
|
|
422
|
+
couponCodes: ['SUMMER20'],
|
|
423
|
+
})
|
|
424
|
+
await wait(undefined, 500)
|
|
425
|
+
await async_test(
|
|
426
|
+
"Purchase Made - Title AND Coupon Match",
|
|
427
|
+
() => sdk.api.endusers.getOne(enduser19.id),
|
|
428
|
+
{ onResult: (e: Enduser) => !!e.tags?.includes('Purchase-Title-And-Coupon') }
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
// Test 20: combined filter does NOT fire when the title matches but the coupon does not
|
|
432
|
+
const purchase20 = await sdk.api.purchases.createOne({
|
|
433
|
+
enduserId: enduser20.id,
|
|
434
|
+
title: 'Premium Course Package',
|
|
435
|
+
cost: { amount: 9999, currency: 'USD' },
|
|
436
|
+
processor: 'Stripe',
|
|
437
|
+
})
|
|
438
|
+
await wait(undefined, 500)
|
|
439
|
+
await async_test(
|
|
440
|
+
"Purchase Made - Title Matches But Coupon Missing (AND semantics)",
|
|
441
|
+
() => sdk.api.endusers.getOne(enduser20.id),
|
|
442
|
+
{ onResult: (e: Enduser) => !!e.tags?.includes('Purchase-Exact-Title') && !e.tags?.includes('Purchase-Title-And-Coupon') }
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
// Validator round-trip: couponCodes persists on the trigger's event.info
|
|
446
|
+
await async_test(
|
|
447
|
+
"Purchase Made - couponCodes round-trips on trigger info",
|
|
448
|
+
() => sdk.api.automation_triggers.getOne(t10.id),
|
|
449
|
+
{ onResult: t => JSON.stringify((t.event as any)?.info?.couponCodes) === JSON.stringify(['SUMMER20']) }
|
|
450
|
+
)
|
|
451
|
+
|
|
324
452
|
try {
|
|
325
453
|
// Clean up test data - triggers first
|
|
326
454
|
await sdk.api.automation_triggers.deleteOne(t1.id)
|
|
@@ -332,6 +460,9 @@ export const purchase_made_trigger_tests = async ({ sdk, sdkNonAdmin } : { sdk:
|
|
|
332
460
|
await sdk.api.automation_triggers.deleteOne(t7.id)
|
|
333
461
|
await sdk.api.automation_triggers.deleteOne(t8.id)
|
|
334
462
|
await sdk.api.automation_triggers.deleteOne(t9.id)
|
|
463
|
+
await sdk.api.automation_triggers.deleteOne(t10.id)
|
|
464
|
+
await sdk.api.automation_triggers.deleteOne(t11.id)
|
|
465
|
+
await sdk.api.automation_triggers.deleteOne(t12.id)
|
|
335
466
|
|
|
336
467
|
// Clean up products
|
|
337
468
|
await sdk.api.products.deleteOne(product1.id)
|
|
@@ -353,6 +484,12 @@ export const purchase_made_trigger_tests = async ({ sdk, sdkNonAdmin } : { sdk:
|
|
|
353
484
|
await sdk.api.endusers.deleteOne(enduser12.id)
|
|
354
485
|
await sdk.api.endusers.deleteOne(enduser13.id)
|
|
355
486
|
await sdk.api.endusers.deleteOne(enduser14.id)
|
|
487
|
+
await sdk.api.endusers.deleteOne(enduser15.id)
|
|
488
|
+
await sdk.api.endusers.deleteOne(enduser16.id)
|
|
489
|
+
await sdk.api.endusers.deleteOne(enduser17.id)
|
|
490
|
+
await sdk.api.endusers.deleteOne(enduser18.id)
|
|
491
|
+
await sdk.api.endusers.deleteOne(enduser19.id)
|
|
492
|
+
await sdk.api.endusers.deleteOne(enduser20.id)
|
|
356
493
|
} finally {}
|
|
357
494
|
}
|
|
358
495
|
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import { ObjectId } from 'bson'
|
|
4
|
+
import { Session } from "../../../sdk"
|
|
5
|
+
import {
|
|
6
|
+
async_test,
|
|
7
|
+
log_header,
|
|
8
|
+
wait,
|
|
9
|
+
} from "@tellescope/testing"
|
|
10
|
+
import { setup_tests } from "../../setup"
|
|
11
|
+
import { PROVIDER_PERMISSIONS } from "@tellescope/constants"
|
|
12
|
+
|
|
13
|
+
const host = process.env.API_URL || 'http://localhost:8080' as const
|
|
14
|
+
const [nonAdminEmail, nonAdminPassword] = [process.env.NON_ADMIN_EMAIL, process.env.NON_ADMIN_PASSWORD]
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Regression test for F-0006 (security-audit/findings/F-0006-zoom-and-benefit-verification-bypass-rbac.md),
|
|
18
|
+
* reopened 2026-08-17 for the `develop_health_run_benefit_verification` sub-item.
|
|
19
|
+
*
|
|
20
|
+
* `enduser_eligibility_results.develop_health_run_benefit_verification` is registered with
|
|
21
|
+
* `noAccessPermissions: true` (packages/private/api/api/v1/api.ts, ~line 24606). Before the fix,
|
|
22
|
+
* it had no in-handler check on `session.access?.endusers?.read` /
|
|
23
|
+
* `session.access?.enduser_eligibility_results?.create`, so it loaded the caller-supplied
|
|
24
|
+
* enduser's PHI via `DB.endusers.findById` and forwarded it to the Develop Health integration
|
|
25
|
+
* regardless of the caller's role. It also let a zero-access caller fingerprint an enduser's
|
|
26
|
+
* profile completeness via the distinct validation error messages returned for missing fields.
|
|
27
|
+
*
|
|
28
|
+
* This test:
|
|
29
|
+
* 1. Creates a role with explicit NO_ACCESS for `endusers` and `enduser_eligibility_results`.
|
|
30
|
+
* 2. Assigns the role to the non-admin user and re-authenticates.
|
|
31
|
+
* 3. Calls the endpoint with a fake enduserId — asserts a 403-equivalent access error, not the
|
|
32
|
+
* validation-error / integration-not-found path that would run pre-fix.
|
|
33
|
+
*
|
|
34
|
+
* Pre-fix: the handler proceeds past `DB.endusers.findById` and either 400s with a
|
|
35
|
+
* `validate_enduser_for_develop_health` message (enduser not found — since the id is fake) or,
|
|
36
|
+
* for a real target id, would load PHI and forward it to Develop Health. Either way, the access
|
|
37
|
+
* check never fires. Test fails (no access-denial signal).
|
|
38
|
+
*
|
|
39
|
+
* Post-fix: the handler throws 403 before touching `DB.endusers` at all. Test passes.
|
|
40
|
+
*/
|
|
41
|
+
export const develop_health_benefit_verification_rbac_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
42
|
+
log_header("F-0006: develop_health benefit verification RBAC regression")
|
|
43
|
+
|
|
44
|
+
const roleName = `f0006-develop-health-no-access-${Date.now()}`
|
|
45
|
+
let rbapId: string | undefined
|
|
46
|
+
const originalRoles = sdkNonAdmin.userInfo.roles
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// 1. Create a role that explicitly denies endusers access AND enduser_eligibility_results
|
|
50
|
+
// access. This is the realistic configuration the bypass affects: a tenant operator
|
|
51
|
+
// deliberately restricts a role from reading enduser PHI (e.g. for PHI-restricted
|
|
52
|
+
// integration/API-key roles).
|
|
53
|
+
const rbap = await sdk.api.role_based_access_permissions.createOne({
|
|
54
|
+
role: roleName,
|
|
55
|
+
permissions: {
|
|
56
|
+
...PROVIDER_PERMISSIONS,
|
|
57
|
+
endusers: { create: null, read: null, update: null, delete: null },
|
|
58
|
+
enduser_eligibility_results: { create: null, read: null, update: null, delete: null },
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
rbapId = rbap.id
|
|
62
|
+
|
|
63
|
+
// 2. Assign the role to the non-admin user and re-authenticate so the new
|
|
64
|
+
// session reflects the role's denied permissions.
|
|
65
|
+
await sdk.api.users.updateOne(
|
|
66
|
+
sdkNonAdmin.userInfo.id,
|
|
67
|
+
{ roles: [roleName] },
|
|
68
|
+
{ replaceObjectFields: true },
|
|
69
|
+
)
|
|
70
|
+
await wait(undefined, 1500)
|
|
71
|
+
await sdkNonAdmin.authenticate(nonAdminEmail!, nonAdminPassword!)
|
|
72
|
+
|
|
73
|
+
// 3. develop_health_run_benefit_verification must throw 403 BEFORE any enduser lookup.
|
|
74
|
+
// Pre-fix: proceeds to DB.endusers.findById and either 400s on validation (fake id) or
|
|
75
|
+
// succeeds and forwards PHI to Develop Health (real id) — never a 403.
|
|
76
|
+
await async_test(
|
|
77
|
+
"F-0006: enduser_eligibility_results.develop_health_run_benefit_verification must throw 403 when role denies endusers/enduser_eligibility_results",
|
|
78
|
+
() => sdkNonAdmin.api.enduser_eligibility_results.develop_health_run_benefit_verification({
|
|
79
|
+
enduserId: new ObjectId().toHexString(), // fake id — access check should fire first
|
|
80
|
+
providerUserId: new ObjectId().toHexString(), // fake id — access check should fire first
|
|
81
|
+
insuranceType: 'primary',
|
|
82
|
+
diagnoses: [],
|
|
83
|
+
// drugs uses a plain (non-empty-ok) list validator, so a real-shaped entry is required
|
|
84
|
+
// to pass request validation and actually reach the handler's access check.
|
|
85
|
+
drugs: [{ name: 'Test Drug', dosage: '10mg', quantity: 1 }],
|
|
86
|
+
drug_history: { currently_taking_drugs: [], previously_taken_drugs: [] },
|
|
87
|
+
} as any),
|
|
88
|
+
{
|
|
89
|
+
shouldError: true,
|
|
90
|
+
onError: (e: any) => {
|
|
91
|
+
// Match the gate's exact message. The SDK error surfaces { message, info } (no top-level
|
|
92
|
+
// status/code), so we key on the message rather than the HTTP status. This is deliberately
|
|
93
|
+
// specific: a lapsed subscription ("Your subscription has expired") or an expired token
|
|
94
|
+
// ("Unauthenticated") also errors BEFORE the handler runs, but neither contains this string,
|
|
95
|
+
// so the test can't false-pass without the gate executing. Also reject the pre-fix
|
|
96
|
+
// downstream messages, which would indicate the access check never fired.
|
|
97
|
+
const msg = (e?.message ?? '').toLowerCase()
|
|
98
|
+
if (msg.includes('integration not found') || msg.includes('enduser is required')
|
|
99
|
+
|| msg.includes('organization') || msg.includes('provider')) {
|
|
100
|
+
return false // reached past the access gate — regression
|
|
101
|
+
}
|
|
102
|
+
return msg.includes('access to this resource')
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
// 4. POSITIVE case: a provider role WITH enduser access must PASS the gate and reach the
|
|
108
|
+
// integration stage (this test org has no Develop Health integration, so it 400s with
|
|
109
|
+
// "Integration not found"). This guards against the gate over-blocking legitimate users —
|
|
110
|
+
// the failure mode a future tightening (e.g. gating on 'All') would introduce.
|
|
111
|
+
const providerRoleName = `f0006-develop-health-provider-${Date.now()}`
|
|
112
|
+
let providerRbapId: string | undefined
|
|
113
|
+
try {
|
|
114
|
+
const providerRbap = await sdk.api.role_based_access_permissions.createOne({
|
|
115
|
+
role: providerRoleName,
|
|
116
|
+
permissions: { ...PROVIDER_PERMISSIONS }, // endusers read/create non-null, eligibility create non-null
|
|
117
|
+
})
|
|
118
|
+
providerRbapId = providerRbap.id
|
|
119
|
+
|
|
120
|
+
await sdk.api.users.updateOne(sdkNonAdmin.userInfo.id, { roles: [providerRoleName] }, { replaceObjectFields: true })
|
|
121
|
+
await wait(undefined, 1500)
|
|
122
|
+
await sdkNonAdmin.authenticate(nonAdminEmail!, nonAdminPassword!)
|
|
123
|
+
|
|
124
|
+
await async_test(
|
|
125
|
+
"F-0006: a provider role with enduser access passes the gate (reaches 'Integration not found', not 403)",
|
|
126
|
+
() => sdkNonAdmin.api.enduser_eligibility_results.develop_health_run_benefit_verification({
|
|
127
|
+
enduserId: new ObjectId().toHexString(),
|
|
128
|
+
providerUserId: new ObjectId().toHexString(),
|
|
129
|
+
insuranceType: 'primary',
|
|
130
|
+
diagnoses: [],
|
|
131
|
+
drugs: [{ name: 'Test Drug', dosage: '10mg', quantity: 1 }],
|
|
132
|
+
drug_history: { currently_taking_drugs: [], previously_taken_drugs: [] },
|
|
133
|
+
} as any),
|
|
134
|
+
{
|
|
135
|
+
shouldError: true,
|
|
136
|
+
onError: (e: any) => {
|
|
137
|
+
const msg = (e?.message ?? '').toLowerCase()
|
|
138
|
+
// Must NOT be the access-denial message (that would mean the gate wrongly blocked a
|
|
139
|
+
// provider). Reaching "Integration not found" (or org/provider validation) proves the
|
|
140
|
+
// gate passed and the read-scoped enduser load did not over-restrict a legitimate role.
|
|
141
|
+
if (msg.includes('access to this resource')) return false
|
|
142
|
+
return msg.includes('integration not found') || msg.includes('organization') || msg.includes('provider')
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
)
|
|
146
|
+
} finally {
|
|
147
|
+
if (providerRbapId) {
|
|
148
|
+
try {
|
|
149
|
+
await sdk.api.users.updateOne(sdkNonAdmin.userInfo.id, { roles: originalRoles ?? [] }, { replaceObjectFields: true })
|
|
150
|
+
} catch {}
|
|
151
|
+
try { await sdk.api.role_based_access_permissions.deleteOne(providerRbapId) } catch {}
|
|
152
|
+
await wait(undefined, 1500)
|
|
153
|
+
try { await sdkNonAdmin.authenticate(nonAdminEmail!, nonAdminPassword!) } catch {}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// NOTE — the create>read bypass (endusers.create broader than endusers.read) that the 2026-08-19
|
|
158
|
+
// read-scoping closes is NOT live-testable here: this org has no Develop Health integration, so
|
|
159
|
+
// the handler throws "Integration not found" BEFORE reaching the (now read-scoped) enduser load,
|
|
160
|
+
// and the enduser load is what the create>read fix governs. Exercising it would need either a real
|
|
161
|
+
// Develop Health integration or reordering the handler purely for the test. It is covered by code
|
|
162
|
+
// review + F-0166 (systemic follow-up). If a Develop Health sandbox integration is ever available
|
|
163
|
+
// in the test org, add: role { endusers: create 'All', read 'Assigned' } → call with a real
|
|
164
|
+
// enduser NOT assigned to the caller → expect the enduser-not-found path, not a successful forward.
|
|
165
|
+
} finally {
|
|
166
|
+
// Cleanup: restore original roles, delete the test role.
|
|
167
|
+
try {
|
|
168
|
+
await sdk.api.users.updateOne(
|
|
169
|
+
sdkNonAdmin.userInfo.id,
|
|
170
|
+
{ roles: originalRoles ?? [] },
|
|
171
|
+
{ replaceObjectFields: true },
|
|
172
|
+
)
|
|
173
|
+
} catch {}
|
|
174
|
+
if (rbapId) {
|
|
175
|
+
try { await sdk.api.role_based_access_permissions.deleteOne(rbapId) } catch {}
|
|
176
|
+
}
|
|
177
|
+
// Re-authenticate the non-admin to drop the no-access role from their JWT
|
|
178
|
+
// before subsequent tests run.
|
|
179
|
+
await wait(undefined, 1500)
|
|
180
|
+
try { await sdkNonAdmin.authenticate(nonAdminEmail!, nonAdminPassword!) } catch {}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Allow running this test file independently
|
|
185
|
+
if (require.main === module) {
|
|
186
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
187
|
+
const sdk = new Session({ host })
|
|
188
|
+
const sdkNonAdmin = new Session({ host })
|
|
189
|
+
|
|
190
|
+
const runTests = async () => {
|
|
191
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
192
|
+
await develop_health_benefit_verification_rbac_tests({ sdk, sdkNonAdmin })
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
runTests()
|
|
196
|
+
.then(() => {
|
|
197
|
+
console.log("✅ F-0006 develop_health_run_benefit_verification RBAC test suite completed successfully")
|
|
198
|
+
process.exit(0)
|
|
199
|
+
})
|
|
200
|
+
.catch((error) => {
|
|
201
|
+
console.error("❌ F-0006 develop_health_run_benefit_verification RBAC test suite failed:", error)
|
|
202
|
+
process.exit(1)
|
|
203
|
+
})
|
|
204
|
+
}
|
|
@@ -327,12 +327,22 @@ export const public_endpoint_auth_field_lock_tests = async (
|
|
|
327
327
|
|
|
328
328
|
// Throwaway host user with all-7-day availability, so G9 has slots to find without depending on
|
|
329
329
|
// (or mutating) the shared test user's calendar configuration.
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
330
|
+
// Stable email + get-or-create (rather than a per-run address): the cleanup below is not in a
|
|
331
|
+
// finally, and reset_db does not clear the users collection, so an interrupted run strands this
|
|
332
|
+
// user permanently. Reusing one address means at most one can ever exist — otherwise every
|
|
333
|
+
// interrupted run adds another provider to org-wide availability queries, which are capped at 25
|
|
334
|
+
// users sorted newest-first and would eventually evict the real test users.
|
|
335
|
+
const hostEmail = 'lock-host@tellescope.com'
|
|
336
|
+
const hostUser = (
|
|
337
|
+
await sdk.api.users.getOne({ email: hostEmail }).catch(() => null)
|
|
338
|
+
) || (
|
|
339
|
+
await sdk.api.users.createOne({
|
|
340
|
+
email: hostEmail,
|
|
341
|
+
verifiedEmail: true,
|
|
342
|
+
fname: 'Lock',
|
|
343
|
+
lname: 'Host',
|
|
344
|
+
})
|
|
345
|
+
)
|
|
336
346
|
createdUserIds.push(hostUser.id)
|
|
337
347
|
await sdk.api.users.updateOne(hostUser.id, {
|
|
338
348
|
weeklyAvailabilities: [0, 1, 2, 3, 4, 5, 6].map(dayOfWeekStartingSundayIndexedByZero => ({
|