Add code coverage tests for notebookUtils (#8524)

This commit is contained in:
Cory Rivera
2019-12-02 14:19:37 -08:00
committed by GitHub
parent b8bc629970
commit 8cc60fde90
3 changed files with 250 additions and 110 deletions

View File

@@ -6,13 +6,10 @@
import * as path from 'vs/base/common/path'; import * as path from 'vs/base/common/path';
import { nb, ServerInfo } from 'azdata'; import { nb, ServerInfo } from 'azdata';
import { DEFAULT_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_FILETYPE, INotebookService } from 'sql/workbench/services/notebook/browser/notebookService'; 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 { URI } from 'vs/base/common/uri';
import { startsWith } from 'vs/base/common/strings'; import { startsWith } from 'vs/base/common/strings';
import { assign } from 'vs/base/common/objects'; import { assign } from 'vs/base/common/objects';
export const clusterEndpointsProperty = 'clusterEndpoints'; export const clusterEndpointsProperty = 'clusterEndpoints';
export const hadoopEndpointNameGateway = 'gateway'; export const hadoopEndpointNameGateway = 'gateway';
/** /**
@@ -56,26 +53,6 @@ export function getStandardKernelsForProvider(providerId: string, notebookServic
return <IStandardKernelWithProvider[]>(standardKernels); return <IStandardKernelWithProvider[]>(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 { export interface IStandardKernelWithProvider {
readonly name: string; readonly name: string;
readonly displayName: string; readonly displayName: string;
@@ -102,30 +79,12 @@ export function tryMatchCellMagic(input: string): string {
return magicName; return magicName;
} }
export async function asyncForEach(array: any, callback: any): Promise<any> { export async function asyncForEach(array: any[], callback: Function): Promise<any> {
for (let index = 0; index < array.length; index++) { if (array && callback) {
await callback(array[index], index, array); 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);
} }
htmlContentCopy = htmlContentCopy.slice(pathEndIndex);
} }
return htmlContent;
} }
export function getClusterEndpoints(serverInfo: ServerInfo): IEndpoint[] | undefined { export function getClusterEndpoints(serverInfo: ServerInfo): IEndpoint[] | undefined {
@@ -163,7 +122,7 @@ export function getHostAndPortFromEndpoint(endpoint: string): HostAndIp {
}; };
} }
interface RawEndpoint { export interface RawEndpoint {
serviceName: string; serviceName: string;
description?: string; description?: string;
endpoint?: string; endpoint?: string;

View File

@@ -8,9 +8,11 @@ import { nb, IConnectionProfile } from 'azdata';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { INotebookModel, ICellModel, IClientSession, IDefaultConnection, NotebookContentChange } from 'sql/workbench/contrib/notebook/browser/models/modelInterfaces'; 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 { 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 { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IStandardKernelWithProvider } from 'sql/workbench/contrib/notebook/browser/models/notebookUtils'; 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 { export class NotebookModelStub implements INotebookModel {
constructor(private _languageInfo?: nb.ILanguageInfo) { constructor(private _languageInfo?: nb.ILanguageInfo) {
@@ -139,3 +141,64 @@ export class ServerManagerStub implements nb.ServerManager {
return this.result; return this.result;
} }
} }
export class NotebookServiceStub implements INotebookService {
_serviceBrand: undefined;
onNotebookEditorAdd: Event<INotebookEditor>;
onNotebookEditorRemove: Event<INotebookEditor>;
onNotebookEditorRename: Event<INotebookEditor>;
isRegistrationComplete: boolean;
registrationComplete: Promise<void>;
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<INotebookManager> {
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<boolean> {
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.');
}
}

View File

@@ -3,86 +3,204 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 assert from 'assert';
import * as TypeMoq from 'typemoq';
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService'; import { nb, ServerInfo } from 'azdata';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { tryMatchCellMagic, getHostAndPortFromEndpoint, isStream, getProvidersForFileName, asyncForEach, clusterEndpointsProperty, getClusterEndpoints, RawEndpoint, IEndpoint, getStandardKernelsForProvider, IStandardKernelWithProvider } from 'sql/workbench/contrib/notebook/browser/models/notebookUtils';
import { formatServerNameWithDatabaseNameForAttachTo, getServerFromFormattedAttachToName, getDatabaseFromFormattedAttachToName } from 'sql/workbench/contrib/notebook/browser/models/notebookUtils'; import { INotebookService, DEFAULT_NOTEBOOK_FILETYPE, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService';
import { mssqlProviderName } from 'sql/platform/connection/common/constants'; import { NotebookServiceStub } from 'sql/workbench/contrib/notebook/test/electron-browser/common';
suite('notebookUtils', function (): void { suite('notebookUtils', function (): void {
let conn: IConnectionProfile = { const mockNotebookService = TypeMoq.Mock.ofType<INotebookService>(NotebookServiceStub);
connectionName: '', const defaultTestProvider = 'testDefaultProvider';
serverName: '', const testProvider = 'testProvider';
databaseName: '', const testKernel: nb.IStandardKernel = {
userName: '', name: 'testName',
password: '', displayName: 'testDisplayName',
authenticationType: '', connectionProviderIds: ['testId1', 'testId2']
savePassword: true,
groupFullName: '',
groupId: '',
providerName: mssqlProviderName,
saveProfile: true,
id: '',
options: {},
azureTenantId: undefined,
azureAccount: undefined
}; };
test('Should format server and database name correctly for attach to', async function (): Promise<void> { function setupMockNotebookService() {
let capabilitiesService = new TestCapabilitiesService(); mockNotebookService.setup(n => n.getProvidersForFileType(TypeMoq.It.isAnyString()))
let connProfile = new ConnectionProfile(capabilitiesService, conn); .returns((fileName, service) => {
connProfile.serverName = 'serverName'; if (fileName === DEFAULT_NOTEBOOK_FILETYPE) {
connProfile.databaseName = 'databaseName'; return [defaultTestProvider];
let attachToNameFormatted = formatServerNameWithDatabaseNameForAttachTo(connProfile); } else {
assert.equal(attachToNameFormatted, 'serverName (databaseName)'); return [testProvider];
}
});
// getStandardKernelsForProvider
mockNotebookService.setup(n => n.getStandardKernelsForProvider(TypeMoq.It.isAnyString()))
.returns((provider) => {
return [testKernel];
});
}
test('isStream Test', async function (): Promise<void> {
let result = isStream(<nb.ICellOutput>{
output_type: 'stream'
});
assert.strictEqual(result, true);
result = isStream(<nb.ICellOutput>{
output_type: 'display_data'
});
assert.strictEqual(result, false);
result = isStream(<nb.ICellOutput>{
output_type: undefined
});
assert.strictEqual(result, false);
}); });
test('Should format server name correctly for attach to', async function (): Promise<void> { test('getProvidersForFileName Test', async function (): Promise<void> {
let capabilitiesService = new TestCapabilitiesService(); setupMockNotebookService();
let connProfile = new ConnectionProfile(capabilitiesService, conn);
connProfile.serverName = 'serverName'; let result = getProvidersForFileName('', mockNotebookService.object);
let attachToNameFormatted = formatServerNameWithDatabaseNameForAttachTo(connProfile); assert.deepStrictEqual(result, [defaultTestProvider]);
assert.equal(attachToNameFormatted, 'serverName');
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<void> { test('getStandardKernelsForProvider Test', async function (): Promise<void> {
let capabilitiesService = new TestCapabilitiesService(); setupMockNotebookService();
let connProfile = new ConnectionProfile(capabilitiesService, conn);
connProfile.serverName = 'serverName'; let result = getStandardKernelsForProvider(undefined, undefined);
connProfile.databaseName = undefined; assert.deepStrictEqual(result, []);
let attachToNameFormatted = formatServerNameWithDatabaseNameForAttachTo(connProfile);
assert.equal(attachToNameFormatted, 'serverName'); result = getStandardKernelsForProvider(undefined, mockNotebookService.object);
assert.deepStrictEqual(result, []);
result = getStandardKernelsForProvider('testProvider', undefined);
assert.deepStrictEqual(result, []);
result = getStandardKernelsForProvider('testProvider', mockNotebookService.object);
assert.deepStrictEqual(result, [<IStandardKernelWithProvider>{
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<void> { test('tryMatchCellMagic Test', async function (): Promise<void> {
let capabilitiesService = new TestCapabilitiesService(); let result = tryMatchCellMagic(undefined);
let connProfile = new ConnectionProfile(capabilitiesService, conn); assert.strictEqual(result, undefined);
connProfile.serverName = undefined;
connProfile.databaseName = undefined; result = tryMatchCellMagic(' ');
let attachToNameFormatted = formatServerNameWithDatabaseNameForAttachTo(connProfile); assert.strictEqual(result, null);
assert.equal(attachToNameFormatted, '');
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<void> { test('asyncForEach Test', async function (): Promise<void> {
let serverName = getServerFromFormattedAttachToName('serverName'); let totalResult = 0;
let databaseName = getDatabaseFromFormattedAttachToName('serverName'); await asyncForEach([1, 2, 3, 4], async (value) => {
assert.equal(serverName, 'serverName'); totalResult += value;
assert.equal(databaseName, ''); });
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<void> { test('getClusterEndpoints Test', async function (): Promise<void> {
let serverName = getServerFromFormattedAttachToName('serverName (databaseName)'); let serverInfo = <ServerInfo>{
let databaseName = getDatabaseFromFormattedAttachToName('serverName (databaseName)'); options: {}
assert.equal(serverName, 'serverName'); };
assert.equal(databaseName, 'databaseName');
serverInfo.options[clusterEndpointsProperty] = undefined;
let result = getClusterEndpoints(serverInfo);
assert.deepStrictEqual(result, []);
serverInfo.options[clusterEndpointsProperty] = [];
result = getClusterEndpoints(serverInfo);
assert.deepStrictEqual(result, []);
let testEndpoint = <RawEndpoint>{
serviceName: 'testName',
description: 'testDescription',
endpoint: 'testEndpoint',
protocol: 'testProtocol',
ipAddress: 'testIpAddress',
port: 1433
};
serverInfo.options[clusterEndpointsProperty] = [testEndpoint];
result = getClusterEndpoints(serverInfo);
assert.deepStrictEqual(result, [<IEndpoint>{
serviceName: testEndpoint.serviceName,
description: testEndpoint.description,
endpoint: testEndpoint.endpoint,
protocol: testEndpoint.protocol
}]);
testEndpoint.endpoint = undefined;
result = getClusterEndpoints(serverInfo);
assert.deepStrictEqual(result, [<IEndpoint>{
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<void> { test('getHostAndPortFromEndpoint Test', async function (): Promise<void> {
let serverName = getServerFromFormattedAttachToName('serv()erName (databaseName)'); let result = getHostAndPortFromEndpoint('https://localhost:1433');
let databaseName = getDatabaseFromFormattedAttachToName('serv()erName (databaseName)'); assert.strictEqual(result.host, 'localhost');
assert.equal(serverName, 'serv()erName'); assert.strictEqual(result.port, '1433');
assert.equal(databaseName, 'databaseName');
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);
}); });
}); });