diff --git a/extensions/import/src/common/constants.ts b/extensions/import/src/common/constants.ts index 186a3876d5..d82f000733 100644 --- a/extensions/import/src/common/constants.ts +++ b/extensions/import/src/common/constants.ts @@ -12,6 +12,8 @@ export const configLogDebugInfo = 'logDebugInfo'; export const sqlConfigSectionName = 'sql'; export const mssqlProvider = 'MSSQL'; +export const summaryErrorSymbol = '✗ '; + export const supportedProviders = [mssqlProvider]; // Links diff --git a/extensions/import/src/test/controllers/mainController.test.ts b/extensions/import/src/test/controllers/mainController.test.ts index 499dcf550c..15ae0b4e5e 100644 --- a/extensions/import/src/test/controllers/mainController.test.ts +++ b/extensions/import/src/test/controllers/mainController.test.ts @@ -3,29 +3,42 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; import * as TypeMoq from 'typemoq'; import { ApiWrapper } from '../../common/apiWrapper'; import MainController from '../../controllers/mainController'; -import { TestExtensionContext } from '../utils.test'; +import * as constants from '../../common/constants'; +import * as should from 'should'; +import * as path from 'path'; +import { ImportTestUtils, TestExtensionContext } from '../utils.test'; describe('Main Controller', function () { - let mockExtensionContext: TypeMoq.IMock; + let testExtensionContext: TestExtensionContext; let mockApiWrapper: TypeMoq.IMock; + let extensionPath: string; - this.beforeEach(function () { - mockExtensionContext = TypeMoq.Mock.ofType(TestExtensionContext, TypeMoq.MockBehavior.Loose); + beforeEach(async function () { + extensionPath = await ImportTestUtils.getExtensionPath(); + // creating a mock Extension Context with current extensionPath + testExtensionContext = await ImportTestUtils.getTestExtensionContext(); mockApiWrapper = TypeMoq.Mock.ofType(ApiWrapper); }); - it('Should create new instance successfully', async function () { - // mocking createOutputChannel in API wrapper - mockApiWrapper.setup(x => x.createOutputChannel(TypeMoq.It.isAny())); + it('Should download required binaries and register flatFileImportStartCommand after activate is called', async function () { + this.timeout(50000); - // creating a Main Controller - new MainController(mockExtensionContext.object, mockApiWrapper.object); - // verifying if the output channel is created - mockApiWrapper.verify(x => x.createOutputChannel(TypeMoq.It.isAny()), TypeMoq.Times.once()); + // using vscode and azdata APIs available during tests + mockApiWrapper.callBase = true; + + let mainController = new MainController(testExtensionContext, mockApiWrapper.object); + + await mainController.activate(); + + // verifying that the task is registered. + mockApiWrapper.verify(x => x.registerTask(constants.flatFileImportStartCommand, TypeMoq.It.isAny()), TypeMoq.Times.once()); + + //Checking if .net code files are downloaded + should.equal(await ImportTestUtils.checkPathExists(path.join(extensionPath, 'flatfileimportservice')), true); }); }); + diff --git a/extensions/import/src/test/services/serviceClient.test.ts b/extensions/import/src/test/services/serviceClient.test.ts index 7d1b7985a1..103d7a61da 100644 --- a/extensions/import/src/test/services/serviceClient.test.ts +++ b/extensions/import/src/test/services/serviceClient.test.ts @@ -16,4 +16,3 @@ describe('Service utitlities test', function () { should(ensure({ 'testkey': 'testval' }, 'testkey')).equal('testval'); }); }); - diff --git a/extensions/import/src/test/utils.test.ts b/extensions/import/src/test/utils.test.ts index ba9c9bd864..58810c77f3 100644 --- a/extensions/import/src/test/utils.test.ts +++ b/extensions/import/src/test/utils.test.ts @@ -6,6 +6,8 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import { ImportDataModel, ColumnMetadata } from '../wizard/api/models'; +import { FlatFileProvider, PROSEDiscoveryParams, InsertDataParams, GetColumnInfoParams, ChangeColumnSettingsParams, PROSEDiscoveryResponse, InsertDataResponse, ChangeColumnSettingsResponse, GetColumnInfoResponse } from '../services/contracts'; +import * as fs from 'fs'; export class ImportTestUtils { @@ -34,6 +36,22 @@ export class ImportTestUtils { options: {} } as azdata.connection.ConnectionProfile; } + + public static async checkPathExists(path: string): Promise { + return fs.promises.access(path, fs.constants.F_OK) + .then(() => true) + .catch(() => false); + } + + public static async getExtensionPath(): Promise { + return await vscode.extensions.getExtension('Microsoft.import').extensionPath; + } + + public static async getTestExtensionContext(): Promise { + let testContext = new TestExtensionContext(); + testContext.extensionPath = await vscode.extensions.getExtension('Microsoft.import').extensionPath; + return testContext; + } } export class TestQueryProvider implements azdata.QueryProvider { @@ -211,3 +229,20 @@ export class TestImportDataModel implements ImportDataModel { filePath: string; fileType: string; } + +export class TestFlatFileProvider implements FlatFileProvider { + providerId?: string; + sendPROSEDiscoveryRequest(params: PROSEDiscoveryParams): Thenable { + throw new Error('Method not implemented.'); + } + sendInsertDataRequest(params: InsertDataParams): Thenable { + throw new Error('Method not implemented.'); + } + sendGetColumnInfoRequest(params: GetColumnInfoParams): Thenable { + throw new Error('Method not implemented.'); + } + sendChangeColumnSettingsRequest(params: ChangeColumnSettingsParams): Thenable { + throw new Error('Method not implemented.'); + } + +} diff --git a/extensions/import/src/test/wizard/api/basePage.test.ts b/extensions/import/src/test/wizard/api/basePage.test.ts index 235cbb16f3..6184479031 100644 --- a/extensions/import/src/test/wizard/api/basePage.test.ts +++ b/extensions/import/src/test/wizard/api/basePage.test.ts @@ -18,7 +18,7 @@ describe('import extension wizard pages', function () { let mockApiWrapper: TypeMoq.IMock; let mockImportModel: TypeMoq.IMock; - this.beforeEach(function () { + beforeEach(function () { mockApiWrapper = TypeMoq.Mock.ofType(ApiWrapper); mockFlatFileWizard = TypeMoq.Mock.ofType(FlatFileWizard, TypeMoq.MockBehavior.Loose, undefined, TypeMoq.It.isAny(), mockApiWrapper.object); mockImportModel = TypeMoq.Mock.ofType(TestImportDataModel, TypeMoq.MockBehavior.Loose); @@ -59,13 +59,16 @@ describe('import extension wizard pages', function () { let serverValues = await importPage.getServerValues(); - should(serverValues).undefined(); + // getServer should be undefined for null active connections + should.equal(serverValues, undefined, 'getServer should be undefined for no active connections'); // mocking getActive connection returns empty array mockApiWrapper.setup(x => x.getActiveConnections()).returns(async () => { return [] as azdata.connection.Connection[]; }); serverValues = await importPage.getServerValues(); - should(serverValues).undefined(); + + // getServer should be undefined for empty active connections + should.equal(serverValues, undefined, 'getServer should be undefined for empty active conections'); }); it('getServerValue return active server value first', async function () { @@ -101,6 +104,7 @@ describe('import extension wizard pages', function () { mockApiWrapper.setup(x => x.getActiveConnections()).returns(async () => { return testActiveConnections; }); mockImportModel.object.server = ImportTestUtils.getTestServer(); + // the second connection should be the first element in the array as it is active let expectedConnectionValues = [ { connection: testActiveConnections[1], diff --git a/extensions/import/src/test/wizard/pages/fileConfigPage.test.ts b/extensions/import/src/test/wizard/pages/fileConfigPage.test.ts index 0fe4d7c5de..2158e6e740 100644 --- a/extensions/import/src/test/wizard/pages/fileConfigPage.test.ts +++ b/extensions/import/src/test/wizard/pages/fileConfigPage.test.ts @@ -11,22 +11,37 @@ import { ImportDataModel } from '../../../wizard/api/models'; import { TestImportDataModel, TestQueryProvider } from '../../utils.test'; import { FileConfigPage } from '../../../wizard/pages/fileConfigPage'; import * as should from 'should'; +import { ImportPage } from '../../../wizard/api/importPage'; +import * as constants from '../../../common/constants'; -describe('import extension wizard pages', function () { +describe('File config page', function () { let mockFlatFileWizard: TypeMoq.IMock; let mockApiWrapper: TypeMoq.IMock; let mockImportModel: TypeMoq.IMock; + let fileConfigPage: FileConfigPage; + let wizard: azdata.window.Wizard; + let page: azdata.window.WizardPage; + let pages: Map = new Map(); + this.beforeEach(function () { mockApiWrapper = TypeMoq.Mock.ofType(ApiWrapper); mockFlatFileWizard = TypeMoq.Mock.ofType(FlatFileWizard, TypeMoq.MockBehavior.Loose, undefined, TypeMoq.It.isAny(), mockApiWrapper.object); mockImportModel = TypeMoq.Mock.ofType(TestImportDataModel, TypeMoq.MockBehavior.Loose); + + // using the actual vscode and azdata apis. + mockApiWrapper.callBase = true; + + wizard = mockApiWrapper.object.createWizard(constants.wizardNameText); + page = mockApiWrapper.object.createWizardPage(constants.page1NameText); }); - it('get schema returns active schema first', async function () { - mockApiWrapper.setup(x => x.getUriForConnection(TypeMoq.It.isAny())); + it('getSchema returns active schema first', async function () { + let mockQueryProvider = TypeMoq.Mock.ofType(TestQueryProvider); + + // mock result for the schema query let schemaQueryResult: azdata.SimpleExecuteResult = { rowCount: 3, rows: [ @@ -41,26 +56,211 @@ describe('import extension wizard pages', function () { ] ], columnInfo: undefined - } + }; + // setting the default schema for the current connection. This schema should be the first value in the dropdown array + mockImportModel.object.schema = 'schema2'; + + // expected schema values for the dropdown that will be created let expectedSchemaValues = [ { displayName: 'schema2', name: 'schema2' }, // This should be the first database as it is active in the extension. { displayName: 'schema1', name: 'schema1' }, { displayName: 'schema3', name: 'schema3' } ]; - mockImportModel.object.schema = 'schema2'; + // creating a mock connection mockImportModel.object.server = { providerName: 'MSSQL', connectionId: 'testConnectionId', options: {} }; - mockQueryProvider.setup(x => x.runQueryAndReturn(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async () => { return schemaQueryResult }); + + // setting up mocks to return test objects created earlier + mockQueryProvider.setup(x => x.runQueryAndReturn(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async () => { return schemaQueryResult; }); mockApiWrapper.setup(x => x.getProvider(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return mockQueryProvider.object; }); - let importPage = new FileConfigPage(mockFlatFileWizard.object, TypeMoq.It.isAny(), mockImportModel.object, TypeMoq.It.isAny(), TypeMoq.It.isAny(), mockApiWrapper.object); - let actualSchemaValues = await importPage.getSchemaValues(); + let fileConfigPage = new FileConfigPage(mockFlatFileWizard.object, TypeMoq.It.isAny(), mockImportModel.object, TypeMoq.It.isAny(), TypeMoq.It.isAny(), mockApiWrapper.object); + let actualSchemaValues = await fileConfigPage.getSchemaValues(); should(expectedSchemaValues).deepEqual(actualSchemaValues); }); + + it('checking if all components are initialized properly', async function () { + + await new Promise(function (resolve) { + page.registerContent(async (view) => { + fileConfigPage = new FileConfigPage(mockFlatFileWizard.object, page, mockImportModel.object, view, TypeMoq.It.isAny(), mockApiWrapper.object); + pages.set(1, fileConfigPage); + await fileConfigPage.start(); + resolve(); + + }); + wizard.generateScriptButton.hidden = true; + + wizard.pages = [page]; + wizard.open(); + }); + + // checking if all the required components are correctly initialized + should.notEqual(fileConfigPage.serverDropdown, undefined, 'serverDropdown should not be undefined'); + should.notEqual(fileConfigPage.databaseDropdown, undefined, 'databaseDropdown should not be undefined'); + should.notEqual(fileConfigPage.fileTextBox, undefined, 'fileTextBox should not be undefined'); + should.notEqual(fileConfigPage.fileButton, undefined, 'fileButton should not be undefined'); + should.notEqual(fileConfigPage.tableNameTextBox, undefined, 'tableNameTextBox should not be undefined'); + should.notEqual(fileConfigPage.schemaDropdown, undefined, 'schemaDropdown should not be undefined'); + should.notEqual(fileConfigPage.form, undefined, 'form should not be undefined'); + should.notEqual(fileConfigPage.databaseLoader, undefined, 'databaseLoader should not be undefined'); + should.notEqual(fileConfigPage.schemaLoader, undefined, 'schemaLoader should not be undefined'); + + await fileConfigPage.onPageLeave(); + await fileConfigPage.cleanup(); + }); + + it('Dropdown values are correctly set', async function () { + + // using the actual vscode and azdata apis. + mockApiWrapper.callBase = true; + + wizard = mockApiWrapper.object.createWizard(constants.wizardNameText); + page = mockApiWrapper.object.createWizardPage(constants.page1NameText); + + // creating mock server values + let testActiveConnections: azdata.connection.Connection[] = [ + { + providerName: 'MSSQL', + connectionId: 'testConnection1Id', + options: { + user: 'testcon1user', + server: 'testcon1server', + database: 'testdb1' + } + }, + { + providerName: 'MSSQL', + connectionId: 'testConnection2Id', + options: { + user: 'testcon2user', + server: 'testcon2server', + database: 'testdb2' + } + }, + { + providerName: 'PGSQL', + connectionId: 'testConnection3Id', + options: { + user: undefined, // setting it undefined to check if function return user as 'default + server: 'testcon3server', + database: 'testdb3' + } + } + ]; + mockApiWrapper.setup(x => x.getActiveConnections()).returns(async () => { return testActiveConnections; }); + + // creating a test active connection. This connection will be the first value in server dropdown array + let testServerConnection: azdata.connection.Connection = { + providerName: 'MSSQL', + connectionId: 'testConnection2Id', + options: { + // default database. This datatabe will be the first value in the database dropdown + database: 'testdb2', + user: 'testcon2user', + server: 'testcon2server' + } + }; + mockImportModel.object.server = testServerConnection; + mockImportModel.object.server.options = testServerConnection.options; + + // expected values for the server dropdown + let expectedConnectionValues = [ + { + connection: testActiveConnections[1], + displayName: 'testcon2server (testcon2user)', + name: 'testConnection2Id' + }, + { + connection: testActiveConnections[0], + displayName: 'testcon1server (testcon1user)', + name: 'testConnection1Id' + }, + { + connection: testActiveConnections[2], + displayName: 'testcon3server (default)', + name: 'testConnection3Id' + } + ]; + + // creating mock database values + let databases: string[] = ['testdb1', 'testdb2', 'testdb3']; + mockApiWrapper.setup(x => x.listDatabases(TypeMoq.It.isAnyString())).returns(async () => { return databases; }); + mockImportModel.object.database = 'testdb2'; + + // expected values for the database dropdown + let expectedDatabaseDropdownValues = [ + { + displayName: 'testdb2', + name: 'testdb2' + }, + { + displayName: 'testdb1', + name: 'testdb1' + }, + { + displayName: 'testdb3', + name: 'testdb3' + } + ]; + + // mock result for the schema query + let schemaQueryResult: azdata.SimpleExecuteResult = { + rowCount: 3, + rows: [ + [ + { displayValue: 'schema1', isNull: false, invariantCultureDisplayValue: 'schema1' } + ], + [ + { displayValue: 'schema2', isNull: false, invariantCultureDisplayValue: 'schema2' } + ], + [ + { displayValue: 'schema3', isNull: false, invariantCultureDisplayValue: 'schema3' } + ] + ], + columnInfo: undefined + }; + mockImportModel.object.schema = 'schema2'; + + // expected values for the schema dropdown + let expectedSchemaValues = [ + { displayName: 'schema2', name: 'schema2' }, // This should be the first database as it is active in the extension. + { displayName: 'schema1', name: 'schema1' }, + { displayName: 'schema3', name: 'schema3' } + ]; + + let mockQueryProvider = TypeMoq.Mock.ofType(TestQueryProvider); + mockApiWrapper.setup(x => x.getProvider(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return mockQueryProvider.object; }); + mockQueryProvider.setup(x => x.runQueryAndReturn(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async () => { return schemaQueryResult; }); + + await new Promise(function (resolve) { + page.registerContent(async (view) => { + fileConfigPage = new FileConfigPage(mockFlatFileWizard.object, page, mockImportModel.object, view, TypeMoq.It.isAny(), mockApiWrapper.object); + pages.set(1, fileConfigPage); + await fileConfigPage.start(); + await fileConfigPage.setupNavigationValidator(); + resolve(); + }); + wizard.generateScriptButton.hidden = true; + + wizard.pages = [page]; + wizard.open(); + }); + + + await fileConfigPage.onPageEnter(); + + should.deepEqual(fileConfigPage.serverDropdown.value, expectedConnectionValues[0]); + should.deepEqual(fileConfigPage.serverDropdown.values, expectedConnectionValues); + should.deepEqual(fileConfigPage.databaseDropdown.value, expectedDatabaseDropdownValues[0]); + should.deepEqual(fileConfigPage.databaseDropdown.values, expectedDatabaseDropdownValues); + should.deepEqual(fileConfigPage.schemaDropdown.value, expectedSchemaValues[0]); + should.deepEqual(fileConfigPage.schemaDropdown.values, expectedSchemaValues); + }); }); diff --git a/extensions/import/src/test/wizard/pages/modifyColumnsPage.test.ts b/extensions/import/src/test/wizard/pages/modifyColumnsPage.test.ts new file mode 100644 index 0000000000..d7cef23ea9 --- /dev/null +++ b/extensions/import/src/test/wizard/pages/modifyColumnsPage.test.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as TypeMoq from 'typemoq'; +import * as azdata from 'azdata'; +import { ApiWrapper } from '../../../common/apiWrapper'; +import * as constants from '../../../common/constants'; +import { FlatFileWizard } from '../../../wizard/flatFileWizard'; +import * as should from 'should'; +import { ModifyColumnsPage } from '../../../wizard/pages/modifyColumnsPage'; +import { ImportDataModel } from '../../../wizard/api/models'; +import { TestImportDataModel, TestFlatFileProvider } from '../../utils.test'; +import { ImportPage } from '../../../wizard/api/importPage'; +import { FlatFileProvider } from '../../../services/contracts'; + +describe('import extension modify Column Page', function () { + let wizard: azdata.window.Wizard; + let page: azdata.window.WizardPage; + let modifyColumnsPage: ModifyColumnsPage; + let mockFlatFileWizard: TypeMoq.IMock; + let mockImportModel: TypeMoq.IMock; + let mockApiWrapper: TypeMoq.IMock; + let pages: Map = new Map(); + let mockFlatFileProvider: TypeMoq.IMock; + + beforeEach(function () { + // Keeping the original behaviour of apiWrapper until some setup is needed to mock stuff + mockApiWrapper = TypeMoq.Mock.ofType(ApiWrapper, TypeMoq.MockBehavior.Loose); + mockApiWrapper.callBase = true; + + mockFlatFileProvider = TypeMoq.Mock.ofType(TestFlatFileProvider); + mockFlatFileWizard = TypeMoq.Mock.ofType(FlatFileWizard, TypeMoq.MockBehavior.Loose, undefined, mockFlatFileProvider.object, mockApiWrapper.object); + mockImportModel = TypeMoq.Mock.ofType(TestImportDataModel, TypeMoq.MockBehavior.Loose); + + wizard = mockApiWrapper.object.createWizard(constants.wizardNameText); + page = mockApiWrapper.object.createWizardPage(constants.page3NameText); + + }); + + it('checking if all components are initialized properly', async function () { + + await new Promise(function (resolve) { + page.registerContent(async (view) => { + modifyColumnsPage = new ModifyColumnsPage(mockFlatFileWizard.object, page, mockImportModel.object, view, TypeMoq.It.isAny(), mockApiWrapper.object); + pages.set(1, modifyColumnsPage); + await modifyColumnsPage.start(); + resolve(); + }); + wizard.generateScriptButton.hidden = true; + + wizard.pages = [page]; + wizard.open(); + }); + + // checking if all the components are initialized properly + should.notEqual(modifyColumnsPage.table, undefined, 'table should not be undefined'); + should.notEqual(modifyColumnsPage.text, undefined, 'text should not be undefined'); + should.notEqual(modifyColumnsPage.loading, undefined, 'loading should not be undefined'); + should.notEqual(modifyColumnsPage.form, undefined, 'form should not be undefined'); + }); + + it('handleImport updates table value correctly when import is successful', async function() { + + + let testProseColumns = [ + { + columnName: 'column1', + dataType: 'nvarchar(50)', + primaryKey: false, + nullable: false + }, + { + columnName: 'column2', + dataType: 'nvarchar(50)', + primaryKey: false, + nullable: false + } + ]; + + let testTableData = [ + [ 'column1', 'nvarchar(50)', false, false], + [ 'column2', 'nvarchar(50)', false, false] + ]; + + mockImportModel.object.proseColumns = testProseColumns; + + await new Promise(function (resolve) { + page.registerContent(async (view) => { + modifyColumnsPage = new ModifyColumnsPage(mockFlatFileWizard.object, page, mockImportModel.object, view, TypeMoq.It.isAny(), mockApiWrapper.object); + pages.set(1, modifyColumnsPage); + await modifyColumnsPage.start(); + resolve(); + }); + wizard.generateScriptButton.hidden = true; + + wizard.pages = [page]; + wizard.open(); + }); + + + await modifyColumnsPage.onPageEnter(); + + // checking if all the required components are correctly initialized + should.deepEqual(modifyColumnsPage.table.data, testTableData); + + }); +}); diff --git a/extensions/import/src/test/wizard/pages/prosePreviewPage.test.ts b/extensions/import/src/test/wizard/pages/prosePreviewPage.test.ts new file mode 100644 index 0000000000..3f1e71cf2f --- /dev/null +++ b/extensions/import/src/test/wizard/pages/prosePreviewPage.test.ts @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as TypeMoq from 'typemoq'; +import * as azdata from 'azdata'; +import { ApiWrapper } from '../../../common/apiWrapper'; +import * as constants from '../../../common/constants'; +import { FlatFileWizard } from '../../../wizard/flatFileWizard'; +import * as should from 'should'; +import { ImportDataModel } from '../../../wizard/api/models'; +import { TestImportDataModel } from '../../utils.test'; +import { ImportPage } from '../../../wizard/api/importPage'; +import { ProsePreviewPage } from '../../../wizard/pages/prosePreviewPage'; + +describe('import extension prose preview tests', function () { + + // declaring mock variables + let mockFlatFileWizard: TypeMoq.IMock; + let mockImportModel: TypeMoq.IMock; + let mockApiWrapper: TypeMoq.IMock; + + // declaring instance variables + let wizard: azdata.window.Wizard; + let page: azdata.window.WizardPage; + let pages: Map = new Map(); + let prosePreviewPage: ProsePreviewPage; + + + beforeEach(async function () { + + // initializing mock variables + mockApiWrapper = TypeMoq.Mock.ofType(ApiWrapper); + mockFlatFileWizard = TypeMoq.Mock.ofType(FlatFileWizard, TypeMoq.MockBehavior.Loose, undefined, TypeMoq.It.isAny(), mockApiWrapper); + mockImportModel = TypeMoq.Mock.ofType(TestImportDataModel, TypeMoq.MockBehavior.Loose); + + // using the actual vscode and azdata apis. + mockApiWrapper.callBase = true; + + // creating a wizard and adding page that will contain the fileConfigPage + wizard = mockApiWrapper.object.createWizard(constants.wizardNameText); + page = mockApiWrapper.object.createWizardPage(constants.page2NameText); + + }); + + it('checking if all components are initialized properly', async function () { + + // Opening the wizard and initializing the page as ProsePreviewPage + await new Promise(function (resolve) { + page.registerContent(async (view) => { + prosePreviewPage = new ProsePreviewPage(mockFlatFileWizard.object, page, mockImportModel.object, view, TypeMoq.It.isAny(), mockApiWrapper.object); + pages.set(1, prosePreviewPage); + await prosePreviewPage.start(); + await prosePreviewPage.setupNavigationValidator(); + await prosePreviewPage.onPageEnter(); + resolve(); + }); + wizard.generateScriptButton.hidden = true; + wizard.pages = [page]; + wizard.open(); + }); + + // checking if all the required components are correctly initialized + should.notEqual(prosePreviewPage.table, undefined, 'table should not be undefined'); + should.notEqual(prosePreviewPage.refresh, undefined, 'refresh should not be undefined'); + should.notEqual(prosePreviewPage.loading, undefined, 'loading should not be undefined'); + should.notEqual(prosePreviewPage.form, undefined, 'form should not be undefined'); + should.notEqual(prosePreviewPage.resultTextComponent, undefined, 'resultTextComponent should not be undefined'); + + // calling the clean up code + await prosePreviewPage.onPageLeave(); + await prosePreviewPage.cleanup(); + }); +}); diff --git a/extensions/import/src/test/wizard/pages/summaryPage.test.ts b/extensions/import/src/test/wizard/pages/summaryPage.test.ts new file mode 100644 index 0000000000..4c89c4c57c --- /dev/null +++ b/extensions/import/src/test/wizard/pages/summaryPage.test.ts @@ -0,0 +1,149 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as TypeMoq from 'typemoq'; +import * as azdata from 'azdata'; +import { ApiWrapper } from '../../../common/apiWrapper'; +import * as constants from '../../../common/constants'; +import { FlatFileWizard } from '../../../wizard/flatFileWizard'; +import * as should from 'should'; +import { ImportDataModel } from '../../../wizard/api/models'; +import { TestImportDataModel, TestFlatFileProvider } from '../../utils.test'; +import { ImportPage } from '../../../wizard/api/importPage'; +import { SummaryPage } from '../../../wizard/pages/summaryPage'; +import { FlatFileProvider, InsertDataResponse } from '../../../services/contracts'; + +describe('import extension summary page tests', function () { + + let mockFlatFileWizard: TypeMoq.IMock; + let mockImportModel: TypeMoq.IMock; + let mockApiWrapper: TypeMoq.IMock; + let mockFlatFileProvider: TypeMoq.IMock; + + let summaryPage: SummaryPage; + let wizard: azdata.window.Wizard; + let page: azdata.window.WizardPage; + let pages: Map = new Map(); + + beforeEach(async function () { + // Keeping the original behaviour of apiWrapper until some setup is needed to mock stuff + mockApiWrapper = TypeMoq.Mock.ofType(ApiWrapper, TypeMoq.MockBehavior.Loose); + mockApiWrapper.callBase = true; + + mockFlatFileProvider = TypeMoq.Mock.ofType(TestFlatFileProvider); + mockFlatFileWizard = TypeMoq.Mock.ofType(FlatFileWizard, TypeMoq.MockBehavior.Loose, undefined, mockFlatFileProvider.object, mockApiWrapper.object); + mockImportModel = TypeMoq.Mock.ofType(TestImportDataModel, TypeMoq.MockBehavior.Loose); + + wizard = mockApiWrapper.object.createWizard(constants.wizardNameText); + page = mockApiWrapper.object.createWizardPage(constants.page4NameText); + + }); + + it('checking if all components are initialized properly', async function () { + + await new Promise(function (resolve) { + page.registerContent(async (view) => { + summaryPage = new SummaryPage(mockFlatFileWizard.object, page, mockImportModel.object, view, TypeMoq.It.isAny(), mockApiWrapper.object); + pages.set(1, summaryPage); + await summaryPage.start(); + resolve(); + + }); + wizard.generateScriptButton.hidden = true; + + wizard.pages = [page]; + wizard.open(); + }); + + // checking if all the required components are correctly initialized + should.notEqual(summaryPage.table, undefined, 'table should not be undefined'); + should.notEqual(summaryPage.statusText, undefined, 'statusText should not be undefined'); + should.notEqual(summaryPage.loading, undefined, 'loading should not be undefined'); + should.notEqual(summaryPage.form, undefined, 'form should not be undefined'); + + await summaryPage.onPageLeave(); + await summaryPage.cleanup(); + + }); + + it('handle import updates status Text correctly', async function () { + + // Creating a test Connection + let testServerConnection: azdata.connection.Connection = { + providerName: 'testProviderName', + connectionId: 'testConnectionId', + options: {} + }; + + + // setting up connection objects in model + mockImportModel.object.server = testServerConnection; + mockImportModel.object.database = 'testDatabase'; + mockImportModel.object.schema = 'testSchema'; + mockImportModel.object.filePath = 'testFilePath'; + + // Creating test columns + let testProseColumns = [ + { + columnName: 'column1', + dataType: 'nvarchar(50)', + primaryKey: false, + nullable: false + }, + { + columnName: 'column2', + dataType: 'nvarchar(50)', + primaryKey: false, + nullable: false + } + ]; + mockImportModel.object.proseColumns = testProseColumns; + + // setting up a test table insert response from FlatFileProvider + let testSendInsertDataRequestResponse: InsertDataResponse = { + result: { + success: true, + errorMessage: '' + } + }; + mockFlatFileProvider.setup(x => x.sendInsertDataRequest(TypeMoq.It.isAny())).returns(async () => { return testSendInsertDataRequestResponse; }); + + await new Promise(function (resolve) { + page.registerContent(async (view) => { + summaryPage = new SummaryPage(mockFlatFileWizard.object, page, mockImportModel.object, view, mockFlatFileProvider.object, mockApiWrapper.object); + pages.set(1, summaryPage); + await summaryPage.start(); + summaryPage.setupNavigationValidator(); + resolve(); + }); + wizard.generateScriptButton.hidden = true; + + wizard.pages = [page]; + wizard.open(); + }); + + // Entering the page. This method will try to create table using FlatFileProvider + await summaryPage.onPageEnter(); + + // In case of success we should see the success message + should.equal(summaryPage.statusText.value, constants.updateText); + + // In case of a failure we should see the error message + testSendInsertDataRequestResponse = { + result: { + success: false, + errorMessage: 'testError' + } + }; + + // mocking the insertDataRequest to fail + mockFlatFileProvider.setup(x => x.sendInsertDataRequest(TypeMoq.It.isAny())).returns(async () => { return testSendInsertDataRequestResponse; }); + + // Entering the page. This method will try to create table using FlatFileProvider + await summaryPage.onPageEnter(); + should.equal(summaryPage.statusText.value, constants.summaryErrorSymbol + 'testError'); + + }); +}); diff --git a/extensions/import/src/wizard/pages/fileConfigPage.ts b/extensions/import/src/wizard/pages/fileConfigPage.ts index 11fb379166..6a146228d7 100644 --- a/extensions/import/src/wizard/pages/fileConfigPage.ts +++ b/extensions/import/src/wizard/pages/fileConfigPage.ts @@ -10,16 +10,89 @@ import * as constants from '../../common/constants'; export class FileConfigPage extends ImportPage { - private serverDropdown: azdata.DropDownComponent; - private databaseDropdown: azdata.DropDownComponent; - private fileTextBox: azdata.InputBoxComponent; - private fileButton: azdata.ButtonComponent; - private tableNameTextBox: azdata.InputBoxComponent; - private schemaDropdown: azdata.DropDownComponent; - private form: azdata.FormContainer; + private _serverDropdown: azdata.DropDownComponent; + private _databaseDropdown: azdata.DropDownComponent; + private _fileTextBox: azdata.InputBoxComponent; + private _fileButton: azdata.ButtonComponent; + private _tableNameTextBox: azdata.InputBoxComponent; + private _schemaDropdown: azdata.DropDownComponent; + private _form: azdata.FormContainer; + + private _databaseLoader: azdata.LoadingComponent; + private _schemaLoader: azdata.LoadingComponent; + + public get serverDropdown(): azdata.DropDownComponent { + return this._serverDropdown; + } + + public set serverDropdown(serverDropdown: azdata.DropDownComponent) { + this._serverDropdown = serverDropdown; + } + + public get databaseDropdown(): azdata.DropDownComponent { + return this._databaseDropdown; + } + + public set databaseDropdown(databaseDropdown: azdata.DropDownComponent) { + this._databaseDropdown = databaseDropdown; + } + + public get fileTextBox(): azdata.InputBoxComponent { + return this._fileTextBox; + } + + public set fileTextBox(fileTextBox: azdata.InputBoxComponent) { + this._fileTextBox = fileTextBox; + } + + public get fileButton(): azdata.ButtonComponent { + return this._fileButton; + } + + public set fileButton(fileButton: azdata.ButtonComponent) { + this._fileButton = fileButton; + } + + public get tableNameTextBox(): azdata.InputBoxComponent { + return this._tableNameTextBox; + } + + public set tableNameTextBox(tableNameTextBox: azdata.InputBoxComponent) { + this._tableNameTextBox = tableNameTextBox; + } + + public get schemaDropdown(): azdata.DropDownComponent { + return this._schemaDropdown; + } + + public set schemaDropdown(schemaDropdown: azdata.DropDownComponent) { + this._schemaDropdown = schemaDropdown; + } + + public get form(): azdata.FormContainer { + return this._form; + } + + public set form(form: azdata.FormContainer) { + this._form = form; + } + + public get databaseLoader(): azdata.LoadingComponent { + return this._databaseLoader; + } + + public set databaseLoader(databaseLoader: azdata.LoadingComponent) { + this._databaseLoader = databaseLoader; + } + + public get schemaLoader(): azdata.LoadingComponent { + return this._schemaLoader; + } + + public set schemaLoader(schemaLoader: azdata.LoadingComponent) { + this._schemaLoader = schemaLoader; + } - private databaseLoader: azdata.LoadingComponent; - private schemaLoader: azdata.LoadingComponent; private tableNames: string[] = []; diff --git a/extensions/import/src/wizard/pages/modifyColumnsPage.ts b/extensions/import/src/wizard/pages/modifyColumnsPage.ts index 00f0f3eb48..c3ec7c7618 100644 --- a/extensions/import/src/wizard/pages/modifyColumnsPage.ts +++ b/extensions/import/src/wizard/pages/modifyColumnsPage.ts @@ -45,10 +45,43 @@ export class ModifyColumnsPage extends ImportPage { { name: 'varchar(50)', displayName: 'varchar(50)' }, { name: 'varchar(MAX)', displayName: 'varchar(MAX)' } ]; - private table: azdata.DeclarativeTableComponent; - private loading: azdata.LoadingComponent; - private text: azdata.TextComponent; - private form: azdata.FormContainer; + + private _table: azdata.DeclarativeTableComponent; + private _loading: azdata.LoadingComponent; + private _text: azdata.TextComponent; + private _form: azdata.FormContainer; + + public get table(): azdata.DeclarativeTableComponent { + return this._table; + } + + public set table(table: azdata.DeclarativeTableComponent) { + this._table = table; + } + + public get loading(): azdata.LoadingComponent { + return this._loading; + } + + public set loading(loading: azdata.LoadingComponent) { + this._loading = loading; + } + + public get text(): azdata.TextComponent { + return this._text; + } + + public set text(text: azdata.TextComponent) { + this._text = text; + } + + public get form(): azdata.FormContainer { + return this._form; + } + + public set form(form: azdata.FormContainer) { + this._form = form; + } private static convertMetadata(column: ColumnMetadata): any[] { return [column.columnName, column.dataType, false, column.nullable]; diff --git a/extensions/import/src/wizard/pages/prosePreviewPage.ts b/extensions/import/src/wizard/pages/prosePreviewPage.ts index b7e16663ea..607e29979b 100644 --- a/extensions/import/src/wizard/pages/prosePreviewPage.ts +++ b/extensions/import/src/wizard/pages/prosePreviewPage.ts @@ -9,12 +9,60 @@ import * as constants from '../../common/constants'; export class ProsePreviewPage extends ImportPage { - private table: azdata.TableComponent; - private loading: azdata.LoadingComponent; - private form: azdata.FormContainer; - private refresh: azdata.ButtonComponent; - private resultTextComponent: azdata.TextComponent; - private isSuccess: boolean; + private _table: azdata.TableComponent; + private _loading: azdata.LoadingComponent; + private _form: azdata.FormContainer; + private _refresh: azdata.ButtonComponent; + private _resultTextComponent: azdata.TextComponent; + private _isSuccess: boolean; + + public get table(): azdata.TableComponent { + return this._table; + } + + public set table(table: azdata.TableComponent) { + this._table = table; + } + + public get loading(): azdata.LoadingComponent { + return this._loading; + } + + public set loading(loading: azdata.LoadingComponent) { + this._loading = loading; + } + + public get form(): azdata.FormContainer { + return this._form; + } + + public set form(form: azdata.FormContainer) { + this._form = form; + } + + public get refresh(): azdata.ButtonComponent { + return this._refresh; + } + + public set refresh(refresh: azdata.ButtonComponent) { + this._refresh = refresh; + } + + public get resultTextComponent(): azdata.TextComponent { + return this._resultTextComponent; + } + + public set resultTextComponent(resultTextComponent: azdata.TextComponent) { + this._resultTextComponent = resultTextComponent; + } + + public get isSuccess(): boolean { + return this._isSuccess; + } + + public set isSuccess(isSuccess: boolean) { + this._isSuccess = isSuccess; + } async start(): Promise { this.table = this.view.modelBuilder.table().withProperties({ diff --git a/extensions/import/src/wizard/pages/summaryPage.ts b/extensions/import/src/wizard/pages/summaryPage.ts index 51dc2cb87f..1868cc347c 100644 --- a/extensions/import/src/wizard/pages/summaryPage.ts +++ b/extensions/import/src/wizard/pages/summaryPage.ts @@ -10,10 +10,42 @@ import { InsertDataResponse } from '../../services/contracts'; import * as constants from '../../common/constants'; export class SummaryPage extends ImportPage { - private table: azdata.TableComponent; - private statusText: azdata.TextComponent; - private loading: azdata.LoadingComponent; - private form: azdata.FormContainer; + private _table: azdata.TableComponent; + private _statusText: azdata.TextComponent; + private _loading: azdata.LoadingComponent; + private _form: azdata.FormContainer; + + public get table(): azdata.TableComponent { + return this._table; + } + + public set table(table: azdata.TableComponent) { + this._table = table; + } + + public get statusText(): azdata.TextComponent { + return this._statusText; + } + + public set statusText(statusText: azdata.TextComponent) { + this._statusText = statusText; + } + + public get loading(): azdata.LoadingComponent { + return this._loading; + } + + public set loading(loading: azdata.LoadingComponent) { + this._loading = loading; + } + + public get form(): azdata.FormContainer { + return this._form; + } + + public set form(form: azdata.FormContainer) { + this._form = form; + } async start(): Promise { this.table = this.view.modelBuilder.table().component(); @@ -101,7 +133,7 @@ export class SummaryPage extends ImportPage { let updateText: string; if (!result || !result.result.success) { - updateText = '✗ '; + updateText = constants.summaryErrorSymbol; if (!result) { updateText += err; } else {