@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
|
@@ -4,7 +4,13 @@ import { Session } from "../../sdk"
|
|
|
4
4
|
import { assert, log_header, wait } from "@tellescope/testing"
|
|
5
5
|
import { setup_tests } from "../setup"
|
|
6
6
|
import {
|
|
7
|
+
charge_succeeded_event,
|
|
7
8
|
cleanup_stripe_test_integrations,
|
|
9
|
+
discount_with_coupon,
|
|
10
|
+
discount_with_promo_code,
|
|
11
|
+
discount_with_promo_code_ref,
|
|
12
|
+
invoice_paid_event,
|
|
13
|
+
invoice_paid_line,
|
|
8
14
|
invoice_payment_failed_event,
|
|
9
15
|
item_with_expanded_price_product,
|
|
10
16
|
item_with_plan_product,
|
|
@@ -640,6 +646,221 @@ export const stripe_webhook_triggers_tests = async ({ sdk, sdkNonAdmin } : { sdk
|
|
|
640
646
|
}
|
|
641
647
|
assert(rejectedInvalidPausedProductId, 'expected a non-ObjectId productId to be rejected', 'invalid Subscription Paused productId rejected')
|
|
642
648
|
|
|
649
|
+
/* ============ invoice.paid / charge.succeeded — coupon codes ============ */
|
|
650
|
+
|
|
651
|
+
// Purchases are created by the webhook handler itself, so persistence is polled the same way
|
|
652
|
+
// tags are. Cleaned up automatically when the enduser is deleted (onDependencyDelete: 'delete').
|
|
653
|
+
const poll_for_purchase = async (enduserId: string, iterations = 20) => {
|
|
654
|
+
for (let i = 0; i < iterations; i++) {
|
|
655
|
+
await wait(undefined, 500)
|
|
656
|
+
const purchases = await sdk.api.purchases.getSome({ filter: { enduserId } })
|
|
657
|
+
if (purchases.length) return purchases
|
|
658
|
+
}
|
|
659
|
+
return []
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// One webhook POST per case: a Purchase Made trigger (coupon-scoped unless couponCodes is
|
|
663
|
+
// undefined) and an invoice.paid built by the caller.
|
|
664
|
+
const run_coupon_invoice_case = async ({ label, couponCodes, discounts, discount, shouldFire, title } : {
|
|
665
|
+
label: string,
|
|
666
|
+
couponCodes?: string[],
|
|
667
|
+
discounts?: (object | string)[],
|
|
668
|
+
discount?: object,
|
|
669
|
+
shouldFire: boolean,
|
|
670
|
+
title: string,
|
|
671
|
+
}) => {
|
|
672
|
+
const caseTag = tag(label)
|
|
673
|
+
const enduser = await new_enduser(label)
|
|
674
|
+
await new_trigger({
|
|
675
|
+
title: `Stripe purchase made (${label}) ${RUN}`,
|
|
676
|
+
event: { type: 'Purchase Made', info: couponCodes !== undefined ? { couponCodes } : {} },
|
|
677
|
+
tags: [caseTag],
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
const res = await post_stripe_webhook({
|
|
681
|
+
stripePublicKey,
|
|
682
|
+
secret: webhooksSecret,
|
|
683
|
+
event: invoice_paid_event({
|
|
684
|
+
stripeCustomerId: enduser.stripeCustomerId,
|
|
685
|
+
lines: [invoice_paid_line({ description: `Coupon Test ${label} ${RUN}` })],
|
|
686
|
+
discounts,
|
|
687
|
+
discount,
|
|
688
|
+
}),
|
|
689
|
+
})
|
|
690
|
+
assert(res.status === 200, `expected 200, got ${res.status} (${res.data})`, `${label} webhook accepted`)
|
|
691
|
+
|
|
692
|
+
if (shouldFire) await expect_fired(enduser.id, caseTag, title)
|
|
693
|
+
else await expect_not_fired(enduser.id, caseTag, title)
|
|
694
|
+
|
|
695
|
+
return enduser
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// C1: the feature — a coupon-scoped trigger fires for an invoice discounted with that promo
|
|
699
|
+
// code, and the code is persisted on the purchase record. The expanded promotion_code object
|
|
700
|
+
// reflects the post-expansion shape (and the checkout-session breakdown shape), not the raw
|
|
701
|
+
// webhook — it's the only way to E2E-prove the promo-code match without outbound Stripe calls;
|
|
702
|
+
// C1b covers the raw shape.
|
|
703
|
+
const c1Enduser = await run_coupon_invoice_case({
|
|
704
|
+
label: 'c1-matching-promo',
|
|
705
|
+
couponCodes: ['SUMMER20'],
|
|
706
|
+
discounts: [discount_with_promo_code('SUMMER20')],
|
|
707
|
+
shouldFire: true,
|
|
708
|
+
title: 'coupon-scoped Purchase Made trigger fires for a matching promo code',
|
|
709
|
+
})
|
|
710
|
+
const c1Purchases = await poll_for_purchase(c1Enduser.id)
|
|
711
|
+
assert(
|
|
712
|
+
c1Purchases.some(p => (p as any).couponCodes?.includes('SUMMER20')),
|
|
713
|
+
`expected a purchase with couponCodes containing SUMMER20, got ${JSON.stringify(c1Purchases.map(p => (p as any).couponCodes))}`,
|
|
714
|
+
'couponCodes persisted on the webhook-created purchase',
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
// C1b: the raw webhook shape, verified against a live sandbox invoice.paid event — the single
|
|
718
|
+
// discount embedded at invoice.discount, only its bare "di_..." string id in invoice.discounts
|
|
719
|
+
// (the dedupe must not trigger a re-fetch), and promotion_code as a bare promo_... string whose
|
|
720
|
+
// retrieve fails under this suite's fake API key. The coupon name + id are still captured
|
|
721
|
+
// call-free, a name-scoped trigger fires, and the failed promo retrieve is proven non-fatal.
|
|
722
|
+
// (No absence assertion on the promo code: in production the retrieve succeeds.)
|
|
723
|
+
const c1bDiscount = discount_with_promo_code_ref({ couponId: `coupon_test_c1b_${RUN}`, couponName: 'Test coupon' })
|
|
724
|
+
const c1bEnduser = await run_coupon_invoice_case({
|
|
725
|
+
label: 'c1b-real-shape',
|
|
726
|
+
couponCodes: ['Test coupon'],
|
|
727
|
+
discount: c1bDiscount,
|
|
728
|
+
discounts: [(c1bDiscount as any).id],
|
|
729
|
+
shouldFire: true,
|
|
730
|
+
title: 'real-shaped invoice.paid payload matches via coupon name',
|
|
731
|
+
})
|
|
732
|
+
const c1bPurchases = await poll_for_purchase(c1bEnduser.id)
|
|
733
|
+
assert(
|
|
734
|
+
c1bPurchases.some(p =>
|
|
735
|
+
(p as any).couponCodes?.includes('Test coupon') && (p as any).couponCodes?.includes(`coupon_test_c1b_${RUN}`)
|
|
736
|
+
),
|
|
737
|
+
`expected couponCodes with the coupon name and id, got ${JSON.stringify(c1bPurchases.map(p => (p as any).couponCodes))}`,
|
|
738
|
+
'coupon name and id persisted from a real-shaped payload',
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
// C2: matching is case-sensitive — Stripe sends the canonical merchant-defined code, so a
|
|
742
|
+
// differently-cased trigger value must stay quiet (staff can list multiple variants if needed)
|
|
743
|
+
await run_coupon_invoice_case({
|
|
744
|
+
label: 'c2-case-mismatch',
|
|
745
|
+
couponCodes: ['summer20'],
|
|
746
|
+
discounts: [discount_with_promo_code('SUMMER20')],
|
|
747
|
+
shouldFire: false,
|
|
748
|
+
title: 'coupon match is case-sensitive: a differently-cased code does not fire',
|
|
749
|
+
})
|
|
750
|
+
|
|
751
|
+
// C3: a different code stays quiet
|
|
752
|
+
await run_coupon_invoice_case({
|
|
753
|
+
label: 'c3-nonmatching',
|
|
754
|
+
couponCodes: ['SUMMER20'],
|
|
755
|
+
discounts: [discount_with_promo_code('WINTER10')],
|
|
756
|
+
shouldFire: false,
|
|
757
|
+
title: 'coupon-scoped trigger does not fire for a different code',
|
|
758
|
+
})
|
|
759
|
+
|
|
760
|
+
// C4: an undiscounted invoice never matches a coupon-scoped trigger
|
|
761
|
+
await run_coupon_invoice_case({
|
|
762
|
+
label: 'c4-no-discounts',
|
|
763
|
+
couponCodes: ['SUMMER20'],
|
|
764
|
+
shouldFire: false,
|
|
765
|
+
title: 'an invoice with no discounts does not fire a coupon-scoped trigger',
|
|
766
|
+
})
|
|
767
|
+
|
|
768
|
+
// C5: admin-applied coupons carry a name but no promotion code, and must still match. Built
|
|
769
|
+
// once and referenced by id from `discounts`, mirroring the real single-discount payload shape.
|
|
770
|
+
const c5Discount = discount_with_coupon('Spring Promo')
|
|
771
|
+
await run_coupon_invoice_case({
|
|
772
|
+
label: 'c5-coupon-name',
|
|
773
|
+
couponCodes: ['Spring Promo'],
|
|
774
|
+
discount: c5Discount,
|
|
775
|
+
discounts: [(c5Discount as any).id],
|
|
776
|
+
shouldFire: true,
|
|
777
|
+
title: 'coupon-scoped trigger matches via coupon name (no promotion code)',
|
|
778
|
+
})
|
|
779
|
+
|
|
780
|
+
// C6: backward compat — an unscoped Purchase Made trigger fires for discounted and
|
|
781
|
+
// undiscounted invoices alike
|
|
782
|
+
await run_coupon_invoice_case({
|
|
783
|
+
label: 'c6-unscoped-discounted',
|
|
784
|
+
discounts: [discount_with_promo_code('SUMMER20')],
|
|
785
|
+
shouldFire: true,
|
|
786
|
+
title: 'unscoped Purchase Made trigger fires for a discounted invoice',
|
|
787
|
+
})
|
|
788
|
+
await run_coupon_invoice_case({
|
|
789
|
+
label: 'c6b-unscoped-undiscounted',
|
|
790
|
+
shouldFire: true,
|
|
791
|
+
title: 'unscoped Purchase Made trigger fires for an undiscounted invoice',
|
|
792
|
+
})
|
|
793
|
+
|
|
794
|
+
// C7: charge.succeeded with payment_intent null — the coupon lookup is skipped entirely, so
|
|
795
|
+
// the purchase is created, the unscoped trigger fires, and the coupon-scoped one does not
|
|
796
|
+
const C7_TAG = tag('c7-charge-unscoped')
|
|
797
|
+
const C7_SCOPED_TAG = tag('c7-charge-coupon-scoped')
|
|
798
|
+
const c7Enduser = await new_enduser('c7')
|
|
799
|
+
await new_trigger({
|
|
800
|
+
title: `Stripe purchase made (c7 unscoped) ${RUN}`,
|
|
801
|
+
event: { type: 'Purchase Made', info: {} },
|
|
802
|
+
tags: [C7_TAG],
|
|
803
|
+
})
|
|
804
|
+
await new_trigger({
|
|
805
|
+
title: `Stripe purchase made (c7 coupon-scoped) ${RUN}`,
|
|
806
|
+
event: { type: 'Purchase Made', info: { couponCodes: ['SUMMER20'] } },
|
|
807
|
+
tags: [C7_SCOPED_TAG],
|
|
808
|
+
})
|
|
809
|
+
const c7Res = await post_stripe_webhook({
|
|
810
|
+
stripePublicKey,
|
|
811
|
+
secret: webhooksSecret,
|
|
812
|
+
event: charge_succeeded_event({
|
|
813
|
+
customer: {
|
|
814
|
+
id: c7Enduser.stripeCustomerId,
|
|
815
|
+
email: `stripe-webhook-c7-${RUN}@tellescope.com`,
|
|
816
|
+
name: 'Stripe Webhook c7',
|
|
817
|
+
},
|
|
818
|
+
}),
|
|
819
|
+
})
|
|
820
|
+
assert(c7Res.status === 200, `expected 200, got ${c7Res.status} (${c7Res.data})`, 'charge.succeeded webhook accepted')
|
|
821
|
+
await expect_fired(c7Enduser.id, C7_TAG, 'unscoped Purchase Made trigger fires for charge.succeeded')
|
|
822
|
+
await expect_not_fired(c7Enduser.id, C7_SCOPED_TAG, 'coupon-scoped trigger does not fire for a charge with no coupon data')
|
|
823
|
+
const c7Purchases = await poll_for_purchase(c7Enduser.id)
|
|
824
|
+
assert(c7Purchases.length > 0, 'expected charge.succeeded to create a purchase', 'charge.succeeded purchase created')
|
|
825
|
+
|
|
826
|
+
// C8: graceful degradation — a payment_intent string triggers the checkout-session lookup,
|
|
827
|
+
// which fails against real Stripe under this suite's fake API key. That failure must be caught:
|
|
828
|
+
// the purchase is still created and the unscoped trigger still fires.
|
|
829
|
+
const C8_TAG = tag('c8-charge-failed-lookup')
|
|
830
|
+
const c8Enduser = await new_enduser('c8')
|
|
831
|
+
await new_trigger({
|
|
832
|
+
title: `Stripe purchase made (c8 failed session lookup) ${RUN}`,
|
|
833
|
+
event: { type: 'Purchase Made', info: {} },
|
|
834
|
+
tags: [C8_TAG],
|
|
835
|
+
})
|
|
836
|
+
const c8Res = await post_stripe_webhook({
|
|
837
|
+
stripePublicKey,
|
|
838
|
+
secret: webhooksSecret,
|
|
839
|
+
event: charge_succeeded_event({
|
|
840
|
+
customer: {
|
|
841
|
+
id: c8Enduser.stripeCustomerId,
|
|
842
|
+
email: `stripe-webhook-c8-${RUN}@tellescope.com`,
|
|
843
|
+
name: 'Stripe Webhook c8',
|
|
844
|
+
},
|
|
845
|
+
payment_intent: `pi_test_${RUN}`,
|
|
846
|
+
}),
|
|
847
|
+
})
|
|
848
|
+
assert(c8Res.status === 200, `expected 200, got ${c8Res.status} (${c8Res.data})`, 'charge.succeeded with failing session lookup still accepted')
|
|
849
|
+
await expect_fired(c8Enduser.id, C8_TAG, 'a failing checkout-session lookup does not block the purchase or its triggers')
|
|
850
|
+
|
|
851
|
+
// C9: couponCodes must round-trip on the trigger's event.info
|
|
852
|
+
const c9Trigger = await new_trigger({
|
|
853
|
+
title: `Stripe purchase made (validator round-trip) ${RUN}`,
|
|
854
|
+
event: { type: 'Purchase Made', info: { couponCodes: ['SUMMER20'] } },
|
|
855
|
+
tags: [tag('c9')],
|
|
856
|
+
})
|
|
857
|
+
const c9Fetched = await sdk.api.automation_triggers.getOne(c9Trigger.id)
|
|
858
|
+
assert(
|
|
859
|
+
JSON.stringify((c9Fetched.event as any).info?.couponCodes) === JSON.stringify(['SUMMER20']),
|
|
860
|
+
`expected couponCodes to round-trip, got ${JSON.stringify(c9Fetched.event)}`,
|
|
861
|
+
'couponCodes round-trips through the validator',
|
|
862
|
+
)
|
|
863
|
+
|
|
643
864
|
console.log("All Stripe webhook trigger tests passed!")
|
|
644
865
|
} finally {
|
|
645
866
|
// Best-effort only: the harness exits the process on the first failed assertion, which skips
|
|
@@ -23,9 +23,13 @@ const decode_jwt_header = (token: string) => (
|
|
|
23
23
|
)
|
|
24
24
|
const decode_jwt_payload = (token: string) => (
|
|
25
25
|
JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString()) as {
|
|
26
|
-
|
|
26
|
+
sub: string,
|
|
27
|
+
iss: string,
|
|
28
|
+
aud: string,
|
|
29
|
+
jti: string,
|
|
27
30
|
iat: number,
|
|
28
31
|
exp: number,
|
|
32
|
+
target?: string,
|
|
29
33
|
}
|
|
30
34
|
)
|
|
31
35
|
const sign_hs256 = (signingInput: string, key: string) => (
|
|
@@ -99,16 +103,55 @@ export const welle_partner_token_tests = async ({ sdk, sdkNonAdmin } : { sdk: Se
|
|
|
99
103
|
|
|
100
104
|
const header = decode_jwt_header(issuedToken)
|
|
101
105
|
const payload = decode_jwt_payload(issuedToken)
|
|
102
|
-
const expectedExp = Math.floor(Date.now() / 1000) +
|
|
106
|
+
const expectedExp = Math.floor(Date.now() / 1000) + 60
|
|
103
107
|
|
|
104
108
|
return (
|
|
105
109
|
header.alg === 'HS256'
|
|
106
|
-
&& payload.
|
|
107
|
-
&&
|
|
110
|
+
&& payload.sub === HEALTHIE_PATIENT_ID
|
|
111
|
+
&& payload.iss === 'tellescope'
|
|
112
|
+
&& payload.aud === 'welle:sso-launch'
|
|
113
|
+
&& typeof payload.jti === 'string' && payload.jti.length > 0
|
|
114
|
+
&& Math.abs(payload.exp - expectedExp) < 10
|
|
108
115
|
)
|
|
109
116
|
}},
|
|
110
117
|
)
|
|
111
118
|
|
|
119
|
+
// each launch gets a unique jti
|
|
120
|
+
await async_test(
|
|
121
|
+
`back-to-back tokens carry distinct jti values`,
|
|
122
|
+
() => linkedSDK.api.integrations.proxy_read({ integration: WELLE_TITLE, type: 'partner-redirect-token' }),
|
|
123
|
+
{ onResult: (r: any) => {
|
|
124
|
+
const second = r?.data?.token
|
|
125
|
+
if (!second) return false
|
|
126
|
+
return decode_jwt_payload(second).jti !== decode_jwt_payload(issuedToken).jti
|
|
127
|
+
}},
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// optional deep-link target — allowlisted value is echoed as a claim
|
|
131
|
+
await async_test(
|
|
132
|
+
`target: 'plan' is included as a target claim`,
|
|
133
|
+
() => linkedSDK.api.integrations.proxy_read({ integration: WELLE_TITLE, type: 'partner-redirect-token', target: 'plan' }),
|
|
134
|
+
{ onResult: (r: any) => (
|
|
135
|
+
!!r?.data?.token && decode_jwt_payload(r.data.token).target === 'plan'
|
|
136
|
+
)},
|
|
137
|
+
)
|
|
138
|
+
// omitted target → no target claim at all (the happy-path token above was minted without one)
|
|
139
|
+
assert(
|
|
140
|
+
decode_jwt_payload(issuedToken).target === undefined,
|
|
141
|
+
"token minted without a target has a target claim",
|
|
142
|
+
"token minted without a target has no target claim",
|
|
143
|
+
)
|
|
144
|
+
// non-allowlisted target is rejected at the API boundary by schema validation
|
|
145
|
+
await async_test(
|
|
146
|
+
`invalid target is rejected by schema validation`,
|
|
147
|
+
() => linkedSDK.api.integrations.proxy_read({
|
|
148
|
+
integration: WELLE_TITLE,
|
|
149
|
+
type: 'partner-redirect-token',
|
|
150
|
+
target: 'admin' as any,
|
|
151
|
+
}),
|
|
152
|
+
{ shouldError: true, onError: () => true },
|
|
153
|
+
)
|
|
154
|
+
|
|
112
155
|
// 2) the signature is real — verifies against the stored signing key, and only that key
|
|
113
156
|
const [header, payload, signature] = issuedToken.split('.')
|
|
114
157
|
assert(
|
|
@@ -131,7 +174,7 @@ export const welle_partner_token_tests = async ({ sdk, sdkNonAdmin } : { sdk: Se
|
|
|
131
174
|
id: unlinkedId,
|
|
132
175
|
}),
|
|
133
176
|
{ onResult: (r: any) => (
|
|
134
|
-
!!r?.data?.token && decode_jwt_payload(r.data.token).
|
|
177
|
+
!!r?.data?.token && decode_jwt_payload(r.data.token).sub === HEALTHIE_PATIENT_ID
|
|
135
178
|
)},
|
|
136
179
|
)
|
|
137
180
|
|
|
@@ -195,7 +238,7 @@ export const welle_partner_token_tests = async ({ sdk, sdkNonAdmin } : { sdk: Se
|
|
|
195
238
|
if (!token) return false
|
|
196
239
|
const [h, p, s] = token.split('.')
|
|
197
240
|
return sign_hs256(`${h}.${p}`, SIGNING_KEY) === s
|
|
198
|
-
&& decode_jwt_payload(token).
|
|
241
|
+
&& decode_jwt_payload(token).sub === HEALTHIE_PATIENT_ID
|
|
199
242
|
}},
|
|
200
243
|
)
|
|
201
244
|
|
|
@@ -151,6 +151,122 @@ export const invoice_payment_failed_event = ({ stripeCustomerId, lines = [], has
|
|
|
151
151
|
},
|
|
152
152
|
})
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Embedded Stripe.Discount-shaped object with an *expanded* promotion_code object. Real webhook
|
|
156
|
+
* payloads carry promotion_code as a bare promo_... string (see discount_with_promo_code_ref);
|
|
157
|
+
* the expanded variant reflects the post-expansion shape (and the checkout-session breakdown
|
|
158
|
+
* shape), and matters here because this suite signs payloads with a real HMAC but a fake Stripe
|
|
159
|
+
* API key, so any fallback fetch (promotionCodes.retrieve on a promo_... string) would fail —
|
|
160
|
+
* expanded objects prove the handler extracts codes without outbound Stripe calls.
|
|
161
|
+
*/
|
|
162
|
+
export const discount_with_promo_code = (code: string, couponName?: string) => ({
|
|
163
|
+
id: `di_test_${unique_suffix()}`,
|
|
164
|
+
object: 'discount',
|
|
165
|
+
coupon: {
|
|
166
|
+
id: `coupon_test_${unique_suffix()}`,
|
|
167
|
+
object: 'coupon',
|
|
168
|
+
name: couponName ?? null,
|
|
169
|
+
},
|
|
170
|
+
promotion_code: {
|
|
171
|
+
id: `promo_test_${unique_suffix()}`,
|
|
172
|
+
object: 'promotion_code',
|
|
173
|
+
code,
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Mirrors a real invoice.paid webhook discount (verified against a live sandbox event): the
|
|
179
|
+
* embedded coupon carries id + name, but promotion_code is a bare promo_... string reference that
|
|
180
|
+
* only the retrieve fallback can resolve — which fails under this suite's fake API key, so a
|
|
181
|
+
* payload built from this captures the coupon name + id and (in tests) not the promo code.
|
|
182
|
+
*/
|
|
183
|
+
export const discount_with_promo_code_ref = ({ couponId, couponName } : { couponId: string, couponName: string }) => ({
|
|
184
|
+
id: `di_test_${unique_suffix()}`,
|
|
185
|
+
object: 'discount',
|
|
186
|
+
coupon: { id: couponId, object: 'coupon', name: couponName },
|
|
187
|
+
promotion_code: `promo_test_${unique_suffix()}`,
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
/** An admin-applied coupon: a name but no promotion code. */
|
|
191
|
+
export const discount_with_coupon = (name: string) => ({
|
|
192
|
+
id: `di_test_${unique_suffix()}`,
|
|
193
|
+
object: 'discount',
|
|
194
|
+
coupon: {
|
|
195
|
+
id: `coupon_test_${unique_suffix()}`,
|
|
196
|
+
object: 'coupon',
|
|
197
|
+
name,
|
|
198
|
+
},
|
|
199
|
+
promotion_code: null,
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* An invoice line item for invoice.paid. `description` feeds purchase.title; the expanded
|
|
204
|
+
* price.product object (when a product id is given) avoids the handler's product-name
|
|
205
|
+
* stripe.products.retrieve network fallback.
|
|
206
|
+
*/
|
|
207
|
+
export const invoice_paid_line = ({ description, stripeProductId } : {
|
|
208
|
+
description?: string,
|
|
209
|
+
stripeProductId?: string,
|
|
210
|
+
}) => ({
|
|
211
|
+
description: description ?? null,
|
|
212
|
+
price: stripeProductId
|
|
213
|
+
? { product: { id: stripeProductId, object: 'product', name: description ?? 'Test Product' } }
|
|
214
|
+
: null,
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Minimal invoice.paid envelope. The handler looks up the enduser by stripeCustomerId, so no
|
|
219
|
+
* customer_email/name are needed. Real webhooks embed the single-discount case at `discount` and
|
|
220
|
+
* carry only bare "di_..." string ids in `discounts` (verified against live sandbox events);
|
|
221
|
+
* cases may instead pass expanded objects in `discounts` to prove call-free extraction — see
|
|
222
|
+
* discount_with_promo_code.
|
|
223
|
+
*/
|
|
224
|
+
export const invoice_paid_event = ({ stripeCustomerId, lines = [], discounts, discount, amount_paid = 1999 } : {
|
|
225
|
+
stripeCustomerId: string,
|
|
226
|
+
lines?: object[],
|
|
227
|
+
discounts?: (object | string)[],
|
|
228
|
+
discount?: object,
|
|
229
|
+
amount_paid?: number,
|
|
230
|
+
}) => ({
|
|
231
|
+
type: 'invoice.paid',
|
|
232
|
+
data: {
|
|
233
|
+
object: {
|
|
234
|
+
id: `in_test_${unique_suffix()}`,
|
|
235
|
+
customer: stripeCustomerId,
|
|
236
|
+
amount_paid,
|
|
237
|
+
lines: { object: 'list', data: lines, has_more: false },
|
|
238
|
+
discount: discount ?? null,
|
|
239
|
+
discounts: discounts ?? [],
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Minimal charge.succeeded envelope. `customer` must be an embedded object — a bare string id makes
|
|
246
|
+
* the handler call stripe.customers.retrieve, an outbound call the fake API key cannot serve. The
|
|
247
|
+
* handler then matches the enduser by the embedded customer's email. `payment_intent: null` is the
|
|
248
|
+
* deterministic no-coupon case (the coupon lookup only runs for a payment_intent string).
|
|
249
|
+
*/
|
|
250
|
+
export const charge_succeeded_event = ({ customer, amount = 1999, currency = 'usd', payment_intent = null } : {
|
|
251
|
+
customer: { id: string, email: string, name?: string },
|
|
252
|
+
amount?: number,
|
|
253
|
+
currency?: string,
|
|
254
|
+
payment_intent?: string | null,
|
|
255
|
+
}) => ({
|
|
256
|
+
type: 'charge.succeeded',
|
|
257
|
+
data: {
|
|
258
|
+
object: {
|
|
259
|
+
id: `ch_test_${unique_suffix()}`,
|
|
260
|
+
customer: { object: 'customer', ...customer },
|
|
261
|
+
amount,
|
|
262
|
+
currency,
|
|
263
|
+
payment_intent,
|
|
264
|
+
invoice: null,
|
|
265
|
+
metadata: {},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
})
|
|
269
|
+
|
|
154
270
|
/** Minimal customer.subscription.deleted envelope; the handler reads items.data[].plan.product. */
|
|
155
271
|
export const subscription_deleted_event = ({ stripeCustomerId, stripeProductIds = [] } : {
|
|
156
272
|
stripeCustomerId: string,
|
package/src/tests/tests.ts
CHANGED
|
@@ -38,6 +38,8 @@ import {
|
|
|
38
38
|
import { Session, APIQuery, EnduserSession } from "../sdk"
|
|
39
39
|
import { enduser_observations_acknowledge_tests } from "./api_tests/enduser_observations_acknowledge.test"
|
|
40
40
|
import { translations_tests } from "./api_tests/translations.test"
|
|
41
|
+
import { configurations_public_read_tests } from "./api_tests/configurations_public_read.test"
|
|
42
|
+
import { form_translations_tests } from "./api_tests/form_translations.test"
|
|
41
43
|
import { embeddables_auth_token_tests } from "./api_tests/embeddables_auth_token.test"
|
|
42
44
|
import { user_portal_settings_tests } from "./api_tests/user_portal_settings.test"
|
|
43
45
|
import { integrations_redacted_tests } from "./api_tests/integrations_redacted.test"
|
|
@@ -56,6 +58,8 @@ import { purchase_made_trigger_tests } from "./api_tests/purchase_made_trigger.t
|
|
|
56
58
|
import { appointment_rescheduled_trigger_tests } from "./api_tests/appointment_rescheduled_trigger.test"
|
|
57
59
|
import { appointment_no_showed_trigger_tests } from "./api_tests/appointment_no_showed_trigger.test"
|
|
58
60
|
import { group_event_attendee_status_triggers_tests } from "./api_tests/group_event_attendee_status_triggers.test"
|
|
61
|
+
import { group_event_attendee_self_update_tests } from "./api_tests/group_event_attendee_self_update.test"
|
|
62
|
+
import { calendar_event_create_and_bulk_triggers_tests } from "./api_tests/calendar_event_create_and_bulk_triggers.test"
|
|
59
63
|
import { journey_error_branching_tests } from "./api_tests/journey_error_branching.test"
|
|
60
64
|
import { journey_delete_cancels_actions_tests } from "./api_tests/journey_delete_cancels_actions.test"
|
|
61
65
|
import { afteraction_day_of_month_delay_tests } from "./api_tests/afteraction_day_of_month_delay.test"
|
|
@@ -109,6 +113,7 @@ import { field_redaction_tests } from "./api_tests/field_redaction.test";
|
|
|
109
113
|
import { data_sync_redaction_bypass_tests } from "./api_tests/security/F-0001-data-sync-redaction-bypass.test";
|
|
110
114
|
import { enduser_write_restrictions_tests } from "./api_tests/security/F-0106-F-0110-enduser-write-restrictions.test";
|
|
111
115
|
import { ai_conversations_rbac_tests } from "./api_tests/security/F-0005-ai-conversations-rbac.test";
|
|
116
|
+
import { develop_health_benefit_verification_rbac_tests } from "./api_tests/security/F-0006-develop-health-benefit-verification-rbac.test";
|
|
112
117
|
import { cascade_role_rename_cross_tenant_tests } from "./api_tests/security/F-0053-cascade-role-rename-cross-tenant.test";
|
|
113
118
|
import { self_admin_role_assignment_tests } from "./api_tests/security/F-0076-self-admin-role-assignment.test";
|
|
114
119
|
import { invite_user_enumeration_tests } from "./api_tests/security/F-0007-invite-user-enumeration.test";
|
|
@@ -153,6 +158,7 @@ import { mdi_case_offerings_tests } from "./api_tests/mdi_case_offerings.test";
|
|
|
153
158
|
import { beluga_manual_sync_tests } from "./api_tests/beluga_manual_sync.test";
|
|
154
159
|
import { metriport_push_form_response_tests } from "./api_tests/metriport_push_form_response.test";
|
|
155
160
|
import { beluga_trigger_refill_tests } from "./api_tests/beluga_trigger_refill.test";
|
|
161
|
+
import { mdi_trigger_refill_tests } from "./api_tests/mdi_trigger_refill.test";
|
|
156
162
|
import { mdi_webhooks_tests } from "./api_tests/mdi_webhooks.test";
|
|
157
163
|
import { stripe_webhook_triggers_tests } from "./api_tests/stripe_webhook_triggers.test";
|
|
158
164
|
import { account_switcher_tests } from "./api_tests/account_switcher.test";
|
|
@@ -5752,6 +5758,8 @@ const automation_trigger_tests = async () => {
|
|
|
5752
5758
|
|
|
5753
5759
|
await appointment_no_showed_trigger_tests({ sdk, sdkNonAdmin })
|
|
5754
5760
|
await group_event_attendee_status_triggers_tests({ sdk, sdkNonAdmin })
|
|
5761
|
+
await group_event_attendee_self_update_tests({ sdk, sdkNonAdmin })
|
|
5762
|
+
await calendar_event_create_and_bulk_triggers_tests({ sdk, sdkNonAdmin })
|
|
5755
5763
|
await assign_care_team_tests()
|
|
5756
5764
|
await push_forms_to_portal_group_completion_tests({ sdk, sdkNonAdmin })
|
|
5757
5765
|
await order_status_equals_tests()
|
|
@@ -6994,18 +7002,23 @@ export const self_serve_appointment_booking_tests = async () => {
|
|
|
6994
7002
|
restrictedByState: false,
|
|
6995
7003
|
}),
|
|
6996
7004
|
{ onResult: r => {
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7005
|
+
// without a state restriction this endpoint returns availability for EVERY user in the business
|
|
7006
|
+
// with weeklyAvailabilities set, so scope to the two users this test provisions. An unrelated
|
|
7007
|
+
// user (e.g. one stranded by an interrupted suite — reset_db does not clear the users
|
|
7008
|
+
// collection) must not break this assertion.
|
|
7009
|
+
const adminBlocks = r.availabilityBlocks.filter(b => b.userId === sdk.userInfo.id)
|
|
7010
|
+
const nonAdminBlocks = r.availabilityBlocks.filter(b => b.userId === sdkNonAdmin.userInfo.id)
|
|
7011
|
+
|
|
7012
|
+
// 1 hour of availability each for 30 minute meetings, and the admin's duplicate
|
|
7013
|
+
// weeklyAvailabilities entry must not produce extra slots
|
|
7014
|
+
if (adminBlocks.length !== 2) return false
|
|
7015
|
+
if (nonAdminBlocks.length !== 2) return false
|
|
7016
|
+
|
|
7017
|
+
// the server shuffles users, so compare earliest-to-earliest rather than relying on ordering
|
|
7018
|
+
const earliest = (bs: typeof adminBlocks) => Math.min(...bs.map(b => b.startTimeInMS))
|
|
7019
|
+
|
|
7020
|
+
// difference should be three hours, since same availability in different timezones
|
|
7021
|
+
return earliest(nonAdminBlocks) - earliest(adminBlocks) === 1000 * 60 * 60 * 3
|
|
7009
7022
|
}},
|
|
7010
7023
|
)
|
|
7011
7024
|
|
|
@@ -15298,6 +15311,7 @@ const ip_address_form_tests = async () => {
|
|
|
15298
15311
|
await beluga_manual_sync_tests({ sdk, sdkNonAdmin })
|
|
15299
15312
|
await metriport_push_form_response_tests({ sdk, sdkNonAdmin })
|
|
15300
15313
|
await beluga_trigger_refill_tests({ sdk, sdkNonAdmin })
|
|
15314
|
+
await mdi_trigger_refill_tests({ sdk, sdkNonAdmin })
|
|
15301
15315
|
await beluga_pharmacy_mappings_tests({ sdk, sdkNonAdmin })
|
|
15302
15316
|
await mdi_case_offerings_tests({ sdk, sdkNonAdmin })
|
|
15303
15317
|
await enduser_write_restrictions_tests({ sdk, sdkNonAdmin })
|
|
@@ -15317,6 +15331,7 @@ const ip_address_form_tests = async () => {
|
|
|
15317
15331
|
await enduser_login_rate_limits_tests({ sdk, sdkNonAdmin })
|
|
15318
15332
|
await data_sync_redaction_bypass_tests({ sdk, sdkNonAdmin })
|
|
15319
15333
|
await ai_conversations_rbac_tests({ sdk, sdkNonAdmin })
|
|
15334
|
+
await develop_health_benefit_verification_rbac_tests({ sdk, sdkNonAdmin })
|
|
15320
15335
|
await cascade_role_rename_cross_tenant_tests({ sdk, sdkNonAdmin })
|
|
15321
15336
|
await self_admin_role_assignment_tests({ sdk, sdkNonAdmin })
|
|
15322
15337
|
await sanitize_user_html_xss_tests()
|
|
@@ -15393,6 +15408,8 @@ const ip_address_form_tests = async () => {
|
|
|
15393
15408
|
await enduser_observations_acknowledge_tests({ sdk, sdkNonAdmin })
|
|
15394
15409
|
await nutrition_tracking_tests({ sdk, sdkNonAdmin })
|
|
15395
15410
|
await translations_tests({ sdk, sdkNonAdmin })
|
|
15411
|
+
await configurations_public_read_tests({ sdk, sdkNonAdmin })
|
|
15412
|
+
await form_translations_tests({ sdk, sdkNonAdmin })
|
|
15396
15413
|
await create_user_notifications_trigger_tests({ sdk })
|
|
15397
15414
|
await group_mms_active_tests()
|
|
15398
15415
|
await auto_reply_tests()
|
package/test_generated.pdf
CHANGED
|
Binary file
|