From fa5bfee0cf335de9753cf54e826c0978dc088314 Mon Sep 17 00:00:00 2001 From: Barbara Valdez <34872381+barbaravaldez@users.noreply.github.com> Date: Fri, 4 Sep 2020 18:51:04 -0700 Subject: [PATCH] Add tests to GitHubRemoteBook (#11953) * Tests for githubRemoteBook class * Add some tests and remove unused class * Address PR comments --- .../notebook/src/book/githubRemoteBook.ts | 26 ++-- extensions/notebook/src/book/remoteBook.ts | 8 +- .../notebook/src/book/remoteBookController.ts | 3 - .../notebook/src/book/sharedRemoteBook.ts | 16 --- .../src/test/book/githubRemoteBook.test.ts | 116 ++++++++++++++++++ 5 files changed, 133 insertions(+), 36 deletions(-) delete mode 100644 extensions/notebook/src/book/sharedRemoteBook.ts create mode 100644 extensions/notebook/src/test/book/githubRemoteBook.test.ts diff --git a/extensions/notebook/src/book/githubRemoteBook.ts b/extensions/notebook/src/book/githubRemoteBook.ts index 9cb7b8725a..22109930ff 100644 --- a/extensions/notebook/src/book/githubRemoteBook.ts +++ b/extensions/notebook/src/book/githubRemoteBook.ts @@ -14,14 +14,14 @@ import { IAsset } from './remoteBookController'; import * as constants from '../common/constants'; export class GitHubRemoteBook extends RemoteBook { - constructor(public remotePath: vscode.Uri, public outputChannel: vscode.OutputChannel, protected _asset: IAsset) { - super(remotePath, outputChannel, _asset); + constructor(public readonly remotePath: vscode.Uri, public readonly outputChannel: vscode.OutputChannel, public readonly asset: IAsset) { + super(remotePath, outputChannel, asset); } public async createLocalCopy(): Promise { this.outputChannel.show(true); this.setLocalPath(); - this.outputChannel.appendLine(loc.msgDownloadLocation(this._localPath.fsPath)); + this.outputChannel.appendLine(loc.msgDownloadLocation(this.localPath.fsPath)); this.outputChannel.appendLine(loc.msgRemoteBookDownloadProgress); this.createDirectory(); let notebookConfig = vscode.workspace.getConfiguration(constants.notebookConfigKey); @@ -34,7 +34,7 @@ export class GitHubRemoteBook extends RemoteBook { 'timeout': downloadTimeout } }; - let downloadRequest = request.get(this._asset.browserDownloadUrl.toString(false), options) + let downloadRequest = request.get(this.asset.browserDownloadUrl.toString(false), options) .on('error', (error) => { this.outputChannel.appendLine(loc.msgRemoteBookDownloadError); this.outputChannel.appendLine(error.message); @@ -46,7 +46,7 @@ export class GitHubRemoteBook extends RemoteBook { return reject(new Error(loc.httpRequestError(response.statusCode, response.statusMessage))); } }); - let remoteBookFullPath = vscode.Uri.file(this._localPath.fsPath.concat('.', this._asset.format)); + let remoteBookFullPath = vscode.Uri.file(this.localPath.fsPath.concat('.', this.asset.format)); downloadRequest.pipe(fs.createWriteStream(remoteBookFullPath.fsPath)) .on('close', async () => { resolve(this.extractFiles(remoteBookFullPath)); @@ -60,14 +60,14 @@ export class GitHubRemoteBook extends RemoteBook { }); } public async createDirectory(): Promise { - let fileName = this._asset.book.concat('-').concat(this._asset.version).concat('-').concat(this._asset.language); - this._localPath = vscode.Uri.file(path.join(this._localPath.fsPath, fileName)); + let fileName = this.asset.book.concat('-').concat(this.asset.version).concat('-').concat(this.asset.language); + this.localPath = vscode.Uri.file(path.join(this.localPath.fsPath, fileName)); try { - let exists = await fs.pathExists(this._localPath.fsPath); + let exists = await fs.pathExists(this.localPath.fsPath); if (exists) { - await fs.remove(this._localPath.fsPath); + await fs.remove(this.localPath.fsPath); } - await fs.promises.mkdir(this._localPath.fsPath); + await fs.promises.mkdir(this.localPath.fsPath); } catch (error) { this.outputChannel.appendLine(loc.msgRemoteBookDirectoryError); this.outputChannel.appendLine(error.message); @@ -77,13 +77,13 @@ export class GitHubRemoteBook extends RemoteBook { try { if (process.platform === constants.winPlatform || process.platform === constants.macPlatform) { let zippedFile = new zip(remoteBookFullPath.fsPath); - zippedFile.extractAllTo(this._localPath.fsPath); + zippedFile.extractAllTo(this.localPath.fsPath); } else { - await tar.extract({ file: remoteBookFullPath.fsPath, cwd: this._localPath.fsPath }); + await tar.extract({ file: remoteBookFullPath.fsPath, cwd: this.localPath.fsPath }); } await fs.promises.unlink(remoteBookFullPath.fsPath); this.outputChannel.appendLine(loc.msgRemoteBookDownloadComplete); - vscode.commands.executeCommand('notebook.command.openNotebookFolder', this._localPath.fsPath, undefined, true); + vscode.commands.executeCommand('notebook.command.openNotebookFolder', this.localPath.fsPath, undefined, true); } catch (err) { this.outputChannel.appendLine(loc.msgRemoteBookUnpackingError); diff --git a/extensions/notebook/src/book/remoteBook.ts b/extensions/notebook/src/book/remoteBook.ts index dc258b87bd..9f542d6664 100644 --- a/extensions/notebook/src/book/remoteBook.ts +++ b/extensions/notebook/src/book/remoteBook.ts @@ -8,9 +8,9 @@ import * as utils from '../common/utils'; import { IAsset } from './remoteBookController'; export abstract class RemoteBook { - protected _localPath: vscode.Uri; + public localPath: vscode.Uri; - constructor(public remotePath: vscode.Uri, public outputChannel: vscode.OutputChannel, protected _asset?: IAsset) { + constructor(public readonly remotePath: vscode.Uri, public readonly outputChannel: vscode.OutputChannel, public readonly _asset?: IAsset) { this.remotePath = remotePath; } @@ -21,10 +21,10 @@ export abstract class RemoteBook { if (vscode.workspace.workspaceFolders !== undefined) { // Get workspace root path let folders = vscode.workspace.workspaceFolders; - this._localPath = vscode.Uri.file(folders[0].uri.fsPath); + this.localPath = vscode.Uri.file(folders[0].uri.fsPath); } else { //If no workspace folder is opened then path is Users directory - this._localPath = vscode.Uri.file(utils.getUserHome()); + this.localPath = vscode.Uri.file(utils.getUserHome()); } } } diff --git a/extensions/notebook/src/book/remoteBookController.ts b/extensions/notebook/src/book/remoteBookController.ts index dda8071320..d9ae3a798f 100644 --- a/extensions/notebook/src/book/remoteBookController.ts +++ b/extensions/notebook/src/book/remoteBookController.ts @@ -8,7 +8,6 @@ import * as loc from '../common/localizedConstants'; import * as vscode from 'vscode'; import { RemoteBookDialogModel } from '../dialog/remoteBookDialogModel'; import { GitHubRemoteBook } from '../book/githubRemoteBook'; -import { SharedRemoteBook } from '../book/sharedRemoteBook'; import { winPlatform, macPlatform } from '../common/constants'; const assetNameRE = /([a-zA-Z0-9]+)(?:-|_)([a-zA-Z0-9.]+)(?:-|_)([a-zA-Z0-9]+).(zip|tar.gz|tgz)/; @@ -20,8 +19,6 @@ export class RemoteBookController { public async setRemoteBook(url: vscode.Uri, remoteLocation: string, asset?: IAsset): Promise { if (remoteLocation === 'GitHub') { this.model.remoteBook = new GitHubRemoteBook(url, this.outputChannel, asset); - } else { - this.model.remoteBook = new SharedRemoteBook(url, this.outputChannel); } return await this.model.remoteBook.createLocalCopy(); } diff --git a/extensions/notebook/src/book/sharedRemoteBook.ts b/extensions/notebook/src/book/sharedRemoteBook.ts deleted file mode 100644 index 64141887db..0000000000 --- a/extensions/notebook/src/book/sharedRemoteBook.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { RemoteBook } from '../book/remoteBook'; -import * as vscode from 'vscode'; - - -export class SharedRemoteBook extends RemoteBook { - constructor(public remotePath: vscode.Uri, public outputChannel: vscode.OutputChannel) { - super(remotePath, outputChannel); - } - public async createLocalCopy(): Promise { - throw new Error('Not yet supported'); - } -} diff --git a/extensions/notebook/src/test/book/githubRemoteBook.test.ts b/extensions/notebook/src/test/book/githubRemoteBook.test.ts new file mode 100644 index 0000000000..cc2ff9426e --- /dev/null +++ b/extensions/notebook/src/test/book/githubRemoteBook.test.ts @@ -0,0 +1,116 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RemoteBookDialogModel } from '../../dialog/remoteBookDialogModel'; +import { IAsset, RemoteBookController } from '../../book/remoteBookController'; +import * as should from 'should'; +import * as sinon from 'sinon'; +import * as vscode from 'vscode'; +import { MockExtensionContext } from '../common/stubs'; +import { AppContext } from '../../common/appContext'; +import * as loc from '../../common/localizedConstants'; +import { GitHubRemoteBook } from '../../book/githubRemoteBook'; +import { RemoteBook } from '../../book/remoteBook'; +import * as nock from 'nock'; +import * as os from 'os'; +import * as fs from 'fs'; + +describe('Github Remote Book', function () { + let mockExtensionContext: vscode.ExtensionContext = new MockExtensionContext(); + let appContext = new AppContext(mockExtensionContext); + let model = new RemoteBookDialogModel(); + let controller = new RemoteBookController(model, appContext.outputChannel); + + afterEach(function (): void { + sinon.restore(); + nock.cleanAll(); + nock.enableNetConnect(); + }); + + it('Verify GitHub Remote Book is created by controller', async function (): Promise { + let releaseURL = vscode.Uri.parse('https://api.github.com/repos/microsoft/test/releases/v1'); + let asset : IAsset = { + name: 'CU-1.0-EN.zip', + book: 'CU', + version: '1.0', + language: 'EN', + format: 'zip', + url: vscode.Uri.parse('https://api.github.com/repos/microsoft/test/releases/v1/assets/1'), + browserDownloadUrl: vscode.Uri.parse('https://github.com/microsoft/test/releases/download/v1/CU-1.0-EN.zip'), + } + let remoteLocation = loc.onGitHub; + controller.setRemoteBook(releaseURL, remoteLocation, asset); + should(controller.model.remoteBook).not.null(); + should(controller.model.remoteBook instanceof GitHubRemoteBook).be.true; + let book = model.remoteBook as GitHubRemoteBook; + should(book.asset.browserDownloadUrl.toString(false)).equal('https://github.com/microsoft/test/releases/download/v1/CU-1.0-EN.zip'); + }); + + it('Verify set local path is called when creating a GitHub Remote Book', async function (): Promise { + let releaseURL = vscode.Uri.parse('https://api.github.com/repos/microsoft/test/releases/v1'); + let asset : IAsset = { + name: 'CU-1.0-EN.zip', + book: 'CU', + version: '1.0', + language: 'EN', + format: 'zip', + url: vscode.Uri.parse('https://api.github.com/repos/microsoft/test/releases/v1/assets/1'), + browserDownloadUrl: vscode.Uri.parse('https://github.com/microsoft/test/releases/download/v1/CU-1.0-EN.zip'), + } + let remoteLocation = loc.onGitHub; + const createCopySpy = sinon.spy(GitHubRemoteBook.prototype, 'createLocalCopy'); + const setPathSpy = sinon.spy(RemoteBook.prototype, 'setLocalPath'); + controller.setRemoteBook(releaseURL, remoteLocation, asset); + should(createCopySpy.calledOnce).be.true; + should(setPathSpy.calledOnce).be.true; + }); + + it('Should download contents from Github', async function (): Promise { + let releaseURL = vscode.Uri.parse('https://api.github.com/repos/microsoft/test/releases/v1'); + let asset : IAsset = { + name: 'CU-1.0-EN.zip', + book: 'CU', + version: '1.0', + language: 'EN', + format: 'zip', + url: vscode.Uri.parse('https://api.github.com/repos/microsoft/test/releases/v1/assets/1'), + browserDownloadUrl: vscode.Uri.parse('https://github.com/microsoft/test/releases/download/v1/CU-1.0-EN.zip'), + } + let remoteLocation = loc.onGitHub; + controller.setRemoteBook(releaseURL, remoteLocation, asset); + + model.remoteBook.localPath = vscode.Uri.file(os.tmpdir()); + let setPathStub = sinon.stub(GitHubRemoteBook.prototype, 'setLocalPath'); + setPathStub.callsFake(function() { + console.log(`Downloading book in ${model.remoteBook.localPath}`); + }) + const setExtractSpy = sinon.spy(GitHubRemoteBook.prototype, 'extractFiles'); + nock('https://github.com') + .persist() + .get('/microsoft/test/releases/download/v1/CU-1.0-EN.zip') + .replyWithFile(200, __filename); + await model.remoteBook.createLocalCopy(); + should(setExtractSpy.calledOnceWith(vscode.Uri.file(model.remoteBook.localPath.fsPath))); + await fs.promises.stat(model.remoteBook.localPath.fsPath); + }); + + it('Should reject if unexpected error', async function (): Promise { + nock('https://github.com') + .persist() + .get('/microsoft/test/releases/download/v1/CU-1.0-EN.zip') + .replyWithError(new Error('Unexpected Error')); + await should(model.remoteBook.createLocalCopy()).be.rejected(); + }); + + it('Should reject if response status code is not 200', async function (): Promise { + nock('https://github.com') + .persist() + .get('/microsoft/test/releases/download/v1/CU-1.0-EN.zip') + .reply(404) + const createLocalCopy = model.remoteBook.createLocalCopy(); + await should(createLocalCopy).be.rejected(); + }); +}); +