Path: blob/main/src/vs/workbench/contrib/mcp/test/common/uriTemplate.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';6import { UriTemplate } from '../../common/uriTemplate.js';7import * as assert from 'assert';89suite('UriTemplate', () => {10ensureNoDisposablesAreLeakedInTestSuite();1112/**13* Helper function to test template parsing and component extraction14*/15function testParsing(template: string, expectedComponents: any[]) {16const templ = UriTemplate.parse(template);17assert.deepStrictEqual(templ.components.filter(c => typeof c === 'object'), expectedComponents);18return templ;19}2021/**22* Helper function to test template resolution23*/24function testResolution(template: string, variables: Record<string, any>, expected: string) {25const templ = UriTemplate.parse(template);26const result = templ.resolve(variables);27assert.strictEqual(result, expected);28}2930test('simple replacement', () => {31const templ = UriTemplate.parse('http://example.com/{var}');32assert.deepStrictEqual(templ.components, ['http://example.com/', {33expression: "{var}",34operator: '',35variables: [{ explodable: false, name: "var", optional: false, prefixLength: undefined, repeatable: false }]36}, '']);37const result = templ.resolve({ var: 'value' });38assert.strictEqual(result, 'http://example.com/value');39});4041test('parsing components correctly', () => {42// Simple component43testParsing('http://example.com/{var}', [{44expression: "{var}",45operator: '',46variables: [{ explodable: false, name: "var", optional: false, prefixLength: undefined, repeatable: false }]47}]);4849// Component with operator50testParsing('http://example.com/{+path}', [{51expression: "{+path}",52operator: '+',53variables: [{ explodable: false, name: "path", optional: false, prefixLength: undefined, repeatable: false }]54}]);5556// Component with multiple variables57testParsing('http://example.com/{x,y}', [{58expression: "{x,y}",59operator: '',60variables: [61{ explodable: false, name: "x", optional: false, prefixLength: undefined, repeatable: false },62{ explodable: false, name: "y", optional: false, prefixLength: undefined, repeatable: false }63]64}]);6566// Component with value modifiers67testParsing('http://example.com/{var:3}', [{68expression: "{var:3}",69operator: '',70variables: [{ explodable: false, name: "var", optional: false, prefixLength: 3, repeatable: false }]71}]);7273testParsing('http://example.com/{list*}', [{74expression: "{list*}",75operator: '',76variables: [{ explodable: true, name: "list", optional: false, prefixLength: undefined, repeatable: true }]77}]);7879// Multiple components80testParsing('http://example.com/{x}/path/{y}', [81{82expression: "{x}",83operator: '',84variables: [{ explodable: false, name: "x", optional: false, prefixLength: undefined, repeatable: false }]85},86{87expression: "{y}",88operator: '',89variables: [{ explodable: false, name: "y", optional: false, prefixLength: undefined, repeatable: false }]90}91]);92});9394test('Level 1 - Simple string expansion', () => {95// Test cases from RFC 6570 Section 1.296const variables = {97var: 'value',98hello: 'Hello World!'99};100101testResolution('{var}', variables, 'value');102testResolution('{hello}', variables, 'Hello%20World%21');103});104105test('Level 2 - Reserved expansion', () => {106// Test cases from RFC 6570 Section 1.2107const variables = {108var: 'value',109hello: 'Hello World!',110path: '/foo/bar'111};112113testResolution('{+var}', variables, 'value');114testResolution('{+hello}', variables, 'Hello%20World!');115testResolution('{+path}/here', variables, '/foo/bar/here');116testResolution('here?ref={+path}', variables, 'here?ref=/foo/bar');117});118119test('Level 2 - Fragment expansion', () => {120// Test cases from RFC 6570 Section 1.2121const variables = {122var: 'value',123hello: 'Hello World!'124};125126testResolution('X{#var}', variables, 'X#value');127testResolution('X{#hello}', variables, 'X#Hello%20World!');128});129130test('Level 3 - String expansion with multiple variables', () => {131// Test cases from RFC 6570 Section 1.2132const variables = {133var: 'value',134hello: 'Hello World!',135empty: '',136path: '/foo/bar',137x: '1024',138y: '768'139};140141testResolution('map?{x,y}', variables, 'map?1024,768');142testResolution('{x,hello,y}', variables, '1024,Hello%20World%21,768');143});144145test('Level 3 - Reserved expansion with multiple variables', () => {146// Test cases from RFC 6570 Section 1.2147const variables = {148var: 'value',149hello: 'Hello World!',150path: '/foo/bar',151x: '1024',152y: '768'153};154155testResolution('{+x,hello,y}', variables, '1024,Hello%20World!,768');156testResolution('{+path,x}/here', variables, '/foo/bar,1024/here');157});158159test('Level 3 - Fragment expansion with multiple variables', () => {160// Test cases from RFC 6570 Section 1.2161const variables = {162var: 'value',163hello: 'Hello World!',164path: '/foo/bar',165x: '1024',166y: '768'167};168169testResolution('{#x,hello,y}', variables, '#1024,Hello%20World!,768');170testResolution('{#path,x}/here', variables, '#/foo/bar,1024/here');171});172173test('Level 3 - Label expansion with dot-prefix', () => {174// Test cases from RFC 6570 Section 1.2175const variables = {176var: 'value',177x: '1024',178y: '768'179};180181testResolution('X{.var}', variables, 'X.value');182testResolution('X{.x,y}', variables, 'X.1024.768');183});184185test('Level 3 - Path segments expansion', () => {186// Test cases from RFC 6570 Section 1.2187const variables = {188var: 'value',189x: '1024'190};191192testResolution('{/var}', variables, '/value');193testResolution('{/var,x}/here', variables, '/value/1024/here');194});195196test('Level 3 - Path-style parameter expansion', () => {197// Test cases from RFC 6570 Section 1.2198const variables = {199x: '1024',200y: '768',201empty: ''202};203204testResolution('{;x,y}', variables, ';x=1024;y=768');205testResolution('{;x,y,empty}', variables, ';x=1024;y=768;empty');206});207208test('Level 3 - Form-style query expansion', () => {209// Test cases from RFC 6570 Section 1.2210const variables = {211x: '1024',212y: '768',213empty: ''214};215216testResolution('{?x,y}', variables, '?x=1024&y=768');217testResolution('{?x,y,empty}', variables, '?x=1024&y=768&empty=');218});219220test('Level 3 - Form-style query continuation', () => {221// Test cases from RFC 6570 Section 1.2222const variables = {223x: '1024',224y: '768',225empty: ''226};227228testResolution('?fixed=yes{&x}', variables, '?fixed=yes&x=1024');229testResolution('{&x,y,empty}', variables, '&x=1024&y=768&empty=');230});231232test('Level 4 - String expansion with value modifiers', () => {233// Test cases from RFC 6570 Section 1.2234const variables = {235var: 'value',236hello: 'Hello World!',237path: '/foo/bar',238list: ['red', 'green', 'blue'],239keys: {240semi: ';',241dot: '.',242comma: ','243}244};245246testResolution('{var:3}', variables, 'val');247testResolution('{var:30}', variables, 'value');248testResolution('{list}', variables, 'red,green,blue');249testResolution('{list*}', variables, 'red,green,blue');250});251252test('Level 4 - Reserved expansion with value modifiers', () => {253// Test cases related to Level 4 features254const variables = {255var: 'value',256hello: 'Hello World!',257path: '/foo/bar',258list: ['red', 'green', 'blue'],259keys: {260semi: ';',261dot: '.',262comma: ','263}264};265266testResolution('{+path:6}/here', variables, '/foo/b/here');267testResolution('{+list}', variables, 'red,green,blue');268testResolution('{+list*}', variables, 'red,green,blue');269testResolution('{+keys}', variables, 'semi,;,dot,.,comma,,');270testResolution('{+keys*}', variables, 'semi=;,dot=.,comma=,');271});272273test('Level 4 - Fragment expansion with value modifiers', () => {274// Test cases related to Level 4 features275const variables = {276var: 'value',277hello: 'Hello World!',278path: '/foo/bar',279list: ['red', 'green', 'blue'],280keys: {281semi: ';',282dot: '.',283comma: ','284}285};286287testResolution('{#path:6}/here', variables, '#/foo/b/here');288testResolution('{#list}', variables, '#red,green,blue');289testResolution('{#list*}', variables, '#red,green,blue');290testResolution('{#keys}', variables, '#semi,;,dot,.,comma,,');291testResolution('{#keys*}', variables, '#semi=;,dot=.,comma=,');292});293294test('Level 4 - Label expansion with value modifiers', () => {295// Test cases related to Level 4 features296const variables = {297var: 'value',298list: ['red', 'green', 'blue'],299keys: {300semi: ';',301dot: '.',302comma: ','303}304};305306testResolution('X{.var:3}', variables, 'X.val');307testResolution('X{.list}', variables, 'X.red,green,blue');308testResolution('X{.list*}', variables, 'X.red.green.blue');309testResolution('X{.keys}', variables, 'X.semi,;,dot,.,comma,,');310testResolution('X{.keys*}', variables, 'X.semi=;.dot=..comma=,');311});312313test('Level 4 - Path expansion with value modifiers', () => {314// Test cases related to Level 4 features315const variables = {316var: 'value',317list: ['red', 'green', 'blue'],318path: '/foo/bar',319keys: {320semi: ';',321dot: '.',322comma: ','323}324};325326testResolution('{/var:1,var}', variables, '/v/value');327testResolution('{/list}', variables, '/red,green,blue');328testResolution('{/list*}', variables, '/red/green/blue');329testResolution('{/list*,path:4}', variables, '/red/green/blue/%2Ffoo');330testResolution('{/keys}', variables, '/semi,;,dot,.,comma,,');331testResolution('{/keys*}', variables, '/semi=%3B/dot=./comma=%2C');332});333334test('Level 4 - Path-style parameters with value modifiers', () => {335// Test cases related to Level 4 features336const variables = {337var: 'value',338list: ['red', 'green', 'blue'],339keys: {340semi: ';',341dot: '.',342comma: ','343}344};345346testResolution('{;hello:5}', { hello: 'Hello World!' }, ';hello=Hello');347testResolution('{;list}', variables, ';list=red,green,blue');348testResolution('{;list*}', variables, ';list=red;list=green;list=blue');349testResolution('{;keys}', variables, ';keys=semi,;,dot,.,comma,,');350testResolution('{;keys*}', variables, ';semi=;;dot=.;comma=,');351});352353test('Level 4 - Form-style query with value modifiers', () => {354// Test cases related to Level 4 features355const variables = {356var: 'value',357list: ['red', 'green', 'blue'],358keys: {359semi: ';',360dot: '.',361comma: ','362}363};364365testResolution('{?var:3}', variables, '?var=val');366testResolution('{?list}', variables, '?list=red,green,blue');367testResolution('{?list*}', variables, '?list=red&list=green&list=blue');368testResolution('{?keys}', variables, '?keys=semi,;,dot,.,comma,,');369testResolution('{?keys*}', variables, '?semi=;&dot=.&comma=,');370});371372test('Level 4 - Form-style query continuation with value modifiers', () => {373// Test cases related to Level 4 features374const variables = {375var: 'value',376list: ['red', 'green', 'blue'],377keys: {378semi: ';',379dot: '.',380comma: ','381}382};383384testResolution('?fixed=yes{&var:3}', variables, '?fixed=yes&var=val');385testResolution('?fixed=yes{&list}', variables, '?fixed=yes&list=red,green,blue');386testResolution('?fixed=yes{&list*}', variables, '?fixed=yes&list=red&list=green&list=blue');387testResolution('?fixed=yes{&keys}', variables, '?fixed=yes&keys=semi,;,dot,.,comma,,');388testResolution('?fixed=yes{&keys*}', variables, '?fixed=yes&semi=;&dot=.&comma=,');389});390391test('handling undefined or null values', () => {392// Test handling of undefined/null values for different operators393const variables = {394defined: 'value',395undef: undefined,396null: null,397empty: ''398};399400// Simple string expansion401testResolution('{defined,undef,null,empty}', variables, 'value,');402403// Reserved expansion404testResolution('{+defined,undef,null,empty}', variables, 'value,');405406// Fragment expansion407testResolution('{#defined,undef,null,empty}', variables, '#value,');408409// Label expansion410testResolution('X{.defined,undef,null,empty}', variables, 'X.value');411412// Path segments413testResolution('{/defined,undef,null}', variables, '/value');414415// Path-style parameters416testResolution('{;defined,empty}', variables, ';defined=value;empty');417418// Form-style query419testResolution('{?defined,undef,null,empty}', variables, '?defined=value&undef=&null=&empty=');420421// Form-style query continuation422testResolution('{&defined,undef,null,empty}', variables, '&defined=value&undef=&null=&empty=');423});424425test('complex templates', () => {426// Test more complex template combinations427const variables = {428domain: 'example.com',429user: 'fred',430path: ['path', 'to', 'resource'],431query: 'search',432page: 5,433lang: 'en',434sessionId: '123abc',435filters: ['color:blue', 'shape:square'],436coordinates: { lat: '37.7', lon: '-122.4' }437};438439// RESTful URL pattern440testResolution('https://{domain}/api/v1/users/{user}{/path*}{?query,page,lang}',441variables,442'https://example.com/api/v1/users/fred/path/to/resource?query=search&page=5&lang=en');443444// Complex query parameters445testResolution('https://{domain}/search{?query,filters,coordinates*}',446variables,447'https://example.com/search?query=search&filters=color:blue,shape:square&lat=37.7&lon=-122.4');448449// Multiple expression types450testResolution('https://{domain}/users/{user}/profile{.lang}{?sessionId}{#path}',451variables,452'https://example.com/users/fred/profile.en?sessionId=123abc#path,to,resource');453});454455test('literals and escaping', () => {456// Test literal segments and escaping457testParsing('http://example.com/literal', []);458testParsing('http://example.com/{var}literal{var2}', [459{460expression: '{var}',461operator: '',462variables: [{ explodable: false, name: 'var', optional: false, prefixLength: undefined, repeatable: false }]463},464{465expression: '{var2}',466operator: '',467variables: [{ explodable: false, name: 'var2', optional: false, prefixLength: undefined, repeatable: false }]468}469]);470471// Test that escaped braces are treated as literals472// Note: The current implementation might not handle this case473testResolution('http://example.com/{{var}}', { var: 'value' }, 'http://example.com/{var}');474});475476test('edge cases', () => {477// Empty template478testResolution('', {}, '');479480// Template with only literals481testResolution('http://example.com/path', {}, 'http://example.com/path');482483// No variables provided for resolution484testResolution('{var}', {}, '');485486// Multiple sequential expressions487testResolution('{a}{b}{c}', { a: '1', b: '2', c: '3' }, '123');488489// Expressions with special characters in variable names490testResolution('{_hidden.var-name$}', { '_hidden.var-name$': 'value' }, 'value');491});492});493494495