From 8cc60fde90f007fcfcb46461f95ac799a66ad665 Mon Sep 17 00:00:00 2001 From: Cory Rivera Date: Mon, 2 Dec 2019 14:19:37 -0800 Subject: [PATCH] Add code coverage tests for notebookUtils (#8524) --- .../notebook/browser/models/notebookUtils.ts | 51 +--- .../notebook/test/electron-browser/common.ts | 65 ++++- .../electron-browser/notebookUtils.test.ts | 244 +++++++++++++----- 3 files changed, 250 insertions(+), 110 deletions(-) diff --git a/src/sql/workbench/contrib/notebook/browser/models/notebookUtils.ts b/src/sql/workbench/contrib/notebook/browser/models/notebookUtils.ts index 783defec83..fdf5f6b562 100644 --- a/src/sql/workbench/contrib/notebook/browser/models/notebookUtils.ts +++ b/src/sql/workbench/contrib/notebook/browser/models/notebookUtils.ts @@ -6,13 +6,10 @@ import * as path from 'vs/base/common/path'; import { nb, ServerInfo } from 'azdata'; import { DEFAULT_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_FILETYPE, INotebookService } from 'sql/workbench/services/notebook/browser/notebookService'; -import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; -import { ICellModel } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces'; import { URI } from 'vs/base/common/uri'; import { startsWith } from 'vs/base/common/strings'; import { assign } from 'vs/base/common/objects'; - export const clusterEndpointsProperty = 'clusterEndpoints'; export const hadoopEndpointNameGateway = 'gateway'; /** @@ -56,26 +53,6 @@ export function getStandardKernelsForProvider(providerId: string, notebookServic return (standardKernels); } -// In the Attach To dropdown, show the database name (if it exists) using the current connection -// Example: myFakeServer (myDatabase) -export function formatServerNameWithDatabaseNameForAttachTo(connectionProfile: ConnectionProfile): string { - if (connectionProfile && connectionProfile.serverName) { - return !connectionProfile.databaseName ? connectionProfile.serverName : connectionProfile.serverName + ' (' + connectionProfile.databaseName + ')'; - } - return ''; -} - -// Extract server name from format used in Attach To: serverName (databaseName) -export function getServerFromFormattedAttachToName(name: string): string { - return name.substring(0, name.lastIndexOf(' (')) ? name.substring(0, name.lastIndexOf(' (')) : name; -} - -// Extract database name from format used in Attach To: serverName (databaseName) -export function getDatabaseFromFormattedAttachToName(name: string): string { - return name.substring(name.lastIndexOf('(') + 1, name.lastIndexOf(')')) ? - name.substring(name.lastIndexOf('(') + 1, name.lastIndexOf(')')) : ''; -} - export interface IStandardKernelWithProvider { readonly name: string; readonly displayName: string; @@ -102,30 +79,12 @@ export function tryMatchCellMagic(input: string): string { return magicName; } -export async function asyncForEach(array: any, callback: any): Promise { - for (let index = 0; index < array.length; index++) { - await callback(array[index], index, array); - } -} - -/** - * Only replace vscode-resource with file when in the same (or a sub) directory - * This matches Jupyter Notebook viewer behavior - */ -export function convertVscodeResourceToFileInSubDirectories(htmlContent: string, cellModel: ICellModel): string { - let htmlContentCopy = htmlContent; - while (htmlContentCopy.search('(?<=img src=\"vscode-resource:)') > 0) { - let pathStartIndex = htmlContentCopy.search('(?<=img src=\"vscode-resource:)'); - let pathEndIndex = htmlContentCopy.indexOf('\" ', pathStartIndex); - let filePath = htmlContentCopy.substring(pathStartIndex, pathEndIndex); - // If the asset is in the same folder or a subfolder, replace 'vscode-resource:' with 'file:', so the image is visible - if (!(path.relative(path.dirname(cellModel.notebookModel.notebookUri.fsPath), filePath).indexOf('..') > -1)) { - // ok to change from vscode-resource: to file: - htmlContent = htmlContent.replace('vscode-resource:' + filePath, 'file:' + filePath); +export async function asyncForEach(array: any[], callback: Function): Promise { + if (array && callback) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); } - htmlContentCopy = htmlContentCopy.slice(pathEndIndex); } - return htmlContent; } export function getClusterEndpoints(serverInfo: ServerInfo): IEndpoint[] | undefined { @@ -163,7 +122,7 @@ export function getHostAndPortFromEndpoint(endpoint: string): HostAndIp { }; } -interface RawEndpoint { +export interface RawEndpoint { serviceName: string; description?: string; endpoint?: string; diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/common.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/common.ts index eb2bea3520..74afa99ed1 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/common.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/common.ts @@ -8,9 +8,11 @@ import { nb, IConnectionProfile } from 'azdata'; import { Event, Emitter } from 'vs/base/common/event'; import { INotebookModel, ICellModel, IClientSession, IDefaultConnection, NotebookContentChange } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces'; import { NotebookChangeType, CellType } from 'sql/workbench/contrib/notebook/common/models/contracts'; -import { INotebookManager } from 'sql/workbench/services/notebook/browser/notebookService'; +import { INotebookManager, INotebookService, INotebookEditor, ILanguageMagic, INotebookProvider, INavigationProvider } from 'sql/workbench/services/notebook/browser/notebookService'; import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IStandardKernelWithProvider } from 'sql/workbench/contrib/notebook/browser/models/notebookUtils'; +import { URI } from 'vs/workbench/workbench.web.api'; +import { RenderMimeRegistry } from 'sql/workbench/contrib/notebook/browser/outputs/registry'; export class NotebookModelStub implements INotebookModel { constructor(private _languageInfo?: nb.ILanguageInfo) { @@ -139,3 +141,64 @@ export class ServerManagerStub implements nb.ServerManager { return this.result; } } + +export class NotebookServiceStub implements INotebookService { + _serviceBrand: undefined; + onNotebookEditorAdd: Event; + onNotebookEditorRemove: Event; + onNotebookEditorRename: Event; + isRegistrationComplete: boolean; + registrationComplete: Promise; + languageMagics: ILanguageMagic[]; + registerProvider(providerId: string, provider: INotebookProvider): void { + throw new Error('Method not implemented.'); + } + unregisterProvider(providerId: string): void { + throw new Error('Method not implemented.'); + } + registerNavigationProvider(provider: INavigationProvider): void { + throw new Error('Method not implemented.'); + } + getNavigationProvider(notebookUri: URI): INavigationProvider { + throw new Error('Method not implemented.'); + } + getSupportedFileExtensions(): string[] { + throw new Error('Method not implemented.'); + } + getProvidersForFileType(fileType: string): string[] { + throw new Error('Method not implemented.'); + } + getStandardKernelsForProvider(provider: string): nb.IStandardKernel[] { + throw new Error('Method not implemented.'); + } + getOrCreateNotebookManager(providerId: string, uri: URI): Thenable { + throw new Error('Method not implemented.'); + } + addNotebookEditor(editor: INotebookEditor): void { + throw new Error('Method not implemented.'); + } + removeNotebookEditor(editor: INotebookEditor): void { + throw new Error('Method not implemented.'); + } + listNotebookEditors(): INotebookEditor[] { + throw new Error('Method not implemented.'); + } + findNotebookEditor(notebookUri: URI): INotebookEditor { + throw new Error('Method not implemented.'); + } + getMimeRegistry(): RenderMimeRegistry { + throw new Error('Method not implemented.'); + } + renameNotebookEditor(oldUri: URI, newUri: URI, currentEditor: INotebookEditor): void { + throw new Error('Method not implemented.'); + } + isNotebookTrustCached(notebookUri: URI, isDirty: boolean): Promise { + throw new Error('Method not implemented.'); + } + serializeNotebookStateChange(notebookUri: URI, changeType: NotebookChangeType, cell?: ICellModel): void { + throw new Error('Method not implemented.'); + } + navigateTo(notebookUri: URI, sectionId: string): void { + throw new Error('Method not implemented.'); + } +} diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookUtils.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookUtils.test.ts index a9b3c41fad..1252605ba9 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookUtils.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookUtils.test.ts @@ -3,86 +3,204 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IConnectionProfile } from 'azdata'; import * as assert from 'assert'; +import * as TypeMoq from 'typemoq'; -import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService'; -import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; -import { formatServerNameWithDatabaseNameForAttachTo, getServerFromFormattedAttachToName, getDatabaseFromFormattedAttachToName } from 'sql/workbench/contrib/notebook/browser/models/notebookUtils'; -import { mssqlProviderName } from 'sql/platform/connection/common/constants'; +import { nb, ServerInfo } from 'azdata'; +import { tryMatchCellMagic, getHostAndPortFromEndpoint, isStream, getProvidersForFileName, asyncForEach, clusterEndpointsProperty, getClusterEndpoints, RawEndpoint, IEndpoint, getStandardKernelsForProvider, IStandardKernelWithProvider } from 'sql/workbench/contrib/notebook/browser/models/notebookUtils'; +import { INotebookService, DEFAULT_NOTEBOOK_FILETYPE, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService'; +import { NotebookServiceStub } from 'sql/workbench/contrib/notebook/test/electron-browser/common'; suite('notebookUtils', function (): void { - let conn: IConnectionProfile = { - connectionName: '', - serverName: '', - databaseName: '', - userName: '', - password: '', - authenticationType: '', - savePassword: true, - groupFullName: '', - groupId: '', - providerName: mssqlProviderName, - saveProfile: true, - id: '', - options: {}, - azureTenantId: undefined, - azureAccount: undefined + const mockNotebookService = TypeMoq.Mock.ofType(NotebookServiceStub); + const defaultTestProvider = 'testDefaultProvider'; + const testProvider = 'testProvider'; + const testKernel: nb.IStandardKernel = { + name: 'testName', + displayName: 'testDisplayName', + connectionProviderIds: ['testId1', 'testId2'] }; - test('Should format server and database name correctly for attach to', async function (): Promise { - let capabilitiesService = new TestCapabilitiesService(); - let connProfile = new ConnectionProfile(capabilitiesService, conn); - connProfile.serverName = 'serverName'; - connProfile.databaseName = 'databaseName'; - let attachToNameFormatted = formatServerNameWithDatabaseNameForAttachTo(connProfile); - assert.equal(attachToNameFormatted, 'serverName (databaseName)'); + function setupMockNotebookService() { + mockNotebookService.setup(n => n.getProvidersForFileType(TypeMoq.It.isAnyString())) + .returns((fileName, service) => { + if (fileName === DEFAULT_NOTEBOOK_FILETYPE) { + return [defaultTestProvider]; + } else { + return [testProvider]; + } + }); + + // getStandardKernelsForProvider + mockNotebookService.setup(n => n.getStandardKernelsForProvider(TypeMoq.It.isAnyString())) + .returns((provider) => { + return [testKernel]; + }); + } + + test('isStream Test', async function (): Promise { + let result = isStream({ + output_type: 'stream' + }); + assert.strictEqual(result, true); + + result = isStream({ + output_type: 'display_data' + }); + assert.strictEqual(result, false); + + result = isStream({ + output_type: undefined + }); + assert.strictEqual(result, false); }); - test('Should format server name correctly for attach to', async function (): Promise { - let capabilitiesService = new TestCapabilitiesService(); - let connProfile = new ConnectionProfile(capabilitiesService, conn); - connProfile.serverName = 'serverName'; - let attachToNameFormatted = formatServerNameWithDatabaseNameForAttachTo(connProfile); - assert.equal(attachToNameFormatted, 'serverName'); + test('getProvidersForFileName Test', async function (): Promise { + setupMockNotebookService(); + + let result = getProvidersForFileName('', mockNotebookService.object); + assert.deepStrictEqual(result, [defaultTestProvider]); + + result = getProvidersForFileName('fileWithoutExtension', mockNotebookService.object); + assert.deepStrictEqual(result, [defaultTestProvider]); + + result = getProvidersForFileName('test.sql', mockNotebookService.object); + assert.deepStrictEqual(result, [testProvider]); + + mockNotebookService.setup(n => n.getProvidersForFileType(TypeMoq.It.isAnyString())) + .returns(() => undefined); + result = getProvidersForFileName('test.sql', mockNotebookService.object); + assert.deepStrictEqual(result, [DEFAULT_NOTEBOOK_PROVIDER]); }); - test('Should format server name correctly for attach to when database is undefined', async function (): Promise { - let capabilitiesService = new TestCapabilitiesService(); - let connProfile = new ConnectionProfile(capabilitiesService, conn); - connProfile.serverName = 'serverName'; - connProfile.databaseName = undefined; - let attachToNameFormatted = formatServerNameWithDatabaseNameForAttachTo(connProfile); - assert.equal(attachToNameFormatted, 'serverName'); + test('getStandardKernelsForProvider Test', async function (): Promise { + setupMockNotebookService(); + + let result = getStandardKernelsForProvider(undefined, undefined); + assert.deepStrictEqual(result, []); + + result = getStandardKernelsForProvider(undefined, mockNotebookService.object); + assert.deepStrictEqual(result, []); + + result = getStandardKernelsForProvider('testProvider', undefined); + assert.deepStrictEqual(result, []); + + result = getStandardKernelsForProvider('testProvider', mockNotebookService.object); + assert.deepStrictEqual(result, [{ + name: 'testName', + displayName: 'testDisplayName', + connectionProviderIds: ['testId1', 'testId2'], + notebookProvider: 'testProvider' + }]); }); - test('Should format server name as empty string when server/database are undefined', async function (): Promise { - let capabilitiesService = new TestCapabilitiesService(); - let connProfile = new ConnectionProfile(capabilitiesService, conn); - connProfile.serverName = undefined; - connProfile.databaseName = undefined; - let attachToNameFormatted = formatServerNameWithDatabaseNameForAttachTo(connProfile); - assert.equal(attachToNameFormatted, ''); + test('tryMatchCellMagic Test', async function (): Promise { + let result = tryMatchCellMagic(undefined); + assert.strictEqual(result, undefined); + + result = tryMatchCellMagic(' '); + assert.strictEqual(result, null); + + result = tryMatchCellMagic('text'); + assert.strictEqual(result, null); + + result = tryMatchCellMagic('%%sql'); + assert.strictEqual(result, 'sql'); + + result = tryMatchCellMagic('%%sql\nselect @@VERSION\nselect * from TestTable'); + assert.strictEqual(result, 'sql'); + + result = tryMatchCellMagic('%%sql\n%%help'); + assert.strictEqual(result, 'sql'); + + result = tryMatchCellMagic('%%'); + assert.strictEqual(result, null); + + result = tryMatchCellMagic('%% sql'); + assert.strictEqual(result, null); }); - test('Should extract server name when no database specified', async function (): Promise { - let serverName = getServerFromFormattedAttachToName('serverName'); - let databaseName = getDatabaseFromFormattedAttachToName('serverName'); - assert.equal(serverName, 'serverName'); - assert.equal(databaseName, ''); + test('asyncForEach Test', async function (): Promise { + let totalResult = 0; + await asyncForEach([1, 2, 3, 4], async (value) => { + totalResult += value; + }); + assert.strictEqual(totalResult, 10); + + totalResult = 0; + await asyncForEach([], async (value) => { + totalResult += value; + }); + assert.strictEqual(totalResult, 0); + + // Shouldn't throw exceptions for these cases + await asyncForEach(undefined, async (value) => { + totalResult += value; + }); + assert.strictEqual(totalResult, 0); + + await asyncForEach([1, 2, 3, 4], undefined); }); - test('Should extract server and database name', async function (): Promise { - let serverName = getServerFromFormattedAttachToName('serverName (databaseName)'); - let databaseName = getDatabaseFromFormattedAttachToName('serverName (databaseName)'); - assert.equal(serverName, 'serverName'); - assert.equal(databaseName, 'databaseName'); + test('getClusterEndpoints Test', async function (): Promise { + let serverInfo = { + options: {} + }; + + serverInfo.options[clusterEndpointsProperty] = undefined; + let result = getClusterEndpoints(serverInfo); + assert.deepStrictEqual(result, []); + + serverInfo.options[clusterEndpointsProperty] = []; + result = getClusterEndpoints(serverInfo); + assert.deepStrictEqual(result, []); + + let testEndpoint = { + serviceName: 'testName', + description: 'testDescription', + endpoint: 'testEndpoint', + protocol: 'testProtocol', + ipAddress: 'testIpAddress', + port: 1433 + }; + serverInfo.options[clusterEndpointsProperty] = [testEndpoint]; + result = getClusterEndpoints(serverInfo); + assert.deepStrictEqual(result, [{ + serviceName: testEndpoint.serviceName, + description: testEndpoint.description, + endpoint: testEndpoint.endpoint, + protocol: testEndpoint.protocol + }]); + + testEndpoint.endpoint = undefined; + result = getClusterEndpoints(serverInfo); + assert.deepStrictEqual(result, [{ + serviceName: testEndpoint.serviceName, + description: testEndpoint.description, + endpoint: 'https://testIpAddress:1433', + protocol: testEndpoint.protocol + }]); }); - test('Should extract server and database name with other parentheses', async function (): Promise { - let serverName = getServerFromFormattedAttachToName('serv()erName (databaseName)'); - let databaseName = getDatabaseFromFormattedAttachToName('serv()erName (databaseName)'); - assert.equal(serverName, 'serv()erName'); - assert.equal(databaseName, 'databaseName'); + test('getHostAndPortFromEndpoint Test', async function (): Promise { + let result = getHostAndPortFromEndpoint('https://localhost:1433'); + assert.strictEqual(result.host, 'localhost'); + assert.strictEqual(result.port, '1433'); + + result = getHostAndPortFromEndpoint('tcp://localhost,12345'); + assert.strictEqual(result.host, 'localhost'); + assert.strictEqual(result.port, '12345'); + + result = getHostAndPortFromEndpoint('tcp://localhost'); + assert.strictEqual(result.host, 'localhost'); + assert.strictEqual(result.port, undefined); + + result = getHostAndPortFromEndpoint('localhost'); + assert.strictEqual(result.host, ''); + assert.strictEqual(result.port, undefined); + + result = getHostAndPortFromEndpoint('localhost:1433'); + assert.strictEqual(result.host, ''); + assert.strictEqual(result.port, undefined); }); });