From 25a6d6cf4d63f54558b77b6ff643a10d3c9a49b2 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Tue, 3 Sep 2024 10:32:16 +0200 Subject: [PATCH] SLVS-1431 Add server connection json model and mapper (#5658) [SLVS-1431](https://sonarsource.atlassian.net/browse/SLVS-1431) [SLVS-1431]: https://sonarsource.atlassian.net/browse/SLVS-1431?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../ServerConnectionModelMapperTest.cs | 239 ++++++++++++++++++ .../Persistence/ServerConnectionJsonModel.cs | 46 ++++ .../ServerConnectionModelMapper.cs | 123 +++++++++ src/Core/Binding/ServerConnection.cs | 2 +- 4 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 src/ConnectedMode.UnitTests/Persistence/ServerConnectionModelMapperTest.cs create mode 100644 src/ConnectedMode/Persistence/ServerConnectionJsonModel.cs create mode 100644 src/ConnectedMode/Persistence/ServerConnectionModelMapper.cs diff --git a/src/ConnectedMode.UnitTests/Persistence/ServerConnectionModelMapperTest.cs b/src/ConnectedMode.UnitTests/Persistence/ServerConnectionModelMapperTest.cs new file mode 100644 index 0000000000..f578b15413 --- /dev/null +++ b/src/ConnectedMode.UnitTests/Persistence/ServerConnectionModelMapperTest.cs @@ -0,0 +1,239 @@ +/* + * 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 SonarLint.VisualStudio.ConnectedMode.Persistence; +using SonarLint.VisualStudio.Core.Binding; +using SonarLint.VisualStudio.TestInfrastructure; +using static SonarLint.VisualStudio.ConnectedMode.Persistence.ServerConnectionModelMapper; + +namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Persistence; + +[TestClass] +public class ServerConnectionModelMapperTest +{ + private ServerConnectionModelMapper testSubject; + + [TestInitialize] + public void TestInitialize() + { + testSubject = new ServerConnectionModelMapper(); + } + + [TestMethod] + public void MefCtor_CheckExports() + { + MefTestHelpers.CheckTypeCanBeImported(); + } + + [TestMethod] + public void Mef_CheckIsSingleton() + { + MefTestHelpers.CheckIsSingletonMefComponent(); + } + + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void GetServerConnection_SonarCloud_ReturnsSonarCloudConnection(bool isSmartNotificationsEnabled) + { + var connectionsModel = GetSonarCloudJsonModel("myOrg", isSmartNotificationsEnabled); + + var serverConnection = testSubject.GetServerConnection(connectionsModel); + + IsExpectedSonarCloudConnection(serverConnection, connectionsModel); + } + + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void GetServerConnection_SonarQube_ReturnsSonarQubeConnection(bool isSmartNotificationsEnabled) + { + var connectionsModel = GetSonarQubeJsonModel("http://localhost:9000", isSmartNotificationsEnabled); + + var serverConnection = testSubject.GetServerConnection(connectionsModel); + + IsExpectedSonarQubeConnection(serverConnection, connectionsModel); + } + + [TestMethod] + public void GetServerConnection_BothOrganizationKeyAndServerUriAreSet_ThrowsException() + { + var connectionsModel = GetSonarCloudJsonModel("myOrg"); + connectionsModel.ServerUri = "http://localhost:9000"; + + Action act = () => testSubject.GetServerConnection(connectionsModel); + + act.Should().Throw($"Invalid {nameof(ServerConnectionJsonModel)}. {nameof(ServerConnection)} could not be created"); + } + + [TestMethod] + public void GetServerConnection_BothOrganizationKeyAndServerUriAreNull_ThrowsException() + { + var connectionsModel = GetSonarCloudJsonModel("myOrg"); + connectionsModel.OrganizationKey = null; + + Action act = () => testSubject.GetServerConnection(connectionsModel); + + act.Should().Throw($"Invalid {nameof(ServerConnectionJsonModel)}. {nameof(ServerConnection)} could not be created"); + } + + [TestMethod] + [DataRow("org", null, true)] + [DataRow(null, "http://localhost", false)] + [DataRow(null, null, false)] + [DataRow("org", "http://localhost", false)] + public void IsServerConnectionForSonarCloud_OrganizationKeySetAndServerUriNotSet_ReturnsTrue(string organizationKey, string serverUi, bool expectedResult) + { + var connectionsModel = GetSonarCloudJsonModel(organizationKey); + connectionsModel.ServerUri = serverUi; + + var isSonarCloud = IsServerConnectionForSonarCloud(connectionsModel); + + isSonarCloud.Should().Be(expectedResult); + } + + [TestMethod] + [DataRow("org", null, false)] + [DataRow(null, "http://localhost", true)] + [DataRow(null, null, false)] + [DataRow("org", "http://localhost", false)] + public void IsServerConnectionForSonarCloud_OrganizationKeyNotSetAndServerUriSet_ReturnsTrue(string organizationKey, string serverUi, bool expectedResult) + { + var connectionsModel = GetSonarCloudJsonModel(organizationKey); + connectionsModel.ServerUri = serverUi; + + var isSonarQube = IsServerConnectionForSonarQube(connectionsModel); + + isSonarQube.Should().Be(expectedResult); + } + + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void GetServerConnectionsListJsonModel_OneSonarCloudConnection_ReturnsServerConnectionModelForSonarCloud(bool isSmartNotifications) + { + var sonarCloud = new ServerConnection.SonarCloud("myOrg", new ServerConnectionSettings(isSmartNotifications)); + + var model = testSubject.GetServerConnectionsListJsonModel([sonarCloud]); + + model.Should().NotBeNull(); + model.ServerConnections.Count.Should().Be(1); + model.ServerConnections[0].Should().BeEquivalentTo(new ServerConnectionJsonModel + { + Id = sonarCloud.Id, + OrganizationKey = sonarCloud.OrganizationKey, + Settings = sonarCloud.Settings, + ServerUri = null + }); + } + + [TestMethod] + public void GetServerConnectionsListJsonModel_OneSonarCloudConnectionWithNullSettings_ThrowsExceptions() + { + var sonarCloud = new ServerConnection.SonarCloud("myOrg") + { + Settings = null + }; + + Action act = () => testSubject.GetServerConnectionsListJsonModel([sonarCloud]); + + act.Should().Throw($"{nameof(ServerConnection.Settings)} can not be null"); + } + + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void GetServerConnectionsListJsonModel_OneSonarQubeConnection_ReturnsServerConnectionModelForSonarQube(bool isSmartNotifications) + { + var sonarQube = new ServerConnection.SonarQube(new Uri("http://localhost"), new ServerConnectionSettings(isSmartNotifications)); + + var model = testSubject.GetServerConnectionsListJsonModel([sonarQube]); + + model.Should().NotBeNull(); + model.ServerConnections.Count.Should().Be(1); + model.ServerConnections[0].Should().BeEquivalentTo(new ServerConnectionJsonModel + { + Id = sonarQube.Id, + OrganizationKey = null, + ServerUri = sonarQube.ServerUri.ToString(), + Settings = sonarQube.Settings + }); + } + + [TestMethod] + public void GetServerConnectionsListJsonModel_OneSonarQubeConnectionWithNullSettings_ThrowsExceptions() + { + var sonarQube = new ServerConnection.SonarQube(new Uri("http://localhost")) + { + Settings = null + }; + + Action act = () => testSubject.GetServerConnectionsListJsonModel([sonarQube]); + + act.Should().Throw($"{nameof(ServerConnection.Settings)} can not be null"); + } + + [TestMethod] + public void GetServerConnectionsListJsonModel_NoConnection_ReturnsServerConnectionModelWithNoConnection() + { + var model = testSubject.GetServerConnectionsListJsonModel([]); + + model.Should().NotBeNull(); + model.ServerConnections.Should().BeEmpty(); + } + + private static ServerConnectionJsonModel GetSonarCloudJsonModel(string id, bool isSmartNotificationsEnabled = false) + { + return new ServerConnectionJsonModel + { + Id = id, + OrganizationKey = id, + Settings = new ServerConnectionSettings(isSmartNotificationsEnabled) + }; + } + + private static ServerConnectionJsonModel GetSonarQubeJsonModel(string id, bool isSmartNotificationsEnabled = false) + { + return new ServerConnectionJsonModel + { + Id = id, + ServerUri = id, + Settings = new ServerConnectionSettings(isSmartNotificationsEnabled) + }; + } + + private static void IsExpectedSonarCloudConnection(ServerConnection serverConnection, ServerConnectionJsonModel connectionsModel) + { + serverConnection.Should().BeOfType(); + serverConnection.Id.Should().Be(serverConnection.Id); + serverConnection.Settings.Should().NotBeNull(); + serverConnection.Settings.IsSmartNotificationsEnabled.Should().Be(connectionsModel.Settings.IsSmartNotificationsEnabled); + ((ServerConnection.SonarCloud)serverConnection).OrganizationKey.Should().Be(connectionsModel.OrganizationKey); + } + + private static void IsExpectedSonarQubeConnection(ServerConnection serverConnection, ServerConnectionJsonModel connectionsModel) + { + serverConnection.Should().BeOfType(); + serverConnection.Id.Should().Be(serverConnection.Id); + serverConnection.Settings.Should().NotBeNull(); + serverConnection.Settings.IsSmartNotificationsEnabled.Should().Be(connectionsModel.Settings.IsSmartNotificationsEnabled); + ((ServerConnection.SonarQube)serverConnection).ServerUri.Should().Be(connectionsModel.ServerUri); + } +} diff --git a/src/ConnectedMode/Persistence/ServerConnectionJsonModel.cs b/src/ConnectedMode/Persistence/ServerConnectionJsonModel.cs new file mode 100644 index 0000000000..ef0142852e --- /dev/null +++ b/src/ConnectedMode/Persistence/ServerConnectionJsonModel.cs @@ -0,0 +1,46 @@ +/* + * 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 Newtonsoft.Json.Converters; +using Newtonsoft.Json; +using SonarLint.VisualStudio.Core.Binding; + +namespace SonarLint.VisualStudio.ConnectedMode.Persistence; + +public class ServerConnectionsListJsonModel +{ + [JsonProperty("serverConnections")] + public List ServerConnections { get; set; } = new(); +} + +public record ServerConnectionJsonModel +{ + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("setting")] + public ServerConnectionSettings Settings { get; set; } + + [JsonProperty("organizationKey")] + public string OrganizationKey { get; set; } + + [JsonProperty("serverUri")] + public string ServerUri { get; set; } +} diff --git a/src/ConnectedMode/Persistence/ServerConnectionModelMapper.cs b/src/ConnectedMode/Persistence/ServerConnectionModelMapper.cs new file mode 100644 index 0000000000..bf7a79d6ef --- /dev/null +++ b/src/ConnectedMode/Persistence/ServerConnectionModelMapper.cs @@ -0,0 +1,123 @@ +/* + * 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.ComponentModel.Composition; +using SonarLint.VisualStudio.Core.Binding; + +namespace SonarLint.VisualStudio.ConnectedMode.Persistence; + +public interface IServerConnectionModelMapper +{ + ServerConnection GetServerConnection(ServerConnectionJsonModel jsonModel); + ServerConnectionsListJsonModel GetServerConnectionsListJsonModel(IEnumerable serverConnections); +} + +[Export(typeof(IServerConnectionModelMapper))] +[PartCreationPolicy(CreationPolicy.Shared)] +public class ServerConnectionModelMapper : IServerConnectionModelMapper +{ + [ImportingConstructor] + public ServerConnectionModelMapper() { } + + public ServerConnection GetServerConnection(ServerConnectionJsonModel jsonModel) + { + if (IsServerConnectionForSonarCloud(jsonModel)) + { + return new ServerConnection.SonarCloud(jsonModel.OrganizationKey, jsonModel.Settings); + } + if (IsServerConnectionForSonarQube(jsonModel)) + { + return new ServerConnection.SonarQube(new Uri(jsonModel.ServerUri), jsonModel.Settings); + } + + throw new InvalidOperationException($"Invalid {nameof(ServerConnectionJsonModel)}. {nameof(ServerConnection)} could not be created"); + } + + public ServerConnectionsListJsonModel GetServerConnectionsListJsonModel(IEnumerable serverConnections) + { + var model = new ServerConnectionsListJsonModel + { + ServerConnections = serverConnections.Select(GetServerConnectionJsonModel).ToList() + }; + + return model; + } + + internal static bool IsServerConnectionForSonarCloud(ServerConnectionJsonModel jsonModel) + { + return IsOrganizationKeyFilled(jsonModel) && !IsServerUriFilled(jsonModel); + } + + internal static bool IsServerConnectionForSonarQube(ServerConnectionJsonModel jsonModel) + { + return IsServerUriFilled(jsonModel) && !IsOrganizationKeyFilled(jsonModel); + } + + private static ServerConnectionJsonModel GetServerConnectionJsonModel(ServerConnection serverConnection) + { + return new ServerConnectionJsonModel + { + Id = serverConnection.Id, + Settings = serverConnection.Settings ?? throw new InvalidOperationException($"{nameof(ServerConnection.Settings)} can not be null"), + OrganizationKey = GetOrganizationKey(serverConnection), + ServerUri = GetServerUri(serverConnection) + }; + } + + private static string GetOrganizationKey(ServerConnection serverConnection) + { + if (serverConnection is not ServerConnection.SonarCloud sonarCloud) + { + return null; + } + + if(string.IsNullOrWhiteSpace(sonarCloud.OrganizationKey)) + { + throw new InvalidOperationException($"{nameof(ServerConnection.SonarCloud.OrganizationKey)} can not be null"); + } + + return sonarCloud.OrganizationKey; + } + + private static string GetServerUri(ServerConnection serverConnection) + { + if (serverConnection is not ServerConnection.SonarQube sonarQube) + { + return null; + } + + if (sonarQube.ServerUri == null) + { + throw new InvalidOperationException($"{nameof(ServerConnection.SonarQube.ServerUri)} can not be null"); + } + + return sonarQube.ServerUri.ToString(); + } + + private static bool IsServerUriFilled(ServerConnectionJsonModel jsonModel) + { + return !string.IsNullOrWhiteSpace(jsonModel.ServerUri); + } + + private static bool IsOrganizationKeyFilled(ServerConnectionJsonModel jsonModel) + { + return !string.IsNullOrWhiteSpace(jsonModel.OrganizationKey); + } +} diff --git a/src/Core/Binding/ServerConnection.cs b/src/Core/Binding/ServerConnection.cs index 3d65b054ec..e21238fda3 100644 --- a/src/Core/Binding/ServerConnection.cs +++ b/src/Core/Binding/ServerConnection.cs @@ -25,7 +25,7 @@ public abstract class ServerConnection internal static readonly ServerConnectionSettings DefaultSettings = new(true); public string Id { get; } - public ServerConnectionSettings Settings { get; } + public ServerConnectionSettings Settings { get; set; } public ICredentials Credentials { get; set; } public abstract Uri ServerUri { get; }