From c1d1856d50df169572b90ecab0d565dc3c16d042 Mon Sep 17 00:00:00 2001 From: Anemy Date: Tue, 31 Aug 2021 18:00:27 -0400 Subject: [PATCH 1/4] fix opening _ids with slashes in them --- playgrounds/different-id-types.mongodb | 56 +++++++++++++++++++ playgrounds/index-types.mongodb | 8 +-- src/editors/editorsController.ts | 24 +++++--- .../suite/editors/editorsController.test.ts | 31 +++++++++- .../explorer/playgroundsExplorer.test.ts | 14 +++-- 5 files changed, 113 insertions(+), 20 deletions(-) create mode 100644 playgrounds/different-id-types.mongodb diff --git a/playgrounds/different-id-types.mongodb b/playgrounds/different-id-types.mongodb new file mode 100644 index 000000000..7011773bb --- /dev/null +++ b/playgrounds/different-id-types.mongodb @@ -0,0 +1,56 @@ +// Playground for seeding the `mongodbVSCodePlaygroundDB.idTypeTesting` with +// documents that have different kinds of ids. + +const databaseName = 'mongodbVSCodePlaygroundDB'; +const collectionName = 'idTypeTesting'; + +use(databaseName); + +db[collectionName].insertOne({ + description: 'auto generated default object id' +}); + +db[collectionName].insertOne({ + _id: ObjectId(), + description: 'object id' +}); + +db[collectionName].insertOne({ + _id: 'testString', + description: 'string' +}); + +db[collectionName].insertOne({ + _id: 123, + description: 'number' +}); + +db[collectionName].insertOne({ + _id: { + name: 'aaa' + }, + description: 'object' +}); + +db[collectionName].insertOne({ + _id: 'abc//\\\nab c$%@1s df', + description: 'string with special characters' +}); + +db[collectionName].insertOne({ + _id: { + name: 'abc//\\\nab c$%@1s df', + 2: 3 + }, + description: 'object with a string with special characters' +}); + +db[collectionName].insertOne({ + _id: new Date(), + description: 'date' +}); + +db[collectionName].insertOne({ + _id: Binary('pineapple'), + description: 'binary' +}); diff --git a/playgrounds/index-types.mongodb b/playgrounds/index-types.mongodb index 2bdebfffb..9c200e90d 100644 --- a/playgrounds/index-types.mongodb +++ b/playgrounds/index-types.mongodb @@ -1,16 +1,12 @@ -// MongoDB Playground -// To disable this template go to Settings | MongoDB | Use Default Template For Playground. -// Make sure you are connected to enable completions and to be able to run a playground. -// Use Ctrl+Space inside a snippet or a string literal to trigger completions. +// Playground for seeding the `mongodbVSCodePlaygroundDB.index-testing` with +// different types of indexes. -// Select the database to use. use('mongodbVSCodePlaygroundDB'); db['index-testing'].insertOne({ 'title': 'there and back again' }); -// Insert a few documents into the sales collection. db['index-testing'].createIndex({ 'fieldAscending': 1, 'fieldDescending': -1 diff --git a/src/editors/editorsController.ts b/src/editors/editorsController.ts index 408e0c0fe..0f8cc863f 100644 --- a/src/editors/editorsController.ts +++ b/src/editors/editorsController.ts @@ -32,6 +32,21 @@ import TelemetryService from '../telemetry/telemetryService'; const log = createLogger('editors controller'); +export function getFileDisplayNameForDocumentId(documentId) { + let displayName = EJSON.stringify(documentId); + + // Strip out all special characters to ensure VSCode handles + // it correctly in a uri. + const regularExpression = /[^a-z0-9{}_-\s":,]/gi; + displayName = displayName.replace(regularExpression, ''); + + displayName = displayName.length > 50 + ? displayName.substring(0, 50) + : displayName; + + return displayName; +} + /** * This controller manages when our extension needs to open * new editors and the data they need. It also manages active editors. @@ -108,12 +123,7 @@ export default class EditorsController { async openMongoDBDocument(data: EditDocumentInfo): Promise { try { - let fileDocumentId = EJSON.stringify(data.documentId); - - fileDocumentId = - fileDocumentId.length > 50 - ? fileDocumentId.substring(0, 50) - : fileDocumentId; + const fileDocumentId = getFileDisplayNameForDocumentId(data.documentId); const fileName = `${VIEW_DOCUMENT_SCHEME}:/${data.namespace}:${fileDocumentId}.json`; const mdbDocument = (await this._mongoDBDocumentService.fetchDocument( @@ -137,7 +147,7 @@ export default class EditorsController { const documentIdReference = this._documentIdStore.add(data.documentId); const documentIdUriQuery = `${DOCUMENT_ID_URI_IDENTIFIER}=${documentIdReference}`; const documentSourceUriQuery = `${DOCUMENT_SOURCE_URI_IDENTIFIER}=${data.source}`; - const uri: vscode.Uri = vscode.Uri.parse(fileName).with({ + const uri: vscode.Uri = vscode.Uri.parse(fileName, true).with({ query: `?${namespaceUriQuery}&${connectionIdUriQuery}&${documentIdUriQuery}&${documentSourceUriQuery}` }); const document = await vscode.workspace.openTextDocument(uri); diff --git a/src/test/suite/editors/editorsController.test.ts b/src/test/suite/editors/editorsController.test.ts index feaa32494..e20deed85 100644 --- a/src/test/suite/editors/editorsController.test.ts +++ b/src/test/suite/editors/editorsController.test.ts @@ -4,8 +4,11 @@ import assert from 'assert'; import chai from 'chai'; import { mockTextEditor } from '../stubs'; import sinon from 'sinon'; +import { ObjectId } from 'bson'; -import { EditorsController } from '../../../editors'; +import EditorsController, { + getFileDisplayNameForDocumentId +} from '../../../editors/editorsController'; const expect = chai.expect; @@ -17,6 +20,32 @@ suite('Editors Controller Test Suite', () => { sinon.restore(); }); + suite('#getFileDisplayNameForDocumentId', () => { + test('it strips special characters from the document id', () => { + const str = 'abc//\\\nab c$%%..@1s df"'; + const result = getFileDisplayNameForDocumentId(str); + const expected = '"abcnab c1s df""'; + assert.strictEqual(result, expected); + }); + + test('it trims the string to 50 characters', () => { + const str = '123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjvbskdafdf'; + const result = getFileDisplayNameForDocumentId(str); + const expected = '"123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjv'; + assert.strictEqual(result, expected); + }); + + test('it handles ids that are objects', () => { + const str = { + str: 'abc//\\\nab c$%%..@1s df"', + b: new ObjectId('5d973ae744376d2aae72a160') + }; + const result = getFileDisplayNameForDocumentId(str); + const expected = '{"str":"abcnab c1s df"","b":{"oid":"5d973ae7443'; + assert.strictEqual(result, expected); + }); + }); + test('getViewCollectionDocumentsUri builds a uri from the namespace and connection info', () => { const testOpId = '100011011101110011'; const testNamespace = 'myFavoriteNamespace'; diff --git a/src/test/suite/explorer/playgroundsExplorer.test.ts b/src/test/suite/explorer/playgroundsExplorer.test.ts index b27723099..a13780c69 100644 --- a/src/test/suite/explorer/playgroundsExplorer.test.ts +++ b/src/test/suite/explorer/playgroundsExplorer.test.ts @@ -54,18 +54,20 @@ suite('Playgrounds Controller Test Suite', function () { try { const children = await treeController.getPlaygrounds(rootUri); - assert( - Object.keys(children).length === 4, - `Tree playgrounds should have 4 child, found ${children.length}` + assert.strictEqual( + Object.keys(children).length, + 5, + `Tree playgrounds should have 5 child, found ${children.length}` ); const playgrounds = Object.values(children).filter( (item: any) => item.label && item.label.split('.').pop() === 'mongodb' ); - assert( - Object.keys(playgrounds).length === 4, - `Tree playgrounds should have 4 playgrounds with mongodb extension, found ${children.length}` + assert.strictEqual( + Object.keys(playgrounds).length, + 5, + `Tree playgrounds should have 5 playgrounds with mongodb extension, found ${children.length}` ); } catch (error) { assert(false, error); From f76ddbca94b00928003156f0b94fb3deedee1177 Mon Sep 17 00:00:00 2001 From: Anemy Date: Tue, 31 Aug 2021 19:44:06 -0400 Subject: [PATCH 2/4] update tests --- src/editors/editorsController.ts | 2 +- src/test/suite/editors/editorsController.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editors/editorsController.ts b/src/editors/editorsController.ts index 0f8cc863f..14c4922e5 100644 --- a/src/editors/editorsController.ts +++ b/src/editors/editorsController.ts @@ -37,7 +37,7 @@ export function getFileDisplayNameForDocumentId(documentId) { // Strip out all special characters to ensure VSCode handles // it correctly in a uri. - const regularExpression = /[^a-z0-9{}_-\s":,]/gi; + const regularExpression = /[^a-z0-9{}_-\s":,$!*#@()]/gi; displayName = displayName.replace(regularExpression, ''); displayName = displayName.length > 50 diff --git a/src/test/suite/editors/editorsController.test.ts b/src/test/suite/editors/editorsController.test.ts index e20deed85..46c85fe3d 100644 --- a/src/test/suite/editors/editorsController.test.ts +++ b/src/test/suite/editors/editorsController.test.ts @@ -24,7 +24,7 @@ suite('Editors Controller Test Suite', () => { test('it strips special characters from the document id', () => { const str = 'abc//\\\nab c$%%..@1s df"'; const result = getFileDisplayNameForDocumentId(str); - const expected = '"abcnab c1s df""'; + const expected = '"abcnab c$@1s df""'; assert.strictEqual(result, expected); }); @@ -41,7 +41,7 @@ suite('Editors Controller Test Suite', () => { b: new ObjectId('5d973ae744376d2aae72a160') }; const result = getFileDisplayNameForDocumentId(str); - const expected = '{"str":"abcnab c1s df"","b":{"oid":"5d973ae7443'; + const expected = '{"str":"abcnab c$@1s df"","b":{"$oid":"5d973ae7'; assert.strictEqual(result, expected); }); }); From 5733cd9e3b788a16d0e6a0da9c803b5ddb399d82 Mon Sep 17 00:00:00 2001 From: Anemy Date: Tue, 12 Oct 2021 20:30:01 -0400 Subject: [PATCH 3/4] update regex to only remove slashes --- src/editors/editorsController.ts | 6 +++--- src/test/suite/editors/editorsController.test.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/editors/editorsController.ts b/src/editors/editorsController.ts index 14c4922e5..5314f16c1 100644 --- a/src/editors/editorsController.ts +++ b/src/editors/editorsController.ts @@ -37,11 +37,11 @@ export function getFileDisplayNameForDocumentId(documentId) { // Strip out all special characters to ensure VSCode handles // it correctly in a uri. - const regularExpression = /[^a-z0-9{}_-\s":,$!*#@()]/gi; + const regularExpression = /[\\\/]/gi; displayName = displayName.replace(regularExpression, ''); - displayName = displayName.length > 50 - ? displayName.substring(0, 50) + displayName = displayName.length > 100 + ? displayName.substring(0, 100) : displayName; return displayName; diff --git a/src/test/suite/editors/editorsController.test.ts b/src/test/suite/editors/editorsController.test.ts index 46c85fe3d..b394995ca 100644 --- a/src/test/suite/editors/editorsController.test.ts +++ b/src/test/suite/editors/editorsController.test.ts @@ -24,14 +24,14 @@ suite('Editors Controller Test Suite', () => { test('it strips special characters from the document id', () => { const str = 'abc//\\\nab c$%%..@1s df"'; const result = getFileDisplayNameForDocumentId(str); - const expected = '"abcnab c$@1s df""'; + const expected = '"abcnab c$%%..@1s df""'; assert.strictEqual(result, expected); }); - test('it trims the string to 50 characters', () => { - const str = '123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjvbskdafdf'; + test('it trims the string to 100 characters', () => { + const str = '123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjvbskdafdf123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjvbskdafdf'; const result = getFileDisplayNameForDocumentId(str); - const expected = '"123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjv'; + const expected = '"123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjvbskdafdf123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkj'; assert.strictEqual(result, expected); }); @@ -41,7 +41,7 @@ suite('Editors Controller Test Suite', () => { b: new ObjectId('5d973ae744376d2aae72a160') }; const result = getFileDisplayNameForDocumentId(str); - const expected = '{"str":"abcnab c$@1s df"","b":{"$oid":"5d973ae7'; + const expected = '{"str":"abcnab c$%%..@1s df"","b":{"$oid":"5d973ae744376d2aae72a160"}}'; assert.strictEqual(result, expected); }); }); From 0c493d63a976c7ee91720ef055756b20b283de1e Mon Sep 17 00:00:00 2001 From: Anemy Date: Tue, 19 Oct 2021 09:26:43 -0400 Subject: [PATCH 4/4] encode special characters --- src/editors/editorsController.ts | 95 +++++++++++-------- .../suite/editors/editorsController.test.ts | 63 +++++++----- 2 files changed, 97 insertions(+), 61 deletions(-) diff --git a/src/editors/editorsController.ts b/src/editors/editorsController.ts index 5314f16c1..5669fe78d 100644 --- a/src/editors/editorsController.ts +++ b/src/editors/editorsController.ts @@ -32,21 +32,51 @@ import TelemetryService from '../telemetry/telemetryService'; const log = createLogger('editors controller'); -export function getFileDisplayNameForDocumentId(documentId) { - let displayName = EJSON.stringify(documentId); - - // Strip out all special characters to ensure VSCode handles - // it correctly in a uri. - const regularExpression = /[\\\/]/gi; - displayName = displayName.replace(regularExpression, ''); - - displayName = displayName.length > 100 - ? displayName.substring(0, 100) +export function getFileDisplayNameForDocument( + documentId: EJSON.SerializableTypes, + namespace: string +) { + let displayName = `${namespace}:${EJSON.stringify(documentId)}`; + + // Encode special file uri characters to ensure VSCode handles + // it correctly in a uri while avoiding collisions. + displayName = displayName.replace(/[\\/%]/gi, function(c) { + return `%${c.charCodeAt(0).toString(16)}`; + }); + + displayName = displayName.length > 200 + ? displayName.substring(0, 200) : displayName; return displayName; } +export function getViewCollectionDocumentsUri( + operationId: string, + namespace: string, + connectionId: string +): vscode.Uri { + // We attach a unique id to the query so that it creates a new file in + // the editor and so that we can virtually manage the amount of docs shown. + const operationIdUriQuery = `${OPERATION_ID_URI_IDENTIFIER}=${operationId}`; + const connectionIdUriQuery = `${CONNECTION_ID_URI_IDENTIFIER}=${connectionId}`; + const namespaceUriQuery = `${NAMESPACE_URI_IDENTIFIER}=${namespace}`; + const uriQuery = `?${namespaceUriQuery}&${connectionIdUriQuery}&${operationIdUriQuery}`; + + // Encode special file uri characters to ensure VSCode handles + // it correctly in a uri while avoiding collisions. + const namespaceDisplayName = encodeURIComponent( + namespace.replace(/[\\/%]/gi, function(c) { + return `%${c.charCodeAt(0).toString(16)}`; + }) + ); + + // The part of the URI after the scheme and before the query is the file name. + return vscode.Uri.parse( + `${VIEW_COLLECTION_SCHEME}:Results: ${namespaceDisplayName}.json${uriQuery}` + ); +} + /** * This controller manages when our extension needs to open * new editors and the data they need. It also manages active editors. @@ -123,9 +153,6 @@ export default class EditorsController { async openMongoDBDocument(data: EditDocumentInfo): Promise { try { - const fileDocumentId = getFileDisplayNameForDocumentId(data.documentId); - - const fileName = `${VIEW_DOCUMENT_SCHEME}:/${data.namespace}:${fileDocumentId}.json`; const mdbDocument = (await this._mongoDBDocumentService.fetchDocument( data )) as EJSON.SerializableTypes; @@ -138,8 +165,6 @@ export default class EditorsController { return false; } - this._saveDocumentToMemoryFileSystem(fileName, mdbDocument); - const activeConnectionId = this._connectionController.getActiveConnectionId() || ''; const namespaceUriQuery = `${NAMESPACE_URI_IDENTIFIER}=${data.namespace}`; @@ -147,10 +172,20 @@ export default class EditorsController { const documentIdReference = this._documentIdStore.add(data.documentId); const documentIdUriQuery = `${DOCUMENT_ID_URI_IDENTIFIER}=${documentIdReference}`; const documentSourceUriQuery = `${DOCUMENT_SOURCE_URI_IDENTIFIER}=${data.source}`; - const uri: vscode.Uri = vscode.Uri.parse(fileName, true).with({ + + const fileTitle = encodeURIComponent(getFileDisplayNameForDocument( + data.documentId, + data.namespace + )); + const fileName = `${VIEW_DOCUMENT_SCHEME}:/${fileTitle}.json`; + + const fileUri = vscode.Uri.parse(fileName, true).with({ query: `?${namespaceUriQuery}&${connectionIdUriQuery}&${documentIdUriQuery}&${documentSourceUriQuery}` }); - const document = await vscode.workspace.openTextDocument(uri); + + this._saveDocumentToMemoryFileSystem(fileUri, mdbDocument); + + const document = await vscode.workspace.openTextDocument(fileUri); await vscode.window.showTextDocument(document, { preview: false }); @@ -222,31 +257,13 @@ export default class EditorsController { } } - static getViewCollectionDocumentsUri( - operationId: string, - namespace: string, - connectionId: string - ): vscode.Uri { - // We attach a unique id to the query so that it creates a new file in - // the editor and so that we can virtually manage the amount of docs shown. - const operationIdUriQuery = `${OPERATION_ID_URI_IDENTIFIER}=${operationId}`; - const connectionIdUriQuery = `${CONNECTION_ID_URI_IDENTIFIER}=${connectionId}`; - const namespaceUriQuery = `${NAMESPACE_URI_IDENTIFIER}=${namespace}`; - const uriQuery = `?${namespaceUriQuery}&${connectionIdUriQuery}&${operationIdUriQuery}`; - - // The part of the URI after the scheme and before the query is the file name. - return vscode.Uri.parse( - `${VIEW_COLLECTION_SCHEME}:Results: ${namespace}.json${uriQuery}` - ); - } - async onViewCollectionDocuments(namespace: string): Promise { log.info('view collection documents', namespace); const operationId = this._collectionDocumentsOperationsStore.createNewOperation(); const activeConnectionId = this._connectionController.getActiveConnectionId() || ''; - const uri = EditorsController.getViewCollectionDocumentsUri( + const uri = getViewCollectionDocumentsUri( operationId, namespace, activeConnectionId @@ -304,7 +321,7 @@ export default class EditorsController { ); } - const uri = EditorsController.getViewCollectionDocumentsUri( + const uri = getViewCollectionDocumentsUri( operationId, namespace, connectionId @@ -321,11 +338,11 @@ export default class EditorsController { } _saveDocumentToMemoryFileSystem( - fileName: string, + fileUri: vscode.Uri, document: EJSON.SerializableTypes ): void { this._memoryFileSystemProvider.writeFile( - vscode.Uri.parse(fileName), + fileUri, Buffer.from(JSON.stringify(document, null, 2)), { create: true, overwrite: true } ); diff --git a/src/test/suite/editors/editorsController.test.ts b/src/test/suite/editors/editorsController.test.ts index b394995ca..1f5ee2d3f 100644 --- a/src/test/suite/editors/editorsController.test.ts +++ b/src/test/suite/editors/editorsController.test.ts @@ -6,8 +6,9 @@ import { mockTextEditor } from '../stubs'; import sinon from 'sinon'; import { ObjectId } from 'bson'; -import EditorsController, { - getFileDisplayNameForDocumentId +import { + getFileDisplayNameForDocument, + getViewCollectionDocumentsUri } from '../../../editors/editorsController'; const expect = chai.expect; @@ -22,16 +23,16 @@ suite('Editors Controller Test Suite', () => { suite('#getFileDisplayNameForDocumentId', () => { test('it strips special characters from the document id', () => { - const str = 'abc//\\\nab c$%%..@1s df"'; - const result = getFileDisplayNameForDocumentId(str); - const expected = '"abcnab c$%%..@1s df""'; + const str = 'abc//\\\nab c"$%%..@1s df""'; + const result = getFileDisplayNameForDocument(str, 'a.b'); + const expected = 'a.b:"abc%2f%2f%5c%5c%5cnab c%5c"$%25%25..@1s df%5c"%5c""'; assert.strictEqual(result, expected); }); - test('it trims the string to 100 characters', () => { - const str = '123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjvbskdafdf123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjvbskdafdf'; - const result = getFileDisplayNameForDocumentId(str); - const expected = '"123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjvbskdafdf123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkj'; + test('it trims the string to 200 characters', () => { + const str = '123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjvbskdafdf123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjvbskdafdffbnjiekbfdakjsdbfkjsabdfkjasbfbnjiekbfdakjsdbfkjsabdfkjasbkjasbfbnjiekbfdakjsdbfkjsabdfkjasb'; + const result = getFileDisplayNameForDocument(str, 'db.col'); + const expected = 'db.col:"123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjvbskdafdf123sdfhadfbnjiekbfdakjsdbfkjsabdfkjasbdfkjsvasdjvbskdafdffbnjiekbfdakjsdbfkjsabdfkjasbfbnjiekbfdakjsdbfkjsabdfkjasbkjasbfbnjiekbfdakjsd'; assert.strictEqual(result, expected); }); @@ -40,8 +41,15 @@ suite('Editors Controller Test Suite', () => { str: 'abc//\\\nab c$%%..@1s df"', b: new ObjectId('5d973ae744376d2aae72a160') }; - const result = getFileDisplayNameForDocumentId(str); - const expected = '{"str":"abcnab c$%%..@1s df"","b":{"$oid":"5d973ae744376d2aae72a160"}}'; + const result = getFileDisplayNameForDocument(str, 'db.col'); + const expected = 'db.col:{"str":"abc%2f%2f%5c%5c%5cnab c$%25%25..@1s df%5c"","b":{"$oid":"5d973ae744376d2aae72a160"}}'; + assert.strictEqual(result, expected); + }); + + test('has the namespace at the start of the display name', () => { + const str = 'pineapples'; + const result = getFileDisplayNameForDocument(str, 'grilled'); + const expected = 'grilled:"pineapples"'; assert.strictEqual(result, expected); }); }); @@ -50,24 +58,35 @@ suite('Editors Controller Test Suite', () => { const testOpId = '100011011101110011'; const testNamespace = 'myFavoriteNamespace'; const testConnectionId = 'alienSateliteConnection'; - const testUri = EditorsController.getViewCollectionDocumentsUri( + const testUri = getViewCollectionDocumentsUri( testOpId, testNamespace, testConnectionId ); - assert( - testUri.path === 'Results: myFavoriteNamespace.json', - `Expected uri path ${testUri.path} to equal 'Results: myFavoriteNamespace.json'.` + assert.strictEqual(testUri.path, 'Results: myFavoriteNamespace.json'); + assert.strictEqual(testUri.scheme, 'VIEW_COLLECTION_SCHEME'); + assert.strictEqual( + testUri.query, + 'namespace=myFavoriteNamespace&connectionId=alienSateliteConnection&operationId=100011011101110011', ); - assert( - testUri.scheme === 'VIEW_COLLECTION_SCHEME', - `Expected uri scheme ${testUri.scheme} to equal 'VIEW_COLLECTION_SCHEME'.` + }); + + test('getViewCollectionDocumentsUri handles / \\ and % in the namespace', () => { + const testOpId = '100011011101110011'; + const testNamespace = 'myFa%%\\\\///\\%vorite%Namespace'; + const testConnectionId = 'alienSateliteConnection'; + const testUri = getViewCollectionDocumentsUri( + testOpId, + testNamespace, + testConnectionId ); - assert( - testUri.query === - 'namespace=myFavoriteNamespace&connectionId=alienSateliteConnection&operationId=100011011101110011', - `Expected uri query ${testUri.query} to equal 'namespace=myFavoriteNamespace&connectionId=alienSateliteConnection&operationId=100011011101110011'.` + + assert.strictEqual(testUri.path, 'Results: myFa%25%25%5c%5c%2f%2f%2f%5c%25vorite%25Namespace.json'); + assert.strictEqual(testUri.scheme, 'VIEW_COLLECTION_SCHEME'); + assert.strictEqual( + testUri.query, + 'namespace=myFa%%\\\\///\\%vorite%Namespace&connectionId=alienSateliteConnection&operationId=100011011101110011', ); });