@slates/test 1.0.0-rc.10

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 ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@slates/test",
3
+ "version": "1.0.0-rc.10",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "files": [
8
+ "src/**",
9
+ "dist/**",
10
+ "README.md",
11
+ "package.json"
12
+ ],
13
+ "author": "Tobias Herber",
14
+ "license": "FSL 1.1",
15
+ "type": "module",
16
+ "source": "src/index.ts",
17
+ "exports": {
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
+ }
30
+ },
31
+ "main": "./dist/index.cjs",
32
+ "module": "./dist/index.module.js",
33
+ "types": "dist/index.d.ts",
34
+ "unpkg": "./dist/index.module.js",
35
+ "scripts": {
36
+ "test": "vitest run --passWithNoTests",
37
+ "build": "tsup --config tsup.config.ts",
38
+ "typecheck": "tsc --noEmit",
39
+ "biome:check": "biome check --write src"
40
+ },
41
+ "dependencies": {
42
+ "@lowerdeck/testing-tools": "latest",
43
+ "@slates/client": "1.0.0-rc.13",
44
+ "@slates/profiles": "1.0.0-rc.11"
45
+ },
46
+ "devDependencies": {
47
+ "@slates/provider": "1.0.0-rc.16",
48
+ "@slates/tsconfig": "1.0.0-rc.1",
49
+ "typescript": "5.8.2",
50
+ "vitest": "^3.1.2",
51
+ "zod": "^4.2"
52
+ }
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);
@@ -0,0 +1,579 @@
1
+ import { openSlatesCliStore } from '@slates/profiles';
2
+ import {
3
+ createTextAttachment,
4
+ Slate,
5
+ SlateAuth,
6
+ SlateConfig,
7
+ SlateSpecification,
8
+ SlateTool,
9
+ SlateTrigger
10
+ } from '@slates/provider';
11
+ import { mkdtemp, rm, writeFile } from 'fs/promises';
12
+ import { tmpdir } from 'os';
13
+ import path from 'path';
14
+ import { afterEach, describe, expect, it } from 'vitest';
15
+ import { z } from 'zod';
16
+ import {
17
+ createLocalSlateTestClient,
18
+ expectSlateContract,
19
+ expectSlateError,
20
+ expectToolCall,
21
+ handleSlateTriggerWebhook,
22
+ loadSlatesRuntimeContext,
23
+ mapSlateTriggerEvent,
24
+ pollSlateTriggerEvents,
25
+ registerSlateTriggerWebhook,
26
+ unregisterSlateTriggerWebhook
27
+ } from './index';
28
+
29
+ (globalThis as typeof globalThis & { expect?: typeof expect }).expect = expect;
30
+
31
+ let tempDirs: string[] = [];
32
+
33
+ let createTempDir = async () => {
34
+ let dir = await mkdtemp(path.join(tmpdir(), 'slates-test-'));
35
+ tempDirs.push(dir);
36
+ return dir;
37
+ };
38
+
39
+ afterEach(async () => {
40
+ delete process.env.SLATES_INTEGRATION;
41
+ delete process.env.SLATES_PROFILE_ID;
42
+ delete process.env.SLATES_STORE_PATH;
43
+ delete process.env.SLATES_STORE_ROOT_DIR;
44
+ delete process.env.SLATES_TEST_CONTEXT_PATH;
45
+ await Promise.all(tempDirs.splice(0).map(dir => rm(dir, { recursive: true, force: true })));
46
+ });
47
+
48
+ let createDemoSlate = () => {
49
+ let config = SlateConfig.create(
50
+ z.object({
51
+ prefix: z.string()
52
+ })
53
+ ).getDefaultConfig(() => ({
54
+ prefix: 'Hello'
55
+ }));
56
+
57
+ let auth = SlateAuth.create<{ token: string }>()
58
+ .output(
59
+ z.object({
60
+ token: z.string()
61
+ })
62
+ )
63
+ .addTokenAuth({
64
+ type: 'auth.token',
65
+ key: 'token_auth',
66
+ name: 'Token Auth',
67
+ inputSchema: z.object({
68
+ token: z.string()
69
+ }),
70
+ getOutput: async ctx => ({
71
+ output: {
72
+ token: ctx.input.token
73
+ }
74
+ })
75
+ });
76
+
77
+ let spec = SlateSpecification.create({
78
+ key: 'demo-slate',
79
+ name: 'Demo Slate',
80
+ description: 'A tiny test slate',
81
+ config,
82
+ auth
83
+ });
84
+
85
+ let echo = SlateTool.create(spec, {
86
+ key: 'echo',
87
+ name: 'Echo',
88
+ tags: {
89
+ readOnly: false
90
+ }
91
+ })
92
+ .input(
93
+ z.object({
94
+ name: z.string()
95
+ })
96
+ )
97
+ .output(
98
+ z.object({
99
+ greeting: z.string(),
100
+ token: z.string()
101
+ })
102
+ )
103
+ .handleInvocation(async ctx => ({
104
+ output: {
105
+ greeting: `${ctx.config.prefix} ${ctx.input.name}`,
106
+ token: ctx.auth.token
107
+ },
108
+ message: 'done'
109
+ }))
110
+ .build();
111
+
112
+ let fail = SlateTool.create(spec, {
113
+ key: 'fail',
114
+ name: 'Fail'
115
+ })
116
+ .input(
117
+ z.object({
118
+ reason: z.string()
119
+ })
120
+ )
121
+ .output(z.object({}))
122
+ .handleInvocation(async ctx => {
123
+ throw new Error(ctx.input.reason);
124
+ })
125
+ .build();
126
+
127
+ let attachmentEcho = SlateTool.create(spec, {
128
+ key: 'attachment_echo',
129
+ name: 'Attachment Echo',
130
+ tags: {
131
+ readOnly: true
132
+ }
133
+ })
134
+ .input(
135
+ z.object({
136
+ mimeType: z.string().optional()
137
+ })
138
+ )
139
+ .output(
140
+ z.object({
141
+ size: z.number()
142
+ })
143
+ )
144
+ .handleInvocation(async ctx => ({
145
+ output: {
146
+ size: 5
147
+ },
148
+ message: 'attached',
149
+ attachments: [createTextAttachment('hello', ctx.input.mimeType)]
150
+ }))
151
+ .build();
152
+
153
+ let downloadLink = SlateTool.create(spec, {
154
+ key: 'download_link',
155
+ name: 'Download Link',
156
+ tags: {
157
+ readOnly: true
158
+ }
159
+ })
160
+ .input(z.object({}))
161
+ .output(
162
+ z.object({
163
+ downloadUrl: z.string()
164
+ })
165
+ )
166
+ .handleInvocation(async () => ({
167
+ output: {
168
+ downloadUrl: 'https://example.com/files/demo.txt'
169
+ },
170
+ message: 'linked'
171
+ }))
172
+ .build();
173
+
174
+ let webhookEcho = SlateTrigger.create(spec, {
175
+ key: 'webhook_echo',
176
+ name: 'Webhook Echo'
177
+ })
178
+ .input(
179
+ z.object({
180
+ value: z.string()
181
+ })
182
+ )
183
+ .output(
184
+ z.object({
185
+ echoed: z.string()
186
+ })
187
+ )
188
+ .webhook({
189
+ autoRegisterWebhook: async ctx => ({
190
+ registrationDetails: {
191
+ webhookBaseUrl: ctx.input.webhookBaseUrl,
192
+ channelId: 'channel-1'
193
+ },
194
+ state: {
195
+ registered: true
196
+ }
197
+ }),
198
+ autoUnregisterWebhook: async ctx => {
199
+ if (ctx.input.registrationDetails?.channelId !== 'channel-1') {
200
+ throw new Error('Unexpected channel');
201
+ }
202
+ },
203
+ handleRequest: async ctx => {
204
+ if (ctx.request.headers.get('x-demo-event') === 'ignore') {
205
+ return { inputs: [] };
206
+ }
207
+
208
+ return {
209
+ inputs: [
210
+ {
211
+ value: (await ctx.request.text()) || 'empty'
212
+ }
213
+ ],
214
+ updatedState: {
215
+ lastEvent: ctx.request.headers.get('x-demo-event') ?? 'unknown'
216
+ }
217
+ };
218
+ },
219
+ handleEvent: async ctx => ({
220
+ type: 'demo.webhook',
221
+ id: `webhook-${ctx.input.value}`,
222
+ output: {
223
+ echoed: ctx.input.value
224
+ }
225
+ })
226
+ })
227
+ .build();
228
+
229
+ let pollEcho = SlateTrigger.create(spec, {
230
+ key: 'poll_echo',
231
+ name: 'Poll Echo'
232
+ })
233
+ .input(
234
+ z.object({
235
+ value: z.string()
236
+ })
237
+ )
238
+ .output(
239
+ z.object({
240
+ echoed: z.string()
241
+ })
242
+ )
243
+ .polling({
244
+ pollEvents: async ctx => ({
245
+ inputs: ctx.input.state?.seen ? [] : [{ value: 'poll-value' }],
246
+ updatedState: {
247
+ seen: true
248
+ }
249
+ }),
250
+ handleEvent: async ctx => ({
251
+ type: 'demo.poll',
252
+ id: `poll-${ctx.input.value}`,
253
+ output: {
254
+ echoed: ctx.input.value
255
+ }
256
+ })
257
+ })
258
+ .build();
259
+
260
+ return Slate.create({
261
+ spec,
262
+ tools: [echo, fail, attachmentEcho, downloadLink],
263
+ triggers: [webhookEcho, pollEcho]
264
+ });
265
+ };
266
+
267
+ describe('@slates/test', () => {
268
+ it('loads runtime context from the CLI handoff file', async () => {
269
+ let cwd = await createTempDir();
270
+ let store = await openSlatesCliStore({
271
+ cwd,
272
+ scope: {
273
+ key: 'integrations/demo',
274
+ name: 'demo'
275
+ }
276
+ });
277
+ let profile = store.upsertProfile({
278
+ name: 'Demo',
279
+ target: {
280
+ type: 'local',
281
+ entry: './demo-slate.mjs',
282
+ exportName: 'provider'
283
+ }
284
+ });
285
+ await store.save();
286
+
287
+ let runtimeContextPath = path.join(cwd, 'runtime.json');
288
+ await writeFile(
289
+ runtimeContextPath,
290
+ JSON.stringify({
291
+ integration: 'integrations/demo',
292
+ profileId: profile.id,
293
+ storePath: store.storePath,
294
+ cliDir: store.dirPath
295
+ }),
296
+ 'utf-8'
297
+ );
298
+
299
+ process.env.SLATES_TEST_CONTEXT_PATH = runtimeContextPath;
300
+
301
+ let context = await loadSlatesRuntimeContext({ cwd });
302
+ expect(context.integration).toBe('integrations/demo');
303
+ expect(context.profileId).toBe(profile.id);
304
+ expect(context.profile?.target.type).toBe('local');
305
+ expect(context.storePath).toBe(store.storePath);
306
+ });
307
+
308
+ it('loads only the selected auth method from the runtime context', async () => {
309
+ let cwd = await createTempDir();
310
+ let store = await openSlatesCliStore({
311
+ cwd,
312
+ scope: {
313
+ key: 'integrations/demo',
314
+ name: 'demo'
315
+ }
316
+ });
317
+ let profile = store.upsertProfile({
318
+ name: 'Demo',
319
+ target: {
320
+ type: 'local',
321
+ entry: './demo-slate.mjs',
322
+ exportName: 'provider'
323
+ }
324
+ });
325
+ store.upsertAuth(profile.id, {
326
+ authMethodId: 'oauth',
327
+ authMethodName: 'Bot OAuth',
328
+ authType: 'auth.oauth',
329
+ input: {},
330
+ output: {
331
+ token: 'bot-token'
332
+ },
333
+ scopes: ['chat:write']
334
+ });
335
+ store.upsertAuth(profile.id, {
336
+ authMethodId: 'user_oauth',
337
+ authMethodName: 'User OAuth',
338
+ authType: 'auth.oauth',
339
+ input: {},
340
+ output: {
341
+ token: 'user-token'
342
+ },
343
+ scopes: ['search:read']
344
+ });
345
+ await store.save();
346
+
347
+ let runtimeContextPath = path.join(cwd, 'runtime.json');
348
+ await writeFile(
349
+ runtimeContextPath,
350
+ JSON.stringify({
351
+ integration: 'integrations/demo',
352
+ profileId: profile.id,
353
+ authMethodId: 'user_oauth',
354
+ storePath: store.storePath,
355
+ cliDir: store.dirPath
356
+ }),
357
+ 'utf-8'
358
+ );
359
+
360
+ process.env.SLATES_TEST_CONTEXT_PATH = runtimeContextPath;
361
+
362
+ let context = await loadSlatesRuntimeContext({ cwd });
363
+ expect(context.authMethodId).toBe('user_oauth');
364
+ expect(Object.keys(context.profile?.auth ?? {})).toEqual(['user_oauth']);
365
+ expect(context.profile?.auth.user_oauth?.output.token).toBe('user-token');
366
+ });
367
+
368
+ it('creates local slate clients and asserts provider contracts', async () => {
369
+ let client = createLocalSlateTestClient({
370
+ slate: createDemoSlate(),
371
+ state: {
372
+ config: {
373
+ prefix: 'Hi'
374
+ },
375
+ auth: {
376
+ authenticationMethodId: 'token_auth',
377
+ output: {
378
+ token: 'secret-token'
379
+ }
380
+ }
381
+ }
382
+ });
383
+
384
+ let contract = await expectSlateContract({
385
+ client,
386
+ provider: {
387
+ id: 'demo-slate',
388
+ name: 'Demo Slate',
389
+ description: 'A tiny test slate'
390
+ },
391
+ toolIds: ['echo', 'fail', 'attachment_echo', 'download_link'],
392
+ triggerIds: ['webhook_echo', 'poll_echo'],
393
+ authMethodIds: ['token_auth'],
394
+ tools: [
395
+ { id: 'echo', readOnly: false, destructive: false },
396
+ { id: 'fail', readOnly: false, destructive: false },
397
+ { id: 'attachment_echo', readOnly: true, destructive: false },
398
+ { id: 'download_link', readOnly: true, destructive: false }
399
+ ],
400
+ triggers: [
401
+ { id: 'webhook_echo', invocationType: 'webhook' },
402
+ { id: 'poll_echo', invocationType: 'polling' }
403
+ ]
404
+ });
405
+
406
+ expect(contract.configSchema.properties.prefix.type).toBe('string');
407
+
408
+ await expectToolCall({
409
+ client,
410
+ toolId: 'echo',
411
+ input: {
412
+ name: 'Tobias'
413
+ },
414
+ output: {
415
+ greeting: 'Hi Tobias',
416
+ token: 'secret-token'
417
+ }
418
+ });
419
+
420
+ let attachmentResult = await client.invokeTool('attachment_echo', {
421
+ mimeType: 'text/plain'
422
+ });
423
+ expect(attachmentResult.attachments).toEqual([
424
+ {
425
+ mimeType: 'text/plain',
426
+ content: {
427
+ type: 'content',
428
+ encoding: 'utf-8',
429
+ content: 'hello'
430
+ }
431
+ }
432
+ ]);
433
+
434
+ let downloadResult = await client.invokeTool('download_link', {});
435
+ expect(downloadResult.attachments).toEqual([
436
+ {
437
+ content: {
438
+ type: 'url',
439
+ url: 'https://example.com/files/demo.txt'
440
+ }
441
+ }
442
+ ]);
443
+ });
444
+
445
+ it('wraps trigger webhook flows and error assertions', async () => {
446
+ let client = createLocalSlateTestClient({
447
+ slate: createDemoSlate(),
448
+ state: {
449
+ config: {
450
+ prefix: 'Hi'
451
+ },
452
+ auth: {
453
+ authenticationMethodId: 'token_auth',
454
+ output: {
455
+ token: 'secret-token'
456
+ }
457
+ }
458
+ }
459
+ });
460
+
461
+ let registration = await registerSlateTriggerWebhook({
462
+ client,
463
+ triggerId: 'webhook_echo',
464
+ webhookBaseUrl: 'https://example.com/hooks/google-calendar'
465
+ });
466
+ expect(registration).toMatchObject({
467
+ registrationDetails: {
468
+ webhookBaseUrl: 'https://example.com/hooks/google-calendar',
469
+ channelId: 'channel-1'
470
+ },
471
+ state: {
472
+ registered: true
473
+ }
474
+ });
475
+
476
+ let ignored = await handleSlateTriggerWebhook({
477
+ client,
478
+ triggerId: 'webhook_echo',
479
+ url: 'https://example.com/hooks/google-calendar',
480
+ headers: {
481
+ 'x-demo-event': 'ignore'
482
+ }
483
+ });
484
+ expect(ignored.inputs).toEqual([]);
485
+
486
+ let handled = await handleSlateTriggerWebhook({
487
+ client,
488
+ triggerId: 'webhook_echo',
489
+ url: 'https://example.com/hooks/google-calendar',
490
+ headers: {
491
+ 'x-demo-event': 'created'
492
+ },
493
+ body: 'payload-value'
494
+ });
495
+ expect(handled).toMatchObject({
496
+ inputs: [{ value: 'payload-value' }],
497
+ updatedState: {
498
+ lastEvent: 'created'
499
+ }
500
+ });
501
+
502
+ let mapped = await mapSlateTriggerEvent({
503
+ client,
504
+ triggerId: 'webhook_echo',
505
+ input: {
506
+ value: 'payload-value'
507
+ },
508
+ type: 'demo.webhook',
509
+ output: {
510
+ echoed: 'payload-value'
511
+ }
512
+ });
513
+ expect(mapped.id).toBe('webhook-payload-value');
514
+
515
+ await unregisterSlateTriggerWebhook({
516
+ client,
517
+ triggerId: 'webhook_echo',
518
+ webhookBaseUrl: 'https://example.com/hooks/google-calendar',
519
+ registrationDetails: registration.registrationDetails
520
+ });
521
+
522
+ await expectSlateError(
523
+ () =>
524
+ client.invokeTool('fail', {
525
+ reason: 'intentional failure'
526
+ }),
527
+ { code: 'internal.unexpected', kind: 'internal', status: 500 }
528
+ );
529
+ });
530
+
531
+ it('wraps trigger polling flows', async () => {
532
+ let client = createLocalSlateTestClient({
533
+ slate: createDemoSlate(),
534
+ state: {
535
+ config: {
536
+ prefix: 'Hi'
537
+ },
538
+ auth: {
539
+ authenticationMethodId: 'token_auth',
540
+ output: {
541
+ token: 'secret-token'
542
+ }
543
+ }
544
+ }
545
+ });
546
+
547
+ let initialPoll = await pollSlateTriggerEvents({
548
+ client,
549
+ triggerId: 'poll_echo'
550
+ });
551
+ expect(initialPoll).toMatchObject({
552
+ inputs: [{ value: 'poll-value' }],
553
+ updatedState: {
554
+ seen: true
555
+ }
556
+ });
557
+
558
+ let mapped = await mapSlateTriggerEvent({
559
+ client,
560
+ triggerId: 'poll_echo',
561
+ input: initialPoll.inputs[0]!,
562
+ type: 'demo.poll',
563
+ output: {
564
+ echoed: 'poll-value'
565
+ }
566
+ });
567
+ expect(mapped.id).toBe('poll-poll-value');
568
+
569
+ let repeatedPoll = await pollSlateTriggerEvents({
570
+ client,
571
+ triggerId: 'poll_echo',
572
+ state: initialPoll.updatedState
573
+ });
574
+ expect(repeatedPoll.inputs).toEqual([]);
575
+ expect(repeatedPoll.updatedState).toEqual({
576
+ seen: true
577
+ });
578
+ });
579
+ });
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './runtime';
2
+ export * from './schema';