@tellescope/sdk 1.255.19 → 1.256.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/tests/api_tests/custom_dashboards.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/custom_dashboards.test.js +99 -8
- package/lib/cjs/tests/api_tests/custom_dashboards.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/default_summary_journey.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/default_summary_journey.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/file_download_unicode_names.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/file_download_unicode_names.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/file_download_unicode_names.test.js +278 -0
- package/lib/cjs/tests/api_tests/file_download_unicode_names.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/openloop_webhooks.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/openloop_webhooks.test.js +212 -33
- package/lib/cjs/tests/api_tests/openloop_webhooks.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/phone_calls_cold_transfer.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/phone_calls_cold_transfer.test.js +85 -9
- package/lib/cjs/tests/api_tests/phone_calls_cold_transfer.test.js.map +1 -1
- package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.d.ts +2 -0
- package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.js +242 -0
- package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.d.ts +29 -0
- package/lib/cjs/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.js +443 -0
- package/lib/cjs/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.d.ts +35 -0
- package/lib/cjs/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.js +231 -0
- package/lib/cjs/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.d.ts.map +1 -1
- package/lib/cjs/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.js +524 -294
- package/lib/cjs/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.js.map +1 -1
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +431 -230
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/esm/enduser.d.ts +0 -1
- package/lib/esm/enduser.d.ts.map +1 -1
- package/lib/esm/sdk.d.ts +2 -3
- package/lib/esm/sdk.d.ts.map +1 -1
- package/lib/esm/session.d.ts +0 -1
- package/lib/esm/session.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/custom_dashboards.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/custom_dashboards.test.js +99 -8
- package/lib/esm/tests/api_tests/custom_dashboards.test.js.map +1 -1
- package/lib/esm/tests/api_tests/default_summary_journey.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/default_summary_journey.test.js.map +1 -0
- package/lib/esm/tests/api_tests/file_download_unicode_names.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/file_download_unicode_names.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/file_download_unicode_names.test.js +248 -0
- package/lib/esm/tests/api_tests/file_download_unicode_names.test.js.map +1 -0
- package/lib/esm/tests/api_tests/openloop_webhooks.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/openloop_webhooks.test.js +213 -34
- package/lib/esm/tests/api_tests/openloop_webhooks.test.js.map +1 -1
- package/lib/esm/tests/api_tests/phone_calls_cold_transfer.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/phone_calls_cold_transfer.test.js +85 -9
- package/lib/esm/tests/api_tests/phone_calls_cold_transfer.test.js.map +1 -1
- package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.d.ts +2 -0
- package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.js +238 -0
- package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.d.ts +29 -0
- package/lib/esm/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.js +436 -0
- package/lib/esm/tests/api_tests/security/F-0015-webhook-outbound-ssrf.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.d.ts +35 -0
- package/lib/esm/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.js +224 -0
- package/lib/esm/tests/api_tests/security/F-0025-proxy-image-unauth-ssrf.test.js.map +1 -0
- package/lib/esm/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.js +524 -294
- package/lib/esm/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.js.map +1 -1
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +431 -230
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/tests/api_tests/custom_dashboards.test.ts +92 -0
- package/src/tests/api_tests/file_download_unicode_names.test.ts +223 -0
- package/src/tests/api_tests/openloop_webhooks.test.ts +138 -0
- package/src/tests/api_tests/phone_calls_cold_transfer.test.ts +54 -0
- package/src/tests/api_tests/security/F-0013-sanitize-user-html-with-iframes.test.ts +215 -0
- package/src/tests/api_tests/security/F-0166-allowed-paths-wildcard-bypass.test.ts +215 -0
- package/src/tests/tests.ts +212 -1
- package/test_generated.pdf +0 -0
package/src/tests/tests.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
CompoundFilter,
|
|
18
18
|
CreateTicketActionInfo,
|
|
19
19
|
FormResponseValue,
|
|
20
|
+
JourneyContext,
|
|
20
21
|
ModelName,
|
|
21
22
|
RoundRobinAssignmentInfo,
|
|
22
23
|
User,
|
|
@@ -41,6 +42,7 @@ import { user_portal_settings_tests } from "./api_tests/user_portal_settings.tes
|
|
|
41
42
|
import { integrations_redacted_tests } from "./api_tests/integrations_redacted.test"
|
|
42
43
|
import { healthie_multi_integration_tests } from "./api_tests/healthie_multi_integration.test"
|
|
43
44
|
import { get_some_projection_tests } from "./api_tests/get_some_projection.test"
|
|
45
|
+
import { file_download_unicode_names_tests } from "./api_tests/file_download_unicode_names.test"
|
|
44
46
|
import { mdb_sort_tests } from "./api_tests/mdb_sort.test"
|
|
45
47
|
import { create_user_notifications_trigger_tests } from "./api_tests/create_user_notifications_trigger.test"
|
|
46
48
|
import { inbox_thread_assignment_updates_tests } from "./api_tests/inbox_thread_assignment_updates.test"
|
|
@@ -110,6 +112,7 @@ import { invite_user_enumeration_tests } from "./api_tests/security/F-0007-invit
|
|
|
110
112
|
import { register_business_id_injection_tests } from "./api_tests/security/register_business_id_injection.test";
|
|
111
113
|
import { handle_incoming_communication_cross_tenant_tests } from "./api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test";
|
|
112
114
|
import { sanitize_user_html_xss_tests } from "./api_tests/security/F-0013-sanitize-user-html.test";
|
|
115
|
+
import { sanitize_user_html_with_iframes_tests } from "./api_tests/security/F-0013-sanitize-user-html-with-iframes.test";
|
|
113
116
|
import { prototype_pollution_tests } from "./api_tests/security/F-0016-prototype-pollution.test";
|
|
114
117
|
import { enduser_cross_create_tests } from "./api_tests/security/F-0116-F-0119-enduser-cross-create.test";
|
|
115
118
|
import { globalunique_leak_tests } from "./api_tests/security/F-0145-globalunique-leak.test";
|
|
@@ -7951,9 +7954,49 @@ const merge_enduser_tests = async () => {
|
|
|
7951
7954
|
{ onResult: e => e.attendees.length === 1 && !!e.attendees?.find(a => a.id === destination.id) }
|
|
7952
7955
|
)
|
|
7953
7956
|
|
|
7957
|
+
// merging two endusers that both lack insurance must leave the fields absent, NOT null.
|
|
7958
|
+
// a stored null breaks every later partial update: those get flattened to dotted paths
|
|
7959
|
+
// ('insurance.memberId'), and MongoDB rejects a dotted $set under a null parent
|
|
7960
|
+
const [noInsuranceSource, noInsuranceDestination] = (await sdk.api.endusers.createSome([
|
|
7961
|
+
{ email: 'noins-source@tellescope.com', fname: 'no', lname: 'insurance' },
|
|
7962
|
+
{ email: 'noins-destination@tellescope.com' },
|
|
7963
|
+
])).created
|
|
7964
|
+
|
|
7965
|
+
await sdk.api.endusers.merge({
|
|
7966
|
+
sourceEnduserId: noInsuranceSource.id,
|
|
7967
|
+
destinationEnduserId: noInsuranceDestination.id,
|
|
7968
|
+
})
|
|
7969
|
+
|
|
7970
|
+
await async_test(
|
|
7971
|
+
"Merged endusers have no null insurance",
|
|
7972
|
+
() => sdk.api.endusers.getOne(noInsuranceDestination.id),
|
|
7973
|
+
{ onResult: e => e.insurance === undefined && e.insuranceSecondary === undefined }
|
|
7974
|
+
)
|
|
7975
|
+
|
|
7976
|
+
await async_test(
|
|
7977
|
+
"Partial insurance update applies after merge",
|
|
7978
|
+
async () => {
|
|
7979
|
+
await sdk.api.endusers.updateOne(noInsuranceDestination.id, {
|
|
7980
|
+
insurance: { memberId: 'M1', payerName: 'Test Payer' },
|
|
7981
|
+
}) // no replaceObjectFields, matching the standard update from the UI
|
|
7982
|
+
return await sdk.api.endusers.getOne(noInsuranceDestination.id)
|
|
7983
|
+
},
|
|
7984
|
+
{ onResult: e => e.insurance?.memberId === 'M1' && e.insurance?.payerName === 'Test Payer' }
|
|
7985
|
+
)
|
|
7986
|
+
|
|
7987
|
+
await async_test(
|
|
7988
|
+
"Second partial insurance update merges",
|
|
7989
|
+
async () => {
|
|
7990
|
+
await sdk.api.endusers.updateOne(noInsuranceDestination.id, { insurance: { groupNumber: 'G1' } })
|
|
7991
|
+
return await sdk.api.endusers.getOne(noInsuranceDestination.id)
|
|
7992
|
+
},
|
|
7993
|
+
{ onResult: e => e.insurance?.groupNumber === 'G1' && e.insurance?.memberId === 'M1' }
|
|
7994
|
+
)
|
|
7995
|
+
|
|
7954
7996
|
await Promise.all([
|
|
7955
7997
|
sdk.api.endusers.deleteOne(destination.id),
|
|
7956
7998
|
sdk.api.endusers.deleteOne(otherEnduser.id),
|
|
7999
|
+
sdk.api.endusers.deleteOne(noInsuranceDestination.id),
|
|
7957
8000
|
sdk.api.calendar_events.deleteOne(event.id),
|
|
7958
8001
|
sdk.api.calendar_events.deleteOne(eventToMove.id),
|
|
7959
8002
|
])
|
|
@@ -8587,6 +8630,8 @@ const test_ticket_automation_assignment_and_optimization = async () => {
|
|
|
8587
8630
|
enduser,
|
|
8588
8631
|
closedForReason,
|
|
8589
8632
|
testDelayedChild,
|
|
8633
|
+
calendarEventId,
|
|
8634
|
+
ticketOwner,
|
|
8590
8635
|
} : {
|
|
8591
8636
|
assignedTo: string[]
|
|
8592
8637
|
info: Pick<CreateTicketActionInfo, 'assignmentStrategy' | 'defaultAssignee'>,
|
|
@@ -8594,6 +8639,8 @@ const test_ticket_automation_assignment_and_optimization = async () => {
|
|
|
8594
8639
|
closedForReason?: string,
|
|
8595
8640
|
enduser?: Enduser,
|
|
8596
8641
|
testDelayedChild?: boolean,
|
|
8642
|
+
calendarEventId?: string, // copied into journeyContext by close_ticket
|
|
8643
|
+
ticketOwner?: string, // override the seed ticket's owner, to avoid masking a previous-owner fallback
|
|
8597
8644
|
}) => {
|
|
8598
8645
|
|
|
8599
8646
|
const e = enduser || await sdk.api.endusers.createOne({ assignedTo, journeys: { [journey.id]: '' }, fields: { Tag: "tag3"} })
|
|
@@ -8631,8 +8678,9 @@ const test_ticket_automation_assignment_and_optimization = async () => {
|
|
|
8631
8678
|
enduserId: e.id,
|
|
8632
8679
|
automationStepId: PLACEHOLDER_ID,
|
|
8633
8680
|
journeyId: journey.id,
|
|
8634
|
-
owner: validOwners[0],
|
|
8681
|
+
owner: ticketOwner ?? validOwners[0],
|
|
8635
8682
|
closedForReason,
|
|
8683
|
+
...(calendarEventId ? { calendarEventId } : {}),
|
|
8636
8684
|
})
|
|
8637
8685
|
|
|
8638
8686
|
await async_test(
|
|
@@ -8765,11 +8813,13 @@ const test_ticket_automation_assignment_and_optimization = async () => {
|
|
|
8765
8813
|
info,
|
|
8766
8814
|
validOwners,
|
|
8767
8815
|
enduser,
|
|
8816
|
+
journeyContext,
|
|
8768
8817
|
} : {
|
|
8769
8818
|
assignedTo: string[]
|
|
8770
8819
|
info: Pick<CreateTicketActionInfo, 'assignmentStrategy' | 'defaultAssignee'>,
|
|
8771
8820
|
validOwners: string[],
|
|
8772
8821
|
enduser?: Enduser,
|
|
8822
|
+
journeyContext?: JourneyContext,
|
|
8773
8823
|
}) => {
|
|
8774
8824
|
const e = enduser || await sdk.api.endusers.createOne({ assignedTo, fields: { Tag: 'tag3' } })
|
|
8775
8825
|
await sdk.api.automated_actions.createOne({
|
|
@@ -8780,6 +8830,7 @@ const test_ticket_automation_assignment_and_optimization = async () => {
|
|
|
8780
8830
|
journeyId: journey.id,
|
|
8781
8831
|
status: 'active',
|
|
8782
8832
|
processAfter: Date.now(),
|
|
8833
|
+
...(journeyContext ? { journeyContext } : {}),
|
|
8783
8834
|
})
|
|
8784
8835
|
|
|
8785
8836
|
await async_test(
|
|
@@ -8880,6 +8931,115 @@ const test_ticket_automation_assignment_and_optimization = async () => {
|
|
|
8880
8931
|
validOwners: [sdkNonAdmin.userInfo.id],
|
|
8881
8932
|
})
|
|
8882
8933
|
|
|
8934
|
+
// 'Recently-Booked Appointment Host' assignment
|
|
8935
|
+
// journeyContext.calendarEventId, when it resolves to a host, takes priority over the legacy
|
|
8936
|
+
// "newest event for this enduser" lookup. one enduser per case, to avoid trigger throttling.
|
|
8937
|
+
const HOST_A = sdkNonAdmin.userInfo.id
|
|
8938
|
+
const HOST_B = sdk.userInfo.id
|
|
8939
|
+
const appointmentHostInfo = (defaultAssignee: string) => ({
|
|
8940
|
+
assignmentStrategy: { type: 'Recently-Booked Appointment Host', info: {} } as const,
|
|
8941
|
+
defaultAssignee,
|
|
8942
|
+
})
|
|
8943
|
+
// the legacy filter matches an event having *some* user attendee and *some* attendee with this
|
|
8944
|
+
// enduser's id, so each hosted event carries both
|
|
8945
|
+
const createHostedEvent = (enduserId: string, hostId: string) => sdk.api.calendar_events.createOne({
|
|
8946
|
+
title: 'appointment host test',
|
|
8947
|
+
durationInMinutes: 30,
|
|
8948
|
+
startTimeInMS: Date.now(),
|
|
8949
|
+
attendees: [{ type: 'enduser', id: enduserId }, { type: 'user', id: hostId }],
|
|
8950
|
+
})
|
|
8951
|
+
|
|
8952
|
+
// CORE PROOF: two appointments with different hosts, journey context names the OLDER one.
|
|
8953
|
+
// fails before this fix, which always took the newest-created event (HOST_B).
|
|
8954
|
+
{
|
|
8955
|
+
const e = await sdk.api.endusers.createOne({ fields: { Tag: 'tag3' } })
|
|
8956
|
+
const older = await createHostedEvent(e.id, HOST_A)
|
|
8957
|
+
const newer = await createHostedEvent(e.id, HOST_B) // higher _id, so legacy sort picks this one
|
|
8958
|
+
await testBackgroundTicket({
|
|
8959
|
+
assignedTo: [],
|
|
8960
|
+
enduser: e,
|
|
8961
|
+
info: appointmentHostInfo(HOST_B), // distinct from expected owner, so a fallback would fail
|
|
8962
|
+
validOwners: [HOST_A],
|
|
8963
|
+
journeyContext: { calendarEventId: older.id },
|
|
8964
|
+
})
|
|
8965
|
+
await Promise.all([older, newer].map(ev => sdk.api.calendar_events.deleteOne(ev.id)))
|
|
8966
|
+
}
|
|
8967
|
+
|
|
8968
|
+
// no journey context -> legacy "newest event" behavior is unchanged
|
|
8969
|
+
{
|
|
8970
|
+
const e = await sdk.api.endusers.createOne({ fields: { Tag: 'tag3' } })
|
|
8971
|
+
const older = await createHostedEvent(e.id, HOST_A)
|
|
8972
|
+
const newer = await createHostedEvent(e.id, HOST_B)
|
|
8973
|
+
await testBackgroundTicket({
|
|
8974
|
+
assignedTo: [],
|
|
8975
|
+
enduser: e,
|
|
8976
|
+
info: appointmentHostInfo(HOST_A),
|
|
8977
|
+
validOwners: [HOST_B], // newest created event's host
|
|
8978
|
+
})
|
|
8979
|
+
await Promise.all([older, newer].map(ev => sdk.api.calendar_events.deleteOne(ev.id)))
|
|
8980
|
+
}
|
|
8981
|
+
|
|
8982
|
+
// context event no longer exists -> falls back to the legacy lookup
|
|
8983
|
+
{
|
|
8984
|
+
const e = await sdk.api.endusers.createOne({ fields: { Tag: 'tag3' } })
|
|
8985
|
+
const deleted = await createHostedEvent(e.id, HOST_B)
|
|
8986
|
+
await sdk.api.calendar_events.deleteOne(deleted.id)
|
|
8987
|
+
const hosted = await createHostedEvent(e.id, HOST_A)
|
|
8988
|
+
await testBackgroundTicket({
|
|
8989
|
+
assignedTo: [],
|
|
8990
|
+
enduser: e,
|
|
8991
|
+
info: appointmentHostInfo(HOST_B),
|
|
8992
|
+
validOwners: [HOST_A],
|
|
8993
|
+
journeyContext: { calendarEventId: deleted.id },
|
|
8994
|
+
})
|
|
8995
|
+
await sdk.api.calendar_events.deleteOne(hosted.id)
|
|
8996
|
+
}
|
|
8997
|
+
|
|
8998
|
+
// context event has no user attendee (so yields no host) -> falls back to the legacy lookup
|
|
8999
|
+
{
|
|
9000
|
+
const e = await sdk.api.endusers.createOne({ fields: { Tag: 'tag3' } })
|
|
9001
|
+
const enduserOnly = await sdk.api.calendar_events.createOne({
|
|
9002
|
+
title: 'appointment host test, no host',
|
|
9003
|
+
durationInMinutes: 30,
|
|
9004
|
+
startTimeInMS: Date.now(),
|
|
9005
|
+
attendees: [{ type: 'enduser', id: e.id }],
|
|
9006
|
+
})
|
|
9007
|
+
const hosted = await createHostedEvent(e.id, HOST_A)
|
|
9008
|
+
await testBackgroundTicket({
|
|
9009
|
+
assignedTo: [],
|
|
9010
|
+
enduser: e,
|
|
9011
|
+
info: appointmentHostInfo(HOST_B),
|
|
9012
|
+
validOwners: [HOST_A],
|
|
9013
|
+
journeyContext: { calendarEventId: enduserOnly.id },
|
|
9014
|
+
})
|
|
9015
|
+
await Promise.all([enduserOnly, hosted].map(ev => sdk.api.calendar_events.deleteOne(ev.id)))
|
|
9016
|
+
}
|
|
9017
|
+
|
|
9018
|
+
// no events at all -> no resolvable host, so defaultAssignee wins
|
|
9019
|
+
await testBackgroundTicket({
|
|
9020
|
+
assignedTo: [],
|
|
9021
|
+
info: appointmentHostInfo(HOST_A),
|
|
9022
|
+
validOwners: [HOST_A],
|
|
9023
|
+
})
|
|
9024
|
+
|
|
9025
|
+
// api path: close_ticket copies the original ticket's calendarEventId into journeyContext
|
|
9026
|
+
{
|
|
9027
|
+
const e = await sdk.api.endusers.createOne({
|
|
9028
|
+
journeys: { [journey.id]: '' }, fields: { Tag: 'tag3' },
|
|
9029
|
+
})
|
|
9030
|
+
const older = await createHostedEvent(e.id, HOST_A)
|
|
9031
|
+
const newer = await createHostedEvent(e.id, HOST_B)
|
|
9032
|
+
await testForegroundTicket({
|
|
9033
|
+
assignedTo: [],
|
|
9034
|
+
enduser: e,
|
|
9035
|
+
info: appointmentHostInfo(HOST_B),
|
|
9036
|
+
validOwners: [HOST_A],
|
|
9037
|
+
calendarEventId: older.id,
|
|
9038
|
+
ticketOwner: HOST_B, // so a previous-owner style fallback would also fail
|
|
9039
|
+
})
|
|
9040
|
+
await Promise.all([older, newer].map(ev => sdk.api.calendar_events.deleteOne(ev.id)))
|
|
9041
|
+
}
|
|
9042
|
+
|
|
8883
9043
|
return Promise.all([
|
|
8884
9044
|
await sdk.api.journeys.deleteOne(journey.id),
|
|
8885
9045
|
await sdk.api.ticket_queues.deleteOne(queue.id),
|
|
@@ -13764,6 +13924,55 @@ const replace_form_field_template_values_tests = async () => {
|
|
|
13764
13924
|
) === '<p>Locations: NYC<br />SF<br />LA</p>',
|
|
13765
13925
|
'fail CRLF in substituted value', 'CRLF in substituted value'
|
|
13766
13926
|
)
|
|
13927
|
+
|
|
13928
|
+
// escapeHTMLValues: substituted values are patient-supplied (their own intake answers / enduser
|
|
13929
|
+
// record), so on any path rendered by a sanitizer that permits iframes they must be escaped —
|
|
13930
|
+
// otherwise a patient can inject a frame into an admin-authored description.
|
|
13931
|
+
const enduserWithMarkupField = {
|
|
13932
|
+
fname: "Multi",
|
|
13933
|
+
fields: { Injected: '<iframe src="https://evil.example"></iframe>' },
|
|
13934
|
+
} as Partial<Enduser>
|
|
13935
|
+
|
|
13936
|
+
const escaped = replace_form_field_template_values(
|
|
13937
|
+
'<p>{{enduser.Injected}}</p>',
|
|
13938
|
+
{ enduser: enduserWithMarkupField, escapeNewlinesAsHTMLBreaks: true, escapeHTMLValues: true }
|
|
13939
|
+
)
|
|
13940
|
+
assert(
|
|
13941
|
+
escaped.includes('<iframe') && !escaped.includes('<iframe'),
|
|
13942
|
+
'fail escapeHTMLValues escapes markup', 'escapeHTMLValues escapes markup'
|
|
13943
|
+
)
|
|
13944
|
+
|
|
13945
|
+
// Escaping must run BEFORE the newline conversion, or the injected breaks become <br />
|
|
13946
|
+
const enduserWithMarkupAndNewline = {
|
|
13947
|
+
fields: { Mixed: 'a<b\nc&d' },
|
|
13948
|
+
} as Partial<Enduser>
|
|
13949
|
+
assert(
|
|
13950
|
+
replace_form_field_template_values(
|
|
13951
|
+
'{{enduser.Mixed}}',
|
|
13952
|
+
{ enduser: enduserWithMarkupAndNewline, escapeNewlinesAsHTMLBreaks: true, escapeHTMLValues: true }
|
|
13953
|
+
) === 'a<b<br />c&d',
|
|
13954
|
+
'fail escapeHTMLValues ordering vs newline breaks', 'escapeHTMLValues ordering vs newline breaks'
|
|
13955
|
+
)
|
|
13956
|
+
|
|
13957
|
+
// Flag absent => byte-identical to today. Plain-text callers (field titles/descriptions rendered
|
|
13958
|
+
// in a Typography) rely on this: escaping there would corrupt e.g. a name containing '&'.
|
|
13959
|
+
const enduserWithAmpersand = {
|
|
13960
|
+
fields: { Practice: 'Smith & Jones <Cardiology>' },
|
|
13961
|
+
} as Partial<Enduser>
|
|
13962
|
+
assert(
|
|
13963
|
+
replace_form_field_template_values(
|
|
13964
|
+
'Practice: {{enduser.Practice}}',
|
|
13965
|
+
{ enduser: enduserWithAmpersand }
|
|
13966
|
+
) === 'Practice: Smith & Jones <Cardiology>',
|
|
13967
|
+
'fail no escaping without the flag', 'no escaping without the flag'
|
|
13968
|
+
)
|
|
13969
|
+
assert(
|
|
13970
|
+
replace_form_field_template_values(
|
|
13971
|
+
'Practice: {{enduser.Practice}}',
|
|
13972
|
+
{ enduser: enduserWithAmpersand, escapeHTMLValues: true }
|
|
13973
|
+
) === 'Practice: Smith & Jones <Cardiology>',
|
|
13974
|
+
'fail escaping with the flag', 'escaping with the flag'
|
|
13975
|
+
)
|
|
13767
13976
|
}
|
|
13768
13977
|
|
|
13769
13978
|
const inbox_threads_building_tests = async () => {
|
|
@@ -15095,6 +15304,7 @@ const ip_address_form_tests = async () => {
|
|
|
15095
15304
|
await cascade_role_rename_cross_tenant_tests({ sdk, sdkNonAdmin })
|
|
15096
15305
|
await self_admin_role_assignment_tests({ sdk, sdkNonAdmin })
|
|
15097
15306
|
await sanitize_user_html_xss_tests()
|
|
15307
|
+
await sanitize_user_html_with_iframes_tests()
|
|
15098
15308
|
await prototype_pollution_tests()
|
|
15099
15309
|
await account_switcher_tests({ sdk, sdkNonAdmin })
|
|
15100
15310
|
await totp_mfa_tests({ sdk, sdkNonAdmin })
|
|
@@ -15129,6 +15339,7 @@ const ip_address_form_tests = async () => {
|
|
|
15129
15339
|
await time_tracks_appointment_duration_tests({ sdk, sdkNonAdmin })
|
|
15130
15340
|
await calendar_event_limits_tests({ sdk, sdkNonAdmin })
|
|
15131
15341
|
await get_some_projection_tests({ sdk, sdkNonAdmin })
|
|
15342
|
+
await file_download_unicode_names_tests({ sdk, sdkNonAdmin })
|
|
15132
15343
|
await elation_user_id_tests({ sdk, sdkNonAdmin })
|
|
15133
15344
|
await concurrent_build_threads_tests({ sdk, sdkNonAdmin })
|
|
15134
15345
|
await custom_aggregation_tests({ sdk, sdkNonAdmin })
|
package/test_generated.pdf
CHANGED
|
Binary file
|