From c903112451a20dbeb1a5996f4722ea75c9e16741 Mon Sep 17 00:00:00 2001 From: Sakshi Sharma <57200045+SakshiS-harma@users.noreply.github.com> Date: Wed, 3 Jun 2020 14:02:50 -0700 Subject: [PATCH] Tests for round tripping with SSDT projects (#10646) * Test code for round tripping feature * Fixed tests. Edited updateImportedTargetsToProjFile method to push newly added import target to the list. * Added couple more tests * Addressed comment --- .../src/common/apiWrapper.ts | 4 ++ .../src/controllers/projectController.ts | 13 +++- .../src/models/project.ts | 31 ++++---- .../SSDTProjectAfterUpdateBaseline.xml | 72 +++++++++++++++++++ .../test/baselines/SSDTProjectBaseline.xml | 72 +++++++++++++++++++ .../src/test/baselines/baselines.ts | 4 ++ .../src/test/project.test.ts | 21 ++++++ .../src/test/projectController.test.ts | 63 ++++++++++++++++ 8 files changed, 260 insertions(+), 20 deletions(-) create mode 100644 extensions/sql-database-projects/src/test/baselines/SSDTProjectAfterUpdateBaseline.xml create mode 100644 extensions/sql-database-projects/src/test/baselines/SSDTProjectBaseline.xml diff --git a/extensions/sql-database-projects/src/common/apiWrapper.ts b/extensions/sql-database-projects/src/common/apiWrapper.ts index 159260de54..c187d98b29 100644 --- a/extensions/sql-database-projects/src/common/apiWrapper.ts +++ b/extensions/sql-database-projects/src/common/apiWrapper.ts @@ -63,6 +63,10 @@ export class ApiWrapper { return vscode.window.showInformationMessage(message, ...items); } + public showWarningMessage(message: string, ...items: string[]): Thenable { + return vscode.window.showWarningMessage(message, ...items); + } + public showOpenDialog(options: vscode.OpenDialogOptions): Thenable { return vscode.window.showOpenDialog(options); } diff --git a/extensions/sql-database-projects/src/controllers/projectController.ts b/extensions/sql-database-projects/src/controllers/projectController.ts index 13918cb33a..7142b8b6ae 100644 --- a/extensions/sql-database-projects/src/controllers/projectController.ts +++ b/extensions/sql-database-projects/src/controllers/projectController.ts @@ -72,7 +72,7 @@ export class ProjectsController { this.projects.push(newProject); // Update for round tripping as needed - await newProject.updateProjectForRoundTrip(); + await this.updateProjectForRoundTrip(newProject); // Read datasources.json (if present) const dataSourcesFilePath = path.join(path.dirname(projectFile.fsPath), constants.dataSourcesFileName); @@ -298,6 +298,17 @@ export class ProjectsController { return new DeployDatabaseDialog(this.apiWrapper, project); } + public async updateProjectForRoundTrip(project: Project) { + if (project.importedTargets.includes(constants.NetCoreTargets)) { + return; + } + + const result = await this.apiWrapper.showWarningMessage(constants.updateProjectForRoundTrip, constants.yesString, constants.noString); + if (result === constants.yesString) { + await project.updateProjectForRoundTrip(); + } + } + private static getProjectFromContext(context: Project | BaseProjectTreeItem) { if (context instanceof Project) { return context; diff --git a/extensions/sql-database-projects/src/models/project.ts b/extensions/sql-database-projects/src/models/project.ts index 8a734c39b6..910c852d19 100644 --- a/extensions/sql-database-projects/src/models/project.ts +++ b/extensions/sql-database-projects/src/models/project.ts @@ -8,7 +8,7 @@ import * as xmldom from 'xmldom'; import * as constants from '../common/constants'; import * as utils from '../common/utils'; -import { Uri, window } from 'vscode'; +import { Uri } from 'vscode'; import { promises as fs } from 'fs'; import { DataSource } from './dataSources/dataSources'; @@ -62,17 +62,9 @@ export class Project { } public async updateProjectForRoundTrip() { - if (this.importedTargets.includes(constants.NetCoreTargets)) { - return; - } - - window.showWarningMessage(constants.updateProjectForRoundTrip, constants.yesString, constants.noString).then(async (result) => { - if (result === constants.yesString) { - await fs.copyFile(this.projectFilePath, this.projectFilePath + '_backup'); - await this.updateImportToSupportRoundTrip(); - await this.updatePackageReferenceInProjFile(); - } - }); + await fs.copyFile(this.projectFilePath, this.projectFilePath + '_backup'); + await this.updateImportToSupportRoundTrip(); + await this.updatePackageReferenceInProjFile(); } private async updateImportToSupportRoundTrip(): Promise { @@ -81,13 +73,13 @@ export class Project { const importTarget = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.Import)[i]; let condition = importTarget.getAttribute(constants.Condition); - let project = importTarget.getAttribute(constants.Project); + let projectAttributeVal = importTarget.getAttribute(constants.Project); - if (condition === constants.SqlDbPresentCondition && project === constants.SqlDbTargets) { - await this.updateImportedTargetsToProjFile(constants.RoundTripSqlDbPresentCondition, project, importTarget); + if (condition === constants.SqlDbPresentCondition && projectAttributeVal === constants.SqlDbTargets) { + await this.updateImportedTargetsToProjFile(constants.RoundTripSqlDbPresentCondition, projectAttributeVal, importTarget); } - if (condition === constants.SqlDbNotPresentCondition && project === constants.MsBuildtargets) { - await this.updateImportedTargetsToProjFile(constants.RoundTripSqlDbNotPresentCondition, project, importTarget); + if (condition === constants.SqlDbNotPresentCondition && projectAttributeVal === constants.MsBuildtargets) { + await this.updateImportedTargetsToProjFile(constants.RoundTripSqlDbNotPresentCondition, projectAttributeVal, importTarget); } } @@ -185,16 +177,17 @@ export class Project { this.findOrCreateItemGroup(constants.Folder).appendChild(newFolderNode); } - private async updateImportedTargetsToProjFile(condition: string, project: string, oldImportNode?: any): Promise { + private async updateImportedTargetsToProjFile(condition: string, projectAttributeVal: string, oldImportNode?: any): Promise { const importNode = this.projFileXmlDoc.createElement(constants.Import); importNode.setAttribute(constants.Condition, condition); - importNode.setAttribute(constants.Project, project); + importNode.setAttribute(constants.Project, projectAttributeVal); if (oldImportNode) { this.projFileXmlDoc.documentElement.replaceChild(importNode, oldImportNode); } else { this.projFileXmlDoc.documentElement.appendChild(importNode, oldImportNode); + this.importedTargets.push(projectAttributeVal); // Add new import target to the list } await this.serializeToProjFile(this.projFileXmlDoc); diff --git a/extensions/sql-database-projects/src/test/baselines/SSDTProjectAfterUpdateBaseline.xml b/extensions/sql-database-projects/src/test/baselines/SSDTProjectAfterUpdateBaseline.xml new file mode 100644 index 0000000000..6cbe572516 --- /dev/null +++ b/extensions/sql-database-projects/src/test/baselines/SSDTProjectAfterUpdateBaseline.xml @@ -0,0 +1,72 @@ + + + + Debug + AnyCPU + TestProjectName + 2.0 + 4.1 + {BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575} + Microsoft.Data.Tools.Schema.Sql.Sql130DatabaseSchemaProvider + Database + + + TestProjectName + TestProjectName + 1033, CI + BySchemaAndSchemaType + True + v4.5 + CS + Properties + False + True + True + + + bin\Release\ + $(MSBuildProjectName).sql + False + pdbonly + true + false + true + prompt + 4 + + + bin\Debug\ + $(MSBuildProjectName).sql + false + true + full + false + true + true + prompt + 4 + + + 11.0 + + True + 11.0 + + + + + + + + + + + + + + + + + + + diff --git a/extensions/sql-database-projects/src/test/baselines/SSDTProjectBaseline.xml b/extensions/sql-database-projects/src/test/baselines/SSDTProjectBaseline.xml new file mode 100644 index 0000000000..1ee9075d77 --- /dev/null +++ b/extensions/sql-database-projects/src/test/baselines/SSDTProjectBaseline.xml @@ -0,0 +1,72 @@ + + + + Debug + AnyCPU + TestProjectName + 2.0 + 4.1 + {BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575} + Microsoft.Data.Tools.Schema.Sql.Sql130DatabaseSchemaProvider + Database + + + TestProjectName + TestProjectName + 1033, CI + BySchemaAndSchemaType + True + v4.5 + CS + Properties + False + True + True + + + bin\Release\ + $(MSBuildProjectName).sql + False + pdbonly + true + false + true + prompt + 4 + + + bin\Debug\ + $(MSBuildProjectName).sql + false + true + full + false + true + true + prompt + 4 + + + 11.0 + + True + 11.0 + + + + + + + + + + + + + + + + + + + diff --git a/extensions/sql-database-projects/src/test/baselines/baselines.ts b/extensions/sql-database-projects/src/test/baselines/baselines.ts index adfd32405d..b233850422 100644 --- a/extensions/sql-database-projects/src/test/baselines/baselines.ts +++ b/extensions/sql-database-projects/src/test/baselines/baselines.ts @@ -10,6 +10,8 @@ import { promises as fs } from 'fs'; export let newProjectFileBaseline: string; export let openProjectFileBaseline: string; export let openDataSourcesBaseline: string; +export let SSDTProjectFileBaseline: string; +export let SSDTProjectAfterUpdateBaseline: string; const baselineFolderPath = __dirname; @@ -17,6 +19,8 @@ export async function loadBaselines() { newProjectFileBaseline = await loadBaseline(baselineFolderPath, 'newSqlProjectBaseline.xml'); openProjectFileBaseline = await loadBaseline(baselineFolderPath, 'openSqlProjectBaseline.xml'); openDataSourcesBaseline = await loadBaseline(baselineFolderPath, 'openDataSourcesBaseline.json'); + SSDTProjectFileBaseline = await loadBaseline(baselineFolderPath, 'SSDTProjectBaseline.xml'); + SSDTProjectAfterUpdateBaseline = await loadBaseline(baselineFolderPath, 'SSDTProjectAfterUpdateBaseline.xml'); } async function loadBaseline(baselineFolderPath: string, fileName: string): Promise { diff --git a/extensions/sql-database-projects/src/test/project.test.ts b/extensions/sql-database-projects/src/test/project.test.ts index 21d31e1774..5e8e14f6d8 100644 --- a/extensions/sql-database-projects/src/test/project.test.ts +++ b/extensions/sql-database-projects/src/test/project.test.ts @@ -10,6 +10,7 @@ import * as testUtils from './testUtils'; import { promises as fs } from 'fs'; import { Project, EntryType } from '../models/project'; +import { exists } from '../common/utils'; let projFilePath: string; @@ -82,3 +83,23 @@ describe('Project: sqlproj content operations', function (): void { await testUtils.shouldThrowSpecificError(async () => await project.addToProject(list), `ENOENT: no such file or directory, stat \'${nonexistentFile}\'`); }); }); + +describe('Project: round trip updates', function (): void { + before(async function () : Promise { + await baselines.loadBaselines(); + }); + + it('Should update SSDT project to work in ADS', async function (): Promise { + projFilePath = await testUtils.createTestSqlProjFile(baselines.SSDTProjectFileBaseline); + const project: Project = new Project(projFilePath); + await project.readProjFile(); + + await project.updateProjectForRoundTrip(); + + should(await exists(projFilePath + '_backup')).equal(true); // backup file should be generated before the project is updated + should(project.importedTargets.length).equal(3); // additional target added by updateProjectForRoundTrip method + + let projFileText = (await fs.readFile(projFilePath)).toString(); + should(projFileText).equal(baselines.SSDTProjectAfterUpdateBaseline.trim()); + }); +}); diff --git a/extensions/sql-database-projects/src/test/projectController.test.ts b/extensions/sql-database-projects/src/test/projectController.test.ts index 35086447ea..0db07856a4 100644 --- a/extensions/sql-database-projects/src/test/projectController.test.ts +++ b/extensions/sql-database-projects/src/test/projectController.test.ts @@ -22,6 +22,7 @@ import { Project } from '../models/project'; import { DeployDatabaseDialog } from '../dialogs/deployDatabaseDialog'; import { ApiWrapper } from '../common/apiWrapper'; import { IDeploymentProfile, IGenerateScriptProfile } from '../models/IDeploymentProfile'; +import { exists } from '../common/utils'; let testContext: TestContext; @@ -225,3 +226,65 @@ describe('ProjectsController: import operations', function (): void { await testUtils.shouldThrowSpecificError(async () => await projController.importNewDatabaseProject(mockConnectionProfile), constants.projectLocationNotEmpty); }); }); + +describe('ProjectsController: round trip feature with SSDT', function (): void { + it('Should show warning message for SSDT project opened in Azure Data Studio', async function (): Promise { + testContext.apiWrapper.setup(x => x.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((s) => { throw new Error(s); }); + + // setup test files + const folderPath = await testUtils.generateTestFolderPath(); + const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.SSDTProjectFileBaseline, folderPath); + await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, folderPath); + + const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider()); + + await testUtils.shouldThrowSpecificError(async () => await projController.openProject(vscode.Uri.file(sqlProjPath)), constants.updateProjectForRoundTrip); + }); + + it('Should not show warning message for non-SSDT projects that have the additional information for Build', async function (): Promise { + testContext.apiWrapper.setup(x => x.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((s) => { throw new Error(s); }); + + // setup test files + const folderPath = await testUtils.generateTestFolderPath(); + const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.openProjectFileBaseline, folderPath); + await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, folderPath); + + const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider()); + + const project = await projController.openProject(vscode.Uri.file(sqlProjPath)); // no error thrown + + should(project.importedTargets.length).equal(3); // additional target should exist by default + }); + + it('Should not update project and no backup file should be created when update to project is rejected', async function (): Promise { + testContext.apiWrapper.setup(x => x.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(constants.noString)); + + // setup test files + const folderPath = await testUtils.generateTestFolderPath(); + const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.SSDTProjectFileBaseline, folderPath); + await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, folderPath); + + const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider()); + + const project = await projController.openProject(vscode.Uri.file(sqlProjPath)); + + should(await exists(sqlProjPath+'_backup')).equal(false); // backup file should not be generated + should(project.importedTargets.length).equal(2); // additional target should not be added by updateProjectForRoundTrip method + }); + + it('Should load Project and associated import targets when update to project is accepted', async function (): Promise { + testContext.apiWrapper.setup(x => x.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(constants.yesString)); + + // setup test files + const folderPath = await testUtils.generateTestFolderPath(); + const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.SSDTProjectFileBaseline, folderPath); + await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, folderPath); + + const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider()); + + const project = await projController.openProject(vscode.Uri.file(sqlProjPath)); + + should(await exists(sqlProjPath+'_backup')).equal(true); // backup file should be generated before the project is updated + should(project.importedTargets.length).equal(3); // additional target added by updateProjectForRoundTrip method + }); +});