@slates/test 1.0.0-rc.2
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 +240 -0
- package/dist/index.d.cts +518 -0
- package/dist/index.d.ts +518 -0
- package/dist/index.module.js +209 -0
- package/package.json +45 -0
- package/src/index.test.ts +360 -0
- package/src/index.ts +332 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { createVitestConfig } from "@lowerdeck/testing-tools";
|
|
3
|
+
import {
|
|
4
|
+
createLocalSlateTransport,
|
|
5
|
+
createSlatesClient,
|
|
6
|
+
SlateProtocolError
|
|
7
|
+
} from "@slates/client";
|
|
8
|
+
import {
|
|
9
|
+
createSlatesClientFromProfile,
|
|
10
|
+
openSlatesCliStore
|
|
11
|
+
} from "@slates/profiles";
|
|
12
|
+
import { readFile } from "fs/promises";
|
|
13
|
+
var getExpect = () => {
|
|
14
|
+
let maybeExpect = globalThis.expect;
|
|
15
|
+
if (!maybeExpect) {
|
|
16
|
+
throw new Error("Vitest expect is not available in the current runtime.");
|
|
17
|
+
}
|
|
18
|
+
return maybeExpect;
|
|
19
|
+
};
|
|
20
|
+
var loadSlatesRuntimeContext = async (opts = {}) => {
|
|
21
|
+
let runtimeContextPath = process.env.SLATES_TEST_CONTEXT_PATH;
|
|
22
|
+
if (runtimeContextPath) {
|
|
23
|
+
let raw = await readFile(runtimeContextPath, "utf-8");
|
|
24
|
+
let parsed = JSON.parse(raw);
|
|
25
|
+
let store2 = await openSlatesCliStore({ storePath: parsed.storePath });
|
|
26
|
+
let profile2 = store2.getProfile(opts.profile ?? parsed.profileId ?? null);
|
|
27
|
+
return {
|
|
28
|
+
integration: parsed.integration ?? store2.scope?.key ?? null,
|
|
29
|
+
profileId: profile2?.id ?? parsed.profileId ?? null,
|
|
30
|
+
profile: profile2,
|
|
31
|
+
storePath: parsed.storePath,
|
|
32
|
+
cliDir: parsed.cliDir
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
let store = process.env.SLATES_STORE_PATH ? await openSlatesCliStore({ storePath: process.env.SLATES_STORE_PATH }) : await openSlatesCliStore({ cwd: opts.cwd });
|
|
36
|
+
let profileId = opts.profile ?? process.env.SLATES_PROFILE_ID ?? null;
|
|
37
|
+
let profile = store.getProfile(profileId);
|
|
38
|
+
return {
|
|
39
|
+
integration: process.env.SLATES_INTEGRATION ?? store.scope?.key ?? null,
|
|
40
|
+
profileId: profile?.id ?? null,
|
|
41
|
+
profile,
|
|
42
|
+
storePath: store.storePath,
|
|
43
|
+
cliDir: store.dirPath
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
var loadSlatesProfile = async (opts = {}) => {
|
|
47
|
+
let context = await loadSlatesRuntimeContext(opts);
|
|
48
|
+
if (!context.profile) {
|
|
49
|
+
throw new Error("No Slates profile is available for the current test context.");
|
|
50
|
+
}
|
|
51
|
+
return context.profile;
|
|
52
|
+
};
|
|
53
|
+
var createSlatesTestClient = async (opts = {}) => {
|
|
54
|
+
let context = await loadSlatesRuntimeContext(opts);
|
|
55
|
+
if (!context.profile) {
|
|
56
|
+
throw new Error("No Slates profile is available for the current test context.");
|
|
57
|
+
}
|
|
58
|
+
let store = await openSlatesCliStore({ storePath: context.storePath });
|
|
59
|
+
return createSlatesClientFromProfile(context.profile, { cwd: opts.cwd, store });
|
|
60
|
+
};
|
|
61
|
+
var withSlateProfile = async (profileName, cb) => {
|
|
62
|
+
let profile = await loadSlatesProfile({ profile: profileName });
|
|
63
|
+
return cb({ profile });
|
|
64
|
+
};
|
|
65
|
+
var expectToolCall = async (d) => {
|
|
66
|
+
let expect = getExpect();
|
|
67
|
+
let client = d.client ?? await createSlatesTestClient({ profile: d.profile });
|
|
68
|
+
let result = await client.invokeTool(d.toolId, d.input);
|
|
69
|
+
if (d.output) {
|
|
70
|
+
expect(result.output).toMatchObject(d.output);
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
};
|
|
74
|
+
var createLocalSlateTestClient = (opts) => createSlatesClient({
|
|
75
|
+
transport: createLocalSlateTransport({ slate: opts.slate }),
|
|
76
|
+
state: opts.state,
|
|
77
|
+
participants: opts.participants
|
|
78
|
+
});
|
|
79
|
+
var getSlateContract = async (client) => {
|
|
80
|
+
let [provider, actions, authMethods, configSchema] = await Promise.all([
|
|
81
|
+
client.identify(),
|
|
82
|
+
client.listActions(),
|
|
83
|
+
client.listAuthMethods(),
|
|
84
|
+
client.getConfigSchema()
|
|
85
|
+
]);
|
|
86
|
+
return {
|
|
87
|
+
provider: provider.provider,
|
|
88
|
+
actions: actions.actions,
|
|
89
|
+
tools: actions.actions.filter((action) => action.type === "action.tool"),
|
|
90
|
+
triggers: actions.actions.filter((action) => action.type === "action.trigger"),
|
|
91
|
+
authMethods: authMethods.authenticationMethods,
|
|
92
|
+
configSchema: configSchema.schema
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
var expectActionMatches = (actual, expected) => {
|
|
96
|
+
let expect = getExpect();
|
|
97
|
+
expect(actual).toBeTruthy();
|
|
98
|
+
expect(actual?.id).toBe(expected.id);
|
|
99
|
+
if (expected.name !== void 0) {
|
|
100
|
+
expect(actual?.name).toBe(expected.name);
|
|
101
|
+
}
|
|
102
|
+
if (expected.description !== void 0) {
|
|
103
|
+
expect(actual?.description).toBe(expected.description);
|
|
104
|
+
}
|
|
105
|
+
if (expected.readOnly !== void 0) {
|
|
106
|
+
expect(actual?.tags?.readOnly ?? false).toBe(expected.readOnly);
|
|
107
|
+
}
|
|
108
|
+
if (expected.destructive !== void 0) {
|
|
109
|
+
expect(actual?.tags?.destructive ?? false).toBe(expected.destructive);
|
|
110
|
+
}
|
|
111
|
+
if (expected.invocationType !== void 0) {
|
|
112
|
+
expect(actual?.invocation?.type).toBe(
|
|
113
|
+
expected.invocationType
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var expectSlateContract = async (d) => {
|
|
118
|
+
let expect = getExpect();
|
|
119
|
+
let contract = await getSlateContract(d.client);
|
|
120
|
+
if (d.provider) {
|
|
121
|
+
expect(contract.provider.id).toBe(d.provider.id);
|
|
122
|
+
if (d.provider.name !== void 0) {
|
|
123
|
+
expect(contract.provider.name).toBe(d.provider.name);
|
|
124
|
+
}
|
|
125
|
+
if (d.provider.description !== void 0) {
|
|
126
|
+
expect(contract.provider.description).toBe(d.provider.description);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (d.toolIds) {
|
|
130
|
+
expect(contract.tools.map((action) => action.id)).toEqual(d.toolIds);
|
|
131
|
+
}
|
|
132
|
+
if (d.triggerIds) {
|
|
133
|
+
expect(contract.triggers.map((action) => action.id)).toEqual(d.triggerIds);
|
|
134
|
+
}
|
|
135
|
+
if (d.authMethodIds) {
|
|
136
|
+
expect(contract.authMethods.map((method) => method.id)).toEqual(d.authMethodIds);
|
|
137
|
+
}
|
|
138
|
+
for (let tool of d.tools ?? []) {
|
|
139
|
+
expectActionMatches(contract.tools.find((action) => action.id === tool.id), tool);
|
|
140
|
+
}
|
|
141
|
+
for (let trigger of d.triggers ?? []) {
|
|
142
|
+
expectActionMatches(contract.triggers.find((action) => action.id === trigger.id), trigger);
|
|
143
|
+
}
|
|
144
|
+
return contract;
|
|
145
|
+
};
|
|
146
|
+
var registerSlateTriggerWebhook = async (d) => d.client.registerTriggerWebhook(d.triggerId, d.webhookBaseUrl);
|
|
147
|
+
var handleSlateTriggerWebhook = async (d) => d.client.handleTriggerWebhook({
|
|
148
|
+
actionId: d.triggerId,
|
|
149
|
+
url: d.url,
|
|
150
|
+
method: d.method ?? "POST",
|
|
151
|
+
headers: d.headers,
|
|
152
|
+
body: d.body,
|
|
153
|
+
state: d.state
|
|
154
|
+
});
|
|
155
|
+
var unregisterSlateTriggerWebhook = async (d) => d.client.unregisterTriggerWebhook({
|
|
156
|
+
actionId: d.triggerId,
|
|
157
|
+
webhookBaseUrl: d.webhookBaseUrl,
|
|
158
|
+
registrationDetails: d.registrationDetails,
|
|
159
|
+
state: d.state
|
|
160
|
+
});
|
|
161
|
+
var mapSlateTriggerEvent = async (d) => {
|
|
162
|
+
let expect = getExpect();
|
|
163
|
+
let result = await d.client.mapTriggerEvent(d.triggerId, d.input);
|
|
164
|
+
if (d.type !== void 0) {
|
|
165
|
+
expect(result.type).toBe(d.type);
|
|
166
|
+
}
|
|
167
|
+
if (d.output) {
|
|
168
|
+
expect(result.output).toMatchObject(d.output);
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
};
|
|
172
|
+
var expectSlateError = async (run, expected) => {
|
|
173
|
+
let expect = getExpect();
|
|
174
|
+
try {
|
|
175
|
+
await (typeof run === "function" ? run() : run);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (typeof expected === "string" || expected instanceof RegExp) {
|
|
178
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
179
|
+
if (expected instanceof RegExp) {
|
|
180
|
+
expect(message).toMatch(expected);
|
|
181
|
+
} else {
|
|
182
|
+
expect(message).toContain(expected);
|
|
183
|
+
}
|
|
184
|
+
} else if (error instanceof SlateProtocolError) {
|
|
185
|
+
expect(error.data).toMatchObject(expected);
|
|
186
|
+
} else {
|
|
187
|
+
expect(error).toMatchObject(expected);
|
|
188
|
+
}
|
|
189
|
+
return error;
|
|
190
|
+
}
|
|
191
|
+
throw new Error("Expected the operation to fail, but it completed successfully.");
|
|
192
|
+
};
|
|
193
|
+
var createSlatesVitestConfig = (config = {}) => createVitestConfig(config);
|
|
194
|
+
export {
|
|
195
|
+
createLocalSlateTestClient,
|
|
196
|
+
createSlatesTestClient,
|
|
197
|
+
createSlatesVitestConfig,
|
|
198
|
+
expectSlateContract,
|
|
199
|
+
expectSlateError,
|
|
200
|
+
expectToolCall,
|
|
201
|
+
getSlateContract,
|
|
202
|
+
handleSlateTriggerWebhook,
|
|
203
|
+
loadSlatesProfile,
|
|
204
|
+
loadSlatesRuntimeContext,
|
|
205
|
+
mapSlateTriggerEvent,
|
|
206
|
+
registerSlateTriggerWebhook,
|
|
207
|
+
unregisterSlateTriggerWebhook,
|
|
208
|
+
withSlateProfile
|
|
209
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@slates/test",
|
|
3
|
+
"version": "1.0.0-rc.2",
|
|
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
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"require": "./dist/index.cjs",
|
|
20
|
+
"import": "./dist/index.module.js",
|
|
21
|
+
"default": "./dist/index.module.js"
|
|
22
|
+
},
|
|
23
|
+
"main": "./dist/index.cjs",
|
|
24
|
+
"module": "./dist/index.module.js",
|
|
25
|
+
"types": "dist/index.d.ts",
|
|
26
|
+
"unpkg": "./dist/index.module.js",
|
|
27
|
+
"scripts": {
|
|
28
|
+
"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"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@lowerdeck/testing-tools": "latest",
|
|
35
|
+
"@slates/client": "1.0.0-rc.2",
|
|
36
|
+
"@slates/profiles": "1.0.0-rc.2"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@slates/provider": "1.0.0-rc.8",
|
|
40
|
+
"@slates/tsconfig": "1.0.0-rc.1",
|
|
41
|
+
"typescript": "5.8.2",
|
|
42
|
+
"vitest": "^3.1.2",
|
|
43
|
+
"zod": "^4.2"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Slate,
|
|
3
|
+
SlateAuth,
|
|
4
|
+
SlateConfig,
|
|
5
|
+
SlateSpecification,
|
|
6
|
+
SlateTool,
|
|
7
|
+
SlateTrigger
|
|
8
|
+
} from '@slates/provider';
|
|
9
|
+
import { openSlatesCliStore } from '@slates/profiles';
|
|
10
|
+
import { mkdtemp, rm, writeFile } from 'fs/promises';
|
|
11
|
+
import { tmpdir } from 'os';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import {
|
|
16
|
+
createLocalSlateTestClient,
|
|
17
|
+
expectSlateContract,
|
|
18
|
+
expectSlateError,
|
|
19
|
+
expectToolCall,
|
|
20
|
+
handleSlateTriggerWebhook,
|
|
21
|
+
loadSlatesRuntimeContext,
|
|
22
|
+
mapSlateTriggerEvent,
|
|
23
|
+
registerSlateTriggerWebhook,
|
|
24
|
+
unregisterSlateTriggerWebhook
|
|
25
|
+
} from './index';
|
|
26
|
+
|
|
27
|
+
(globalThis as typeof globalThis & { expect?: typeof expect }).expect = expect;
|
|
28
|
+
|
|
29
|
+
let tempDirs: string[] = [];
|
|
30
|
+
|
|
31
|
+
let createTempDir = async () => {
|
|
32
|
+
let dir = await mkdtemp(path.join(tmpdir(), 'slates-test-'));
|
|
33
|
+
tempDirs.push(dir);
|
|
34
|
+
return dir;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
afterEach(async () => {
|
|
38
|
+
delete process.env.SLATES_INTEGRATION;
|
|
39
|
+
delete process.env.SLATES_PROFILE_ID;
|
|
40
|
+
delete process.env.SLATES_STORE_PATH;
|
|
41
|
+
delete process.env.SLATES_TEST_CONTEXT_PATH;
|
|
42
|
+
await Promise.all(tempDirs.splice(0).map(dir => rm(dir, { recursive: true, force: true })));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
let createDemoSlate = () => {
|
|
46
|
+
let config = SlateConfig.create(
|
|
47
|
+
z.object({
|
|
48
|
+
prefix: z.string()
|
|
49
|
+
})
|
|
50
|
+
).getDefaultConfig(() => ({
|
|
51
|
+
prefix: 'Hello'
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
let auth = SlateAuth.create<{ token: string }>()
|
|
55
|
+
.output(
|
|
56
|
+
z.object({
|
|
57
|
+
token: z.string()
|
|
58
|
+
})
|
|
59
|
+
)
|
|
60
|
+
.addTokenAuth({
|
|
61
|
+
type: 'auth.token',
|
|
62
|
+
key: 'token_auth',
|
|
63
|
+
name: 'Token Auth',
|
|
64
|
+
inputSchema: z.object({
|
|
65
|
+
token: z.string()
|
|
66
|
+
}),
|
|
67
|
+
getOutput: async ctx => ({
|
|
68
|
+
output: {
|
|
69
|
+
token: ctx.input.token
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
let spec = SlateSpecification.create({
|
|
75
|
+
key: 'demo-slate',
|
|
76
|
+
name: 'Demo Slate',
|
|
77
|
+
description: 'A tiny test slate',
|
|
78
|
+
config,
|
|
79
|
+
auth
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
let echo = SlateTool.create(spec, {
|
|
83
|
+
key: 'echo',
|
|
84
|
+
name: 'Echo',
|
|
85
|
+
tags: {
|
|
86
|
+
readOnly: false
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
.input(
|
|
90
|
+
z.object({
|
|
91
|
+
name: z.string()
|
|
92
|
+
})
|
|
93
|
+
)
|
|
94
|
+
.output(
|
|
95
|
+
z.object({
|
|
96
|
+
greeting: z.string(),
|
|
97
|
+
token: z.string()
|
|
98
|
+
})
|
|
99
|
+
)
|
|
100
|
+
.handleInvocation(async ctx => ({
|
|
101
|
+
output: {
|
|
102
|
+
greeting: `${ctx.config.prefix} ${ctx.input.name}`,
|
|
103
|
+
token: ctx.auth.token
|
|
104
|
+
},
|
|
105
|
+
message: 'done'
|
|
106
|
+
}))
|
|
107
|
+
.build();
|
|
108
|
+
|
|
109
|
+
let fail = SlateTool.create(spec, {
|
|
110
|
+
key: 'fail',
|
|
111
|
+
name: 'Fail'
|
|
112
|
+
})
|
|
113
|
+
.input(
|
|
114
|
+
z.object({
|
|
115
|
+
reason: z.string()
|
|
116
|
+
})
|
|
117
|
+
)
|
|
118
|
+
.output(z.object({}))
|
|
119
|
+
.handleInvocation(async ctx => {
|
|
120
|
+
throw new Error(ctx.input.reason);
|
|
121
|
+
})
|
|
122
|
+
.build();
|
|
123
|
+
|
|
124
|
+
let webhookEcho = SlateTrigger.create(spec, {
|
|
125
|
+
key: 'webhook_echo',
|
|
126
|
+
name: 'Webhook Echo'
|
|
127
|
+
})
|
|
128
|
+
.input(
|
|
129
|
+
z.object({
|
|
130
|
+
value: z.string()
|
|
131
|
+
})
|
|
132
|
+
)
|
|
133
|
+
.output(
|
|
134
|
+
z.object({
|
|
135
|
+
echoed: z.string()
|
|
136
|
+
})
|
|
137
|
+
)
|
|
138
|
+
.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');
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
handleRequest: async ctx => {
|
|
154
|
+
if (ctx.request.headers.get('x-demo-event') === 'ignore') {
|
|
155
|
+
return { inputs: [] };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
inputs: [
|
|
160
|
+
{
|
|
161
|
+
value: (await ctx.request.text()) || 'empty'
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
updatedState: {
|
|
165
|
+
lastEvent: ctx.request.headers.get('x-demo-event') ?? 'unknown'
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
handleEvent: async ctx => ({
|
|
170
|
+
type: 'demo.webhook',
|
|
171
|
+
id: `webhook-${ctx.input.value}`,
|
|
172
|
+
output: {
|
|
173
|
+
echoed: ctx.input.value
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
.build();
|
|
178
|
+
|
|
179
|
+
return Slate.create({
|
|
180
|
+
spec,
|
|
181
|
+
tools: [echo, fail],
|
|
182
|
+
triggers: [webhookEcho]
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
describe('@slates/test', () => {
|
|
187
|
+
it('loads runtime context from the CLI handoff file', async () => {
|
|
188
|
+
let cwd = await createTempDir();
|
|
189
|
+
let store = await openSlatesCliStore({
|
|
190
|
+
cwd,
|
|
191
|
+
scope: {
|
|
192
|
+
key: 'integrations/demo',
|
|
193
|
+
name: 'demo'
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
let profile = store.upsertProfile({
|
|
197
|
+
name: 'Demo',
|
|
198
|
+
target: {
|
|
199
|
+
type: 'local',
|
|
200
|
+
entry: './demo-slate.mjs',
|
|
201
|
+
exportName: 'provider'
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
await store.save();
|
|
205
|
+
|
|
206
|
+
let runtimeContextPath = path.join(cwd, 'runtime.json');
|
|
207
|
+
await writeFile(
|
|
208
|
+
runtimeContextPath,
|
|
209
|
+
JSON.stringify({
|
|
210
|
+
integration: 'integrations/demo',
|
|
211
|
+
profileId: profile.id,
|
|
212
|
+
storePath: store.storePath,
|
|
213
|
+
cliDir: store.dirPath
|
|
214
|
+
}),
|
|
215
|
+
'utf-8'
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
process.env.SLATES_TEST_CONTEXT_PATH = runtimeContextPath;
|
|
219
|
+
|
|
220
|
+
let context = await loadSlatesRuntimeContext({ cwd });
|
|
221
|
+
expect(context.integration).toBe('integrations/demo');
|
|
222
|
+
expect(context.profileId).toBe(profile.id);
|
|
223
|
+
expect(context.profile?.target.type).toBe('local');
|
|
224
|
+
expect(context.storePath).toBe(store.storePath);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('creates local slate clients and asserts provider contracts', async () => {
|
|
228
|
+
let client = createLocalSlateTestClient({
|
|
229
|
+
slate: createDemoSlate(),
|
|
230
|
+
state: {
|
|
231
|
+
config: {
|
|
232
|
+
prefix: 'Hi'
|
|
233
|
+
},
|
|
234
|
+
auth: {
|
|
235
|
+
authenticationMethodId: 'token_auth',
|
|
236
|
+
output: {
|
|
237
|
+
token: 'secret-token'
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
let contract = await expectSlateContract({
|
|
244
|
+
client,
|
|
245
|
+
provider: {
|
|
246
|
+
id: 'demo-slate',
|
|
247
|
+
name: 'Demo Slate',
|
|
248
|
+
description: 'A tiny test slate'
|
|
249
|
+
},
|
|
250
|
+
toolIds: ['echo', 'fail'],
|
|
251
|
+
triggerIds: ['webhook_echo'],
|
|
252
|
+
authMethodIds: ['token_auth'],
|
|
253
|
+
tools: [
|
|
254
|
+
{ id: 'echo', readOnly: false, destructive: false },
|
|
255
|
+
{ id: 'fail', readOnly: false, destructive: false }
|
|
256
|
+
],
|
|
257
|
+
triggers: [{ id: 'webhook_echo', invocationType: 'webhook' }]
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
expect(contract.configSchema.properties.prefix.type).toBe('string');
|
|
261
|
+
|
|
262
|
+
await expectToolCall({
|
|
263
|
+
client,
|
|
264
|
+
toolId: 'echo',
|
|
265
|
+
input: {
|
|
266
|
+
name: 'Tobias'
|
|
267
|
+
},
|
|
268
|
+
output: {
|
|
269
|
+
greeting: 'Hi Tobias',
|
|
270
|
+
token: 'secret-token'
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('wraps trigger webhook flows and error assertions', async () => {
|
|
276
|
+
let client = createLocalSlateTestClient({
|
|
277
|
+
slate: createDemoSlate(),
|
|
278
|
+
state: {
|
|
279
|
+
config: {
|
|
280
|
+
prefix: 'Hi'
|
|
281
|
+
},
|
|
282
|
+
auth: {
|
|
283
|
+
authenticationMethodId: 'token_auth',
|
|
284
|
+
output: {
|
|
285
|
+
token: 'secret-token'
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
let registration = await registerSlateTriggerWebhook({
|
|
292
|
+
client,
|
|
293
|
+
triggerId: 'webhook_echo',
|
|
294
|
+
webhookBaseUrl: 'https://example.com/hooks/google-calendar'
|
|
295
|
+
});
|
|
296
|
+
expect(registration).toMatchObject({
|
|
297
|
+
registrationDetails: {
|
|
298
|
+
webhookBaseUrl: 'https://example.com/hooks/google-calendar',
|
|
299
|
+
channelId: 'channel-1'
|
|
300
|
+
},
|
|
301
|
+
state: {
|
|
302
|
+
registered: true
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
let ignored = await handleSlateTriggerWebhook({
|
|
307
|
+
client,
|
|
308
|
+
triggerId: 'webhook_echo',
|
|
309
|
+
url: 'https://example.com/hooks/google-calendar',
|
|
310
|
+
headers: {
|
|
311
|
+
'x-demo-event': 'ignore'
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
expect(ignored.inputs).toEqual([]);
|
|
315
|
+
|
|
316
|
+
let handled = await handleSlateTriggerWebhook({
|
|
317
|
+
client,
|
|
318
|
+
triggerId: 'webhook_echo',
|
|
319
|
+
url: 'https://example.com/hooks/google-calendar',
|
|
320
|
+
headers: {
|
|
321
|
+
'x-demo-event': 'created'
|
|
322
|
+
},
|
|
323
|
+
body: 'payload-value'
|
|
324
|
+
});
|
|
325
|
+
expect(handled).toMatchObject({
|
|
326
|
+
inputs: [{ value: 'payload-value' }],
|
|
327
|
+
updatedState: {
|
|
328
|
+
lastEvent: 'created'
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
let mapped = await mapSlateTriggerEvent({
|
|
333
|
+
client,
|
|
334
|
+
triggerId: 'webhook_echo',
|
|
335
|
+
input: {
|
|
336
|
+
value: 'payload-value'
|
|
337
|
+
},
|
|
338
|
+
type: 'demo.webhook',
|
|
339
|
+
output: {
|
|
340
|
+
echoed: 'payload-value'
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
expect(mapped.id).toBe('webhook-payload-value');
|
|
344
|
+
|
|
345
|
+
await unregisterSlateTriggerWebhook({
|
|
346
|
+
client,
|
|
347
|
+
triggerId: 'webhook_echo',
|
|
348
|
+
webhookBaseUrl: 'https://example.com/hooks/google-calendar',
|
|
349
|
+
registrationDetails: registration.registrationDetails
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
await expectSlateError(
|
|
353
|
+
() =>
|
|
354
|
+
client.invokeTool('fail', {
|
|
355
|
+
reason: 'intentional failure'
|
|
356
|
+
}),
|
|
357
|
+
{ code: 'internal.unexpected', kind: 'internal', status: 500 }
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
});
|