Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SLVS-1474 Refactor ServerConnectionsProvider #5718

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.ComponentModel;
using System.IO;
using System.IO.Abstractions;
using SonarLint.VisualStudio.ConnectedMode.Binding;
Expand Down Expand Up @@ -338,6 +339,32 @@ public void TryAdd_WritingThrowsException_DoesNotUpdateConnectionAndWritesLog()
logger.Received(1).WriteLine($"Failed updating the {ServerConnectionsRepository.ConnectionsFileName}: {exceptionMsg}");
}

[TestMethod]
public void TryAdd_DoesNotAddConnection_DoesNotInvokeConnectionChangedEvent()
{
MockReadingFile(new ServerConnectionsListJsonModel());
jsonFileHandler.TryWriteToFile(Arg.Any<string>(), Arg.Any<ServerConnectionsListJsonModel>()).Returns(false);
var eventHandler = Substitute.For<EventHandler>();
testSubject.ConnectionChanged += eventHandler;

testSubject.TryAdd(sonarCloudServerConnection);

eventHandler.DidNotReceive().Invoke(testSubject, Arg.Any<EventArgs>());
}

[TestMethod]
public void TryAdd_AddsConnection_InvokesConnectionChangedEvent()
{
MockReadingFile(new ServerConnectionsListJsonModel());
jsonFileHandler.TryWriteToFile(Arg.Any<string>(), Arg.Any<ServerConnectionsListJsonModel>()).Returns(true);
var eventHandler = Substitute.For<EventHandler>();
testSubject.ConnectionChanged += eventHandler;

testSubject.TryAdd(sonarCloudServerConnection);

eventHandler.Received().Invoke(testSubject, Arg.Any<EventArgs>());
}

[TestMethod]
public void TryDelete_FileCouldNotBeRead_ReturnsFalse()
{
Expand Down Expand Up @@ -439,6 +466,32 @@ public void TryDelete_WritingThrowsException_DoesNotUpdateConnectionAndWritesLog
logger.Received(1).WriteLine($"Failed updating the {ServerConnectionsRepository.ConnectionsFileName}: {exceptionMsg}");
}

[TestMethod]
public void TryDelete_DoesNotDeleteConnection_DoesNotInvokeConnectionChangedEvent()
{
var sonarQube = MockFileWithOneSonarQubeConnection();
jsonFileHandler.TryWriteToFile(Arg.Any<string>(), Arg.Any<ServerConnectionsListJsonModel>()).Returns(false);
var eventHandler = Substitute.For<EventHandler>();
testSubject.ConnectionChanged += eventHandler;

testSubject.TryDelete(sonarQube.Id);

eventHandler.DidNotReceive().Invoke(testSubject, Arg.Any<EventArgs>());
}

[TestMethod]
public void TryDelete_DeletesConnection_InvokesConnectionChangedEvent()
{
var sonarQube = MockFileWithOneSonarQubeConnection();
jsonFileHandler.TryWriteToFile(Arg.Any<string>(), Arg.Any<ServerConnectionsListJsonModel>()).Returns(true);
var eventHandler = Substitute.For<EventHandler>();
testSubject.ConnectionChanged += eventHandler;

testSubject.TryDelete(sonarQube.Id);

eventHandler.Received(1).Invoke(testSubject, Arg.Any<EventArgs>());
}

[TestMethod]
public void TryUpdateSettingsById_FileCouldNotBeRead_ReturnsFalse()
{
Expand Down Expand Up @@ -505,6 +558,32 @@ public void TryUpdateSettingsById_WritingThrowsException_DoesNotUpdateConnection
logger.Received(1).WriteLine($"Failed updating the {ServerConnectionsRepository.ConnectionsFileName}: {exceptionMsg}");
}

[TestMethod]
public void TryUpdateSettingsById_DoesNotUpdateConnection_DoesNotInvokeConnectionChangedEvent()
{
var sonarQube = MockFileWithOneSonarQubeConnection();
jsonFileHandler.TryWriteToFile(Arg.Any<string>(), Arg.Any<ServerConnectionsListJsonModel>()).Returns(false);
var eventHandler = Substitute.For<EventHandler>();
testSubject.ConnectionChanged += eventHandler;

testSubject.TryDelete(sonarQube.Id);

eventHandler.DidNotReceive().Invoke(testSubject, Arg.Any<EventArgs>());
}

[TestMethod]
public void TryUpdateSettingsById_UpdatesConnection_InvokesConnectionChangedEvent()
{
var sonarQube = MockFileWithOneSonarQubeConnection();
jsonFileHandler.TryWriteToFile(Arg.Any<string>(), Arg.Any<ServerConnectionsListJsonModel>()).Returns(true);
var eventHandler = Substitute.For<EventHandler>();
testSubject.ConnectionChanged += eventHandler;

testSubject.TryUpdateSettingsById(sonarQube.Id, new ServerConnectionSettings(true));

eventHandler.Received(1).Invoke(testSubject, Arg.Any<EventArgs>());
}

[TestMethod]
public void TryUpdateCredentialsById_ConnectionDoesNotExist_DoesNotUpdateCredentials()
{
Expand Down Expand Up @@ -540,6 +619,30 @@ public void TryUpdateCredentialsById_SonarQubeConnectionExists_UpdatesCredential
credentialsLoader.Received(1).Save(newCredentials, sonarQube.ServerUri);
}

[TestMethod]
public void TryUpdateCredentialsById_DoesNotUpdateCredentials_DoesNotInvokeConnectionChangedEvent()
{
MockReadingFile(new ServerConnectionsListJsonModel());
var eventHandler = Substitute.For<EventHandler<ServerConnectionUpdatedEventArgs>>();
testSubject.CredentialsChanged += eventHandler;

testSubject.TryUpdateCredentialsById("non-existingConn", Substitute.For<ICredentials>());

eventHandler.DidNotReceive().Invoke(testSubject, Arg.Any<ServerConnectionUpdatedEventArgs>());
}

[TestMethod]
public void TryUpdateCredentialsById_UpdatesCredentials_InvokesConnectionChangedEvent()
{
var sonarQube = MockFileWithOneSonarQubeConnection();
var eventHandler = Substitute.For<EventHandler<ServerConnectionUpdatedEventArgs>>();
testSubject.CredentialsChanged += eventHandler;

testSubject.TryUpdateCredentialsById(sonarQube.Id, Substitute.For<ICredentials>());

eventHandler.Received(1).Invoke(testSubject, Arg.Is<ServerConnectionUpdatedEventArgs>(args => args.ServerConnection == sonarQube));
}

[TestMethod]
[DataRow(true)]
[DataRow(false)]
Expand Down
14 changes: 13 additions & 1 deletion src/ConnectedMode/Persistence/ServerConnectionsRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ internal class ServerConnectionsRepository : IServerConnectionsRepository
private readonly string connectionsStorageFilePath;
private static readonly object LockObject = new();

public event EventHandler ConnectionChanged;
public event EventHandler<ServerConnectionUpdatedEventArgs> CredentialsChanged;

[ImportingConstructor]
public ServerConnectionsRepository(
IJsonFileHandler jsonFileHandle,
Expand Down Expand Up @@ -127,6 +130,7 @@ public bool TryUpdateCredentialsById(string connectionId, ICredentials credentia
if (wasFound)
{
credentialsLoader.Save(credentials, serverConnection.CredentialsUri);
OnCredentialsChanged(serverConnection);
return true;
}
}
Expand Down Expand Up @@ -229,7 +233,12 @@ private bool SafeUpdateConnectionsFile(Func<List<ServerConnection>, bool> tryUpd
if (tryUpdateConnectionModels(serverConnections))
{
var model = serverConnectionModelMapper.GetServerConnectionsListJsonModel(serverConnections);
return jsonFileHandle.TryWriteToFile(connectionsStorageFilePath, model);
var wasSaved = jsonFileHandle.TryWriteToFile(connectionsStorageFilePath, model);
if (wasSaved)
{
OnConnectionChanged();
}
return wasSaved;
}
}
catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex))
Expand All @@ -240,4 +249,7 @@ private bool SafeUpdateConnectionsFile(Func<List<ServerConnection>, bool> tryUpd
return false;
}
}

private void OnConnectionChanged() => ConnectionChanged?.Invoke(this, EventArgs.Empty);
private void OnCredentialsChanged(ServerConnection serverConnection) => CredentialsChanged?.Invoke(this, new ServerConnectionUpdatedEventArgs(serverConnection));
}
13 changes: 13 additions & 0 deletions src/Core/Binding/IServerConnectionsRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,17 @@ public interface IServerConnectionsRepository
bool TryUpdateSettingsById(string connectionId, ServerConnectionSettings connectionSettings);
bool TryUpdateCredentialsById(string connectionId, ICredentials credentials);
bool ConnectionsFileExists();
event EventHandler ConnectionChanged;
event EventHandler<ServerConnectionUpdatedEventArgs> CredentialsChanged;
}

public class ServerConnectionUpdatedEventArgs : EventArgs
{
public ServerConnectionUpdatedEventArgs(ServerConnection serverConnection)
{
ServerConnection = serverConnection;
}

public ServerConnection ServerConnection { get; }
}

36 changes: 10 additions & 26 deletions src/SLCore.Listeners.UnitTests/CredentialsListenerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System;
using System.Threading.Tasks;
using NSubstitute;
using SonarLint.VisualStudio.ConnectedMode.Binding;
using SonarLint.VisualStudio.SLCore.Common.Helpers;
using SonarLint.VisualStudio.SLCore.Common.Models;
using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Listener.Credentials;
Expand All @@ -32,15 +28,14 @@ namespace SonarLint.VisualStudio.SLCore.Listeners.UnitTests;
[TestClass]
public class CredentialsListenerTests
{
private const string ConnectionId = "connectionId123";
private static readonly Uri Uri = new("http://myfavouriteuri.nonexistingdomain");
private const string ConnectionId = "http://myfavouriteuri.nonexistingdomain";
private static readonly Uri Uri = new(ConnectionId);

[TestMethod]
public void MefCtor_CheckIsExported()
{
MefTestHelpers.CheckTypeCanBeImported<CredentialsListener, ISLCoreListener>(
MefTestHelpers.CreateExport<ICredentialProvider>(),
MefTestHelpers.CreateExport<IConnectionIdHelper>());
MefTestHelpers.CreateExport<ICredentialProvider>());
}

[TestMethod]
Expand All @@ -52,8 +47,7 @@ public void MefCtor_CheckIsSingleton()
[TestMethod]
public async Task GetCredentialsAsync_NullConnectionId_ReturnsNoCredentials()
{
var testSubject = CreateTestSubject(out _, out var connectionIdHelperMock);
connectionIdHelperMock.GetUriFromConnectionId(null).Returns((Uri)null);
var testSubject = CreateTestSubject(out _);

var response = await testSubject.GetCredentialsAsync(new GetCredentialsParams(null));

Expand All @@ -63,8 +57,7 @@ public async Task GetCredentialsAsync_NullConnectionId_ReturnsNoCredentials()
[TestMethod]
public async Task GetCredentialsAsync_NullParams_ReturnsNoCredentials()
{
var testSubject = CreateTestSubject(out _, out var connectionIdHelperMock);
connectionIdHelperMock.GetUriFromConnectionId(null).Returns((Uri)null);
var testSubject = CreateTestSubject(out _);

var response = await testSubject.GetCredentialsAsync(null);

Expand All @@ -74,8 +67,7 @@ public async Task GetCredentialsAsync_NullParams_ReturnsNoCredentials()
[TestMethod]
public async Task GetCredentialsAsync_CredentialsNotFound_ReturnsNoCredentials()
{
var testSubject = CreateTestSubject(out var credentialStoreMock, out var connectionIdHelperMock);
SetUpConnectionIdHelper(connectionIdHelperMock);
var testSubject = CreateTestSubject(out var credentialStoreMock);
credentialStoreMock.GetCredentials(Arg.Is<Uri>(x => UriEquals(x, Uri))).Returns((ConnectionCredentials)null);

var response = await testSubject.GetCredentialsAsync(new GetCredentialsParams(ConnectionId));
Expand All @@ -90,8 +82,7 @@ public async Task GetCredentialsAsync_SonarQubeUsernameAndPasswordFound_ReturnsU
const string username = "user1";
const string password = "password123";

var testSubject = CreateTestSubject(out var credentialStoreMock, out var connectionIdHelperMock);
SetUpConnectionIdHelper(connectionIdHelperMock);
var testSubject = CreateTestSubject(out var credentialStoreMock);
credentialStoreMock.GetCredentials(Arg.Is<Uri>(x => UriEquals(x, Uri))).Returns(new ConnectionCredentials(username, password));

var response = await testSubject.GetCredentialsAsync(new GetCredentialsParams(ConnectionId));
Expand All @@ -104,26 +95,19 @@ public async Task GetCredentialsAsync_SonarQubeTokenFound_ReturnsToken()
{
const string token = "token123";

var testSubject = CreateTestSubject(out var credentialStoreMock, out var connectionIdHelperMock);
SetUpConnectionIdHelper(connectionIdHelperMock);
var testSubject = CreateTestSubject(out var credentialStoreMock);
credentialStoreMock.GetCredentials(Arg.Is<Uri>(x => UriEquals(x, Uri))).Returns(new ConnectionCredentials(token));

var response = await testSubject.GetCredentialsAsync(new GetCredentialsParams(ConnectionId));

response.Should().BeEquivalentTo(new GetCredentialsResponse(new TokenDto(token)));
}

private CredentialsListener CreateTestSubject(out ICredentialProvider credentialStoreMock, out IConnectionIdHelper connectionIdHelperMock)
private CredentialsListener CreateTestSubject(out ICredentialProvider credentialStoreMock)
{
credentialStoreMock = Substitute.For<ICredentialProvider>();
connectionIdHelperMock = Substitute.For<IConnectionIdHelper>();

return new CredentialsListener(credentialStoreMock, connectionIdHelperMock);
}

private static void SetUpConnectionIdHelper(IConnectionIdHelper connectionIdHelperMock)
{
connectionIdHelperMock.GetUriFromConnectionId(ConnectionId).Returns(Uri);
return new CredentialsListener(credentialStoreMock);
}

private static bool UriEquals(Uri uri, Uri serverUri)
Expand Down
11 changes: 3 additions & 8 deletions src/SLCore.Listeners/Implementation/CredentialsListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@
*/

using System.ComponentModel.Composition;
using System.Threading.Tasks;
using SonarLint.VisualStudio.ConnectedMode.Binding;
using SonarLint.VisualStudio.SLCore.Common.Helpers;
using SonarLint.VisualStudio.SLCore.Common.Models;
using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Listener.Credentials;
Expand All @@ -36,24 +34,21 @@ namespace SonarLint.VisualStudio.SLCore.Listeners.Implementation
internal class CredentialsListener : ICredentialsListener
{
private readonly ICredentialProvider credentialProvider;
private readonly IConnectionIdHelper connectionIdHelper;

[ImportingConstructor]
public CredentialsListener(ICredentialProvider credentialProvider, IConnectionIdHelper connectionIdHelper)
public CredentialsListener(ICredentialProvider credentialProvider)
{
this.credentialProvider = credentialProvider;
this.connectionIdHelper = connectionIdHelper;
}

public Task<GetCredentialsResponse> GetCredentialsAsync(GetCredentialsParams parameters)
{
var serverUri = connectionIdHelper.GetUriFromConnectionId(parameters?.connectionId);

if (serverUri == null)
if (parameters?.connectionId == null)
{
return Task.FromResult(GetCredentialsResponse.NoCredentials);
}

var serverUri = new Uri(parameters.connectionId);
var credentials = credentialProvider.GetCredentials(serverUri);

if (credentials == null)
Expand Down
Loading