@ai-sdk/provider-utils 4.0.8 → 4.0.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/CHANGELOG.md +14 -0
- package/dist/index.js +29 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +29 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -2
- package/src/handle-fetch-error.ts +33 -0
- package/src/__snapshots__/schema.test.ts.snap +0 -346
- package/src/add-additional-properties-to-json-schema.test.ts +0 -289
- package/src/convert-async-iterator-to-readable-stream.test.ts +0 -78
- package/src/convert-image-model-file-to-data-uri.test.ts +0 -85
- package/src/convert-to-form-data.test.ts +0 -167
- package/src/create-tool-name-mapping.test.ts +0 -163
- package/src/delay.test.ts +0 -212
- package/src/delayed-promise.test.ts +0 -132
- package/src/download-blob.test.ts +0 -145
- package/src/generate-id.test.ts +0 -31
- package/src/get-from-api.test.ts +0 -199
- package/src/get-runtime-environment-user-agent.test.ts +0 -47
- package/src/inject-json-instruction.test.ts +0 -404
- package/src/is-url-supported.test.ts +0 -282
- package/src/media-type-to-extension.test.ts +0 -26
- package/src/normalize-headers.test.ts +0 -64
- package/src/parse-json.test.ts +0 -191
- package/src/remove-undefined-entries.test.ts +0 -57
- package/src/resolve.test.ts +0 -125
- package/src/response-handler.test.ts +0 -89
- package/src/schema.test-d.ts +0 -11
- package/src/schema.test.ts +0 -502
- package/src/secure-json-parse.test.ts +0 -59
- package/src/to-json-schema/zod3-to-json-schema/parse-def.test.ts +0 -224
- package/src/to-json-schema/zod3-to-json-schema/parsers/array.test.ts +0 -98
- package/src/to-json-schema/zod3-to-json-schema/parsers/bigint.test.ts +0 -51
- package/src/to-json-schema/zod3-to-json-schema/parsers/branded.test.ts +0 -16
- package/src/to-json-schema/zod3-to-json-schema/parsers/catch.test.ts +0 -15
- package/src/to-json-schema/zod3-to-json-schema/parsers/date.test.ts +0 -97
- package/src/to-json-schema/zod3-to-json-schema/parsers/default.test.ts +0 -54
- package/src/to-json-schema/zod3-to-json-schema/parsers/effects.test.ts +0 -41
- package/src/to-json-schema/zod3-to-json-schema/parsers/intersection.test.ts +0 -92
- package/src/to-json-schema/zod3-to-json-schema/parsers/map.test.ts +0 -48
- package/src/to-json-schema/zod3-to-json-schema/parsers/native-enum.test.ts +0 -102
- package/src/to-json-schema/zod3-to-json-schema/parsers/nullable.test.ts +0 -67
- package/src/to-json-schema/zod3-to-json-schema/parsers/number.test.ts +0 -65
- package/src/to-json-schema/zod3-to-json-schema/parsers/object.test.ts +0 -149
- package/src/to-json-schema/zod3-to-json-schema/parsers/optional.test.ts +0 -147
- package/src/to-json-schema/zod3-to-json-schema/parsers/pipe.test.ts +0 -35
- package/src/to-json-schema/zod3-to-json-schema/parsers/promise.test.ts +0 -15
- package/src/to-json-schema/zod3-to-json-schema/parsers/readonly.test.ts +0 -20
- package/src/to-json-schema/zod3-to-json-schema/parsers/record.test.ts +0 -108
- package/src/to-json-schema/zod3-to-json-schema/parsers/set.test.ts +0 -20
- package/src/to-json-schema/zod3-to-json-schema/parsers/string.test.ts +0 -438
- package/src/to-json-schema/zod3-to-json-schema/parsers/tuple.test.ts +0 -33
- package/src/to-json-schema/zod3-to-json-schema/parsers/union.test.ts +0 -226
- package/src/to-json-schema/zod3-to-json-schema/refs.test.ts +0 -919
- package/src/to-json-schema/zod3-to-json-schema/zod3-to-json-schema.test.ts +0 -862
- package/src/types/tool.test-d.ts +0 -228
- package/src/validate-types.test.ts +0 -105
- package/src/with-user-agent-suffix.test.ts +0 -84
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
import { JSONSchema7, LanguageModelV3Prompt } from '@ai-sdk/provider';
|
|
2
|
-
import { expect, it, describe } from 'vitest';
|
|
3
|
-
import {
|
|
4
|
-
injectJsonInstruction,
|
|
5
|
-
injectJsonInstructionIntoMessages,
|
|
6
|
-
} from './inject-json-instruction';
|
|
7
|
-
|
|
8
|
-
const basicSchema: JSONSchema7 = {
|
|
9
|
-
type: 'object',
|
|
10
|
-
properties: {
|
|
11
|
-
name: { type: 'string' },
|
|
12
|
-
age: { type: 'number' },
|
|
13
|
-
},
|
|
14
|
-
required: ['name', 'age'],
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
describe('injectJsonInstruction', () => {
|
|
18
|
-
it('should handle basic case with prompt and schema', () => {
|
|
19
|
-
const result = injectJsonInstruction({
|
|
20
|
-
prompt: 'Generate a person',
|
|
21
|
-
schema: basicSchema,
|
|
22
|
-
});
|
|
23
|
-
expect(result).toBe(
|
|
24
|
-
'Generate a person\n\n' +
|
|
25
|
-
'JSON schema:\n' +
|
|
26
|
-
'{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}\n' +
|
|
27
|
-
'You MUST answer with a JSON object that matches the JSON schema above.',
|
|
28
|
-
);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('should handle only prompt, no schema', () => {
|
|
32
|
-
const result = injectJsonInstruction({
|
|
33
|
-
prompt: 'Generate a person',
|
|
34
|
-
});
|
|
35
|
-
expect(result).toBe('Generate a person\n\nYou MUST answer with JSON.');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should handle only schema, no prompt', () => {
|
|
39
|
-
const result = injectJsonInstruction({
|
|
40
|
-
schema: basicSchema,
|
|
41
|
-
});
|
|
42
|
-
expect(result).toBe(
|
|
43
|
-
'JSON schema:\n' +
|
|
44
|
-
'{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}\n' +
|
|
45
|
-
'You MUST answer with a JSON object that matches the JSON schema above.',
|
|
46
|
-
);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should handle no prompt, no schema', () => {
|
|
50
|
-
const result = injectJsonInstruction({});
|
|
51
|
-
expect(result).toBe('You MUST answer with JSON.');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should handle custom schemaPrefix and schemaSuffix', () => {
|
|
55
|
-
const result = injectJsonInstruction({
|
|
56
|
-
prompt: 'Generate a person',
|
|
57
|
-
schema: basicSchema,
|
|
58
|
-
schemaPrefix: 'Custom prefix:',
|
|
59
|
-
schemaSuffix: 'Custom suffix',
|
|
60
|
-
});
|
|
61
|
-
expect(result).toBe(
|
|
62
|
-
'Generate a person\n\n' +
|
|
63
|
-
'Custom prefix:\n' +
|
|
64
|
-
'{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}\n' +
|
|
65
|
-
'Custom suffix',
|
|
66
|
-
);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should handle empty string prompt', () => {
|
|
70
|
-
const result = injectJsonInstruction({
|
|
71
|
-
prompt: '',
|
|
72
|
-
schema: basicSchema,
|
|
73
|
-
});
|
|
74
|
-
expect(result).toBe(
|
|
75
|
-
'JSON schema:\n' +
|
|
76
|
-
'{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}\n' +
|
|
77
|
-
'You MUST answer with a JSON object that matches the JSON schema above.',
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('should handle empty object schema', () => {
|
|
82
|
-
const result = injectJsonInstruction({
|
|
83
|
-
prompt: 'Generate something',
|
|
84
|
-
schema: {},
|
|
85
|
-
});
|
|
86
|
-
expect(result).toBe(
|
|
87
|
-
'Generate something\n\n' +
|
|
88
|
-
'JSON schema:\n' +
|
|
89
|
-
'{}\n' +
|
|
90
|
-
'You MUST answer with a JSON object that matches the JSON schema above.',
|
|
91
|
-
);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('should handle complex nested schema', () => {
|
|
95
|
-
const complexSchema: JSONSchema7 = {
|
|
96
|
-
type: 'object',
|
|
97
|
-
properties: {
|
|
98
|
-
person: {
|
|
99
|
-
type: 'object',
|
|
100
|
-
properties: {
|
|
101
|
-
name: { type: 'string' },
|
|
102
|
-
age: { type: 'number' },
|
|
103
|
-
address: {
|
|
104
|
-
type: 'object',
|
|
105
|
-
properties: {
|
|
106
|
-
street: { type: 'string' },
|
|
107
|
-
city: { type: 'string' },
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
const result = injectJsonInstruction({
|
|
115
|
-
prompt: 'Generate a complex person',
|
|
116
|
-
schema: complexSchema,
|
|
117
|
-
});
|
|
118
|
-
expect(result).toBe(
|
|
119
|
-
'Generate a complex person\n\n' +
|
|
120
|
-
'JSON schema:\n' +
|
|
121
|
-
'{"type":"object","properties":{"person":{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"},"address":{"type":"object","properties":{"street":{"type":"string"},"city":{"type":"string"}}}}}}}\n' +
|
|
122
|
-
'You MUST answer with a JSON object that matches the JSON schema above.',
|
|
123
|
-
);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('should handle schema with special characters', () => {
|
|
127
|
-
const specialSchema: JSONSchema7 = {
|
|
128
|
-
type: 'object',
|
|
129
|
-
properties: {
|
|
130
|
-
'special@property': { type: 'string' },
|
|
131
|
-
'emoji😊': { type: 'string' },
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
const result = injectJsonInstruction({
|
|
135
|
-
schema: specialSchema,
|
|
136
|
-
});
|
|
137
|
-
expect(result).toBe(
|
|
138
|
-
'JSON schema:\n' +
|
|
139
|
-
'{"type":"object","properties":{"special@property":{"type":"string"},"emoji😊":{"type":"string"}}}\n' +
|
|
140
|
-
'You MUST answer with a JSON object that matches the JSON schema above.',
|
|
141
|
-
);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('should handle very long prompt and schema', () => {
|
|
145
|
-
const longPrompt = 'A'.repeat(1000);
|
|
146
|
-
const longSchema: JSONSchema7 = {
|
|
147
|
-
type: 'object',
|
|
148
|
-
properties: {},
|
|
149
|
-
};
|
|
150
|
-
for (let i = 0; i < 100; i++) {
|
|
151
|
-
longSchema.properties![`prop${i}`] = { type: 'string' };
|
|
152
|
-
}
|
|
153
|
-
const result = injectJsonInstruction({
|
|
154
|
-
prompt: longPrompt,
|
|
155
|
-
schema: longSchema,
|
|
156
|
-
});
|
|
157
|
-
expect(result).toBe(
|
|
158
|
-
longPrompt +
|
|
159
|
-
'\n\n' +
|
|
160
|
-
'JSON schema:\n' +
|
|
161
|
-
JSON.stringify(longSchema) +
|
|
162
|
-
'\n' +
|
|
163
|
-
'You MUST answer with a JSON object that matches the JSON schema above.',
|
|
164
|
-
);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('should handle null values for optional parameters', () => {
|
|
168
|
-
const result = injectJsonInstruction({
|
|
169
|
-
prompt: null as any,
|
|
170
|
-
schema: null as any,
|
|
171
|
-
schemaPrefix: null as any,
|
|
172
|
-
schemaSuffix: null as any,
|
|
173
|
-
});
|
|
174
|
-
expect(result).toBe('');
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('should handle undefined values for optional parameters', () => {
|
|
178
|
-
const result = injectJsonInstruction({
|
|
179
|
-
prompt: undefined,
|
|
180
|
-
schema: undefined,
|
|
181
|
-
schemaPrefix: undefined,
|
|
182
|
-
schemaSuffix: undefined,
|
|
183
|
-
});
|
|
184
|
-
expect(result).toBe('You MUST answer with JSON.');
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe('injectJsonInstructionIntoMessages', () => {
|
|
189
|
-
it('should handle basic case with prompt and schema', () => {
|
|
190
|
-
const result = injectJsonInstructionIntoMessages({
|
|
191
|
-
messages: [{ role: 'system', content: 'Generate a person' }],
|
|
192
|
-
schema: basicSchema,
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
expect(result).toMatchInlineSnapshot(`
|
|
196
|
-
[
|
|
197
|
-
{
|
|
198
|
-
"content": "Generate a person
|
|
199
|
-
|
|
200
|
-
JSON schema:
|
|
201
|
-
{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}
|
|
202
|
-
You MUST answer with a JSON object that matches the JSON schema above.",
|
|
203
|
-
"role": "system",
|
|
204
|
-
},
|
|
205
|
-
]
|
|
206
|
-
`);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it('should not mutate the input messages array', () => {
|
|
210
|
-
const originalMessages: LanguageModelV3Prompt = [
|
|
211
|
-
{ role: 'system', content: 'Generate a person' },
|
|
212
|
-
];
|
|
213
|
-
|
|
214
|
-
injectJsonInstructionIntoMessages({
|
|
215
|
-
messages: originalMessages,
|
|
216
|
-
schema: basicSchema,
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
expect(originalMessages).toEqual([
|
|
220
|
-
{ role: 'system', content: 'Generate a person' },
|
|
221
|
-
]);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it('should handle empty messages array', () => {
|
|
225
|
-
const result = injectJsonInstructionIntoMessages({
|
|
226
|
-
messages: [],
|
|
227
|
-
schema: basicSchema,
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
expect(result).toMatchInlineSnapshot(`
|
|
231
|
-
[
|
|
232
|
-
{
|
|
233
|
-
"content": "JSON schema:
|
|
234
|
-
{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}
|
|
235
|
-
You MUST answer with a JSON object that matches the JSON schema above.",
|
|
236
|
-
"role": "system",
|
|
237
|
-
},
|
|
238
|
-
]
|
|
239
|
-
`);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('should handle messages without initial system message', () => {
|
|
243
|
-
const result = injectJsonInstructionIntoMessages({
|
|
244
|
-
messages: [
|
|
245
|
-
{ role: 'user', content: [{ type: 'text', text: 'Hello' }] },
|
|
246
|
-
{
|
|
247
|
-
role: 'assistant',
|
|
248
|
-
content: [{ type: 'text', text: 'Hi there' }],
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
schema: basicSchema,
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
expect(result).toMatchInlineSnapshot(`
|
|
255
|
-
[
|
|
256
|
-
{
|
|
257
|
-
"content": "JSON schema:
|
|
258
|
-
{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}
|
|
259
|
-
You MUST answer with a JSON object that matches the JSON schema above.",
|
|
260
|
-
"role": "system",
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
"content": [
|
|
264
|
-
{
|
|
265
|
-
"text": "Hello",
|
|
266
|
-
"type": "text",
|
|
267
|
-
},
|
|
268
|
-
],
|
|
269
|
-
"role": "user",
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
"content": [
|
|
273
|
-
{
|
|
274
|
-
"text": "Hi there",
|
|
275
|
-
"type": "text",
|
|
276
|
-
},
|
|
277
|
-
],
|
|
278
|
-
"role": "assistant",
|
|
279
|
-
},
|
|
280
|
-
]
|
|
281
|
-
`);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
it('should handle system message with empty content', () => {
|
|
285
|
-
const result = injectJsonInstructionIntoMessages({
|
|
286
|
-
messages: [
|
|
287
|
-
{ role: 'system', content: '' },
|
|
288
|
-
{ role: 'user', content: [{ type: 'text', text: 'Generate data' }] },
|
|
289
|
-
],
|
|
290
|
-
schema: basicSchema,
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
expect(result).toMatchInlineSnapshot(`
|
|
294
|
-
[
|
|
295
|
-
{
|
|
296
|
-
"content": "JSON schema:
|
|
297
|
-
{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}
|
|
298
|
-
You MUST answer with a JSON object that matches the JSON schema above.",
|
|
299
|
-
"role": "system",
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
"content": [
|
|
303
|
-
{
|
|
304
|
-
"text": "Generate data",
|
|
305
|
-
"type": "text",
|
|
306
|
-
},
|
|
307
|
-
],
|
|
308
|
-
"role": "user",
|
|
309
|
-
},
|
|
310
|
-
]
|
|
311
|
-
`);
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it('should preserve all non-system messages', () => {
|
|
315
|
-
const result = injectJsonInstructionIntoMessages({
|
|
316
|
-
messages: [
|
|
317
|
-
{ role: 'system', content: 'You are helpful' },
|
|
318
|
-
{ role: 'user', content: [{ type: 'text', text: 'Hello' }] },
|
|
319
|
-
{ role: 'assistant', content: [{ type: 'text', text: 'Hi' }] },
|
|
320
|
-
{ role: 'user', content: [{ type: 'text', text: 'Generate person' }] },
|
|
321
|
-
],
|
|
322
|
-
schema: basicSchema,
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
expect(result).toMatchInlineSnapshot(`
|
|
326
|
-
[
|
|
327
|
-
{
|
|
328
|
-
"content": "You are helpful
|
|
329
|
-
|
|
330
|
-
JSON schema:
|
|
331
|
-
{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}
|
|
332
|
-
You MUST answer with a JSON object that matches the JSON schema above.",
|
|
333
|
-
"role": "system",
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
"content": [
|
|
337
|
-
{
|
|
338
|
-
"text": "Hello",
|
|
339
|
-
"type": "text",
|
|
340
|
-
},
|
|
341
|
-
],
|
|
342
|
-
"role": "user",
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
"content": [
|
|
346
|
-
{
|
|
347
|
-
"text": "Hi",
|
|
348
|
-
"type": "text",
|
|
349
|
-
},
|
|
350
|
-
],
|
|
351
|
-
"role": "assistant",
|
|
352
|
-
},
|
|
353
|
-
{
|
|
354
|
-
"content": [
|
|
355
|
-
{
|
|
356
|
-
"text": "Generate person",
|
|
357
|
-
"type": "text",
|
|
358
|
-
},
|
|
359
|
-
],
|
|
360
|
-
"role": "user",
|
|
361
|
-
},
|
|
362
|
-
]
|
|
363
|
-
`);
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it('should handle case with no schema', () => {
|
|
367
|
-
const result = injectJsonInstructionIntoMessages({
|
|
368
|
-
messages: [{ role: 'system', content: 'Generate data' }],
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
expect(result).toMatchInlineSnapshot(`
|
|
372
|
-
[
|
|
373
|
-
{
|
|
374
|
-
"content": "Generate data
|
|
375
|
-
|
|
376
|
-
You MUST answer with JSON.",
|
|
377
|
-
"role": "system",
|
|
378
|
-
},
|
|
379
|
-
]
|
|
380
|
-
`);
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it('should handle custom schema prefix and suffix', () => {
|
|
384
|
-
const result = injectJsonInstructionIntoMessages({
|
|
385
|
-
messages: [{ role: 'system', content: 'Generate data' }],
|
|
386
|
-
schema: basicSchema,
|
|
387
|
-
schemaPrefix: 'Custom schema:',
|
|
388
|
-
schemaSuffix: 'Follow this format exactly.',
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
expect(result).toMatchInlineSnapshot(`
|
|
392
|
-
[
|
|
393
|
-
{
|
|
394
|
-
"content": "Generate data
|
|
395
|
-
|
|
396
|
-
Custom schema:
|
|
397
|
-
{"type":"object","properties":{"name":{"type":"string"},"age":{"type":"number"}},"required":["name","age"]}
|
|
398
|
-
Follow this format exactly.",
|
|
399
|
-
"role": "system",
|
|
400
|
-
},
|
|
401
|
-
]
|
|
402
|
-
`);
|
|
403
|
-
});
|
|
404
|
-
});
|
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import { isUrlSupported } from './is-url-supported';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
|
|
4
|
-
describe('isUrlSupported', () => {
|
|
5
|
-
describe('when the model does not support any URLs', () => {
|
|
6
|
-
it('should return false', async () => {
|
|
7
|
-
expect(
|
|
8
|
-
isUrlSupported({
|
|
9
|
-
mediaType: 'text/plain',
|
|
10
|
-
url: 'https://example.com',
|
|
11
|
-
supportedUrls: {},
|
|
12
|
-
}),
|
|
13
|
-
).toBe(false);
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
describe('when the model supports specific media types and URLs', () => {
|
|
18
|
-
it('should return true for exact media type and exact URL match', async () => {
|
|
19
|
-
expect(
|
|
20
|
-
isUrlSupported({
|
|
21
|
-
mediaType: 'text/plain',
|
|
22
|
-
url: 'https://example.com',
|
|
23
|
-
supportedUrls: { 'text/plain': [/https:\/\/example\.com/] },
|
|
24
|
-
}),
|
|
25
|
-
).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should return true for exact media type and regex URL match', async () => {
|
|
29
|
-
expect(
|
|
30
|
-
isUrlSupported({
|
|
31
|
-
mediaType: 'image/png',
|
|
32
|
-
url: 'https://images.example.com/cat.png',
|
|
33
|
-
supportedUrls: {
|
|
34
|
-
'image/png': [/https:\/\/images\.example\.com\/.+/],
|
|
35
|
-
},
|
|
36
|
-
}),
|
|
37
|
-
).toBe(true);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should return true for exact media type and one of multiple regex URLs match', async () => {
|
|
41
|
-
expect(
|
|
42
|
-
isUrlSupported({
|
|
43
|
-
mediaType: 'image/png',
|
|
44
|
-
url: 'https://another.com/img.png',
|
|
45
|
-
supportedUrls: {
|
|
46
|
-
'image/png': [
|
|
47
|
-
/https:\/\/images\.example\.com\/.+/,
|
|
48
|
-
/https:\/\/another\.com\/img\.png/,
|
|
49
|
-
],
|
|
50
|
-
},
|
|
51
|
-
}),
|
|
52
|
-
).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should return false for exact media type but URL mismatch', async () => {
|
|
56
|
-
expect(
|
|
57
|
-
isUrlSupported({
|
|
58
|
-
mediaType: 'text/plain',
|
|
59
|
-
url: 'https://another.com',
|
|
60
|
-
supportedUrls: { 'text/plain': [/https:\/\/example\.com/] },
|
|
61
|
-
}),
|
|
62
|
-
).toBe(false);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should return false for URL match but media type mismatch', async () => {
|
|
66
|
-
expect(
|
|
67
|
-
isUrlSupported({
|
|
68
|
-
mediaType: 'image/png', // Different media type
|
|
69
|
-
url: 'https://example.com',
|
|
70
|
-
supportedUrls: { 'text/plain': [/https:\/\/example\.com/] },
|
|
71
|
-
}),
|
|
72
|
-
).toBe(false);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe('when the model supports URLs via wildcard media type (*)', () => {
|
|
77
|
-
it('should return true for wildcard media type and exact URL match', async () => {
|
|
78
|
-
expect(
|
|
79
|
-
isUrlSupported({
|
|
80
|
-
mediaType: 'text/plain',
|
|
81
|
-
url: 'https://example.com',
|
|
82
|
-
supportedUrls: { '*': [/https:\/\/example\.com/] },
|
|
83
|
-
}),
|
|
84
|
-
).toBe(true);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('should return true for wildcard media type and regex URL match', async () => {
|
|
88
|
-
expect(
|
|
89
|
-
isUrlSupported({
|
|
90
|
-
mediaType: 'image/jpeg',
|
|
91
|
-
url: 'https://images.example.com/dog.jpg',
|
|
92
|
-
supportedUrls: { '*': [/https:\/\/images\.example\.com\/.+/] },
|
|
93
|
-
}),
|
|
94
|
-
).toBe(true);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('should return false for wildcard media type but URL mismatch', async () => {
|
|
98
|
-
expect(
|
|
99
|
-
isUrlSupported({
|
|
100
|
-
mediaType: 'video/mp4',
|
|
101
|
-
url: 'https://another.com',
|
|
102
|
-
supportedUrls: { '*': [/https:\/\/example\.com/] },
|
|
103
|
-
}),
|
|
104
|
-
).toBe(false);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe('when both specific and wildcard media types are defined', () => {
|
|
109
|
-
const supportedUrls = {
|
|
110
|
-
'text/plain': [/https:\/\/text\.com/],
|
|
111
|
-
'*': [/https:\/\/any\.com/],
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
it('should return true if URL matches under specific media type', async () => {
|
|
115
|
-
expect(
|
|
116
|
-
isUrlSupported({
|
|
117
|
-
mediaType: 'text/plain',
|
|
118
|
-
url: 'https://text.com',
|
|
119
|
-
supportedUrls,
|
|
120
|
-
}),
|
|
121
|
-
).toBe(true);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should return true if URL matches under wildcard media type even if specific exists', async () => {
|
|
125
|
-
// Assumes the logic checks specific first, then falls back to wildcard
|
|
126
|
-
expect(
|
|
127
|
-
isUrlSupported({
|
|
128
|
-
mediaType: 'text/plain', // Specific type exists
|
|
129
|
-
url: 'https://any.com', // Matches wildcard
|
|
130
|
-
supportedUrls,
|
|
131
|
-
}),
|
|
132
|
-
).toBe(true);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should return true if URL matches under wildcard for a non-specified media type', async () => {
|
|
136
|
-
expect(
|
|
137
|
-
isUrlSupported({
|
|
138
|
-
mediaType: 'image/png', // No specific entry for this type
|
|
139
|
-
url: 'https://any.com', // Matches wildcard
|
|
140
|
-
supportedUrls,
|
|
141
|
-
}),
|
|
142
|
-
).toBe(true);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('should return false if URL matches neither specific nor wildcard', async () => {
|
|
146
|
-
expect(
|
|
147
|
-
isUrlSupported({
|
|
148
|
-
mediaType: 'text/plain',
|
|
149
|
-
url: 'https://other.com',
|
|
150
|
-
supportedUrls,
|
|
151
|
-
}),
|
|
152
|
-
).toBe(false);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('should return false if URL does not match wildcard for a non-specified media type', async () => {
|
|
156
|
-
expect(
|
|
157
|
-
isUrlSupported({
|
|
158
|
-
mediaType: 'image/png',
|
|
159
|
-
url: 'https://other.com',
|
|
160
|
-
supportedUrls,
|
|
161
|
-
}),
|
|
162
|
-
).toBe(false);
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
describe('edge cases', () => {
|
|
167
|
-
it('should return true if an empty URL matches a pattern', async () => {
|
|
168
|
-
expect(
|
|
169
|
-
isUrlSupported({
|
|
170
|
-
mediaType: 'text/plain',
|
|
171
|
-
url: '',
|
|
172
|
-
supportedUrls: { 'text/plain': [/.*/] }, // Matches any string, including empty
|
|
173
|
-
}),
|
|
174
|
-
).toBe(true);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('should return false if an empty URL does not match a pattern', async () => {
|
|
178
|
-
expect(
|
|
179
|
-
isUrlSupported({
|
|
180
|
-
mediaType: 'text/plain',
|
|
181
|
-
url: '',
|
|
182
|
-
supportedUrls: { 'text/plain': [/https:\/\/.+/] }, // Requires non-empty string
|
|
183
|
-
}),
|
|
184
|
-
).toBe(false);
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe('case sensitivity', () => {
|
|
189
|
-
it('should be case-insensitive for media types', async () => {
|
|
190
|
-
expect(
|
|
191
|
-
isUrlSupported({
|
|
192
|
-
mediaType: 'TEXT/PLAIN', // Uppercase
|
|
193
|
-
url: 'https://example.com',
|
|
194
|
-
supportedUrls: { 'text/plain': [/https:\/\/example\.com/] }, // Lowercase
|
|
195
|
-
}),
|
|
196
|
-
).toBe(true);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it('should handle case-insensitive regex for URLs if specified', async () => {
|
|
200
|
-
expect(
|
|
201
|
-
isUrlSupported({
|
|
202
|
-
mediaType: 'text/plain',
|
|
203
|
-
url: 'https://EXAMPLE.com/path', // Uppercase domain
|
|
204
|
-
supportedUrls: { 'text/plain': [/https:\/\/example\.com\/path/] },
|
|
205
|
-
}),
|
|
206
|
-
).toBe(true);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it('should be case-insensitive for URL paths by default regex', async () => {
|
|
210
|
-
expect(
|
|
211
|
-
isUrlSupported({
|
|
212
|
-
mediaType: 'text/plain',
|
|
213
|
-
url: 'https://example.com/PATH', // Uppercase path
|
|
214
|
-
supportedUrls: { 'text/plain': [/https:\/\/example\.com\/path/] }, // Lowercase path in regex
|
|
215
|
-
}),
|
|
216
|
-
).toBe(true);
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
describe('wildcard subtypes in media types', () => {
|
|
221
|
-
it('should return true for wildcard subtype match', async () => {
|
|
222
|
-
expect(
|
|
223
|
-
isUrlSupported({
|
|
224
|
-
mediaType: 'image/png',
|
|
225
|
-
url: 'https://example.com',
|
|
226
|
-
supportedUrls: { 'image/*': [/https:\/\/example\.com/] },
|
|
227
|
-
}),
|
|
228
|
-
).toBe(true);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('should use full wildcard "*" if subtype wildcard is not matched or supported', async () => {
|
|
232
|
-
expect(
|
|
233
|
-
isUrlSupported({
|
|
234
|
-
mediaType: 'image/png',
|
|
235
|
-
url: 'https://any.com',
|
|
236
|
-
supportedUrls: {
|
|
237
|
-
'image/*': [/https:\/\/images\.com/], // Doesn't match URL
|
|
238
|
-
'*': [/https:\/\/any\.com/], // Matches URL
|
|
239
|
-
},
|
|
240
|
-
}),
|
|
241
|
-
).toBe(true);
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
describe('empty URL arrays for a media type', () => {
|
|
246
|
-
it('should return false if the specific media type has an empty URL array', async () => {
|
|
247
|
-
expect(
|
|
248
|
-
isUrlSupported({
|
|
249
|
-
mediaType: 'text/plain',
|
|
250
|
-
url: 'https://example.com',
|
|
251
|
-
supportedUrls: { 'text/plain': [] },
|
|
252
|
-
}),
|
|
253
|
-
).toBe(false);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('should fall back to wildcard "*" if specific media type has empty array but wildcard matches', async () => {
|
|
257
|
-
expect(
|
|
258
|
-
isUrlSupported({
|
|
259
|
-
mediaType: 'text/plain',
|
|
260
|
-
url: 'https://any.com',
|
|
261
|
-
supportedUrls: {
|
|
262
|
-
'text/plain': [],
|
|
263
|
-
'*': [/https:\/\/any\.com/],
|
|
264
|
-
},
|
|
265
|
-
}),
|
|
266
|
-
).toBe(true);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('should return false if specific media type has empty array and wildcard does not match', async () => {
|
|
270
|
-
expect(
|
|
271
|
-
isUrlSupported({
|
|
272
|
-
mediaType: 'text/plain',
|
|
273
|
-
url: 'https://another.com',
|
|
274
|
-
supportedUrls: {
|
|
275
|
-
'text/plain': [],
|
|
276
|
-
'*': [/https:\/\/any\.com/],
|
|
277
|
-
},
|
|
278
|
-
}),
|
|
279
|
-
).toBe(false);
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
});
|