Skip to content

Commit c0e374d

Browse files

File tree

10 files changed

+140
-45
lines changed

10 files changed

+140
-45
lines changed

src/ConnectedMode.UnitTests/SlCoreConnectionAdapterTests.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public async Task ValidateConnectionAsync_SwitchesToBackgroundThread()
6060
var threadHandlingMock = Substitute.For<IThreadHandling>();
6161
var slCoreConnectionAdapter = new SlCoreConnectionAdapter(slCoreServiceProvider, threadHandlingMock, logger);
6262

63-
await slCoreConnectionAdapter.ValidateConnectionAsync(sonarQubeConnectionInfo, "myToken");
63+
await slCoreConnectionAdapter.ValidateConnectionAsync(sonarQubeConnectionInfo, new TokenCredentialsModel("myToken"));
6464

6565
await threadHandlingMock.Received(1).RunOnBackgroundThread(Arg.Any<Func<Task<AdapterResponse>>>());
6666
}
@@ -70,7 +70,7 @@ public async Task ValidateConnectionAsync_GettingConnectionConfigurationSLCoreSe
7070
{
7171
slCoreServiceProvider.TryGetTransientService(out IConnectionConfigurationSLCoreService _).Returns(false);
7272

73-
var response = await testSubject.ValidateConnectionAsync(sonarQubeConnectionInfo, "myToken");
73+
var response = await testSubject.ValidateConnectionAsync(sonarQubeConnectionInfo, new TokenCredentialsModel("myToken"));
7474

7575
logger.Received(1).LogVerbose($"[{nameof(IConnectionConfigurationSLCoreService)}] {SLCoreStrings.ServiceProviderNotInitialized}");
7676
response.Success.Should().BeFalse();
@@ -81,7 +81,7 @@ public async Task ValidateConnectionAsync_ConnectionToSonarQubeWithToken_CallsVa
8181
{
8282
var token = "myToken";
8383

84-
await testSubject.ValidateConnectionAsync(sonarQubeConnectionInfo, token);
84+
await testSubject.ValidateConnectionAsync(sonarQubeConnectionInfo, new TokenCredentialsModel(token));
8585

8686
await connectionConfigurationSlCoreService.Received(1)
8787
.ValidateConnectionAsync(Arg.Is<ValidateConnectionParams>(x => IsExpectedSonarQubeConnectionParams(x, token)));
@@ -93,7 +93,7 @@ public async Task ValidateConnectionAsync_ConnectionToSonarQubeWithCredentials_C
9393
var username = "username";
9494
var password = "password";
9595

96-
await testSubject.ValidateConnectionAsync(sonarQubeConnectionInfo, username, password);
96+
await testSubject.ValidateConnectionAsync(sonarQubeConnectionInfo, new UsernamePasswordModel(username, password));
9797

9898
await connectionConfigurationSlCoreService.Received(1)
9999
.ValidateConnectionAsync(Arg.Is<ValidateConnectionParams>(x => IsExpectedSonarQubeConnectionParams(x, username, password)));
@@ -104,7 +104,7 @@ public async Task ValidateConnectionAsync_ConnectionToSonarCloudWithToken_CallsV
104104
{
105105
var token = "myToken";
106106

107-
await testSubject.ValidateConnectionAsync(sonarCloudConnectionInfo, token);
107+
await testSubject.ValidateConnectionAsync(sonarCloudConnectionInfo, new TokenCredentialsModel(token));
108108

109109
await connectionConfigurationSlCoreService.Received(1)
110110
.ValidateConnectionAsync(Arg.Is<ValidateConnectionParams>(x => IsExpectedSonarCloudConnectionParams(x, token)));
@@ -116,7 +116,7 @@ public async Task ValidateConnectionAsync_ConnectionToSonarCloudWithCredentials_
116116
var username = "username";
117117
var password = "password";
118118

119-
await testSubject.ValidateConnectionAsync(sonarCloudConnectionInfo, username, password);
119+
await testSubject.ValidateConnectionAsync(sonarCloudConnectionInfo, new UsernamePasswordModel(username, password));
120120

121121
await connectionConfigurationSlCoreService.Received(1)
122122
.ValidateConnectionAsync(Arg.Is<ValidateConnectionParams>(x => IsExpectedSonarCloudConnectionParams(x, username, password)));
@@ -130,7 +130,7 @@ public async Task ValidateConnectionAsync_ReturnsResponseFromSlCore(bool success
130130
var expectedResponse = new ValidateConnectionResponse(success, message);
131131
connectionConfigurationSlCoreService.ValidateConnectionAsync(Arg.Any<ValidateConnectionParams>()).Returns(expectedResponse);
132132

133-
var response = await testSubject.ValidateConnectionAsync(sonarCloudConnectionInfo, "token");
133+
var response = await testSubject.ValidateConnectionAsync(sonarCloudConnectionInfo, new TokenCredentialsModel("myToken"));
134134

135135
response.Success.Should().Be(success);
136136
}
@@ -142,7 +142,7 @@ public async Task ValidateConnectionAsync_SlCoreValidationThrowsException_Return
142142
connectionConfigurationSlCoreService.When(x => x.ValidateConnectionAsync(Arg.Any<ValidateConnectionParams>()))
143143
.Do(x => throw new Exception(exceptionMessage));
144144

145-
var response = await testSubject.ValidateConnectionAsync(sonarCloudConnectionInfo, "token");
145+
var response = await testSubject.ValidateConnectionAsync(sonarCloudConnectionInfo, new TokenCredentialsModel("token"));
146146

147147
logger.Received(1).LogVerbose($"{Resources.ValidateCredentials_Fails}: {exceptionMessage}");
148148
response.Success.Should().BeFalse();

src/ConnectedMode.UnitTests/UI/Credentials/CredentialsViewModelTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,8 @@ public async Task AdapterValidateConnectionAsync_TokenIsProvided_ShouldValidateC
284284

285285
await testSubject.AdapterValidateConnectionAsync();
286286

287-
await slCoreConnectionAdapter.Received(1).ValidateConnectionAsync(testSubject.ConnectionInfo, testSubject.Token);
287+
await slCoreConnectionAdapter.Received(1).ValidateConnectionAsync(testSubject.ConnectionInfo,
288+
Arg.Is<TokenCredentialsModel>(x => x.Token == testSubject.Token));
288289
}
289290

290291
[TestMethod]
@@ -297,7 +298,8 @@ public async Task AdapterValidateConnectionAsync_CredentialsAreProvided_ShouldVa
297298

298299
await testSubject.AdapterValidateConnectionAsync();
299300

300-
await slCoreConnectionAdapter.Received(1).ValidateConnectionAsync(testSubject.ConnectionInfo, testSubject.Username, testSubject.Password);
301+
await slCoreConnectionAdapter.Received(1).ValidateConnectionAsync(testSubject.ConnectionInfo,
302+
Arg.Is<UsernamePasswordModel>(x => x.Username == testSubject.Username && x.Password == testSubject.Password));
301303
}
302304

303305
[TestMethod]
@@ -368,9 +370,7 @@ public void GetCredentialsModel_SelectedAuthenticationTypeIsCredentials_ReturnsM
368370

369371
private void MockAdapterValidateConnectionAsync(bool success = true)
370372
{
371-
slCoreConnectionAdapter.ValidateConnectionAsync(Arg.Any<ConnectionInfo>(), Arg.Any<string>())
372-
.Returns(new AdapterResponse(success));
373-
slCoreConnectionAdapter.ValidateConnectionAsync(Arg.Any<ConnectionInfo>(), Arg.Any<string>(), Arg.Any<string>())
373+
slCoreConnectionAdapter.ValidateConnectionAsync(Arg.Any<ConnectionInfo>(), Arg.Any<ICredentialsModel>())
374374
.Returns(new AdapterResponse(success));
375375
}
376376
}

src/ConnectedMode.UnitTests/UI/OrganizationSelection/OrganizationSelectionViewModelTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020

2121
using System.ComponentModel;
22+
using Microsoft.VisualStudio.Threading;
2223
using SonarLint.VisualStudio.ConnectedMode.UI;
2324
using SonarLint.VisualStudio.ConnectedMode.UI.Credentials;
2425
using SonarLint.VisualStudio.ConnectedMode.UI.OrganizationSelection;
@@ -40,6 +41,7 @@ public void TestInitialize()
4041
credentialsModel = Substitute.For<ICredentialsModel>();
4142
slCoreConnectionAdapter = Substitute.For<ISlCoreConnectionAdapter>();
4243
progressReporterViewModel = Substitute.For<IProgressReporterViewModel>();
44+
progressReporterViewModel.ExecuteTaskWithProgressAsync(Arg.Any<ITaskToPerformParams<AdapterResponse>>()).Returns(new AdapterResponse(true));
4345

4446
testSubject = new(credentialsModel, slCoreConnectionAdapter, progressReporterViewModel);
4547
}
@@ -61,6 +63,12 @@ public void Ctor_OrganizationList_SetsPropertyValue()
6163
testSubject.Organizations[0].Should().Be(organization);
6264
}
6365

66+
[TestMethod]
67+
public void FinalConnectionInfo_SetByDefaultToNull()
68+
{
69+
testSubject.FinalConnectionInfo.Should().BeNull();
70+
}
71+
6472
[TestMethod]
6573
public void SelectedOrganization_NotSet_ValueIsNull()
6674
{
@@ -213,4 +221,48 @@ public void UpdateOrganizations_RaisesEvents()
213221
eventHandler.Received().Invoke(testSubject,
214222
Arg.Is<PropertyChangedEventArgs>(x => x.PropertyName == nameof(testSubject.NoOrganizationExists)));
215223
}
224+
225+
[TestMethod]
226+
[DataRow(true)]
227+
[DataRow(false)]
228+
public async Task ValidateConnectionForOrganizationAsync_ReturnsResponseFromSlCore(bool success)
229+
{
230+
progressReporterViewModel.ExecuteTaskWithProgressAsync(Arg.Any<ITaskToPerformParams<AdapterResponse>>()).Returns(new AdapterResponse(success));
231+
232+
var response = await testSubject.ValidateConnectionForOrganizationAsync("key","warning");
233+
234+
response.Should().Be(success);
235+
}
236+
237+
[TestMethod]
238+
public async Task ValidateConnectionForOrganizationAsync_CallsExecuteTaskWithProgressAsync()
239+
{
240+
var organizationKey = "key";
241+
var warningText = "warning";
242+
243+
await testSubject.ValidateConnectionForOrganizationAsync(organizationKey, warningText);
244+
245+
await progressReporterViewModel.Received(1)
246+
.ExecuteTaskWithProgressAsync(Arg.Is<ITaskToPerformParams<AdapterResponse>>(x =>
247+
IsExpectedSlCoreAdapterValidateConnectionAsync(x.TaskToPerform, organizationKey) &&
248+
x.ProgressStatus == UiResources.ValidatingConnectionProgressText &&
249+
x.WarningText == warningText));
250+
}
251+
252+
[TestMethod]
253+
public void UpdateFinalConnectionInfo_ValueChanges_UpdatesConnectionInfo()
254+
{
255+
testSubject.UpdateFinalConnectionInfo("newKey");
256+
257+
testSubject.FinalConnectionInfo.Should().NotBeNull();
258+
testSubject.FinalConnectionInfo.Id.Should().Be("newKey");
259+
testSubject.FinalConnectionInfo.ServerType.Should().Be(ConnectionServerType.SonarCloud);
260+
}
261+
262+
private bool IsExpectedSlCoreAdapterValidateConnectionAsync(Func<Task<AdapterResponse>> xTaskToPerform, string organizationKey)
263+
{
264+
xTaskToPerform().Forget();
265+
slCoreConnectionAdapter.Received(1).ValidateConnectionAsync(Arg.Is<ConnectionInfo>(x=> x.Id == organizationKey), Arg.Any<ICredentialsModel>());
266+
return true;
267+
}
216268
}

src/ConnectedMode/SlCoreConnectionAdapter.cs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
using SonarLint.VisualStudio.ConnectedMode.UI;
2323
using SonarLint.VisualStudio.ConnectedMode.UI.Credentials;
2424
using SonarLint.VisualStudio.ConnectedMode.UI.OrganizationSelection;
25-
using SonarLint.VisualStudio.ConnectedMode.UI.Resources;
2625
using SonarLint.VisualStudio.Core;
2726
using SonarLint.VisualStudio.SLCore;
2827
using SonarLint.VisualStudio.SLCore.Common.Models;
@@ -35,8 +34,7 @@ namespace SonarLint.VisualStudio.ConnectedMode;
3534

3635
public interface ISlCoreConnectionAdapter
3736
{
38-
Task<AdapterResponse> ValidateConnectionAsync(ConnectionInfo connectionInfo, string token);
39-
Task<AdapterResponse> ValidateConnectionAsync(ConnectionInfo connectionInfo, string username, string password);
37+
Task<AdapterResponse> ValidateConnectionAsync(ConnectionInfo connectionInfo, ICredentialsModel credentialsModel);
4038
Task<AdapterResponseWithData<List<OrganizationDisplay>>> GetOrganizationsAsync(ICredentialsModel credentialsModel);
4139
}
4240

@@ -68,15 +66,10 @@ public SlCoreConnectionAdapter(ISLCoreServiceProvider serviceProvider, IThreadHa
6866
this.logger = logger;
6967
}
7068

71-
public async Task<AdapterResponse> ValidateConnectionAsync(ConnectionInfo connectionInfo, string token)
69+
public async Task<AdapterResponse> ValidateConnectionAsync(ConnectionInfo connectionInfo, ICredentialsModel credentialsModel)
7270
{
73-
var validateConnectionParams = GetValidateConnectionParams(connectionInfo, GetEitherForToken(token));
74-
return await ValidateConnectionAsync(validateConnectionParams);
75-
}
76-
77-
public async Task<AdapterResponse> ValidateConnectionAsync(ConnectionInfo connectionInfo, string username, string password)
78-
{
79-
var validateConnectionParams = GetValidateConnectionParams(connectionInfo, GetEitherForUsernamePassword(username, password));
71+
var credentials = GetCredentialsDto(credentialsModel);
72+
var validateConnectionParams = GetValidateConnectionParams(connectionInfo, credentials);
8073
return await ValidateConnectionAsync(validateConnectionParams);
8174
}
8275

src/ConnectedMode/UI/Credentials/CredentialsViewModel.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,7 @@ internal async Task<bool> ValidateConnectionAsync()
118118

119119
internal async Task<AdapterResponse> AdapterValidateConnectionAsync()
120120
{
121-
if (IsTokenAuthentication)
122-
{
123-
return await slCoreConnectionAdapter.ValidateConnectionAsync(ConnectionInfo, Token);
124-
}
125-
return await slCoreConnectionAdapter.ValidateConnectionAsync(ConnectionInfo, Username, Password);
121+
return await slCoreConnectionAdapter.ValidateConnectionAsync(ConnectionInfo, GetCredentialsModel());
126122
}
127123

128124
internal void AfterProgressStatusUpdated()

src/ConnectedMode/UI/ManageConnections/ManageConnectionsDialog.xaml.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,8 @@ private ConnectionInfo FinalizeConnection(ConnectionInfo newConnectionInfo, Cred
9191
}
9292

9393
var organizationSelectionDialog = new OrganizationSelectionDialog(connectedModeServices, credentialsDialog.ViewModel.GetCredentialsModel());
94-
if (organizationSelectionDialog.ShowDialog(this) == true)
95-
{
96-
return newConnectionInfo with { Id = organizationSelectionDialog.ViewModel.SelectedOrganization.Key };
97-
}
98-
return null;
94+
95+
return organizationSelectionDialog.ShowDialog(this) == true ? organizationSelectionDialog.ViewModel.FinalConnectionInfo : null;
9996
}
10097

10198
private void ManageConnectionsWindow_OnInitialized(object sender, EventArgs e)

src/ConnectedMode/UI/OrganizationSelection/OrganizationSelectionDialog.xaml.cs

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,42 +20,71 @@
2020

2121
using System.Diagnostics.CodeAnalysis;
2222
using System.Windows;
23+
using Microsoft.VisualStudio;
2324
using SonarLint.VisualStudio.ConnectedMode.UI.Credentials;
25+
using SonarLint.VisualStudio.ConnectedMode.UI.Resources;
2426

2527
namespace SonarLint.VisualStudio.ConnectedMode.UI.OrganizationSelection;
2628

2729
[ExcludeFromCodeCoverage]
2830
public partial class OrganizationSelectionDialog : Window
2931
{
32+
private readonly IConnectedModeServices connectedModeServices;
33+
3034
public OrganizationSelectionDialog(IConnectedModeServices connectedModeServices, ICredentialsModel credentialsModel)
3135
{
36+
this.connectedModeServices = connectedModeServices;
3237
ViewModel = new OrganizationSelectionViewModel(credentialsModel, connectedModeServices.SlCoreConnectionAdapter, new ProgressReporterViewModel());
3338
InitializeComponent();
3439
}
3540

3641
public OrganizationSelectionViewModel ViewModel { get; }
3742

38-
private void OkButton_OnClick(object sender, RoutedEventArgs e)
43+
private async void OkButton_OnClick(object sender, RoutedEventArgs e)
3944
{
40-
DialogResult = true;
45+
await UpdateFinalConnectionInfoAsync(ViewModel.SelectedOrganization.Key);
4146
}
4247

43-
private void ChooseAnotherOrganizationButton_OnClick(object sender, RoutedEventArgs e)
48+
private async void ChooseAnotherOrganizationButton_OnClick(object sender, RoutedEventArgs e)
4449
{
4550
ViewModel.SelectedOrganization = null;
4651
var manualOrganizationSelectionDialog = new ManualOrganizationSelectionDialog();
47-
manualOrganizationSelectionDialog.Owner = this;
48-
var manualSelection = manualOrganizationSelectionDialog.ShowDialog(this);
49-
ViewModel.SelectedOrganization =new OrganizationDisplay(manualOrganizationSelectionDialog.ViewModel.OrganizationKey, null);
50-
if (manualSelection is true)
52+
var manualSelectionDialogSucceeded = manualOrganizationSelectionDialog.ShowDialog(this);
53+
if(manualSelectionDialogSucceeded is not true)
54+
{
55+
return;
56+
}
57+
58+
await UpdateFinalConnectionInfoAsync(manualOrganizationSelectionDialog.ViewModel.OrganizationKey);
59+
}
60+
61+
private async Task<bool> ValidateConnectionForSelectedOrganizationAsync(string selectedOrganizationKey)
62+
{
63+
try
5164
{
52-
DialogResult = true;
65+
var organizationSelectionInvalidMsg = string.Format(UiResources.ValidatingOrganziationSelectionFailedText, selectedOrganizationKey);
66+
return await ViewModel.ValidateConnectionForOrganizationAsync(selectedOrganizationKey, organizationSelectionInvalidMsg);
67+
}
68+
catch (Exception e) when (!ErrorHandler.IsCriticalException(e))
69+
{
70+
connectedModeServices.Logger.WriteLine(e.ToString());
71+
return false;
5372
}
5473
}
5574

5675
private async void OrganizationSelectionDialog_OnLoaded(object sender, RoutedEventArgs e)
5776
{
5877
await ViewModel.LoadOrganizationsAsync();
5978
}
79+
80+
private async Task UpdateFinalConnectionInfoAsync(string organizationKey)
81+
{
82+
var isConnectionValid = await ValidateConnectionForSelectedOrganizationAsync(organizationKey);
83+
if (isConnectionValid)
84+
{
85+
ViewModel.UpdateFinalConnectionInfo(organizationKey);
86+
DialogResult = true;
87+
}
88+
}
6089
}
6190

src/ConnectedMode/UI/OrganizationSelection/OrganizationSelectionViewModel.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,13 @@
2222
using SonarLint.VisualStudio.ConnectedMode.UI.Credentials;
2323
using SonarLint.VisualStudio.ConnectedMode.UI.Resources;
2424
using SonarLint.VisualStudio.Core.WPF;
25-
using static SonarLint.VisualStudio.ConnectedMode.UI.ProgressReporterViewModel;
2625

2726
namespace SonarLint.VisualStudio.ConnectedMode.UI.OrganizationSelection;
2827

2928
public class OrganizationSelectionViewModel(ICredentialsModel credentialsModel, ISlCoreConnectionAdapter connectionAdapter, IProgressReporterViewModel progressReporterViewModel) : ViewModelBase
3029
{
30+
public ConnectionInfo FinalConnectionInfo { get; private set; }
3131
public IProgressReporterViewModel ProgressReporterViewModel { get; } = progressReporterViewModel;
32-
private OrganizationDisplay selectedOrganization;
3332

3433
public OrganizationDisplay SelectedOrganization
3534
{
@@ -43,10 +42,11 @@ public OrganizationDisplay SelectedOrganization
4342
}
4443

4544
public bool IsValidSelectedOrganization => SelectedOrganization is { Key: var key } && !string.IsNullOrWhiteSpace(key);
46-
4745
public ObservableCollection<OrganizationDisplay> Organizations { get; } = [];
4846
public bool NoOrganizationExists => Organizations.Count == 0;
4947

48+
private OrganizationDisplay selectedOrganization;
49+
5050
public void AddOrganization(OrganizationDisplay organization)
5151
{
5252
Organizations.Add(organization);
@@ -76,4 +76,20 @@ internal void UpdateOrganizations(AdapterResponseWithData<List<OrganizationDispl
7676
responseWithData.ResponseData.ForEach(AddOrganization);
7777
RaisePropertyChanged(nameof(NoOrganizationExists));
7878
}
79+
80+
internal async Task<bool> ValidateConnectionForOrganizationAsync(string organizationKey, string warningText)
81+
{
82+
var connectionInfoToValidate = new ConnectionInfo(organizationKey, ConnectionServerType.SonarCloud);
83+
var validationParams = new TaskToPerformParams<AdapterResponse>(
84+
async () => await connectionAdapter.ValidateConnectionAsync(connectionInfoToValidate, credentialsModel),
85+
UiResources.ValidatingConnectionProgressText,
86+
warningText);
87+
var adapterResponse = await ProgressReporterViewModel.ExecuteTaskWithProgressAsync(validationParams);
88+
return adapterResponse.Success;
89+
}
90+
91+
public void UpdateFinalConnectionInfo(string organizationKey)
92+
{
93+
FinalConnectionInfo = new ConnectionInfo(organizationKey, ConnectionServerType.SonarCloud);
94+
}
7995
}

src/ConnectedMode/UI/Resources/UiResources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ConnectedMode/UI/Resources/UiResources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,4 +348,7 @@
348348
<data name="LoadingOrganizationsProgressText" xml:space="preserve">
349349
<value>Loading organizations...</value>
350350
</data>
351+
<data name="ValidatingOrganziationSelectionFailedText" xml:space="preserve">
352+
<value>The connection is not valid for the chosen organization "{0}". Make sure the entered key matches exactly your organization's key.</value>
353+
</data>
351354
</root>

0 commit comments

Comments
 (0)