@enactprotocol/shared 1.2.13 → 2.0.1
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/README.md +44 -0
- package/dist/config.d.ts +164 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +386 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts +15 -5
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +24 -8
- package/dist/constants.js.map +1 -0
- package/dist/execution/command.d.ts +102 -0
- package/dist/execution/command.d.ts.map +1 -0
- package/dist/execution/command.js +262 -0
- package/dist/execution/command.js.map +1 -0
- package/dist/execution/index.d.ts +12 -0
- package/dist/execution/index.d.ts.map +1 -0
- package/dist/execution/index.js +17 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/runtime.d.ts +82 -0
- package/dist/execution/runtime.d.ts.map +1 -0
- package/dist/execution/runtime.js +273 -0
- package/dist/execution/runtime.js.map +1 -0
- package/dist/execution/types.d.ts +306 -0
- package/dist/execution/types.d.ts.map +1 -0
- package/dist/execution/types.js +14 -0
- package/dist/execution/types.js.map +1 -0
- package/dist/execution/validation.d.ts +43 -0
- package/dist/execution/validation.d.ts.map +1 -0
- package/dist/execution/validation.js +430 -0
- package/dist/execution/validation.js.map +1 -0
- package/dist/index.d.ts +21 -21
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -25
- package/dist/index.js.map +1 -0
- package/dist/manifest/index.d.ts +7 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +10 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/manifest/loader.d.ts +76 -0
- package/dist/manifest/loader.d.ts.map +1 -0
- package/dist/manifest/loader.js +146 -0
- package/dist/manifest/loader.js.map +1 -0
- package/dist/manifest/parser.d.ts +64 -0
- package/dist/manifest/parser.d.ts.map +1 -0
- package/dist/manifest/parser.js +135 -0
- package/dist/manifest/parser.js.map +1 -0
- package/dist/manifest/validator.d.ts +95 -0
- package/dist/manifest/validator.d.ts.map +1 -0
- package/dist/manifest/validator.js +258 -0
- package/dist/manifest/validator.js.map +1 -0
- package/dist/paths.d.ts +57 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +93 -0
- package/dist/paths.js.map +1 -0
- package/dist/registry.d.ts +73 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +147 -0
- package/dist/registry.js.map +1 -0
- package/dist/resolver.d.ts +89 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +282 -0
- package/dist/resolver.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/manifest.d.ts +201 -0
- package/dist/types/manifest.d.ts.map +1 -0
- package/dist/types/manifest.js +13 -0
- package/dist/types/manifest.js.map +1 -0
- package/dist/types.d.ts +5 -132
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -3
- package/dist/types.js.map +1 -0
- package/dist/utils/fs.d.ts +105 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +233 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/logger.d.ts +102 -25
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +214 -57
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/version.d.ts +60 -2
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +255 -31
- package/dist/utils/version.js.map +1 -0
- package/package.json +16 -58
- package/src/config.ts +510 -0
- package/src/constants.ts +36 -0
- package/src/execution/command.ts +314 -0
- package/src/execution/index.ts +73 -0
- package/src/execution/runtime.ts +308 -0
- package/src/execution/types.ts +379 -0
- package/src/execution/validation.ts +508 -0
- package/src/index.ts +238 -30
- package/src/manifest/index.ts +36 -0
- package/src/manifest/loader.ts +187 -0
- package/src/manifest/parser.ts +173 -0
- package/src/manifest/validator.ts +309 -0
- package/src/paths.ts +108 -0
- package/src/registry.ts +219 -0
- package/src/resolver.ts +345 -0
- package/src/types/index.ts +30 -0
- package/src/types/manifest.ts +255 -0
- package/src/types.ts +5 -188
- package/src/utils/fs.ts +281 -0
- package/src/utils/logger.ts +270 -59
- package/src/utils/version.ts +304 -36
- package/tests/config.test.ts +515 -0
- package/tests/execution/command.test.ts +317 -0
- package/tests/execution/validation.test.ts +384 -0
- package/tests/fixtures/invalid-tool.yaml +4 -0
- package/tests/fixtures/valid-tool.md +62 -0
- package/tests/fixtures/valid-tool.yaml +40 -0
- package/tests/index.test.ts +8 -0
- package/tests/manifest/loader.test.ts +291 -0
- package/tests/manifest/parser.test.ts +345 -0
- package/tests/manifest/validator.test.ts +394 -0
- package/tests/manifest-types.test.ts +358 -0
- package/tests/paths.test.ts +153 -0
- package/tests/registry.test.ts +231 -0
- package/tests/resolver.test.ts +272 -0
- package/tests/utils/fs.test.ts +388 -0
- package/tests/utils/logger.test.ts +480 -0
- package/tests/utils/version.test.ts +390 -0
- package/tsconfig.json +12 -0
- package/dist/LocalToolResolver.d.ts +0 -84
- package/dist/LocalToolResolver.js +0 -353
- package/dist/api/enact-api.d.ts +0 -130
- package/dist/api/enact-api.js +0 -428
- package/dist/api/index.d.ts +0 -2
- package/dist/api/index.js +0 -2
- package/dist/api/types.d.ts +0 -103
- package/dist/api/types.js +0 -1
- package/dist/core/DaggerExecutionProvider.d.ts +0 -169
- package/dist/core/DaggerExecutionProvider.js +0 -1029
- package/dist/core/DirectExecutionProvider.d.ts +0 -23
- package/dist/core/DirectExecutionProvider.js +0 -406
- package/dist/core/EnactCore.d.ts +0 -162
- package/dist/core/EnactCore.js +0 -597
- package/dist/core/NativeExecutionProvider.d.ts +0 -9
- package/dist/core/NativeExecutionProvider.js +0 -16
- package/dist/core/index.d.ts +0 -3
- package/dist/core/index.js +0 -3
- package/dist/exec/index.d.ts +0 -3
- package/dist/exec/index.js +0 -3
- package/dist/exec/logger.d.ts +0 -11
- package/dist/exec/logger.js +0 -57
- package/dist/exec/validate.d.ts +0 -5
- package/dist/exec/validate.js +0 -167
- package/dist/lib/enact-direct.d.ts +0 -150
- package/dist/lib/enact-direct.js +0 -159
- package/dist/lib/index.d.ts +0 -1
- package/dist/lib/index.js +0 -1
- package/dist/security/index.d.ts +0 -3
- package/dist/security/index.js +0 -3
- package/dist/security/security.d.ts +0 -23
- package/dist/security/security.js +0 -137
- package/dist/security/sign.d.ts +0 -103
- package/dist/security/sign.js +0 -666
- package/dist/security/verification-enforcer.d.ts +0 -53
- package/dist/security/verification-enforcer.js +0 -204
- package/dist/services/McpCoreService.d.ts +0 -98
- package/dist/services/McpCoreService.js +0 -124
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.js +0 -1
- package/dist/utils/config.d.ts +0 -111
- package/dist/utils/config.js +0 -342
- package/dist/utils/env-loader.d.ts +0 -54
- package/dist/utils/env-loader.js +0 -270
- package/dist/utils/help.d.ts +0 -36
- package/dist/utils/help.js +0 -248
- package/dist/utils/index.d.ts +0 -7
- package/dist/utils/index.js +0 -7
- package/dist/utils/silent-monitor.d.ts +0 -67
- package/dist/utils/silent-monitor.js +0 -242
- package/dist/utils/timeout.d.ts +0 -5
- package/dist/utils/timeout.js +0 -23
- package/dist/web/env-manager-server.d.ts +0 -29
- package/dist/web/env-manager-server.js +0 -367
- package/dist/web/index.d.ts +0 -1
- package/dist/web/index.js +0 -1
- package/src/LocalToolResolver.ts +0 -424
- package/src/api/enact-api.ts +0 -604
- package/src/api/index.ts +0 -2
- package/src/api/types.ts +0 -114
- package/src/core/DaggerExecutionProvider.ts +0 -1357
- package/src/core/DirectExecutionProvider.ts +0 -484
- package/src/core/EnactCore.ts +0 -847
- package/src/core/index.ts +0 -3
- package/src/exec/index.ts +0 -3
- package/src/exec/logger.ts +0 -63
- package/src/exec/validate.ts +0 -238
- package/src/lib/enact-direct.ts +0 -254
- package/src/lib/index.ts +0 -1
- package/src/services/McpCoreService.ts +0 -201
- package/src/services/index.ts +0 -1
- package/src/utils/config.ts +0 -438
- package/src/utils/env-loader.ts +0 -370
- package/src/utils/help.ts +0 -257
- package/src/utils/index.ts +0 -7
- package/src/utils/silent-monitor.ts +0 -328
- package/src/utils/timeout.ts +0 -26
- package/src/web/env-manager-server.ts +0 -465
- package/src/web/index.ts +0 -1
- package/src/web/static/app.js +0 -663
- package/src/web/static/index.html +0 -117
- package/src/web/static/style.css +0 -291
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for command interpolation module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import {
|
|
7
|
+
getMissingParams,
|
|
8
|
+
interpolateCommand,
|
|
9
|
+
parseCommand,
|
|
10
|
+
parseCommandArgs,
|
|
11
|
+
prepareCommand,
|
|
12
|
+
shellEscape,
|
|
13
|
+
} from "../../src/execution/command";
|
|
14
|
+
|
|
15
|
+
describe("Command Interpolation", () => {
|
|
16
|
+
describe("parseCommand", () => {
|
|
17
|
+
test("parses command with no parameters", () => {
|
|
18
|
+
const result = parseCommand("echo hello");
|
|
19
|
+
|
|
20
|
+
expect(result.original).toBe("echo hello");
|
|
21
|
+
expect(result.tokens).toHaveLength(1);
|
|
22
|
+
expect(result.tokens[0]).toEqual({ type: "literal", value: "echo hello" });
|
|
23
|
+
expect(result.parameters).toHaveLength(0);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("parses command with single parameter", () => {
|
|
27
|
+
const result = parseCommand("echo ${message}");
|
|
28
|
+
|
|
29
|
+
expect(result.original).toBe("echo ${message}");
|
|
30
|
+
expect(result.tokens).toHaveLength(2);
|
|
31
|
+
expect(result.tokens[0]).toEqual({ type: "literal", value: "echo " });
|
|
32
|
+
expect(result.tokens[1]).toEqual({ type: "parameter", name: "message" });
|
|
33
|
+
expect(result.parameters).toEqual(["message"]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("parses command with multiple parameters", () => {
|
|
37
|
+
const result = parseCommand("curl -X ${method} ${url} -d '${data}'");
|
|
38
|
+
|
|
39
|
+
expect(result.parameters).toContain("method");
|
|
40
|
+
expect(result.parameters).toContain("url");
|
|
41
|
+
expect(result.parameters).toContain("data");
|
|
42
|
+
expect(result.parameters).toHaveLength(3);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("parses command with parameter at start", () => {
|
|
46
|
+
const result = parseCommand("${cmd} arg1 arg2");
|
|
47
|
+
|
|
48
|
+
expect(result.tokens[0]).toEqual({ type: "parameter", name: "cmd" });
|
|
49
|
+
expect(result.tokens[1]).toEqual({ type: "literal", value: " arg1 arg2" });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("parses command with adjacent parameters", () => {
|
|
53
|
+
const result = parseCommand("${prefix}${suffix}");
|
|
54
|
+
|
|
55
|
+
expect(result.tokens).toHaveLength(2);
|
|
56
|
+
expect(result.tokens[0]).toEqual({ type: "parameter", name: "prefix" });
|
|
57
|
+
expect(result.tokens[1]).toEqual({ type: "parameter", name: "suffix" });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("handles nested braces", () => {
|
|
61
|
+
const result = parseCommand("echo '${json}' | jq '.${field}'");
|
|
62
|
+
|
|
63
|
+
expect(result.parameters).toContain("json");
|
|
64
|
+
expect(result.parameters).toContain("field");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("removes duplicate parameters", () => {
|
|
68
|
+
const result = parseCommand("echo ${name} ${name} ${name}");
|
|
69
|
+
|
|
70
|
+
expect(result.parameters).toEqual(["name"]);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("interpolateCommand", () => {
|
|
75
|
+
test("interpolates single parameter", () => {
|
|
76
|
+
// By default, escape=true, so values get shell-escaped
|
|
77
|
+
const result = interpolateCommand("echo ${message}", {
|
|
78
|
+
message: "hello world",
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// "hello world" contains space, gets single-quoted
|
|
82
|
+
expect(result).toBe("echo 'hello world'");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("interpolates multiple parameters", () => {
|
|
86
|
+
const result = interpolateCommand("curl -X ${method} ${url}", {
|
|
87
|
+
method: "POST",
|
|
88
|
+
url: "https://api.example.com",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// URL contains special chars, gets quoted
|
|
92
|
+
expect(result).toBe("curl -X POST 'https://api.example.com'");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("handles missing parameters with keep option", () => {
|
|
96
|
+
const result = interpolateCommand("echo ${message}", {}, { onMissing: "keep" });
|
|
97
|
+
|
|
98
|
+
// Missing params are left as-is when onMissing is "keep"
|
|
99
|
+
expect(result).toBe("echo ${message}");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("throws on missing parameters by default", () => {
|
|
103
|
+
expect(() => interpolateCommand("echo ${message}", {})).toThrow(
|
|
104
|
+
"Missing required parameter: message"
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("converts numbers to strings", () => {
|
|
109
|
+
const result = interpolateCommand("seq ${start} ${end}", {
|
|
110
|
+
start: 1,
|
|
111
|
+
end: 10,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(result).toBe("seq 1 10");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("handles boolean values", () => {
|
|
118
|
+
const result = interpolateCommand("echo ${flag}", {
|
|
119
|
+
flag: true,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(result).toBe("echo true");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("handles null values as empty string", () => {
|
|
126
|
+
const result = interpolateCommand("echo ${a}", {
|
|
127
|
+
a: null,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// null becomes empty string
|
|
131
|
+
expect(result).toBe("echo ''");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("stringifies objects as JSON", () => {
|
|
135
|
+
const result = interpolateCommand(
|
|
136
|
+
"echo ${data}",
|
|
137
|
+
{
|
|
138
|
+
data: { key: "value" },
|
|
139
|
+
},
|
|
140
|
+
{ escape: false }
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(result).toBe('echo {"key":"value"}');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("handles arrays", () => {
|
|
147
|
+
const result = interpolateCommand(
|
|
148
|
+
"echo ${items}",
|
|
149
|
+
{
|
|
150
|
+
items: [1, 2, 3],
|
|
151
|
+
},
|
|
152
|
+
{ escape: false }
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect(result).toBe("echo [1,2,3]");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("shellEscape", () => {
|
|
160
|
+
test("escapes single quotes", () => {
|
|
161
|
+
// The implementation uses: 'it'"'"'s' pattern
|
|
162
|
+
expect(shellEscape("it's")).toBe("'it'\"'\"'s'");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("wraps strings with spaces", () => {
|
|
166
|
+
expect(shellEscape("hello world")).toBe("'hello world'");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("escapes special characters", () => {
|
|
170
|
+
expect(shellEscape("test$var")).toBe("'test$var'");
|
|
171
|
+
expect(shellEscape("test`cmd`")).toBe("'test`cmd`'");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("returns safe strings as-is", () => {
|
|
175
|
+
expect(shellEscape("simple")).toBe("simple");
|
|
176
|
+
expect(shellEscape("path/to/file")).toBe("path/to/file");
|
|
177
|
+
expect(shellEscape("file.txt")).toBe("file.txt");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("handles empty string", () => {
|
|
181
|
+
expect(shellEscape("")).toBe("''");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("handles strings with newlines", () => {
|
|
185
|
+
expect(shellEscape("line1\nline2")).toBe("'line1\nline2'");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("handles strings with backslashes", () => {
|
|
189
|
+
expect(shellEscape("path\\to\\file")).toBe("'path\\to\\file'");
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe("parseCommandArgs", () => {
|
|
194
|
+
test("parses simple arguments", () => {
|
|
195
|
+
const result = parseCommandArgs("arg1 arg2 arg3");
|
|
196
|
+
|
|
197
|
+
expect(result).toEqual(["arg1", "arg2", "arg3"]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("handles quoted strings", () => {
|
|
201
|
+
const result = parseCommandArgs('echo "hello world"');
|
|
202
|
+
|
|
203
|
+
expect(result).toEqual(["echo", "hello world"]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("handles single-quoted strings", () => {
|
|
207
|
+
const result = parseCommandArgs("echo 'hello world'");
|
|
208
|
+
|
|
209
|
+
expect(result).toEqual(["echo", "hello world"]);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("handles mixed quotes", () => {
|
|
213
|
+
const result = parseCommandArgs("echo 'single' \"double\"");
|
|
214
|
+
|
|
215
|
+
expect(result).toEqual(["echo", "single", "double"]);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("handles empty input", () => {
|
|
219
|
+
expect(parseCommandArgs("")).toEqual([]);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("handles extra whitespace", () => {
|
|
223
|
+
const result = parseCommandArgs(" arg1 arg2 ");
|
|
224
|
+
|
|
225
|
+
expect(result).toEqual(["arg1", "arg2"]);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("handles escaped quotes within strings", () => {
|
|
229
|
+
const result = parseCommandArgs('echo "say \\"hello\\""');
|
|
230
|
+
|
|
231
|
+
expect(result).toEqual(["echo", 'say "hello"']);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe("prepareCommand", () => {
|
|
236
|
+
test("prepares simple command without shell wrap", () => {
|
|
237
|
+
const result = prepareCommand("echo hello", {});
|
|
238
|
+
|
|
239
|
+
expect(result).toEqual(["echo", "hello"]);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("wraps command with pipes", () => {
|
|
243
|
+
const result = prepareCommand("echo hello | cat", {});
|
|
244
|
+
|
|
245
|
+
// Contains | which triggers shell wrap
|
|
246
|
+
expect(result).toEqual(["sh", "-c", "echo hello | cat"]);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("interpolates parameters without shell wrap when no special chars", () => {
|
|
250
|
+
// After interpolation, "echo world" has no special chars
|
|
251
|
+
const result = prepareCommand("echo ${name}", { name: "world" });
|
|
252
|
+
|
|
253
|
+
expect(result).toEqual(["echo", "world"]);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("handles commands with pipes", () => {
|
|
257
|
+
const result = prepareCommand("cat file | grep pattern", {});
|
|
258
|
+
|
|
259
|
+
expect(result).toEqual(["sh", "-c", "cat file | grep pattern"]);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("parses simple args without shell features", () => {
|
|
263
|
+
// No special chars, so should parse as args
|
|
264
|
+
const result = prepareCommand("simple command here", {});
|
|
265
|
+
|
|
266
|
+
expect(result).toEqual(["simple", "command", "here"]);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("shell wraps when escaped value contains quotes", () => {
|
|
270
|
+
// When value has spaces, it gets single-quoted, but parseCommandArgs
|
|
271
|
+
// handles quotes properly, so it still gets parsed as args
|
|
272
|
+
const result = prepareCommand("echo ${msg}", { msg: "hello world" });
|
|
273
|
+
|
|
274
|
+
// parseCommandArgs strips the quotes, so we get the unquoted value
|
|
275
|
+
expect(result).toEqual(["echo", "hello world"]);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe("getMissingParams", () => {
|
|
280
|
+
test("returns empty array when all params present", () => {
|
|
281
|
+
const result = getMissingParams("echo ${a} ${b}", { a: "1", b: "2" });
|
|
282
|
+
|
|
283
|
+
expect(result).toEqual([]);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("returns missing param names", () => {
|
|
287
|
+
const result = getMissingParams("echo ${a} ${b} ${c}", { a: "1" });
|
|
288
|
+
|
|
289
|
+
expect(result).toContain("b");
|
|
290
|
+
expect(result).toContain("c");
|
|
291
|
+
expect(result).not.toContain("a");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("handles no parameters", () => {
|
|
295
|
+
const result = getMissingParams("echo hello", {});
|
|
296
|
+
|
|
297
|
+
expect(result).toEqual([]);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("handles all parameters missing", () => {
|
|
301
|
+
const result = getMissingParams("echo ${x} ${y}", {});
|
|
302
|
+
|
|
303
|
+
expect(result).toEqual(["x", "y"]);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("treats null as present but undefined as missing", () => {
|
|
307
|
+
// null is a value (present), undefined means not provided
|
|
308
|
+
const result = getMissingParams("echo ${a} ${b}", {
|
|
309
|
+
a: null,
|
|
310
|
+
b: undefined,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Only b is missing since undefined is treated as not provided
|
|
314
|
+
expect(result).toEqual(["b"]);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
});
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for input validation module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
import type { JSONSchema7 } from "json-schema";
|
|
7
|
+
import {
|
|
8
|
+
applyDefaults,
|
|
9
|
+
getParamInfo,
|
|
10
|
+
getRequiredParams,
|
|
11
|
+
validateInputs,
|
|
12
|
+
} from "../../src/execution/validation";
|
|
13
|
+
|
|
14
|
+
describe("Input Validation", () => {
|
|
15
|
+
describe("validateInputs", () => {
|
|
16
|
+
const simpleSchema: JSONSchema7 = {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
name: { type: "string" },
|
|
20
|
+
count: { type: "number" },
|
|
21
|
+
},
|
|
22
|
+
required: ["name"],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
test("validates correct inputs", () => {
|
|
26
|
+
const result = validateInputs({ name: "test", count: 5 }, simpleSchema);
|
|
27
|
+
|
|
28
|
+
expect(result.valid).toBe(true);
|
|
29
|
+
expect(result.errors).toHaveLength(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("reports missing required fields", () => {
|
|
33
|
+
const result = validateInputs({ count: 5 }, simpleSchema);
|
|
34
|
+
|
|
35
|
+
expect(result.valid).toBe(false);
|
|
36
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
37
|
+
expect(result.errors[0]?.path).toContain("name");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("reports type errors", () => {
|
|
41
|
+
const result = validateInputs({ name: "test", count: "not a number" }, simpleSchema);
|
|
42
|
+
|
|
43
|
+
expect(result.valid).toBe(false);
|
|
44
|
+
expect(result.errors.some((e) => e.path.includes("count"))).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("validates nested objects", () => {
|
|
48
|
+
const nestedSchema: JSONSchema7 = {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
config: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
timeout: { type: "number" },
|
|
55
|
+
},
|
|
56
|
+
required: ["timeout"],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ["config"],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const result = validateInputs({ config: { timeout: 30 } }, nestedSchema);
|
|
63
|
+
|
|
64
|
+
expect(result.valid).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("validates arrays", () => {
|
|
68
|
+
const arraySchema: JSONSchema7 = {
|
|
69
|
+
type: "object",
|
|
70
|
+
properties: {
|
|
71
|
+
items: {
|
|
72
|
+
type: "array",
|
|
73
|
+
items: { type: "string" },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const result = validateInputs({ items: ["a", "b", "c"] }, arraySchema);
|
|
79
|
+
|
|
80
|
+
expect(result.valid).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("handles empty inputs", () => {
|
|
84
|
+
const result = validateInputs({}, simpleSchema);
|
|
85
|
+
|
|
86
|
+
// Missing required "name"
|
|
87
|
+
expect(result.valid).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("validates string patterns", () => {
|
|
91
|
+
const patternSchema: JSONSchema7 = {
|
|
92
|
+
type: "object",
|
|
93
|
+
properties: {
|
|
94
|
+
email: {
|
|
95
|
+
type: "string",
|
|
96
|
+
pattern: "^[a-z]+@[a-z]+\\.[a-z]+$",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const validResult = validateInputs({ email: "test@example.com" }, patternSchema);
|
|
102
|
+
expect(validResult.valid).toBe(true);
|
|
103
|
+
|
|
104
|
+
const invalidResult = validateInputs({ email: "not-an-email" }, patternSchema);
|
|
105
|
+
expect(invalidResult.valid).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("validates enums", () => {
|
|
109
|
+
const enumSchema: JSONSchema7 = {
|
|
110
|
+
type: "object",
|
|
111
|
+
properties: {
|
|
112
|
+
status: {
|
|
113
|
+
type: "string",
|
|
114
|
+
enum: ["active", "inactive", "pending"],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const validResult = validateInputs({ status: "active" }, enumSchema);
|
|
120
|
+
expect(validResult.valid).toBe(true);
|
|
121
|
+
|
|
122
|
+
const invalidResult = validateInputs({ status: "unknown" }, enumSchema);
|
|
123
|
+
expect(invalidResult.valid).toBe(false);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("validates minimum and maximum", () => {
|
|
127
|
+
const rangeSchema: JSONSchema7 = {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
count: { type: "number", minimum: 0, maximum: 100 },
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
expect(validateInputs({ count: 50 }, rangeSchema).valid).toBe(true);
|
|
135
|
+
expect(validateInputs({ count: -1 }, rangeSchema).valid).toBe(false);
|
|
136
|
+
expect(validateInputs({ count: 101 }, rangeSchema).valid).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("validates string lengths", () => {
|
|
140
|
+
const lengthSchema: JSONSchema7 = {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
name: { type: "string", minLength: 2, maxLength: 10 },
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
expect(validateInputs({ name: "test" }, lengthSchema).valid).toBe(true);
|
|
148
|
+
expect(validateInputs({ name: "x" }, lengthSchema).valid).toBe(false);
|
|
149
|
+
expect(validateInputs({ name: "this is too long" }, lengthSchema).valid).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("applyDefaults", () => {
|
|
154
|
+
test("applies default values", () => {
|
|
155
|
+
const schema: JSONSchema7 = {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
name: { type: "string", default: "default_name" },
|
|
159
|
+
count: { type: "number", default: 10 },
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const result = applyDefaults({}, schema);
|
|
164
|
+
|
|
165
|
+
expect(result.name).toBe("default_name");
|
|
166
|
+
expect(result.count).toBe(10);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("preserves provided values", () => {
|
|
170
|
+
const schema: JSONSchema7 = {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
name: { type: "string", default: "default_name" },
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const result = applyDefaults({ name: "custom_name" }, schema);
|
|
178
|
+
|
|
179
|
+
expect(result.name).toBe("custom_name");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("applies nested defaults", () => {
|
|
183
|
+
const schema: JSONSchema7 = {
|
|
184
|
+
type: "object",
|
|
185
|
+
properties: {
|
|
186
|
+
config: {
|
|
187
|
+
type: "object",
|
|
188
|
+
default: { timeout: 30, retries: 3 },
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const result = applyDefaults({}, schema);
|
|
194
|
+
|
|
195
|
+
expect(result.config).toEqual({ timeout: 30, retries: 3 });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("handles array defaults", () => {
|
|
199
|
+
const schema: JSONSchema7 = {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {
|
|
202
|
+
tags: {
|
|
203
|
+
type: "array",
|
|
204
|
+
default: ["default", "tag"],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const result = applyDefaults({}, schema);
|
|
210
|
+
|
|
211
|
+
expect(result.tags).toEqual(["default", "tag"]);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("handles boolean defaults", () => {
|
|
215
|
+
const schema: JSONSchema7 = {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: {
|
|
218
|
+
enabled: { type: "boolean", default: true },
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const result = applyDefaults({}, schema);
|
|
223
|
+
|
|
224
|
+
expect(result.enabled).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("does not apply defaults for explicit null", () => {
|
|
228
|
+
const schema: JSONSchema7 = {
|
|
229
|
+
type: "object",
|
|
230
|
+
properties: {
|
|
231
|
+
value: { type: "string", default: "default" },
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Explicitly passing null should not trigger default
|
|
236
|
+
const result = applyDefaults({ value: null }, schema);
|
|
237
|
+
|
|
238
|
+
expect(result.value).toBeNull();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("applies defaults only for undefined", () => {
|
|
242
|
+
const schema: JSONSchema7 = {
|
|
243
|
+
type: "object",
|
|
244
|
+
properties: {
|
|
245
|
+
value: { type: "string", default: "default" },
|
|
246
|
+
count: { type: "number", default: 0 },
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const result = applyDefaults({ value: undefined }, schema);
|
|
251
|
+
|
|
252
|
+
expect(result.value).toBe("default");
|
|
253
|
+
expect(result.count).toBe(0);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe("getRequiredParams", () => {
|
|
258
|
+
test("returns required parameter names", () => {
|
|
259
|
+
const schema: JSONSchema7 = {
|
|
260
|
+
type: "object",
|
|
261
|
+
properties: {
|
|
262
|
+
name: { type: "string" },
|
|
263
|
+
email: { type: "string" },
|
|
264
|
+
age: { type: "number" },
|
|
265
|
+
},
|
|
266
|
+
required: ["name", "email"],
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const result = getRequiredParams(schema);
|
|
270
|
+
|
|
271
|
+
expect(result).toContain("name");
|
|
272
|
+
expect(result).toContain("email");
|
|
273
|
+
expect(result).not.toContain("age");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("returns empty array when no required params", () => {
|
|
277
|
+
const schema: JSONSchema7 = {
|
|
278
|
+
type: "object",
|
|
279
|
+
properties: {
|
|
280
|
+
name: { type: "string" },
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const result = getRequiredParams(schema);
|
|
285
|
+
|
|
286
|
+
expect(result).toEqual([]);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("handles schema without properties", () => {
|
|
290
|
+
const schema: JSONSchema7 = {
|
|
291
|
+
type: "object",
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const result = getRequiredParams(schema);
|
|
295
|
+
|
|
296
|
+
expect(result).toEqual([]);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe("getParamInfo", () => {
|
|
301
|
+
test("extracts parameter information", () => {
|
|
302
|
+
const schema: JSONSchema7 = {
|
|
303
|
+
type: "object",
|
|
304
|
+
properties: {
|
|
305
|
+
name: {
|
|
306
|
+
type: "string",
|
|
307
|
+
description: "User name",
|
|
308
|
+
default: "anonymous",
|
|
309
|
+
},
|
|
310
|
+
count: {
|
|
311
|
+
type: "number",
|
|
312
|
+
description: "Item count",
|
|
313
|
+
minimum: 0,
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
required: ["name"],
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const result = getParamInfo(schema);
|
|
320
|
+
|
|
321
|
+
expect(result.size).toBe(2);
|
|
322
|
+
|
|
323
|
+
const nameParam = result.get("name");
|
|
324
|
+
expect(nameParam).toBeDefined();
|
|
325
|
+
expect(nameParam?.type).toBe("string");
|
|
326
|
+
expect(nameParam?.description).toBe("User name");
|
|
327
|
+
expect(nameParam?.required).toBe(true);
|
|
328
|
+
expect(nameParam?.default).toBe("anonymous");
|
|
329
|
+
|
|
330
|
+
const countParam = result.get("count");
|
|
331
|
+
expect(countParam).toBeDefined();
|
|
332
|
+
expect(countParam?.type).toBe("number");
|
|
333
|
+
expect(countParam?.required).toBe(false);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("handles array types", () => {
|
|
337
|
+
const schema: JSONSchema7 = {
|
|
338
|
+
type: "object",
|
|
339
|
+
properties: {
|
|
340
|
+
tags: {
|
|
341
|
+
type: "array",
|
|
342
|
+
items: { type: "string" },
|
|
343
|
+
description: "Tags for categorization",
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const result = getParamInfo(schema);
|
|
349
|
+
const tagsParam = result.get("tags");
|
|
350
|
+
|
|
351
|
+
expect(tagsParam?.type).toBe("array");
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("handles enum types", () => {
|
|
355
|
+
const schema: JSONSchema7 = {
|
|
356
|
+
type: "object",
|
|
357
|
+
properties: {
|
|
358
|
+
status: {
|
|
359
|
+
type: "string",
|
|
360
|
+
enum: ["active", "inactive"],
|
|
361
|
+
description: "Account status",
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const result = getParamInfo(schema);
|
|
367
|
+
const statusParam = result.get("status");
|
|
368
|
+
|
|
369
|
+
// Note: getParamInfo doesn't extract enum values, just type
|
|
370
|
+
expect(statusParam).toBeDefined();
|
|
371
|
+
expect(statusParam?.type).toBe("string");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test("returns empty Map for schema without properties", () => {
|
|
375
|
+
const schema: JSONSchema7 = {
|
|
376
|
+
type: "object",
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const result = getParamInfo(schema);
|
|
380
|
+
|
|
381
|
+
expect(result.size).toBe(0);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
});
|