Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Upd
Browse files Browse the repository at this point in the history
georgii-borovinskikh-sonarsource committed Aug 30, 2024
1 parent a1916fd commit 1ee12ee
Showing 6 changed files with 162 additions and 63 deletions.
187 changes: 132 additions & 55 deletions src/ConnectedMode.UnitTests/Binding/UnintrusiveBindingControllerTests.cs
Original file line number Diff line number Diff line change
@@ -18,82 +18,159 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System;
using System.Collections.Generic;
using System.Threading;
using SonarLint.VisualStudio.ConnectedMode.Binding;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.Binding;
using SonarLint.VisualStudio.TestInfrastructure;
using Task = System.Threading.Tasks.Task;

namespace SonarLint.VisualStudio.ConnectedMode.Binding.UnitTests
namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Binding;

[TestClass]
public class UnintrusiveBindingControllerTests
{
[TestClass]
public class UnintrusiveBindingControllerTests
{
private static readonly BoundServerProject AnyBoundProject = new BoundServerProject("any", "any", new ServerConnection.SonarCloud("any"));
private static readonly BoundSonarQubeProject OldBoundProject = new BoundSonarQubeProject(new Uri("http://any"), "any", "any");
private static readonly BoundServerProject AnyBoundProject = new BoundServerProject("any", "any", new ServerConnection.SonarCloud("any"));

[TestMethod]
public void MefCtor_CheckTypeIsNonShared()
=> MefTestHelpers.CheckIsNonSharedMefComponent<UnintrusiveBindingController>();
[TestMethod]
public void MefCtor_CheckTypeIsNonShared()
=> MefTestHelpers.CheckIsNonSharedMefComponent<UnintrusiveBindingController>();

[TestMethod]
public void MefCtor_CheckIsExported()
{
MefTestHelpers.CheckTypeCanBeImported<UnintrusiveBindingController, IUnintrusiveBindingController>(
MefTestHelpers.CreateExport<IBindingProcessFactory>());
}
[TestMethod]
public void MefCtor_CheckIsExported()
{
MefTestHelpers.CheckTypeCanBeImported<UnintrusiveBindingController, IUnintrusiveBindingController>(
MefTestHelpers.CreateExport<IBindingProcessFactory>(),
MefTestHelpers.CreateExport<IServerConnectionsRepository>(),
MefTestHelpers.CreateExport<ISolutionInfoProvider>());
}

[TestMethod]
public async Task BindAsnyc_GetsBindingProcessFromFactory()
{
var bindingProcessFactory = CreateBindingProcessFactory();
[TestMethod]
public async Task BindAsync_CallsBindingProcessInOrder()
{
var cancellationToken = CancellationToken.None;
var bindingProcess = Substitute.For<IBindingProcess>();
var bindingProcessFactory = CreateBindingProcessFactory(bindingProcess);
var testSubject = CreateTestSubject(bindingProcessFactory);

await testSubject.BindAsync(AnyBoundProject, null, cancellationToken);

var testSubject = CreateTestSubject(bindingProcessFactory: bindingProcessFactory);
await testSubject.BindAsync(AnyBoundProject, null, CancellationToken.None);
Received.InOrder(() =>
{
bindingProcessFactory.Create(Arg.Is<BindCommandArgs>(b => b.ProjectToBind == AnyBoundProject));
bindingProcess.DownloadQualityProfileAsync(null, cancellationToken);
bindingProcess.SaveServerExclusionsAsync(cancellationToken);
});
}

[TestMethod]
public async Task BindAsync_OldProject_ConnectionExists_EstablishesBinding()
{
var cancellationToken = CancellationToken.None;
var bindingProcess = Substitute.For<IBindingProcess>();
var bindingProcessFactory = CreateBindingProcessFactory(bindingProcess);
var convertedConnection = ServerConnection.FromBoundSonarQubeProject(OldBoundProject);
var storedConnection = new ServerConnection.SonarQube(new Uri("http://any"));
var serverConnectionsRepository = CreateServerConnectionsRepository(convertedConnection.Id, storedConnection);
var solutionInfoProvider = CreateSolutionInfoProvider();
var testSubject = CreateTestSubject(bindingProcessFactory, serverConnectionsRepository, solutionInfoProvider);

var args = bindingProcessFactory.ReceivedCalls().First().GetArguments()[0] as BindCommandArgs;
await testSubject.BindAsync(OldBoundProject, null, cancellationToken);

args.ProjectToBind.Should().BeSameAs(AnyBoundProject);
Received.InOrder(() =>
{
serverConnectionsRepository.TryGet(convertedConnection.Id, out Arg.Any<ServerConnection>());
solutionInfoProvider.GetSolutionNameAsync();
bindingProcessFactory.Create(Arg.Is<BindCommandArgs>(b => b.ProjectToBind.ServerProjectKey == OldBoundProject.ProjectKey && b.ProjectToBind.ServerConnection == storedConnection));
bindingProcess.DownloadQualityProfileAsync(null, cancellationToken);
bindingProcess.SaveServerExclusionsAsync(cancellationToken);
});
}

bindingProcessFactory.Received().Create(Arg.Any<BindCommandArgs>());
}
[TestMethod]
public async Task BindAsync_OldProject_ConnectionDoesNotExist_AddsConnectionAndEstablishesBinding()
{
var cancellationToken = CancellationToken.None;
var bindingProcess = Substitute.For<IBindingProcess>();
var bindingProcessFactory = CreateBindingProcessFactory(bindingProcess);
var convertedConnection = ServerConnection.FromBoundSonarQubeProject(OldBoundProject);
var serverConnectionsRepository = CreateServerConnectionsRepository();
serverConnectionsRepository.TryAdd(Arg.Is<ServerConnection>(s => s.Id == convertedConnection.Id)).Returns(true);
var solutionInfoProvider = CreateSolutionInfoProvider();
var testSubject = CreateTestSubject(bindingProcessFactory, serverConnectionsRepository, solutionInfoProvider);

await testSubject.BindAsync(OldBoundProject, null, cancellationToken);

[TestMethod]
public async Task BindAsnyc_CallsBindingProcessInOrder()
Received.InOrder(() =>
{
var calls = new List<string>();
var cancellationToken = CancellationToken.None;

var bindingProcess = new Mock<IBindingProcess>();
bindingProcess.Setup(x => x.DownloadQualityProfileAsync(null, cancellationToken)).Callback(() => calls.Add("DownloadQualityProfiles"));
bindingProcess.Setup(x => x.SaveServerExclusionsAsync(cancellationToken)).Callback(() => calls.Add("SaveServerExclusionsAsync"));
serverConnectionsRepository.TryGet(convertedConnection.Id, out Arg.Any<ServerConnection>());
serverConnectionsRepository.TryAdd(Arg.Is<ServerConnection>(c => c.Id == convertedConnection.Id));
solutionInfoProvider.GetSolutionNameAsync();
bindingProcessFactory.Create(Arg.Is<BindCommandArgs>(b => b.ProjectToBind.ServerProjectKey == OldBoundProject.ProjectKey && b.ProjectToBind.ServerConnection.Id == convertedConnection.Id));
bindingProcess.DownloadQualityProfileAsync(null, cancellationToken);
bindingProcess.SaveServerExclusionsAsync(cancellationToken);
});
}

[TestMethod]
public void BindAsync_OldProject_ConnectionDoesNotExist_CannotAdd_Throws()
{
var convertedConnection = ServerConnection.FromBoundSonarQubeProject(OldBoundProject);
var serverConnectionsRepository = CreateServerConnectionsRepository(convertedConnection.Id);
var testSubject = CreateTestSubject(serverConnectionsRepository: serverConnectionsRepository);

Func<Task> act = async () => await testSubject.BindAsync(OldBoundProject, null, CancellationToken.None);

var testSubject = CreateTestSubject(bindingProcessFactory: CreateBindingProcessFactory(bindingProcess.Object));
await testSubject.BindAsync(AnyBoundProject, null, cancellationToken);
act.Should().Throw<InvalidOperationException>().WithMessage(BindingStrings.UnintrusiveController_CantAddConnection);
}

[TestMethod]
public void BindAsync_OldProject_InvalidServerInformation_Throws()
{
var testSubject = CreateTestSubject();

Func<Task> act = async () => await testSubject.BindAsync(new BoundSonarQubeProject(), null, CancellationToken.None);

calls.Should().ContainInOrder("DownloadQualityProfiles", "SaveServerExclusionsAsync");
}
act.Should().Throw<InvalidOperationException>().WithMessage(BindingStrings.UnintrusiveController_InvalidConnection);
}

private UnintrusiveBindingController CreateTestSubject(IBindingProcessFactory bindingProcessFactory = null,
IServerConnectionsRepository serverConnectionsRepository = null,
ISolutionInfoProvider solutionInfoProvider = null)
{
var testSubject = new UnintrusiveBindingController(bindingProcessFactory ?? CreateBindingProcessFactory(),
serverConnectionsRepository ?? Substitute.For<IServerConnectionsRepository>(),
solutionInfoProvider ?? Substitute.For<ISolutionInfoProvider>());
private UnintrusiveBindingController CreateTestSubject(IBindingProcessFactory bindingProcessFactory = null,
IServerConnectionsRepository serverConnectionsRepository = null,
ISolutionInfoProvider solutionInfoProvider = null)
{
var testSubject = new UnintrusiveBindingController(bindingProcessFactory ?? CreateBindingProcessFactory(),
serverConnectionsRepository ?? Substitute.For<IServerConnectionsRepository>(),
solutionInfoProvider ?? Substitute.For<ISolutionInfoProvider>());

return testSubject;
}
return testSubject;
}

private IBindingProcessFactory CreateBindingProcessFactory(IBindingProcess bindingProcess = null)
{
bindingProcess ??= Substitute.For<IBindingProcess>();
private IBindingProcessFactory CreateBindingProcessFactory(IBindingProcess bindingProcess = null)
{
bindingProcess ??= Substitute.For<IBindingProcess>();

var bindingProcessFactory = Substitute.For<IBindingProcessFactory>();
bindingProcessFactory.Create(It.IsAny<BindCommandArgs>()).Returns(bindingProcess);
var bindingProcessFactory = Substitute.For<IBindingProcessFactory>();
bindingProcessFactory.Create(Arg.Any<BindCommandArgs>()).Returns(bindingProcess);

return bindingProcessFactory;
}
return bindingProcessFactory;
}

private static IServerConnectionsRepository CreateServerConnectionsRepository(string id = null, ServerConnection.SonarQube storedConnection = null)
{
var serverConnectionsRepository = Substitute.For<IServerConnectionsRepository>();
serverConnectionsRepository.TryGet(id ?? Arg.Any<string>(), out Arg.Any<ServerConnection>())
.Returns(info =>
{
info[1] = storedConnection;
return storedConnection != null;
});
return serverConnectionsRepository;
}

private static ISolutionInfoProvider CreateSolutionInfoProvider()
{
var solutionInfoProvider = Substitute.For<ISolutionInfoProvider>();
solutionInfoProvider.GetSolutionNameAsync().Returns("solution");
return solutionInfoProvider;
}
}
18 changes: 18 additions & 0 deletions src/ConnectedMode/Binding/BindingStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/ConnectedMode/Binding/BindingStrings.resx
Original file line number Diff line number Diff line change
@@ -138,4 +138,10 @@
<data name="SharedBindingSuggestionInfoOptionText" xml:space="preserve">
<value>Learn more</value>
</data>
<data name="UnintrusiveController_InvalidConnection" xml:space="preserve">
<value>Could not convert connection information for the binding</value>
</data>
<data name="UnintrusiveController_CantAddConnection" xml:space="preserve">
<value>Could not add converted connection</value>
</data>
</root>
6 changes: 3 additions & 3 deletions src/ConnectedMode/Binding/IUnintrusiveBindingController.cs
Original file line number Diff line number Diff line change
@@ -62,20 +62,20 @@ public async Task BindAsync(BoundSonarQubeProject project, IProgress<FixedStepsP

if (proposedConnection is null)
{
throw new NotImplementedException();
throw new InvalidOperationException(BindingStrings.UnintrusiveController_InvalidConnection);
}

if (!serverConnectionsRepository.TryGet(proposedConnection.Id, out var connection))
{
if (!serverConnectionsRepository.TryAdd(proposedConnection))
{
throw new NotImplementedException();
throw new InvalidOperationException(BindingStrings.UnintrusiveController_CantAddConnection);
}

connection = proposedConnection;
}

await BindAsync(new BoundServerProject(await solutionInfoProvider.GetSolutionNameAsync(), project.ProjectKey, connection), progress, token);
await BindAsync(BoundServerProject.FromBoundSonarQubeProject(project, await solutionInfoProvider.GetSolutionNameAsync(), connection), progress, token);
}

private IBindingProcess CreateBindingProcess(BoundServerProject project)
6 changes: 3 additions & 3 deletions src/Core/Binding/BoundServerProject.cs
Original file line number Diff line number Diff line change
@@ -46,10 +46,10 @@ public BoundServerProject(string localBindingKey, string serverProjectKey, Serve
LocalBindingKey = localBindingKey;
}

public static BoundServerProject FromBoundSonarQubeProject(BoundSonarQubeProject boundProject) =>
new("Solution Name Placeholder", // todo https://sonarsource.atlassian.net/browse/SLVS-1422
public static BoundServerProject FromBoundSonarQubeProject(BoundSonarQubeProject boundProject, string localBindingKey = null, ServerConnection connection = null) =>
new(localBindingKey ?? "Solution Name Placeholder", // todo https://sonarsource.atlassian.net/browse/SLVS-1422
boundProject.ProjectKey,
ServerConnection.FromBoundSonarQubeProject(boundProject))
connection ?? ServerConnection.FromBoundSonarQubeProject(boundProject))
{
Profiles = boundProject.Profiles
};
2 changes: 0 additions & 2 deletions src/Core/Binding/ServerConnection.cs
Original file line number Diff line number Diff line change
@@ -18,8 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using SonarQube.Client.Models;

namespace SonarLint.VisualStudio.Core.Binding;

public abstract class ServerConnection

0 comments on commit 1ee12ee

Please sign in to comment.