@tellescope/sdk 0.0.20 → 0.0.21
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 +24 -0
- package/lib/cjs/enduser.d.ts.map +1 -1
- package/lib/cjs/sdk.d.ts +37 -1
- package/lib/cjs/sdk.d.ts.map +1 -1
- package/lib/cjs/sdk.js +3 -0
- package/lib/cjs/sdk.js.map +1 -1
- package/lib/cjs/tests/tests.js +9 -6
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/cjs/tests/webhooks_tests.d.ts +2 -0
- package/lib/cjs/tests/webhooks_tests.d.ts.map +1 -0
- package/lib/cjs/tests/webhooks_tests.js +261 -0
- package/lib/cjs/tests/webhooks_tests.js.map +1 -0
- package/lib/esm/enduser.d.ts +24 -0
- package/lib/esm/enduser.d.ts.map +1 -1
- package/lib/esm/sdk.d.ts +37 -1
- package/lib/esm/sdk.d.ts.map +1 -1
- package/lib/esm/sdk.js +3 -0
- package/lib/esm/sdk.js.map +1 -1
- package/lib/esm/tests/tests.js +9 -6
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/esm/tests/webhooks_tests.d.ts +2 -0
- package/lib/esm/tests/webhooks_tests.d.ts.map +1 -0
- package/lib/esm/tests/webhooks_tests.js +256 -0
- package/lib/esm/tests/webhooks_tests.js.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -11
- package/src/sdk.ts +9 -0
- package/src/tests/tests.ts +8 -6
- package/src/tests/webhooks_tests.ts +183 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import express from "express"
|
|
2
|
+
import bodyParser from 'body-parser'
|
|
3
|
+
import crypto from "crypto"
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
assert,
|
|
7
|
+
async_test,
|
|
8
|
+
log_header,
|
|
9
|
+
wait,
|
|
10
|
+
} from "@tellescope/testing"
|
|
11
|
+
import {
|
|
12
|
+
objects_equivalent
|
|
13
|
+
} from "@tellescope/utilities"
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
WEBHOOK_MODELS,
|
|
17
|
+
WebhookSupportedModel,
|
|
18
|
+
WebhookRecord,
|
|
19
|
+
WebhookCall,
|
|
20
|
+
CUDSubscription,
|
|
21
|
+
} from "@tellescope/types-models"
|
|
22
|
+
|
|
23
|
+
import { Session } from "../sdk"
|
|
24
|
+
|
|
25
|
+
const [email, password] = [process.env.TEST_EMAIL, process.env.TEST_PASSWORD]
|
|
26
|
+
const [email2, password2] = [process.env.TEST_EMAIL_2, process.env.TEST_PASSWORD_2]
|
|
27
|
+
const [nonAdminEmail, nonAdminPassword] = [process.env.NON_ADMIN_EMAIL, process.env.NON_ADMIN_PASSWORD]
|
|
28
|
+
if (!(email && password && email2 && password2 && nonAdminEmail && nonAdminPassword)) {
|
|
29
|
+
console.error("Set TEST_EMAIL and TEST_PASSWORD")
|
|
30
|
+
process.exit()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
const sdk = new Session({ host: 'http://localhost:8080' })
|
|
35
|
+
const nonAdminSdk = new Session({ host: 'http://localhost:8080' })
|
|
36
|
+
|
|
37
|
+
const app = express()
|
|
38
|
+
app.use(bodyParser.urlencoded({ extended: true, limit: '25mb' }))
|
|
39
|
+
app.use(bodyParser.json({ limit: "25mb" }))
|
|
40
|
+
|
|
41
|
+
const PORT = 4000
|
|
42
|
+
const TEST_SECRET = "this is a test secret for verifying integrity of web hooks"
|
|
43
|
+
const webhookEndpoint = '/handle-webhook'
|
|
44
|
+
const webhookURL = `http://127.0.0.1:${PORT}${webhookEndpoint}`
|
|
45
|
+
|
|
46
|
+
const sha256 = (s: string) => crypto.createHash('sha256').update(s).digest('hex')
|
|
47
|
+
|
|
48
|
+
const verify_integrity = (records: WebhookRecord[], timestamp: string, integrity: string,) => (
|
|
49
|
+
sha256(records.map(r => r.id).join('') + timestamp + TEST_SECRET) === integrity
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const handledEvents: WebhookCall[] = []
|
|
53
|
+
app.post(webhookEndpoint, (req, res) => {
|
|
54
|
+
const body = req.body as WebhookCall
|
|
55
|
+
// console.log('got hook', body.records, body.timestamp, body.integrity)
|
|
56
|
+
|
|
57
|
+
if (!verify_integrity(body.records, body.timestamp, body.integrity)) {
|
|
58
|
+
console.error("Integrity check failed for request", JSON.stringify(body, null, 2))
|
|
59
|
+
process.exit()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
handledEvents.push(req.body)
|
|
63
|
+
res.status(204).end()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const fullSubscription = {} as { [K in WebhookSupportedModel]: CUDSubscription }
|
|
67
|
+
const emptySubscription = {} as { [K in WebhookSupportedModel]: CUDSubscription }
|
|
68
|
+
for (const model in WEBHOOK_MODELS) {
|
|
69
|
+
fullSubscription[model as WebhookSupportedModel] = { create: true, update: true, delete: true }
|
|
70
|
+
emptySubscription[model as WebhookSupportedModel] = { create: false, update: false, delete: false }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let webhookIndex = 0
|
|
74
|
+
const check_next_webhook = async (evaluate: (hook: WebhookCall) => boolean, name: string, error: string, isSubscribed: boolean) => {
|
|
75
|
+
if (isSubscribed === false) return
|
|
76
|
+
|
|
77
|
+
await wait(undefined, 25) // wait for hook to post
|
|
78
|
+
|
|
79
|
+
const event = handledEvents[webhookIndex]
|
|
80
|
+
assert(!!event, 'did not get hook', 'got hook')
|
|
81
|
+
if (!event) return // ensure webhookIndex not incremented
|
|
82
|
+
|
|
83
|
+
const success = evaluate(event)
|
|
84
|
+
assert(success, error, name)
|
|
85
|
+
if (!success) { console.error('Got', event) }
|
|
86
|
+
|
|
87
|
+
webhookIndex++
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const chats_tests = async (isSubscribed: boolean) => {
|
|
91
|
+
log_header(`Chats Tests, isSubscribed=${isSubscribed}`)
|
|
92
|
+
const room = await sdk.api.chat_rooms.createOne({ userIds: [sdk.userInfo.id] })
|
|
93
|
+
|
|
94
|
+
const chat = await sdk.api.chats.createOne({ roomId: room.id, message: "Hello hello hi hello" })
|
|
95
|
+
await check_next_webhook(a => objects_equivalent(a.records, [chat]), 'Create chat error', 'Create chat webhook', isSubscribed)
|
|
96
|
+
|
|
97
|
+
// cleanup
|
|
98
|
+
await sdk.api.chat_rooms.deleteOne(room.id) // also cleans up messages
|
|
99
|
+
|
|
100
|
+
// when chatroom support added for webhooks, check deletion here
|
|
101
|
+
// await check_next_webhook(a => objects_equivalent(a.records, [chat_room]), 'Delete chat room error', 'Delete chat room webhook', isSubscribed)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const meetings_tests = async (isSubscribed: boolean) => {
|
|
105
|
+
log_header(`Meetings Tests, isSubscribed=${isSubscribed}`)
|
|
106
|
+
const meeting = await sdk.api.meetings.start_meeting()
|
|
107
|
+
|
|
108
|
+
await check_next_webhook(a => objects_equivalent(a.records, [meeting]), 'Create meeting error', 'Create meeting webhook', isSubscribed)
|
|
109
|
+
|
|
110
|
+
// cleanup
|
|
111
|
+
await sdk.api.meetings.end_meeting({ id: meeting.id }) // also cleans up messages
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const tests: { [K in WebhookSupportedModel]: (isSubscribed: boolean) => Promise<void> } = {
|
|
115
|
+
chats: chats_tests,
|
|
116
|
+
meetings: meetings_tests,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const run_tests = async () => {
|
|
120
|
+
log_header("Webhooks Tests")
|
|
121
|
+
await sdk.authenticate(email, password)
|
|
122
|
+
await sdk.reset_db()
|
|
123
|
+
await nonAdminSdk.authenticate(nonAdminEmail, nonAdminPassword)
|
|
124
|
+
|
|
125
|
+
await async_test(
|
|
126
|
+
'configure webhook is admin only',
|
|
127
|
+
() => nonAdminSdk.api.webhooks.configure({ url: webhookURL, secret: TEST_SECRET }),
|
|
128
|
+
{ shouldError: true, onError: e => e.message === "Inaccessible" || e.message === "Admin access only"}
|
|
129
|
+
)
|
|
130
|
+
await async_test(
|
|
131
|
+
'update webhook is admin only',
|
|
132
|
+
() => nonAdminSdk.api.webhooks.update({ subscriptionUpdates: fullSubscription }),
|
|
133
|
+
{ shouldError: true, onError: e => e.message === "Inaccessible" || e.message === "Admin access only"}
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
await async_test(
|
|
137
|
+
'configure webhook',
|
|
138
|
+
() => sdk.api.webhooks.configure({ url: webhookURL, secret: TEST_SECRET }),
|
|
139
|
+
{ onResult: _ => true }
|
|
140
|
+
)
|
|
141
|
+
await async_test(
|
|
142
|
+
'configure webhook (only callable once)',
|
|
143
|
+
() => sdk.api.webhooks.configure({ url: webhookURL, secret: TEST_SECRET }),
|
|
144
|
+
{ shouldError: true, onError: e => e.message === "Only one webhook configuration is supported per organization. Use /update-webooks to update your configuration." }
|
|
145
|
+
)
|
|
146
|
+
await async_test(
|
|
147
|
+
'update webhook (set subscriptions)',
|
|
148
|
+
() => sdk.api.webhooks.update({ subscriptionUpdates: fullSubscription }),
|
|
149
|
+
{ onResult: _ => true }
|
|
150
|
+
)
|
|
151
|
+
await async_test(
|
|
152
|
+
'update webhook invalid model',
|
|
153
|
+
() => sdk.api.webhooks.update({ subscriptionUpdates: { notAModel: { create: false } } as any }),
|
|
154
|
+
{ shouldError: true, onError: e => e.message === "Error parsing field subscriptionUpdates: Got unexpected field(s) [notAModel]" }
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
log_header("Webhooks Tests with Subscriptions")
|
|
158
|
+
for (const t in tests) {
|
|
159
|
+
await tests[t as keyof typeof tests](true)
|
|
160
|
+
}
|
|
161
|
+
const finalLength = handledEvents.length
|
|
162
|
+
|
|
163
|
+
await async_test(
|
|
164
|
+
'update webhook (set subscriptions empty)',
|
|
165
|
+
() => sdk.api.webhooks.update({ subscriptionUpdates: emptySubscription }),
|
|
166
|
+
{ onResult: _ => true }
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
log_header("Webhooks Tests without Subscriptions")
|
|
170
|
+
for (const t in tests) {
|
|
171
|
+
await tests[t as keyof typeof tests](false)
|
|
172
|
+
}
|
|
173
|
+
assert(finalLength === handledEvents.length, 'length changed after subscriptions', 'No webhooks posted when no subscription')
|
|
174
|
+
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
app.listen(PORT, async () => {
|
|
178
|
+
try {
|
|
179
|
+
await run_tests()
|
|
180
|
+
} catch(err) { console.error(err) }
|
|
181
|
+
|
|
182
|
+
process.exit()
|
|
183
|
+
})
|