From d9a00c868df20715b59cd106d259c21f3e24a49f Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Thu, 5 Dec 2024 11:44:28 +0100 Subject: [PATCH] SLVS-1404 Implement DeleteBinding method in SolutionBindingRepository (#5874) --- .../SolutionBindingFileLoaderTests.cs | 335 ++++++++++-------- .../SolutionBindingRepositoryTests.cs | 117 +++--- .../Persistence/ISolutionBindingFileLoader.cs | 14 +- .../PersistenceStrings.Designer.cs | 9 + .../Persistence/PersistenceStrings.resx | 3 + .../Persistence/SolutionBindingFileLoader.cs | 152 ++++---- .../Persistence/SolutionBindingRepository.cs | 6 +- .../Binding/ISolutionBindingRepository.cs | 3 +- 8 files changed, 348 insertions(+), 291 deletions(-) diff --git a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingFileLoaderTests.cs b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingFileLoaderTests.cs index 5912c344e3..862a5988b6 100644 --- a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingFileLoaderTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingFileLoaderTests.cs @@ -20,54 +20,48 @@ using System.IO; using System.IO.Abstractions; +using NSubstitute.ExceptionExtensions; using SonarLint.VisualStudio.ConnectedMode.Persistence; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; -namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence +namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence; + +[TestClass] +public class SolutionBindingFileLoaderTests { - [TestClass] - public class SolutionBindingFileLoaderTests + private const string MockFilePath = "c:\\test.txt"; + private const string MockDirectory = "c:\\"; + private BindingJsonModel bindingJsonModel; + private IFileSystem fileSystem; + private ILogger logger; + private string serializedProject; + private SolutionBindingFileLoader testSubject; + + [TestInitialize] + public void TestInitialize() { - private Mock logger; - private Mock fileSystem; - private SolutionBindingFileLoader testSubject; - private BindingJsonModel bindingJsonModel; - private string serializedProject; - - private const string MockFilePath = "c:\\test.txt"; - private const string MockDirectory = "c:\\"; + logger = Substitute.For(); + fileSystem = Substitute.For(); - [TestInitialize] - public void TestInitialize() - { - logger = new Mock(); - fileSystem = new Mock(); + testSubject = new SolutionBindingFileLoader(logger, fileSystem); - testSubject = new SolutionBindingFileLoader(logger.Object, fileSystem.Object); + fileSystem.Directory.Exists(MockDirectory).Returns(true); - fileSystem.Setup(x => x.Directory.Exists(MockDirectory)).Returns(true); - - bindingJsonModel = new BindingJsonModel + bindingJsonModel = new BindingJsonModel + { + ServerUri = new Uri("http://xxx.www.zzz/yyy:9000"), + Organization = null, + ProjectKey = "MyProject Key", + ProjectName = "projectName", + ServerConnectionId = null, + Profiles = new Dictionary { - ServerUri = new Uri("http://xxx.www.zzz/yyy:9000"), - Organization = null, - ProjectKey = "MyProject Key", - ProjectName = "projectName", - ServerConnectionId = null, - Profiles = new Dictionary - { - { - Language.CSharp, - new ApplicableQualityProfile - { - ProfileKey = "sonar way", ProfileTimestamp = DateTime.Parse("2020-02-25T08:57:54+0000") - } - } - } - }; - - serializedProject = @"{ + { Language.CSharp, new ApplicableQualityProfile { ProfileKey = "sonar way", ProfileTimestamp = DateTime.Parse("2020-02-25T08:57:54+0000") } } + } + }; + + serializedProject = @"{ ""ServerUri"": ""http://xxx.www.zzz/yyy:9000"", ""ProjectKey"": ""MyProject Key"", ""ProjectName"": ""projectName"", @@ -78,156 +72,191 @@ public void TestInitialize() } } }"; - } + } - [TestMethod] - public void Ctor_NullLogger_Exception() - { - Action act = () => new SolutionBindingFileLoader(null, null); + [TestMethod] + public void Ctor_NullLogger_Exception() + { + Action act = () => new SolutionBindingFileLoader(null, null); - act.Should().ThrowExactly().And.ParamName.Should().Be("logger"); - } + act.Should().ThrowExactly().And.ParamName.Should().Be("logger"); + } - [TestMethod] - public void Ctor_NullFileSystem_Exception() - { - Action act = () => new SolutionBindingFileLoader(logger.Object, null); + [TestMethod] + public void Ctor_NullFileSystem_Exception() + { + Action act = () => new SolutionBindingFileLoader(logger, null); - act.Should().ThrowExactly().And.ParamName.Should().Be("fileSystem"); - } + act.Should().ThrowExactly().And.ParamName.Should().Be("fileSystem"); + } - [TestMethod] - public void Save_DirectoryDoesNotExist_DirectoryIsCreated() - { - fileSystem.Setup(x => x.Directory.Exists(MockDirectory)).Returns(false); + [TestMethod] + public void Save_DirectoryDoesNotExist_DirectoryIsCreated() + { + fileSystem.Directory.Exists(MockDirectory).Returns(false); - testSubject.Save(MockFilePath, bindingJsonModel); + testSubject.Save(MockFilePath, bindingJsonModel); - fileSystem.Verify(x => x.Directory.CreateDirectory(MockDirectory), Times.Once); - } + fileSystem.Directory.Received(1).CreateDirectory(MockDirectory); + } - [TestMethod] - public void Save_DirectoryExists_DirectoryNotCreated() - { - fileSystem.Setup(x => x.Directory.Exists(MockDirectory)).Returns(true); + [TestMethod] + public void Save_DirectoryExists_DirectoryNotCreated() + { + fileSystem.Directory.Exists(MockDirectory).Returns(true); - testSubject.Save(MockFilePath, bindingJsonModel); + testSubject.Save(MockFilePath, bindingJsonModel); - fileSystem.Verify(x => x.Directory.CreateDirectory(It.IsAny()), Times.Never); - } + fileSystem.Directory.DidNotReceive().CreateDirectory(Arg.Any()); + } - [TestMethod] - public void Save_ReturnsTrue() - { - fileSystem.Setup(x => x.File.WriteAllText(MockFilePath, serializedProject)); + [TestMethod] + public void Save_ReturnsTrue() + { + var actual = testSubject.Save(MockFilePath, bindingJsonModel); + actual.Should().BeTrue(); + } - var actual = testSubject.Save(MockFilePath, bindingJsonModel); - actual.Should().BeTrue(); - } + [TestMethod] + public void Save_FileSerializedAndWritten() + { + testSubject.Save(MockFilePath, bindingJsonModel); - [TestMethod] - public void Save_FileSerializedAndWritten() - { - fileSystem.Setup(x => x.File.WriteAllText(MockFilePath, serializedProject)); + fileSystem.File.Received(1).WriteAllText(MockFilePath, serializedProject); + } - testSubject.Save(MockFilePath, bindingJsonModel); + [TestMethod] + public void Save_NonCriticalException_False() + { + fileSystem.File.When(x => x.WriteAllText(MockFilePath, Arg.Any())).Throw(); - fileSystem.Verify(x => x.File.WriteAllText(MockFilePath, serializedProject), Times.Once); - } + var actual = testSubject.Save(MockFilePath, bindingJsonModel); + actual.Should().BeFalse(); + } - [TestMethod] - public void Save_NonCriticalException_False() - { - fileSystem.Setup(x => x.File.WriteAllText(MockFilePath, It.IsAny())).Throws(); + [TestMethod] + public void Save_CriticalException_Exception() + { + fileSystem.File.When(x => x.WriteAllText(MockFilePath, Arg.Any())).Throw(); - var actual = testSubject.Save(MockFilePath, bindingJsonModel); - actual.Should().BeFalse(); - } + Action act = () => testSubject.Save(MockFilePath, bindingJsonModel); - [TestMethod] - public void Save_CriticalException_Exception() - { - fileSystem.Setup(x => x.File.WriteAllText(MockFilePath, It.IsAny())).Throws(); + act.Should().ThrowExactly(); + } - Action act = () => testSubject.Save(MockFilePath, bindingJsonModel); + [DataTestMethod] + [DataRow("")] + [DataRow(null)] + public void Load_FilePathIsNull_Null(string filePath) + { + var actual = testSubject.Load(filePath); + actual.Should().Be(null); + } - act.Should().ThrowExactly(); - } + [TestMethod] + public void Load_FileDoesNotExist_Null() + { + MockFileNotExists(MockFilePath); - [DataTestMethod] - [DataRow("")] - [DataRow(null)] - public void Load_FilePathIsNull_Null(string filePath) - { - var actual = testSubject.Load(filePath); - actual.Should().Be(null); - } + var actual = testSubject.Load(MockFilePath); + actual.Should().Be(null); + } - [TestMethod] - public void Load_FileDoesNotExist_Null() - { - fileSystem.Setup(x => x.File.Exists(MockFilePath)).Returns(false); + [TestMethod] + public void Load_InvalidJson_Null() + { + MockFileExists(MockFilePath); + fileSystem.File.ReadAllText(MockFilePath).Returns("bad json"); - var actual = testSubject.Load(MockFilePath); - actual.Should().Be(null); - } + var actual = testSubject.Load(MockFilePath); + actual.Should().Be(null); + } - [TestMethod] - public void Load_InvalidJson_Null() - { - fileSystem.Setup(x => x.File.Exists(MockFilePath)).Returns(true); - fileSystem.Setup(x => x.File.ReadAllText(MockFilePath)).Returns("bad json"); + [TestMethod] + public void Load_NonCriticalException_Null() + { + MockFileExists(MockFilePath); + fileSystem.File.ReadAllText(MockFilePath).Throws(); - var actual = testSubject.Load(MockFilePath); - actual.Should().Be(null); - } + var actual = testSubject.Load(MockFilePath); + actual.Should().Be(null); + } - [TestMethod] - public void Load_NonCriticalException_Null() - { - fileSystem.Setup(x => x.File.Exists(MockFilePath)).Returns(true); - fileSystem.Setup(x => x.File.ReadAllText(MockFilePath)).Throws(); + [TestMethod] + public void Load_CriticalException_Exception() + { + MockFileExists(MockFilePath); + fileSystem.File.ReadAllText(MockFilePath).Throws(); - var actual = testSubject.Load(MockFilePath); - actual.Should().Be(null); - } + Action act = () => testSubject.Load(MockFilePath); - [TestMethod] - public void Load_CriticalException_Exception() - { - fileSystem.Setup(x => x.File.Exists(MockFilePath)).Returns(true); - fileSystem.Setup(x => x.File.ReadAllText(MockFilePath)).Throws(); + act.Should().ThrowExactly(); + } - Action act = () => testSubject.Load(MockFilePath); + [TestMethod] + public void Load_FileExists_DeserializedProject() + { + MockFileExists(MockFilePath); + fileSystem.File.ReadAllText(MockFilePath).Returns(serializedProject); - act.Should().ThrowExactly(); - } + var actual = testSubject.Load(MockFilePath); + actual.Should().BeEquivalentTo(bindingJsonModel); + } - [TestMethod] - public void Load_FileExists_DeserializedProject() - { - fileSystem.Setup(x => x.File.Exists(MockFilePath)).Returns(true); - fileSystem.Setup(x => x.File.ReadAllText(MockFilePath)).Returns(serializedProject); + [TestMethod] + public void Load_FileExists_ProjectWithNonUtcTimestamp_DeserializedProjectWithCorrectTimestampData() + { + const string utcDate = "2020-02-25T08:57:54Z"; + const string localDate = "2020-02-25T10:57:54+02:00"; + serializedProject = serializedProject.Replace(utcDate, localDate); - var actual = testSubject.Load(MockFilePath); - actual.Should().BeEquivalentTo(bindingJsonModel); - } + MockFileExists(MockFilePath); + fileSystem.File.ReadAllText(MockFilePath).Returns(serializedProject); - [TestMethod] - public void Load_FileExists_ProjectWithNonUtcTimestamp_DeserializedProjectWithCorrectTimestampData() - { - const string utcDate = "2020-02-25T08:57:54Z"; - const string localDate = "2020-02-25T10:57:54+02:00"; - serializedProject = serializedProject.Replace(utcDate, localDate); + var actual = testSubject.Load(MockFilePath); + actual.Should().BeEquivalentTo(bindingJsonModel); + + var deserializedTimestamp = actual.Profiles[Language.CSharp].ProfileTimestamp.Value.ToUniversalTime(); + deserializedTimestamp.Should().Be(new DateTime(2020, 2, 25, 8, 57, 54)); + } + + [TestMethod] + public void DeleteBindingDirectory_ConfigFilePathNotExists_ReturnsFalseAndLogs() + { + MockFileNotExists(MockFilePath); + + var result = testSubject.DeleteBindingDirectory(MockFilePath); + + result.Should().BeFalse(); + fileSystem.Directory.DidNotReceive().Delete(MockDirectory, true); + logger.Received(1).LogVerbose(PersistenceStrings.BindingDirectoryNotDeleted, MockFilePath); + } - fileSystem.Setup(x => x.File.Exists(MockFilePath)).Returns(true); - fileSystem.Setup(x => x.File.ReadAllText(MockFilePath)).Returns(serializedProject); + [TestMethod] + public void DeleteBindingDirectory_ConfigFilePathExists_DeletesBindingDirectoryRecursively() + { + MockFileExists(MockFilePath); + + var result = testSubject.DeleteBindingDirectory(MockFilePath); + + result.Should().BeTrue(); + fileSystem.Directory.Received(1).Delete(MockDirectory, true); + } - var actual = testSubject.Load(MockFilePath); - actual.Should().BeEquivalentTo(bindingJsonModel); + [TestMethod] + public void DeleteBindingDirectory_DeletingDirectoryThrows_ReturnsFalseAndLogs() + { + MockFileExists(MockFilePath); + fileSystem.Directory.When(x => x.Delete(MockDirectory, true)).Throw(); + + var result = testSubject.DeleteBindingDirectory(MockFilePath); - var deserializedTimestamp = actual.Profiles[Language.CSharp].ProfileTimestamp.Value.ToUniversalTime(); - deserializedTimestamp.Should().Be(new DateTime(2020, 2, 25, 8, 57, 54)); - } + result.Should().BeFalse(); + fileSystem.Directory.Received(1).Delete(MockDirectory, true); + logger.Received(1).WriteLine(Arg.Any()); } + + private void MockFileExists(string filePath) => fileSystem.File.Exists(filePath).Returns(true); + + private void MockFileNotExists(string filePath) => fileSystem.File.Exists(filePath).Returns(false); } diff --git a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs index 68a46629bd..72d5b7ce8f 100644 --- a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs @@ -30,20 +30,21 @@ namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence; [TestClass] public class SolutionBindingRepositoryTests { - private IUnintrusiveBindingPathProvider unintrusiveBindingPathProvider; + private const string MockFilePath = "test file path"; + private const string LocalBindingKey = "my solution"; + + private BindingJsonModel bindingJsonModel; private IBindingJsonModelConverter bindingJsonModelConverter; - private IServerConnectionsRepository serverConnectionsRepository; + private BoundServerProject boundServerProject; private ISolutionBindingCredentialsLoader credentialsLoader; - private ISolutionBindingFileLoader solutionBindingFileLoader; private TestLogger logger; - private BindingJsonModel bindingJsonModel; + private BasicAuthCredentials mockCredentials; private ServerConnection serverConnection; - private BoundServerProject boundServerProject; + private IServerConnectionsRepository serverConnectionsRepository; + private ISolutionBindingFileLoader solutionBindingFileLoader; private ISolutionBindingRepository testSubject; - - private BasicAuthCredentials mockCredentials; - private const string MockFilePath = "test file path"; + private IUnintrusiveBindingPathProvider unintrusiveBindingPathProvider; [TestInitialize] public void TestInitialize() @@ -61,10 +62,7 @@ public void TestInitialize() serverConnection = new ServerConnection.SonarCloud("org"); boundServerProject = new BoundServerProject("solution.123", "project_123", serverConnection); - bindingJsonModel = new BindingJsonModel - { - ServerConnectionId = serverConnection.Id - }; + bindingJsonModel = new BindingJsonModel { ServerConnectionId = serverConnection.Id }; } [TestMethod] @@ -85,10 +83,7 @@ public void MefCtor_CheckIsExported() } [TestMethod] - public void MefCtor_CheckIsSingleton() - { - MefTestHelpers.CheckIsSingletonMefComponent(); - } + public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); [TestMethod] public void Read_ProjectIsNull_Null() @@ -124,10 +119,10 @@ public void Read_ProjectIsNotNull_ReadsConnectionRepositoryForConnection() var actual = testSubject.Read(MockFilePath); actual.Should().BeSameAs(boundServerProject); - + credentialsLoader.DidNotReceiveWithAnyArgs().Load(default); } - + [TestMethod] public void Read_ProjectIsNotNull_NoConnection_ReturnsNull() { @@ -139,7 +134,7 @@ public void Read_ProjectIsNotNull_NoConnection_ReturnsNull() solutionBindingFileLoader.Load(MockFilePath).Returns(bindingJsonModel); var actual = testSubject.Read(MockFilePath); - + credentialsLoader.DidNotReceiveWithAnyArgs().Load(default); actual.Should().BeNull(); } @@ -152,31 +147,28 @@ public void Write_ConfigFilePathIsNull_ReturnsFalse(string filePath) var actual = testSubject.Write(filePath, boundServerProject); actual.Should().Be(false); } - + [DataTestMethod] [DataRow(null)] [DataRow("")] public void Write_ConfigFilePathIsNull_FileNotWritten(string filePath) { testSubject.Write(filePath, boundServerProject); - + solutionBindingFileLoader.DidNotReceiveWithAnyArgs().Save(default, default); } - + [TestMethod] - public void Write_ProjectIsNull_Exception() - { - Assert.ThrowsException(() => testSubject.Write(MockFilePath, null)); - } - + public void Write_ProjectIsNull_Exception() => Assert.ThrowsException(() => testSubject.Write(MockFilePath, null)); + [TestMethod] public void Write_ProjectIsNull_FileNotWritten() { Assert.ThrowsException(() => testSubject.Write(MockFilePath, null)); - + solutionBindingFileLoader.DidNotReceiveWithAnyArgs().Save(default, default); } - + [DataTestMethod] [DataRow(true)] [DataRow(false)] @@ -186,23 +178,22 @@ public void Write_EventTriggered_DependingOnFileWriteStatus(bool triggered) testSubject.BindingUpdated += eventHandler; bindingJsonModelConverter.ConvertToModel(boundServerProject).Returns(bindingJsonModel); solutionBindingFileLoader.Save(MockFilePath, bindingJsonModel).Returns(triggered); - + testSubject.Write(MockFilePath, boundServerProject); - + eventHandler.ReceivedWithAnyArgs(triggered ? 1 : 0).Invoke(default, default); } - - + [TestMethod] public void Write_FileWritten_NoOnSaveCallback_NoException() { bindingJsonModelConverter.ConvertToModel(boundServerProject).Returns(bindingJsonModel); solutionBindingFileLoader.Save(MockFilePath, bindingJsonModel).Returns(true); - + Action act = () => testSubject.Write(MockFilePath, boundServerProject); act.Should().NotThrow(); } - + [TestMethod] public void List_FilesExist_Returns() { @@ -216,9 +207,9 @@ public void List_FilesExist_Returns() SetUpConnections(connection1, connection2); var boundServerProject1 = SetUpBinding(solution1, connection1, bindingConfig1); var boundServerProject2 = SetUpBinding(solution2, connection2, bindingConfig2); - + var result = testSubject.List(); - + result.Should().BeEquivalentTo(boundServerProject1, boundServerProject2); } @@ -235,12 +226,12 @@ public void List_SkipsBindingsWithoutConnections() SetUpConnections(connection2); // only one connection _ = SetUpBinding(solution1, null, bindingConfig1); var boundServerProject2 = SetUpBinding(solution2, connection2, bindingConfig2); - + var result = testSubject.List(); result.Should().BeEquivalentTo(boundServerProject2); } - + [TestMethod] public void List_SkipsBindingsThatCannotBeRead() { @@ -253,17 +244,17 @@ public void List_SkipsBindingsThatCannotBeRead() SetUpConnections(connection1, connection2); var boundServerProject1 = SetUpBinding(solution1, connection1, bindingConfig1); solutionBindingFileLoader.Load(bindingConfig2).Returns((BindingJsonModel)null); - + var result = testSubject.List(); result.Should().BeEquivalentTo(boundServerProject1); } - + [TestMethod] public void List_CannotGetConnections_EmptyList() { serverConnectionsRepository.TryGetAll(out Arg.Any>()).Returns(false); - + var act = () => testSubject.List().ToList(); act.Should().Throw(); @@ -273,11 +264,11 @@ public void List_CannotGetConnections_EmptyList() public void LegacyRead_NoFile_ReturnsNull() { solutionBindingFileLoader.Load(MockFilePath).Returns((BindingJsonModel)null); - + ((ILegacySolutionBindingRepository)testSubject).Read(MockFilePath).Should().BeNull(); credentialsLoader.DidNotReceiveWithAnyArgs().Load(default); } - + [TestMethod] public void LegacyRead_ValidBinding_LoadsCredentials() { @@ -286,14 +277,37 @@ public void LegacyRead_ValidBinding_LoadsCredentials() credentialsLoader.Load(bindingJsonModel.ServerUri).Returns(mockCredentials); solutionBindingFileLoader.Load(MockFilePath).Returns(bindingJsonModel); bindingJsonModelConverter.ConvertFromModelToLegacy(bindingJsonModel, mockCredentials).Returns(boundSonarQubeProject); - + ((ILegacySolutionBindingRepository)testSubject).Read(MockFilePath).Should().BeSameAs(boundSonarQubeProject); credentialsLoader.Received().Load(bindingJsonModel.ServerUri); } + [TestMethod] + public void DeleteBinding_DeletesBindingDirectoryOfBindingFile() + { + unintrusiveBindingPathProvider.GetBindingPath(LocalBindingKey).Returns(MockFilePath); + + testSubject.DeleteBinding(LocalBindingKey); + + solutionBindingFileLoader.Received(1).DeleteBindingDirectory(MockFilePath); + } + + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void DeleteBinding_ReturnsResultOfDeleteBindingDirectory(bool expectedResult) + { + unintrusiveBindingPathProvider.GetBindingPath(LocalBindingKey).Returns(MockFilePath); + solutionBindingFileLoader.DeleteBindingDirectory(MockFilePath).Returns(expectedResult); + + var result = testSubject.DeleteBinding(LocalBindingKey); + + result.Should().Be(expectedResult); + } + private BoundServerProject SetUpBinding(string solution, ServerConnection connection, string bindingConfig) { - var dto = new BindingJsonModel{ServerConnectionId = connection?.Id}; + var dto = new BindingJsonModel { ServerConnectionId = connection?.Id }; solutionBindingFileLoader.Load(bindingConfig).Returns(dto); if (connection == null) { @@ -304,9 +318,8 @@ private BoundServerProject SetUpBinding(string solution, ServerConnection connec bindingJsonModelConverter.ConvertFromModel(dto, connection, solution).Returns(bound); return bound; } - - private void SetUpConnections(params ServerConnection[] connections) - { + + private void SetUpConnections(params ServerConnection[] connections) => serverConnectionsRepository .TryGetAll(out Arg.Any>()) .Returns(call => @@ -314,10 +327,6 @@ private void SetUpConnections(params ServerConnection[] connections) call[0] = connections; return true; }); - } - private void SetUpUnintrusiveBindingPathProvider(params string[] bindigFolders) - { - unintrusiveBindingPathProvider.GetBindingPaths().Returns(bindigFolders); - } + private void SetUpUnintrusiveBindingPathProvider(params string[] bindigFolders) => unintrusiveBindingPathProvider.GetBindingPaths().Returns(bindigFolders); } diff --git a/src/ConnectedMode/Persistence/ISolutionBindingFileLoader.cs b/src/ConnectedMode/Persistence/ISolutionBindingFileLoader.cs index 69e70a2ea9..d1c49cd8ea 100644 --- a/src/ConnectedMode/Persistence/ISolutionBindingFileLoader.cs +++ b/src/ConnectedMode/Persistence/ISolutionBindingFileLoader.cs @@ -18,11 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -namespace SonarLint.VisualStudio.ConnectedMode.Persistence +namespace SonarLint.VisualStudio.ConnectedMode.Persistence; + +internal interface ISolutionBindingFileLoader { - internal interface ISolutionBindingFileLoader - { - BindingJsonModel Load(string filePath); - bool Save(string filePath, BindingJsonModel project); - } + BindingJsonModel Load(string filePath); + + bool Save(string filePath, BindingJsonModel project); + + bool DeleteBindingDirectory(string configFilePath); } diff --git a/src/ConnectedMode/Persistence/PersistenceStrings.Designer.cs b/src/ConnectedMode/Persistence/PersistenceStrings.Designer.cs index adf1f9ea50..b1151b7a42 100644 --- a/src/ConnectedMode/Persistence/PersistenceStrings.Designer.cs +++ b/src/ConnectedMode/Persistence/PersistenceStrings.Designer.cs @@ -60,6 +60,15 @@ internal PersistenceStrings() { } } + /// + /// Looks up a localized string similar to The Connected Mode configuration file '{0}' does not exist. Connected Mode configuration not deleted.. + /// + internal static string BindingDirectoryNotDeleted { + get { + return ResourceManager.GetString("BindingDirectoryNotDeleted", resourceCulture); + } + } + /// /// Looks up a localized string similar to Failed loading file: '{0}'. Please rebind solution to create a new configuration file.. /// diff --git a/src/ConnectedMode/Persistence/PersistenceStrings.resx b/src/ConnectedMode/Persistence/PersistenceStrings.resx index 682885485d..871b023244 100644 --- a/src/ConnectedMode/Persistence/PersistenceStrings.resx +++ b/src/ConnectedMode/Persistence/PersistenceStrings.resx @@ -121,4 +121,7 @@ Failed loading file: '{0}'. Please rebind solution to create a new configuration file. Output window message in case of deserialization error. {0} file path. + + The Connected Mode configuration file '{0}' does not exist. Connected Mode configuration not deleted. + \ No newline at end of file diff --git a/src/ConnectedMode/Persistence/SolutionBindingFileLoader.cs b/src/ConnectedMode/Persistence/SolutionBindingFileLoader.cs index 98953b5a0e..6bf1b6ee00 100644 --- a/src/ConnectedMode/Persistence/SolutionBindingFileLoader.cs +++ b/src/ConnectedMode/Persistence/SolutionBindingFileLoader.cs @@ -18,114 +18,114 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Diagnostics; using System.IO; using System.IO.Abstractions; using Newtonsoft.Json; using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Binding; +using ErrorHandler = Microsoft.VisualStudio.ErrorHandler; -namespace SonarLint.VisualStudio.ConnectedMode.Persistence +namespace SonarLint.VisualStudio.ConnectedMode.Persistence; + +internal class SolutionBindingFileLoader : ISolutionBindingFileLoader { - internal class SolutionBindingFileLoader : ISolutionBindingFileLoader - { - private readonly ILogger logger; - private readonly IFileSystem fileSystem; + private readonly IFileSystem fileSystem; + private readonly ILogger logger; - public SolutionBindingFileLoader(ILogger logger) - : this(logger, new FileSystem()) - { - } + public SolutionBindingFileLoader(ILogger logger) + : this(logger, new FileSystem()) + { + } - internal SolutionBindingFileLoader(ILogger logger, IFileSystem fileSystem) - { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); - this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); - } + internal SolutionBindingFileLoader(ILogger logger, IFileSystem fileSystem) + { + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + } - public bool Save(string filePath, BindingJsonModel project) - { - var serializedProject = Serialize(project); + public bool Save(string filePath, BindingJsonModel project) + { + var serializedProject = Serialize(project); - return SafePerformFileSystemOperation(() => WriteConfig(filePath, serializedProject)); - } + return SafePerformFileSystemOperation(() => WriteConfig(filePath, serializedProject)); + } - private void WriteConfig(string configFile, string serializedProject) + public bool DeleteBindingDirectory(string configFilePath) + { + if (fileSystem.File.Exists(configFilePath)) { - Debug.Assert(!string.IsNullOrWhiteSpace(configFile)); - - var directoryName = Path.GetDirectoryName(configFile); - - if (!fileSystem.Directory.Exists(directoryName)) + return SafePerformFileSystemOperation(() => { - fileSystem.Directory.CreateDirectory(directoryName); - } - - fileSystem.File.WriteAllText(configFile, serializedProject); + var directoryName = Path.GetDirectoryName(configFilePath); + fileSystem.Directory.Delete(directoryName, true); + }); } - public BindingJsonModel Load(string filePath) - { - if (string.IsNullOrEmpty(filePath) || !fileSystem.File.Exists(filePath)) - { - return null; - } - - string configJson = null; - - if (SafePerformFileSystemOperation(() => ReadConfig(filePath, out configJson))) - { - try - { - return Deserialize(configJson); - } - catch (JsonException) - { - logger.WriteLine(PersistenceStrings.FailedToDeserializeSQCOnfiguration, filePath); - } - } + logger.LogVerbose(PersistenceStrings.BindingDirectoryNotDeleted, configFilePath); + return false; + } + public BindingJsonModel Load(string filePath) + { + if (string.IsNullOrEmpty(filePath) || !fileSystem.File.Exists(filePath)) + { return null; } - private void ReadConfig(string configFile, out string text) - { - text = fileSystem.File.ReadAllText(configFile); - } + string configJson = null; - private bool SafePerformFileSystemOperation(Action operation) + if (SafePerformFileSystemOperation(() => ReadConfig(filePath, out configJson))) { - Debug.Assert(operation != null); - try { - operation(); - return true; + return Deserialize(configJson); } - catch (Exception e) when (!Microsoft.VisualStudio.ErrorHandler.IsCriticalException(e)) + catch (JsonException) { - logger.WriteLine(e.Message); - return false; + logger.WriteLine(PersistenceStrings.FailedToDeserializeSQCOnfiguration, filePath); } } - private BindingJsonModel Deserialize(string projectJson) + return null; + } + + private void WriteConfig(string configFile, string serializedProject) + { + Debug.Assert(!string.IsNullOrWhiteSpace(configFile)); + + var directoryName = Path.GetDirectoryName(configFile); + + if (!fileSystem.Directory.Exists(directoryName)) { - return JsonConvert.DeserializeObject(projectJson, new JsonSerializerSettings - { - DateFormatHandling = DateFormatHandling.IsoDateFormat, - DateTimeZoneHandling = DateTimeZoneHandling.Local, - DateParseHandling = DateParseHandling.DateTimeOffset - }); + fileSystem.Directory.CreateDirectory(directoryName); } - private string Serialize(BindingJsonModel project) + fileSystem.File.WriteAllText(configFile, serializedProject); + } + + private void ReadConfig(string configFile, out string text) => text = fileSystem.File.ReadAllText(configFile); + + private bool SafePerformFileSystemOperation(Action operation) + { + Debug.Assert(operation != null); + + try { - return JsonConvert.SerializeObject(project, Formatting.Indented, new JsonSerializerSettings - { - DateTimeZoneHandling = DateTimeZoneHandling.Utc - }); + operation(); + return true; + } + catch (Exception e) when (!ErrorHandler.IsCriticalException(e)) + { + logger.WriteLine(e.Message); + return false; } } + + private BindingJsonModel Deserialize(string projectJson) => + JsonConvert.DeserializeObject(projectJson, + new JsonSerializerSettings + { + DateFormatHandling = DateFormatHandling.IsoDateFormat, DateTimeZoneHandling = DateTimeZoneHandling.Local, DateParseHandling = DateParseHandling.DateTimeOffset + }); + + private string Serialize(BindingJsonModel project) => JsonConvert.SerializeObject(project, Formatting.Indented, new JsonSerializerSettings { DateTimeZoneHandling = DateTimeZoneHandling.Utc }); } diff --git a/src/ConnectedMode/Persistence/SolutionBindingRepository.cs b/src/ConnectedMode/Persistence/SolutionBindingRepository.cs index 79b22f5c18..e67db54d4c 100644 --- a/src/ConnectedMode/Persistence/SolutionBindingRepository.cs +++ b/src/ConnectedMode/Persistence/SolutionBindingRepository.cs @@ -100,7 +100,11 @@ public bool Write(string configFilePath, BoundServerProject binding) return true; } - public bool DeleteBinding(string configFilePath) => throw new NotImplementedException(); + public bool DeleteBinding(string localBindingKey) + { + var bindingPath = unintrusiveBindingPathProvider.GetBindingPath(localBindingKey); + return solutionBindingFileLoader.DeleteBindingDirectory(bindingPath); + } public event EventHandler BindingUpdated; diff --git a/src/Core/Binding/ISolutionBindingRepository.cs b/src/Core/Binding/ISolutionBindingRepository.cs index 99d63a4580..e0909422b7 100644 --- a/src/Core/Binding/ISolutionBindingRepository.cs +++ b/src/Core/Binding/ISolutionBindingRepository.cs @@ -46,8 +46,9 @@ public interface ISolutionBindingRepository /// /// Deletes the binding information /// + /// The local binding key of the /// If binding has been deleted - bool DeleteBinding(string configFilePath); + bool DeleteBinding(string localBindingKey); /// /// Raises when operation completes successfully