// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.Data.SqlClient; using Microsoft.SqlServer.Dac; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.SqlTools.ServiceLayer.DacFx; using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts; using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.ServiceLayer.Test.Common; using Microsoft.SqlServer.Dac.Model; using NUnit.Framework; using Moq; using System.Reflection; namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx { public class DacFxServiceTests { private string publishProfileFolder = Path.Combine("..", "..", "..", "DacFx", "PublishProfiles"); private const string SourceScript = @"CREATE TABLE [dbo].[table1] ( [ID] INT NOT NULL PRIMARY KEY, [Date] DATE NOT NULL ) CREATE TABLE [dbo].[table2] ( [ID] INT NOT NULL PRIMARY KEY, [col1] NCHAR(10) NULL )"; private const string SourceViewScript = @"CREATE VIEW [dbo].[view1] AS SELECT dbo.table1.* FROM dbo.table1"; private const string TargetScript = @"CREATE TABLE [dbo].[table2] ( [ID] INT NOT NULL PRIMARY KEY, [col1] NCHAR(10) NULL, [col2] NCHAR(10) NULL ) CREATE TABLE [dbo].[table3] ( [ID] INT NOT NULL PRIMARY KEY, [col1] INT NULL, )"; private const string databaseRefVarName = "DatabaseRef"; private const string filterValueVarName = "FilterValue"; private string storedProcScript = $@" CREATE PROCEDURE [dbo].[Procedure1] @param1 int = 0, @param2 int AS SELECT * FROM [$({databaseRefVarName})].[dbo].[Table1] WHERE Type = '$({filterValueVarName})' RETURN 0 "; private string dacpacsFolder = Path.Combine("..", "..", "..", "DacFx", "Dacpacs"); private string goodCreateStreamingJob = @"EXEC sys.sp_create_streaming_job @NAME = 'myJob', @STATEMENT = 'INSERT INTO SqlOutputStream SELECT timeCreated, machine.temperature as machine_temperature, machine.pressure as machine_pressure, ambient.temperature as ambient_temperature, ambient.humidity as ambient_humidity FROM EdgeHubInputStream'"; private string missingCreateBothStreamingJob = @$"EXEC sys.sp_create_streaming_job @NAME = 'myJob', @STATEMENT = 'INSERT INTO MissingSqlOutputStream SELECT timeCreated, machine.temperature as machine_temperature, machine.pressure as machine_pressure, ambient.temperature as ambient_temperature, ambient.humidity as ambient_humidity FROM MissingEdgeHubInputStream'"; private LiveConnectionHelper.TestConnectionResult GetLiveAutoCompleteTestObjects() { var result = LiveConnectionHelper.InitLiveConnectionInfo(); return result; } /// /// Verify the export bacpac request /// [Test] public async Task ExportBacpac() { var result = GetLiveAutoCompleteTestObjects(); SqlTestDb testdb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxExportTest"); string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); Directory.CreateDirectory(folderPath); try { var exportParams = new ExportParams { DatabaseName = testdb.DatabaseName, PackageFilePath = Path.Combine(folderPath, string.Format("{0}.bacpac", testdb.DatabaseName)) }; DacFxService service = new DacFxService(); ExportOperation operation = new ExportOperation(exportParams, result.ConnectionInfo); service.PerformOperation(operation, TaskExecutionMode.Execute); VerifyAndCleanup(exportParams.PackageFilePath); } finally { testdb.Cleanup(); } } /// /// Verify the import bacpac request /// [Test] public async Task ImportBacpac() { // first export a bacpac var result = GetLiveAutoCompleteTestObjects(); SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxImportTest"); SqlTestDb targetDb = null; string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); Directory.CreateDirectory(folderPath); try { var exportParams = new ExportParams { DatabaseName = sourceDb.DatabaseName, PackageFilePath = Path.Combine(folderPath, string.Format("{0}.bacpac", sourceDb.DatabaseName)) }; DacFxService service = new DacFxService(); ExportOperation exportOperation = new ExportOperation(exportParams, result.ConnectionInfo); service.PerformOperation(exportOperation, TaskExecutionMode.Execute); // import the created bacpac var importParams = new ImportParams { PackageFilePath = exportParams.PackageFilePath, DatabaseName = string.Concat(sourceDb.DatabaseName, "-imported") }; ImportOperation importOperation = new ImportOperation(importParams, result.ConnectionInfo); service.PerformOperation(importOperation, TaskExecutionMode.Execute); targetDb = SqlTestDb.CreateFromExisting(importParams.DatabaseName); VerifyAndCleanup(exportParams.PackageFilePath); } finally { sourceDb.Cleanup(); if (targetDb != null) { targetDb.Cleanup(); } } } /// /// Verify the extract dacpac request /// [Test] public async Task ExtractDacpac() { var result = GetLiveAutoCompleteTestObjects(); SqlTestDb testdb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxExtractTest"); string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); Directory.CreateDirectory(folderPath); try { var extractParams = new ExtractParams { DatabaseName = testdb.DatabaseName, PackageFilePath = Path.Combine(folderPath, string.Format("{0}.dacpac", testdb.DatabaseName)), ApplicationName = "test", ApplicationVersion = "1.0.0.0" }; DacFxService service = new DacFxService(); ExtractOperation operation = new ExtractOperation(extractParams, result.ConnectionInfo); service.PerformOperation(operation, TaskExecutionMode.Execute); VerifyAndCleanup(extractParams.PackageFilePath); } finally { testdb.Cleanup(); } } /// /// Verify the extract request to create Sql file /// [Test] public async Task ExtractDBToFileTarget() { var result = GetLiveAutoCompleteTestObjects(); SqlTestDb testdb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, doNotCleanupDb: false, databaseName: null, query: SourceScript, dbNamePrefix: "DacFxExtractDBToFileTarget"); string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); Directory.CreateDirectory(folderPath); try { var extractParams = new ExtractParams { DatabaseName = testdb.DatabaseName, PackageFilePath = Path.Combine(folderPath, string.Format("{0}.sql", testdb.DatabaseName)), ApplicationName = "test", ApplicationVersion = "1.0.0.0", ExtractTarget = DacExtractTarget.File }; DacFxService service = new DacFxService(); ExtractOperation operation = new ExtractOperation(extractParams, result.ConnectionInfo); service.PerformOperation(operation, TaskExecutionMode.Execute); VerifyAndCleanup(extractParams.PackageFilePath); } finally { testdb.Cleanup(); } } /// /// Verify the extract request to create a Flat file structure /// [Test] public async Task ExtractDBToFlatTarget() { var result = GetLiveAutoCompleteTestObjects(); SqlTestDb testdb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, doNotCleanupDb: false, databaseName: null, query: SourceScript, dbNamePrefix: "DacFxExtractDBToFlatTarget"); string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest", "FlatExtract"); Directory.CreateDirectory(folderPath); try { var extractParams = new ExtractParams { DatabaseName = testdb.DatabaseName, PackageFilePath = folderPath, ApplicationName = "test", ApplicationVersion = "1.0.0.0", ExtractTarget = DacExtractTarget.Flat }; DacFxService service = new DacFxService(); ExtractOperation operation = new ExtractOperation(extractParams, result.ConnectionInfo); service.PerformOperation(operation, TaskExecutionMode.Execute); // Verify two sql files are generated in the target folder path // for dev-servers where there are more users/permissions present on server - the extract might have more files than just 2 expected tables, so check only for tables int actualCnt = Directory.GetFiles(folderPath, "table*.sql", SearchOption.AllDirectories).Length; Assert.AreEqual(2, actualCnt); } finally { // Remove the directory if (Directory.Exists(folderPath)) { Directory.Delete(folderPath, true); } testdb.Cleanup(); } } /// /// Verify the deploy dacpac request /// [Test] public async Task DeployDacpac() { // first extract a db to have a dacpac to import later var result = GetLiveAutoCompleteTestObjects(); SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxDeployTest"); SqlTestDb targetDb = null; string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); Directory.CreateDirectory(folderPath); try { var extractParams = new ExtractParams { DatabaseName = sourceDb.DatabaseName, PackageFilePath = Path.Combine(folderPath, string.Format("{0}.dacpac", sourceDb.DatabaseName)), ApplicationName = "test", ApplicationVersion = "1.0.0.0" }; DacFxService service = new DacFxService(); ExtractOperation extractOperation = new ExtractOperation(extractParams, result.ConnectionInfo); service.PerformOperation(extractOperation, TaskExecutionMode.Execute); // deploy the created dacpac var deployParams = new DeployParams { PackageFilePath = extractParams.PackageFilePath, DatabaseName = string.Concat(sourceDb.DatabaseName, "-deployed"), UpgradeExisting = false }; DeployOperation deployOperation = new DeployOperation(deployParams, result.ConnectionInfo); service.PerformOperation(deployOperation, TaskExecutionMode.Execute); targetDb = SqlTestDb.CreateFromExisting(deployParams.DatabaseName); VerifyAndCleanup(extractParams.PackageFilePath); } finally { sourceDb.Cleanup(); if (targetDb != null) { targetDb.Cleanup(); } } return; } /// /// Verify the export request being cancelled /// [Test] public async Task ExportBacpacCancellationTest() { var result = GetLiveAutoCompleteTestObjects(); SqlTestDb testdb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxExportTest"); string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); Directory.CreateDirectory(folderPath); try { var exportParams = new ExportParams { DatabaseName = testdb.DatabaseName, PackageFilePath = Path.Combine(folderPath, string.Format("{0}.bacpac", testdb.DatabaseName)) }; DacFxService service = new DacFxService(); ExportOperation operation = new ExportOperation(exportParams, result.ConnectionInfo); // set cancellation token to cancel operation.Cancel(); OperationCanceledException expectedException = null; try { service.PerformOperation(operation, TaskExecutionMode.Execute); } catch (OperationCanceledException canceledException) { expectedException = canceledException; } Assert.NotNull(expectedException); } finally { testdb.Cleanup(); } } /// /// Verify the generate deploy script request /// [Test] public async Task GenerateDeployScript() { // first extract a dacpac var result = GetLiveAutoCompleteTestObjects(); SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, SourceScript, "DacFxGenerateScriptTest"); SqlTestDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxGenerateScriptTest"); try { DacFxService service = new DacFxService(); string dacpacPath = InitialExtract(service, sourceDb, result); // generate script var generateScriptParams = new GenerateDeployScriptParams { PackageFilePath = dacpacPath, DatabaseName = targetDb.DatabaseName }; // Generate script for deploying source dacpac to target db GenerateDeployScriptOperation generateScriptOperation = new GenerateDeployScriptOperation(generateScriptParams, result.ConnectionInfo); service.PerformOperation(generateScriptOperation, TaskExecutionMode.Script); // Verify script was generated Assert.That(generateScriptOperation.Result.DatabaseScript, Is.Not.Empty); Assert.That(generateScriptOperation.Result.DatabaseScript, Does.Contain("CREATE TABLE")); VerifyAndCleanup(dacpacPath); } finally { sourceDb.Cleanup(); targetDb.Cleanup(); } } /// /// Verify the generate deploy plan request /// [Test] public async Task GenerateDeployPlan() { var result = GetLiveAutoCompleteTestObjects(); SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, SourceScript, "DacFxGenerateDeployPlanTest"); SqlTestDb targetDb = null; try { DacFxService service = new DacFxService(); string dacpacPath = InitialExtract(service, sourceDb, result); // generate deploy plan for deploying dacpac to targetDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, TargetScript, "DacFxGenerateDeployPlanTestTarget"); var generateDeployPlanParams = new GenerateDeployPlanParams { PackageFilePath = dacpacPath, DatabaseName = targetDb.DatabaseName, }; GenerateDeployPlanOperation generateDeployPlanOperation = new GenerateDeployPlanOperation(generateDeployPlanParams, result.ConnectionInfo); service.PerformOperation(generateDeployPlanOperation, TaskExecutionMode.Execute); string report = generateDeployPlanOperation.DeployReport; Assert.NotNull(report); Assert.Multiple(() => { Assert.That(report, Does.Contain("Create")); Assert.That(report, Does.Contain("Drop")); Assert.That(report, Does.Contain("Alter")); }); VerifyAndCleanup(dacpacPath); } finally { sourceDb.Cleanup(); if (targetDb != null) { targetDb.Cleanup(); } } } // /// Verify that SqlCmdVars are set correctly for a deploy request /// [Test] public async Task DeployWithSqlCmdVariables() { var result = GetLiveAutoCompleteTestObjects(); SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, query: storedProcScript, dbNamePrefix: "DacFxDeploySqlCmdVarsTest"); SqlTestDb targetDb = null; try { DacFxService service = new DacFxService(); // First extract a db to have a dacpac to deploy later string dacpacPath = InitialExtract(service, sourceDb, result); // Deploy the created dacpac with SqlCmdVars var deployParams = new DeployParams { PackageFilePath = dacpacPath, DatabaseName = string.Concat(sourceDb.DatabaseName, "-deployed"), UpgradeExisting = false, SqlCommandVariableValues = new Dictionary() { { databaseRefVarName, "OtherDatabase" }, { filterValueVarName, "Employee" } } }; DeployOperation deployOperation = new DeployOperation(deployParams, result.ConnectionInfo); service.PerformOperation(deployOperation, TaskExecutionMode.Execute); targetDb = SqlTestDb.CreateFromExisting(deployParams.DatabaseName); string deployedProc; using (SqlConnection conn = new SqlConnection(targetDb.ConnectionString)) { try { await conn.OpenAsync(); deployedProc = (string)ReliableConnectionHelper.ExecuteScalar(conn, "SELECT OBJECT_DEFINITION (OBJECT_ID(N'Procedure1'));"); } finally { conn.Close(); } } Assert.That(deployedProc, Does.Contain(deployParams.SqlCommandVariableValues[databaseRefVarName])); Assert.That(deployedProc, Does.Contain(deployParams.SqlCommandVariableValues[filterValueVarName])); VerifyAndCleanup(dacpacPath); } finally { sourceDb.Cleanup(); if (targetDb != null) { targetDb.Cleanup(); } } } // /// Verify that SqlCmdVars are set correctly for a generate script request /// [Test] public async Task GenerateDeployScriptWithSqlCmdVariables() { var result = GetLiveAutoCompleteTestObjects(); SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, query: storedProcScript, dbNamePrefix: "DacFxGenerateScriptSqlCmdVarsTest"); try { DacFxService service = new DacFxService(); // First extract a db to have a dacpac to generate the script for later string dacpacPath = InitialExtract(service, sourceDb, result); // Generate script for deploying source dacpac to target db with SqlCmdVars var generateScriptParams = new GenerateDeployScriptParams { PackageFilePath = dacpacPath, DatabaseName = string.Concat(sourceDb.DatabaseName, "-generated"), SqlCommandVariableValues = new Dictionary() { { databaseRefVarName, "OtherDatabase" }, { filterValueVarName, "Employee" } } }; GenerateDeployScriptOperation generateScriptOperation = new GenerateDeployScriptOperation(generateScriptParams, result.ConnectionInfo); service.PerformOperation(generateScriptOperation, TaskExecutionMode.Script); // Verify the SqlCmdVars were set correctly in the script Assert.That(generateScriptOperation.Result.DatabaseScript, Is.Not.Empty); Assert.That(generateScriptOperation.Result.DatabaseScript, Does.Contain($":setvar {databaseRefVarName} \"{generateScriptParams.SqlCommandVariableValues[databaseRefVarName]}\"")); Assert.That(generateScriptOperation.Result.DatabaseScript, Does.Contain($":setvar {filterValueVarName} \"{generateScriptParams.SqlCommandVariableValues[filterValueVarName]}\"")); VerifyAndCleanup(dacpacPath); } finally { sourceDb.Cleanup(); } } /// /// Verify that options are set correctly for a deploy request /// [Test] public async Task DeployWithOptions() { var result = GetLiveAutoCompleteTestObjects(); SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, query: SourceScript, dbNamePrefix: "DacFxDeployOptionsTestSource"); sourceDb.RunQuery(SourceViewScript); SqlTestDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, query: TargetScript, dbNamePrefix: "DacFxDeployOptionsTestTarget"); try { DacFxService service = new DacFxService(); // First extract a db to have a dacpac to deploy later string dacpacPath = InitialExtract(service, sourceDb, result); // Deploy the created dacpac with options var deployParams = new DeployParams { PackageFilePath = dacpacPath, DatabaseName = targetDb.DatabaseName, UpgradeExisting = true, DeploymentOptions = new DeploymentOptions() { ExcludeObjectTypes = new DeploymentOptionProperty(new[] { Enum.GetName(ObjectType.Views) }) } }; // Updating the BooleanOptionsDictionary since it has all the boolean type options deployParams.DeploymentOptions.BooleanOptionsDictionary[nameof(DacDeployOptions.DropObjectsNotInSource)].Value = false; // expect table3 to not have been dropped and view1 to not have been created await VerifyDeployWithOptions(deployParams, targetDb, service, result.ConnectionInfo, expectedTableResult: "table3", expectedViewResult: null); // Deploy the created dacpac with options var deployNoOptionsParams = new DeployParams { PackageFilePath = dacpacPath, DatabaseName = targetDb.DatabaseName, UpgradeExisting = true }; // expect table3 to be dropped and view1 created await VerifyDeployWithOptions(deployNoOptionsParams, targetDb, service, result.ConnectionInfo, expectedTableResult: null, expectedViewResult: "view1"); VerifyAndCleanup(dacpacPath); } finally { sourceDb.Cleanup(); if (targetDb != null) { targetDb.Cleanup(); } } } private async Task VerifyDeployWithOptions(DeployParams deployParams, SqlTestDb targetDb, DacFxService service, ConnectionInfo connInfo, string expectedTableResult, string expectedViewResult) { var deployOperation = new DeployOperation(deployParams, connInfo); service.PerformOperation(deployOperation, TaskExecutionMode.Execute); using (SqlConnection conn = new SqlConnection(targetDb.ConnectionString)) { try { await conn.OpenAsync(); var deployedResult = (string)ReliableConnectionHelper.ExecuteScalar(conn, $"SELECT TABLE_NAME FROM {targetDb.DatabaseName}.INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'table3'; "); Assert.AreEqual(expectedTableResult, deployedResult); deployedResult = (string)ReliableConnectionHelper.ExecuteScalar(conn, $"SELECT TABLE_NAME FROM {targetDb.DatabaseName}.INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = 'view1'; "); Assert.AreEqual(expectedViewResult, deployedResult); } finally { conn.Close(); } } } // /// Verify that options are set correctly for a generate script request /// [Test] public async Task GenerateDeployScriptWithOptions() { var result = GetLiveAutoCompleteTestObjects(); SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, query: SourceScript, dbNamePrefix: "DacFxDeployOptionsTestSource"); sourceDb.RunQuery(SourceViewScript); SqlTestDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, query: TargetScript, dbNamePrefix: "DacFxDeployOptionsTestTarget"); try { DacFxService service = new DacFxService(); // First extract a db to have a dacpac to deploy later string dacpacPath = InitialExtract(service, sourceDb, result); // generate script to deploy the created dacpac with options var generateScriptFalseOptionParams = new GenerateDeployScriptParams { PackageFilePath = dacpacPath, DatabaseName = targetDb.DatabaseName, DeploymentOptions = new DeploymentOptions() { ExcludeObjectTypes = new DeploymentOptionProperty(new[] { Enum.GetName(ObjectType.Views) }) } }; // Updating the BooleanOptionsDictionary since it has all the boolean type properties generateScriptFalseOptionParams.DeploymentOptions.BooleanOptionsDictionary[nameof(DacDeployOptions.DropObjectsNotInSource)].Value = false; var generateScriptFalseOptionOperation = new GenerateDeployScriptOperation(generateScriptFalseOptionParams, result.ConnectionInfo); service.PerformOperation(generateScriptFalseOptionOperation, TaskExecutionMode.Execute); Assert.That(generateScriptFalseOptionOperation.Result.DatabaseScript, Does.Not.Contain("table3")); Assert.That(generateScriptFalseOptionOperation.Result.DatabaseScript, Does.Not.Contain("CREATE VIEW")); // try to deploy with the option set to true to make sure it works var generateScriptTrueOptionParams = new GenerateDeployScriptParams { PackageFilePath = dacpacPath, DatabaseName = targetDb.DatabaseName, DeploymentOptions = new DeploymentOptions() { ExcludeObjectTypes = new DeploymentOptionProperty(new[] { Enum.GetName(ObjectType.Views) }) } }; var generateScriptTrueOptionOperation = new GenerateDeployScriptOperation(generateScriptTrueOptionParams, result.ConnectionInfo); service.PerformOperation(generateScriptTrueOptionOperation, TaskExecutionMode.Execute); Assert.That(generateScriptTrueOptionOperation.Result.DatabaseScript, Does.Contain("DROP TABLE [dbo].[table3]")); Assert.That(generateScriptTrueOptionOperation.Result.DatabaseScript, Does.Not.Contain("CREATE VIEW")); // now generate script without options var generateScriptNoOptionsParams = new GenerateDeployScriptParams { PackageFilePath = dacpacPath, DatabaseName = targetDb.DatabaseName, }; var generateScriptNoOptionsOperation = new GenerateDeployScriptOperation(generateScriptNoOptionsParams, result.ConnectionInfo); service.PerformOperation(generateScriptNoOptionsOperation, TaskExecutionMode.Execute); Assert.That(generateScriptNoOptionsOperation.Result.DatabaseScript, Does.Contain("table3")); Assert.That(generateScriptNoOptionsOperation.Result.DatabaseScript, Does.Contain("CREATE VIEW")); VerifyAndCleanup(dacpacPath); } finally { sourceDb.Cleanup(); if (targetDb != null) { targetDb.Cleanup(); } } } // /// Verify that options can get retrieved from publish profile /// [Test] public async Task GetOptionsFromProfile() { DeploymentOptions expectedResults = DeploymentOptions.GetDefaultPublishOptions(); expectedResults.ExcludeObjectTypes = null; expectedResults.BooleanOptionsDictionary[nameof(DacDeployOptions.IncludeCompositeObjects)].Value = true; expectedResults.BooleanOptionsDictionary[nameof(DacDeployOptions.BlockOnPossibleDataLoss)].Value = true; expectedResults.BooleanOptionsDictionary[nameof(DacDeployOptions.AllowIncompatiblePlatform)].Value = true; expectedResults.BooleanOptionsDictionary[nameof(DacDeployOptions.DisableIndexesForDataPhase)].Value = false; var dacfxRequestContext = new Mock>(); dacfxRequestContext.Setup((RequestContext x) => x.SendResult(It.Is((result) => ValidateOptions(expectedResults, result.DeploymentOptions) == true))).Returns(Task.FromResult(new object())); DacFxService service = new DacFxService(); string file = Path.Combine(publishProfileFolder, "profileWithOptions.publish.xml"); var getOptionsFromProfileParams = new GetOptionsFromProfileParams { ProfilePath = file }; await service.HandleGetOptionsFromProfileRequest(getOptionsFromProfileParams, dacfxRequestContext.Object); dacfxRequestContext.VerifyAll(); } // /// Verify that default options are returned if a profile doesn't specify any options /// [Test] public async Task GetOptionsFromProfileWithoutOptions() { DeploymentOptions expectedResults = DeploymentOptions.GetDefaultPublishOptions(); expectedResults.ExcludeObjectTypes = null; expectedResults.BooleanOptionsDictionary["DisableIndexesForDataPhase"].Value = false; var dacfxRequestContext = new Mock>(); dacfxRequestContext.Setup((RequestContext x) => x.SendResult(It.Is((result) => ValidateOptions(expectedResults, result.DeploymentOptions) == true))).Returns(Task.FromResult(new object())); DacFxService service = new DacFxService(); string file = Path.Combine(publishProfileFolder, "profileNoOptions.publish.xml"); var getOptionsFromProfileParams = new GetOptionsFromProfileParams { ProfilePath = file }; await service.HandleGetOptionsFromProfileRequest(getOptionsFromProfileParams, dacfxRequestContext.Object); dacfxRequestContext.VerifyAll(); } /// /// Verify the default dacFx options for publishing /// [Test] public async Task ValidateGetDefaultPublishOptionsCallFromService() { DeploymentOptions expectedResults = DeploymentOptions.GetDefaultPublishOptions(); var dacfxRequestContext = new Mock>(); dacfxRequestContext.Setup((RequestContext x) => x.SendResult(It.Is((result) => ValidateOptions(expectedResults, result.DeploymentOptions) == true))).Returns(Task.FromResult(new object())); GetDefaultPublishOptionsParams p = new GetDefaultPublishOptionsParams(); DacFxService service = new DacFxService(); await service.HandleGetDefaultPublishOptionsRequest(p, dacfxRequestContext.Object); } /// /// Verify that streaming job /// /// [Test] public async Task ValidateStreamingJob() { var dacfxRequestContext = new Mock>(); DacFxService service = new DacFxService(); ValidateStreamingJobResult expectedResult; // Positive case: both input and output are present expectedResult = new ValidateStreamingJobResult() { Success = true }; dacfxRequestContext.Setup((RequestContext x) => x.SendResult(It.Is((result) => ValidateStreamingJobErrors(expectedResult, result) == true))).Returns(Task.FromResult(new object())); ValidateStreamingJobParams parameters = new ValidateStreamingJobParams() { PackageFilePath = Path.Combine(dacpacsFolder, "StreamingJobTestDb.dacpac"), CreateStreamingJobTsql = goodCreateStreamingJob }; await service.HandleValidateStreamingJobRequest(parameters, dacfxRequestContext.Object); dacfxRequestContext.VerifyAll(); // Negative case: input and output streams are both missing from model const string errorMessage = @"Validation for external streaming job 'myJob' failed: Streaming query statement contains a reference to missing input stream 'MissingEdgeHubInputStream'. You must add it to the database model. Streaming query statement contains a reference to missing output stream 'MissingSqlOutputStream'. You must add it to the database model."; expectedResult = new ValidateStreamingJobResult() { Success = false, ErrorMessage = errorMessage }; dacfxRequestContext.Setup((RequestContext x) => x.SendResult(It.Is((result) => ValidateStreamingJobErrors(expectedResult, result)))).Returns(Task.FromResult(new object())); parameters = new ValidateStreamingJobParams() { PackageFilePath = Path.Combine(dacpacsFolder, "StreamingJobTestDb.dacpac"), CreateStreamingJobTsql = missingCreateBothStreamingJob }; await service.HandleValidateStreamingJobRequest(parameters, dacfxRequestContext.Object); dacfxRequestContext.VerifyAll(); } /// /// Verify Object Types Dictionary items with ObjectType Enum members /// /// [Test] public void ValidateObjectTypesOptionswithEnum() { DeploymentOptions options = new DeploymentOptions(); // Verify the object types dictionary should exists Assert.That(options.ObjectTypesDictionary, Is.Not.Null, "Object types dictionary is empty"); // Verify that the objects dictionary has all the item from Enum Assert.That(options.ObjectTypesDictionary.Count, Is.EqualTo(Enum.GetNames(typeof(ObjectType)).Length), @"ObjectTypesDictionary is missing these objectTypes: {0}", string.Join(", ", Enum.GetNames(typeof(ObjectType)).Except(options.ObjectTypesDictionary.Keys))); // Verify the options in the objects dictionary exists in the ObjectType Enum foreach (var objTypeRow in options.ObjectTypesDictionary) { // Verify the option exists in ObjectType Enum Assert.That(Enum.IsDefined(typeof(ObjectType), objTypeRow.Key), Is.True, $"{objTypeRow.Key} is not an enum member"); // Verify the options display name exists Assert.That(objTypeRow.Value, Is.Not.Empty, $"Display name for the option {objTypeRow.Key} is empty"); } } private bool ValidateStreamingJobErrors(ValidateStreamingJobResult expected, ValidateStreamingJobResult actual) { return expected.Success == actual.Success && expected.ErrorMessage == actual.ErrorMessage; } private bool ValidateOptions(DeploymentOptions expected, DeploymentOptions actual) { System.Reflection.PropertyInfo[] deploymentOptionsProperties = expected.GetType().GetProperties(); var booleanOptionsDictionary = new Dictionary>(); foreach (PropertyInfo v in deploymentOptionsProperties) { if (v.Name != nameof(DeploymentOptions.BooleanOptionsDictionary)) { var defaultP = v.GetValue(expected); var defaultPValue = defaultP != null ? defaultP.GetType().GetProperty("Value").GetValue(defaultP) : defaultP; var actualP = v.GetValue(actual); var actualPValue = actualP.GetType().GetProperty("Value").GetValue(actualP); if (v.Name == nameof(DeploymentOptions.ExcludeObjectTypes)) { Assert.True((defaultP as string[])?.Length == (actualP as string[])?.Length, "Number of excluded objects is different not equal"); } else { //Verifying expected and actual deployment options properties are equal Assert.True((defaultPValue == null && String.IsNullOrEmpty(actualPValue as string)) || (defaultPValue).Equals(actualPValue) , $"Actual Property from Service is not equal to default property for {v.Name}, Actual value: {actualPValue} and Default value: {defaultPValue}"); } } else { booleanOptionsDictionary = v.GetValue(expected) as Dictionary>; } } // Verify expected and actual DeploymentOptions BooleanOptionsDictionary VerifyExpectedAndActualBooleanOptionsDictionary(booleanOptionsDictionary, actual.BooleanOptionsDictionary); return true; } private string InitialExtract(DacFxService service, SqlTestDb sourceDb, LiveConnectionHelper.TestConnectionResult result) { string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); Directory.CreateDirectory(folderPath); var extractParams = new ExtractParams { DatabaseName = sourceDb.DatabaseName, PackageFilePath = Path.Combine(folderPath, string.Format("{0}.dacpac", sourceDb.DatabaseName)), ApplicationName = "test", ApplicationVersion = "1.0.0.0" }; ExtractOperation extractOperation = new ExtractOperation(extractParams, result.ConnectionInfo); service.PerformOperation(extractOperation, TaskExecutionMode.Execute); return extractParams.PackageFilePath; } private void VerifyAndCleanup(string filePath) { // Verify it was created Assert.True(File.Exists(filePath)); // Remove the file if (File.Exists(filePath)) { File.Delete(filePath); } } /// /// Verify expected and actual DeploymentOptions BooleanOptionsDictionary values /// /// /// public void VerifyExpectedAndActualBooleanOptionsDictionary(Dictionary> expectedBooleanOptionsDictionary, Dictionary> actualBooleanOptionsDictionary) { foreach (KeyValuePair> optionRow in expectedBooleanOptionsDictionary) { var expectedValue = optionRow.Value.Value; var actualValue = actualBooleanOptionsDictionary[optionRow.Key].Value; Assert.That(actualValue, Is.EqualTo(expectedValue), $"Actual Property from Service is not equal to default property for {optionRow.Key}"); } } } } [TestFixture] public class TSqlModelRequestTests { private string TSqlModelTestFolder = string.Empty; private DacFxService service = new DacFxService(); [SetUp] public void Create() { TSqlModelTestFolder = Path.Combine("..", "..", "..", "DacFx", "TSqlModels", Guid.NewGuid().ToString()); Directory.CreateDirectory(TSqlModelTestFolder); } [TearDown] public void CleanUp() { Directory.Delete(TSqlModelTestFolder, true); } /// /// Verify the generate Tsql model operation /// [Test] public void GenerateTSqlModelFromSqlFiles() { string sqlTable1DefinitionPath = Path.Join(TSqlModelTestFolder, "table1.sql"); string sqlTable2DefinitionPath = Path.Join(TSqlModelTestFolder, "table2.sql"); const string table1 = @"CREATE TABLE [dbo].[table1] ( [ID] INT NOT NULL PRIMARY KEY, )"; const string table2 = @"CREATE TABLE [dbo].[table2] ( [ID] INT NOT NULL PRIMARY KEY, )"; // create sql file File.WriteAllText(sqlTable1DefinitionPath, table1); File.WriteAllText(sqlTable2DefinitionPath, table2); var generateTSqlScriptParams = new GenerateTSqlModelParams { ProjectUri = Path.Join(TSqlModelTestFolder, "test.sqlproj"), ModelTargetVersion = "Sql160", FilePaths = new[] { sqlTable1DefinitionPath, sqlTable2DefinitionPath } }; GenerateTSqlModelOperation op = new GenerateTSqlModelOperation(generateTSqlScriptParams); var model = op.GenerateTSqlModel(); var objects = model.GetObjects(DacQueryScopes.UserDefined, ModelSchema.Table).ToList(); Assert.That(model.Version.ToString(), Is.EqualTo(generateTSqlScriptParams.ModelTargetVersion), $"Model version is not equal to {generateTSqlScriptParams.ModelTargetVersion}"); Assert.That(objects, Is.Not.Empty, "Model is empty"); var tableNames = objects.Select(o => o.Name.ToString()).ToList(); Assert.That(tableNames.Count, Is.EqualTo(2), "Model was not populated correctly"); CollectionAssert.AreEquivalent(tableNames, new[] { "[dbo].[table1]", "[dbo].[table2]" }, "Table names do not match"); } /// /// Verify the generate Tsql model operation, creates an empty model when files are empty /// [Test] public void GenerateEmptyTSqlModel() { var generateTSqlScriptParams = new GenerateTSqlModelParams { ProjectUri = Path.Join(TSqlModelTestFolder, "test.sqlproj"), ModelTargetVersion = "Sql160", FilePaths = new string[] { } }; GenerateTSqlModelOperation op = new GenerateTSqlModelOperation(generateTSqlScriptParams); var model = op.GenerateTSqlModel(); Assert.That(model.GetObjects(DacQueryScopes.UserDefined, ModelSchema.Table).ToList().Count, Is.EqualTo(0), "Model is not empty"); Assert.That(model.Version.ToString(), Is.EqualTo(generateTSqlScriptParams.ModelTargetVersion), $"Model version is not equal to {generateTSqlScriptParams.ModelTargetVersion}"); } /// /// Verify the generate TSql Model handle /// [Test] public async Task VerifyGenerateTSqlModelHandle() { var generateTSqlScriptParams = new GenerateTSqlModelParams { ProjectUri = Path.Join(TSqlModelTestFolder, "test.sqlproj"), ModelTargetVersion = "Sql160", FilePaths = new string[] { } }; var requestContext = new Mock>(); requestContext.Setup((RequestContext x) => x.SendResult(It.Is((result) => result == true))).Returns(Task.FromResult(new object())); await service.HandleGenerateTSqlModelRequest(generateTSqlScriptParams, requestContext.Object); Assert.That(service.projectModels.Value, Contains.Key(generateTSqlScriptParams.ProjectUri), "Model was not stored under project uri"); } /// /// Verify the get objects TSql Model handle /// [Test] public async Task VerifyGetObjectsFromTSqlModelHandle() { string sqlTable1DefinitionPath = Path.Join(TSqlModelTestFolder, "table1.sql"); string sqlTable2DefinitionPath = Path.Join(TSqlModelTestFolder, "table2.sql"); string view1DefinitionPath = Path.Join(TSqlModelTestFolder, "view1.sql"); const string table1 = @"CREATE TABLE [dbo].[table1] ( [ID] INT NOT NULL PRIMARY KEY, )"; const string table2 = @"CREATE TABLE [dbo].[table2] ( [ID] INT NOT NULL PRIMARY KEY, )"; const string view1 = "CREATE VIEW [dbo].[view1] AS SELECT dbo.table1.* FROM dbo.table1"; // create sql file File.WriteAllText(sqlTable1DefinitionPath, table1); File.WriteAllText(sqlTable2DefinitionPath, table2); File.WriteAllText(view1DefinitionPath, view1); var generateTSqlScriptParams = new GenerateTSqlModelParams { ProjectUri = Path.Join(TSqlModelTestFolder, "test.sqlproj"), ModelTargetVersion = "Sql160", FilePaths = new[] { sqlTable1DefinitionPath, sqlTable2DefinitionPath } }; GenerateTSqlModelOperation op = new GenerateTSqlModelOperation(generateTSqlScriptParams); var model = op.GenerateTSqlModel(); service.projectModels.Value.TryAdd(generateTSqlScriptParams.ProjectUri, model); var getObjectsParams = new GetObjectsFromTSqlModelParams { ProjectUri = Path.Join(TSqlModelTestFolder, "test.sqlproj"), ObjectTypes = new[] { "Table" } }; var requestContext = new Mock>(); var actualResponse = new List(); requestContext.Setup(x => x.SendResult(It.IsAny())) .Callback(actual => actualResponse = actual.ToList()) .Returns(Task.CompletedTask); await service.HandleGetObjectsFromTSqlModelRequest(getObjectsParams, requestContext.Object); Assert.IsNotNull(actualResponse); Assert.AreEqual(actualResponse.Count, 2); CollectionAssert.AreEquivalent(actualResponse.Select(o => o.Name), new[] { "[dbo].[table1]", "[dbo].[table2]" }, "Table names do not match"); } }