diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index b8006f27b8..1b1b520d99 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -92,7 +92,7 @@ const indentationFilter = [ '!**/*.dockerfile', '!extensions/markdown-language-features/media/*.js', // {{SQL CARBON EDIT}} - '!**/*.{xlf,docx,sql,vsix}', + '!**/*.{xlf,docx,sql,vsix,bacpac}', '!extensions/mssql/sqltoolsservice/**', '!extensions/import/flatfileimportservice/**', '!extensions/admin-tool-ext-win/ssmsmin/**', @@ -156,7 +156,8 @@ const copyrightFilter = [ '!extensions/notebook/resources/jupyter_config/**', '!**/*.gif', '!**/*.xlf', - '!**/*.dacpac' + '!**/*.dacpac', + '!**/*.bacpac' ]; const eslintFilter = [ diff --git a/extensions/integration-tests/src/dacpac.test.ts b/extensions/integration-tests/src/dacpac.test.ts new file mode 100644 index 0000000000..d0d0e9d48a --- /dev/null +++ b/extensions/integration-tests/src/dacpac.test.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import 'mocha'; +import * as azdata from 'azdata'; +import * as utils from './utils'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import { context } from './testContext'; +import { getStandaloneServer } from './testConfig'; +import assert = require('assert'); + +const retryCount = 24; // 2 minutes +if (context.RunTest) { + suite('Dacpac integration test suite', () => { + suiteSetup(async function () { + await utils.sleep(5000); // To ensure the providers are registered. + console.log(`Start dacpac tests`); + }); + + test('Deploy and extract dacpac', async function () { + const server = await getStandaloneServer(); + await utils.connectToServer(server); + + const nodes = await azdata.objectexplorer.getActiveConnectionNodes(); + const index = nodes.findIndex(node => node.nodePath.includes(server.serverName)); + const ownerUri = await azdata.connection.getUriForConnection(nodes[index].connectionId); + const now = new Date(); + const databaseName = 'ADS_deployDacpac_' + now.getTime().toString(); + + try { + const dacfxService = await azdata.dataprotocol.getProvider('MSSQL', azdata.DataProviderType.DacFxServicesProvider); + assert(dacfxService, 'DacFx Service Provider is not available'); + + // Deploy dacpac + const deployResult = await dacfxService.deployDacpac(path.join(__dirname, 'testData/Database1.dacpac'), databaseName, false, ownerUri, azdata.TaskExecutionMode.execute); + await utils.assertDatabaseCreationResult(databaseName, ownerUri, retryCount); + assert(deployResult.success === true && deployResult.errorMessage === '', `Deploy dacpac should succeed Expected: there should be no error. Actual Error message: "${deployResult.errorMessage}"`); + + // Extract dacpac + const folderPath = path.join(os.tmpdir(), 'DacFxTest'); + if (!fs.existsSync(folderPath)) { + fs.mkdirSync(folderPath); + } + const packageFilePath = path.join(folderPath, `${databaseName}.dacpac`); + const extractResult = await dacfxService.extractDacpac(databaseName, packageFilePath, databaseName, '1.0.0.0', ownerUri, azdata.TaskExecutionMode.execute); + await utils.assertFileGenerationResult(packageFilePath, retryCount); + + assert(extractResult.success === true && extractResult.errorMessage === '', `Extract dacpac should succeed. Expected: there should be no error. Actual Error message: "${extractResult.errorMessage}"`); + } finally { + await utils.deleteDB(databaseName, ownerUri); + } + }); + + test('Import and export bacpac', async function () { + const server = await getStandaloneServer(); + await utils.connectToServer(server); + + const nodes = await azdata.objectexplorer.getActiveConnectionNodes(); + const index = nodes.findIndex(node => node.nodePath.includes(server.serverName)); + const ownerUri = await azdata.connection.getUriForConnection(nodes[index].connectionId); + const now = new Date(); + const databaseName = 'ADS_importBacpac_' + now.getTime().toString(); + + try { + let dacfxService = await azdata.dataprotocol.getProvider('MSSQL', azdata.DataProviderType.DacFxServicesProvider); + assert(dacfxService, 'DacFx Service Provider is not available'); + + // Import bacpac + const importResult = await dacfxService.importBacpac(path.join(__dirname, 'testData/Database1.bacpac'), databaseName, ownerUri, azdata.TaskExecutionMode.execute); + await utils.assertDatabaseCreationResult(databaseName, ownerUri, retryCount); + assert(importResult.success === true && importResult.errorMessage === '', `Expected: Import bacpac should succeed and there should be no error. Actual Error message: "${importResult.errorMessage}"`); + + // Export bacpac + const folderPath = path.join(os.tmpdir(), 'DacFxTest'); + if (!fs.existsSync(folderPath)) { + fs.mkdirSync(folderPath); + } + const packageFilePath = path.join(folderPath, `${databaseName}.bacpac`); + const exportResult = await dacfxService.exportBacpac(databaseName, packageFilePath, ownerUri, azdata.TaskExecutionMode.execute); + await utils.assertFileGenerationResult(packageFilePath, retryCount); + assert(exportResult.success === true && exportResult.errorMessage === '', `Expected: Export bacpac should succeed and there should be no error. Actual Error message: "${exportResult.errorMessage}"`); + } finally { + await utils.deleteDB(databaseName, ownerUri); + } + }); + }); +} diff --git a/extensions/integration-tests/src/testData/Database1.bacpac b/extensions/integration-tests/src/testData/Database1.bacpac new file mode 100644 index 0000000000..a3646d2d91 Binary files /dev/null and b/extensions/integration-tests/src/testData/Database1.bacpac differ diff --git a/extensions/integration-tests/src/utils.ts b/extensions/integration-tests/src/utils.ts index a03be21945..d33b443a94 100644 --- a/extensions/integration-tests/src/utils.ts +++ b/extensions/integration-tests/src/utils.ts @@ -6,6 +6,7 @@ import assert = require('assert'); import * as azdata from 'azdata'; import * as vscode from 'vscode'; +import * as fs from 'fs'; import { TestServerProfile } from './testConfig'; /** @@ -77,6 +78,12 @@ export async function deleteDB(dbName: string, ownerUri: string): Promise await queryProvider.runQueryAndReturn(ownerUri, query); } +export async function runQuery(query: string, ownerUri: string): Promise { + let queryProvider = azdata.dataprotocol.getProvider('MSSQL', azdata.DataProviderType.QueryProvider); + let result = await queryProvider.runQueryAndReturn(ownerUri, query); + return result; +} + export async function assertThrowsAsync(fn: () => Promise, msg: string): Promise { let f = () => { // Empty @@ -89,3 +96,51 @@ export async function assertThrowsAsync(fn: () => Promise, msg: string): Pr assert.throws(f, msg); } } + +/** + * + * @param databaseName name of database to check for + * @param ownerUri owner uri + * @param retryCount number of times to retry with a 5 second wait between each try + * Checks for database getting created for operations that have async database creation + */ +export async function assertDatabaseCreationResult(databaseName: string, ownerUri: string, retryCount: number): Promise { + let result: azdata.SimpleExecuteResult; + while (retryCount > 0) { + --retryCount; + await sleep(5000); + + let query = `BEGIN TRY + SELECT name FROM master.dbo.sysdatabases WHERE name='${databaseName}' + END TRY + BEGIN CATCH + SELECT ERROR_MESSAGE() AS ErrorMessage; + END CATCH`; + result = await runQuery(query, ownerUri); + if (result.rowCount > 0) { + break; + } + } + + assert(result.rowCount === 1, `Database ${databaseName} should be created`); + assert(result.columnInfo[0].columnName !== 'ErrorMessage', 'Checking for db creation threw error'); +} + +/** + * + * @param filepath File path to check for + * @param retryCount number of times to retry with a 5 second wait between each try + * Checks for file getting created for async file generation and deletes file + */ +export async function assertFileGenerationResult(filepath: string, retryCount: number): Promise { + let exists = false; + while (retryCount > 0 && !exists) { + --retryCount; + exists = fs.existsSync(filepath); + await sleep(5000); + } + + assert(exists, `File ${filepath} is expected to be present`); + assert(fs.readFileSync(filepath).byteLength > 0, 'File should not be empty'); + fs.unlinkSync(filepath); +} \ No newline at end of file