// // 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.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.SqlServer.Dac.Projects; using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; using Microsoft.SqlTools.ServiceLayer.SqlProjects; using Microsoft.SqlTools.ServiceLayer.SqlProjects.Contracts; using Microsoft.SqlTools.ServiceLayer.Test.Common; using Microsoft.SqlTools.ServiceLayer.Test.Common.RequestContextMocking; using Microsoft.SqlTools.ServiceLayer.Utility; using NUnit.Framework; namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects { public class SqlProjectsServiceTests : TestBase { internal const string TEST_GUID = "BA5EBA11-C0DE-5EA7-ACED-BABB1E70A575"; [Test] public async Task TestErrorDuringExecution() { SqlProjectsService service = new(); string projectUri = await service.CreateSqlProject(); // validates result.Success == true // Validate that result indicates failure when there's an exception MockRequest requestMock = new(); await service.HandleCreateSqlProjectRequest(new ServiceLayer.SqlProjects.Contracts.CreateSqlProjectParams() { ProjectUri = projectUri, SqlProjectType = ProjectType.SdkStyle }, requestMock.Object); Assert.IsFalse(requestMock.Result.Success, $"{nameof(service.HandleCreateSqlProjectRequest)} when file already exists expected to fail"); Assert.IsTrue(requestMock.Result.ErrorMessage!.Contains("Cannot create a new SQL project") && requestMock.Result.ErrorMessage!.Contains("a file already exists at that location"), $"Error message expected to mention that a file already exists, but instead was: '{requestMock.Result.ErrorMessage}'"); } [Test] public async Task TestOpenCloseProject() { // Setup string sdkProjectUri = TestContext.CurrentContext.GetTestProjectPath(nameof(TestOpenCloseProject) + "Sdk"); string legacyProjectUri = TestContext.CurrentContext.GetTestProjectPath(nameof(TestOpenCloseProject) + "Legacy"); if (File.Exists(sdkProjectUri)) File.Delete(sdkProjectUri); if (File.Exists(legacyProjectUri)) File.Delete(legacyProjectUri); SqlProjectsService service = new(); Assert.AreEqual(0, service.Projects.Count, "Baseline number of loaded projects"); // Validate creating SDK-style project MockRequest requestMock = new(); await service.HandleCreateSqlProjectRequest(new ServiceLayer.SqlProjects.Contracts.CreateSqlProjectParams() { ProjectUri = sdkProjectUri, SqlProjectType = ProjectType.SdkStyle }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleCreateSqlProjectRequest), "SDK"); Assert.AreEqual(1, service.Projects.Count, "Number of loaded projects after creating SDK not as expected"); Assert.IsTrue(service.Projects.ContainsKey(sdkProjectUri), "Loaded project list expected to contain SDK project URI"); Assert.AreEqual(ProjectType.SdkStyle, service.Projects[sdkProjectUri].SqlProjStyle, "SqlProj style expected to be SDK"); // Validate creating Legacy-style project requestMock = new(); await service.HandleCreateSqlProjectRequest(new ServiceLayer.SqlProjects.Contracts.CreateSqlProjectParams() { ProjectUri = legacyProjectUri, SqlProjectType = ProjectType.LegacyStyle }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleCreateSqlProjectRequest), "Legacy"); Assert.AreEqual(2, service.Projects.Count, "Number of loaded projects after creating Legacy"); Assert.IsTrue(service.Projects.ContainsKey(legacyProjectUri), "Loaded project list expected to contain Legacy project URI"); Assert.AreEqual(service.Projects[legacyProjectUri].SqlProjStyle, ProjectType.LegacyStyle, "SqlProj style"); // Validate closing a project requestMock = new(); await service.HandleCloseSqlProjectRequest(new SqlProjectParams() { ProjectUri = sdkProjectUri }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleCloseSqlProjectRequest)); Assert.AreEqual(1, service.Projects.Count, "Number of loaded projects after closing SDK project"); Assert.IsTrue(!service.Projects.ContainsKey(sdkProjectUri), "Loaded project list should not contain SDK after closing"); // Validate opening a project requestMock = new(); await service.HandleOpenSqlProjectRequest(new SqlProjectParams() { ProjectUri = sdkProjectUri }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleOpenSqlProjectRequest)); Assert.AreEqual(2, service.Projects.Count, "Number of loaded projects after re-opening"); Assert.IsTrue(service.Projects.ContainsKey(sdkProjectUri), "Loaded project list expected to contain SDK project URI after re-opening"); } [Test] public async Task TestSqlObjectScriptOperations() { // Setup SqlProjectsService service = new(); string projectUri = await service.CreateSqlProject(); Assert.AreEqual(0, service.Projects[projectUri].SqlObjectScripts.Count, "Baseline number of SqlObjectScripts"); // Validate adding a SQL object script MockRequest requestMock = new(); string scriptRelativePath = "MyTable.sql"; string scriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), scriptRelativePath); await File.WriteAllTextAsync(scriptAbsolutePath, "CREATE TABLE [MyTable] ([Id] INT)"); Assert.IsTrue(File.Exists(scriptAbsolutePath), $"{scriptAbsolutePath} expected to be on disk"); await service.HandleAddSqlObjectScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = scriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddSqlObjectScriptRequest)); Assert.AreEqual(1, service.Projects[projectUri].SqlObjectScripts.Count, "SqlObjectScripts count after add"); Assert.IsTrue(service.Projects[projectUri].SqlObjectScripts.Contains(scriptRelativePath), $"SqlObjectScripts expected to contain {scriptRelativePath}"); // Validate getting a list of the SQL object scripts MockRequest getMock = new(); await service.HandleGetSqlObjectScriptsRequest(new SqlProjectParams() { ProjectUri = projectUri }, getMock.Object); getMock.AssertSuccess(nameof(service.HandleGetSqlObjectScriptsRequest)); Assert.AreEqual(1, getMock.Result.Scripts.Length); Assert.AreEqual(scriptRelativePath, getMock.Result.Scripts[0]); // Validate excluding a SQL object script requestMock = new(); await service.HandleExcludeSqlObjectScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = scriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleExcludeSqlObjectScriptRequest)); Assert.AreEqual(0, service.Projects[projectUri].SqlObjectScripts.Count, "SqlObjectScripts count after exclude"); Assert.IsTrue(File.Exists(scriptAbsolutePath), $"{scriptAbsolutePath} expected to still exist on disk"); // Re-add to set up for Delete requestMock = new(); await service.HandleAddSqlObjectScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = scriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddSqlObjectScriptRequest)); Assert.AreEqual(1, service.Projects[projectUri].SqlObjectScripts.Count, "SqlObjectScripts count after re-add"); // Validate moving a SQL object script string movedScriptRelativePath = @"SubPath\MyRenamedTable.sql"; string movedScriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), FileUtils.NormalizePath(movedScriptRelativePath)); Directory.CreateDirectory(Path.GetDirectoryName(movedScriptAbsolutePath)!); requestMock = new(); await service.HandleMoveSqlObjectScriptRequest(new MoveItemParams() { ProjectUri = projectUri, Path = scriptRelativePath, DestinationPath = movedScriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleMoveSqlObjectScriptRequest)); Assert.IsTrue(File.Exists(movedScriptAbsolutePath), "Script should exist at new location"); Assert.AreEqual(1, service.Projects[projectUri].SqlObjectScripts.Count, "SqlObjectScripts count after move"); // Validate deleting a SQL object script requestMock = new(); await service.HandleDeleteSqlObjectScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = movedScriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleDeleteSqlObjectScriptRequest)); Assert.AreEqual(0, service.Projects[projectUri].SqlObjectScripts.Count, "SqlObjectScripts count after delete"); Assert.IsFalse(File.Exists(movedScriptAbsolutePath), $"{movedScriptAbsolutePath} expected to have been deleted from disk"); } [Test] public async Task TestNoneScriptOperations() { // Setup SqlProjectsService service = new(); string projectUri = await service.CreateSqlProject(); Assert.AreEqual(0, service.Projects[projectUri].NoneScripts.Count, "Baseline number of NoneScripts"); // Validate adding a None script MockRequest requestMock = new(); string relativePath = "NoneIncludeFile.json"; string absolutePath = Path.Join(Path.GetDirectoryName(projectUri), relativePath); #pragma warning disable JSON002 // Probable JSON string detected await File.WriteAllTextAsync(absolutePath, @"{""included"" : false }"); #pragma warning restore JSON002 // Probable JSON string detected Assert.IsTrue(File.Exists(absolutePath), $"{absolutePath} expected to be on disk"); await service.HandleAddNoneScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = relativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddNoneScriptRequest)); Assert.AreEqual(1, service.Projects[projectUri].NoneScripts.Count, "NoneScripts count after add"); Assert.IsTrue(service.Projects[projectUri].NoneScripts.Contains(relativePath), $"NoneScripts expected to contain {relativePath}"); // Validate getting a list of the None scripts MockRequest getMock = new(); await service.HandleGetNoneScriptsRequest(new SqlProjectParams() { ProjectUri = projectUri }, getMock.Object); getMock.AssertSuccess(nameof(service.HandleGetNoneScriptsRequest)); Assert.AreEqual(1, getMock.Result.Scripts.Length); Assert.AreEqual(relativePath, getMock.Result.Scripts[0]); // Validate excluding a None script requestMock = new(); await service.HandleExcludeNoneScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = relativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleExcludeNoneScriptRequest)); Assert.AreEqual(0, service.Projects[projectUri].NoneScripts.Count, "NoneScripts count after exclude"); Assert.IsTrue(File.Exists(absolutePath), $"{absolutePath} expected to still exist on disk"); // Re-add to set up for Delete requestMock = new(); await service.HandleAddNoneScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = relativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddNoneScriptRequest)); Assert.AreEqual(1, service.Projects[projectUri].NoneScripts.Count, "NoneScripts count after re-add"); // Validate moving a None script string movedScriptRelativePath = @"SubPath\RenamedNoneIncludeFile.json"; string movedScriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), FileUtils.NormalizePath(movedScriptRelativePath)); Directory.CreateDirectory(Path.GetDirectoryName(movedScriptAbsolutePath)!); requestMock = new(); await service.HandleMoveNoneScriptRequest(new MoveItemParams() { ProjectUri = projectUri, Path = relativePath, DestinationPath = movedScriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleMoveNoneScriptRequest)); Assert.IsTrue(File.Exists(movedScriptAbsolutePath), "Script should exist at new location"); Assert.AreEqual(1, service.Projects[projectUri].NoneScripts.Count, "NoneScripts count after move"); // Validate deleting a None script requestMock = new(); await service.HandleDeleteNoneScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = movedScriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleDeleteNoneScriptRequest)); Assert.AreEqual(0, service.Projects[projectUri].NoneScripts.Count, "NoneScripts count after delete"); Assert.IsFalse(File.Exists(movedScriptAbsolutePath), $"{movedScriptAbsolutePath} expected to have been deleted from disk"); } [Test] public async Task TestPreDeploymentScriptOperations() { // Setup SqlProjectsService service = new(); string projectUri = await service.CreateSqlProject(); Assert.AreEqual(0, service.Projects[projectUri].PreDeployScripts.Count, "Baseline number of Pre-deployment scripts"); // Validate adding a pre-deployment script MockRequest requestMock = new(); string scriptRelativePath = "PreDeploymentScript.sql"; string scriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), scriptRelativePath); await File.WriteAllTextAsync(scriptAbsolutePath, "SELECT 'Deployment starting...'"); Assert.IsTrue(File.Exists(scriptAbsolutePath), $"{scriptAbsolutePath} expected to be on disk"); await service.HandleAddPreDeploymentScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = scriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddPreDeploymentScriptRequest)); Assert.AreEqual(1, service.Projects[projectUri].PreDeployScripts.Count, "PreDeployScript count after add"); Assert.IsTrue(service.Projects[projectUri].PreDeployScripts.Contains(scriptRelativePath), $"PreDeployScripts expected to contain {scriptRelativePath}"); // Validate getting a list of the pre-deployment scripts MockRequest getMock = new(); await service.HandleGetPreDeploymentScriptsRequest(new SqlProjectParams() { ProjectUri = projectUri }, getMock.Object); getMock.AssertSuccess(nameof(service.HandleGetPreDeploymentScriptsRequest)); Assert.AreEqual(1, getMock.Result.Scripts.Length); Assert.AreEqual(scriptRelativePath, getMock.Result.Scripts[0]); // Validate excluding a pre-deployment script requestMock = new(); await service.HandleExcludePreDeploymentScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = scriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleExcludePreDeploymentScriptRequest)); Assert.AreEqual(0, service.Projects[projectUri].PreDeployScripts.Count, "PreDeployScripts count after exclude"); Assert.IsTrue(File.Exists(scriptAbsolutePath), $"{scriptAbsolutePath} expected to still exist on disk"); // Re-add to set up for Delete requestMock = new(); await service.HandleAddPreDeploymentScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = scriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddPreDeploymentScriptRequest)); Assert.AreEqual(1, service.Projects[projectUri].PreDeployScripts.Count, "PreDeployScripts count after re-add"); // Validate moving a pre-deployment object script string movedScriptRelativePath = @"SubPath\RenamedPreDeploymentScript.sql"; string movedScriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), FileUtils.NormalizePath(movedScriptRelativePath)); Directory.CreateDirectory(Path.GetDirectoryName(movedScriptAbsolutePath)!); requestMock = new(); await service.HandleMovePreDeploymentScriptRequest(new MoveItemParams() { ProjectUri = projectUri, Path = scriptRelativePath, DestinationPath = movedScriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleMovePreDeploymentScriptRequest)); Assert.IsTrue(File.Exists(movedScriptAbsolutePath), "Script should exist at new location"); Assert.AreEqual(1, service.Projects[projectUri].PreDeployScripts.Count, "PreDeployScripts count after move"); // Validate deleting a pre-deployment script requestMock = new(); await service.HandleDeletePreDeploymentScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = movedScriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleDeletePreDeploymentScriptRequest)); Assert.AreEqual(0, service.Projects[projectUri].PreDeployScripts.Count, "PreDeployScripts count after delete"); Assert.IsFalse(File.Exists(movedScriptAbsolutePath), $"{movedScriptAbsolutePath} expected to have been deleted from disk"); } [Test] public async Task TestPostDeploymentScriptAddDeleteExcludeMove() { // Setup SqlProjectsService service = new(); string projectUri = await service.CreateSqlProject(); Assert.AreEqual(0, service.Projects[projectUri].PostDeployScripts.Count, "Baseline number of Post-deployment scripts"); // Validate adding a Post-deployment script MockRequest requestMock = new(); string scriptRelativePath = "PostDeploymentScript.sql"; string scriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), scriptRelativePath); await File.WriteAllTextAsync(scriptAbsolutePath, "SELECT 'Deployment finished!'"); Assert.IsTrue(File.Exists(scriptAbsolutePath), $"{scriptAbsolutePath} expected to be on disk"); await service.HandleAddPostDeploymentScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = scriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddPostDeploymentScriptRequest)); Assert.AreEqual(1, service.Projects[projectUri].PostDeployScripts.Count, "PostDeployScript count after add"); Assert.IsTrue(service.Projects[projectUri].PostDeployScripts.Contains(scriptRelativePath), $"PostDeployScripts expected to contain {scriptRelativePath}"); // Validate getting a list of the post-deployment scripts MockRequest getMock = new(); await service.HandleGetPostDeploymentScriptsRequest(new SqlProjectParams() { ProjectUri = projectUri }, getMock.Object); getMock.AssertSuccess(nameof(service.HandleGetPostDeploymentScriptsRequest)); Assert.AreEqual(1, getMock.Result.Scripts.Length); Assert.AreEqual(scriptRelativePath, getMock.Result.Scripts[0]); // Validate excluding a Post-deployment script requestMock = new(); await service.HandleExcludePostDeploymentScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = scriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleExcludePostDeploymentScriptRequest)); Assert.AreEqual(0, service.Projects[projectUri].PostDeployScripts.Count, "PostDeployScripts count after exclude"); Assert.IsTrue(File.Exists(scriptAbsolutePath), $"{scriptAbsolutePath} expected to still exist on disk"); // Re-add to set up for Delete requestMock = new(); await service.HandleAddPostDeploymentScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = scriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddPostDeploymentScriptRequest)); Assert.AreEqual(1, service.Projects[projectUri].PostDeployScripts.Count, "PostDeployScripts count after re-add"); // Validate moving a post-deployment object script string movedScriptRelativePath = @"SubPath\RenamedPostDeploymentScript.sql"; string movedScriptAbsolutePath = Path.Join(Path.GetDirectoryName(projectUri), FileUtils.NormalizePath(movedScriptRelativePath)); Directory.CreateDirectory(Path.GetDirectoryName(movedScriptAbsolutePath)!); requestMock = new(); await service.HandleMovePostDeploymentScriptRequest(new MoveItemParams() { ProjectUri = projectUri, Path = scriptRelativePath, DestinationPath = movedScriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleMovePostDeploymentScriptRequest)); Assert.IsTrue(File.Exists(movedScriptAbsolutePath), "Script should exist at new location"); Assert.AreEqual(1, service.Projects[projectUri].PostDeployScripts.Count, "PostDeployScripts count after move"); // Validate deleting a Post-deployment script requestMock = new(); await service.HandleDeletePostDeploymentScriptRequest(new SqlProjectScriptParams() { ProjectUri = projectUri, Path = movedScriptRelativePath }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleDeletePostDeploymentScriptRequest)); Assert.AreEqual(0, service.Projects[projectUri].PostDeployScripts.Count, "PostDeployScripts count after delete"); Assert.IsFalse(File.Exists(movedScriptAbsolutePath), $"{movedScriptAbsolutePath} expected to have been deleted from disk"); } #region Database reference tests [Test] public async Task TestGetDatabaseReferences() { var (service, projectUri, _, _) = await SetUpDatabaseReferenceTest(); // directly add some database references SystemDatabaseReference systemDatabaseReference = new SystemDatabaseReference(SystemDatabase.MSDB, suppressMissingDependencies: true); DacpacReference dacpacReference = new DacpacReference("OtherDatabaseDacpac.dacpac", suppressMissingDependencies: true); SqlProjectReference sqlProjectReference = new SqlProjectReference("OtherDatabaseProject.sqlproj", projectGuid: TEST_GUID, suppressMissingDependencies: true); service.Projects[projectUri].DatabaseReferences.Add(systemDatabaseReference); service.Projects[projectUri].DatabaseReferences.Add(dacpacReference); service.Projects[projectUri].DatabaseReferences.Add(sqlProjectReference); // Validate getting a list of the post-deployment scripts MockRequest getMock = new(); await service.HandleGetDatabaseReferencesRequest(new SqlProjectParams() { ProjectUri = projectUri }, getMock.Object); getMock.AssertSuccess(nameof(service.HandleGetDatabaseReferencesRequest)); Assert.AreEqual(1, getMock.Result.SystemDatabaseReferences.Length); Assert.AreEqual(systemDatabaseReference, getMock.Result.SystemDatabaseReferences[0]); Assert.AreEqual(1, getMock.Result.DacpacReferences.Length); Assert.AreEqual(dacpacReference, getMock.Result.DacpacReferences[0]); Assert.AreEqual(1, getMock.Result.SqlProjectReferences.Length); Assert.AreEqual(sqlProjectReference, getMock.Result.SqlProjectReferences[0]); } [Test] public async Task TestDatabaseReferenceDelete() { var (service, projectUri, _, _) = await SetUpDatabaseReferenceTest(); // directly add a reference so there's something to delete service.Projects[projectUri].DatabaseReferences.Add(new SystemDatabaseReference(SystemDatabase.Master, suppressMissingDependencies: true)); Assert.AreEqual(1, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding reference"); MockRequest requestMock = new(); await service.HandleDeleteDatabaseReferenceRequest(new DeleteDatabaseReferenceParams() { ProjectUri = projectUri, Name = SystemDatabase.Master.ToString() }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleDeleteDatabaseReferenceRequest)); Assert.AreEqual(0, service.Projects[projectUri].DatabaseReferences.Count, "Database references after deleting reference"); } [Test] public async Task TestSystemDatabaseReferenceAdd() { var (service, projectUri, _, _) = await SetUpDatabaseReferenceTest(); MockRequest requestMock = new(); await service.HandleAddSystemDatabaseReferenceRequest(new AddSystemDatabaseReferenceParams() { ProjectUri = projectUri, SystemDatabase = SystemDatabase.MSDB, DatabaseLiteral = "EmEssDeeBee", SuppressMissingDependencies = false }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddSystemDatabaseReferenceRequest)); Assert.AreEqual(1, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding system db reference"); SystemDatabaseReference systemDbRef = (SystemDatabaseReference)service.Projects[projectUri].DatabaseReferences.Get(SystemDatabase.MSDB.ToString()); Assert.AreEqual(SystemDatabase.MSDB, systemDbRef.SystemDb, "Referenced system DB"); Assert.AreEqual("EmEssDeeBee", systemDbRef.DatabaseVariableLiteralName); Assert.IsFalse(systemDbRef.SuppressMissingDependencies, nameof(systemDbRef.SuppressMissingDependencies)); } [Test] public async Task TestDacpacReferenceAdd() { var (service, projectUri, databaseVar, serverVar) = await SetUpDatabaseReferenceTest(); // Validate adding a dacpac reference on the same server string mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "OtherDatabaseSameServer.dacpac"); MockRequest requestMock = new(); await service.HandleAddDacpacReferenceRequest(new AddDacpacReferenceParams() { ProjectUri = projectUri, DacpacPath = mockReferencePath, SuppressMissingDependencies = false }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddDacpacReferenceRequest), "same server"); Assert.AreEqual(1, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding dacpac reference (same server)"); DacpacReference dacpacRef = (DacpacReference)service.Projects[projectUri].DatabaseReferences.Get(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT)); Assert.AreEqual(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT), dacpacRef.DacpacPath, "Referenced dacpac"); Assert.IsFalse(dacpacRef.SuppressMissingDependencies, nameof(dacpacRef.SuppressMissingDependencies)); Assert.IsNull(dacpacRef.DatabaseVariableLiteralName, nameof(dacpacRef.DatabaseVariableLiteralName)); Assert.IsNull(dacpacRef.DatabaseVariable, nameof(dacpacRef.DatabaseVariable)); // Validate adding a dacpac reference via SQLCMD variable mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "OtherDatabaseSqlCmd.dacpac"); requestMock = new(); await service.HandleAddDacpacReferenceRequest(new AddDacpacReferenceParams() { ProjectUri = projectUri, DacpacPath = mockReferencePath, SuppressMissingDependencies = false, DatabaseVariable = databaseVar.Name, ServerVariable = serverVar.Name }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddDacpacReferenceRequest), "sqlcmdvar"); Assert.AreEqual(2, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding dacpac reference (sqlcmdvar)"); dacpacRef = (DacpacReference)service.Projects[projectUri].DatabaseReferences.Get(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT)); Assert.AreEqual(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT), dacpacRef.DacpacPath, "Referenced dacpac"); Assert.AreEqual(databaseVar.Name, dacpacRef.DatabaseVariable!.VarName); Assert.AreEqual(serverVar.Name, dacpacRef.ServerVariable!.VarName); Assert.IsFalse(dacpacRef.SuppressMissingDependencies, nameof(dacpacRef.SuppressMissingDependencies)); // Validate adding a dacpac reference via database literal mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "OtherDatabaseLiteral.dacpac"); requestMock = new(); await service.HandleAddDacpacReferenceRequest(new AddDacpacReferenceParams() { ProjectUri = projectUri, DacpacPath = mockReferencePath, SuppressMissingDependencies = false, DatabaseLiteral = "DacpacLiteral" }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddDacpacReferenceRequest), "db literal"); Assert.AreEqual(3, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding dacpac reference (db literal)"); dacpacRef = (DacpacReference)service.Projects[projectUri].DatabaseReferences.Get(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT)); Assert.AreEqual(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT), dacpacRef.DacpacPath, "Referenced dacpac"); Assert.AreEqual("DacpacLiteral", dacpacRef.DatabaseVariableLiteralName, nameof(dacpacRef.DatabaseVariableLiteralName)); Assert.IsFalse(dacpacRef.SuppressMissingDependencies, nameof(dacpacRef.SuppressMissingDependencies)); } [Test] public async Task TestSqlProjectReferenceAdd() { var (service, projectUri, databaseVar, serverVar) = await SetUpDatabaseReferenceTest(); // Validate adding a project reference on the same server string mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "..", "SameDatabase", "SameDatabaseSqlCmd.sqlproj"); MockRequest requestMock = new(); await service.HandleAddSqlProjectReferenceRequest(new AddSqlProjectReferenceParams() { ProjectUri = projectUri, ProjectPath = mockReferencePath, SuppressMissingDependencies = false }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddSqlProjectReferenceRequest), "same server"); Assert.AreEqual(1, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding SQL project reference (same server)"); SqlProjectReference projectRef = (SqlProjectReference)service.Projects[projectUri].DatabaseReferences.Get(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT)); Assert.AreEqual(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT), projectRef.ProjectPath, "Referenced project"); Assert.IsFalse(projectRef.SuppressMissingDependencies, nameof(projectRef.SuppressMissingDependencies)); Assert.IsNull(projectRef.DatabaseVariableLiteralName, nameof(projectRef.DatabaseVariableLiteralName)); Assert.IsNull(projectRef.DatabaseVariable, nameof(projectRef.DatabaseVariable)); // Validate adding a project reference via SQLCMD variable mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "..", "OtherDatabase", "OtherDatabaseSqlCmd.sqlproj"); requestMock = new(); await service.HandleAddSqlProjectReferenceRequest(new AddSqlProjectReferenceParams() { ProjectUri = projectUri, ProjectPath = mockReferencePath, ProjectGuid = TEST_GUID, SuppressMissingDependencies = false, DatabaseVariable = databaseVar.Name, ServerVariable = serverVar.Name }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddSqlProjectReferenceRequest)); Assert.AreEqual(2, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding SQL project reference (sqlcmdvar)"); projectRef = (SqlProjectReference)service.Projects[projectUri].DatabaseReferences.Get(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT)); Assert.AreEqual(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT), projectRef.ProjectPath, "Referenced project"); Assert.AreEqual(TEST_GUID, projectRef.ProjectGuid, "Referenced project GUID"); Assert.AreEqual(databaseVar.Name, projectRef.DatabaseVariable!.VarName); Assert.AreEqual(serverVar.Name, projectRef.ServerVariable!.VarName); Assert.IsFalse(projectRef.SuppressMissingDependencies, nameof(projectRef.SuppressMissingDependencies)); // Validate adding a project reference via database literal mockReferencePath = Path.Join(Path.GetDirectoryName(projectUri), "..", "OtherDatabase", "OtherDatabaseLiteral.sqlproj"); requestMock = new(); await service.HandleAddSqlProjectReferenceRequest(new AddSqlProjectReferenceParams() { ProjectUri = projectUri, ProjectPath = mockReferencePath, ProjectGuid = TEST_GUID, SuppressMissingDependencies = false, DatabaseLiteral = "ProjectLiteral" }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddSqlProjectReferenceRequest)); Assert.AreEqual(3, service.Projects[projectUri].DatabaseReferences.Count, "Database references after adding SQL project reference (db literal)"); projectRef = (SqlProjectReference)service.Projects[projectUri].DatabaseReferences.Get(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT)); Assert.AreEqual(FileUtils.NormalizePath(mockReferencePath, PlatformID.Win32NT), projectRef.ProjectPath, "Referenced project"); Assert.AreEqual(TEST_GUID, projectRef.ProjectGuid, "Referenced project GUID"); Assert.AreEqual("ProjectLiteral", projectRef.DatabaseVariableLiteralName); Assert.IsFalse(projectRef.SuppressMissingDependencies, nameof(projectRef.SuppressMissingDependencies)); } #endregion [Test] public async Task TestFolderOperations() { // Setup SqlProjectsService service = new(); string projectUri = await service.CreateSqlProject(); Assert.AreEqual(0, service.Projects[projectUri].Folders.Count, "Baseline number of folders"); // Validate adding a folder MockRequest requestMock = new(); FolderParams folderParams = new FolderParams() { ProjectUri = projectUri, Path = "TestFolder" }; await service.HandleAddFolderRequest(folderParams, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddFolderRequest)); Assert.AreEqual(1, service.Projects[projectUri].Folders.Count, "Folder count after add"); Assert.IsTrue(Directory.Exists(Path.Join(Path.GetDirectoryName(projectUri), folderParams.Path)), $"Subfolder '{folderParams.Path}' expected to exist on disk"); Assert.IsTrue(service.Projects[projectUri].Folders.Contains(folderParams.Path), $"SqlObjectScripts expected to contain {folderParams.Path}"); // Validate getting a list of the post-deployment scripts MockRequest getMock = new(); await service.HandleGetFoldersRequest(new SqlProjectParams() { ProjectUri = projectUri }, getMock.Object); getMock.AssertSuccess(nameof(service.HandleGetFoldersRequest)); Assert.AreEqual(1, getMock.Result.Folders.Length); Assert.AreEqual(folderParams.Path, getMock.Result.Folders[0]); // Validate deleting a folder requestMock = new(); await service.HandleDeleteFolderRequest(folderParams, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleDeleteFolderRequest)); Assert.AreEqual(0, service.Projects[projectUri].Folders.Count, "Folder count after delete"); } [Test] public async Task TestSqlCmdVariablesOperations() { SqlProjectsService service = new(); string projectUri = await service.CreateSqlProject(); Assert.AreEqual(0, service.Projects[projectUri].SqlCmdVariables.Count, "Baseline number of SQLCMD variables not as expected"); // Validate adding a SQLCMD variable MockRequest requestMock = new(); const string variableName = "TestVarName"; await service.HandleAddSqlCmdVariableRequest(new AddSqlCmdVariableParams() { ProjectUri = projectUri, Name = variableName, DefaultValue = "TestVarDefaultValue", }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleAddSqlCmdVariableRequest)); Assert.AreEqual(1, service.Projects[projectUri].SqlCmdVariables.Count, "Number of SQLCMD variables after addition not as expected"); Assert.IsTrue(service.Projects[projectUri].SqlCmdVariables.Contains(variableName), $"List of SQLCMD variables expected to contain {variableName}"); // Validate getting a list of the SQLCMD variables MockRequest getMock = new(); await service.HandleGetSqlCmdVariablesRequest(new SqlProjectParams() { ProjectUri = projectUri }, getMock.Object); getMock.AssertSuccess(nameof(service.HandleGetSqlCmdVariablesRequest)); Assert.AreEqual(1, getMock.Result.SqlCmdVariables.Length); Assert.AreEqual(variableName, getMock.Result.SqlCmdVariables[0].VarName); // Validate updating a SQLCMD variable const string updatedDefaultValue = "UpdatedDefaultValues"; requestMock = new(); await service.HandleUpdateSqlCmdVariableRequest(new AddSqlCmdVariableParams() { ProjectUri = projectUri, Name = variableName, DefaultValue = updatedDefaultValue, }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleUpdateSqlCmdVariableRequest)); Assert.AreEqual(1, service.Projects[projectUri].SqlCmdVariables.Count, "Number of SQLCMD variables after update not as expected"); Assert.AreEqual(updatedDefaultValue, service.Projects[projectUri].SqlCmdVariables.First().DefaultValue, "Updated default value"); // Validate deleting a SQLCMD variable requestMock = new(); await service.HandleDeleteSqlCmdVariableRequest(new DeleteSqlCmdVariableParams() { ProjectUri = projectUri, Name = variableName, }, requestMock.Object); requestMock.AssertSuccess(nameof(service.HandleDeleteSqlCmdVariableRequest)); Assert.AreEqual(0, service.Projects[projectUri].SqlCmdVariables.Count, "Number of SQLCMD variables after deletion not as expected"); } [Test] public async Task TestCrossPlatformUpdates() { string inputProjectPath = Path.Join(Path.GetDirectoryName(typeof(SqlProjectsServiceTests).Assembly.Location), "SqlProjects", "Inputs", "SSDTProject.sqlproj"); string projectPath = Path.Join(TestContext.CurrentContext.GetTestWorkingFolder(), "SSDTProject.sqlproj"); Directory.CreateDirectory(Path.GetDirectoryName(projectPath)!); File.Copy(inputProjectPath, projectPath); SqlProjectsService service = new(); /// Validate that the cross-platform status can be fetched MockRequest getRequestMock = new(); await service.HandleGetCrossPlatformCompatibilityRequest(new SqlProjectParams() { ProjectUri = projectPath }, getRequestMock.Object); getRequestMock.AssertSuccess(nameof(service.HandleGetCrossPlatformCompatibilityRequest)); Assert.IsFalse(getRequestMock.Result.IsCrossPlatformCompatible, "Input file should not be cross-platform compatible before conversion"); // Validate that the project can be updated MockRequest updateRequestMock = new(); await service.HandleUpdateProjectForCrossPlatformRequest(new SqlProjectParams() { ProjectUri = projectPath, }, updateRequestMock.Object); updateRequestMock.AssertSuccess(nameof(service.HandleUpdateProjectForCrossPlatformRequest)); // Validate that the cross-platform status has changed getRequestMock = new(); await service.HandleGetCrossPlatformCompatibilityRequest(new SqlProjectParams() { ProjectUri = projectPath }, getRequestMock.Object); getRequestMock.AssertSuccess(nameof(service.HandleGetCrossPlatformCompatibilityRequest)); Assert.IsTrue(((GetCrossPlatformCompatibilityResult)getRequestMock.Result).IsCrossPlatformCompatible, "Input file should be cross-platform compatible after conversion"); } [Test] public async Task TestProjectProperties() { SqlProjectsService service = new(); string projectUri = await service.CreateSqlProject(); MockRequest mock = new(); await service.HandleGetProjectPropertiesRequest(new SqlProjectParams() { ProjectUri = projectUri }, mock.Object); mock.AssertSuccess(nameof(service.HandleGetProjectPropertiesRequest)); Assert.IsTrue(Guid.TryParse(mock.Result.ProjectGuid, out _), $"{mock.Result.ProjectGuid} should be set"); Assert.AreEqual("AnyCPU", mock.Result.Platform); Assert.AreEqual("Debug", mock.Result.Configuration); Assert.AreEqual(@"bin\Debug\", mock.Result.OutputPath); // default value is normalized to Windows slashes Assert.AreEqual("SQL_Latin1_General_CP1_CI_AS", mock.Result.DefaultCollation); Assert.IsNull(mock.Result.DatabaseSource, nameof(mock.Result.DatabaseSource)); // validate DatabaseSource is null when the tag isn't present // Validate that DatabaseSource can be set when the tag doesn't exist MockRequest setSourceMock = new(); await service.HandleSetDatabaseSourceRequest(new SetDatabaseSourceParams() { ProjectUri = projectUri, DatabaseSource = "TestSource" }, setSourceMock.Object); setSourceMock.AssertSuccess(nameof(service.HandleSetDatabaseSourceRequest)); Assert.AreEqual("TestSource", service.Projects[projectUri].Properties.DatabaseSource); // Validate DatabaseSource is read when it has a value mock = new(); await service.HandleGetProjectPropertiesRequest(new SqlProjectParams() { ProjectUri = projectUri }, mock.Object); mock.AssertSuccess(nameof(service.HandleGetProjectPropertiesRequest)); Assert.AreEqual("TestSource", mock.Result.DatabaseSource); } #region Helpers private async Task<(SqlProjectsService Service, string ProjectUri, SqlCmdVariable DatabaseVar, SqlCmdVariable ServerVar)> SetUpDatabaseReferenceTest() { // Setup SqlProjectsService service = new(); string projectUri = await service.CreateSqlProject(); SqlCmdVariable databaseVar = new SqlCmdVariable("$(OtherDb)", "OtherDbDefaultValue", "OtherDbValue"); SqlCmdVariable serverVar = new SqlCmdVariable("$(OtherServer)", "OtherServerDefaultValue", "OtherServerValue"); service.Projects[projectUri].SqlCmdVariables.Add(databaseVar); service.Projects[projectUri].SqlCmdVariables.Add(serverVar); Assert.AreEqual(0, service.Projects[projectUri].DatabaseReferences.Count, "Baseline number of database references"); return (service, projectUri, databaseVar, serverVar); } #endregion } internal static class SqlProjectsExtensions { /// /// Uses the service to create a new SQL project /// /// /// public async static Task CreateSqlProject(this SqlProjectsService service) { string projectUri = TestContext.CurrentContext.GetTestProjectPath(); MockRequest requestMock = new(); await service.HandleCreateSqlProjectRequest(new ServiceLayer.SqlProjects.Contracts.CreateSqlProjectParams() { ProjectUri = projectUri, SqlProjectType = ProjectType.SdkStyle }, requestMock.Object); requestMock.AssertSuccess(nameof(CreateSqlProject)); return projectUri; } } }