@slates/provider-handler 1.0.0-rc.42 → 1.0.0-rc.44
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/dist/index.cjs +247 -83
- package/dist/index.module.js +251 -84
- package/package.json +3 -3
- package/src/index.ts +275 -78
- package/src/runtimeInfo.test.ts +129 -0
- package/src/spec.test.ts +91 -1
- package/src/spec.ts +47 -9
- package/src/validation.ts +2 -1
- package/src/webhookDeliveryAuthenticate.test.ts +179 -0
- package/src/webhookSetup.test.ts +179 -0
package/src/spec.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isServiceError } from '@lowerdeck/error';
|
|
2
|
+
import { slatesTriggerGroup } from '@slates/proto';
|
|
2
3
|
import { describe, expect, it } from 'vitest';
|
|
3
4
|
import { z } from 'zod';
|
|
4
5
|
import {
|
|
@@ -8,11 +9,14 @@ import {
|
|
|
8
9
|
getExposedTriggerGroups,
|
|
9
10
|
getMappableAction,
|
|
10
11
|
getTriggersForGroup,
|
|
12
|
+
getWebhookDeliveryConfig,
|
|
13
|
+
getWebhookManualRegistration,
|
|
11
14
|
isMappableTrigger,
|
|
12
15
|
mapAction,
|
|
13
16
|
mapAdapter,
|
|
14
17
|
mapAuthMethod,
|
|
15
|
-
mapToolCategory
|
|
18
|
+
mapToolCategory,
|
|
19
|
+
mapTriggerGroup
|
|
16
20
|
} from './spec';
|
|
17
21
|
|
|
18
22
|
let baseAction = {
|
|
@@ -202,6 +206,92 @@ describe('adapter exposure', () => {
|
|
|
202
206
|
});
|
|
203
207
|
});
|
|
204
208
|
|
|
209
|
+
describe('webhook registration config', () => {
|
|
210
|
+
let config = {
|
|
211
|
+
userConfigSchema: z.object({ secret: z.string().describe('Signing secret') }),
|
|
212
|
+
fullConfigSchema: z.object({ secret: z.string(), endpoint: z.string() }),
|
|
213
|
+
setup: async () => ({ webhookSetupDocument: '', partialWebhookRegistrationPayload: {} }),
|
|
214
|
+
finish: async () => ({ webhookRegistrationPayload: {} }),
|
|
215
|
+
authenticate: async () => ({ routingKeys: [] })
|
|
216
|
+
};
|
|
217
|
+
let schemas = {
|
|
218
|
+
userConfigSchema: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
properties: { secret: { type: 'string', description: 'Signing secret' } },
|
|
221
|
+
required: ['secret']
|
|
222
|
+
},
|
|
223
|
+
fullConfigSchema: {
|
|
224
|
+
type: 'object',
|
|
225
|
+
properties: { secret: { type: 'string' }, endpoint: { type: 'string' } },
|
|
226
|
+
required: ['secret', 'endpoint']
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
let autoRegistration = {
|
|
230
|
+
webhookTargetList: async () => ({ targets: [], nextPageToken: null }),
|
|
231
|
+
webhookRegister: async () => ({
|
|
232
|
+
webhookRegistrationIdentifier: 'reg',
|
|
233
|
+
webhookRegistrationPayload: {}
|
|
234
|
+
}),
|
|
235
|
+
webhookUnregister: async () => {}
|
|
236
|
+
};
|
|
237
|
+
let group = (webhook: Record<string, unknown>): any => ({
|
|
238
|
+
key: 'group',
|
|
239
|
+
name: 'Group',
|
|
240
|
+
source: 'webhook',
|
|
241
|
+
webhook: { process: async () => ({ events: [] }), ...webhook }
|
|
242
|
+
});
|
|
243
|
+
let auto = group({ autoRegistration });
|
|
244
|
+
let manual = group({ manualRegistration: config });
|
|
245
|
+
let configured = group({ autoRegistration, deliveryConfig: config });
|
|
246
|
+
|
|
247
|
+
it('keeps plain automatic and manual groups serialised as before', () => {
|
|
248
|
+
expect(mapTriggerGroup(auto).invocation).toEqual({
|
|
249
|
+
type: 'webhook',
|
|
250
|
+
registration: { mode: 'auto' }
|
|
251
|
+
});
|
|
252
|
+
expect(mapTriggerGroup(manual).invocation).toMatchObject({
|
|
253
|
+
type: 'webhook',
|
|
254
|
+
registration: { mode: 'manual', ...schemas }
|
|
255
|
+
});
|
|
256
|
+
expect(
|
|
257
|
+
Object.keys((mapTriggerGroup(manual).invocation as any).registration).sort()
|
|
258
|
+
).toEqual(['fullConfigSchema', 'mode', 'userConfigSchema']);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('advertises the delivery config of an automatic group', () => {
|
|
262
|
+
let mapped = mapTriggerGroup(configured);
|
|
263
|
+
expect(mapped.invocation).toMatchObject({
|
|
264
|
+
type: 'webhook',
|
|
265
|
+
registration: { mode: 'auto', deliveryConfig: schemas }
|
|
266
|
+
});
|
|
267
|
+
expect(slatesTriggerGroup.parse(mapped).invocation).toEqual(mapped.invocation);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('authenticates deliveries only for automatic groups with a delivery config', () => {
|
|
271
|
+
expect(getWebhookDeliveryConfig(configured)).toBe(config);
|
|
272
|
+
for (let unsupported of [
|
|
273
|
+
auto,
|
|
274
|
+
manual,
|
|
275
|
+
{ key: 'group', name: 'Group', source: 'polling' } as any
|
|
276
|
+
]) {
|
|
277
|
+
expect(() => getWebhookDeliveryConfig(unsupported)).toThrow(
|
|
278
|
+
'Trigger group does not support webhook delivery authentication: group'
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('serves setup and finish for manual groups and automatic groups with a delivery config', () => {
|
|
284
|
+
expect(getWebhookManualRegistration(manual)).toBe(config);
|
|
285
|
+
expect(getWebhookManualRegistration(configured)).toBe(config);
|
|
286
|
+
expect(() => getWebhookManualRegistration(auto)).toThrow(
|
|
287
|
+
'Trigger group does not support manual webhook registration: group'
|
|
288
|
+
);
|
|
289
|
+
expect(() =>
|
|
290
|
+
getWebhookManualRegistration({ key: 'poll', name: 'Poll', source: 'polling' } as any)
|
|
291
|
+
).toThrow('Trigger group does not support manual webhook registration: poll');
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
205
295
|
describe('mapAuthMethod', () => {
|
|
206
296
|
let slate: any = { spec: { auth: { outputSchema: z.object({ token: z.string() }) } } };
|
|
207
297
|
let oauthMethod: any = {
|
package/src/spec.ts
CHANGED
|
@@ -266,7 +266,11 @@ export let getWebhookAutoRegistration = <ConfigType extends {}, AuthType extends
|
|
|
266
266
|
export let getWebhookManualRegistration = <ConfigType extends {}, AuthType extends {}>(
|
|
267
267
|
group: ReturnType<typeof getTriggerGroup<ConfigType, AuthType>>
|
|
268
268
|
) => {
|
|
269
|
-
|
|
269
|
+
let registration =
|
|
270
|
+
group.source === 'webhook'
|
|
271
|
+
? (group.webhook?.manualRegistration ?? group.webhook?.deliveryConfig)
|
|
272
|
+
: undefined;
|
|
273
|
+
if (!registration) {
|
|
270
274
|
throw new ServiceError(
|
|
271
275
|
badRequestError({
|
|
272
276
|
message: `Trigger group does not support manual webhook registration: ${group.key}`
|
|
@@ -274,9 +278,31 @@ export let getWebhookManualRegistration = <ConfigType extends {}, AuthType exten
|
|
|
274
278
|
);
|
|
275
279
|
}
|
|
276
280
|
|
|
277
|
-
return
|
|
281
|
+
return registration;
|
|
278
282
|
};
|
|
279
283
|
|
|
284
|
+
export let getWebhookDeliveryConfig = <ConfigType extends {}, AuthType extends {}>(
|
|
285
|
+
group: ReturnType<typeof getTriggerGroup<ConfigType, AuthType>>
|
|
286
|
+
) => {
|
|
287
|
+
if (group.source !== 'webhook' || !group.webhook?.deliveryConfig) {
|
|
288
|
+
throw new ServiceError(
|
|
289
|
+
badRequestError({
|
|
290
|
+
message: `Trigger group does not support webhook delivery authentication: ${group.key}`
|
|
291
|
+
})
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return group.webhook.deliveryConfig;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
let mapWebhookConfigSchemas = (config: {
|
|
299
|
+
userConfigSchema: z.ZodType<any>;
|
|
300
|
+
fullConfigSchema: z.ZodType<any>;
|
|
301
|
+
}) => ({
|
|
302
|
+
userConfigSchema: toJsonSchema(config.userConfigSchema),
|
|
303
|
+
fullConfigSchema: toJsonSchema(config.fullConfigSchema)
|
|
304
|
+
});
|
|
305
|
+
|
|
280
306
|
export let mapToolCategory = (category: SlateToolCategory): SlatesToolCategory => ({
|
|
281
307
|
id: category.key,
|
|
282
308
|
name: category.name,
|
|
@@ -307,6 +333,9 @@ export let mapTriggerGroup = <ConfigType extends {}, AuthType extends {}>(
|
|
|
307
333
|
name: group.name,
|
|
308
334
|
description: group.description,
|
|
309
335
|
metadata: group.metadata,
|
|
336
|
+
...(group.selectionSchema
|
|
337
|
+
? { selectionSchema: toJsonSchema(group.selectionSchema, 'input') }
|
|
338
|
+
: {}),
|
|
310
339
|
invocation:
|
|
311
340
|
group.source === 'polling'
|
|
312
341
|
? {
|
|
@@ -317,16 +346,25 @@ export let mapTriggerGroup = <ConfigType extends {}, AuthType extends {}>(
|
|
|
317
346
|
? { type: 'gateway' }
|
|
318
347
|
: {
|
|
319
348
|
type: 'webhook',
|
|
349
|
+
...(group.webhook?.expandWebhook ? { supportsExpansion: true } : {}),
|
|
350
|
+
...(group.webhook?.autoRegistration?.webhookRenew
|
|
351
|
+
? { supportsRenewal: true }
|
|
352
|
+
: {}),
|
|
353
|
+
...(group.webhook?.reconcileIntervalSeconds !== undefined
|
|
354
|
+
? { reconcileIntervalSeconds: group.webhook.reconcileIntervalSeconds }
|
|
355
|
+
: {}),
|
|
320
356
|
registration: group.webhook?.manualRegistration
|
|
321
357
|
? {
|
|
322
358
|
mode: 'manual',
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
359
|
+
...mapWebhookConfigSchemas(group.webhook.manualRegistration)
|
|
360
|
+
}
|
|
361
|
+
: {
|
|
362
|
+
mode: 'auto',
|
|
363
|
+
...(group.webhook?.deliveryConfig
|
|
364
|
+
? {
|
|
365
|
+
deliveryConfig: mapWebhookConfigSchemas(group.webhook.deliveryConfig)
|
|
366
|
+
}
|
|
367
|
+
: {})
|
|
329
368
|
}
|
|
330
|
-
: { mode: 'auto' }
|
|
331
369
|
}
|
|
332
370
|
});
|
package/src/validation.ts
CHANGED
|
@@ -26,8 +26,9 @@ export let validate = <T>(
|
|
|
26
26
|
return result.data;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
export let toJsonSchema = (schema: z.ZodType<any
|
|
29
|
+
export let toJsonSchema = (schema: z.ZodType<any>, io: 'input' | 'output' = 'output') =>
|
|
30
30
|
schema.toJSONSchema({
|
|
31
|
+
io,
|
|
31
32
|
unrepresentable: 'any',
|
|
32
33
|
override: ctx => {
|
|
33
34
|
let def = ctx.zodSchema._zod.def;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { SLATES_PROTOCOL_VERSION, SlatesProviderProtoHandlerManager } from '@slates/proto';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { createProviderHandler } from './index';
|
|
5
|
+
|
|
6
|
+
type Authentication = { routingKeys?: string[]; rejected?: { status: number } };
|
|
7
|
+
|
|
8
|
+
let createManager = async (authenticate: (ctx: any) => Promise<Authentication>) => {
|
|
9
|
+
let contexts: {
|
|
10
|
+
auth: unknown;
|
|
11
|
+
config: unknown;
|
|
12
|
+
deliveryConfig: unknown;
|
|
13
|
+
request: Request;
|
|
14
|
+
}[] = [];
|
|
15
|
+
let autoRegistration = {
|
|
16
|
+
webhookTargetList: async () => ({ targets: [], nextPageToken: null }),
|
|
17
|
+
webhookRegister: async () => ({
|
|
18
|
+
webhookRegistrationIdentifier: 'reg',
|
|
19
|
+
webhookRegistrationPayload: {}
|
|
20
|
+
}),
|
|
21
|
+
webhookUnregister: async () => {}
|
|
22
|
+
};
|
|
23
|
+
let deliveryConfig = {
|
|
24
|
+
userConfigSchema: z.object({}),
|
|
25
|
+
fullConfigSchema: z.object({}),
|
|
26
|
+
setup: async () => ({ webhookSetupDocument: '', partialWebhookRegistrationPayload: {} }),
|
|
27
|
+
finish: async () => ({ webhookRegistrationPayload: {} }),
|
|
28
|
+
authenticate: async (ctx: any) => {
|
|
29
|
+
contexts.push({
|
|
30
|
+
auth: ctx.auth,
|
|
31
|
+
config: ctx.config,
|
|
32
|
+
deliveryConfig: ctx.input.deliveryConfig,
|
|
33
|
+
request: ctx.input.request
|
|
34
|
+
});
|
|
35
|
+
return authenticate(ctx);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
let group = (key: string, webhook: Record<string, unknown>) => ({
|
|
39
|
+
key,
|
|
40
|
+
name: key,
|
|
41
|
+
description: undefined,
|
|
42
|
+
metadata: {},
|
|
43
|
+
source: 'webhook',
|
|
44
|
+
webhook: { process: async () => ({ events: [] }), ...webhook }
|
|
45
|
+
});
|
|
46
|
+
let slate = {
|
|
47
|
+
spec: { key: 'test', name: 'Test' },
|
|
48
|
+
adapters: [],
|
|
49
|
+
actions: [],
|
|
50
|
+
triggerGroups: [
|
|
51
|
+
group('configured', { autoRegistration, deliveryConfig }),
|
|
52
|
+
group('auto', { autoRegistration }),
|
|
53
|
+
group('manual', { manualRegistration: deliveryConfig }),
|
|
54
|
+
{ key: 'poll', name: 'poll', source: 'polling', polling: { intervalSeconds: 600 } }
|
|
55
|
+
]
|
|
56
|
+
};
|
|
57
|
+
let manager = await createProviderHandler(slate as any, []).run();
|
|
58
|
+
|
|
59
|
+
await SlatesProviderProtoHandlerManager.handleInput(manager, {
|
|
60
|
+
jsonrpc: '2.0',
|
|
61
|
+
method: 'slates/hello',
|
|
62
|
+
params: { protocol: SLATES_PROTOCOL_VERSION }
|
|
63
|
+
});
|
|
64
|
+
await SlatesProviderProtoHandlerManager.handleInput(manager, {
|
|
65
|
+
jsonrpc: '2.0',
|
|
66
|
+
method: 'slates/participant.set',
|
|
67
|
+
params: { participants: [] }
|
|
68
|
+
});
|
|
69
|
+
await SlatesProviderProtoHandlerManager.handleInput(manager, {
|
|
70
|
+
jsonrpc: '2.0',
|
|
71
|
+
method: 'slates/hub.capabilities.set',
|
|
72
|
+
params: { capabilities: { triggers: true } }
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
contexts,
|
|
77
|
+
authenticate: (params: Record<string, unknown> = {}) =>
|
|
78
|
+
SlatesProviderProtoHandlerManager.handleInput(manager, {
|
|
79
|
+
jsonrpc: '2.0',
|
|
80
|
+
id: 'authenticate',
|
|
81
|
+
method: 'slates/trigger_group.webhook.delivery_authenticate',
|
|
82
|
+
params: {
|
|
83
|
+
triggerGroupId: 'configured',
|
|
84
|
+
url: 'https://hub.example/receive/config?attempt=1',
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: { authorization: 'Bearer token', 'content-type': 'application/json' },
|
|
87
|
+
body: { encoding: 'base64', content: btoa('{"message":{}}') },
|
|
88
|
+
deliveryConfig: { audience: 'https://hub.example/receive/config', extra: [1, 2] },
|
|
89
|
+
...params
|
|
90
|
+
}
|
|
91
|
+
} as any)
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
describe('slates/trigger_group.webhook.delivery_authenticate', () => {
|
|
96
|
+
it('hands the received request and the saved config to the handler without credentials', async () => {
|
|
97
|
+
let { authenticate, contexts } = await createManager(async () => ({
|
|
98
|
+
routingKeys: ['mailbox:user@example.com', 'mailbox:other@example.com']
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
expect(await authenticate()).toEqual({
|
|
102
|
+
jsonrpc: '2.0',
|
|
103
|
+
id: 'authenticate',
|
|
104
|
+
result: {
|
|
105
|
+
routingKeys: ['mailbox:user@example.com', 'mailbox:other@example.com'],
|
|
106
|
+
rejected: null
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
let [received] = contexts;
|
|
110
|
+
expect(received).toMatchObject({
|
|
111
|
+
auth: {},
|
|
112
|
+
config: {},
|
|
113
|
+
deliveryConfig: { audience: 'https://hub.example/receive/config', extra: [1, 2] }
|
|
114
|
+
});
|
|
115
|
+
expect(received!.request.url).toBe('https://hub.example/receive/config?attempt=1');
|
|
116
|
+
expect(received!.request.method).toBe('POST');
|
|
117
|
+
expect(Object.fromEntries(received!.request.headers)).toEqual({
|
|
118
|
+
authorization: 'Bearer token',
|
|
119
|
+
'content-type': 'application/json'
|
|
120
|
+
});
|
|
121
|
+
expect(await received!.request.text()).toBe('{"message":{}}');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('reconstructs a request without a body', async () => {
|
|
125
|
+
let { authenticate, contexts } = await createManager(async () => ({ routingKeys: [] }));
|
|
126
|
+
|
|
127
|
+
await authenticate({ method: 'GET', headers: {}, body: null });
|
|
128
|
+
expect(contexts[0]!.request.method).toBe('GET');
|
|
129
|
+
expect(contexts[0]!.request.body).toBeNull();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('acknowledges an authenticated request that addresses no registration', async () => {
|
|
133
|
+
let { authenticate } = await createManager(async () => ({ routingKeys: [] }));
|
|
134
|
+
|
|
135
|
+
expect(await authenticate()).toMatchObject({
|
|
136
|
+
result: { routingKeys: [], rejected: null }
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it.each([
|
|
141
|
+
401, 403, 404, 503
|
|
142
|
+
])('passes a rejection with status %i through without routing keys', async status => {
|
|
143
|
+
let { authenticate } = await createManager(async () => ({ rejected: { status } }));
|
|
144
|
+
|
|
145
|
+
expect(await authenticate()).toMatchObject({
|
|
146
|
+
result: { routingKeys: [], rejected: { status } }
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it.each([
|
|
151
|
+
['a status outside 400-599', { rejected: { status: 200 } }],
|
|
152
|
+
['a fractional status', { rejected: { status: 401.5 } }],
|
|
153
|
+
['no routing keys and no rejection', {}],
|
|
154
|
+
['an empty routing key', { routingKeys: [''] }],
|
|
155
|
+
['a routing key over 512 characters', { routingKeys: ['x'.repeat(513)] }],
|
|
156
|
+
['more than 16 routing keys', { routingKeys: Array(17).fill('key') }]
|
|
157
|
+
])('rejects a handler result with %s', async (_, result) => {
|
|
158
|
+
let { authenticate } = await createManager(async () => result);
|
|
159
|
+
|
|
160
|
+
expect(await authenticate()).toMatchObject({
|
|
161
|
+
error: { message: 'Webhook delivery authentication returned an invalid result' }
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it.each([
|
|
166
|
+
'auto',
|
|
167
|
+
'manual',
|
|
168
|
+
'poll'
|
|
169
|
+
])('rejects the %s group, which has no delivery config', async triggerGroupId => {
|
|
170
|
+
let { authenticate, contexts } = await createManager(async () => ({ routingKeys: [] }));
|
|
171
|
+
|
|
172
|
+
expect(await authenticate({ triggerGroupId })).toMatchObject({
|
|
173
|
+
error: {
|
|
174
|
+
message: `Trigger group does not support webhook delivery authentication: ${triggerGroupId}`
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
expect(contexts).toEqual([]);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { SLATES_PROTOCOL_VERSION, SlatesProviderProtoHandlerManager } from '@slates/proto';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { createProviderHandler } from './index';
|
|
5
|
+
|
|
6
|
+
let createManager = async () => {
|
|
7
|
+
let contexts: { operation: string; auth: unknown; config: unknown; input: unknown }[] = [];
|
|
8
|
+
let config = (finish = true) => ({
|
|
9
|
+
userConfigSchema: z.object({ secret: z.string() }),
|
|
10
|
+
fullConfigSchema: z.object({ secret: z.string(), endpoint: z.string() }),
|
|
11
|
+
setup: async (ctx: any) => {
|
|
12
|
+
contexts.push({
|
|
13
|
+
operation: 'setup',
|
|
14
|
+
auth: ctx.auth,
|
|
15
|
+
config: ctx.config,
|
|
16
|
+
input: ctx.input
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
webhookSetupDocument: `Send events to ${ctx.input.webhookUrl}`,
|
|
20
|
+
partialWebhookRegistrationPayload: { endpoint: ctx.input.webhookUrl }
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
...(finish
|
|
24
|
+
? {
|
|
25
|
+
finish: async (ctx: any) => {
|
|
26
|
+
contexts.push({
|
|
27
|
+
operation: 'finish',
|
|
28
|
+
auth: ctx.auth,
|
|
29
|
+
config: ctx.config,
|
|
30
|
+
input: ctx.input
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
webhookRegistrationPayload: {
|
|
34
|
+
...ctx.input.partialWebhookRegistrationPayload,
|
|
35
|
+
...ctx.input.userWebhookRegistrationPayload
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
: {})
|
|
41
|
+
});
|
|
42
|
+
let autoRegistration = {
|
|
43
|
+
webhookTargetList: async () => ({ targets: [], nextPageToken: null }),
|
|
44
|
+
webhookRegister: async () => ({
|
|
45
|
+
webhookRegistrationIdentifier: 'reg',
|
|
46
|
+
webhookRegistrationPayload: {}
|
|
47
|
+
}),
|
|
48
|
+
webhookUnregister: async () => {}
|
|
49
|
+
};
|
|
50
|
+
let group = (key: string, webhook: Record<string, unknown>) => ({
|
|
51
|
+
key,
|
|
52
|
+
name: key,
|
|
53
|
+
description: undefined,
|
|
54
|
+
metadata: {},
|
|
55
|
+
source: 'webhook',
|
|
56
|
+
webhook: { process: async () => ({ events: [] }), ...webhook }
|
|
57
|
+
});
|
|
58
|
+
let slate = {
|
|
59
|
+
spec: { key: 'test', name: 'Test' },
|
|
60
|
+
adapters: [],
|
|
61
|
+
actions: [],
|
|
62
|
+
triggerGroups: [
|
|
63
|
+
group('manual', { manualRegistration: config() }),
|
|
64
|
+
group('manual_without_finish', { manualRegistration: config(false) }),
|
|
65
|
+
group('configured', { autoRegistration, deliveryConfig: config() }),
|
|
66
|
+
group('auto', { autoRegistration }),
|
|
67
|
+
{ key: 'poll', name: 'poll', source: 'polling', polling: { intervalSeconds: 600 } }
|
|
68
|
+
]
|
|
69
|
+
};
|
|
70
|
+
let manager = await createProviderHandler(slate as any, []).run();
|
|
71
|
+
|
|
72
|
+
await SlatesProviderProtoHandlerManager.handleInput(manager, {
|
|
73
|
+
jsonrpc: '2.0',
|
|
74
|
+
method: 'slates/hello',
|
|
75
|
+
params: { protocol: SLATES_PROTOCOL_VERSION }
|
|
76
|
+
});
|
|
77
|
+
await SlatesProviderProtoHandlerManager.handleInput(manager, {
|
|
78
|
+
jsonrpc: '2.0',
|
|
79
|
+
method: 'slates/participant.set',
|
|
80
|
+
params: { participants: [] }
|
|
81
|
+
});
|
|
82
|
+
await SlatesProviderProtoHandlerManager.handleInput(manager, {
|
|
83
|
+
jsonrpc: '2.0',
|
|
84
|
+
method: 'slates/hub.capabilities.set',
|
|
85
|
+
params: { capabilities: { triggers: true } }
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
let request = (method: string, params: Record<string, unknown>) =>
|
|
89
|
+
SlatesProviderProtoHandlerManager.handleInput(manager, {
|
|
90
|
+
jsonrpc: '2.0',
|
|
91
|
+
id: method,
|
|
92
|
+
method,
|
|
93
|
+
params
|
|
94
|
+
} as any);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
contexts,
|
|
98
|
+
setup: (triggerGroupId: string) =>
|
|
99
|
+
request('slates/trigger_group.webhook.manual_setup', {
|
|
100
|
+
triggerGroupId,
|
|
101
|
+
webhookUrl: 'https://receive.example/key'
|
|
102
|
+
}),
|
|
103
|
+
finish: (triggerGroupId: string) =>
|
|
104
|
+
request('slates/trigger_group.webhook.manual_finish', {
|
|
105
|
+
triggerGroupId,
|
|
106
|
+
webhookUrl: 'https://receive.example/key',
|
|
107
|
+
partialWebhookRegistrationPayload: { endpoint: 'https://receive.example/key' },
|
|
108
|
+
userWebhookRegistrationPayload: { secret: 'shared' }
|
|
109
|
+
})
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
describe('slates/trigger_group.webhook.manual_setup and manual_finish', () => {
|
|
114
|
+
it.each([
|
|
115
|
+
'manual',
|
|
116
|
+
'configured'
|
|
117
|
+
])('serve the %s group with unchanged shapes and an empty context', async triggerGroupId => {
|
|
118
|
+
let { setup, finish, contexts } = await createManager();
|
|
119
|
+
|
|
120
|
+
expect(await setup(triggerGroupId)).toMatchObject({
|
|
121
|
+
result: {
|
|
122
|
+
webhookSetupDocument: 'Send events to https://receive.example/key',
|
|
123
|
+
partialWebhookRegistrationPayload: { endpoint: 'https://receive.example/key' }
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
expect(await finish(triggerGroupId)).toMatchObject({
|
|
127
|
+
result: {
|
|
128
|
+
webhookRegistrationPayload: {
|
|
129
|
+
endpoint: 'https://receive.example/key',
|
|
130
|
+
secret: 'shared'
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
expect(contexts).toEqual([
|
|
135
|
+
{
|
|
136
|
+
operation: 'setup',
|
|
137
|
+
auth: {},
|
|
138
|
+
config: {},
|
|
139
|
+
input: { webhookUrl: 'https://receive.example/key' }
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
operation: 'finish',
|
|
143
|
+
auth: {},
|
|
144
|
+
config: {},
|
|
145
|
+
input: {
|
|
146
|
+
webhookUrl: 'https://receive.example/key',
|
|
147
|
+
partialWebhookRegistrationPayload: { endpoint: 'https://receive.example/key' },
|
|
148
|
+
userWebhookRegistrationPayload: { secret: 'shared' }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it.each([
|
|
155
|
+
'auto',
|
|
156
|
+
'poll'
|
|
157
|
+
])('reject the %s group, which has no setup to complete', async triggerGroupId => {
|
|
158
|
+
let { setup, finish, contexts } = await createManager();
|
|
159
|
+
|
|
160
|
+
for (let response of [await setup(triggerGroupId), await finish(triggerGroupId)]) {
|
|
161
|
+
expect(response).toMatchObject({
|
|
162
|
+
error: {
|
|
163
|
+
message: `Trigger group does not support manual webhook registration: ${triggerGroupId}`
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
expect(contexts).toEqual([]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('reject finish for a manual group that does not implement it', async () => {
|
|
171
|
+
let { finish } = await createManager();
|
|
172
|
+
|
|
173
|
+
expect(await finish('manual_without_finish')).toMatchObject({
|
|
174
|
+
error: {
|
|
175
|
+
message: 'Trigger group does not support manual webhook finish: manual_without_finish'
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|