@tellescope/sdk 1.255.14 → 1.255.16
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/formsort_header_auth.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/formsort_header_auth.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/formsort_header_auth.test.js +221 -0
- package/lib/cjs/tests/api_tests/formsort_header_auth.test.js.map +1 -0
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +103 -99
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/esm/enduser.d.ts +1 -0
- package/lib/esm/enduser.d.ts.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/formsort_header_auth.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/formsort_header_auth.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/formsort_header_auth.test.js +214 -0
- package/lib/esm/tests/api_tests/formsort_header_auth.test.js.map +1 -0
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +103 -99
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/tests/api_tests/formsort_header_auth.test.ts +179 -0
- package/src/tests/tests.ts +2 -0
- package/test_generated.pdf +0 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
require('source-map-support').install();
|
|
2
|
+
|
|
3
|
+
import axios from "axios"
|
|
4
|
+
import { Session } from "../../sdk"
|
|
5
|
+
import { log_header, async_test, assert } from "@tellescope/testing"
|
|
6
|
+
import { setup_tests } from "../setup"
|
|
7
|
+
|
|
8
|
+
const host = process.env.API_URL || "http://localhost:8080"
|
|
9
|
+
|
|
10
|
+
// same seeded test API key used by formsort_tests (tests.ts)
|
|
11
|
+
const TEST_API_KEY = "9d4f9dff00f60df2690a16da2cb848f289b447614ad9bef850e54af09a1fbf7a"
|
|
12
|
+
// seeded second-tenant key (sdkOther in tests.ts) — valid, but for a different organization
|
|
13
|
+
const OTHER_TENANT_API_KEY = "ba745e25162bb95a795c5fa1af70df188d93c4d3aac9c48b34a5c8c9dd7b80f7"
|
|
14
|
+
const TEST_EMAIL = "formsort-header-auth@tellescope.com"
|
|
15
|
+
|
|
16
|
+
export const formsort_header_auth_tests = async ({ sdk, sdkNonAdmin } : { sdk: Session, sdkNonAdmin: Session }) => {
|
|
17
|
+
log_header("FormSort Header Auth Tests")
|
|
18
|
+
|
|
19
|
+
const form = await sdk.api.forms.createOne({ title: "FormSort Header Auth" })
|
|
20
|
+
|
|
21
|
+
const buildUrl = (route: 'formsort' | 'form-ingestion', pathSegment: string) => {
|
|
22
|
+
const url = new URL(`${host}/v1/webhooks/${route}/${pathSegment}`)
|
|
23
|
+
url.searchParams.set('formId', form.id)
|
|
24
|
+
url.searchParams.set('returnJSON', 'true')
|
|
25
|
+
return url.toString()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const bodyFor = (responder_uuid: string) => ({
|
|
29
|
+
answers: [{ key: 'email', value: TEST_EMAIL }],
|
|
30
|
+
responder_uuid,
|
|
31
|
+
finalized: true,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const formResponseIds: string[] = []
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
// 1. Valid key via Authorization: API_KEY header (placeholder path segment)
|
|
38
|
+
const headerAuthResult = await axios.post(
|
|
39
|
+
buildUrl('formsort', 'key-in-header'),
|
|
40
|
+
bodyFor("fha-1"),
|
|
41
|
+
{ headers: { Authorization: `API_KEY ${TEST_API_KEY}` } },
|
|
42
|
+
)
|
|
43
|
+
assert(
|
|
44
|
+
headerAuthResult.status === 200 && !!headerAuthResult.data?.formResponseId && !!headerAuthResult.data?.enduserId,
|
|
45
|
+
`Header auth failed: ${headerAuthResult.status} ${JSON.stringify(headerAuthResult.data)}`,
|
|
46
|
+
"Valid API_KEY header authenticates",
|
|
47
|
+
)
|
|
48
|
+
if (headerAuthResult.data?.formResponseId) formResponseIds.push(headerAuthResult.data.formResponseId)
|
|
49
|
+
|
|
50
|
+
await async_test(
|
|
51
|
+
"Form response created via header auth is retrievable",
|
|
52
|
+
() => sdk.api.form_responses.getOne(headerAuthResult.data.formResponseId),
|
|
53
|
+
{ onResult: fr => fr.formId === form.id && fr.source === 'Formsort' && !!fr.submittedAt },
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
// 2. Invalid key in header rejected, even with a valid key in the path (header takes precedence)
|
|
57
|
+
await async_test(
|
|
58
|
+
"Invalid API_KEY header rejected despite valid key in path",
|
|
59
|
+
() => axios.post(
|
|
60
|
+
buildUrl('formsort', TEST_API_KEY),
|
|
61
|
+
bodyFor("fha-2"),
|
|
62
|
+
{ headers: { Authorization: `API_KEY not-a-real-key` } },
|
|
63
|
+
),
|
|
64
|
+
{ shouldError: true, onError: (e: any) => e.response?.status === 401 },
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
// 3. Back-compat: valid key in path, no Authorization header
|
|
68
|
+
const pathAuthResult = await axios.post(buildUrl('formsort', TEST_API_KEY), bodyFor("fha-3"))
|
|
69
|
+
assert(
|
|
70
|
+
pathAuthResult.status === 200 && !!pathAuthResult.data?.formResponseId,
|
|
71
|
+
`Path auth failed: ${pathAuthResult.status} ${JSON.stringify(pathAuthResult.data)}`,
|
|
72
|
+
"Path-segment API key still authenticates (no header)",
|
|
73
|
+
)
|
|
74
|
+
if (pathAuthResult.data?.formResponseId) formResponseIds.push(pathAuthResult.data.formResponseId)
|
|
75
|
+
|
|
76
|
+
// 4. Back-compat: non-API_KEY Authorization scheme falls through to path key
|
|
77
|
+
const bearerFallthroughResult = await axios.post(
|
|
78
|
+
buildUrl('formsort', TEST_API_KEY),
|
|
79
|
+
bodyFor("fha-4"),
|
|
80
|
+
{ headers: { Authorization: 'Bearer unrelated-token' } },
|
|
81
|
+
)
|
|
82
|
+
assert(
|
|
83
|
+
bearerFallthroughResult.status === 200 && !!bearerFallthroughResult.data?.formResponseId,
|
|
84
|
+
`Bearer fallthrough failed: ${bearerFallthroughResult.status} ${JSON.stringify(bearerFallthroughResult.data)}`,
|
|
85
|
+
"Non-API_KEY Authorization scheme falls through to path key",
|
|
86
|
+
)
|
|
87
|
+
if (bearerFallthroughResult.data?.formResponseId) formResponseIds.push(bearerFallthroughResult.data.formResponseId)
|
|
88
|
+
|
|
89
|
+
// 5. No valid key in header or path
|
|
90
|
+
await async_test(
|
|
91
|
+
"No valid key anywhere rejected",
|
|
92
|
+
() => axios.post(buildUrl('formsort', 'not-a-real-key'), bodyFor("fha-5")),
|
|
93
|
+
{ shouldError: true, onError: (e: any) => e.response?.status === 401 },
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
// 6. Alias route accepts header auth too
|
|
97
|
+
const aliasResult = await axios.post(
|
|
98
|
+
buildUrl('form-ingestion', 'key-in-header'),
|
|
99
|
+
bodyFor("fha-6"),
|
|
100
|
+
{ headers: { Authorization: `API_KEY ${TEST_API_KEY}` } },
|
|
101
|
+
)
|
|
102
|
+
assert(
|
|
103
|
+
aliasResult.status === 200 && !!aliasResult.data?.formResponseId,
|
|
104
|
+
`form-ingestion header auth failed: ${aliasResult.status} ${JSON.stringify(aliasResult.data)}`,
|
|
105
|
+
"form-ingestion alias accepts API_KEY header",
|
|
106
|
+
)
|
|
107
|
+
if (aliasResult.data?.formResponseId) formResponseIds.push(aliasResult.data.formResponseId)
|
|
108
|
+
|
|
109
|
+
// 7. Back-compat: API_KEY scheme with no value falls through to the path key
|
|
110
|
+
const emptyValueResult = await axios.post(
|
|
111
|
+
buildUrl('formsort', TEST_API_KEY),
|
|
112
|
+
bodyFor("fha-7"),
|
|
113
|
+
{ headers: { Authorization: 'API_KEY' } },
|
|
114
|
+
)
|
|
115
|
+
assert(
|
|
116
|
+
emptyValueResult.status === 200 && !!emptyValueResult.data?.formResponseId,
|
|
117
|
+
`Empty-value header fallthrough failed: ${emptyValueResult.status} ${JSON.stringify(emptyValueResult.data)}`,
|
|
118
|
+
"API_KEY scheme without value falls through to path key",
|
|
119
|
+
)
|
|
120
|
+
if (emptyValueResult.data?.formResponseId) formResponseIds.push(emptyValueResult.data.formResponseId)
|
|
121
|
+
|
|
122
|
+
// 8. Back-compat: single-token (schemeless) Authorization header falls through to the path key
|
|
123
|
+
const singleTokenResult = await axios.post(
|
|
124
|
+
buildUrl('formsort', TEST_API_KEY),
|
|
125
|
+
bodyFor("fha-8"),
|
|
126
|
+
{ headers: { Authorization: 'some-opaque-token' } },
|
|
127
|
+
)
|
|
128
|
+
assert(
|
|
129
|
+
singleTokenResult.status === 200 && !!singleTokenResult.data?.formResponseId,
|
|
130
|
+
`Single-token header fallthrough failed: ${singleTokenResult.status} ${JSON.stringify(singleTokenResult.data)}`,
|
|
131
|
+
"Schemeless Authorization header falls through to path key",
|
|
132
|
+
)
|
|
133
|
+
if (singleTokenResult.data?.formResponseId) formResponseIds.push(singleTokenResult.data.formResponseId)
|
|
134
|
+
|
|
135
|
+
// 9. When header and path both hold VALID keys, the header key's organization scopes the request:
|
|
136
|
+
// the formId belongs to the primary tenant, so an other-tenant header key must fail form lookup (400),
|
|
137
|
+
// proving the path key's org is not used
|
|
138
|
+
await async_test(
|
|
139
|
+
"Valid other-tenant header key overrides valid path key (org scoping)",
|
|
140
|
+
() => axios.post(
|
|
141
|
+
buildUrl('formsort', TEST_API_KEY),
|
|
142
|
+
bodyFor("fha-9"),
|
|
143
|
+
{ headers: { Authorization: `API_KEY ${OTHER_TENANT_API_KEY}` } },
|
|
144
|
+
),
|
|
145
|
+
{ shouldError: true, onError: (e: any) => e.response?.status === 400 && e.response?.data === 'formId Invalid' },
|
|
146
|
+
)
|
|
147
|
+
} finally {
|
|
148
|
+
for (const id of formResponseIds) {
|
|
149
|
+
try { await sdk.api.form_responses.deleteOne(id) } catch (err) { /* may not exist */ }
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const enduser = await sdk.api.endusers.getOne({ email: TEST_EMAIL })
|
|
153
|
+
await sdk.api.endusers.deleteOne(enduser.id)
|
|
154
|
+
} catch (err) { /* may not exist */ }
|
|
155
|
+
try { await sdk.api.forms.deleteOne(form.id) } catch (err) { /* may not exist */ }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Allow running this test file independently
|
|
160
|
+
if (require.main === module) {
|
|
161
|
+
console.log(`🌐 Using API URL: ${host}`)
|
|
162
|
+
const sdk = new Session({ host })
|
|
163
|
+
const sdkNonAdmin = new Session({ host })
|
|
164
|
+
|
|
165
|
+
const runTests = async () => {
|
|
166
|
+
await setup_tests(sdk, sdkNonAdmin)
|
|
167
|
+
await formsort_header_auth_tests({ sdk, sdkNonAdmin })
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
runTests()
|
|
171
|
+
.then(() => {
|
|
172
|
+
console.log("✅ FormSort header auth tests completed successfully")
|
|
173
|
+
process.exit(0)
|
|
174
|
+
})
|
|
175
|
+
.catch((error) => {
|
|
176
|
+
console.error("❌ FormSort header auth tests failed:", error)
|
|
177
|
+
process.exit(1)
|
|
178
|
+
})
|
|
179
|
+
}
|
package/src/tests/tests.ts
CHANGED
|
@@ -121,6 +121,7 @@ import { database_cascade_delete_tests } from "./api_tests/database_cascade_dele
|
|
|
121
121
|
import { ai_conversations_tests } from "./api_tests/ai_conversations.test";
|
|
122
122
|
import { load_team_chat_tests } from "./api_tests/load_team_chat.test";
|
|
123
123
|
import { form_started_trigger_tests } from "./api_tests/form_started_trigger.test";
|
|
124
|
+
import { formsort_header_auth_tests } from "./api_tests/formsort_header_auth.test";
|
|
124
125
|
import { form_submitted_trigger_tests } from "./api_tests/form_submitted_trigger.test";
|
|
125
126
|
import { dont_sync_to_elation_form_submission_tests } from "./api_tests/dont_sync_to_elation_form_submission.test";
|
|
126
127
|
import { medication_added_trigger_tests } from "./api_tests/medication_added_trigger.test";
|
|
@@ -15126,6 +15127,7 @@ const ip_address_form_tests = async () => {
|
|
|
15126
15127
|
await afteraction_day_of_month_delay_tests({ sdk, sdkNonAdmin })
|
|
15127
15128
|
await bulk_assignment_tests({ sdk, sdkNonAdmin })
|
|
15128
15129
|
await formsort_tests()
|
|
15130
|
+
await formsort_header_auth_tests({ sdk, sdkNonAdmin })
|
|
15129
15131
|
await self_serve_appointment_booking_tests()
|
|
15130
15132
|
await test_ticket_automation_assignment_and_optimization()
|
|
15131
15133
|
await monthly_availability_restrictions_tests({ sdk, sdkNonAdmin })
|
package/test_generated.pdf
CHANGED
|
Binary file
|