Skip to content

Commit

Permalink
SLVS-1565 User should be able to update the credentials (#5785)
Browse files Browse the repository at this point in the history
  • Loading branch information
vnaskos-sonar authored Oct 29, 2024
1 parent 83ccf37 commit 3bc0fff
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ public void TryAddConnection_TokenCredentialsModel_MapsCredentials()
testSubject.TryAddConnection(sonarQube, new TokenCredentialsModel(token.CreateSecureString()));

serverConnectionsRepository.Received(1)
.TryAdd(Arg.Is<ServerConnection.SonarQube>(sc => IsExpectedCredentials(sc, token, string.Empty)));
.TryAdd(Arg.Is<ServerConnection.SonarQube>(sq => IsExpectedCredentials(sq.Credentials, token, string.Empty)));
}

[TestMethod]
Expand All @@ -222,7 +222,7 @@ public void TryAddConnection_UsernamePasswordModel_MapsCredentials()
testSubject.TryAddConnection(sonarQube, new UsernamePasswordModel(username, password.CreateSecureString()));

serverConnectionsRepository.Received(1)
.TryAdd(Arg.Is<ServerConnection.SonarQube>(sc => IsExpectedCredentials(sc, username, password)));
.TryAdd(Arg.Is<ServerConnection.SonarQube>(sq => IsExpectedCredentials(sq.Credentials, username, password)));
}

[TestMethod]
Expand All @@ -232,7 +232,77 @@ public void TryAddConnection_NullCredentials_TriesAddingAConnectionWithNoCredent

testSubject.TryAddConnection(sonarQube, null);

serverConnectionsRepository.Received(1).TryAdd(Arg.Is<ServerConnection.SonarQube>(sc => sc.Credentials == null));
serverConnectionsRepository.Received(1).TryAdd(Arg.Is<ServerConnection.SonarQube>(sq => sq.Credentials == null));
}

[TestMethod]
[DataRow(true)]
[DataRow(false)]
public void TryUpdateCredentials_ReturnsStatusFromSlCore(bool slCoreResponse)
{
var sonarCloud = CreateSonarCloudConnection();
serverConnectionsRepository.TryUpdateCredentialsById(Arg.Any<string>(), Arg.Any<ICredentials>()).Returns(slCoreResponse);

var succeeded = testSubject.TryUpdateCredentials(sonarCloud, Substitute.For<ICredentialsModel>());

succeeded.Should().Be(slCoreResponse);
}

[TestMethod]
public void TryUpdateCredentials_TokenCredentialsModel_MapsCredentials()
{
var sonarQube = CreateSonarQubeConnection();
const string token = "myToken";

testSubject.TryUpdateCredentials(sonarQube, new TokenCredentialsModel(token.CreateSecureString()));

serverConnectionsRepository.Received(1)
.TryUpdateCredentialsById(Arg.Any<string>(), Arg.Is<ICredentials>(x => IsExpectedCredentials(x, token, string.Empty)));
}

[TestMethod]
public void TryUpdateCredentials_UserPasswordModel_MapsCredentials()
{
var sonarQube = CreateSonarQubeConnection();
const string username = "username";
const string password = "password";

testSubject.TryUpdateCredentials(sonarQube, new UsernamePasswordModel(username, password.CreateSecureString()));

serverConnectionsRepository.Received(1)
.TryUpdateCredentialsById(Arg.Any<string>(), Arg.Is<ICredentials>(x => IsExpectedCredentials(x, username, password)));
}

[TestMethod]
public void TryUpdateCredentials_SonarQube_MapsConnection()
{
var sonarQube = CreateSonarQubeConnection();

testSubject.TryUpdateCredentials(sonarQube, Substitute.For<ICredentialsModel>());

serverConnectionsRepository.Received(1)
.TryUpdateCredentialsById(Arg.Is<string>(x => x.Equals(sonarQube.Info.Id)), Arg.Any<ICredentials>());
}

[TestMethod]
public void TryUpdateCredentials_SonarCloud_MapsConnection()
{
var sonarCloud = CreateSonarCloudConnection();

testSubject.TryUpdateCredentials(sonarCloud, Substitute.For<ICredentialsModel>());

serverConnectionsRepository.Received(1)
.TryUpdateCredentialsById(Arg.Is<string>(x => x.EndsWith(sonarCloud.Info.Id)), Arg.Any<ICredentials>());
}

[TestMethod]
public void TryUpdateCredentials_NullCredentials_TriesUpdatingConnectionWithNoCredentials()
{
var sonarQube = CreateSonarQubeConnection();

testSubject.TryUpdateCredentials(sonarQube, null);

serverConnectionsRepository.Received(1).TryUpdateCredentialsById(Arg.Any<string>(), Arg.Is<ICredentials>(x => x == null));
}

[TestMethod]
Expand Down Expand Up @@ -292,12 +362,12 @@ private static Connection CreateSonarCloudConnection(bool enableSmartNotificatio

private static Connection CreateSonarQubeConnection(bool enableSmartNotifications = true)
{
return new Connection(new ConnectionInfo("http://localhost:9000", ConnectionServerType.SonarQube), enableSmartNotifications);
return new Connection(new ConnectionInfo("http://localhost:9000/", ConnectionServerType.SonarQube), enableSmartNotifications);
}

private static bool IsExpectedCredentials(ServerConnection.SonarQube sc, string expectedUsername, string expectedPassword)
private static bool IsExpectedCredentials(ICredentials credentials, string expectedUsername, string expectedPassword)
{
return sc.Credentials is BasicAuthCredentials basicAuthCredentials && basicAuthCredentials.UserName == expectedUsername && basicAuthCredentials.Password?.ToUnsecureString() == expectedPassword;
return credentials is BasicAuthCredentials basicAuthCredentials && basicAuthCredentials.UserName == expectedUsername && basicAuthCredentials.Password?.ToUnsecureString() == expectedPassword;
}

private void MockTryGet(string connectionId, bool expectedResponse, ServerConnection expectedServerConnection)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,43 @@ public void CreateNewConnection_ConnectionWasNotAddedToRepository_DoesNotAddConn
testSubject.ConnectionViewModels.Should().BeEmpty();
serverConnectionsRepositoryAdapter.Received(1).TryAddConnection(connectionToAdd, Arg.Any<ICredentialsModel>());
}

[TestMethod]
public async Task UpdateConnectionCredentialsWithProgressAsync_InitializesDataAndReportsProgress()
{
var connectionToUpdate = CreateSonarCloudConnection();

await testSubject.UpdateConnectionCredentialsWithProgressAsync(connectionToUpdate, Substitute.For<ICredentialsModel>());

await progressReporterViewModel.Received(1)
.ExecuteTaskWithProgressAsync(
Arg.Is<TaskToPerformParams<AdapterResponse>>(x =>
x.ProgressStatus == UiResources.UpdatingConnectionCredentialsProgressText &&
x.WarningText == UiResources.UpdatingConnectionCredentialsFailedText));
}

[TestMethod]
public void UpdateConnectionCredentials_UpdatesProvidedConnection()
{
var connectionToUpdate = CreateSonarCloudConnection();
var credentials = Substitute.For<ICredentialsModel>();
serverConnectionsRepositoryAdapter.TryUpdateCredentials(connectionToUpdate, credentials).Returns(true);

var succeeded = testSubject.UpdateConnectionCredentials(connectionToUpdate, credentials);

succeeded.Should().BeTrue();
serverConnectionsRepositoryAdapter.Received(1).TryUpdateCredentials(connectionToUpdate, credentials);
}

[TestMethod]
public void UpdateConnectionCredentials_ConnectionIsNull_DoesNotUpdateConnection()
{
var succeeded = testSubject.UpdateConnectionCredentials(null, Substitute.For<ICredentialsModel>());

succeeded.Should().BeFalse();
serverConnectionsRepositoryAdapter.DidNotReceive().TryUpdateCredentials(Arg.Any<Connection>(), Arg.Any<ICredentialsModel>());
}

[TestMethod]
public async Task GetConnectionReferencesWithProgressAsync_CalculatesReferencesAndReportsProgress()
{
Expand Down
10 changes: 8 additions & 2 deletions src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public interface IServerConnectionsRepositoryAdapter
bool TryGetAllConnectionsInfo(out List<ConnectionInfo> connectionInfos);
bool TryRemoveConnection(ConnectionInfo connectionInfo);
bool TryAddConnection(Connection connection, ICredentialsModel credentialsModel);
bool TryUpdateCredentials(Connection connection, ICredentialsModel credentialsModel);
bool TryGet(ConnectionInfo connectionInfo, out ServerConnection serverConnection);
}

Expand Down Expand Up @@ -60,6 +61,13 @@ public bool TryAddConnection(Connection connection, ICredentialsModel credential
serverConnection.Credentials = MapCredentials(credentialsModel);
return serverConnectionsRepository.TryAdd(serverConnection);
}

public bool TryUpdateCredentials(Connection connection, ICredentialsModel credentialsModel)
{
var serverConnection = MapConnection(connection);
serverConnection.Credentials = MapCredentials(credentialsModel);
return serverConnectionsRepository.TryUpdateCredentialsById(serverConnection.Id, serverConnection.Credentials);
}

public bool TryGet(ConnectionInfo connectionInfo, out ServerConnection serverConnection)
{
Expand Down Expand Up @@ -96,9 +104,7 @@ private static ICredentials MapCredentials(ICredentialsModel credentialsModel)
case TokenCredentialsModel tokenCredentialsModel:
return new BasicAuthCredentials(tokenCredentialsModel.Token.ToUnsecureString(), new SecureString());
case UsernamePasswordModel usernameCredentialsModel:
{
return new BasicAuthCredentials(usernameCredentialsModel.Username, usernameCredentialsModel.Password);
}
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</ResourceDictionary>
</Window.Resources>
<Window.Style>
<Style TargetType="Window" BasedOn="{StaticResource ConnectedModeWindow}"></Style>
<Style TargetType="Window" BasedOn="{StaticResource ConnectedModeWindow}" />
</Window.Style>

<Grid DataContext="{Binding ElementName=This, Path=ViewModel}" Margin="10">
Expand Down Expand Up @@ -66,10 +66,10 @@
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ServerType}" Value="SonarCloud">
<Setter Property="Source" Value="{StaticResource SonarCloudIconDrawingImage}"></Setter>
<Setter Property="Source" Value="{StaticResource SonarCloudIconDrawingImage}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=ServerType}" Value="SonarQube">
<Setter Property="Source" Value="{StaticResource SonarQubeIconDrawingImage}"></Setter>
<Setter Property="Source" Value="{StaticResource SonarQubeIconDrawingImage}" />
</DataTrigger>
</Style.Triggers>
</Style>
Expand All @@ -92,7 +92,7 @@
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>

<Button Grid.Column="0" Style="{StaticResource IconButtonStyle}" ToolTip="{x:Static res:UiResources.EditConnectionToolTip}" Click="EditConnection_Clicked" Visibility="Collapsed">
<Button Grid.Column="0" Style="{StaticResource IconButtonStyle}" ToolTip="{x:Static res:UiResources.EditConnectionToolTip}" Click="EditConnection_Clicked">
<imaging:CrispImage Width="20" Height="20" VerticalAlignment="Center" Cursor="Hand" Moniker="{x:Static vsimagecatalog:KnownMonikers.Settings}"
Margin="10,0" />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

using System.Diagnostics.CodeAnalysis;
using System.Windows;
using System.Windows.Controls;
using SonarLint.VisualStudio.ConnectedMode.UI.Credentials;
using SonarLint.VisualStudio.ConnectedMode.UI.DeleteConnection;
using SonarLint.VisualStudio.ConnectedMode.UI.OrganizationSelection;
Expand All @@ -43,12 +44,20 @@ public ManageConnectionsDialog(IConnectedModeServices connectedModeServices, ICo
InitializeComponent();
}

private void EditConnection_Clicked(object sender, RoutedEventArgs e)
private async void EditConnection_Clicked(object sender, RoutedEventArgs e)
{
if(sender is System.Windows.Controls.Button button && button.DataContext is ConnectionViewModel connectionViewModel)
if(sender is not Button { DataContext: ConnectionViewModel connectionViewModel })
{
new CredentialsDialog(connectedModeServices, connectionViewModel.Connection.Info, false).ShowDialog(this);
return;
}

var credentialsDialog = new CredentialsDialog(connectedModeServices, connectionViewModel.Connection.Info, false);
if (!CredentialsDialogSucceeded(credentialsDialog))
{
return;
}

await ViewModel.UpdateConnectionCredentialsWithProgressAsync(connectionViewModel.Connection, credentialsDialog.ViewModel.GetCredentialsModel());
}

private async void NewConnection_Clicked(object sender, RoutedEventArgs e)
Expand Down Expand Up @@ -103,7 +112,7 @@ private async void ManageConnectionsWindow_OnInitialized(object sender, EventArg

private async void RemoveConnectionButton_OnClick(object sender, RoutedEventArgs e)
{
if (sender is not System.Windows.Controls.Button { DataContext: ConnectionViewModel connectionViewModel })
if (sender is not Button { DataContext: ConnectionViewModel connectionViewModel })
{
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ internal async Task CreateConnectionsWithProgressAsync(Connection connection, IC
UiResources.CreatingConnectionFailedText);
await ProgressReporterViewModel.ExecuteTaskWithProgressAsync(validationParams);
}

internal async Task UpdateConnectionCredentialsWithProgressAsync(Connection connection, ICredentialsModel credentialsModel)
{
var validationParams = new TaskToPerformParams<AdapterResponse>(
async () => await SafeExecuteActionAsync(() => UpdateConnectionCredentials(connection, credentialsModel)),
UiResources.UpdatingConnectionCredentialsProgressText,
UiResources.UpdatingConnectionCredentialsFailedText);
await ProgressReporterViewModel.ExecuteTaskWithProgressAsync(validationParams);
}

internal async Task<AdapterResponse> SafeExecuteActionAsync(Func<bool> funcToExecute)
{
Expand Down Expand Up @@ -141,6 +150,16 @@ internal bool CreateNewConnection(Connection connection, ICredentialsModel crede
return succeeded;
}

internal bool UpdateConnectionCredentials(Connection connection, ICredentialsModel credentialsModel)
{
if (connection is null)
{
return false;
}

return connectedModeServices.ServerConnectionsRepositoryAdapter.TryUpdateCredentials(connection, credentialsModel);
}

internal void AddConnectionViewModel(Connection connection)
{
ConnectionViewModels.Add(new ConnectionViewModel(connection));
Expand Down
18 changes: 18 additions & 0 deletions src/ConnectedMode/UI/Resources/UiResources.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/UI/Resources/UiResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -422,4 +422,10 @@ Please manually add the credentials for the connection and then try again.</valu
<data name="NoProjectsFoundForSearchTermLabel" xml:space="preserve">
<value>Your search did not match any project</value>
</data>
<data name="UpdatingConnectionCredentialsProgressText" xml:space="preserve">
<value>Updating credentials...</value>
</data>
<data name="UpdatingConnectionCredentialsFailedText" xml:space="preserve">
<value>Failed to update the connection, please make sure that the credentials are correct.</value>
</data>
</root>

0 comments on commit 3bc0fff

Please sign in to comment.