diff --git a/src/main/java/org/sonarlint/intellij/SonarLintIntelliJClient.kt b/src/main/java/org/sonarlint/intellij/SonarLintIntelliJClient.kt index c26b9f796e..bee4f829db 100644 --- a/src/main/java/org/sonarlint/intellij/SonarLintIntelliJClient.kt +++ b/src/main/java/org/sonarlint/intellij/SonarLintIntelliJClient.kt @@ -391,9 +391,9 @@ object SonarLintIntelliJClient : SonarLintClient { override fun getCredentials(params: GetCredentialsParams): CompletableFuture { val connectionId = params.connectionId - return ServerConnectionService.getInstance().getServerConnectionByName(connectionId) - .map { connection -> connection.credentials.token?.let { CompletableFuture.completedFuture(GetCredentialsResponse(TokenDto(it))) } - ?: connection.credentials.login?.let { CompletableFuture.completedFuture(GetCredentialsResponse(UsernamePasswordDto(it, connection.credentials.password))) } + return ServerConnectionService.getInstance().getServerCredentialsByName(connectionId) + .map { credentials -> credentials.token?.let { CompletableFuture.completedFuture(GetCredentialsResponse(TokenDto(it))) } + ?: credentials.login?.let { CompletableFuture.completedFuture(GetCredentialsResponse(UsernamePasswordDto(it, credentials.password))) } ?: CompletableFuture.failedFuture(IllegalArgumentException("Invalid credentials for connection: $connectionId"))} .orElseGet { CompletableFuture.failedFuture(IllegalArgumentException("Connection '$connectionId' not found")) } } diff --git a/src/main/java/org/sonarlint/intellij/config/global/ServerConnection.kt b/src/main/java/org/sonarlint/intellij/config/global/ServerConnection.kt index 1fbc8faf6d..2ed487988c 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/ServerConnection.kt +++ b/src/main/java/org/sonarlint/intellij/config/global/ServerConnection.kt @@ -34,7 +34,6 @@ sealed class ServerConnection { abstract val name: String abstract val notificationsDisabled: Boolean abstract val hostUrl: String - abstract val credentials: ServerConnectionCredentials abstract val product: SonarProduct abstract val links: ServerLinks abstract val endpointParams: EndpointParams @@ -45,14 +44,13 @@ sealed class ServerConnection { val productName get() = product.productName } -data class SonarQubeConnection(override val name: String, override val hostUrl: String, override val credentials: ServerConnectionCredentials, override val notificationsDisabled: Boolean) : ServerConnection() { +data class SonarQubeConnection(override val name: String, override val hostUrl: String, override val notificationsDisabled: Boolean) : ServerConnection() { override val product = SonarProduct.SONARQUBE override val links = SonarQubeLinks(hostUrl) override val endpointParams = EndpointParams(hostUrl, false, null) } -data class SonarCloudConnection(override val name: String, val token: String, val organizationKey: String, override val notificationsDisabled: Boolean) : ServerConnection() { - override val credentials = ServerConnectionCredentials(null, null, token) +data class SonarCloudConnection(override val name: String, val organizationKey: String, override val notificationsDisabled: Boolean) : ServerConnection() { override val product = SonarProduct.SONARCLOUD override val links = SonarCloudLinks override val hostUrl: String = SONARCLOUD_URL diff --git a/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionCredentialsNotFound.kt b/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionCredentialsNotFound.kt new file mode 100644 index 0000000000..e70a736afa --- /dev/null +++ b/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionCredentialsNotFound.kt @@ -0,0 +1,3 @@ +package org.sonarlint.intellij.config.global + +class ServerConnectionCredentialsNotFound(connectionName: String) : RuntimeException("Unable to load credentials for connection '$connectionName'") diff --git a/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionMgmtPanel.java b/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionMgmtPanel.java index 54117320b7..0a38e776d3 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionMgmtPanel.java +++ b/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionMgmtPanel.java @@ -20,6 +20,7 @@ package org.sonarlint.intellij.config.global; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.ui.Messages; @@ -36,8 +37,10 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -53,6 +56,8 @@ import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; import static org.sonarlint.intellij.config.Settings.getSettingsFor; +import static org.sonarlint.intellij.ui.UiUtils.runOnUiThread; +import static org.sonarlint.intellij.util.ThreadUtilsKt.runOnPooledThread; public class ServerConnectionMgmtPanel implements ConfigurationPanel { private static final String LABEL_NO_SERVERS = "Add a connection to SonarQube or SonarCloud"; @@ -64,8 +69,10 @@ public class ServerConnectionMgmtPanel implements ConfigurationPanel connections = new ArrayList<>(); + private final Map updatedConnectionsByName = new HashMap<>(); + private final Map addedConnectionsByName = new HashMap<>(); private final Set deletedServerIds = new HashSet<>(); + private CollectionListModel listModel; private void create() { var app = ApplicationManager.getApplication(); @@ -139,29 +146,35 @@ public JComponent getComponent() { @Override public boolean isModified(SonarLintGlobalSettings settings) { - return !connections.equals(ServerConnectionService.getInstance().getConnections()); + return !updatedConnectionsByName.isEmpty() || !addedConnectionsByName.isEmpty() || !deletedServerIds.isEmpty(); } @Override public void save(SonarLintGlobalSettings newSettings) { - ServerConnectionService.getInstance().setServerConnections(newSettings, connections); - // remove them even if a server with the same name was later added - unbindRemovedServers(); + // use background thread because of credentials save + runOnPooledThread(() -> { + ServerConnectionService.getInstance().updateServerConnections(newSettings, new HashSet<>(deletedServerIds), new ArrayList<>(updatedConnectionsByName.values()), + new ArrayList<>(addedConnectionsByName.values())); + // remove them even if a server with the same name was later added + unbindRemovedServers(); + }); + } @Override public void load(SonarLintGlobalSettings settings) { - connections.clear(); + updatedConnectionsByName.clear(); + addedConnectionsByName.clear(); deletedServerIds.clear(); - var listModel = new CollectionListModel(new ArrayList<>()); + listModel = new CollectionListModel<>(new ArrayList<>()); var serverConnections = ServerConnectionService.getInstance().getConnections(); + listModel.add(serverConnections); - connections.addAll(serverConnections); connectionList.setModel(listModel); - if (!connections.isEmpty()) { - connectionList.setSelectedValue(connections.get(0), true); + if (!serverConnections.isEmpty()) { + connectionList.setSelectedValue(serverConnections.get(0), true); } } @@ -171,7 +184,7 @@ private ServerConnection getSelectedConnection() { } List getConnections() { - return connections; + return listModel.getItems(); } private void editSelectedConnection() { @@ -179,27 +192,56 @@ private void editSelectedConnection() { int selectedIndex = connectionList.getSelectedIndex(); if (selectedConnection != null) { - var serverEditor = ServerConnectionWizard.forConnectionEdition(selectedConnection); - if (serverEditor.showAndGet()) { - var editedConnection = serverEditor.getConnection(); - ((CollectionListModel) connectionList.getModel()).setElementAt(editedConnection, selectedIndex); - connections.set(connections.indexOf(selectedConnection), editedConnection); - connectionChangeListener.changed(connections); - } + var connectionName = selectedConnection.getName(); + runOnPooledThread(() -> { + var previousCredentials = getCredentialsForEdition(connectionName); + runOnUiThread(ModalityState.any(), () -> { + var serverEditor = ServerConnectionWizard.forConnectionEdition(new ServerConnectionWithAuth(selectedConnection, previousCredentials)); + if (serverEditor.showAndGet()) { + var editedConnectionWithAuth = serverEditor.getConnectionWithAuth(); + listModel.setElementAt(editedConnectionWithAuth.getConnection(), selectedIndex); + if (addedConnectionsByName.containsKey(connectionName)) { + addedConnectionsByName.put(connectionName, editedConnectionWithAuth); + } else { + updatedConnectionsByName.put(connectionName, editedConnectionWithAuth); + } + connectionChangeListener.changed(getConnections()); + } + }); + }); + } + } + + private ServerConnectionCredentials getCredentialsForEdition(String connectionName) { + var connection = addedConnectionsByName.get(connectionName); + if (connection != null) { + return connection.getCredentials(); + } + connection = updatedConnectionsByName.get(connectionName); + if (connection != null) { + return connection.getCredentials(); } + return ServerConnectionService.getInstance().getServerCredentialsByName(connectionName) + .orElseThrow(() -> new IllegalStateException("Credentials for connection '" + connectionName + "' not found")); } private class AddConnectionAction implements AnActionButtonRunnable { @Override public void run(AnActionButton anActionButton) { - var existingNames = connections.stream().map(ServerConnection::getName).collect(Collectors.toSet()); + var existingNames = getConnections().stream().map(ServerConnection::getName).collect(Collectors.toSet()); var wizard = ServerConnectionWizard.forNewConnection(existingNames); if (wizard.showAndGet()) { - var created = wizard.getConnection(); - connections.add(created); - ((CollectionListModel) connectionList.getModel()).add(created); - connectionList.setSelectedIndex(connectionList.getModel().getSize() - 1); - connectionChangeListener.changed(connections); + var created = wizard.getConnectionWithAuth(); + var connectionName = created.getConnection().getName(); + if (deletedServerIds.contains(connectionName)) { + updatedConnectionsByName.put(connectionName, created); + deletedServerIds.remove(connectionName); + } else { + addedConnectionsByName.put(connectionName, created); + } + listModel.add(created.getConnection()); + connectionList.setSelectedIndex(listModel.getSize() - 1); + connectionChangeListener.changed(getConnections()); } } } @@ -228,15 +270,18 @@ public void run(AnActionButton anActionButton) { } } - var model = (CollectionListModel) connectionList.getModel(); - // it's not removed from serverIds and editorList - model.remove(connection); - connections.remove(connection); - connectionChangeListener.changed(connections); + listModel.remove(connection); + var connectionName = connection.getName(); + if (!addedConnectionsByName.containsKey(connectionName)) { + deletedServerIds.add(connectionName); + } + addedConnectionsByName.remove(connectionName); + updatedConnectionsByName.remove(connectionName); + connectionChangeListener.changed(getConnections()); - if (model.getSize() > 0) { - var newIndex = Math.min(model.getSize() - 1, Math.max(selectedIndex - 1, 0)); - connectionList.setSelectedValue(model.getElementAt(newIndex), true); + if (listModel.getSize() > 0) { + var newIndex = Math.min(listModel.getSize() - 1, Math.max(selectedIndex - 1, 0)); + connectionList.setSelectedValue(listModel.getElementAt(newIndex), true); } } diff --git a/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionService.kt b/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionService.kt index 9f58941b03..23ecdfb80f 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionService.kt +++ b/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionService.kt @@ -25,22 +25,23 @@ import com.intellij.credentialStore.generateServiceName import com.intellij.ide.passwordSafe.PasswordSafe import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.Service +import com.intellij.util.ui.EDT import java.util.Optional -import kotlinx.collections.immutable.toImmutableList import org.sonarlint.intellij.common.util.SonarLintUtils.getService import org.sonarlint.intellij.config.Settings.getGlobalSettings import org.sonarlint.intellij.messages.ServerConnectionsListener import org.sonarlint.intellij.util.GlobalLogOutput +import org.sonarlint.intellij.util.runOnPooledThread @Service(Service.Level.APP) class ServerConnectionService { init { - loadAndMigrateServerConnections() + loadAndMigrateServerConnectionsAsync() } - private fun loadAndMigrateServerConnections() { - getGlobalSettings().serverConnections.forEach { migrate(it) } + private fun loadAndMigrateServerConnectionsAsync() { + runOnPooledThread { getGlobalSettings().serverConnections.forEach { migrate(it) } } } private fun migrate(connection: ServerConnectionSettings) { @@ -49,16 +50,13 @@ class ServerConnectionService { } fun getConnections(): List = getGlobalSettings().serverConnections.filter { isValid(it) }.mapNotNull { connection -> - val credentials = loadCredentials(connection.name) ?: return@mapNotNull null if (connection.isSonarCloud) { - val token = credentials.token ?: run { - GlobalLogOutput.get().logError("Token not found in secure storage for connection $connection", null) - return@mapNotNull null - } - SonarCloudConnection(connection.name, token, connection.organizationKey!!, connection.isDisableNotifications) - } else SonarQubeConnection(connection.name, connection.hostUrl, credentials, connection.isDisableNotifications) + SonarCloudConnection(connection.name, connection.organizationKey!!, connection.isDisableNotifications) + } else SonarQubeConnection(connection.name, connection.hostUrl, connection.isDisableNotifications) } + private fun getConnectionsWithAuth(): List = getConnections().map { ServerConnectionWithAuth(it, loadCredentials(it.name)) } + private fun isValid(connectionSettings: ServerConnectionSettings): Boolean { val valid = connectionSettings.name != null && if (connectionSettings.isSonarCloud) connectionSettings.organizationKey != null else connectionSettings.hostUrl != null @@ -72,6 +70,14 @@ class ServerConnectionService { return Optional.ofNullable(getConnections().firstOrNull { name == it.name }) } + fun getServerConnectionWithAuthByName(name: String): Optional { + return Optional.ofNullable(getConnectionsWithAuth().firstOrNull { name == it.connection.name }) + } + + fun getServerCredentialsByName(name: String): Optional { + return Optional.ofNullable(loadCredentials(name)) + } + fun connectionExists(connectionName: String): Boolean { return getConnections().any { it.name == connectionName } } @@ -80,64 +86,69 @@ class ServerConnectionService { return getConnections().map { it.name }.toSet() } - fun addServerConnection(connection: ServerConnection) { - setServerConnections(getConnections() + connection) + fun addServerConnection(connection: ServerConnectionWithAuth) { + updateServerConnections(getGlobalSettings(), emptySet(), emptyList(), listOf(connection)) } - fun replaceConnection(name: String, replacementConnection: ServerConnection) { - val serverConnections = getConnections().toMutableList() - serverConnections[serverConnections.indexOfFirst { it.name == name }] = replacementConnection - setServerConnections(serverConnections.toImmutableList()) + fun replaceConnection(replacementConnection: ServerConnectionWithAuth) { + updateServerConnections(getGlobalSettings(), emptySet(), listOf(replacementConnection), emptyList()) } - private fun setServerConnections(connections: List) { - setServerConnections(getGlobalSettings(), connections) + fun updateServerConnections(settings: SonarLintGlobalSettings, deletedConnectionNames: Set, updatedConnectionsWithAuth: Collection, addedConnectionsWithAuth: Collection) { + // save credentials + deletedConnectionNames.forEach { forgetCredentials(it) } + updatedConnectionsWithAuth.forEach { saveCredentials(it.connection.name, it.credentials) } + addedConnectionsWithAuth.forEach { saveCredentials(it.connection.name, it.credentials) } + + // save connections + val currentlySavedConnections = settings.serverConnections.toMutableList() + currentlySavedConnections.removeAll { deletedConnectionNames.contains(it.name) } + updatedConnectionsWithAuth.map { it.connection }.forEach { updatedConnection -> + currentlySavedConnections[currentlySavedConnections.indexOfFirst { it.name == updatedConnection.name }] = toSettings(updatedConnection) + } + currentlySavedConnections.addAll(addedConnectionsWithAuth.map { it.connection }.map { toSettings(it) }) + settings.serverConnections = currentlySavedConnections.toList() + + // notify + notifyConnectionsChange(getConnections()) + notifyCredentialsChange(updatedConnectionsWithAuth.map { it.connection }) } - fun setServerConnections(settings: SonarLintGlobalSettings, connections: List) { - val previousConnections = getConnections() - settings.serverConnections = connections.map { - var builder = ServerConnectionSettings.newBuilder().setName(it.name).setHostUrl(it.hostUrl).setDisableNotifications(it.notificationsDisabled) - if (it is SonarCloudConnection) { - builder = builder.setOrganizationKey(it.organizationKey) + private fun toSettings(serverConnection: ServerConnection): ServerConnectionSettings { + return with(serverConnection) { + var builder = ServerConnectionSettings.newBuilder().setName(this.name).setHostUrl(this.hostUrl).setDisableNotifications(this.notificationsDisabled) + if (this is SonarCloudConnection) { + builder = builder.setOrganizationKey(this.organizationKey) } builder.build() - }.toList() - connections.forEach { saveCredentials(it.name, it.credentials) } - notifyConnectionsChange(connections) - notifyCredentialsChange(previousConnections, connections) - val removedConnectionNames = previousConnections.map { it.name }.filter { name -> !connections.map { it.name }.contains(name) } - removedConnectionNames.forEach { forgetCredentials(it) } + } } private fun notifyConnectionsChange(connections: List) { ApplicationManager.getApplication().messageBus.syncPublisher(ServerConnectionsListener.TOPIC).afterChange(connections) } - private fun notifyCredentialsChange(previousConnections: List, newConnections: List) { - val changedConnections = newConnections.filter { connection -> - val previousConnection = previousConnections.find { it.name == connection.name } - previousConnection?.let { connection.credentials != it.credentials } == true - } - if (changedConnections.isNotEmpty()) { - ApplicationManager.getApplication().messageBus.syncPublisher(ServerConnectionsListener.TOPIC).credentialsChanged(changedConnections) - } + private fun notifyCredentialsChange(serverConnections: List) { + ApplicationManager.getApplication().messageBus.syncPublisher(ServerConnectionsListener.TOPIC).credentialsChanged(serverConnections) } - private fun loadCredentials(connectionId: String): ServerConnectionCredentials? { - val token = PasswordSafe.instance.getPassword(tokenCredentials(connectionId)) + private fun loadCredentials(connectionName: String): ServerConnectionCredentials { + // loading credentials is a slow operation + check(!EDT.isCurrentThreadEdt()) { "Cannot load credentials from EDT" } + val token = PasswordSafe.instance.getPassword(tokenCredentials(connectionName)) if (token != null) { return ServerConnectionCredentials(null, null, token) } - val loginPassword = PasswordSafe.instance[loginPasswordCredentials(connectionId)] + val loginPassword = PasswordSafe.instance[loginPasswordCredentials(connectionName)] if (loginPassword != null) { return ServerConnectionCredentials(loginPassword.userName, loginPassword.password.toString(), null) } - GlobalLogOutput.get().logError("Unable to retrieve credentials from secure storage for connection '$connectionId'", null) - return null + throw ServerConnectionCredentialsNotFound(connectionName) } private fun saveCredentials(connectionId: String, credentials: ServerConnectionCredentials) { + // saving credentials is a slow operation + check(!EDT.isCurrentThreadEdt()) { "Cannot save credentials from EDT" } if (credentials.token != null) { PasswordSafe.instance.setPassword(tokenCredentials(connectionId), credentials.token) } else if (credentials.login != null && credentials.password != null) { @@ -147,6 +158,8 @@ class ServerConnectionService { } private fun forgetCredentials(connectionId: String) { + // saving credentials is a slow operation + check(!EDT.isCurrentThreadEdt()) { "Cannot save credentials from EDT" } PasswordSafe.instance[tokenCredentials(connectionId)] = null } diff --git a/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionWithAuth.kt b/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionWithAuth.kt new file mode 100644 index 0000000000..03a0ac30da --- /dev/null +++ b/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionWithAuth.kt @@ -0,0 +1,23 @@ +/* + * SonarLint for IntelliJ IDEA + * Copyright (C) 2015-2023 SonarSource + * sonarlint@sonarsource.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonarlint.intellij.config.global + + +data class ServerConnectionWithAuth(val connection: ServerConnection, val credentials: ServerConnectionCredentials) diff --git a/src/main/java/org/sonarlint/intellij/config/global/wizard/ServerConnectionCreator.kt b/src/main/java/org/sonarlint/intellij/config/global/wizard/ServerConnectionCreator.kt index 650105a95e..274d4eeb40 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/wizard/ServerConnectionCreator.kt +++ b/src/main/java/org/sonarlint/intellij/config/global/wizard/ServerConnectionCreator.kt @@ -21,6 +21,7 @@ package org.sonarlint.intellij.config.global.wizard import org.sonarlint.intellij.config.global.ServerConnection import org.sonarlint.intellij.config.global.ServerConnectionService +import org.sonarlint.intellij.util.runOnPooledThread open class ServerConnectionCreator { @@ -28,9 +29,9 @@ open class ServerConnectionCreator { val serverConnectionService = ServerConnectionService.getInstance() val wizard = ServerConnectionWizard.forNewConnection(serverUrl, serverConnectionService.getServerNames()) if (wizard.showAndGet()) { - val created = wizard.connection - serverConnectionService.addServerConnection(created) - return created + val created = wizard.connectionWithAuth + runOnPooledThread { serverConnectionService.addServerConnection(created) } + return created.connection } return null } diff --git a/src/main/java/org/sonarlint/intellij/config/global/wizard/ServerConnectionWizard.java b/src/main/java/org/sonarlint/intellij/config/global/wizard/ServerConnectionWizard.java index c19560611f..1306177721 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/wizard/ServerConnectionWizard.java +++ b/src/main/java/org/sonarlint/intellij/config/global/wizard/ServerConnectionWizard.java @@ -25,7 +25,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import org.sonarlint.intellij.config.global.ServerConnection; +import org.sonarlint.intellij.config.global.ServerConnectionWithAuth; import org.sonarlint.intellij.documentation.SonarLintDocumentation; public class ServerConnectionWizard { @@ -50,14 +50,14 @@ public static ServerConnectionWizard forNewConnection(String serverUrl, Set connections) { - ServerConnectionService.getInstance().setServerConnections(getGlobalSettings(), connections); + ServerConnectionService.getInstance().updateServerConnections(getGlobalSettings(), ServerConnectionService.getInstance().getConnections().stream().map(ServerConnection::getName).collect(Collectors.toSet()), + Collections.emptyList(), connections.stream().map(connection -> new ServerConnectionWithAuth(connection, new ServerConnectionCredentials(null, null, "token"))).collect(Collectors.toList())); } protected void connectProjectTo(ServerConnection connection, String projectKey) { - ServerConnectionService.getInstance().addServerConnection(connection); + ServerConnectionService.getInstance().addServerConnection(new ServerConnectionWithAuth(connection, new ServerConnectionCredentials(null, null, "token"))); getProjectSettings().bindTo(connection, projectKey); } diff --git a/src/test/java/org/sonarlint/intellij/config/global/wizard/WizardModelTests.kt b/src/test/java/org/sonarlint/intellij/config/global/wizard/WizardModelTests.kt index cafadaf581..5c24697d59 100644 --- a/src/test/java/org/sonarlint/intellij/config/global/wizard/WizardModelTests.kt +++ b/src/test/java/org/sonarlint/intellij/config/global/wizard/WizardModelTests.kt @@ -21,12 +21,14 @@ package org.sonarlint.intellij.config.global.wizard import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import org.sonarlint.intellij.config.global.ServerConnectionCredentials +import org.sonarlint.intellij.config.global.ServerConnectionWithAuth import org.sonarlint.intellij.config.global.SonarCloudConnection internal class WizardModelTests { @Test fun testCreateFromConfig() { - val connection = SonarCloudConnection("name", "token", "org", false) + val connection = ServerConnectionWithAuth(SonarCloudConnection("name", "org", false), ServerConnectionCredentials(null, null, "token")) val model = WizardModel(connection) assertThat(model.organizationKey).isEqualTo("org") assertThat(model.organizationList).isEmpty() @@ -41,8 +43,8 @@ internal class WizardModelTests { model.setIsSonarQube("url") model.setLoginPassword("login", charArrayOf('p', 'a', 's', 's')) - val connection = model.createConnection() - assertThat(connection.hostUrl).isEqualTo("url") + val connection = model.createConnectionWithAuth() + assertThat(connection.connection.hostUrl).isEqualTo("url") assertThat(connection.credentials.login).isEqualTo("login") assertThat(connection.credentials.password).isEqualTo("pass") assertThat(connection.credentials.token).isNull() @@ -56,9 +58,9 @@ internal class WizardModelTests { model.setOrganizationKey("org") model.setToken("token") - val connection = model.createConnection() - assertThat(connection.hostUrl).isEqualTo("https://sonarcloud.io") - assertThat((connection as SonarCloudConnection).organizationKey).isEqualTo("org") + val connection = model.createConnectionWithAuth() + assertThat(connection.connection.hostUrl).isEqualTo("https://sonarcloud.io") + assertThat((connection.connection as SonarCloudConnection).organizationKey).isEqualTo("org") assertThat(connection.credentials.token).isEqualTo("token") assertThat(connection.credentials.login).isNull() assertThat(connection.credentials.password).isNull() diff --git a/src/test/java/org/sonarlint/intellij/core/BackendServiceTests.kt b/src/test/java/org/sonarlint/intellij/core/BackendServiceTests.kt index 5238f4ffe8..bff6a77a19 100644 --- a/src/test/java/org/sonarlint/intellij/core/BackendServiceTests.kt +++ b/src/test/java/org/sonarlint/intellij/core/BackendServiceTests.kt @@ -42,6 +42,7 @@ import org.sonarlint.intellij.AbstractSonarLintHeavyTests import org.sonarlint.intellij.config.global.ServerConnection import org.sonarlint.intellij.config.global.ServerConnectionCredentials import org.sonarlint.intellij.config.global.ServerConnectionService +import org.sonarlint.intellij.config.global.ServerConnectionWithAuth import org.sonarlint.intellij.fixtures.newSonarCloudConnection import org.sonarlint.intellij.fixtures.newSonarQubeConnection import org.sonarsource.sonarlint.core.clientapi.SonarLintBackend @@ -65,8 +66,7 @@ class BackendServiceTests : AbstractSonarLintHeavyTests() { super.initApplication() clearServerConnections() - addServerConnection(newSonarQubeConnection("idSQ", "url")) - addServerConnection(newSonarCloudConnection("idSC", "org")) + setServerConnections(newSonarQubeConnection("idSQ", "url"), newSonarCloudConnection("idSC", "org")) backend = mock(SonarLintBackend::class.java) `when`(backend.initialize(any())).thenReturn(CompletableFuture.completedFuture(null)) @@ -136,7 +136,7 @@ class BackendServiceTests : AbstractSonarLintHeavyTests() { @Test fun test_notify_backend_when_opening_a_bound_project() { val connection = newSonarQubeConnection("id", "url") - addServerConnection(connection) + setServerConnections(connection) projectSettings.bindTo(connection, "key") service.projectOpened(project) @@ -278,43 +278,43 @@ class BackendServiceTests : AbstractSonarLintHeavyTests() { @Test fun test_notify_backend_when_connection_token_changed() { reset(backendConnectionService) - addServerConnection(newSonarQubeConnection("idSQ", credentials = ServerConnectionCredentials(null, null, "oldToken"))) + updateServerCredentials("idSQ", credentials = ServerConnectionCredentials(null, null, "oldToken")) verify(backendConnectionService).didChangeCredentials(refEq(DidChangeCredentialsParams("idSQ"))) } @Test fun test_notify_backend_when_connection_password_changed() { - addServerConnection(newSonarQubeConnection("id", credentials = ServerConnectionCredentials("login", "oldPass", null))) + addServerConnectionsWithAuth(newSonarQubeConnection("id"), credentials = ServerConnectionCredentials("login", "oldPass", null)) reset(backendConnectionService) - addServerConnection(newSonarQubeConnection("id", credentials = ServerConnectionCredentials("login", "newPass", null))) + updateServerCredentials("id", credentials = ServerConnectionCredentials("login", "newPass", null)) verify(backendConnectionService).didChangeCredentials(refEq(DidChangeCredentialsParams("id"))) } @Test fun test_notify_backend_when_connection_login_changed() { - addServerConnection(newSonarQubeConnection("id", credentials = ServerConnectionCredentials("oldLogin", "pass", null))) + addServerConnectionsWithAuth(newSonarQubeConnection("id"), credentials = ServerConnectionCredentials("oldLogin", "pass", null)) reset(backendConnectionService) - addServerConnection(newSonarQubeConnection("id", credentials = ServerConnectionCredentials("newLogin", "pass", null))) + updateServerCredentials("id", credentials = ServerConnectionCredentials("newLogin", "pass", null)) verify(backendConnectionService).didChangeCredentials(refEq(DidChangeCredentialsParams("id"))) } @Test fun test_do_not_notify_backend_of_credentials_change_when_connection_is_new() { - ServerConnectionService.getInstance().setServerConnections(globalSettings, emptyList()) + clearServerConnections() reset(backendConnectionService) - addServerConnection(newSonarQubeConnection("id", credentials = ServerConnectionCredentials("login", "newPass", null))) + addServerConnectionsWithAuth(newSonarQubeConnection("id"), credentials = ServerConnectionCredentials("login", "newPass", null)) verify(backendConnectionService, never()).didChangeCredentials(refEq(DidChangeCredentialsParams("id"))) } @Test fun test_do_not_notify_backend_of_credentials_change_when_something_else_changed() { - addServerConnection(newSonarQubeConnection("id", "oldUrl")) + setServerConnections(newSonarQubeConnection("id", "oldUrl")) reset(backendConnectionService) - addServerConnection(newSonarQubeConnection("id", "newUrl")) + setServerConnections(newSonarQubeConnection("id", "newUrl")) verify(backendConnectionService, never()).didChangeCredentials(refEq(DidChangeCredentialsParams("id"))) } @@ -326,11 +326,24 @@ class BackendServiceTests : AbstractSonarLintHeavyTests() { verify(backend).shutdown() } - private fun addServerConnection(connection: ServerConnection) { - ServerConnectionService.getInstance().addServerConnection(connection) + private fun addServerConnectionsWithAuth(connection: ServerConnection, credentials: ServerConnectionCredentials) { + addServerConnectionsWithAuth(listOf(ServerConnectionWithAuth(connection, credentials))) + } + + private fun updateServerCredentials(connectionName: String, credentials: ServerConnectionCredentials) { + val connection = ServerConnectionService.getInstance().getConnections().find { it.name == connectionName }!! + ServerConnectionService.getInstance().updateServerConnections(globalSettings, emptySet(), listOf(ServerConnectionWithAuth(connection, credentials)), emptyList()) + } + + private fun setServerConnections(vararg connections: ServerConnection) { + addServerConnectionsWithAuth(connections.map { ServerConnectionWithAuth(it, ServerConnectionCredentials(null, null, "token")) }) + } + + private fun addServerConnectionsWithAuth(connections: List) { + ServerConnectionService.getInstance().updateServerConnections(globalSettings, emptySet(), emptyList(), connections) } private fun clearServerConnections() { - ServerConnectionService.getInstance().setServerConnections(globalSettings, emptyList()) + ServerConnectionService.getInstance().updateServerConnections(globalSettings, ServerConnectionService.getInstance().getConnections().map { it.name }.toSet(), emptyList(), emptyList()) } } diff --git a/src/test/java/org/sonarlint/intellij/fixtures/ServerConnectionFixtures.kt b/src/test/java/org/sonarlint/intellij/fixtures/ServerConnectionFixtures.kt index 204448777f..d83da1656f 100644 --- a/src/test/java/org/sonarlint/intellij/fixtures/ServerConnectionFixtures.kt +++ b/src/test/java/org/sonarlint/intellij/fixtures/ServerConnectionFixtures.kt @@ -26,12 +26,12 @@ import org.sonarlint.intellij.config.global.SonarQubeConnection import org.sonarlint.intellij.config.global.wizard.PartialConnection import org.sonarlint.intellij.core.SonarProduct -fun newSonarQubeConnection(name: String = "id", hostUrl: String = "https://host", credentials: ServerConnectionCredentials = ServerConnectionCredentials(null, null, "token")): ServerConnection { - return SonarQubeConnection(name, hostUrl, credentials, true) +fun newSonarQubeConnection(name: String = "id", hostUrl: String = "https://host"): ServerConnection { + return SonarQubeConnection(name, hostUrl, true) } -fun newSonarCloudConnection(name: String, organizationkey: String): ServerConnection { - return SonarCloudConnection(name, "token", organizationkey, true) +fun newSonarCloudConnection(name: String, organizationKey: String): ServerConnection { + return SonarCloudConnection(name, organizationKey, true) } fun newPartialConnection(serverUrl: String = "https://serverUrl") = PartialConnection(serverUrl, SonarProduct.SONARQUBE, "orgKey", ServerConnectionCredentials(null, null, "token")) \ No newline at end of file