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-1473 Refactor AliveConnectionTracker #5717

Merged
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
SLVS-1419 Update ActiveConnectionTracker to listen to events from Ser…
…verConnectionRepository instead of ISolutionBindingRepository.

The connections are saved in a dedicated repository and is no longer calculated from binding, therefore we need to listen to the repository.
  • Loading branch information
gabriela-trutan-sonarsource committed Sep 30, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit e46793544ec28832355fcfb61f41d8225cf303dd
105 changes: 81 additions & 24 deletions src/SLCore.UnitTests/State/AliveConnectionTrackerTests.cs
Original file line number Diff line number Diff line change
@@ -18,12 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.Linq;
using System.Threading.Tasks;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.Binding;
using SonarLint.VisualStudio.Core.Synchronization;
using SonarLint.VisualStudio.SLCore.Common.Helpers;
using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Service.Connection;
using SonarLint.VisualStudio.SLCore.Service.Connection.Models;
@@ -39,8 +36,8 @@ public void MefCtor_CheckIsExported()
{
MefTestHelpers.CheckTypeCanBeImported<AliveConnectionTracker, IAliveConnectionTracker>(
MefTestHelpers.CreateExport<ISLCoreServiceProvider>(),
MefTestHelpers.CreateExport<IServerConnectionsRepository>(),
MefTestHelpers.CreateExport<IServerConnectionsProvider>(),
MefTestHelpers.CreateExport<ISolutionBindingRepository>(),
MefTestHelpers.CreateExport<IThreadHandling>(),
MefTestHelpers.CreateExport<IAsyncLockFactory>());
}
@@ -54,13 +51,14 @@ public void MefCtor_CheckIsSingleton()
[TestMethod]
public void Ctor_SubscribesToEvents()
{
var bindingRepositoryMock = new Mock<ISolutionBindingRepository>();
var serverConnectionsRepository = new Mock<IServerConnectionsRepository>();
ConfigureAsyncLockFactory(out var asyncLockFactoryMock, out _, out _);

CreateTestSubject(Mock.Of<ISLCoreServiceProvider>(), Mock.Of<IServerConnectionsProvider>(), bindingRepositoryMock.Object,
CreateTestSubject(Mock.Of<ISLCoreServiceProvider>(), Mock.Of<IServerConnectionsProvider>(), serverConnectionsRepository.Object,
asyncLockFactoryMock.Object);

bindingRepositoryMock.VerifyAdd(x => x.BindingUpdated += It.IsAny<EventHandler>());
serverConnectionsRepository.VerifyAdd(x => x.ConnectionChanged += It.IsAny<EventHandler>());
serverConnectionsRepository.VerifyAdd(x => x.CredentialsChanged += It.IsAny<EventHandler<ServerConnectionUpdatedEventArgs>>());
asyncLockFactoryMock.Verify(x => x.Create());
}

@@ -74,7 +72,7 @@ public void Refresh_SonarQubeConnection_CorrectlyUpdated()
ConfigureAsyncLockFactory(out var asyncLockFactoryMock, out var asyncLockMock, out var asyncLockReleaseMock);
ConfigureConnectionProvider(out var connectionProviderMock, sonarQubeConnection);
var testSubject = CreateTestSubject(serviceProviderMock.Object, connectionProviderMock.Object,
Mock.Of<ISolutionBindingRepository>(), asyncLockFactoryMock.Object);
Mock.Of<IServerConnectionsRepository>(), asyncLockFactoryMock.Object);

testSubject.RefreshConnectionList();

@@ -100,7 +98,7 @@ public void Refresh_SonarCloudConnection_CorrectlyUpdated()
ConfigureAsyncLockFactory(out var asyncLockFactoryMock, out var asyncLockMock, out var asyncLockReleaseMock);
ConfigureConnectionProvider(out var connectionProviderMock, sonarCloudConnection);
var testSubject = CreateTestSubject(serviceProviderMock.Object, connectionProviderMock.Object,
Mock.Of<ISolutionBindingRepository>(), asyncLockFactoryMock.Object);
Mock.Of<IServerConnectionsRepository>(), asyncLockFactoryMock.Object);

testSubject.RefreshConnectionList();

@@ -124,7 +122,7 @@ public void Refresh_ChecksThread()
ConfigureConnectionProvider(out var connectionProviderMock);
var threadHandlingMock = new Mock<IThreadHandling>();
var testSubject = CreateTestSubject(serviceProviderMock.Object, connectionProviderMock.Object,
Mock.Of<ISolutionBindingRepository>(), asyncLockFactoryMock.Object, threadHandlingMock.Object);
Mock.Of<IServerConnectionsRepository>(), asyncLockFactoryMock.Object, threadHandlingMock.Object);

testSubject.RefreshConnectionList();

@@ -136,7 +134,7 @@ public void Refresh_ServiceUnavailable_Throws()
{
var serviceProviderMock = new Mock<ISLCoreServiceProvider>();
var testSubject = CreateTestSubject(serviceProviderMock.Object, Mock.Of<IServerConnectionsProvider>(),
Mock.Of<ISolutionBindingRepository>(), Mock.Of<IAsyncLockFactory>());
Mock.Of<IServerConnectionsRepository>(), Mock.Of<IAsyncLockFactory>());

var act = () => testSubject.RefreshConnectionList();

@@ -146,16 +144,16 @@ public void Refresh_ServiceUnavailable_Throws()
[TestMethod]
public void Event_TriggersRefresh()
{
var bindingRepositoryMock = new Mock<ISolutionBindingRepository>();
var serverConnectionsRepository = new Mock<IServerConnectionsRepository>();
var sonarQubeConnection = new SonarQubeConnectionConfigurationDto("sq1", true, "http://localhost/");
var sonarCloudConnection = new SonarCloudConnectionConfigurationDto("sc2", true, "org");
ConfigureServiceProvider(out var serviceProviderMock, out var connectionServiceMock);
ConfigureAsyncLockFactory(out var asyncLockFactoryMock, out var asyncLockMock, out var asyncLockReleaseMock);
ConfigureConnectionProvider(out var connectionProviderMock, sonarQubeConnection, sonarCloudConnection);
var testSubject = CreateTestSubject(serviceProviderMock.Object, connectionProviderMock.Object,
bindingRepositoryMock.Object, asyncLockFactoryMock.Object);
CreateTestSubject(serviceProviderMock.Object, connectionProviderMock.Object,
serverConnectionsRepository.Object, asyncLockFactoryMock.Object);

bindingRepositoryMock.Raise(x => x.BindingUpdated += It.IsAny<EventHandler>(), EventArgs.Empty);
serverConnectionsRepository.Raise(x => x.ConnectionChanged += It.IsAny<EventHandler>(), EventArgs.Empty);

connectionServiceMock.Verify(x => x.DidUpdateConnections(
It.Is<DidUpdateConnectionsParams>(p =>
@@ -169,31 +167,90 @@ public void Event_TriggersRefresh()
[TestMethod]
public void Event_RunsOnBackgroundThread()
{
var bindingRepositoryMock = new Mock<ISolutionBindingRepository>();
var serverConnectionsRepository = new Mock<IServerConnectionsRepository>();
var threadHandlingMock = new Mock<IThreadHandling>();
ConfigureConnectionProvider(out var connectionProviderMock);
var testSubject = CreateTestSubject(Mock.Of<ISLCoreServiceProvider>(), connectionProviderMock.Object,
bindingRepositoryMock.Object, Mock.Of<IAsyncLockFactory>(), threadHandlingMock.Object);
CreateTestSubject(Mock.Of<ISLCoreServiceProvider>(), connectionProviderMock.Object,
serverConnectionsRepository.Object, Mock.Of<IAsyncLockFactory>(), threadHandlingMock.Object);

bindingRepositoryMock.Raise(x => x.BindingUpdated += It.IsAny<EventHandler>(), EventArgs.Empty);
serverConnectionsRepository.Raise(x => x.ConnectionChanged += It.IsAny<EventHandler>(), EventArgs.Empty);

threadHandlingMock.Verify(x => x.RunOnBackgroundThread(It.IsAny<Func<Task<int>>>()));
}

[TestMethod]
public void Dispose_UnsubscribesAndDisposesLock()
{
var bindingRepositoryMock = new Mock<ISolutionBindingRepository>();
var serverConnectionsRepository = new Mock<IServerConnectionsRepository>();
ConfigureAsyncLockFactory(out var asyncLockFactoryMock, out var asyncLockMock, out _);
var testSubject = CreateTestSubject(Mock.Of<ISLCoreServiceProvider>(), Mock.Of<IServerConnectionsProvider>(),
bindingRepositoryMock.Object, asyncLockFactoryMock.Object);
serverConnectionsRepository.Object, asyncLockFactoryMock.Object);

testSubject.Dispose();

bindingRepositoryMock.VerifyRemove(x => x.BindingUpdated -= It.IsAny<EventHandler>());
serverConnectionsRepository.VerifyRemove(x => x.ConnectionChanged -= It.IsAny<EventHandler>());
asyncLockMock.Verify(x => x.Dispose());
}

[TestMethod]
public void UpdateCredentials_ChecksThread()
{
ConfigureServiceProvider(out var serviceProviderMock, out _);
ConfigureAsyncLockFactory(out var asyncLockFactoryMock, out _, out _);
ConfigureConnectionProvider(out var connectionProviderMock);
var threadHandlingMock = new Mock<IThreadHandling>();
var testSubject = CreateTestSubject(serviceProviderMock.Object, connectionProviderMock.Object, Mock.Of<IServerConnectionsRepository>(), asyncLockFactory: asyncLockFactoryMock.Object, threadHandlingMock.Object);

testSubject.UpdateCredentials("connId");

threadHandlingMock.Verify(x => x.ThrowIfOnUIThread());
}

[TestMethod]
public void UpdateCredentials_ServiceUnavailable_Throws()
{
var serviceProviderMock = new Mock<ISLCoreServiceProvider>();
var testSubject = CreateTestSubject(serviceProviderMock.Object, Mock.Of<IServerConnectionsProvider>(), Mock.Of<IServerConnectionsRepository>(), Mock.Of<IAsyncLockFactory>());

var act = () => testSubject.UpdateCredentials("connId");

act.Should().ThrowExactly<InvalidOperationException>().WithMessage(SLCoreStrings.ServiceProviderNotInitialized);
}

[TestMethod]
public void UpdateCredentials_SonarCloud_TriggersRefreshCredentials()
{
ConfigureServiceProvider(out var serviceProviderMock, out var connectionServiceMock);
ConfigureAsyncLockFactory(out var asyncLockFactoryMock, out var asyncLockMock, out var asyncLockReleaseMock);
var serverConnectionRepository = new Mock<IServerConnectionsRepository>();
CreateTestSubject(serviceProviderMock.Object, Mock.Of<IServerConnectionsProvider>(), serverConnectionRepository.Object, asyncLockFactoryMock.Object);
var sonarCloud = new ServerConnection.SonarCloud("myOrg");

serverConnectionRepository.Raise(
x => x.CredentialsChanged += It.IsAny<EventHandler<ServerConnectionUpdatedEventArgs>>(), new ServerConnectionUpdatedEventArgs(sonarCloud));

connectionServiceMock.Verify(
x => x.DidChangeCredentials(It.Is<DidChangeCredentialsParams>(args => args.connectionId == sonarCloud.Id)), Times.Once);
VerifyLockTakenAndReleased(asyncLockMock, asyncLockReleaseMock);
}

[TestMethod]
public void UpdateCredentials_SonarQube_TriggersRefreshCredentials()
{
ConfigureServiceProvider(out var serviceProviderMock, out var connectionServiceMock);
ConfigureAsyncLockFactory(out var asyncLockFactoryMock, out var asyncLockMock, out var asyncLockReleaseMock);
var serverConnectionRepository = new Mock<IServerConnectionsRepository>();
CreateTestSubject(serviceProviderMock.Object, Mock.Of<IServerConnectionsProvider>(), serverConnectionRepository.Object, asyncLockFactoryMock.Object);
var sonarQube = new ServerConnection.SonarQube(new Uri("http://localhost:9000"));

serverConnectionRepository.Raise(
x => x.CredentialsChanged += It.IsAny<EventHandler<ServerConnectionUpdatedEventArgs>>(), new ServerConnectionUpdatedEventArgs(sonarQube));

connectionServiceMock.Verify(
x => x.DidChangeCredentials(It.Is<DidChangeCredentialsParams>(args => args.connectionId == sonarQube.Id)), Times.Once);
VerifyLockTakenAndReleased(asyncLockMock, asyncLockReleaseMock);
}

private static void VerifyLockTakenAndReleased(Mock<IAsyncLock> asyncLock, Mock<IReleaseAsyncLock> lockRelease)
{
asyncLock.Verify(x => x.Acquire(), Times.Once);
@@ -228,13 +285,13 @@ private static void ConfigureServiceProvider(out Mock<ISLCoreServiceProvider> se

private static AliveConnectionTracker CreateTestSubject(ISLCoreServiceProvider slCoreServiceProvider,
IServerConnectionsProvider serverConnectionsProvider,
ISolutionBindingRepository bindingRepository,
IServerConnectionsRepository connectionsRepository,
IAsyncLockFactory asyncLockFactory,
IThreadHandling threadHandling = null)
{
return new AliveConnectionTracker(slCoreServiceProvider,
serverConnectionsProvider,
bindingRepository,
connectionsRepository,
asyncLockFactory,
threadHandling ?? new NoOpThreadHandler());
}
48 changes: 34 additions & 14 deletions src/SLCore/State/AliveConnectionTracker.cs
Original file line number Diff line number Diff line change
@@ -18,16 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.Binding;
using SonarLint.VisualStudio.Core.Synchronization;
using SonarLint.VisualStudio.SLCore.Common.Helpers;
using SonarLint.VisualStudio.SLCore.Core;
using SonarLint.VisualStudio.SLCore.Service.Connection;
using SonarLint.VisualStudio.SLCore.Service.Connection.Models;
@@ -47,20 +42,21 @@ internal sealed class AliveConnectionTracker : IAliveConnectionTracker
private readonly IThreadHandling threadHandling;
private readonly IAsyncLock asyncLock;
private readonly IServerConnectionsProvider serverConnectionsProvider;
private readonly ISolutionBindingRepository solutionBindingRepository;
private readonly IServerConnectionsRepository serverConnectionsRepository;

[ImportingConstructor]
public AliveConnectionTracker(ISLCoreServiceProvider serviceProvider,
IServerConnectionsProvider serverConnectionsProvider,
ISolutionBindingRepository solutionBindingRepository,
IServerConnectionsRepository serverConnectionsRepository,
IAsyncLockFactory asyncLockFactory,
IThreadHandling threadHandling)
{
this.serviceProvider = serviceProvider;
this.serverConnectionsProvider = serverConnectionsProvider;
this.threadHandling = threadHandling;
this.solutionBindingRepository = solutionBindingRepository;
this.solutionBindingRepository.BindingUpdated += BindingUpdateHandler;
this.serverConnectionsRepository = serverConnectionsRepository;
this.serverConnectionsRepository.ConnectionChanged += ConnectionUpdateHandler;
this.serverConnectionsRepository.CredentialsChanged += CredentialsUpdateHandler;
asyncLock = asyncLockFactory.Create();
}

@@ -83,14 +79,27 @@ internal void RefreshConnectionList()

foreach (var connectionId in serverConnections.Keys)
{
// we don't manage connections as separate entities and we don't know when credentials actually change
connectionConfigurationService.DidChangeCredentials(
new DidChangeCredentialsParams(connectionId));
connectionConfigurationService.DidChangeCredentials(new DidChangeCredentialsParams(connectionId));
}
}
}

private void BindingUpdateHandler(object sender, EventArgs arg)
internal void UpdateCredentials(string connectionId)
{
threadHandling.ThrowIfOnUIThread();

if (!serviceProvider.TryGetTransientService(out IConnectionConfigurationSLCoreService connectionConfigurationService))
{
throw new InvalidOperationException(SLCoreStrings.ServiceProviderNotInitialized);
}

using (asyncLock.Acquire())
{
connectionConfigurationService.DidChangeCredentials(new DidChangeCredentialsParams(connectionId));
}
}

private void ConnectionUpdateHandler(object sender, EventArgs arg)
{
threadHandling.RunOnBackgroundThread(() =>
{
@@ -100,9 +109,20 @@ private void BindingUpdateHandler(object sender, EventArgs arg)
}).Forget();
}

private void CredentialsUpdateHandler(object sender, ServerConnectionUpdatedEventArgs e)
{
threadHandling.RunOnBackgroundThread(() =>
{
UpdateCredentials(e.ServerConnection.Id);

return Task.FromResult(0);
}).Forget();
}

public void Dispose()
{
solutionBindingRepository.BindingUpdated -= BindingUpdateHandler;
serverConnectionsRepository.ConnectionChanged -= ConnectionUpdateHandler;
serverConnectionsRepository.CredentialsChanged -= CredentialsUpdateHandler;
asyncLock.Dispose();
}
}