diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Folders/AddFolder.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Folders/AddFolder.cs new file mode 100644 index 00000000..8b5de800 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Folders/AddFolder.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.Hosting.Protocol.Contracts; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.SqlProjects.Contracts +{ + /// + /// Parameters for adding, deleting, or excluding a folder + /// + public class FolderParams : SqlProjectParams + { + /// + /// Path of the folder, typically relative to the .sqlproj file + /// + public string Path { get; set; } + } + + public class AddFolderRequest + { + public static readonly RequestType Type = RequestType.Create("sqlProjects/addFolder"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Folders/DeleteFolder.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Folders/DeleteFolder.cs new file mode 100644 index 00000000..00cca65b --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/Contracts/Folders/DeleteFolder.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.Hosting.Protocol.Contracts; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.SqlProjects.Contracts +{ + public class DeleteFolderRequest + { + public static readonly RequestType Type = RequestType.Create("sqlProjects/deleteFolder"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs index 2e81496a..0d8b212f 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SqlProjects/SqlProjectsService.cs @@ -50,6 +50,10 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects serviceHost.SetRequestHandler(DeleteSqlObjectScriptRequest.Type, HandleDeleteSqlObjectScriptRequest, isParallelProcessingSupported: false); serviceHost.SetRequestHandler(ExcludeSqlObjectScriptRequest.Type, HandleExcludeSqlObjectScriptRequest, isParallelProcessingSupported: false); + // Folder functions + serviceHost.SetRequestHandler(AddFolderRequest.Type, HandleAddFolderRequest, isParallelProcessingSupported: false); + serviceHost.SetRequestHandler(DeleteFolderRequest.Type, HandleDeleteFolderRequest, isParallelProcessingSupported: false); + // SQLCMD variable functions serviceHost.SetRequestHandler(AddSqlCmdVariableRequest.Type, HandleAddSqlCmdVariableRequest, isParallelProcessingSupported: false); serviceHost.SetRequestHandler(DeleteSqlCmdVariableRequest.Type, HandleDeleteSqlCmdVariableRequest, isParallelProcessingSupported: false); @@ -143,6 +147,20 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlProjects #endregion + #region Folder functions + + internal async Task HandleAddFolderRequest(FolderParams requestParams, RequestContext requestContext) + { + await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).Folders.Add(new Folder(requestParams.Path)), requestContext); + } + + internal async Task HandleDeleteFolderRequest(FolderParams requestParams, RequestContext requestContext) + { + await RunWithErrorHandling(() => GetProject(requestParams.ProjectUri).Folders.Delete(requestParams.Path), requestContext); + } + + #endregion + #region SQLCMD variable functions internal async Task HandleAddSqlCmdVariableRequest(AddSqlCmdVariableParams requestParams, RequestContext requestContext) diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs index bb17dc9d..18e1e89d 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SqlProjects/SqlProjectsServiceTests.cs @@ -252,6 +252,37 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.SqlProjects Assert.IsFalse(service.Projects[projectUri].DatabaseReferences.Any(x => x is SqlProjectReference), "Database references list expected to not contain the SQL Project reference"); } + [Test] + public async Task TestFolderAddDelete() + { + // 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 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 TestSqlCmdVariablesAddDelete() {