@slates/test 1.0.0-rc.2 → 1.0.0-rc.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slates/test",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0-rc.21",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -15,10 +15,18 @@
15
15
  "type": "module",
16
16
  "source": "src/index.ts",
17
17
  "exports": {
18
- "types": "./dist/index.d.ts",
19
- "require": "./dist/index.cjs",
20
- "import": "./dist/index.module.js",
21
- "default": "./dist/index.module.js"
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "require": "./dist/index.cjs",
21
+ "import": "./dist/index.module.js",
22
+ "default": "./dist/index.module.js"
23
+ },
24
+ "./config": {
25
+ "types": "./dist/config.d.ts",
26
+ "require": "./dist/config.cjs",
27
+ "import": "./dist/config.module.js",
28
+ "default": "./dist/config.module.js"
29
+ }
22
30
  },
23
31
  "main": "./dist/index.cjs",
24
32
  "module": "./dist/index.module.js",
@@ -26,20 +34,20 @@
26
34
  "unpkg": "./dist/index.module.js",
27
35
  "scripts": {
28
36
  "test": "vitest run --passWithNoTests",
29
- "lint": "prettier src/**/*.ts --check",
30
- "build": "tsup --config ../../tsup.packages.config.ts --external async_hooks",
31
- "typecheck": "tsc --noEmit"
37
+ "build": "tsup --config tsup.config.ts",
38
+ "typecheck": "tsc --noEmit",
39
+ "biome:check": "biome check --write src"
32
40
  },
33
41
  "dependencies": {
34
42
  "@lowerdeck/testing-tools": "latest",
35
- "@slates/client": "1.0.0-rc.2",
36
- "@slates/profiles": "1.0.0-rc.2"
43
+ "@slates/client": "1.0.0-rc.24",
44
+ "@slates/profiles": "1.0.0-rc.22"
37
45
  },
38
46
  "devDependencies": {
39
- "@slates/provider": "1.0.0-rc.8",
47
+ "@slates/provider": "1.0.0-rc.24",
40
48
  "@slates/tsconfig": "1.0.0-rc.1",
41
49
  "typescript": "5.8.2",
42
50
  "vitest": "^3.1.2",
43
51
  "zod": "^4.2"
44
52
  }
45
- }
53
+ }
package/src/config.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { createVitestConfig } from '@lowerdeck/testing-tools';
2
+
3
+ export let createSlatesVitestConfig = (
4
+ config: Parameters<typeof createVitestConfig>[0] = {}
5
+ ) => createVitestConfig(config);
package/src/index.test.ts CHANGED
@@ -1,12 +1,14 @@
1
+ import { openSlatesCliStore } from '@slates/profiles';
1
2
  import {
3
+ createTextAttachment,
2
4
  Slate,
3
5
  SlateAuth,
4
6
  SlateConfig,
5
7
  SlateSpecification,
6
8
  SlateTool,
7
- SlateTrigger
9
+ SlateTrigger,
10
+ SlateTriggerGroup
8
11
  } from '@slates/provider';
9
- import { openSlatesCliStore } from '@slates/profiles';
10
12
  import { mkdtemp, rm, writeFile } from 'fs/promises';
11
13
  import { tmpdir } from 'os';
12
14
  import path from 'path';
@@ -17,11 +19,12 @@ import {
17
19
  expectSlateContract,
18
20
  expectSlateError,
19
21
  expectToolCall,
20
- handleSlateTriggerWebhook,
21
22
  loadSlatesRuntimeContext,
22
23
  mapSlateTriggerEvent,
23
- registerSlateTriggerWebhook,
24
- unregisterSlateTriggerWebhook
24
+ pollSlateTriggerGroupEvents,
25
+ processSlateTriggerGroupWebhook,
26
+ registerSlateTriggerGroupWebhook,
27
+ unregisterSlateTriggerGroupWebhook
25
28
  } from './index';
26
29
 
27
30
  (globalThis as typeof globalThis & { expect?: typeof expect }).expect = expect;
@@ -38,6 +41,7 @@ afterEach(async () => {
38
41
  delete process.env.SLATES_INTEGRATION;
39
42
  delete process.env.SLATES_PROFILE_ID;
40
43
  delete process.env.SLATES_STORE_PATH;
44
+ delete process.env.SLATES_STORE_ROOT_DIR;
41
45
  delete process.env.SLATES_TEST_CONTEXT_PATH;
42
46
  await Promise.all(tempDirs.splice(0).map(dir => rm(dir, { recursive: true, force: true })));
43
47
  });
@@ -121,65 +125,184 @@ let createDemoSlate = () => {
121
125
  })
122
126
  .build();
123
127
 
124
- let webhookEcho = SlateTrigger.create(spec, {
125
- key: 'webhook_echo',
126
- name: 'Webhook Echo'
128
+ let attachmentEcho = SlateTool.create(spec, {
129
+ key: 'attachment_echo',
130
+ name: 'Attachment Echo',
131
+ tags: {
132
+ readOnly: true
133
+ }
127
134
  })
128
135
  .input(
129
136
  z.object({
130
- value: z.string()
137
+ mimeType: z.string().optional()
131
138
  })
132
139
  )
133
140
  .output(
134
141
  z.object({
135
- echoed: z.string()
142
+ size: z.number()
143
+ })
144
+ )
145
+ .handleInvocation(async ctx => ({
146
+ output: {
147
+ size: 5
148
+ },
149
+ message: 'attached',
150
+ attachments: [createTextAttachment('hello', ctx.input.mimeType)]
151
+ }))
152
+ .build();
153
+
154
+ let downloadLink = SlateTool.create(spec, {
155
+ key: 'download_link',
156
+ name: 'Download Link',
157
+ tags: {
158
+ readOnly: true
159
+ }
160
+ })
161
+ .input(z.object({}))
162
+ .output(
163
+ z.object({
164
+ downloadUrl: z.string()
136
165
  })
137
166
  )
167
+ .handleInvocation(async () => ({
168
+ output: {
169
+ downloadUrl: 'https://example.com/files/demo.txt'
170
+ },
171
+ message: 'linked'
172
+ }))
173
+ .build();
174
+
175
+ let webhookGroup = SlateTriggerGroup.create(spec, {
176
+ key: 'webhook_group',
177
+ name: 'Webhook Group'
178
+ })
138
179
  .webhook({
139
- autoRegisterWebhook: async ctx => ({
140
- registrationDetails: {
141
- webhookBaseUrl: ctx.input.webhookBaseUrl,
142
- channelId: 'channel-1'
143
- },
144
- state: {
145
- registered: true
146
- }
147
- }),
148
- autoUnregisterWebhook: async ctx => {
149
- if (ctx.input.registrationDetails?.channelId !== 'channel-1') {
150
- throw new Error('Unexpected channel');
180
+ autoRegistration: {
181
+ webhookTargetList: async () => ({
182
+ targets: [
183
+ {
184
+ webhookTargetIdentifier: 'default',
185
+ name: 'Default',
186
+ metadata: {},
187
+ webhookTargetPayload: {},
188
+ targetOwnership: 'single_user'
189
+ }
190
+ ],
191
+ nextPageToken: null
192
+ }),
193
+ webhookRegister: async ctx => ({
194
+ webhookRegistrationIdentifier: 'reg-1',
195
+ webhookRegistrationPayload: {
196
+ webhookBaseUrl: ctx.input.webhookUrl,
197
+ channelId: 'channel-1'
198
+ }
199
+ }),
200
+ webhookUnregister: async ctx => {
201
+ if (ctx.input.webhookRegistrationPayload?.channelId !== 'channel-1') {
202
+ throw new Error('Unexpected channel');
203
+ }
151
204
  }
152
205
  },
153
- handleRequest: async ctx => {
154
- if (ctx.request.headers.get('x-demo-event') === 'ignore') {
155
- return { inputs: [] };
206
+ process: async ctx => {
207
+ if (ctx.input.request.headers.get('x-demo-event') === 'ignore') {
208
+ return { events: [] };
156
209
  }
157
210
 
158
211
  return {
159
- inputs: [
212
+ events: [
160
213
  {
161
- value: (await ctx.request.text()) || 'empty'
214
+ matchers: [],
215
+ payload: {
216
+ value: (await ctx.input.request.text()) || 'empty'
217
+ }
162
218
  }
163
219
  ],
164
- updatedState: {
165
- lastEvent: ctx.request.headers.get('x-demo-event') ?? 'unknown'
166
- }
220
+ response: new Response(
221
+ ctx.input.webhookRegistrationPayload?.channelId ?? 'missing',
222
+ {
223
+ status: 201,
224
+ headers: {
225
+ 'content-type': 'text/plain',
226
+ 'x-demo-response': 'accepted'
227
+ }
228
+ }
229
+ )
167
230
  };
168
- },
169
- handleEvent: async ctx => ({
170
- type: 'demo.webhook',
171
- id: `webhook-${ctx.input.value}`,
172
- output: {
173
- echoed: ctx.input.value
231
+ }
232
+ })
233
+ .routingMatchers(async () => [])
234
+ .build();
235
+
236
+ let webhookEcho = SlateTrigger.create(spec, {
237
+ key: 'webhook_echo',
238
+ name: 'Webhook Echo'
239
+ })
240
+ .input(
241
+ z.object({
242
+ value: z.string()
243
+ })
244
+ )
245
+ .output(
246
+ z.object({
247
+ echoed: z.string()
248
+ })
249
+ )
250
+ .triggerGroup(webhookGroup)
251
+ .matches(() => true)
252
+ .map(async ctx => ({
253
+ type: 'demo.webhook',
254
+ id: `webhook-${ctx.input.value}`,
255
+ output: {
256
+ echoed: ctx.input.value
257
+ }
258
+ }))
259
+ .build();
260
+
261
+ let pollGroup = SlateTriggerGroup.create(spec, {
262
+ key: 'poll_group',
263
+ name: 'Poll Group'
264
+ })
265
+ .polling({
266
+ pollEvents: async ctx => ({
267
+ events: ctx.input.state?.seen ? [] : [{ payload: { value: 'poll-value' } }],
268
+ updatedState: {
269
+ seen: true
174
270
  }
175
271
  })
176
272
  })
273
+ .routingMatchers(async () => [])
274
+ .build();
275
+
276
+ let pollEcho = SlateTrigger.create(spec, {
277
+ key: 'poll_echo',
278
+ name: 'Poll Echo'
279
+ })
280
+ .input(
281
+ z.object({
282
+ value: z.string()
283
+ })
284
+ )
285
+ .output(
286
+ z.object({
287
+ echoed: z.string()
288
+ })
289
+ )
290
+ .triggerGroup(pollGroup)
291
+ .matches(() => true)
292
+ .map(async ctx => ({
293
+ type: 'demo.poll',
294
+ id: `poll-${ctx.input.value}`,
295
+ output: {
296
+ echoed: ctx.input.value
297
+ }
298
+ }))
177
299
  .build();
178
300
 
179
301
  return Slate.create({
180
302
  spec,
181
- tools: [echo, fail],
182
- triggers: [webhookEcho]
303
+ tools: [echo, fail, attachmentEcho, downloadLink],
304
+ triggers: [webhookEcho, pollEcho],
305
+ triggerGroups: [webhookGroup, pollGroup]
183
306
  });
184
307
  };
185
308
 
@@ -224,6 +347,66 @@ describe('@slates/test', () => {
224
347
  expect(context.storePath).toBe(store.storePath);
225
348
  });
226
349
 
350
+ it('loads only the selected auth method from the runtime context', async () => {
351
+ let cwd = await createTempDir();
352
+ let store = await openSlatesCliStore({
353
+ cwd,
354
+ scope: {
355
+ key: 'integrations/demo',
356
+ name: 'demo'
357
+ }
358
+ });
359
+ let profile = store.upsertProfile({
360
+ name: 'Demo',
361
+ target: {
362
+ type: 'local',
363
+ entry: './demo-slate.mjs',
364
+ exportName: 'provider'
365
+ }
366
+ });
367
+ store.upsertAuth(profile.id, {
368
+ authMethodId: 'oauth',
369
+ authMethodName: 'Bot OAuth',
370
+ authType: 'auth.oauth',
371
+ input: {},
372
+ output: {
373
+ token: 'bot-token'
374
+ },
375
+ scopes: ['chat:write']
376
+ });
377
+ store.upsertAuth(profile.id, {
378
+ authMethodId: 'user_oauth',
379
+ authMethodName: 'User OAuth',
380
+ authType: 'auth.oauth',
381
+ input: {},
382
+ output: {
383
+ token: 'user-token'
384
+ },
385
+ scopes: ['search:read']
386
+ });
387
+ await store.save();
388
+
389
+ let runtimeContextPath = path.join(cwd, 'runtime.json');
390
+ await writeFile(
391
+ runtimeContextPath,
392
+ JSON.stringify({
393
+ integration: 'integrations/demo',
394
+ profileId: profile.id,
395
+ authMethodId: 'user_oauth',
396
+ storePath: store.storePath,
397
+ cliDir: store.dirPath
398
+ }),
399
+ 'utf-8'
400
+ );
401
+
402
+ process.env.SLATES_TEST_CONTEXT_PATH = runtimeContextPath;
403
+
404
+ let context = await loadSlatesRuntimeContext({ cwd });
405
+ expect(context.authMethodId).toBe('user_oauth');
406
+ expect(Object.keys(context.profile?.auth ?? {})).toEqual(['user_oauth']);
407
+ expect(context.profile?.auth.user_oauth?.output.token).toBe('user-token');
408
+ });
409
+
227
410
  it('creates local slate clients and asserts provider contracts', async () => {
228
411
  let client = createLocalSlateTestClient({
229
412
  slate: createDemoSlate(),
@@ -247,14 +430,21 @@ describe('@slates/test', () => {
247
430
  name: 'Demo Slate',
248
431
  description: 'A tiny test slate'
249
432
  },
250
- toolIds: ['echo', 'fail'],
251
- triggerIds: ['webhook_echo'],
433
+ toolIds: ['echo', 'fail', 'attachment_echo', 'download_link'],
434
+ triggerIds: ['webhook_echo', 'poll_echo'],
435
+ triggerGroupIds: ['webhook_group', 'poll_group'],
252
436
  authMethodIds: ['token_auth'],
253
437
  tools: [
254
438
  { id: 'echo', readOnly: false, destructive: false },
255
- { id: 'fail', readOnly: false, destructive: false }
439
+ { id: 'fail', readOnly: false, destructive: false },
440
+ { id: 'attachment_echo', readOnly: true, destructive: false },
441
+ { id: 'download_link', readOnly: true, destructive: false }
256
442
  ],
257
- triggers: [{ id: 'webhook_echo', invocationType: 'webhook' }]
443
+ triggers: [{ id: 'webhook_echo' }, { id: 'poll_echo' }],
444
+ triggerGroups: [
445
+ { id: 'webhook_group', invocationType: 'webhook' },
446
+ { id: 'poll_group', invocationType: 'polling' }
447
+ ]
258
448
  });
259
449
 
260
450
  expect(contract.configSchema.properties.prefix.type).toBe('string');
@@ -270,6 +460,30 @@ describe('@slates/test', () => {
270
460
  token: 'secret-token'
271
461
  }
272
462
  });
463
+
464
+ let attachmentResult = await client.invokeTool('attachment_echo', {
465
+ mimeType: 'text/plain'
466
+ });
467
+ expect(attachmentResult.attachments).toEqual([
468
+ {
469
+ mimeType: 'text/plain',
470
+ content: {
471
+ type: 'content',
472
+ encoding: 'utf-8',
473
+ content: 'hello'
474
+ }
475
+ }
476
+ ]);
477
+
478
+ let downloadResult = await client.invokeTool('download_link', {});
479
+ expect(downloadResult.attachments).toEqual([
480
+ {
481
+ content: {
482
+ type: 'url',
483
+ url: 'https://example.com/files/demo.txt'
484
+ }
485
+ }
486
+ ]);
273
487
  });
274
488
 
275
489
  it('wraps trigger webhook flows and error assertions', async () => {
@@ -288,44 +502,54 @@ describe('@slates/test', () => {
288
502
  }
289
503
  });
290
504
 
291
- let registration = await registerSlateTriggerWebhook({
505
+ let registration = await registerSlateTriggerGroupWebhook({
292
506
  client,
293
- triggerId: 'webhook_echo',
294
- webhookBaseUrl: 'https://example.com/hooks/google-calendar'
507
+ triggerGroupId: 'webhook_group',
508
+ webhookTargetIdentifier: 'default',
509
+ webhookTargetPayload: {},
510
+ webhookUrl: 'https://example.com/hooks/google-calendar'
295
511
  });
296
512
  expect(registration).toMatchObject({
297
- registrationDetails: {
513
+ webhookRegistrationIdentifier: 'reg-1',
514
+ webhookRegistrationPayload: {
298
515
  webhookBaseUrl: 'https://example.com/hooks/google-calendar',
299
516
  channelId: 'channel-1'
300
- },
301
- state: {
302
- registered: true
303
517
  }
304
518
  });
305
519
 
306
- let ignored = await handleSlateTriggerWebhook({
520
+ let ignored = await processSlateTriggerGroupWebhook({
307
521
  client,
308
- triggerId: 'webhook_echo',
522
+ triggerGroupId: 'webhook_group',
309
523
  url: 'https://example.com/hooks/google-calendar',
310
524
  headers: {
311
525
  'x-demo-event': 'ignore'
312
- }
526
+ },
527
+ webhookRegistrationPayload: registration.webhookRegistrationPayload
313
528
  });
314
- expect(ignored.inputs).toEqual([]);
529
+ expect(ignored.events).toEqual([]);
315
530
 
316
- let handled = await handleSlateTriggerWebhook({
531
+ let handled = await processSlateTriggerGroupWebhook({
317
532
  client,
318
- triggerId: 'webhook_echo',
533
+ triggerGroupId: 'webhook_group',
319
534
  url: 'https://example.com/hooks/google-calendar',
320
535
  headers: {
321
536
  'x-demo-event': 'created'
322
537
  },
323
- body: 'payload-value'
538
+ body: 'payload-value',
539
+ webhookRegistrationPayload: registration.webhookRegistrationPayload
324
540
  });
325
541
  expect(handled).toMatchObject({
326
- inputs: [{ value: 'payload-value' }],
327
- updatedState: {
328
- lastEvent: 'created'
542
+ events: [{ matchers: [], payload: { value: 'payload-value' } }],
543
+ response: {
544
+ status: 201,
545
+ headers: {
546
+ 'content-type': 'text/plain',
547
+ 'x-demo-response': 'accepted'
548
+ },
549
+ body: {
550
+ encoding: 'base64',
551
+ content: Buffer.from('channel-1').toString('base64')
552
+ }
329
553
  }
330
554
  });
331
555
 
@@ -342,11 +566,11 @@ describe('@slates/test', () => {
342
566
  });
343
567
  expect(mapped.id).toBe('webhook-payload-value');
344
568
 
345
- await unregisterSlateTriggerWebhook({
569
+ await unregisterSlateTriggerGroupWebhook({
346
570
  client,
347
- triggerId: 'webhook_echo',
348
- webhookBaseUrl: 'https://example.com/hooks/google-calendar',
349
- registrationDetails: registration.registrationDetails
571
+ triggerGroupId: 'webhook_group',
572
+ webhookRegistrationIdentifier: registration.webhookRegistrationIdentifier,
573
+ webhookRegistrationPayload: registration.webhookRegistrationPayload
350
574
  });
351
575
 
352
576
  await expectSlateError(
@@ -357,4 +581,53 @@ describe('@slates/test', () => {
357
581
  { code: 'internal.unexpected', kind: 'internal', status: 500 }
358
582
  );
359
583
  });
584
+
585
+ it('wraps trigger polling flows', async () => {
586
+ let client = createLocalSlateTestClient({
587
+ slate: createDemoSlate(),
588
+ state: {
589
+ config: {
590
+ prefix: 'Hi'
591
+ },
592
+ auth: {
593
+ authenticationMethodId: 'token_auth',
594
+ output: {
595
+ token: 'secret-token'
596
+ }
597
+ }
598
+ }
599
+ });
600
+
601
+ let initialPoll = await pollSlateTriggerGroupEvents({
602
+ client,
603
+ triggerGroupId: 'poll_group'
604
+ });
605
+ expect(initialPoll).toMatchObject({
606
+ events: [{ payload: { value: 'poll-value' } }],
607
+ updatedState: {
608
+ seen: true
609
+ }
610
+ });
611
+
612
+ let mapped = await mapSlateTriggerEvent({
613
+ client,
614
+ triggerId: 'poll_echo',
615
+ input: initialPoll.events[0]!.payload,
616
+ type: 'demo.poll',
617
+ output: {
618
+ echoed: 'poll-value'
619
+ }
620
+ });
621
+ expect(mapped.id).toBe('poll-poll-value');
622
+
623
+ let repeatedPoll = await pollSlateTriggerGroupEvents({
624
+ client,
625
+ triggerGroupId: 'poll_group',
626
+ state: initialPoll.updatedState
627
+ });
628
+ expect(repeatedPoll.events).toEqual([]);
629
+ expect(repeatedPoll.updatedState).toEqual({
630
+ seen: true
631
+ });
632
+ });
360
633
  });