Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/mcp/test/common/uriTemplate.test.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
7
import { UriTemplate } from '../../common/uriTemplate.js';
8
import * as assert from 'assert';
9
10
suite('UriTemplate', () => {
11
ensureNoDisposablesAreLeakedInTestSuite();
12
13
/**
14
* Helper function to test template parsing and component extraction
15
*/
16
function testParsing(template: string, expectedComponents: any[]) {
17
const templ = UriTemplate.parse(template);
18
assert.deepStrictEqual(templ.components.filter(c => typeof c === 'object'), expectedComponents);
19
return templ;
20
}
21
22
/**
23
* Helper function to test template resolution
24
*/
25
function testResolution(template: string, variables: Record<string, any>, expected: string) {
26
const templ = UriTemplate.parse(template);
27
const result = templ.resolve(variables);
28
assert.strictEqual(result, expected);
29
}
30
31
test('simple replacement', () => {
32
const templ = UriTemplate.parse('http://example.com/{var}');
33
assert.deepStrictEqual(templ.components, ['http://example.com/', {
34
expression: "{var}",
35
operator: '',
36
variables: [{ explodable: false, name: "var", optional: false, prefixLength: undefined, repeatable: false }]
37
}, '']);
38
const result = templ.resolve({ var: 'value' });
39
assert.strictEqual(result, 'http://example.com/value');
40
});
41
42
test('parsing components correctly', () => {
43
// Simple component
44
testParsing('http://example.com/{var}', [{
45
expression: "{var}",
46
operator: '',
47
variables: [{ explodable: false, name: "var", optional: false, prefixLength: undefined, repeatable: false }]
48
}]);
49
50
// Component with operator
51
testParsing('http://example.com/{+path}', [{
52
expression: "{+path}",
53
operator: '+',
54
variables: [{ explodable: false, name: "path", optional: false, prefixLength: undefined, repeatable: false }]
55
}]);
56
57
// Component with multiple variables
58
testParsing('http://example.com/{x,y}', [{
59
expression: "{x,y}",
60
operator: '',
61
variables: [
62
{ explodable: false, name: "x", optional: false, prefixLength: undefined, repeatable: false },
63
{ explodable: false, name: "y", optional: false, prefixLength: undefined, repeatable: false }
64
]
65
}]);
66
67
// Component with value modifiers
68
testParsing('http://example.com/{var:3}', [{
69
expression: "{var:3}",
70
operator: '',
71
variables: [{ explodable: false, name: "var", optional: false, prefixLength: 3, repeatable: false }]
72
}]);
73
74
testParsing('http://example.com/{list*}', [{
75
expression: "{list*}",
76
operator: '',
77
variables: [{ explodable: true, name: "list", optional: false, prefixLength: undefined, repeatable: true }]
78
}]);
79
80
// Multiple components
81
testParsing('http://example.com/{x}/path/{y}', [
82
{
83
expression: "{x}",
84
operator: '',
85
variables: [{ explodable: false, name: "x", optional: false, prefixLength: undefined, repeatable: false }]
86
},
87
{
88
expression: "{y}",
89
operator: '',
90
variables: [{ explodable: false, name: "y", optional: false, prefixLength: undefined, repeatable: false }]
91
}
92
]);
93
});
94
95
test('Level 1 - Simple string expansion', () => {
96
// Test cases from RFC 6570 Section 1.2
97
const variables = {
98
var: 'value',
99
hello: 'Hello World!'
100
};
101
102
testResolution('{var}', variables, 'value');
103
testResolution('{hello}', variables, 'Hello%20World%21');
104
});
105
106
test('Level 2 - Reserved expansion', () => {
107
// Test cases from RFC 6570 Section 1.2
108
const variables = {
109
var: 'value',
110
hello: 'Hello World!',
111
path: '/foo/bar'
112
};
113
114
testResolution('{+var}', variables, 'value');
115
testResolution('{+hello}', variables, 'Hello%20World!');
116
testResolution('{+path}/here', variables, '/foo/bar/here');
117
testResolution('here?ref={+path}', variables, 'here?ref=/foo/bar');
118
});
119
120
test('Level 2 - Fragment expansion', () => {
121
// Test cases from RFC 6570 Section 1.2
122
const variables = {
123
var: 'value',
124
hello: 'Hello World!'
125
};
126
127
testResolution('X{#var}', variables, 'X#value');
128
testResolution('X{#hello}', variables, 'X#Hello%20World!');
129
});
130
131
test('Level 3 - String expansion with multiple variables', () => {
132
// Test cases from RFC 6570 Section 1.2
133
const variables = {
134
var: 'value',
135
hello: 'Hello World!',
136
empty: '',
137
path: '/foo/bar',
138
x: '1024',
139
y: '768'
140
};
141
142
testResolution('map?{x,y}', variables, 'map?1024,768');
143
testResolution('{x,hello,y}', variables, '1024,Hello%20World%21,768');
144
});
145
146
test('Level 3 - Reserved expansion with multiple variables', () => {
147
// Test cases from RFC 6570 Section 1.2
148
const variables = {
149
var: 'value',
150
hello: 'Hello World!',
151
path: '/foo/bar',
152
x: '1024',
153
y: '768'
154
};
155
156
testResolution('{+x,hello,y}', variables, '1024,Hello%20World!,768');
157
testResolution('{+path,x}/here', variables, '/foo/bar,1024/here');
158
});
159
160
test('Level 3 - Fragment expansion with multiple variables', () => {
161
// Test cases from RFC 6570 Section 1.2
162
const variables = {
163
var: 'value',
164
hello: 'Hello World!',
165
path: '/foo/bar',
166
x: '1024',
167
y: '768'
168
};
169
170
testResolution('{#x,hello,y}', variables, '#1024,Hello%20World!,768');
171
testResolution('{#path,x}/here', variables, '#/foo/bar,1024/here');
172
});
173
174
test('Level 3 - Label expansion with dot-prefix', () => {
175
// Test cases from RFC 6570 Section 1.2
176
const variables = {
177
var: 'value',
178
x: '1024',
179
y: '768'
180
};
181
182
testResolution('X{.var}', variables, 'X.value');
183
testResolution('X{.x,y}', variables, 'X.1024.768');
184
});
185
186
test('Level 3 - Path segments expansion', () => {
187
// Test cases from RFC 6570 Section 1.2
188
const variables = {
189
var: 'value',
190
x: '1024'
191
};
192
193
testResolution('{/var}', variables, '/value');
194
testResolution('{/var,x}/here', variables, '/value/1024/here');
195
});
196
197
test('Level 3 - Path-style parameter expansion', () => {
198
// Test cases from RFC 6570 Section 1.2
199
const variables = {
200
x: '1024',
201
y: '768',
202
empty: ''
203
};
204
205
testResolution('{;x,y}', variables, ';x=1024;y=768');
206
testResolution('{;x,y,empty}', variables, ';x=1024;y=768;empty');
207
});
208
209
test('Level 3 - Form-style query expansion', () => {
210
// Test cases from RFC 6570 Section 1.2
211
const variables = {
212
x: '1024',
213
y: '768',
214
empty: ''
215
};
216
217
testResolution('{?x,y}', variables, '?x=1024&y=768');
218
testResolution('{?x,y,empty}', variables, '?x=1024&y=768&empty=');
219
});
220
221
test('Level 3 - Form-style query continuation', () => {
222
// Test cases from RFC 6570 Section 1.2
223
const variables = {
224
x: '1024',
225
y: '768',
226
empty: ''
227
};
228
229
testResolution('?fixed=yes{&x}', variables, '?fixed=yes&x=1024');
230
testResolution('{&x,y,empty}', variables, '&x=1024&y=768&empty=');
231
});
232
233
test('Level 4 - String expansion with value modifiers', () => {
234
// Test cases from RFC 6570 Section 1.2
235
const variables = {
236
var: 'value',
237
hello: 'Hello World!',
238
path: '/foo/bar',
239
list: ['red', 'green', 'blue'],
240
keys: {
241
semi: ';',
242
dot: '.',
243
comma: ','
244
}
245
};
246
247
testResolution('{var:3}', variables, 'val');
248
testResolution('{var:30}', variables, 'value');
249
testResolution('{list}', variables, 'red,green,blue');
250
testResolution('{list*}', variables, 'red,green,blue');
251
});
252
253
test('Level 4 - Reserved expansion with value modifiers', () => {
254
// Test cases related to Level 4 features
255
const variables = {
256
var: 'value',
257
hello: 'Hello World!',
258
path: '/foo/bar',
259
list: ['red', 'green', 'blue'],
260
keys: {
261
semi: ';',
262
dot: '.',
263
comma: ','
264
}
265
};
266
267
testResolution('{+path:6}/here', variables, '/foo/b/here');
268
testResolution('{+list}', variables, 'red,green,blue');
269
testResolution('{+list*}', variables, 'red,green,blue');
270
testResolution('{+keys}', variables, 'semi,;,dot,.,comma,,');
271
testResolution('{+keys*}', variables, 'semi=;,dot=.,comma=,');
272
});
273
274
test('Level 4 - Fragment expansion with value modifiers', () => {
275
// Test cases related to Level 4 features
276
const variables = {
277
var: 'value',
278
hello: 'Hello World!',
279
path: '/foo/bar',
280
list: ['red', 'green', 'blue'],
281
keys: {
282
semi: ';',
283
dot: '.',
284
comma: ','
285
}
286
};
287
288
testResolution('{#path:6}/here', variables, '#/foo/b/here');
289
testResolution('{#list}', variables, '#red,green,blue');
290
testResolution('{#list*}', variables, '#red,green,blue');
291
testResolution('{#keys}', variables, '#semi,;,dot,.,comma,,');
292
testResolution('{#keys*}', variables, '#semi=;,dot=.,comma=,');
293
});
294
295
test('Level 4 - Label expansion with value modifiers', () => {
296
// Test cases related to Level 4 features
297
const variables = {
298
var: 'value',
299
list: ['red', 'green', 'blue'],
300
keys: {
301
semi: ';',
302
dot: '.',
303
comma: ','
304
}
305
};
306
307
testResolution('X{.var:3}', variables, 'X.val');
308
testResolution('X{.list}', variables, 'X.red,green,blue');
309
testResolution('X{.list*}', variables, 'X.red.green.blue');
310
testResolution('X{.keys}', variables, 'X.semi,;,dot,.,comma,,');
311
testResolution('X{.keys*}', variables, 'X.semi=;.dot=..comma=,');
312
});
313
314
test('Level 4 - Path expansion with value modifiers', () => {
315
// Test cases related to Level 4 features
316
const variables = {
317
var: 'value',
318
list: ['red', 'green', 'blue'],
319
path: '/foo/bar',
320
keys: {
321
semi: ';',
322
dot: '.',
323
comma: ','
324
}
325
};
326
327
testResolution('{/var:1,var}', variables, '/v/value');
328
testResolution('{/list}', variables, '/red,green,blue');
329
testResolution('{/list*}', variables, '/red/green/blue');
330
testResolution('{/list*,path:4}', variables, '/red/green/blue/%2Ffoo');
331
testResolution('{/keys}', variables, '/semi,;,dot,.,comma,,');
332
testResolution('{/keys*}', variables, '/semi=%3B/dot=./comma=%2C');
333
});
334
335
test('Level 4 - Path-style parameters with value modifiers', () => {
336
// Test cases related to Level 4 features
337
const variables = {
338
var: 'value',
339
list: ['red', 'green', 'blue'],
340
keys: {
341
semi: ';',
342
dot: '.',
343
comma: ','
344
}
345
};
346
347
testResolution('{;hello:5}', { hello: 'Hello World!' }, ';hello=Hello');
348
testResolution('{;list}', variables, ';list=red,green,blue');
349
testResolution('{;list*}', variables, ';list=red;list=green;list=blue');
350
testResolution('{;keys}', variables, ';keys=semi,;,dot,.,comma,,');
351
testResolution('{;keys*}', variables, ';semi=;;dot=.;comma=,');
352
});
353
354
test('Level 4 - Form-style query with value modifiers', () => {
355
// Test cases related to Level 4 features
356
const variables = {
357
var: 'value',
358
list: ['red', 'green', 'blue'],
359
keys: {
360
semi: ';',
361
dot: '.',
362
comma: ','
363
}
364
};
365
366
testResolution('{?var:3}', variables, '?var=val');
367
testResolution('{?list}', variables, '?list=red,green,blue');
368
testResolution('{?list*}', variables, '?list=red&list=green&list=blue');
369
testResolution('{?keys}', variables, '?keys=semi,;,dot,.,comma,,');
370
testResolution('{?keys*}', variables, '?semi=;&dot=.&comma=,');
371
});
372
373
test('Level 4 - Form-style query continuation with value modifiers', () => {
374
// Test cases related to Level 4 features
375
const variables = {
376
var: 'value',
377
list: ['red', 'green', 'blue'],
378
keys: {
379
semi: ';',
380
dot: '.',
381
comma: ','
382
}
383
};
384
385
testResolution('?fixed=yes{&var:3}', variables, '?fixed=yes&var=val');
386
testResolution('?fixed=yes{&list}', variables, '?fixed=yes&list=red,green,blue');
387
testResolution('?fixed=yes{&list*}', variables, '?fixed=yes&list=red&list=green&list=blue');
388
testResolution('?fixed=yes{&keys}', variables, '?fixed=yes&keys=semi,;,dot,.,comma,,');
389
testResolution('?fixed=yes{&keys*}', variables, '?fixed=yes&semi=;&dot=.&comma=,');
390
});
391
392
test('handling undefined or null values', () => {
393
// Test handling of undefined/null values for different operators
394
const variables = {
395
defined: 'value',
396
undef: undefined,
397
null: null,
398
empty: ''
399
};
400
401
// Simple string expansion
402
testResolution('{defined,undef,null,empty}', variables, 'value,');
403
404
// Reserved expansion
405
testResolution('{+defined,undef,null,empty}', variables, 'value,');
406
407
// Fragment expansion
408
testResolution('{#defined,undef,null,empty}', variables, '#value,');
409
410
// Label expansion
411
testResolution('X{.defined,undef,null,empty}', variables, 'X.value');
412
413
// Path segments
414
testResolution('{/defined,undef,null}', variables, '/value');
415
416
// Path-style parameters
417
testResolution('{;defined,empty}', variables, ';defined=value;empty');
418
419
// Form-style query
420
testResolution('{?defined,undef,null,empty}', variables, '?defined=value&undef=&null=&empty=');
421
422
// Form-style query continuation
423
testResolution('{&defined,undef,null,empty}', variables, '&defined=value&undef=&null=&empty=');
424
});
425
426
test('complex templates', () => {
427
// Test more complex template combinations
428
const variables = {
429
domain: 'example.com',
430
user: 'fred',
431
path: ['path', 'to', 'resource'],
432
query: 'search',
433
page: 5,
434
lang: 'en',
435
sessionId: '123abc',
436
filters: ['color:blue', 'shape:square'],
437
coordinates: { lat: '37.7', lon: '-122.4' }
438
};
439
440
// RESTful URL pattern
441
testResolution('https://{domain}/api/v1/users/{user}{/path*}{?query,page,lang}',
442
variables,
443
'https://example.com/api/v1/users/fred/path/to/resource?query=search&page=5&lang=en');
444
445
// Complex query parameters
446
testResolution('https://{domain}/search{?query,filters,coordinates*}',
447
variables,
448
'https://example.com/search?query=search&filters=color:blue,shape:square&lat=37.7&lon=-122.4');
449
450
// Multiple expression types
451
testResolution('https://{domain}/users/{user}/profile{.lang}{?sessionId}{#path}',
452
variables,
453
'https://example.com/users/fred/profile.en?sessionId=123abc#path,to,resource');
454
});
455
456
test('literals and escaping', () => {
457
// Test literal segments and escaping
458
testParsing('http://example.com/literal', []);
459
testParsing('http://example.com/{var}literal{var2}', [
460
{
461
expression: '{var}',
462
operator: '',
463
variables: [{ explodable: false, name: 'var', optional: false, prefixLength: undefined, repeatable: false }]
464
},
465
{
466
expression: '{var2}',
467
operator: '',
468
variables: [{ explodable: false, name: 'var2', optional: false, prefixLength: undefined, repeatable: false }]
469
}
470
]);
471
472
// Test that escaped braces are treated as literals
473
// Note: The current implementation might not handle this case
474
testResolution('http://example.com/{{var}}', { var: 'value' }, 'http://example.com/{var}');
475
});
476
477
test('edge cases', () => {
478
// Empty template
479
testResolution('', {}, '');
480
481
// Template with only literals
482
testResolution('http://example.com/path', {}, 'http://example.com/path');
483
484
// No variables provided for resolution
485
testResolution('{var}', {}, '');
486
487
// Multiple sequential expressions
488
testResolution('{a}{b}{c}', { a: '1', b: '2', c: '3' }, '123');
489
490
// Expressions with special characters in variable names
491
testResolution('{_hidden.var-name$}', { '_hidden.var-name$': 'value' }, 'value');
492
});
493
});
494
495