ai-evaluate 2.1.8 → 2.2.0
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/evaluate.d.ts.map +1 -1
- package/dist/evaluate.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/miniflare-pool.d.ts.map +1 -1
- package/dist/miniflare-pool.js.map +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js.map +1 -1
- package/dist/static/index.d.ts +111 -0
- package/dist/static/index.d.ts.map +1 -0
- package/dist/static/index.js +347 -0
- package/dist/static/index.js.map +1 -0
- package/dist/type-guards.d.ts.map +1 -1
- package/dist/type-guards.js.map +1 -1
- package/dist/worker-template/core.d.ts.map +1 -1
- package/dist/worker-template/core.js +1 -1
- package/dist/worker-template/core.js.map +1 -1
- package/package.json +17 -4
- package/public/capnweb.mjs +220 -0
- package/public/index.mjs +426 -0
- package/public/scaffold.mjs +198 -0
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-test.log +0 -54
- package/.turbo/turbo-typecheck.log +0 -4
- package/CHANGELOG.md +0 -48
- package/example/package.json +0 -20
- package/example/src/index.ts +0 -221
- package/example/wrangler.jsonc +0 -25
- package/src/capnweb-bundle.ts +0 -2596
- package/src/evaluate.ts +0 -329
- package/src/index.ts +0 -23
- package/src/miniflare-pool.ts +0 -395
- package/src/node.ts +0 -245
- package/src/repl.ts +0 -228
- package/src/shared.ts +0 -186
- package/src/type-guards.ts +0 -323
- package/src/types.ts +0 -196
- package/src/validation.ts +0 -120
- package/src/worker-template/code-transforms.ts +0 -32
- package/src/worker-template/core.ts +0 -557
- package/src/worker-template/helpers.ts +0 -90
- package/src/worker-template/index.ts +0 -23
- package/src/worker-template/sdk-generator.ts +0 -2515
- package/src/worker-template/test-generator.ts +0 -358
- package/test/evaluate-extended.test.js +0 -429
- package/test/evaluate-extended.test.ts +0 -469
- package/test/evaluate.test.js +0 -235
- package/test/evaluate.test.ts +0 -253
- package/test/index.test.js +0 -77
- package/test/index.test.ts +0 -95
- package/test/miniflare-pool.test.ts +0 -246
- package/test/node.test.ts +0 -467
- package/test/security.test.ts +0 -1009
- package/test/shared.test.ts +0 -105
- package/test/type-guards.test.ts +0 -303
- package/test/validation.test.ts +0 -240
- package/test/worker-template.test.js +0 -365
- package/test/worker-template.test.ts +0 -432
- package/tsconfig.json +0 -22
- package/vitest.config.js +0 -21
- package/vitest.config.ts +0 -28
package/test/shared.test.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { matchesDomainPattern, isDomainAllowed, generateDomainCheckCode } from '../src/shared.js'
|
|
3
|
-
|
|
4
|
-
describe('domain matching utilities', () => {
|
|
5
|
-
describe('matchesDomainPattern', () => {
|
|
6
|
-
describe('exact matching', () => {
|
|
7
|
-
it('matches exact domain', () => {
|
|
8
|
-
expect(matchesDomainPattern('api.example.com', 'api.example.com')).toBe(true)
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it('does not match different domains', () => {
|
|
12
|
-
expect(matchesDomainPattern('api.example.com', 'other.example.com')).toBe(false)
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it('is case-insensitive', () => {
|
|
16
|
-
expect(matchesDomainPattern('API.EXAMPLE.COM', 'api.example.com')).toBe(true)
|
|
17
|
-
expect(matchesDomainPattern('api.example.com', 'API.EXAMPLE.COM')).toBe(true)
|
|
18
|
-
})
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
describe('wildcard matching', () => {
|
|
22
|
-
it('matches subdomain with wildcard pattern', () => {
|
|
23
|
-
expect(matchesDomainPattern('api.example.com', '*.example.com')).toBe(true)
|
|
24
|
-
expect(matchesDomainPattern('data.example.com', '*.example.com')).toBe(true)
|
|
25
|
-
expect(matchesDomainPattern('nested.api.example.com', '*.example.com')).toBe(true)
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('matches root domain with wildcard pattern', () => {
|
|
29
|
-
// Wildcard also matches the root domain itself
|
|
30
|
-
expect(matchesDomainPattern('example.com', '*.example.com')).toBe(true)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('does not match unrelated domains with wildcard', () => {
|
|
34
|
-
expect(matchesDomainPattern('api.other.com', '*.example.com')).toBe(false)
|
|
35
|
-
expect(matchesDomainPattern('example.com.evil.com', '*.example.com')).toBe(false)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('wildcard is case-insensitive', () => {
|
|
39
|
-
expect(matchesDomainPattern('API.EXAMPLE.COM', '*.example.com')).toBe(true)
|
|
40
|
-
expect(matchesDomainPattern('api.example.com', '*.EXAMPLE.COM')).toBe(true)
|
|
41
|
-
})
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
describe('isDomainAllowed', () => {
|
|
46
|
-
it('returns true for allowed exact domain', () => {
|
|
47
|
-
expect(isDomainAllowed('https://api.example.com/path', ['api.example.com'])).toBe(true)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('returns false for non-allowed domain', () => {
|
|
51
|
-
expect(isDomainAllowed('https://blocked.com/path', ['api.example.com'])).toBe(false)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('returns true for domain matching wildcard', () => {
|
|
55
|
-
expect(isDomainAllowed('https://api.example.com/path', ['*.example.com'])).toBe(true)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('supports multiple allowed domains', () => {
|
|
59
|
-
const allowed = ['api.example.com', '*.trusted.com', 'data.org']
|
|
60
|
-
expect(isDomainAllowed('https://api.example.com/path', allowed)).toBe(true)
|
|
61
|
-
expect(isDomainAllowed('https://any.trusted.com/path', allowed)).toBe(true)
|
|
62
|
-
expect(isDomainAllowed('https://data.org/path', allowed)).toBe(true)
|
|
63
|
-
expect(isDomainAllowed('https://blocked.com/path', allowed)).toBe(false)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('returns false for invalid URLs', () => {
|
|
67
|
-
expect(isDomainAllowed('not-a-url', ['api.example.com'])).toBe(false)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('returns false for empty allowlist', () => {
|
|
71
|
-
expect(isDomainAllowed('https://example.com', [])).toBe(false)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('handles URLs with ports', () => {
|
|
75
|
-
expect(isDomainAllowed('https://api.example.com:8080/path', ['api.example.com'])).toBe(true)
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('handles URLs with authentication', () => {
|
|
79
|
-
expect(isDomainAllowed('https://user:pass@api.example.com/path', ['api.example.com'])).toBe(
|
|
80
|
-
true
|
|
81
|
-
)
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
describe('generateDomainCheckCode', () => {
|
|
86
|
-
it('generates valid JavaScript code', () => {
|
|
87
|
-
const code = generateDomainCheckCode(['api.example.com', '*.trusted.com'])
|
|
88
|
-
expect(code).toContain('__allowedDomains__')
|
|
89
|
-
expect(code).toContain('__matchesDomainPattern__')
|
|
90
|
-
expect(code).toContain('__isDomainAllowed__')
|
|
91
|
-
expect(code).toContain('globalThis.fetch')
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('includes the allowed domains in the generated code', () => {
|
|
95
|
-
const code = generateDomainCheckCode(['api.example.com', '*.trusted.com'])
|
|
96
|
-
expect(code).toContain('api.example.com')
|
|
97
|
-
expect(code).toContain('*.trusted.com')
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('generates code that throws for blocked domains', () => {
|
|
101
|
-
const code = generateDomainCheckCode(['api.example.com'])
|
|
102
|
-
expect(code).toContain('not in allowlist')
|
|
103
|
-
})
|
|
104
|
-
})
|
|
105
|
-
})
|
package/test/type-guards.test.ts
DELETED
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { isEvaluateResult, assertEvaluateResult } from '../src/type-guards.js'
|
|
3
|
-
|
|
4
|
-
describe('type guards', () => {
|
|
5
|
-
describe('isEvaluateResult', () => {
|
|
6
|
-
it('returns true for valid EvaluateResult', () => {
|
|
7
|
-
const result = {
|
|
8
|
-
success: true,
|
|
9
|
-
duration: 100,
|
|
10
|
-
logs: [],
|
|
11
|
-
}
|
|
12
|
-
expect(isEvaluateResult(result)).toBe(true)
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it('returns true for valid EvaluateResult with value', () => {
|
|
16
|
-
const result = {
|
|
17
|
-
success: true,
|
|
18
|
-
duration: 100,
|
|
19
|
-
logs: [],
|
|
20
|
-
value: 42,
|
|
21
|
-
}
|
|
22
|
-
expect(isEvaluateResult(result)).toBe(true)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('returns true for valid EvaluateResult with error', () => {
|
|
26
|
-
const result = {
|
|
27
|
-
success: false,
|
|
28
|
-
duration: 100,
|
|
29
|
-
logs: [],
|
|
30
|
-
error: 'Something went wrong',
|
|
31
|
-
}
|
|
32
|
-
expect(isEvaluateResult(result)).toBe(true)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('returns true for valid EvaluateResult with logs', () => {
|
|
36
|
-
const result = {
|
|
37
|
-
success: true,
|
|
38
|
-
duration: 100,
|
|
39
|
-
logs: [
|
|
40
|
-
{ level: 'log' as const, message: 'hello', timestamp: 1234567890 },
|
|
41
|
-
{ level: 'error' as const, message: 'oops', timestamp: 1234567891 },
|
|
42
|
-
],
|
|
43
|
-
}
|
|
44
|
-
expect(isEvaluateResult(result)).toBe(true)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('returns true for valid EvaluateResult with testResults', () => {
|
|
48
|
-
const result = {
|
|
49
|
-
success: true,
|
|
50
|
-
duration: 100,
|
|
51
|
-
logs: [],
|
|
52
|
-
testResults: {
|
|
53
|
-
total: 2,
|
|
54
|
-
passed: 1,
|
|
55
|
-
failed: 1,
|
|
56
|
-
skipped: 0,
|
|
57
|
-
duration: 50,
|
|
58
|
-
tests: [
|
|
59
|
-
{ name: 'test1', passed: true, duration: 25 },
|
|
60
|
-
{ name: 'test2', passed: false, duration: 25, error: 'failed' },
|
|
61
|
-
],
|
|
62
|
-
},
|
|
63
|
-
}
|
|
64
|
-
expect(isEvaluateResult(result)).toBe(true)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('returns false for null', () => {
|
|
68
|
-
expect(isEvaluateResult(null)).toBe(false)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('returns false for non-object', () => {
|
|
72
|
-
expect(isEvaluateResult('string')).toBe(false)
|
|
73
|
-
expect(isEvaluateResult(123)).toBe(false)
|
|
74
|
-
expect(isEvaluateResult(undefined)).toBe(false)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('returns false for missing success', () => {
|
|
78
|
-
const result = {
|
|
79
|
-
duration: 100,
|
|
80
|
-
logs: [],
|
|
81
|
-
}
|
|
82
|
-
expect(isEvaluateResult(result)).toBe(false)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('returns false for missing duration', () => {
|
|
86
|
-
const result = {
|
|
87
|
-
success: true,
|
|
88
|
-
logs: [],
|
|
89
|
-
}
|
|
90
|
-
expect(isEvaluateResult(result)).toBe(false)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('returns false for missing logs', () => {
|
|
94
|
-
const result = {
|
|
95
|
-
success: true,
|
|
96
|
-
duration: 100,
|
|
97
|
-
}
|
|
98
|
-
expect(isEvaluateResult(result)).toBe(false)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('returns false for wrong type success', () => {
|
|
102
|
-
const result = {
|
|
103
|
-
success: 'true',
|
|
104
|
-
duration: 100,
|
|
105
|
-
logs: [],
|
|
106
|
-
}
|
|
107
|
-
expect(isEvaluateResult(result)).toBe(false)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('returns false for wrong type duration', () => {
|
|
111
|
-
const result = {
|
|
112
|
-
success: true,
|
|
113
|
-
duration: '100',
|
|
114
|
-
logs: [],
|
|
115
|
-
}
|
|
116
|
-
expect(isEvaluateResult(result)).toBe(false)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('returns false for logs not being array', () => {
|
|
120
|
-
const result = {
|
|
121
|
-
success: true,
|
|
122
|
-
duration: 100,
|
|
123
|
-
logs: {},
|
|
124
|
-
}
|
|
125
|
-
expect(isEvaluateResult(result)).toBe(false)
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('returns false for invalid log entry', () => {
|
|
129
|
-
const result = {
|
|
130
|
-
success: true,
|
|
131
|
-
duration: 100,
|
|
132
|
-
logs: [{ level: 'invalid', message: 'hello', timestamp: 1234 }],
|
|
133
|
-
}
|
|
134
|
-
expect(isEvaluateResult(result)).toBe(false)
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('returns false for error being wrong type', () => {
|
|
138
|
-
const result = {
|
|
139
|
-
success: false,
|
|
140
|
-
duration: 100,
|
|
141
|
-
logs: [],
|
|
142
|
-
error: 123,
|
|
143
|
-
}
|
|
144
|
-
expect(isEvaluateResult(result)).toBe(false)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('returns false for invalid testResults', () => {
|
|
148
|
-
const result = {
|
|
149
|
-
success: true,
|
|
150
|
-
duration: 100,
|
|
151
|
-
logs: [],
|
|
152
|
-
testResults: {
|
|
153
|
-
total: 'not a number',
|
|
154
|
-
passed: 0,
|
|
155
|
-
failed: 0,
|
|
156
|
-
skipped: 0,
|
|
157
|
-
duration: 0,
|
|
158
|
-
tests: [],
|
|
159
|
-
},
|
|
160
|
-
}
|
|
161
|
-
expect(isEvaluateResult(result)).toBe(false)
|
|
162
|
-
})
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
describe('assertEvaluateResult', () => {
|
|
166
|
-
it('does not throw for valid EvaluateResult', () => {
|
|
167
|
-
const result = {
|
|
168
|
-
success: true,
|
|
169
|
-
duration: 100,
|
|
170
|
-
logs: [],
|
|
171
|
-
}
|
|
172
|
-
expect(() => assertEvaluateResult(result)).not.toThrow()
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
it('throws for null', () => {
|
|
176
|
-
expect(() => assertEvaluateResult(null)).toThrow(
|
|
177
|
-
'Invalid EvaluateResult: expected object, got null'
|
|
178
|
-
)
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('throws for non-object', () => {
|
|
182
|
-
expect(() => assertEvaluateResult('string')).toThrow(
|
|
183
|
-
'Invalid EvaluateResult: expected object, got string'
|
|
184
|
-
)
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it('throws for missing success', () => {
|
|
188
|
-
const result = { duration: 100, logs: [] }
|
|
189
|
-
expect(() => assertEvaluateResult(result)).toThrow(
|
|
190
|
-
"Invalid EvaluateResult: 'success' must be a boolean, got undefined"
|
|
191
|
-
)
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it('throws for missing duration', () => {
|
|
195
|
-
const result = { success: true, logs: [] }
|
|
196
|
-
expect(() => assertEvaluateResult(result)).toThrow(
|
|
197
|
-
"Invalid EvaluateResult: 'duration' must be a number, got undefined"
|
|
198
|
-
)
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
it('throws for missing logs', () => {
|
|
202
|
-
const result = { success: true, duration: 100 }
|
|
203
|
-
expect(() => assertEvaluateResult(result)).toThrow(
|
|
204
|
-
"Invalid EvaluateResult: 'logs' must be an array, got undefined"
|
|
205
|
-
)
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
it('throws for invalid log entry level', () => {
|
|
209
|
-
const result = {
|
|
210
|
-
success: true,
|
|
211
|
-
duration: 100,
|
|
212
|
-
logs: [{ level: 'invalid', message: 'hello', timestamp: 1234 }],
|
|
213
|
-
}
|
|
214
|
-
expect(() => assertEvaluateResult(result)).toThrow(
|
|
215
|
-
'Invalid EvaluateResult: logs[0].level must be one of log, warn, error, info, debug'
|
|
216
|
-
)
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
it('throws for invalid log entry message', () => {
|
|
220
|
-
const result = {
|
|
221
|
-
success: true,
|
|
222
|
-
duration: 100,
|
|
223
|
-
logs: [{ level: 'log', message: 123, timestamp: 1234 }],
|
|
224
|
-
}
|
|
225
|
-
expect(() => assertEvaluateResult(result)).toThrow(
|
|
226
|
-
'Invalid EvaluateResult: logs[0].message must be a string'
|
|
227
|
-
)
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
it('throws for invalid log entry timestamp', () => {
|
|
231
|
-
const result = {
|
|
232
|
-
success: true,
|
|
233
|
-
duration: 100,
|
|
234
|
-
logs: [{ level: 'log', message: 'hello', timestamp: 'not a number' }],
|
|
235
|
-
}
|
|
236
|
-
expect(() => assertEvaluateResult(result)).toThrow(
|
|
237
|
-
'Invalid EvaluateResult: logs[0].timestamp must be a number'
|
|
238
|
-
)
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
it('throws for invalid error type', () => {
|
|
242
|
-
const result = {
|
|
243
|
-
success: false,
|
|
244
|
-
duration: 100,
|
|
245
|
-
logs: [],
|
|
246
|
-
error: 123,
|
|
247
|
-
}
|
|
248
|
-
expect(() => assertEvaluateResult(result)).toThrow(
|
|
249
|
-
"Invalid EvaluateResult: 'error' must be a string if present"
|
|
250
|
-
)
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
it('throws for invalid testResults.total', () => {
|
|
254
|
-
const result = {
|
|
255
|
-
success: true,
|
|
256
|
-
duration: 100,
|
|
257
|
-
logs: [],
|
|
258
|
-
testResults: {
|
|
259
|
-
total: 'not a number',
|
|
260
|
-
passed: 0,
|
|
261
|
-
failed: 0,
|
|
262
|
-
skipped: 0,
|
|
263
|
-
duration: 0,
|
|
264
|
-
tests: [],
|
|
265
|
-
},
|
|
266
|
-
}
|
|
267
|
-
expect(() => assertEvaluateResult(result)).toThrow(
|
|
268
|
-
'Invalid EvaluateResult: testResults.total must be a number'
|
|
269
|
-
)
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
it('throws for invalid test result', () => {
|
|
273
|
-
const result = {
|
|
274
|
-
success: true,
|
|
275
|
-
duration: 100,
|
|
276
|
-
logs: [],
|
|
277
|
-
testResults: {
|
|
278
|
-
total: 1,
|
|
279
|
-
passed: 0,
|
|
280
|
-
failed: 1,
|
|
281
|
-
skipped: 0,
|
|
282
|
-
duration: 50,
|
|
283
|
-
tests: [{ name: 'test1', passed: 'not a boolean', duration: 25 }],
|
|
284
|
-
},
|
|
285
|
-
}
|
|
286
|
-
expect(() => assertEvaluateResult(result)).toThrow(
|
|
287
|
-
'Invalid EvaluateResult: testResults.tests[0].passed must be a boolean'
|
|
288
|
-
)
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
it('validates all log level types', () => {
|
|
292
|
-
const levels = ['log', 'warn', 'error', 'info', 'debug'] as const
|
|
293
|
-
for (const level of levels) {
|
|
294
|
-
const result = {
|
|
295
|
-
success: true,
|
|
296
|
-
duration: 100,
|
|
297
|
-
logs: [{ level, message: 'test', timestamp: 1234 }],
|
|
298
|
-
}
|
|
299
|
-
expect(() => assertEvaluateResult(result)).not.toThrow()
|
|
300
|
-
}
|
|
301
|
-
})
|
|
302
|
-
})
|
|
303
|
-
})
|
package/test/validation.test.ts
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
validateOptions,
|
|
4
|
-
ValidationError,
|
|
5
|
-
MAX_SCRIPT_SIZE,
|
|
6
|
-
MAX_IMPORTS,
|
|
7
|
-
MAX_TIMEOUT,
|
|
8
|
-
DEFAULT_TIMEOUT,
|
|
9
|
-
} from '../src/validation.js'
|
|
10
|
-
|
|
11
|
-
describe('validation', () => {
|
|
12
|
-
describe('constants', () => {
|
|
13
|
-
it('exports expected constant values', () => {
|
|
14
|
-
expect(MAX_SCRIPT_SIZE).toBe(1024 * 1024) // 1MB
|
|
15
|
-
expect(MAX_IMPORTS).toBe(100)
|
|
16
|
-
expect(MAX_TIMEOUT).toBe(60000) // 60s
|
|
17
|
-
expect(DEFAULT_TIMEOUT).toBe(5000) // 5s
|
|
18
|
-
})
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
describe('validateOptions', () => {
|
|
22
|
-
describe('valid options', () => {
|
|
23
|
-
it('accepts empty options', () => {
|
|
24
|
-
expect(() => validateOptions({})).not.toThrow()
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('accepts valid script', () => {
|
|
28
|
-
expect(() => validateOptions({ script: 'return 1 + 1' })).not.toThrow()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('accepts valid module', () => {
|
|
32
|
-
expect(() => validateOptions({ module: 'export const x = 1' })).not.toThrow()
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('accepts valid tests', () => {
|
|
36
|
-
expect(() => validateOptions({ tests: 'it("works", () => {})' })).not.toThrow()
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('accepts valid timeout', () => {
|
|
40
|
-
expect(() => validateOptions({ timeout: 5000 })).not.toThrow()
|
|
41
|
-
expect(() => validateOptions({ timeout: 1 })).not.toThrow()
|
|
42
|
-
expect(() => validateOptions({ timeout: MAX_TIMEOUT })).not.toThrow()
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('accepts valid imports array', () => {
|
|
46
|
-
expect(() => validateOptions({ imports: ['https://esm.sh/lodash'] })).not.toThrow()
|
|
47
|
-
expect(() =>
|
|
48
|
-
validateOptions({ imports: ['http://localhost:3000/module.js'] })
|
|
49
|
-
).not.toThrow()
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('accepts undefined values', () => {
|
|
53
|
-
expect(() =>
|
|
54
|
-
validateOptions({
|
|
55
|
-
script: undefined,
|
|
56
|
-
module: undefined,
|
|
57
|
-
tests: undefined,
|
|
58
|
-
timeout: undefined,
|
|
59
|
-
imports: undefined,
|
|
60
|
-
})
|
|
61
|
-
).not.toThrow()
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
describe('timeout validation', () => {
|
|
66
|
-
it('rejects non-number timeout', () => {
|
|
67
|
-
expect(() => validateOptions({ timeout: '5000' as unknown as number })).toThrow(
|
|
68
|
-
ValidationError
|
|
69
|
-
)
|
|
70
|
-
expect(() => validateOptions({ timeout: '5000' as unknown as number })).toThrow(
|
|
71
|
-
'timeout must be a number'
|
|
72
|
-
)
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('rejects non-finite timeout', () => {
|
|
76
|
-
expect(() => validateOptions({ timeout: Infinity })).toThrow(ValidationError)
|
|
77
|
-
expect(() => validateOptions({ timeout: Infinity })).toThrow(
|
|
78
|
-
'timeout must be a finite number'
|
|
79
|
-
)
|
|
80
|
-
expect(() => validateOptions({ timeout: NaN })).toThrow('timeout must be a finite number')
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it('rejects zero timeout', () => {
|
|
84
|
-
expect(() => validateOptions({ timeout: 0 })).toThrow(ValidationError)
|
|
85
|
-
expect(() => validateOptions({ timeout: 0 })).toThrow('timeout must be a positive number')
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('rejects negative timeout', () => {
|
|
89
|
-
expect(() => validateOptions({ timeout: -1000 })).toThrow(ValidationError)
|
|
90
|
-
expect(() => validateOptions({ timeout: -1000 })).toThrow(
|
|
91
|
-
'timeout must be a positive number'
|
|
92
|
-
)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('rejects timeout exceeding maximum', () => {
|
|
96
|
-
expect(() => validateOptions({ timeout: MAX_TIMEOUT + 1 })).toThrow(ValidationError)
|
|
97
|
-
expect(() => validateOptions({ timeout: MAX_TIMEOUT + 1 })).toThrow(
|
|
98
|
-
`timeout exceeds maximum allowed value of ${MAX_TIMEOUT}ms`
|
|
99
|
-
)
|
|
100
|
-
})
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
describe('script validation', () => {
|
|
104
|
-
it('rejects non-string script', () => {
|
|
105
|
-
expect(() => validateOptions({ script: 123 as unknown as string })).toThrow(ValidationError)
|
|
106
|
-
expect(() => validateOptions({ script: 123 as unknown as string })).toThrow(
|
|
107
|
-
'script must be a string'
|
|
108
|
-
)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('rejects script exceeding size limit', () => {
|
|
112
|
-
const largeScript = 'a'.repeat(MAX_SCRIPT_SIZE + 1)
|
|
113
|
-
expect(() => validateOptions({ script: largeScript })).toThrow(ValidationError)
|
|
114
|
-
expect(() => validateOptions({ script: largeScript })).toThrow(
|
|
115
|
-
/script size.*exceeds maximum allowed size/
|
|
116
|
-
)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('accepts script at exact size limit', () => {
|
|
120
|
-
const maxScript = 'a'.repeat(MAX_SCRIPT_SIZE)
|
|
121
|
-
expect(() => validateOptions({ script: maxScript })).not.toThrow()
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
describe('module validation', () => {
|
|
126
|
-
it('rejects non-string module', () => {
|
|
127
|
-
expect(() => validateOptions({ module: {} as unknown as string })).toThrow(ValidationError)
|
|
128
|
-
expect(() => validateOptions({ module: {} as unknown as string })).toThrow(
|
|
129
|
-
'module must be a string'
|
|
130
|
-
)
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it('rejects module exceeding size limit', () => {
|
|
134
|
-
const largeModule = 'a'.repeat(MAX_SCRIPT_SIZE + 1)
|
|
135
|
-
expect(() => validateOptions({ module: largeModule })).toThrow(ValidationError)
|
|
136
|
-
expect(() => validateOptions({ module: largeModule })).toThrow(
|
|
137
|
-
/module size.*exceeds maximum allowed size/
|
|
138
|
-
)
|
|
139
|
-
})
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
describe('tests validation', () => {
|
|
143
|
-
it('rejects non-string tests', () => {
|
|
144
|
-
expect(() => validateOptions({ tests: [] as unknown as string })).toThrow(ValidationError)
|
|
145
|
-
expect(() => validateOptions({ tests: [] as unknown as string })).toThrow(
|
|
146
|
-
'tests must be a string'
|
|
147
|
-
)
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
it('rejects tests exceeding size limit', () => {
|
|
151
|
-
const largeTests = 'a'.repeat(MAX_SCRIPT_SIZE + 1)
|
|
152
|
-
expect(() => validateOptions({ tests: largeTests })).toThrow(ValidationError)
|
|
153
|
-
expect(() => validateOptions({ tests: largeTests })).toThrow(
|
|
154
|
-
/tests size.*exceeds maximum allowed size/
|
|
155
|
-
)
|
|
156
|
-
})
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
describe('imports validation', () => {
|
|
160
|
-
it('rejects non-array imports', () => {
|
|
161
|
-
expect(() =>
|
|
162
|
-
validateOptions({ imports: 'https://esm.sh/lodash' as unknown as string[] })
|
|
163
|
-
).toThrow(ValidationError)
|
|
164
|
-
expect(() =>
|
|
165
|
-
validateOptions({ imports: 'https://esm.sh/lodash' as unknown as string[] })
|
|
166
|
-
).toThrow('imports must be an array')
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
it('rejects imports exceeding count limit', () => {
|
|
170
|
-
const tooManyImports = Array(MAX_IMPORTS + 1).fill('https://esm.sh/lodash')
|
|
171
|
-
expect(() => validateOptions({ imports: tooManyImports })).toThrow(ValidationError)
|
|
172
|
-
expect(() => validateOptions({ imports: tooManyImports })).toThrow(
|
|
173
|
-
`imports count (${MAX_IMPORTS + 1}) exceeds maximum allowed count of ${MAX_IMPORTS}`
|
|
174
|
-
)
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
it('accepts imports at exact count limit', () => {
|
|
178
|
-
const maxImports = Array(MAX_IMPORTS).fill('https://esm.sh/lodash')
|
|
179
|
-
expect(() => validateOptions({ imports: maxImports })).not.toThrow()
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
it('rejects non-string import entries', () => {
|
|
183
|
-
expect(() => validateOptions({ imports: [123 as unknown as string] })).toThrow(
|
|
184
|
-
ValidationError
|
|
185
|
-
)
|
|
186
|
-
expect(() => validateOptions({ imports: [123 as unknown as string] })).toThrow(
|
|
187
|
-
'imports[0] must be a string'
|
|
188
|
-
)
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('rejects invalid URLs', () => {
|
|
192
|
-
expect(() => validateOptions({ imports: ['not-a-url'] })).toThrow(ValidationError)
|
|
193
|
-
expect(() => validateOptions({ imports: ['not-a-url'] })).toThrow(
|
|
194
|
-
'imports[0] is not a valid URL: not-a-url'
|
|
195
|
-
)
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
it('rejects non-http/https URLs', () => {
|
|
199
|
-
expect(() => validateOptions({ imports: ['file:///etc/passwd'] })).toThrow(ValidationError)
|
|
200
|
-
expect(() => validateOptions({ imports: ['file:///etc/passwd'] })).toThrow(
|
|
201
|
-
'imports[0] is not a valid URL: file:///etc/passwd'
|
|
202
|
-
)
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
it('accepts both http and https URLs', () => {
|
|
206
|
-
expect(() =>
|
|
207
|
-
validateOptions({
|
|
208
|
-
imports: ['https://esm.sh/lodash', 'http://localhost:3000/module.js'],
|
|
209
|
-
})
|
|
210
|
-
).not.toThrow()
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
it('reports correct index for invalid import', () => {
|
|
214
|
-
expect(() =>
|
|
215
|
-
validateOptions({
|
|
216
|
-
imports: ['https://esm.sh/lodash', 'invalid-url', 'https://esm.sh/react'],
|
|
217
|
-
})
|
|
218
|
-
).toThrow('imports[1] is not a valid URL: invalid-url')
|
|
219
|
-
})
|
|
220
|
-
})
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
describe('ValidationError', () => {
|
|
224
|
-
it('is an instance of Error', () => {
|
|
225
|
-
const error = new ValidationError('test message')
|
|
226
|
-
expect(error).toBeInstanceOf(Error)
|
|
227
|
-
expect(error).toBeInstanceOf(ValidationError)
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
it('has correct name', () => {
|
|
231
|
-
const error = new ValidationError('test message')
|
|
232
|
-
expect(error.name).toBe('ValidationError')
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
it('has correct message', () => {
|
|
236
|
-
const error = new ValidationError('test message')
|
|
237
|
-
expect(error.message).toBe('test message')
|
|
238
|
-
})
|
|
239
|
-
})
|
|
240
|
-
})
|