diff --git a/src/ConnectedMode.UnitTests/Binding/BindingInfoProviderTests.cs b/src/ConnectedMode.UnitTests/Binding/BindingInfoProviderTests.cs index 7401ddda6a..32cea840fd 100644 --- a/src/ConnectedMode.UnitTests/Binding/BindingInfoProviderTests.cs +++ b/src/ConnectedMode.UnitTests/Binding/BindingInfoProviderTests.cs @@ -32,6 +32,19 @@ namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Binding [TestClass] public class BindingInfoProviderTests { + [TestMethod] + public void MefCtor_CheckIsExported() + { + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport()); + } + + [TestMethod] + public void MefCtor_CheckIsSingleton() + { + MefTestHelpers.CheckIsSingletonMefComponent(); + } + [TestMethod] public void GetExistingBindings_NoBindings_ReturnsEmpty() { @@ -58,17 +71,16 @@ public void GetExistingBindings_MakeSureCallInBackground() [TestMethod] public void GetExistingBindings_HaveBindings_ReturnsBinding() { - var unintrusiveBindingPathProvider = CreateUnintrusiveBindingPathProvider("C:\\Bindings\\Binding1\\binding.config", "C:\\Bindings\\Binding2\\binding.config"); - - var solutionBindingFileLoader = new Mock(); + var solutionBindingRepository = new Mock(); var binding1 = CreateBoundSonarQubeProject("https://sonarqube.somedomain.com", null, "projectKey1"); var binding2 = CreateBoundSonarQubeProject("https://sonarcloud.io", "organisation", "projectKey2"); - solutionBindingFileLoader.Setup(sbf => sbf.Load("C:\\Bindings\\Binding1\\binding.config")).Returns(binding1); - solutionBindingFileLoader.Setup(sbf => sbf.Load("C:\\Bindings\\Binding2\\binding.config")).Returns(binding2); + var bindings = new[] { binding1, binding2 }; + + solutionBindingRepository.Setup(sbr => sbr.List()).Returns(bindings); - var testSubject = CreateTestSubject(unintrusiveBindingPathProvider: unintrusiveBindingPathProvider, solutionBindingFileLoader: solutionBindingFileLoader.Object); + var testSubject = CreateTestSubject(solutionBindingRepository: solutionBindingRepository.Object); var result = testSubject.GetExistingBindings().ToList(); @@ -79,61 +91,31 @@ public void GetExistingBindings_HaveBindings_ReturnsBinding() result[1].Organization.Should().Be("organisation"); } - [TestMethod] - public void GetExistingBindings_BindingConfigMissing_SkipFile() - { - var unintrusiveBindingPathProvider = CreateUnintrusiveBindingPathProvider("C:\\Bindings\\Binding1\\binding.config", "C:\\Bindings\\Binding2\\binding.config"); - - var solutionBindingFileLoader = new Mock(); - - var binding1 = CreateBoundSonarQubeProject("https://sonarqube.somedomain.com", null, "projectKey1"); - var binding2 = CreateBoundSonarQubeProject("https://sonarcloud.io", "organisation", "projectKey2"); - - solutionBindingFileLoader.Setup(sbf => sbf.Load("C:\\Bindings\\Binding1\\binding.config")).Returns(binding1); - solutionBindingFileLoader.Setup(sbf => sbf.Load("C:\\Bindings\\Binding2\\binding.config")).Returns((BoundSonarQubeProject)null); - - var testSubject = CreateTestSubject(unintrusiveBindingPathProvider: unintrusiveBindingPathProvider, solutionBindingFileLoader: solutionBindingFileLoader.Object); - - var result = testSubject.GetExistingBindings(); - - result.Should().HaveCount(1); - } - [TestMethod] public void GetExistingBindings_SameBindingMultipleTime_ReturnsDistinct() { - var unintrusiveBindingPathProvider = CreateUnintrusiveBindingPathProvider("C:\\Bindings\\Binding1\\binding.config", "C:\\Bindings\\Binding2\\binding.config"); - - var solutionBindingFileLoader = new Mock(); + var solutionBindingRepository = new Mock(); var binding1 = CreateBoundSonarQubeProject("https://sonarqube.somedomain.com", null, "projectKey1"); var binding2 = CreateBoundSonarQubeProject("https://sonarqube.somedomain.com", null, "projectKey2"); - solutionBindingFileLoader.Setup(sbf => sbf.Load("C:\\Bindings\\Binding1\\binding.config")).Returns(binding1); - solutionBindingFileLoader.Setup(sbf => sbf.Load("C:\\Bindings\\Binding2\\binding.config")).Returns(binding2); + var bindings = new[] { binding1, binding2 }; - var testSubject = CreateTestSubject(unintrusiveBindingPathProvider: unintrusiveBindingPathProvider, solutionBindingFileLoader: solutionBindingFileLoader.Object); + solutionBindingRepository.Setup(sbr => sbr.List()).Returns(bindings); + + var testSubject = CreateTestSubject(solutionBindingRepository: solutionBindingRepository.Object); var result = testSubject.GetExistingBindings(); result.Should().HaveCount(1); } - private static IUnintrusiveBindingPathProvider CreateUnintrusiveBindingPathProvider(params string[] bindigFolders) + private static BoundConnectionInfoProvider CreateTestSubject(ISolutionBindingRepository solutionBindingRepository = null, IThreadHandling threadHandling = null) { - var unintrusiveBindingPathProvider = new Mock(); - unintrusiveBindingPathProvider.Setup(u => u.GetBindingPaths()).Returns(bindigFolders); - return unintrusiveBindingPathProvider.Object; - } - - private static BoundConnectionInfoProvider CreateTestSubject(IUnintrusiveBindingPathProvider unintrusiveBindingPathProvider = null, ISolutionBindingFileLoader solutionBindingFileLoader = null, IThreadHandling threadHandling = null) - { - unintrusiveBindingPathProvider ??= CreateUnintrusiveBindingPathProvider(); - - solutionBindingFileLoader ??= Mock.Of(); + solutionBindingRepository ??= Mock.Of(); threadHandling ??= new NoOpThreadHandler(); - var testSubject = new BoundConnectionInfoProvider(unintrusiveBindingPathProvider, solutionBindingFileLoader, threadHandling); + var testSubject = new BoundConnectionInfoProvider(solutionBindingRepository, threadHandling); return testSubject; } diff --git a/src/ConnectedMode.UnitTests/Binding/UnintrusiveConfigurationProviderTests.cs b/src/ConnectedMode.UnitTests/Binding/UnintrusiveConfigurationProviderTests.cs index 32a25a2b05..e0bc2de8c9 100644 --- a/src/ConnectedMode.UnitTests/Binding/UnintrusiveConfigurationProviderTests.cs +++ b/src/ConnectedMode.UnitTests/Binding/UnintrusiveConfigurationProviderTests.cs @@ -32,7 +32,7 @@ public void MefCtor_CheckIsExported() { MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); + MefTestHelpers.CreateExport()); } [TestMethod] @@ -40,8 +40,8 @@ public void GetConfig_NoConfig_ReturnsStandalone() { // Arrange var pathProvider = CreatePathProvider(null); - var configReader = new Mock(); - var testSubject = CreateTestSubject(pathProvider, configReader.Object); + var configRepository = new Mock(); + var testSubject = CreateTestSubject(pathProvider, configRepository.Object); // Act var actual = testSubject.GetConfiguration(); @@ -50,7 +50,7 @@ public void GetConfig_NoConfig_ReturnsStandalone() actual.Should().NotBeNull(); actual.Project.Should().BeNull(); actual.Mode.Should().Be(SonarLintMode.Standalone); - configReader.Invocations.Should().BeEmpty(); + configRepository.Invocations.Should().BeEmpty(); } [TestMethod] @@ -60,7 +60,7 @@ public void GetConfig_Bound_ReturnsExpectedConfig() var expectedProject = new BoundSonarQubeProject(); var pathProvider = CreatePathProvider("c:\\users\\foo\\bindings\\xxx.config"); - var configReader = CreateReader(expectedProject); + var configReader = CreateRepo(expectedProject); var testSubject = CreateTestSubject(pathProvider, configReader.Object); // Act @@ -79,7 +79,7 @@ public void GetConfig_ConfigReaderReturnsNull_ReturnsStandalone() { // Arrange var pathProvider = CreatePathProvider("c:\\users\\foo\\bindings\\xxx.config"); - var configReader = CreateReader(null); + var configReader = CreateRepo(null); var testSubject = CreateTestSubject(pathProvider, configReader.Object); // Act @@ -91,10 +91,10 @@ public void GetConfig_ConfigReaderReturnsNull_ReturnsStandalone() } private static UnintrusiveConfigurationProvider CreateTestSubject(IUnintrusiveBindingPathProvider pathProvider, - ISolutionBindingDataReader configReader = null) + ISolutionBindingRepository configRepo = null) { - configReader ??= Mock.Of(); - return new UnintrusiveConfigurationProvider(pathProvider, configReader); + configRepo ??= Mock.Of(); + return new UnintrusiveConfigurationProvider(pathProvider, configRepo); } private static IUnintrusiveBindingPathProvider CreatePathProvider(string pathToReturn) @@ -104,14 +104,14 @@ private static IUnintrusiveBindingPathProvider CreatePathProvider(string pathToR return pathProvider.Object; } - private static Mock CreateReader(BoundSonarQubeProject projectToReturn) + private static Mock CreateRepo(BoundSonarQubeProject projectToReturn) { - var reader = new Mock(); - reader.Setup(x => x.Read(It.IsAny())).Returns(projectToReturn); - return reader; + var repo = new Mock(); + repo.Setup(x => x.Read(It.IsAny())).Returns(projectToReturn); + return repo; } - private static void CheckExpectedFileRead(Mock configReader, string expectedFilePath) + private static void CheckExpectedFileRead(Mock configReader, string expectedFilePath) => configReader.Verify(x => x.Read(expectedFilePath), Times.Once); } } diff --git a/src/ConnectedMode.UnitTests/Migration/ObsoleteConfigurationProviderTests.cs b/src/ConnectedMode.UnitTests/Migration/ObsoleteConfigurationProviderTests.cs index 806872048f..20319c405e 100644 --- a/src/ConnectedMode.UnitTests/Migration/ObsoleteConfigurationProviderTests.cs +++ b/src/ConnectedMode.UnitTests/Migration/ObsoleteConfigurationProviderTests.cs @@ -32,7 +32,7 @@ public class ObsoleteConfigurationProviderTests [TestMethod] public void MefCtor_CheckIsExported() => MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); [TestMethod] @@ -46,13 +46,13 @@ public void Ctor_DoesNotCallServices() // -> should not call services that swtich threads var legacyProvider = new Mock(); var connectedProvider = new Mock(); - var slnDataReader = new Mock(); + var slnDataRepository = new Mock(); - _ = CreateTestSubject(legacyProvider.Object, connectedProvider.Object, slnDataReader.Object); + _ = CreateTestSubject(legacyProvider.Object, connectedProvider.Object, slnDataRepository.Object); legacyProvider.Invocations.Should().BeEmpty(); connectedProvider.Invocations.Should().BeEmpty(); - slnDataReader.Invocations.Should().BeEmpty(); + slnDataRepository.Invocations.Should().BeEmpty(); } [TestMethod] @@ -79,9 +79,9 @@ public void GetConfig_NewConfigOnly_ReturnsConnected() // Arrange var legacyPathProvider = CreatePathProvider(null); var newPathProvider = CreatePathProvider("c:\\new"); - + var expectedProject = new BoundSonarQubeProject(); - var reader = CreateReader("c:\\new", expectedProject); + var reader = CreateRpo("c:\\new", expectedProject); var testSubject = CreateTestSubject(legacyPathProvider, newPathProvider, reader); @@ -102,7 +102,7 @@ public void GetConfig_LegacyConfigOnly_ReturnsLegacy() var legacyPathProvider = CreatePathProvider("c:\\old"); var expectedProject = new BoundSonarQubeProject(); - var reader = CreateReader("c:\\old", expectedProject); + var reader = CreateRpo("c:\\old", expectedProject); var testSubject = CreateTestSubject(legacyPathProvider, null, reader); @@ -123,11 +123,11 @@ public void GetConfig_NoLegacyProjectAtFileLocation_NoConnectedProjectAtFileLoca var legacyPathProvider = CreatePathProvider("c:\\legacy"); var newPathProvider = CreatePathProvider("c:\\new"); - var reader = new Mock(); - reader.Setup(x => x.Read("c:\\legacy")).Returns(null as BoundSonarQubeProject); - reader.Setup(x => x.Read("c:\\new")).Returns(null as BoundSonarQubeProject); + var repo = new Mock(); + repo.Setup(x => x.Read("c:\\legacy")).Returns(null as BoundSonarQubeProject); + repo.Setup(x => x.Read("c:\\new")).Returns(null as BoundSonarQubeProject); - var testSubject = CreateTestSubject(legacyPathProvider, newPathProvider, reader.Object); + var testSubject = CreateTestSubject(legacyPathProvider, newPathProvider, repo.Object); // Act var actual = testSubject.GetConfiguration(); @@ -145,12 +145,12 @@ public void GetConfig_NoLegacyProjectAtFileLocation_ConnectedProjectAtFileLocati var legacyPathProvider = CreatePathProvider("c:\\legacy"); var newPathProvider = CreatePathProvider("c:\\new"); - var reader = new Mock(); + var repo = new Mock(); var expectedProject = new BoundSonarQubeProject(); - reader.Setup(x => x.Read("c:\\legacy")).Returns(null as BoundSonarQubeProject); - reader.Setup(x => x.Read("c:\\new")).Returns(expectedProject); + repo.Setup(x => x.Read("c:\\legacy")).Returns(null as BoundSonarQubeProject); + repo.Setup(x => x.Read("c:\\new")).Returns(expectedProject); - var testSubject = CreateTestSubject(legacyPathProvider, newPathProvider, reader.Object); + var testSubject = CreateTestSubject(legacyPathProvider, newPathProvider, repo.Object); // Act var actual = testSubject.GetConfiguration(); @@ -171,7 +171,7 @@ public void GetConfig_LegacyProjectAtFileLocation_ConnectedProjectAtFileLocation var legacyPathProvider = CreatePathProvider("c:\\legacy"); var newPathProvider = CreatePathProvider("c:\\new"); - var reader = new Mock(); + var reader = new Mock(); var legacyProject = new BoundSonarQubeProject(); var newProject = new BoundSonarQubeProject(); reader.Setup(x => x.Read("c:\\legacy")).Returns(legacyProject); @@ -195,21 +195,21 @@ private static ISolutionBindingPathProvider CreatePathProvider(string pathToRetu return provider.Object; } - private static ISolutionBindingDataReader CreateReader(string inputPath, BoundSonarQubeProject projectToReturn) + private static ISolutionBindingRepository CreateRpo(string inputPath, BoundSonarQubeProject projectToReturn) { - var reader = new Mock(); - reader.Setup(x => x.Read(inputPath)).Returns(projectToReturn); - return reader.Object; + var repo = new Mock(); + repo.Setup(x => x.Read(inputPath)).Returns(projectToReturn); + return repo.Object; } private static ObsoleteConfigurationProvider CreateTestSubject(ISolutionBindingPathProvider legacyProvider = null, ISolutionBindingPathProvider connectedModePathProvider = null, - ISolutionBindingDataReader slnDataReader = null) + ISolutionBindingRepository slnDataRepo = null) { var testSubject = new ObsoleteConfigurationProvider( legacyProvider ?? Mock.Of(), connectedModePathProvider ?? Mock.Of(), - slnDataReader ?? Mock.Of()); + slnDataRepo ?? Mock.Of()); return testSubject; } } diff --git a/src/ConnectedMode.UnitTests/Persistence/ConfigurationPersisterTests.cs b/src/ConnectedMode.UnitTests/Persistence/ConfigurationPersisterTests.cs index f9f293f8cd..a28ef50fa6 100644 --- a/src/ConnectedMode.UnitTests/Persistence/ConfigurationPersisterTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/ConfigurationPersisterTests.cs @@ -30,18 +30,18 @@ namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence public class ConfigurationPersisterTests { private Mock configFilePathProvider; - private Mock solutionBindingDataWriter; + private Mock solutionBindingRepository; private ConfigurationPersister testSubject; [TestInitialize] public void TestInitialize() { configFilePathProvider = new Mock(); - solutionBindingDataWriter = new Mock(); + solutionBindingRepository = new Mock(); testSubject = new ConfigurationPersister( configFilePathProvider.Object, - solutionBindingDataWriter.Object); + solutionBindingRepository.Object); } [TestMethod] @@ -49,7 +49,7 @@ public void MefCtor_CheckIsExported() { MefTestHelpers.CheckTypeCanBeImported( MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); + MefTestHelpers.CreateExport()); } [TestMethod] @@ -68,7 +68,7 @@ public void Persist_SaveNewConfig() var projectToWrite = new BoundSonarQubeProject(); configFilePathProvider.Setup(x => x.GetCurrentBindingPath()).Returns("c:\\new.txt"); - solutionBindingDataWriter + solutionBindingRepository .Setup(x => x.Write("c:\\new.txt", projectToWrite)) .Returns(true); @@ -78,7 +78,7 @@ public void Persist_SaveNewConfig() // Assert actual.Should().NotBe(null); - solutionBindingDataWriter.Verify(x => + solutionBindingRepository.Verify(x => x.Write("c:\\new.txt", projectToWrite), Times.Once); } diff --git a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingDataReaderTests.cs b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingDataReaderTests.cs deleted file mode 100644 index 5ae0703dd4..0000000000 --- a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingDataReaderTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -using System; -using SonarLint.VisualStudio.ConnectedMode.Binding; -using SonarLint.VisualStudio.ConnectedMode.Persistence; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Binding; -using SonarLint.VisualStudio.TestInfrastructure; -using SonarQube.Client.Helpers; - -namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence -{ - [TestClass] - public class SolutionBindingDataReaderTests - { - private Mock credentialsLoader; - private Mock solutionBindingFileLoader; - private BoundSonarQubeProject boundSonarQubeProject; - private SolutionBindingDataReader testSubject; - - private BasicAuthCredentials mockCredentials; - private const string MockFilePath = "test file path"; - - [TestInitialize] - public void TestInitialize() - { - credentialsLoader = new Mock(); - solutionBindingFileLoader = new Mock(); - - testSubject = new SolutionBindingDataReader(solutionBindingFileLoader.Object, credentialsLoader.Object); - - mockCredentials = new BasicAuthCredentials("user", "pwd".ToSecureString()); - - boundSonarQubeProject = new BoundSonarQubeProject( - new Uri("http://xxx.www.zzz/yyy:9000"), - "MyProject Key", - "projectName", - mockCredentials); - } - - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public void Read_ProjectIsNull_Null() - { - solutionBindingFileLoader.Setup(x => x.Load(MockFilePath)).Returns(null as BoundSonarQubeProject); - - var actual = testSubject.Read(MockFilePath); - actual.Should().Be(null); - } - - [TestMethod] - public void Read_ProjectIsNull_CredentialsNotRead() - { - solutionBindingFileLoader.Setup(x => x.Load(MockFilePath)).Returns(null as BoundSonarQubeProject); - - testSubject.Read(MockFilePath); - - credentialsLoader.Verify(x => x.Load(It.IsAny()), Times.Never); - } - - [TestMethod] - public void Read_ProjectIsNotNull_ReturnsProjectWithCredentials() - { - boundSonarQubeProject.ServerUri = new Uri("http://sonarsource.com"); - boundSonarQubeProject.Credentials = null; - - solutionBindingFileLoader.Setup(x => x.Load(MockFilePath)).Returns(boundSonarQubeProject); - credentialsLoader.Setup(x => x.Load(boundSonarQubeProject.ServerUri)).Returns(mockCredentials); - - var actual = testSubject.Read(MockFilePath); - actual.Credentials.Should().Be(mockCredentials); - } - } -} diff --git a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingDataWriterTests.cs b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs similarity index 52% rename from src/ConnectedMode.UnitTests/Persistence/SolutionBindingDataWriterTests.cs rename to src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs index 83823e02d2..feae0d9f07 100644 --- a/src/ConnectedMode.UnitTests/Persistence/SolutionBindingDataWriterTests.cs +++ b/src/ConnectedMode.UnitTests/Persistence/SolutionBindingRepositoryTests.cs @@ -19,22 +19,26 @@ */ using System; +using System.Linq; using SonarLint.VisualStudio.ConnectedMode.Binding; using SonarLint.VisualStudio.ConnectedMode.Persistence; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; using SonarLint.VisualStudio.TestInfrastructure; using SonarQube.Client.Helpers; +using SonarQube.Client.Models; namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence { [TestClass] - public class SolutionBindingDataWriterTests + public class SolutionBindingRepositoryTests { + private Mock unintrusiveBindingPathProvider; private Mock credentialsLoader; private Mock solutionBindingFileLoader; + private BoundSonarQubeProject boundSonarQubeProject; - private SolutionBindingDataWriter testSubject; + private ISolutionBindingRepository testSubject; private BasicAuthCredentials mockCredentials; private const string MockFilePath = "test file path"; @@ -42,11 +46,12 @@ public class SolutionBindingDataWriterTests [TestInitialize] public void TestInitialize() { + unintrusiveBindingPathProvider = CreateUnintrusiveBindingPathProvider("C:\\Bindings\\Binding1\\binding.config", "C:\\Bindings\\Binding2\\binding.config"); + credentialsLoader = new Mock(); solutionBindingFileLoader = new Mock(); - testSubject = new SolutionBindingDataWriter(solutionBindingFileLoader.Object, - credentialsLoader.Object); + testSubject = new SolutionBindingRepository(unintrusiveBindingPathProvider.Object, solutionBindingFileLoader.Object, credentialsLoader.Object); mockCredentials = new BasicAuthCredentials("user", "pwd".ToSecureString()); @@ -60,11 +65,50 @@ public void TestInitialize() [TestMethod] public void MefCtor_CheckIsExported() { - MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); } + [TestMethod] + public void MefCtor_CheckIsSingleton() + { + MefTestHelpers.CheckIsSingletonMefComponent(); + } + + [TestMethod] + public void Read_ProjectIsNull_Null() + { + solutionBindingFileLoader.Setup(x => x.Load(MockFilePath)).Returns(null as BoundSonarQubeProject); + + var actual = testSubject.Read(MockFilePath); + actual.Should().Be(null); + } + + [TestMethod] + public void Read_ProjectIsNull_CredentialsNotRead() + { + solutionBindingFileLoader.Setup(x => x.Load(MockFilePath)).Returns(null as BoundSonarQubeProject); + + testSubject.Read(MockFilePath); + + credentialsLoader.Verify(x => x.Load(It.IsAny()), Times.Never); + } + + [TestMethod] + public void Read_ProjectIsNotNull_ReturnsProjectWithCredentials() + { + boundSonarQubeProject.ServerUri = new Uri("http://sonarsource.com"); + boundSonarQubeProject.Credentials = null; + + solutionBindingFileLoader.Setup(x => x.Load(MockFilePath)).Returns(boundSonarQubeProject); + credentialsLoader.Setup(x => x.Load(boundSonarQubeProject.ServerUri)).Returns(mockCredentials); + + var actual = testSubject.Read(MockFilePath); + actual.Credentials.Should().Be(mockCredentials); + } + [DataTestMethod] [DataRow(null)] [DataRow("")] @@ -98,7 +142,7 @@ public void Write_ConfigFilePathIsNull_CredentialsNotWritten(string filePath) [TestMethod] public void Write_ProjectIsNull_Exception() { - Assert.ThrowsException(() => testSubject.Write(MockFilePath, null)); + Assert.ThrowsException(() => testSubject.Write(MockFilePath, null)); } [TestMethod] @@ -146,5 +190,60 @@ public void Write_FileWritten_NoOnSaveCallback_NoException() Action act = () => testSubject.Write(MockFilePath, boundSonarQubeProject); act.Should().NotThrow(); } + + [TestMethod] + public void List_FilesExist_Returns() + { + var binding1 = CreateBoundSonarQubeProject("https://sonarqube.somedomain.com", null, "projectKey1"); + var binding2 = CreateBoundSonarQubeProject("https://sonarcloud.io", "organisation", "projectKey2"); + + solutionBindingFileLoader.Setup(sbf => sbf.Load("C:\\Bindings\\Binding1\\binding.config")).Returns(binding1); + solutionBindingFileLoader.Setup(sbf => sbf.Load("C:\\Bindings\\Binding2\\binding.config")).Returns(binding2); + + var expected = new[] { binding1, binding2 }; + + var testSubject = new SolutionBindingRepository(unintrusiveBindingPathProvider.Object, solutionBindingFileLoader.Object, credentialsLoader.Object); + + var result = testSubject.List(); + + credentialsLoader.VerifyNoOtherCalls(); + + result.Should().HaveCount(2); + result.Should().BeEquivalentTo(expected); + } + + [TestMethod] + public void List_FilesMissing_Skips() + { + var binding = CreateBoundSonarQubeProject("https://sonarqube.somedomain.com", null, "projectKey1"); + + solutionBindingFileLoader.Setup(sbf => sbf.Load("C:\\Bindings\\Binding1\\binding.config")).Returns(binding); + solutionBindingFileLoader.Setup(sbf => sbf.Load("C:\\Bindings\\Binding2\\binding.config")).Returns((BoundSonarQubeProject)null); + + var testSubject = new SolutionBindingRepository(unintrusiveBindingPathProvider.Object, solutionBindingFileLoader.Object, credentialsLoader.Object); + + var result = testSubject.List(); + + result.Should().HaveCount(1); + result.ElementAt(0).Should().BeEquivalentTo(binding); + } + + private static Mock CreateUnintrusiveBindingPathProvider(params string[] bindigFolders) + { + var unintrusiveBindingPathProvider = new Mock(); + unintrusiveBindingPathProvider.Setup(u => u.GetBindingPaths()).Returns(bindigFolders); + return unintrusiveBindingPathProvider; + } + + private static BoundSonarQubeProject CreateBoundSonarQubeProject(string uri, string organizationKey, string projectKey) + { + var organization = CreateOrganization(organizationKey); + + var serverUri = new Uri(uri); + + return new BoundSonarQubeProject(serverUri, projectKey, null, organization: organization); + } + + private static SonarQubeOrganization CreateOrganization(string organizationKey) => organizationKey == null ? null : new SonarQubeOrganization(organizationKey, null); } } diff --git a/src/ConnectedMode/Binding/BoundConnectionInfoProvider.cs b/src/ConnectedMode/Binding/BoundConnectionInfoProvider.cs index 5e3a7bf488..487513e415 100644 --- a/src/ConnectedMode/Binding/BoundConnectionInfoProvider.cs +++ b/src/ConnectedMode/Binding/BoundConnectionInfoProvider.cs @@ -28,24 +28,22 @@ namespace SonarLint.VisualStudio.ConnectedMode.Binding { - [Export(typeof(IBindingInfoProvider))] + [Export(typeof(IBoundConnectionInfoProvider))] [PartCreationPolicy(CreationPolicy.Shared)] - internal class BoundConnectionInfoProvider : IBindingInfoProvider + internal class BoundConnectionInfoProvider : IBoundConnectionInfoProvider { - private readonly IUnintrusiveBindingPathProvider unintrusiveBindingPathProvider; - private readonly ISolutionBindingFileLoader solutionBindingFileLoader; + private readonly ISolutionBindingRepository solutionBindingRepository; private readonly IThreadHandling threadHandling; [ImportingConstructor] - public BoundConnectionInfoProvider(IUnintrusiveBindingPathProvider unintrusiveBindingPathProvider, ISolutionBindingFileLoader solutionBindingFileLoader) - : this(unintrusiveBindingPathProvider, solutionBindingFileLoader, ThreadHandling.Instance) + public BoundConnectionInfoProvider(ISolutionBindingRepository solutionBindingRepository) + : this(solutionBindingRepository, ThreadHandling.Instance) { } - internal BoundConnectionInfoProvider(IUnintrusiveBindingPathProvider unintrusiveBindingPathProvider, ISolutionBindingFileLoader solutionBindingFileLoader, IThreadHandling threadHandling) + internal BoundConnectionInfoProvider(ISolutionBindingRepository solutionBindingRepository, IThreadHandling threadHandling) { - this.unintrusiveBindingPathProvider = unintrusiveBindingPathProvider; - this.solutionBindingFileLoader = solutionBindingFileLoader; + this.solutionBindingRepository = solutionBindingRepository; this.threadHandling = threadHandling; } @@ -55,15 +53,11 @@ public IEnumerable GetExistingBindings() var result = new List(); - var bindings = unintrusiveBindingPathProvider.GetBindingPaths(); + var bindings = solutionBindingRepository.List(); foreach (var binding in bindings) { - var boundSonarQubeProject = solutionBindingFileLoader.Load(binding); - - if (boundSonarQubeProject == null) { continue; } - - result.Add(ConvertToBindingInfo(boundSonarQubeProject)); + result.Add(ConvertToBindingInfo(binding)); } return result.Distinct(new BoundConnectionInfoUriComparer()); diff --git a/src/ConnectedMode/Binding/IBindingInfoProvider.cs b/src/ConnectedMode/Binding/IBoundConnectionInfoProvider.cs similarity index 95% rename from src/ConnectedMode/Binding/IBindingInfoProvider.cs rename to src/ConnectedMode/Binding/IBoundConnectionInfoProvider.cs index 2f9187d475..2cddf7a4eb 100644 --- a/src/ConnectedMode/Binding/IBindingInfoProvider.cs +++ b/src/ConnectedMode/Binding/IBoundConnectionInfoProvider.cs @@ -22,7 +22,7 @@ namespace SonarLint.VisualStudio.ConnectedMode.Binding { - public interface IBindingInfoProvider + public interface IBoundConnectionInfoProvider { /// /// Lists all bindings that have been saved locally diff --git a/src/ConnectedMode/Binding/IConfigurationPersister.cs b/src/ConnectedMode/Binding/IConfigurationPersister.cs index f4165937d8..714638238f 100644 --- a/src/ConnectedMode/Binding/IConfigurationPersister.cs +++ b/src/ConnectedMode/Binding/IConfigurationPersister.cs @@ -36,16 +36,16 @@ public interface IConfigurationPersister internal class ConfigurationPersister : IConfigurationPersister { private readonly IUnintrusiveBindingPathProvider configFilePathProvider; - private readonly ISolutionBindingDataWriter solutionBindingDataWriter; + private readonly ISolutionBindingRepository solutionBindingRepository; [ImportingConstructor] public ConfigurationPersister( IUnintrusiveBindingPathProvider configFilePathProvider, - ISolutionBindingDataWriter solutionBindingDataWriter) + ISolutionBindingRepository solutionBindingRepository) { this.configFilePathProvider = configFilePathProvider; - this.solutionBindingDataWriter = solutionBindingDataWriter; + this.solutionBindingRepository = solutionBindingRepository; } public BindingConfiguration Persist(BoundSonarQubeProject project) @@ -58,7 +58,7 @@ public BindingConfiguration Persist(BoundSonarQubeProject project) var configFilePath = configFilePathProvider.GetCurrentBindingPath(); var success = configFilePath != null && - solutionBindingDataWriter.Write(configFilePath, project); + solutionBindingRepository.Write(configFilePath, project); // The binding directory is the folder containing the binding config file var bindingConfigDirectory = Path.GetDirectoryName(configFilePath); diff --git a/src/ConnectedMode/Binding/UnintrusiveConfigurationProvider.cs b/src/ConnectedMode/Binding/UnintrusiveConfigurationProvider.cs index d9b5b7cedd..59d971ea5d 100644 --- a/src/ConnectedMode/Binding/UnintrusiveConfigurationProvider.cs +++ b/src/ConnectedMode/Binding/UnintrusiveConfigurationProvider.cs @@ -30,15 +30,15 @@ namespace SonarLint.VisualStudio.ConnectedMode.Binding internal class UnintrusiveConfigurationProvider : IConfigurationProvider { private readonly IUnintrusiveBindingPathProvider pathProvider; - private readonly ISolutionBindingDataReader solutionBindingDataReader; + private readonly ISolutionBindingRepository solutionBindingRepository; [ImportingConstructor] public UnintrusiveConfigurationProvider( IUnintrusiveBindingPathProvider pathProvider, - ISolutionBindingDataReader solutionBindingDataReader) + ISolutionBindingRepository solutionBindingRepository) { this.pathProvider = pathProvider; - this.solutionBindingDataReader = solutionBindingDataReader; + this.solutionBindingRepository = solutionBindingRepository; } public BindingConfiguration GetConfiguration() @@ -55,7 +55,7 @@ private BindingConfiguration TryGetBindingConfiguration(string bindingPath) return null; } - var boundProject = solutionBindingDataReader.Read(bindingPath); + var boundProject = solutionBindingRepository.Read(bindingPath); if (boundProject == null) { @@ -67,5 +67,4 @@ private BindingConfiguration TryGetBindingConfiguration(string bindingPath) return BindingConfiguration.CreateBoundConfiguration(boundProject, SonarLintMode.Connected, bindingConfigDirectory); } } - } diff --git a/src/ConnectedMode/Migration/ObsoleteConfigurationProvider.cs b/src/ConnectedMode/Migration/ObsoleteConfigurationProvider.cs index f4d94d3c9f..cb69f34b13 100644 --- a/src/ConnectedMode/Migration/ObsoleteConfigurationProvider.cs +++ b/src/ConnectedMode/Migration/ObsoleteConfigurationProvider.cs @@ -33,26 +33,26 @@ internal class ObsoleteConfigurationProvider : IObsoleteConfigurationProvider { private readonly ISolutionBindingPathProvider legacyPathProvider; private readonly ISolutionBindingPathProvider connectedModePathProvider; - private readonly ISolutionBindingDataReader solutionBindingDataReader; + private readonly ISolutionBindingRepository solutionBindingRepository; [ImportingConstructor] public ObsoleteConfigurationProvider( ISolutionInfoProvider solutionInfoProvider, - ISolutionBindingDataReader solutionBindingDataReader) + ISolutionBindingRepository solutionBindingRepository) : this( new LegacySolutionBindingPathProvider(solutionInfoProvider), new ObsoleteConnectedModeSolutionBindingPathProvider(solutionInfoProvider), - solutionBindingDataReader) + solutionBindingRepository) { } internal /* for testing */ ObsoleteConfigurationProvider(ISolutionBindingPathProvider legacyPathProvider, ISolutionBindingPathProvider connectedModePathProvider, - ISolutionBindingDataReader solutionBindingDataReader) + ISolutionBindingRepository solutionBindingRepository) { this.legacyPathProvider = legacyPathProvider ?? throw new ArgumentNullException(nameof(legacyPathProvider)); this.connectedModePathProvider = connectedModePathProvider ?? throw new ArgumentNullException(nameof(connectedModePathProvider)); - this.solutionBindingDataReader = solutionBindingDataReader ?? throw new ArgumentNullException(nameof(solutionBindingDataReader)); + this.solutionBindingRepository = solutionBindingRepository ?? throw new ArgumentNullException(nameof(solutionBindingRepository)); } public BindingConfiguration GetConfiguration() @@ -71,7 +71,7 @@ private BindingConfiguration TryGetBindingConfiguration(string bindingPath, Sona return null; } - var boundProject = solutionBindingDataReader.Read(bindingPath); + var boundProject = solutionBindingRepository.Read(bindingPath); if (boundProject == null) { diff --git a/src/ConnectedMode/Persistence/ISolutionBindingDataReader.cs b/src/ConnectedMode/Persistence/ISolutionBindingDataReader.cs deleted file mode 100644 index 0354e12343..0000000000 --- a/src/ConnectedMode/Persistence/ISolutionBindingDataReader.cs +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -using System; -using System.ComponentModel.Composition; -using System.Diagnostics; -using SonarLint.VisualStudio.ConnectedMode.Binding; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Binding; - -namespace SonarLint.VisualStudio.ConnectedMode.Persistence -{ - internal interface ISolutionBindingDataReader - { - /// - /// Retrieves solution binding information - /// - /// Can be null if not bound - BoundSonarQubeProject Read(string configFilePath); - } - - [Export(typeof(ISolutionBindingDataReader))] - internal class SolutionBindingDataReader : ISolutionBindingDataReader - { - private readonly ISolutionBindingFileLoader solutionBindingFileLoader; - private readonly ISolutionBindingCredentialsLoader credentialsLoader; - - [ImportingConstructor] - public SolutionBindingDataReader(ICredentialStoreService credentialStoreService, ILogger logger) - : this(new SolutionBindingFileLoader(logger), new SolutionBindingCredentialsLoader(credentialStoreService)) - { - } - - internal /* for testing */ SolutionBindingDataReader(ISolutionBindingFileLoader solutionBindingFileLoader, ISolutionBindingCredentialsLoader credentialsLoader) - { - this.solutionBindingFileLoader = solutionBindingFileLoader ?? throw new ArgumentNullException(nameof(solutionBindingFileLoader)); - this.credentialsLoader = credentialsLoader ?? throw new ArgumentNullException(nameof(credentialsLoader)); - } - - public BoundSonarQubeProject Read(string configFilePath) - { - var bound = solutionBindingFileLoader.Load(configFilePath); - - if (bound == null) - { - return null; - } - - bound.Credentials = credentialsLoader.Load(bound.ServerUri); - - Debug.Assert(!bound.Profiles?.ContainsKey(Core.Language.Unknown) ?? true, - "Not expecting the deserialized binding config to contain the profile for an unknown language"); - - return bound; - } - } -} diff --git a/src/ConnectedMode/Persistence/ISolutionBindingDataWriter.cs b/src/ConnectedMode/Persistence/ISolutionBindingDataWriter.cs deleted file mode 100644 index 622c5e14bd..0000000000 --- a/src/ConnectedMode/Persistence/ISolutionBindingDataWriter.cs +++ /dev/null @@ -1,85 +0,0 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -using System; -using System.ComponentModel.Composition; -using SonarLint.VisualStudio.ConnectedMode.Binding; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Binding; - -namespace SonarLint.VisualStudio.ConnectedMode.Persistence -{ - internal interface ISolutionBindingDataWriter - { - /// - /// Writes the binding information - /// - /// Has file been saved - bool Write(string configFilePath, BoundSonarQubeProject binding); - } - - [Export(typeof(ISolutionBindingDataWriter))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal class SolutionBindingDataWriter : ISolutionBindingDataWriter - { - private readonly ISolutionBindingFileLoader solutionBindingFileLoader; - private readonly ISolutionBindingCredentialsLoader credentialsLoader; - - [ImportingConstructor] - public SolutionBindingDataWriter(ICredentialStoreService credentialStoreService, - ILogger logger) - : this(new SolutionBindingFileLoader(logger), new SolutionBindingCredentialsLoader(credentialStoreService)) - { - } - - internal /* for testing */ SolutionBindingDataWriter( - ISolutionBindingFileLoader solutionBindingFileLoader, - ISolutionBindingCredentialsLoader credentialsLoader) - { - this.solutionBindingFileLoader = solutionBindingFileLoader; - this.credentialsLoader = credentialsLoader; - } - - /// - /// Writes the binding configuration file to the source controlled file system - /// - public bool Write(string configFilePath, BoundSonarQubeProject binding) - { - if (binding == null) - { - throw new ArgumentNullException(nameof(binding)); - } - - if (string.IsNullOrEmpty(configFilePath)) - { - return false; - } - - if (!solutionBindingFileLoader.Save(configFilePath, binding)) - { - return false; - } - - credentialsLoader.Save(binding.Credentials, binding.ServerUri); - - return true; - } - } -} diff --git a/src/ConnectedMode/Persistence/ISolutionBindingRepository.cs b/src/ConnectedMode/Persistence/ISolutionBindingRepository.cs new file mode 100644 index 0000000000..d7d424e550 --- /dev/null +++ b/src/ConnectedMode/Persistence/ISolutionBindingRepository.cs @@ -0,0 +1,131 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics; +using SonarLint.VisualStudio.ConnectedMode.Binding; +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Binding; + +namespace SonarLint.VisualStudio.ConnectedMode.Persistence +{ + public interface ISolutionBindingRepository + { + /// + /// Retrieves solution binding information + /// + /// Can be null if not bound + BoundSonarQubeProject Read(string configFilePath); + + /// + /// Writes the binding information + /// + /// Has file been saved + bool Write(string configFilePath, BoundSonarQubeProject binding); + + /// + /// Lists all the binding information + /// + /// + IEnumerable List(); + } + + [Export(typeof(ISolutionBindingRepository))] + [PartCreationPolicy(CreationPolicy.Shared)] + internal class SolutionBindingRepository : ISolutionBindingRepository + { + private readonly ISolutionBindingFileLoader solutionBindingFileLoader; + private readonly ISolutionBindingCredentialsLoader credentialsLoader; + private readonly IUnintrusiveBindingPathProvider unintrusiveBindingPathProvider; + + [ImportingConstructor] + public SolutionBindingRepository(IUnintrusiveBindingPathProvider unintrusiveBindingPathProvider, ICredentialStoreService credentialStoreService, ILogger logger) + : this(unintrusiveBindingPathProvider, new SolutionBindingFileLoader(logger), new SolutionBindingCredentialsLoader(credentialStoreService)) + { + } + + internal /* for testing */ SolutionBindingRepository(IUnintrusiveBindingPathProvider unintrusiveBindingPathProvider, ISolutionBindingFileLoader solutionBindingFileLoader, ISolutionBindingCredentialsLoader credentialsLoader) + { + this.solutionBindingFileLoader = solutionBindingFileLoader ?? throw new ArgumentNullException(nameof(solutionBindingFileLoader)); + this.credentialsLoader = credentialsLoader ?? throw new ArgumentNullException(nameof(credentialsLoader)); + this.unintrusiveBindingPathProvider = unintrusiveBindingPathProvider; + } + + public BoundSonarQubeProject Read(string configFilePath) + { + return Read(configFilePath, true); + } + + public bool Write(string configFilePath, BoundSonarQubeProject binding) + { + _ = binding ?? throw new ArgumentNullException(nameof(binding)); + + if (string.IsNullOrEmpty(configFilePath)) + { + return false; + } + + if (!solutionBindingFileLoader.Save(configFilePath, binding)) + { + return false; + } + + credentialsLoader.Save(binding.Credentials, binding.ServerUri); + + return true; + } + + public IEnumerable List() + { + var bindingConfigPaths = unintrusiveBindingPathProvider.GetBindingPaths(); + + foreach (var bindingConfigPath in bindingConfigPaths) + { + var boundSonarQubeProject = Read(bindingConfigPath, false); + + if (boundSonarQubeProject == null) { continue; } + + yield return boundSonarQubeProject; + } + } + + private BoundSonarQubeProject Read(string configFilePath, bool loadCredentials) + { + var bound = solutionBindingFileLoader.Load(configFilePath); + + if (bound is null) + { + return null; + } + + if (loadCredentials) + { + bound.Credentials = credentialsLoader.Load(bound.ServerUri); + } + + Debug.Assert(!bound.Profiles?.ContainsKey(Core.Language.Unknown) ?? true, + "Not expecting the deserialized binding config to contain the profile for an unknown language"); + + return bound; + } + } +}