Add basic notebook model tests (#3396)

- Ported from the extension
- Only adding tests that related to the internally implemented functionality, not to anything provider-specific.
This commit is contained in:
Kevin Cunnane
2018-12-04 10:01:10 -08:00
committed by GitHub
parent 27a978cba5
commit 8d8be27f22
10 changed files with 1214 additions and 3 deletions

View File

@@ -90,6 +90,7 @@
"@types/mocha": "2.2.39",
"@types/sanitize-html": "^1.18.2",
"@types/semver": "5.3.30",
"@types/should": "^13.0.0",
"@types/sinon": "1.16.34",
"@types/winreg": "^1.2.30",
"asar": "^0.14.0",
@@ -148,8 +149,10 @@
"queue": "3.0.6",
"remap-istanbul": "^0.6.4",
"rimraf": "^2.2.8",
"should": "^13.2.3",
"sinon": "^1.17.2",
"source-map": "^0.4.4",
"temp-write": "^3.4.0",
"tslint": "^5.9.1",
"typemoq": "^0.3.2",
"typescript": "2.9.2",

View File

@@ -40,7 +40,7 @@ export class SessionManager implements nb.SessionManager {
}
}
class EmptySession implements nb.ISession {
export class EmptySession implements nb.ISession {
private _kernel: EmptyKernel;
private _defaultKernelLoaded = false;
@@ -146,7 +146,7 @@ class EmptyKernel implements nb.IKernel {
}
}
class EmptyFuture implements FutureInternal {
export class EmptyFuture implements FutureInternal {
get inProgress(): boolean {

View File

@@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { nb, IConnectionProfile } from 'sqlops';
import { Event, Emitter } from 'vs/base/common/event';
import { INotebookModel, ICellModel, IClientSession, IDefaultConnection } from 'sql/parts/notebook/models/modelInterfaces';
import { NotebookChangeType, CellType } from 'sql/parts/notebook/models/contracts';
import { INotebookManager } from 'sql/services/notebook/notebookService';
export class NotebookModelStub implements INotebookModel {
constructor(private _languageInfo?: nb.ILanguageInfo) {
}
public trustedMode: boolean;
public get languageInfo(): nb.ILanguageInfo {
return this._languageInfo;
}
onCellChange(cell: ICellModel, change: NotebookChangeType): void {
// Default: do nothing
}
get cells(): ReadonlyArray<ICellModel> {
throw new Error('method not implemented.');
}
get clientSession(): IClientSession {
throw new Error('method not implemented.');
}
get notebookManager(): INotebookManager {
throw new Error('method not implemented.');
}
get kernelChanged(): Event<nb.IKernelChangedArgs> {
throw new Error('method not implemented.');
}
get kernelsChanged(): Event<nb.IKernelSpec> {
throw new Error('method not implemented.');
} get defaultKernel(): nb.IKernelSpec {
throw new Error('method not implemented.');
}
get contextsChanged(): Event<void> {
throw new Error('method not implemented.');
}
get specs(): nb.IAllKernels {
throw new Error('method not implemented.');
}
get contexts(): IDefaultConnection {
throw new Error('method not implemented.');
}
changeKernel(displayName: string): void {
throw new Error('Method not implemented.');
}
changeContext(host: string, connection?: IConnectionProfile): void {
throw new Error('Method not implemented.');
}
findCellIndex(cellModel: ICellModel): number {
throw new Error('Method not implemented.');
}
addCell(cellType: CellType, index?: number): void {
throw new Error('Method not implemented.');
}
deleteCell(cellModel: ICellModel): void {
throw new Error('Method not implemented.');
}
saveModel(): Promise<boolean> {
throw new Error('Method not implemented.');
}
}
export class NotebookManagerStub implements INotebookManager {
providerId: string;
contentManager: nb.ContentManager;
sessionManager: nb.SessionManager;
serverManager: nb.ServerManager;
}
export class ServerManagerStub implements nb.ServerManager {
public onServerStartedEmitter = new Emitter<void>();
onServerStarted: Event<void> = this.onServerStartedEmitter.event;
isStarted: boolean = false;
calledStart: boolean = false;
calledEnd: boolean = false;
public result: Promise<void> = undefined;
startServer(): Promise<void> {
this.calledStart = true;
return this.result;
}
stopServer(): Promise<void> {
this.calledEnd = true;
return this.result;
}
}

View File

@@ -0,0 +1,262 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import { nb } from 'sqlops';
import * as objects from 'vs/base/common/objects';
import { CellTypes } from 'sql/parts/notebook/models/contracts';
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
import { NotebookModelStub } from '../common';
import { EmptyFuture } from 'sql/services/notebook/sessionManager';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
describe('Cell Model', function (): void {
let factory = new ModelFactory();
it('Should set default values if none defined', async function (): Promise<void> {
let cell = factory.createCell(undefined, undefined);
should(cell.cellType).equal(CellTypes.Code);
should(cell.source).equal('');
});
it('Should update values', async function (): Promise<void> {
let cell = factory.createCell(undefined, undefined);
cell.language = 'sql';
should(cell.language).equal('sql');
cell.source = 'abcd';
should(cell.source).equal('abcd');
});
it('Should match ICell values if defined', async function (): Promise<void> {
let output: nb.IStreamResult = {
output_type: 'stream',
text: 'Some output',
name: 'stdout'
};
let cellData: nb.ICell = {
cell_type: CellTypes.Markdown,
source: 'some *markdown*',
outputs: [output],
metadata: { language: 'python'},
execution_count: 1
};
let cell = factory.createCell(cellData, undefined);
should(cell.cellType).equal(cellData.cell_type);
should(cell.source).equal(cellData.source);
should(cell.outputs).have.length(1);
should(cell.outputs[0].output_type).equal('stream');
should((<nb.IStreamResult>cell.outputs[0]).text).equal('Some output');
});
it('Should set cell language to python if defined as python in languageInfo', async function (): Promise<void> {
let cellData: nb.ICell = {
cell_type: CellTypes.Code,
source: 'print(\'1\')',
metadata: { language: 'python'},
execution_count: 1
};
let notebookModel = new NotebookModelStub({
name: 'python',
version: '',
mimetype: ''
});
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
should(cell.language).equal('python');
});
it('Should set cell language to python if defined as pyspark in languageInfo', async function (): Promise<void> {
let cellData: nb.ICell = {
cell_type: CellTypes.Code,
source: 'print(\'1\')',
metadata: { language: 'python'},
execution_count: 1
};
let notebookModel = new NotebookModelStub({
name: 'pyspark',
version: '',
mimetype: ''
});
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
should(cell.language).equal('python');
});
it('Should set cell language to scala if defined as scala in languageInfo', async function (): Promise<void> {
let cellData: nb.ICell = {
cell_type: CellTypes.Code,
source: 'print(\'1\')',
metadata: { language: 'python'},
execution_count: 1
};
let notebookModel = new NotebookModelStub({
name: 'scala',
version: '',
mimetype: ''
});
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
should(cell.language).equal('scala');
});
it('Should set cell language to python if no language defined', async function (): Promise<void> {
let cellData: nb.ICell = {
cell_type: CellTypes.Code,
source: 'print(\'1\')',
metadata: { language: 'python'},
execution_count: 1
};
let notebookModel = new NotebookModelStub({
name: '',
version: '',
mimetype: ''
});
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
should(cell.language).equal('python');
});
it('Should match cell language to language specified if unknown language defined in languageInfo', async function (): Promise<void> {
let cellData: nb.ICell = {
cell_type: CellTypes.Code,
source: 'std::cout << "hello world";',
metadata: { language: 'python'},
execution_count: 1
};
let notebookModel = new NotebookModelStub({
name: 'cplusplus',
version: '',
mimetype: ''
});
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
should(cell.language).equal('cplusplus');
});
it('Should match cell language to mimetype name is not supplied in languageInfo', async function (): Promise<void> {
let cellData: nb.ICell = {
cell_type: CellTypes.Code,
source: 'print(\'1\')',
metadata: { language: 'python'},
execution_count: 1
};
let notebookModel = new NotebookModelStub({
name: '',
version: '',
mimetype: 'x-scala'
});
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
should(cell.language).equal('scala');
});
describe('Model Future handling', function(): void {
let future: TypeMoq.Mock<EmptyFuture>;
let cell: ICellModel;
beforeEach(() => {
future = TypeMoq.Mock.ofType(EmptyFuture);
cell = factory.createCell({
cell_type: CellTypes.Code,
source: 'print "Hello"',
metadata: { language: 'python'},
execution_count: 1
}, {
notebook: new NotebookModelStub({
name: '',
version: '',
mimetype: 'x-scala'
}),
isTrusted: false
});
});
it('should send and handle incoming messages', async () => {
// Given a future
let onReply: nb.MessageHandler<nb.IShellMessage>;
let onIopub: nb.MessageHandler<nb.IIOPubMessage>;
future.setup(f => f.setReplyHandler(TypeMoq.It.isAny())).callback((handler) => onReply = handler);
future.setup(f => f.setIOPubHandler(TypeMoq.It.isAny())).callback((handler) => onIopub = handler);
let outputs: ReadonlyArray<nb.ICellOutput> = undefined;
cell.onOutputsChanged((o => outputs = o));
// When I set it on the cell
cell.setFuture(future.object);
// Then I expect outputs to have been cleared
should(outputs).have.length(0);
should(onReply).not.be.undefined();
// ... And when I send an IoPub message
let message: nb.IIOPubMessage = {
channel: 'iopub',
type: 'iopub',
parent_header: undefined,
metadata: undefined,
header: <nb.IHeader> {
msg_type: 'stream'
},
content: {
text: 'Printed hello world'
}
};
onIopub.handle(message);
// Then I expect an output to be added
should(outputs).have.length(1);
should(outputs[0].output_type).equal('stream');
message = objects.deepClone(message);
message.header.msg_type = 'display_data';
onIopub.handle(message);
should(outputs[1].output_type).equal('display_data');
// ... TODO: And when I sent a reply I expect it to be processed.
});
it('should delete transient tag while handling incoming messages', async () => {
// Given a future
let onIopub: nb.MessageHandler<nb.IIOPubMessage>;
future.setup(f => f.setIOPubHandler(TypeMoq.It.isAny())).callback((handler) => onIopub = handler);
let outputs: ReadonlyArray<nb.ICellOutput> = undefined;
cell.onOutputsChanged((o => outputs = o));
//Set the future
cell.setFuture(future.object);
// ... And when I send an IoPub message
let message: nb.IIOPubMessage = {
channel: 'iopub',
type: 'iopub',
parent_header: undefined,
metadata: undefined,
header: <nb.IHeader> {
msg_type: 'display_data'
},
content: {
text: 'Printed hello world',
transient: 'transient data'
}
};
onIopub.handle(message);
//Output array's length should be 1
//'transient' tag should no longer exist in the output
should(outputs).have.length(1);
should(outputs[0]['transient']).be.undefined();
});
it('should dispose old future', async () => {
let oldFuture = TypeMoq.Mock.ofType(EmptyFuture);
cell.setFuture(oldFuture.object);
cell.setFuture(future.object);
oldFuture.verify(f => f.dispose(), TypeMoq.Times.once());
});
});
});

View File

@@ -0,0 +1,202 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import { nb } from 'sqlops';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import URI from 'vs/base/common/uri';
import { ClientSession } from 'sql/parts/notebook/models/clientSession';
import { SessionManager, EmptySession } from 'sql/services/notebook/sessionManager';
import { NotebookManagerStub, ServerManagerStub } from 'sqltest/parts/notebook/common';
describe('Client Session', function(): void {
let path = URI.file('my/notebook.ipynb');
let notebookManager: NotebookManagerStub;
let serverManager: ServerManagerStub;
let mockSessionManager: TypeMoq.Mock<nb.SessionManager>;
let notificationService: TypeMoq.Mock<INotificationService>;
let session: ClientSession;
let remoteSession: ClientSession;
beforeEach(() => {
serverManager = new ServerManagerStub();
mockSessionManager = TypeMoq.Mock.ofType(SessionManager);
notebookManager = new NotebookManagerStub();
notebookManager.serverManager = serverManager;
notebookManager.sessionManager = mockSessionManager.object;
notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose);
session = new ClientSession({
notebookManager: notebookManager,
notebookUri: path,
notificationService: notificationService.object
});
let serverlessNotebookManager = new NotebookManagerStub();
serverlessNotebookManager.sessionManager = mockSessionManager.object;
remoteSession = new ClientSession({
notebookManager: serverlessNotebookManager,
notebookUri: path,
notificationService: notificationService.object
});
});
it('Should set path, isReady and ready on construction', function(): void {
should(session.notebookUri).equal(path);
should(session.ready).not.be.undefined();
should(session.isReady).be.false();
should(session.status).equal('starting');
should(session.isInErrorState).be.false();
should(session.errorMessage).be.undefined();
});
it('Should call on serverManager startup if set', async function(): Promise<void> {
// Given I have a serverManager that starts successfully
serverManager.result = Promise.resolve();
should(session.isReady).be.false();
// When I kick off initialization
await session.initialize();
// Then I expect ready to be completed too
await session.ready;
should(serverManager.calledStart).be.true();
should(session.isReady).be.true();
});
it('Should go to error state if serverManager startup fails', async function(): Promise<void> {
// Given I have a serverManager that fails to start
serverManager.result = Promise.reject('error');
should(session.isInErrorState).be.false();
// When I initialize
await session.initialize();
// Then I expect ready to complete, but isInErrorState to be true
await session.ready;
should(session.isReady).be.true();
should(serverManager.calledStart).be.true();
should(session.isInErrorState).be.true();
should(session.errorMessage).equal('error');
});
it('Should be ready when session manager is ready', async function(): Promise<void> {
serverManager.result = new Promise((resolve) => {
serverManager.isStarted = true;
resolve();
});
mockSessionManager.setup(s => s.ready).returns(() => Promise.resolve());
// When I call initialize
await session.initialize();
// Then
should(session.isReady).be.true();
should(session.isInErrorState).be.false();
await session.ready;
});
it('Should be in error state if server fails to start', async function(): Promise<void> {
serverManager.result = new Promise((resolve) => {
serverManager.isStarted = false;
resolve();
});
mockSessionManager.setup(s => s.ready).returns(() => Promise.resolve());
// When I call initialize
await session.initialize();
// Then
await session.ready;
should(session.isReady).be.true();
should(session.isInErrorState).be.true();
});
it('Should go to error state if sessionManager fails', async function(): Promise<void> {
serverManager.isStarted = true;
mockSessionManager.setup(s => s.isReady).returns(() => false);
mockSessionManager.setup(s => s.ready).returns(() => Promise.reject('error'));
// When I call initialize
await session.initialize();
// Then
should(session.isReady).be.true();
should(session.isInErrorState).be.true();
should(session.errorMessage).equal('error');
});
it('Should start session automatically if kernel preference requests it', async function(): Promise<void> {
serverManager.isStarted = true;
mockSessionManager.setup(s => s.ready).returns(() => Promise.resolve());
let sessionMock = TypeMoq.Mock.ofType(EmptySession);
let startOptions: nb.ISessionOptions = undefined;
mockSessionManager.setup(s => s.startNew(TypeMoq.It.isAny())).returns((options) => {
startOptions = options;
return Promise.resolve(sessionMock.object);
});
// When I call initialize after defining kernel preferences
session.kernelPreference = {
shouldStart: true,
name: 'python'
};
await session.initialize();
// Then
should(session.isReady).be.true();
should(session.isInErrorState).be.false();
should(startOptions.kernelName).equal('python');
should(startOptions.path).equal(path.fsPath);
});
it('Should shutdown session even if no serverManager is set', async function(): Promise<void> {
// Given a session against a remote server
let expectedId = 'abc';
mockSessionManager.setup(s => s.isReady).returns(() => true);
mockSessionManager.setup(s => s.shutdown(TypeMoq.It.isAny())).returns(() => Promise.resolve());
let sessionMock = TypeMoq.Mock.ofType(EmptySession);
sessionMock.setup(s => s.id).returns(() => expectedId);
mockSessionManager.setup(s => s.startNew(TypeMoq.It.isAny())).returns(() => Promise.resolve(sessionMock.object));
remoteSession.kernelPreference = {
shouldStart: true,
name: 'python'
};
await remoteSession.initialize();
// When I call shutdown
await remoteSession.shutdown();
// Then
mockSessionManager.verify(s => s.shutdown(TypeMoq.It.isValue(expectedId)), TypeMoq.Times.once());
});
it('Should stop server if server is set', async function(): Promise<void> {
// Given a kernel has been started
serverManager.isStarted = true;
serverManager.result = Promise.resolve();
mockSessionManager.setup(s => s.isReady).returns(() => true);
mockSessionManager.setup(s => s.shutdown(TypeMoq.It.isAny())).returns(() => Promise.resolve());
await session.initialize();
// When I call shutdown
await session.shutdown();
// Then
should(serverManager.calledEnd).be.true();
});
});

View File

@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as path from 'path';
import { nb } from 'sqlops';
import URI from 'vs/base/common/uri';
import * as tempWrite from 'temp-write';
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
import * as testUtils from '../../../utils/testUtils';
import { CellTypes } from 'sql/parts/notebook/models/contracts';
let expectedNotebookContent: nb.INotebook = {
cells: [{
cell_type: CellTypes.Code,
source: 'insert into t1 values (c1, c2)',
metadata: { language: 'python' },
execution_count: 1
}],
metadata: {
kernelspec: {
name: 'mssql',
language: 'sql'
}
},
nbformat: 5,
nbformat_minor: 0
};
let notebookContentString = JSON.stringify(expectedNotebookContent);
function verifyMatchesExpectedNotebook(notebook: nb.INotebook): void {
should(notebook.cells).have.length(1, 'Expected 1 cell');
should(notebook.cells[0].cell_type).equal(CellTypes.Code);
should(notebook.cells[0].source).equal(expectedNotebookContent.cells[0].source);
should(notebook.metadata.kernelspec.name).equal(expectedNotebookContent.metadata.kernelspec.name);
should(notebook.nbformat).equal(expectedNotebookContent.nbformat);
should(notebook.nbformat_minor).equal(expectedNotebookContent.nbformat_minor);
}
describe('Local Content Manager', function(): void {
let contentManager = new LocalContentManager();
it('Should return undefined if path is undefined', async function(): Promise<void> {
let content = await contentManager.getNotebookContents(undefined);
should(content).be.undefined();
// tslint:disable-next-line:no-null-keyword
content = await contentManager.getNotebookContents(null);
should(content).be.undefined();
});
it('Should throw if file does not exist', async function(): Promise<void> {
await testUtils.assertThrowsAsync(async () => await contentManager.getNotebookContents(URI.file('/path/doesnot/exist.ipynb')), undefined);
});
it('Should return notebook contents parsed as INotebook when valid notebook file parsed', async function(): Promise<void> {
// Given a file containing a valid notebook
let localFile = tempWrite.sync(notebookContentString, 'notebook.ipynb');
// when I read the content
let notebook = await contentManager.getNotebookContents(URI.file(localFile));
// then I expect notebook format to match
verifyMatchesExpectedNotebook(notebook);
});
it('Should ignore invalid content in the notebook file', async function(): Promise<void> {
// Given a file containing a notebook with some garbage properties
let invalidContent = notebookContentString + '\\nasddfdsafasdf';
let localFile = tempWrite.sync(invalidContent, 'notebook.ipynb');
// when I read the content
let notebook = await contentManager.getNotebookContents(URI.file(localFile));
// then I expect notebook format to still be valid
verifyMatchesExpectedNotebook(notebook);
});
});

View File

@@ -0,0 +1,251 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import { nb } from 'sqlops';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import URI from 'vs/base/common/uri';
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
import * as testUtils from '../../../utils/testUtils';
import { NotebookManagerStub } from '../common';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
import { IClientSession, ICellModel, INotebookModelOptions } from 'sql/parts/notebook/models/modelInterfaces';
import { ClientSession } from 'sql/parts/notebook/models/clientSession';
import { CellTypes } from 'sql/parts/notebook/models/contracts';
import { Deferred } from 'sql/base/common/promise';
import { ConnectionManagementService } from 'sql/parts/connection/common/connectionManagementService';
import { Memento } from 'vs/workbench/common/memento';
import { Emitter } from 'vs/base/common/event';
let expectedNotebookContent: nb.INotebook = {
cells: [{
cell_type: CellTypes.Code,
source: 'insert into t1 values (c1, c2)',
metadata: { language: 'python' },
execution_count: 1
}, {
cell_type: CellTypes.Markdown,
source: 'I am *markdown*',
metadata: { language: 'python' },
execution_count: 1
}],
metadata: {
kernelspec: {
name: 'mssql',
language: 'sql'
}
},
nbformat: 5,
nbformat_minor: 0
};
let expectedNotebookContentOneCell: nb.INotebook = {
cells: [{
cell_type: CellTypes.Code,
source: 'insert into t1 values (c1, c2)',
metadata: { language: 'python' },
execution_count: 1
}],
metadata: {
kernelspec: {
name: 'mssql',
language: 'sql'
}
},
nbformat: 5,
nbformat_minor: 0
};
let defaultUri = URI.file('/some/path.ipynb');
let mockClientSession: TypeMoq.Mock<IClientSession>;
let sessionReady: Deferred<void>;
let mockModelFactory: TypeMoq.Mock<ModelFactory>;
let notificationService: TypeMoq.Mock<INotificationService>;
describe('notebook model', function(): void {
let notebookManager = new NotebookManagerStub();
let memento: TypeMoq.Mock<Memento>;
let queryConnectionService: TypeMoq.Mock<ConnectionManagementService>;
let defaultModelOptions: INotebookModelOptions;
beforeEach(() => {
sessionReady = new Deferred<void>();
notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose);
memento = TypeMoq.Mock.ofType(Memento, TypeMoq.MockBehavior.Loose, '');
memento.setup(x => x.getMemento(TypeMoq.It.isAny())).returns(() => void 0);
queryConnectionService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, memento.object, undefined);
queryConnectionService.callBase = true;
defaultModelOptions = {
notebookUri: defaultUri,
factory: new ModelFactory(),
notebookManager,
notificationService: notificationService.object,
connectionService: queryConnectionService.object };
mockClientSession = TypeMoq.Mock.ofType(ClientSession, undefined, defaultModelOptions);
mockClientSession.setup(c => c.initialize(TypeMoq.It.isAny())).returns(() => {
return Promise.resolve();
});
mockClientSession.setup(c => c.ready).returns(() => sessionReady.promise);
mockModelFactory = TypeMoq.Mock.ofType(ModelFactory);
mockModelFactory.callBase = true;
mockModelFactory.setup(f => f.createClientSession(TypeMoq.It.isAny())).returns(() => {
return mockClientSession.object;
});
});
it('Should create single cell if model has no contents', async function(): Promise<void> {
// Given an empty notebook
let emptyNotebook: nb.INotebook = {
cells: [],
metadata: {
kernelspec: {
name: 'mssql',
language: 'sql'
}
},
nbformat: 5,
nbformat_minor: 0
};
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(emptyNotebook));
notebookManager.contentManager = mockContentManager.object;
// When I initialize the model
let model = new NotebookModel(defaultModelOptions);
await model.requestModelLoad();
// Then I expect to have 1 code cell as the contents
should(model.cells).have.length(1);
should(model.cells[0].source).be.empty();
});
it('Should throw if model load fails', async function(): Promise<void> {
// Given a call to get Contents fails
let error = new Error('File not found');
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).throws(error);
notebookManager.contentManager = mockContentManager.object;
// When I initalize the model
// Then it should throw
let model = new NotebookModel(defaultModelOptions);
should(model.inErrorState).be.false();
await testUtils.assertThrowsAsync(() => model.requestModelLoad(), error.message);
should(model.inErrorState).be.true();
});
it('Should convert cell info to CellModels', async function(): Promise<void> {
// Given a notebook with 2 cells
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContent));
notebookManager.contentManager = mockContentManager.object;
// When I initalize the model
let model = new NotebookModel(defaultModelOptions);
await model.requestModelLoad();
// Then I expect all cells to be in the model
should(model.cells).have.length(2);
should(model.cells[0].source).be.equal(expectedNotebookContent.cells[0].source);
should(model.cells[1].source).be.equal(expectedNotebookContent.cells[1].source);
});
it('Should load contents but then go to error state if client session startup fails', async function(): Promise<void> {
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
notebookManager.contentManager = mockContentManager.object;
// Given I have a session that fails to start
mockClientSession.setup(c => c.isInErrorState).returns(() => true);
mockClientSession.setup(c => c.errorMessage).returns(() => 'Error');
sessionReady.resolve();
let sessionFired = false;
let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>> {
factory: mockModelFactory.object
});
let model = new NotebookModel(options);
model.onClientSessionReady((session) => sessionFired = true);
await model.requestModelLoad();
model.backgroundStartSession();
// Then I expect load to succeed
shouldHaveOneCell(model);
should(model.clientSession).not.be.undefined();
// but on server load completion I expect error state to be set
// Note: do not expect serverLoad event to throw even if failed
await model.sessionLoadFinished;
should(model.inErrorState).be.true();
should(sessionFired).be.false();
});
it('Should not be in error state if client session initialization succeeds', async function(): Promise<void> {
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
notebookManager.contentManager = mockContentManager.object;
let kernelChangedEmitter: Emitter<nb.IKernelChangedArgs> = new Emitter<nb.IKernelChangedArgs>();
mockClientSession.setup(c => c.isInErrorState).returns(() => false);
mockClientSession.setup(c => c.isReady).returns(() => true);
mockClientSession.setup(c => c.kernelChanged).returns(() => kernelChangedEmitter.event);
queryConnectionService.setup(c => c.getActiveConnections(TypeMoq.It.isAny())).returns(() => null);
sessionReady.resolve();
let actualSession: IClientSession = undefined;
let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>> {
factory: mockModelFactory.object
});
let model = new NotebookModel(options, false);
model.onClientSessionReady((session) => actualSession = session);
await model.requestModelLoad();
model.backgroundStartSession();
// Then I expect load to succeed
should(model.clientSession).not.be.undefined();
// but on server load completion I expect error state to be set
// Note: do not expect serverLoad event to throw even if failed
let kernelChangedArg: nb.IKernelChangedArgs = undefined;
model.kernelChanged((kernel) => kernelChangedArg = kernel);
await model.sessionLoadFinished;
should(model.inErrorState).be.false();
should(actualSession).equal(mockClientSession.object);
should(model.clientSession).equal(mockClientSession.object);
});
it('Should sanitize kernel display name when IP is included', async function(): Promise<void> {
let model = new NotebookModel(defaultModelOptions);
let displayName = 'PySpark (1.1.1.1)';
let sanitizedDisplayName = model.sanitizeDisplayName(displayName);
should(sanitizedDisplayName).equal('PySpark');
});
it('Should sanitize kernel display name properly when IP is not included', async function(): Promise<void> {
let model = new NotebookModel(defaultModelOptions);
let displayName = 'PySpark';
let sanitizedDisplayName = model.sanitizeDisplayName(displayName);
should(sanitizedDisplayName).equal('PySpark');
});
function shouldHaveOneCell(model: NotebookModel): void {
should(model.cells).have.length(1);
verifyCellModel(model.cells[0], { cell_type: CellTypes.Code, source: 'insert into t1 values (c1, c2)', metadata: { language: 'python' }, execution_count: 1 });
}
function verifyCellModel(cellModel: ICellModel, expected: nb.ICell): void {
should(cellModel.cellType).equal(expected.cell_type);
should(cellModel.source).equal(expected.source);
}
});

238
src/typings/should.d.ts vendored Executable file
View File

@@ -0,0 +1,238 @@
// Type definitions for should.js
declare function should(obj: any): should.Assertion;
// node assert methods
/*interface NodeAssert {
fail(actual: any, expected: any, message?: string, operator?: string): void;
ok(value: any, message?: string): void;
equal(actual: any, expected: any, message?: string): void;
notEqual(actual: any, expected: any, message?: string): void;
deepEqual(actual: any, expected: any, message?: string): void;
notDeepEqual(actual: any, expected: any, message?: string): void;
strictEqual(actual: any, expected: any, message?: string): void;
notStrictEqual(actual: any, expected: any, message?: string): void;
throws(block: Function, message?: string): void;
throws(block: Function, error: Function, message?: string): void;
throws(block: Function, error: RegExp, message?: string): void;
throws(block: Function, error: (err: any) => boolean, message?: string): void;
doesNotThrow(block: Function, message?: string): void;
doesNotThrow(block: Function, error: Function, message?: string): void;
doesNotThrow(block: Function, error: RegExp, message?: string): void;
doesNotThrow(block: Function, error: (err: any) => boolean, message?: string): void;
ifError(value: any): void;
}
interface should extends NodeAssert, ShouldAssertExt {
not: ShouldAssertExt;
}*/
declare module should {
interface ShouldAssertExt {
exist(obj: any, msg?: string): void;
exists(obj: any, msg?: string): void;
}
function fail(actual: any, expected: any, message?: string, operator?: string): void;
function ok(value: any, message?: string): void;
function equal(actual: any, expected: any, message?: string): void;
function notEqual(actual: any, expected: any, message?: string): void;
function deepEqual(actual: any, expected: any, message?: string): void;
function notDeepEqual(actual: any, expected: any, message?: string): void;
function strictEqual(actual: any, expected: any, message?: string): void;
function notStrictEqual(actual: any, expected: any, message?: string): void;
function throws(block: Function, message?: string): void;
function throws(block: Function, error: Function, message?: string): void;
function throws(block: Function, error: RegExp, message?: string): void;
function throws(block: Function, error: (err: any) => boolean, message?: string): void;
function doesNotThrow(block: Function, message?: string): void;
function doesNotThrow(block: Function, error: Function, message?: string): void;
function doesNotThrow(block: Function, error: RegExp, message?: string): void;
function doesNotThrow(block: Function, error: (err: any) => boolean, message?: string): void;
function ifError(value: any): void;
function exist(obj: any, msg?: string): void;
function exists(obj: any, msg?: string): void;
const not: ShouldAssertExt;
interface Assertion {
assert(expr: boolean): this;
fail(): this;
not: this;
any: this;
only: this;
// bool
true(message?: string): this;
True(message?: string): this;
false(message?: string): this;
False(message?: string): this;
ok(): this;
//chain
an: this;
of: this;
a: this;
and: this;
be: this;
been: this;
has: this;
have: this;
with: this;
is: this;
which: this;
the: this;
it: this;
//contain
containEql(obj: any): this;
containDeepOrdered(obj: any): this;
containDeep(obj: any): this;
// eql
eql(obj: any, description?: string): this;
eqls(obj: any, description?: string): this;
deepEqual(obj: any, description?: string): this;
equal(obj: any, description?: string): this;
equals(obj: any, description?: string): this;
exactly(obj: any, description?: string): this;
equalOneOf(...objs: any[]): this;
equalOneOf(obj: any[]): this;
oneOf(...objs: any[]): this;
oneOf(obj: any[]): this;
//error
throw(): this;
throw(msg: RegExp | string | Function, properties?: {}): this;
throw(properties: {}): this;
//TODO how to express generators???
throwError(): this;
throwError(msg: RegExp | string | Function, properties?: {}): this;
throwError(properties: {}): this;
// match
match(
obj: RegExp | ((value: any, key: any) => boolean) | ((value: any, key: any) => void) | {},
description?: string
): this;
matchEach(
obj: RegExp | ((value: any, key: any) => boolean) | ((value: any, key: any) => void) | {},
description?: string
): this;
matchEvery(
obj: RegExp | ((value: any, key: any) => boolean) | ((value: any, key: any) => void) | {},
description?: string
): this;
matchAny(
obj: RegExp | ((value: any, key: any) => boolean) | ((value: any, key: any) => void) | {},
description?: string
): this;
matchSome(
obj: RegExp | ((value: any, key: any) => boolean) | ((value: any, key: any) => void) | {},
description?: string
): this;
//number
NaN(): this;
Infinity(): this;
within(start: number, finish: number, description?: string): this;
approximately(value: number, delta: number, description?: string): this;
above(value: number, description?: string): this;
greaterThan(value: number, description?: string): this;
below(value: number, description?: string): this;
lessThan(value: number, description?: string): this;
aboveOrEqual(value: number, description?: string): this;
greaterThanOrEqual(value: number, description?: string): this;
belowOrEqual(value: number, description?: string): this;
lessThanOrEqual(value: number, description?: string): this;
//promise
Promise(): this;
fulfilled(): Promise<any>;
resolved(): Promise<any>;
rejected(): Promise<any>;
fulfilledWith(obj: any): Promise<any>;
resolvedWith(obj: any): Promise<any>;
rejectedWith(msg: RegExp | string | Error, properties?: {}): Promise<any>;
rejectedWith(properties: {}): Promise<any>;
finally: PromisedAssertion;
eventually: PromisedAssertion;
// property
propertyWithDescriptor(name: string, descriptor: {}): this;
property(name: string, value?: any): this;
properties(...names: string[]): this;
properties(names: string[]): this;
properties(props: {}): this;
length(value: number, description?: string): this;
lengthOf(value: number, description?: string): this;
ownProperty(name: string, description?: string): this;
hasOwnProperty(name: string, description?: string): this;
empty(): this;
keys(...keys: any[]): this;
key(key: any): this;
value(key: any, value: any): this;
size(value: number): this;
propertyByPath(...path: string[]): this;
propertyByPath(path: string[]): this;
//string
startWith(prefix: string, description?: string): this;
endWith(postfix: string, description?: string): this;
//type
Number(): this;
arguments(): this;
Arguments(): this;
type(typeName: string, description?: string): this;
instanceof(constructor: Function, description?: string): this;
instanceOf(constructor: Function, description?: string): this;
Function(): this;
Object(): this;
String(): this;
Array(): this;
Boolean(): this;
Error(): this;
Date(): this;
null(): this;
Null(): this;
class(className: string): this;
Class(className: string): this;
undefined(): this;
Undefined(): this;
iterable(): this;
iterator(): this;
generator(): this;
}
interface PromisedAssertion extends Assertion, PromiseLike<any> {}
}
declare module 'should' {
export = should;
}

3
src/typings/temp-write.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
declare module 'temp-write' {
function sync(input: string, filePath?: string): string;
}

View File

@@ -99,6 +99,13 @@
version "5.5.0"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
"@types/should@^13.0.0":
version "13.0.0"
resolved "https://registry.yarnpkg.com/@types/should/-/should-13.0.0.tgz#96c00117f1896177848fdecfa336313c230c879e"
integrity sha512-Mi6YZ2ABnnGGFMuiBDP0a8s1ZDCDNHqP97UH8TyDmCWuGGavpsFMfJnAMYaaqmDlSCOCNbVLHBrSDEOpx/oLhw==
dependencies:
should "*"
"@types/sinon@1.16.34":
version "1.16.34"
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-1.16.34.tgz#a9761fff33d0f7b3fe61875b577778a2576a9a03"
@@ -4373,6 +4380,13 @@ macaddress@^0.2.8:
version "0.2.8"
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
make-dir@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
dependencies:
pify "^3.0.0"
make-error-cause@^1.1.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/make-error-cause/-/make-error-cause-1.2.2.tgz#df0388fcd0b37816dff0a5fb8108939777dcbc9d"
@@ -5265,6 +5279,11 @@ pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
@@ -6350,6 +6369,50 @@ shelljs@^0.7.5:
interpret "^1.0.0"
rechoir "^0.6.2"
should-equal@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3"
integrity sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==
dependencies:
should-type "^1.4.0"
should-format@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/should-format/-/should-format-3.0.3.tgz#9bfc8f74fa39205c53d38c34d717303e277124f1"
integrity sha1-m/yPdPo5IFxT04w01xcwPidxJPE=
dependencies:
should-type "^1.3.0"
should-type-adaptors "^1.0.1"
should-type-adaptors@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz#401e7f33b5533033944d5cd8bf2b65027792e27a"
integrity sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==
dependencies:
should-type "^1.3.0"
should-util "^1.0.0"
should-type@^1.3.0, should-type@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/should-type/-/should-type-1.4.0.tgz#0756d8ce846dfd09843a6947719dfa0d4cff5cf3"
integrity sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=
should-util@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/should-util/-/should-util-1.0.0.tgz#c98cda374aa6b190df8ba87c9889c2b4db620063"
integrity sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=
should@*, should@^13.2.3:
version "13.2.3"
resolved "https://registry.yarnpkg.com/should/-/should-13.2.3.tgz#96d8e5acf3e97b49d89b51feaa5ae8d07ef58f10"
integrity sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==
dependencies:
should-equal "^2.0.0"
should-format "^3.0.3"
should-type "^1.4.0"
should-type-adaptors "^1.0.1"
should-util "^1.0.0"
sigmund@^1.0.1, sigmund@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
@@ -6804,6 +6867,23 @@ tar-stream@^1.1.2:
to-buffer "^1.1.0"
xtend "^4.0.0"
temp-dir@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d"
integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=
temp-write@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-3.4.0.tgz#8cff630fb7e9da05f047c74ce4ce4d685457d492"
integrity sha1-jP9jD7fp2gXwR8dM5M5NaFRX1JI=
dependencies:
graceful-fs "^4.1.2"
is-stream "^1.1.0"
make-dir "^1.0.0"
pify "^3.0.0"
temp-dir "^1.0.0"
uuid "^3.0.1"
temp@^0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59"
@@ -7228,7 +7308,7 @@ uuid@^3.0.0, uuid@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
uuid@^3.3.2:
uuid@^3.0.1, uuid@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"