diff --git a/.cirrus/Dockerfile b/.cirrus/Dockerfile index b398a4b995..95ac0a7db4 100644 --- a/.cirrus/Dockerfile +++ b/.cirrus/Dockerfile @@ -5,7 +5,10 @@ FROM ${CIRRUS_AWS_ACCOUNT}.dkr.ecr.eu-central-1.amazonaws.com/base:j${JDK_VERSIO USER root ENV NODE_VERSION=18 + +# dbus-x11 is for SonarLint token secure storage (required by the IDE) RUN apt-get update && apt-get install -y metacity xvfb xauth ffmpeg \ + dbus-x11 \ nodejs=${NODE_VERSION}.* \ build-essential \ gettext-base \ diff --git a/src/main/java/org/sonarlint/intellij/SonarLintIntelliJClient.kt b/src/main/java/org/sonarlint/intellij/SonarLintIntelliJClient.kt index 9f7ccd05c1..bee4f829db 100644 --- a/src/main/java/org/sonarlint/intellij/SonarLintIntelliJClient.kt +++ b/src/main/java/org/sonarlint/intellij/SonarLintIntelliJClient.kt @@ -56,8 +56,8 @@ import org.sonarlint.intellij.analysis.AnalysisSubmitter import org.sonarlint.intellij.common.ui.ReadActionUtils.Companion.computeReadActionSafely import org.sonarlint.intellij.common.util.SonarLintUtils import org.sonarlint.intellij.common.util.SonarLintUtils.getService -import org.sonarlint.intellij.config.Settings.getGlobalSettings import org.sonarlint.intellij.config.Settings.getSettingsFor +import org.sonarlint.intellij.config.global.ServerConnectionService import org.sonarlint.intellij.config.global.wizard.ServerConnectionCreator import org.sonarlint.intellij.core.BackendService import org.sonarlint.intellij.core.ProjectBindingManager @@ -334,7 +334,7 @@ object SonarLintIntelliJClient : SonarLintClient { return CompletableFuture.supplyAsync { val connectionId = params.connectionId val projectKey = params.projectKey - val connection = getGlobalSettings().getServerConnectionByName(connectionId) + val connection = ServerConnectionService.getInstance().getServerConnectionByName(connectionId) .orElseThrow { IllegalStateException("Unable to find connection '$connectionId'") } val message = "Cannot automatically find a project bound to:\n" + " • Project: $projectKey\n" + @@ -390,11 +390,12 @@ object SonarLintIntelliJClient : SonarLintClient { } override fun getCredentials(params: GetCredentialsParams): CompletableFuture { - return getGlobalSettings().getServerConnectionByName(params.connectionId) - .map { connection -> connection.token?.let { CompletableFuture.completedFuture(GetCredentialsResponse(TokenDto(it))) } - ?: connection.login?.let { CompletableFuture.completedFuture(GetCredentialsResponse(UsernamePasswordDto(it, connection.password))) } - ?: CompletableFuture.failedFuture(IllegalArgumentException("Invalid credentials for connection: " + params.connectionId)) - }.orElse(CompletableFuture.failedFuture(IllegalArgumentException("Unknown connection: " + params.connectionId))) + val connectionId = params.connectionId + 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")) } } override fun getProxyPasswordAuthentication(params: GetProxyPasswordAuthenticationParams): CompletableFuture { diff --git a/src/main/java/org/sonarlint/intellij/actions/OpenIssueInBrowserAction.kt b/src/main/java/org/sonarlint/intellij/actions/OpenIssueInBrowserAction.kt index b7f3cfe1e9..2831baac3a 100644 --- a/src/main/java/org/sonarlint/intellij/actions/OpenIssueInBrowserAction.kt +++ b/src/main/java/org/sonarlint/intellij/actions/OpenIssueInBrowserAction.kt @@ -45,7 +45,7 @@ class OpenIssueInBrowserAction : AbstractSonarAction( override fun updatePresentation(e: AnActionEvent, project: Project) { val serverConnection = serverConnection(project) ?: return e.presentation.text = "Open in " + serverConnection.productName - e.presentation.icon = serverConnection.productIcon + e.presentation.icon = serverConnection.product.icon } override fun actionPerformed(e: AnActionEvent) { diff --git a/src/main/java/org/sonarlint/intellij/actions/OpenSecurityHotspotInBrowserAction.kt b/src/main/java/org/sonarlint/intellij/actions/OpenSecurityHotspotInBrowserAction.kt index c501b9521b..d791cb47ec 100644 --- a/src/main/java/org/sonarlint/intellij/actions/OpenSecurityHotspotInBrowserAction.kt +++ b/src/main/java/org/sonarlint/intellij/actions/OpenSecurityHotspotInBrowserAction.kt @@ -48,7 +48,7 @@ class OpenSecurityHotspotInBrowserAction : AbstractSonarAction( val serverConnection = serverConnection(project) ?: return e.presentation.text = "Open in " + serverConnection.productName e.presentation.description = "Open Security Hotspot in browser interface of " + serverConnection.productName - e.presentation.icon = serverConnection.productIcon + e.presentation.icon = serverConnection.product.icon } override fun actionPerformed(e: AnActionEvent) { diff --git a/src/main/java/org/sonarlint/intellij/config/global/ServerConnection.kt b/src/main/java/org/sonarlint/intellij/config/global/ServerConnection.kt new file mode 100644 index 0000000000..2ed487988c --- /dev/null +++ b/src/main/java/org/sonarlint/intellij/config/global/ServerConnection.kt @@ -0,0 +1,58 @@ +/* + * 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 + +import org.sonarlint.intellij.common.util.SonarLintUtils +import org.sonarlint.intellij.common.util.SonarLintUtils.SONARCLOUD_URL +import org.sonarlint.intellij.core.BackendService +import org.sonarlint.intellij.core.SonarProduct +import org.sonarlint.intellij.core.server.ServerLinks +import org.sonarlint.intellij.core.server.SonarCloudLinks +import org.sonarlint.intellij.core.server.SonarQubeLinks +import org.sonarsource.sonarlint.core.serverapi.EndpointParams +import org.sonarsource.sonarlint.core.serverapi.ServerApi + + +sealed class ServerConnection { + abstract val name: String + abstract val notificationsDisabled: Boolean + abstract val hostUrl: String + abstract val product: SonarProduct + abstract val links: ServerLinks + abstract val endpointParams: EndpointParams + fun api() = ServerApi(endpointParams, SonarLintUtils.getService(BackendService::class.java).getHttpClient(name)) + override fun toString() = name + val isSonarCloud get() = product == SonarProduct.SONARCLOUD + val isSonarQube get() = product == SonarProduct.SONARQUBE + val productName get() = product.productName +} + +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 organizationKey: String, override val notificationsDisabled: Boolean) : ServerConnection() { + override val product = SonarProduct.SONARCLOUD + override val links = SonarCloudLinks + override val hostUrl: String = SONARCLOUD_URL + override val endpointParams = EndpointParams(hostUrl, true, organizationKey) +} diff --git a/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionCredentials.kt b/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionCredentials.kt new file mode 100644 index 0000000000..9a4e382234 --- /dev/null +++ b/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionCredentials.kt @@ -0,0 +1,22 @@ +/* + * 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 ServerConnectionCredentials(val login: String?, val password: String?, val token: String?) 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 36681ba5b9..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; @@ -32,13 +33,14 @@ import com.intellij.ui.ToolbarDecorator; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBList; -import org.sonarlint.intellij.SonarLintIcons; import java.awt.BorderLayout; 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; @@ -54,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"; @@ -65,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(); @@ -105,15 +111,11 @@ public void mouseClicked(MouseEvent evt) { connectionList.setCellRenderer(new ColoredListCellRenderer<>() { @Override - protected void customizeCellRenderer(JList list, ServerConnection server, int index, boolean selected, boolean hasFocus) { - if (server.isSonarCloud()) { - setIcon(SonarLintIcons.ICON_SONARCLOUD_16); - } else { - setIcon(SonarLintIcons.ICON_SONARQUBE_16); - } - append(server.getName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); - if (!server.isSonarCloud()) { - append(" (" + server.getHostUrl() + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES, false); + protected void customizeCellRenderer(JList list, ServerConnection connection, int index, boolean selected, boolean hasFocus) { + setIcon(connection.getProduct().getIcon()); + append(connection.getName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + if (connection.isSonarQube()) { + append(" (" + connection.getHostUrl() + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES, false); } } }); @@ -144,30 +146,35 @@ public JComponent getComponent() { @Override public boolean isModified(SonarLintGlobalSettings settings) { - return !connections.equals(settings.getServerConnections()); + return !updatedConnectionsByName.isEmpty() || !addedConnectionsByName.isEmpty() || !deletedServerIds.isEmpty(); } @Override public void save(SonarLintGlobalSettings newSettings) { - var newConnections = new ArrayList<>(connections); - newSettings.setServerConnections(newConnections); + // 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(); + }); - // 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.add(settings.getServerConnections()); - connections.addAll(settings.getServerConnections()); + listModel = new CollectionListModel<>(new ArrayList<>()); + var serverConnections = ServerConnectionService.getInstance().getConnections(); + + listModel.add(serverConnections); connectionList.setModel(listModel); - if (!connections.isEmpty()) { - connectionList.setSelectedValue(connections.get(0), true); + if (!serverConnections.isEmpty()) { + connectionList.setSelectedValue(serverConnections.get(0), true); } } @@ -177,7 +184,7 @@ private ServerConnection getSelectedConnection() { } List getConnections() { - return connections; + return listModel.getItems(); } private void editSelectedConnection() { @@ -185,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()); } } } @@ -213,15 +249,15 @@ public void run(AnActionButton anActionButton) { private class RemoveServerAction implements AnActionButtonRunnable { @Override public void run(AnActionButton anActionButton) { - var server = getSelectedConnection(); + var connection = getSelectedConnection(); var selectedIndex = connectionList.getSelectedIndex(); - if (server == null) { + if (connection == null) { return; } var openProjects = ProjectManager.getInstance().getOpenProjects(); - var projectsUsingNames = getOpenProjectNames(openProjects, server); + var projectsUsingNames = getOpenProjectNames(openProjects, connection); if (!projectsUsingNames.isEmpty()) { var projects = String.join("
", projectsUsingNames); @@ -234,15 +270,18 @@ public void run(AnActionButton anActionButton) { } } - var model = (CollectionListModel) connectionList.getModel(); - // it's not removed from serverIds and editorList - model.remove(server); - connections.remove(server); - 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 new file mode 100644 index 0000000000..23ecdfb80f --- /dev/null +++ b/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionService.kt @@ -0,0 +1,172 @@ +/* + * 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 + +import com.intellij.credentialStore.CredentialAttributes +import com.intellij.credentialStore.Credentials +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 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 { + loadAndMigrateServerConnectionsAsync() + } + + private fun loadAndMigrateServerConnectionsAsync() { + runOnPooledThread { getGlobalSettings().serverConnections.forEach { migrate(it) } } + } + + private fun migrate(connection: ServerConnectionSettings) { + saveCredentials(connection.name, ServerConnectionCredentials(connection.login, connection.password, connection.token)) + connection.clearCredentials() + } + + fun getConnections(): List = getGlobalSettings().serverConnections.filter { isValid(it) }.mapNotNull { connection -> + if (connection.isSonarCloud) { + 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 + if (!valid) { + GlobalLogOutput.get().logError("The connection $connectionSettings is not valid", null) + } + return valid + } + + fun getServerConnectionByName(name: String): Optional { + 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 } + } + + fun getServerNames(): Set { + return getConnections().map { it.name }.toSet() + } + + fun addServerConnection(connection: ServerConnectionWithAuth) { + updateServerConnections(getGlobalSettings(), emptySet(), emptyList(), listOf(connection)) + } + + fun replaceConnection(replacementConnection: ServerConnectionWithAuth) { + updateServerConnections(getGlobalSettings(), emptySet(), listOf(replacementConnection), emptyList()) + } + + 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 }) + } + + 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() + } + } + + private fun notifyConnectionsChange(connections: List) { + ApplicationManager.getApplication().messageBus.syncPublisher(ServerConnectionsListener.TOPIC).afterChange(connections) + } + + private fun notifyCredentialsChange(serverConnections: List) { + ApplicationManager.getApplication().messageBus.syncPublisher(ServerConnectionsListener.TOPIC).credentialsChanged(serverConnections) + } + + 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(connectionName)] + if (loginPassword != null) { + return ServerConnectionCredentials(loginPassword.userName, loginPassword.password.toString(), 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) { + PasswordSafe.instance[loginPasswordCredentials(connectionId)] = Credentials(credentials.login, credentials.password) + } + // else probably already migrated + } + + private fun forgetCredentials(connectionId: String) { + // saving credentials is a slow operation + check(!EDT.isCurrentThreadEdt()) { "Cannot save credentials from EDT" } + PasswordSafe.instance[tokenCredentials(connectionId)] = null + } + + companion object { + @JvmStatic + fun getInstance(): ServerConnectionService = getService(ServerConnectionService::class.java) + private fun tokenCredentials(connectionId: String) = CredentialAttributes(generateServiceName("SonarLint connections", "$connectionId.token")) + private fun loginPasswordCredentials(connectionId: String) = CredentialAttributes(generateServiceName("SonarLint connections", "$connectionId.loginPassword")) + } +} \ No newline at end of file diff --git a/src/main/java/org/sonarlint/intellij/config/global/ServerConnection.java b/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionSettings.java similarity index 61% rename from src/main/java/org/sonarlint/intellij/config/global/ServerConnection.java rename to src/main/java/org/sonarlint/intellij/config/global/ServerConnectionSettings.java index b6132abb5f..847a99f912 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/ServerConnection.java +++ b/src/main/java/org/sonarlint/intellij/config/global/ServerConnectionSettings.java @@ -26,17 +26,7 @@ import javax.annotation.CheckForNull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import javax.swing.Icon; -import org.sonarlint.intellij.SonarLintIcons; import org.sonarlint.intellij.common.util.SonarLintUtils; -import org.sonarlint.intellij.core.BackendService; -import org.sonarlint.intellij.core.server.ServerLinks; -import org.sonarlint.intellij.core.server.SonarCloudLinks; -import org.sonarlint.intellij.core.server.SonarQubeLinks; -import org.sonarsource.sonarlint.core.serverapi.EndpointParams; -import org.sonarsource.sonarlint.core.serverapi.ServerApi; - -import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; /** * This class is serialized in XML when SonarLintGlobalSettings is saved by IntelliJ. @@ -46,48 +36,46 @@ * Note that we use both {@link OptionTag} and {@link Tag} (which will result in 2 different ways of serializing the fields) to remain * backward-compatible with existing serialized configurations. * - * @see com.intellij.util.xmlb.annotations.Tag - * @see com.intellij.util.xmlb.annotations.OptionTag + * @see Tag + * @see OptionTag */ @Immutable // Don't change annotation, used for backward compatibility @Tag("SonarQubeServer") -public class ServerConnection { +public class ServerConnectionSettings { @OptionTag private String hostUrl; + // credentials are migrated to secure storage @Tag + @Deprecated(since = "10.0") private String token; @OptionTag private String name; @OptionTag + @Deprecated(since = "10.0") private String login; @Tag + @Deprecated(since = "10.0") private String password; - @OptionTag - private boolean enableProxy; @Tag private String organizationKey; @Tag private boolean disableNotifications; - private ServerConnection() { + private ServerConnectionSettings() { // necessary for XML deserialization } - private ServerConnection(Builder builder) { + private ServerConnectionSettings(Builder builder) { this.hostUrl = builder.hostUrl; - this.token = builder.token; this.name = builder.name; - this.login = builder.login; - this.password = builder.password; - this.enableProxy = builder.enableProxy; this.organizationKey = builder.organizationKey; this.disableNotifications = builder.disableNotifications; } @Override public boolean equals(Object o) { - if (!(o instanceof ServerConnection other)) { + if (!(o instanceof ServerConnectionSettings other)) { return false; } @@ -97,13 +85,12 @@ public boolean equals(Object o) { Objects.equals(getLogin(), other.getLogin()) && Objects.equals(getName(), other.getName()) && Objects.equals(getOrganizationKey(), other.getOrganizationKey()) && - Objects.equals(enableProxy(), other.enableProxy()) && Objects.equals(isDisableNotifications(), other.isDisableNotifications()); } @Override public int hashCode() { - return Objects.hash(getHostUrl(), getPassword(), getToken(), getLogin(), getOrganizationKey(), getName(), enableProxy, disableNotifications); + return Objects.hash(getHostUrl(), getPassword(), getToken(), getLogin(), getOrganizationKey(), getName(), disableNotifications); } public boolean isDisableNotifications() { @@ -135,24 +122,15 @@ public String getToken() { return null; } } - public boolean isSonarCloud() { - return SonarLintUtils.isSonarCloudAlias(hostUrl); - } - - public boolean isSonarQube() { - return !isSonarCloud(); - } - - public String getProductName() { - return isSonarCloud() ? "SonarCloud" : "SonarQube"; - } - public Icon getProductIcon() { - return isSonarCloud() ? SonarLintIcons.ICON_SONARCLOUD_16 : SonarLintIcons.ICON_SONARQUBE_16; + public void clearCredentials() { + this.login = null; + this.password = null; + this.token = null; } - public boolean enableProxy() { - return enableProxy; + public boolean isSonarCloud() { + return SonarLintUtils.isSonarCloudAlias(hostUrl); } @CheckForNull @@ -167,25 +145,10 @@ public String getPassword() { } } - public boolean hasSameCredentials(ServerConnection otherConnection) { - if (token != null) { - return Objects.equals(token, otherConnection.token); - } - return Objects.equals(password, otherConnection.password) && Objects.equals(login, otherConnection.login); - } - public String getName() { return name; } - public EndpointParams getEndpointParams() { - return new EndpointParams(getHostUrl(), isSonarCloud(), getOrganizationKey()); - } - - public ServerApi api() { - return new ServerApi(getEndpointParams(), getService(BackendService.class).getHttpClient(name)); - } - @Override public String toString() { return name; @@ -195,31 +158,18 @@ public static Builder newBuilder() { return new Builder(); } - public ServerLinks links() { - return isSonarCloud() ? SonarCloudLinks.INSTANCE : new SonarQubeLinks(hostUrl); - } - public static class Builder { private String hostUrl; - private String token; private String organizationKey; private String name; - private String login; - private String password; - private boolean enableProxy; private boolean disableNotifications; private Builder() { // no args } - public ServerConnection build() { - return new ServerConnection(this); - } - - public Builder setLogin(@Nullable String login) { - this.login = login; - return this; + public ServerConnectionSettings build() { + return new ServerConnectionSettings(this); } public Builder setDisableNotifications(boolean disableNotifications) { @@ -237,29 +187,6 @@ public Builder setHostUrl(String hostUrl) { return this; } - public Builder setEnableProxy(boolean enableProxy) { - this.enableProxy = enableProxy; - return this; - } - - public Builder setToken(@Nullable String token) { - if (token == null) { - this.token = null; - } else { - this.token = PasswordUtil.encodePassword(token); - } - return this; - } - - public Builder setPassword(@Nullable String password) { - if (password == null) { - this.password = null; - } else { - this.password = PasswordUtil.encodePassword(password); - } - return this; - } - public Builder setName(String name) { this.name = name; return this; 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/SonarLintGlobalSettings.java b/src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalSettings.java index 226468bc4a..0a10123746 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalSettings.java +++ b/src/main/java/org/sonarlint/intellij/config/global/SonarLintGlobalSettings.java @@ -24,9 +24,7 @@ import com.intellij.util.xmlb.annotations.Transient; import com.intellij.util.xmlb.annotations.XCollection; import com.intellij.util.xmlb.annotations.XMap; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -36,16 +34,13 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import org.sonarlint.intellij.common.util.SonarLintUtils; - -import static org.sonarlint.intellij.common.util.SonarLintUtils.equalsIgnoringTrailingSlash; public final class SonarLintGlobalSettings { private boolean isFocusOnNewCode = false; private boolean autoTrigger = true; private String nodejsPath = ""; - private List servers = new LinkedList<>(); + private List servers = new LinkedList<>(); private List fileExclusions = new LinkedList<>(); @Deprecated private Set includedRules; @@ -174,37 +169,15 @@ public void setNodejsPath(String nodejsPath) { } // Don't change annotation, used for backward compatibility + // always use ServerConnectionService to access server connections @OptionTag("sonarQubeServers") - public List getServerConnections() { + public List getServerConnections() { return this.servers; } - public void setServerConnections(List servers) { - this.servers = servers.stream() - .filter(s -> !SonarLintUtils.isBlank(s.getName())) - .toList(); - } - - public Optional getServerConnectionByName(String name) { - return servers.stream() - .filter(s -> name.equals(s.getName())) - .findFirst(); - } - - public boolean connectionExists(String connectionName) { - return getServerConnectionByName(connectionName).isPresent(); - } - - public void addServerConnection(ServerConnection connection) { - ArrayList sonarQubeServers = new ArrayList<>(servers); - sonarQubeServers.add(connection); - this.servers = Collections.unmodifiableList(sonarQubeServers); - } - - public Set getServerNames() { - return servers.stream() - .map(ServerConnection::getName) - .collect(Collectors.toSet()); + // always use ServerConnectionService to set server connections + void setServerConnections(List servers) { + this.servers = servers; } public List getFileExclusions() { @@ -247,12 +220,6 @@ public void setExcludedRules(Set excludedRules) { this.excludedRules = excludedRules; } - public List getConnectionsTo(String serverUrl) { - return servers.stream() - .filter(it -> equalsIgnoringTrailingSlash(it.getHostUrl(), serverUrl)) - .toList(); - } - public static class Rule { String key; boolean isActive; diff --git a/src/main/java/org/sonarlint/intellij/config/global/wizard/AuthStep.java b/src/main/java/org/sonarlint/intellij/config/global/wizard/AuthStep.java index 3f8a65ae0f..3d6af9ecb2 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/wizard/AuthStep.java +++ b/src/main/java/org/sonarlint/intellij/config/global/wizard/AuthStep.java @@ -46,7 +46,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.sonarlint.intellij.common.util.SonarLintUtils; -import org.sonarlint.intellij.config.global.ServerConnection; import org.sonarlint.intellij.core.BackendService; import org.sonarlint.intellij.tasks.ConnectionTestTask; import org.sonarlint.intellij.util.GlobalLogOutput; @@ -108,37 +107,33 @@ protected void textChanged(DocumentEvent e) { @Override public void _init() { - if (model.getServerType() == WizardModel.ServerType.SONARCLOUD) { + var credentials = model.getCredentials(); + if (model.isSonarCloud()) { authComboBox.setSelectedItem(TOKEN_ITEM); authComboBox.setEnabled(false); } else { authComboBox.setEnabled(true); - if (model.getLogin() != null) { + if (credentials != null && credentials.getLogin() != null) { authComboBox.setSelectedItem(LOGIN_ITEM); } else { authComboBox.setSelectedItem(TOKEN_ITEM); } } - tokenField.setText(model.getToken()); - loginField.setText(model.getLogin()); - if (model.getPassword() != null) { - passwordField.setText(new String(model.getPassword())); - } else { - passwordField.setText(null); + if (credentials != null) { + tokenField.setText(credentials.getToken()); + loginField.setText(credentials.getLogin()); + passwordField.setText(credentials.getPassword()); } + openTokenCreationPageButton.setText("Create token"); } private void save() { if (LOGIN_ITEM.equals(authComboBox.getSelectedItem())) { - model.setToken(null); - model.setLogin(loginField.getText()); - model.setPassword(passwordField.getPassword()); + model.setLoginPassword(loginField.getText(), passwordField.getPassword()); } else { model.setToken(String.valueOf(tokenField.getPassword())); - model.setLogin(null); - model.setPassword(null); } } @@ -156,7 +151,7 @@ public Object getStepId() { @Nullable @Override public Object getNextStepId() { - if (model.getServerType() == WizardModel.ServerType.SONARCLOUD) { + if (model.isSonarCloud()) { return OrganizationStep.class; } if (model.isNotificationsSupported()) { @@ -224,8 +219,8 @@ private void tryQueryIfNotificationsSupported() throws CommitStepException { } private void checkConnection() throws CommitStepException { - ServerConnection tmpServer = model.createConnectionWithoutOrganization(); - ConnectionTestTask test = new ConnectionTestTask(tmpServer); + var partialConnection = model.createPartialConnection(); + var test = new ConnectionTestTask(partialConnection); var msg = "Failed to connect to the server. Please check the configuration."; ValidateConnectionResponse result; try { @@ -264,7 +259,7 @@ private void openTokenCreationPage() { Disposer.register(this, progressWindow); try { ProgressResult progressResult = new ProgressRunner<>(pi -> { - var future = SonarLintUtils.getService(BackendService.class).helpGenerateUserToken(serverUrl, model.getServerType() == WizardModel.ServerType.SONARCLOUD); + var future = SonarLintUtils.getService(BackendService.class).helpGenerateUserToken(serverUrl, model.isSonarCloud()); return ProgressUtils.waitForFuture(pi, future); }) .sync() diff --git a/src/main/java/org/sonarlint/intellij/config/global/wizard/ConfirmStep.java b/src/main/java/org/sonarlint/intellij/config/global/wizard/ConfirmStep.java index 4c44166e3c..bd86e8aa61 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/wizard/ConfirmStep.java +++ b/src/main/java/org/sonarlint/intellij/config/global/wizard/ConfirmStep.java @@ -75,7 +75,7 @@ public Object getPreviousStepId() { if (model.isNotificationsSupported()) { return NotificationsStep.class; } - if (model.getServerType() == WizardModel.ServerType.SONARCLOUD) { + if (model.isSonarCloud()) { return OrganizationStep.class; } return AuthStep.class; diff --git a/src/main/java/org/sonarlint/intellij/config/global/wizard/NotificationsStep.java b/src/main/java/org/sonarlint/intellij/config/global/wizard/NotificationsStep.java index 0c7809a5d4..8f57b0764d 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/wizard/NotificationsStep.java +++ b/src/main/java/org/sonarlint/intellij/config/global/wizard/NotificationsStep.java @@ -22,6 +22,7 @@ import com.intellij.ide.wizard.AbstractWizardStepEx; import com.intellij.ui.BrowserHyperlinkListener; import com.intellij.util.ui.SwingHelper; +import java.util.Objects; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JEditorPane; @@ -49,16 +50,15 @@ public JComponent getComponent() { @Override public void _init() { - final boolean isSc = model.getServerType() == WizardModel.ServerType.SONARCLOUD; - final String sqOrSc = isSc ? "SonarCloud" : "SonarQube"; - notificationsCheckBox.setText("Receive notifications from " + sqOrSc); + var serverProduct = Objects.requireNonNull(model.getServerProduct()); + final String productName = serverProduct.getProductName(); + notificationsCheckBox.setText("Receive notifications from " + productName); notificationsCheckBox.setSelected(!model.isNotificationsDisabled()); - final String docUrl = isSc ? "https://docs.sonarcloud.io/advanced-setup/sonarlint-smart-notifications/" : - "https://docs.sonarqube.org/latest/user-guide/sonarlint-connected-mode/"; - notificationsDetails.setText("You will receive notifications from " + sqOrSc + " in situations like:\n" + + notificationsDetails.setText("You will receive notifications from " + productName + " in situations like:\n" + "
    " + "
  • the Quality Gate status of a bound project changes
  • " + - "
  • the latest analysis of a bound project on " + sqOrSc + " raises new issues assigned to you
  • " + + "
  • the latest analysis of a bound project on " + productName + " raises new issues assigned to you
  • " + "
"); notificationsDetails.addHyperlinkListener(BrowserHyperlinkListener.INSTANCE); } @@ -84,7 +84,7 @@ public Object getPreviousStepId() { if (onlyEditNotifications) { return null; } - if (model.getServerType() == WizardModel.ServerType.SONARCLOUD) { + if (model.isSonarCloud()) { return OrganizationStep.class; } else { return AuthStep.class; diff --git a/src/main/java/org/sonarlint/intellij/config/global/wizard/OrganizationStep.java b/src/main/java/org/sonarlint/intellij/config/global/wizard/OrganizationStep.java index 5ae48eb30c..ff4bc31044 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/wizard/OrganizationStep.java +++ b/src/main/java/org/sonarlint/intellij/config/global/wizard/OrganizationStep.java @@ -73,7 +73,7 @@ private void enterCustomOrganizationKey() { break; } - var task = new GetOrganizationTask(model.createConnectionWithoutOrganization(), organizationKey); + var task = new GetOrganizationTask(model.createPartialConnection(), organizationKey); ProgressManager.getInstance().run(task); if (task.organization() != null) { diff --git a/src/main/java/org/sonarlint/intellij/config/global/wizard/PartialConnection.kt b/src/main/java/org/sonarlint/intellij/config/global/wizard/PartialConnection.kt new file mode 100644 index 0000000000..93d03e62c4 --- /dev/null +++ b/src/main/java/org/sonarlint/intellij/config/global/wizard/PartialConnection.kt @@ -0,0 +1,25 @@ +/* + * 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.wizard + +import org.sonarlint.intellij.config.global.ServerConnectionCredentials +import org.sonarlint.intellij.core.SonarProduct + +data class PartialConnection(val hostUrl: String, val sonarProduct: SonarProduct, val organizationKey: String?, 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 6facd0275a..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 @@ -19,24 +19,19 @@ */ package org.sonarlint.intellij.config.global.wizard -import com.intellij.openapi.application.ApplicationManager -import org.sonarlint.intellij.config.Settings import org.sonarlint.intellij.config.global.ServerConnection -import org.sonarlint.intellij.messages.GlobalConfigurationListener +import org.sonarlint.intellij.config.global.ServerConnectionService +import org.sonarlint.intellij.util.runOnPooledThread open class ServerConnectionCreator { open fun createThroughWizard(serverUrl: String): ServerConnection? { - val globalSettings = Settings.getGlobalSettings() - val connectionToCreate = ServerConnection.newBuilder().setHostUrl(serverUrl).setDisableNotifications(false).build() - val wizard = ServerConnectionWizard.forNewConnection(connectionToCreate, globalSettings.serverNames) + val serverConnectionService = ServerConnectionService.getInstance() + val wizard = ServerConnectionWizard.forNewConnection(serverUrl, serverConnectionService.getServerNames()) if (wizard.showAndGet()) { - val created = wizard.connection - Settings.getGlobalSettings().addServerConnection(created) - val serverChangeListener = ApplicationManager.getApplication().messageBus.syncPublisher(GlobalConfigurationListener.TOPIC) - // notify in case the connections settings dialog is open to reflect the change - serverChangeListener.changed(globalSettings.serverConnections) - 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 2419b37f90..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 { @@ -43,21 +43,21 @@ public static ServerConnectionWizard forNewConnection(Set existingNames) return wizard; } - public static ServerConnectionWizard forNewConnection(ServerConnection prefilledConnection, Set existingNames) { - var wizard = new ServerConnectionWizard(new WizardModel(prefilledConnection)); + public static ServerConnectionWizard forNewConnection(String serverUrl, Set existingNames) { + var wizard = new ServerConnectionWizard(new WizardModel(serverUrl)); var steps = createSteps(wizard.model, false, existingNames); wizard.wizardEx = new ServerConnectionWizardEx(steps, "New Connection"); return wizard; } - public static ServerConnectionWizard forConnectionEdition(ServerConnection connectionToEdit) { + public static ServerConnectionWizard forConnectionEdition(ServerConnectionWithAuth connectionToEdit) { var wizard = new ServerConnectionWizard(new WizardModel(connectionToEdit)); var steps = createSteps(wizard.model, true, Collections.emptySet()); wizard.wizardEx = new ServerConnectionWizardEx(steps, "Edit Connection"); return wizard; } - public static ServerConnectionWizard forNotificationsEdition(ServerConnection connectionToEdit) { + public static ServerConnectionWizard forNotificationsEdition(ServerConnectionWithAuth connectionToEdit) { var wizard = new ServerConnectionWizard(new WizardModel(connectionToEdit)); // Assume notifications are supported, if not, why would we want to edit the setting wizard.model.setNotificationsSupported(true); @@ -80,8 +80,8 @@ public boolean showAndGet() { return wizardEx.showAndGet(); } - public ServerConnection getConnection() { - return model.createConnection(); + public ServerConnectionWithAuth getConnectionWithAuth() { + return model.createConnectionWithAuth(); } private static class ServerConnectionWizardEx extends AbstractWizardEx { diff --git a/src/main/java/org/sonarlint/intellij/config/global/wizard/ServerStep.java b/src/main/java/org/sonarlint/intellij/config/global/wizard/ServerStep.java index b9d952f172..30dbc2473f 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/wizard/ServerStep.java +++ b/src/main/java/org/sonarlint/intellij/config/global/wizard/ServerStep.java @@ -45,8 +45,7 @@ import org.jetbrains.annotations.Nullable; import org.sonarlint.intellij.SonarLintIcons; import org.sonarlint.intellij.common.util.SonarLintUtils; - -import static org.sonarlint.intellij.common.util.SonarLintUtils.SONARCLOUD_URL; +import org.sonarlint.intellij.core.SonarProduct; public class ServerStep extends AbstractWizardStepEx { private static final int NAME_MAX_LENGTH = 50; @@ -83,11 +82,11 @@ public ServerStep(WizardModel model, boolean editing, Collection existin nameField.setToolTipText("Name of this configuration (mandatory field)"); - String cloudText = "Connect to the online service"; + var cloudText = "Connect to the online service"; sonarcloudText.setText(cloudText); sonarcloudText.addHyperlinkListener(BrowserHyperlinkListener.INSTANCE); - String sqText = "Connect to a server"; + var sqText = "Connect to a server"; sonarqubeText.setText(sqText); if (!editing) { @@ -105,9 +104,7 @@ public ServerStep(WizardModel model, boolean editing, Collection existin }); } - proxyButton.addActionListener(evt -> { - HttpConfigurable.editConfigurable(panel); - }); + proxyButton.addActionListener(evt -> HttpConfigurable.editConfigurable(panel)); load(editing); paintErrors(); @@ -122,7 +119,7 @@ private void load(boolean editing) { Icon sqIcon = SonarLintIcons.ICON_SONARQUBE; Icon clIcon = SonarLintIcons.ICON_SONARCLOUD; - if (model.getServerType() == WizardModel.ServerType.SONARCLOUD || model.getServerType() == null) { + if (model.getServerProduct() == SonarProduct.SONARCLOUD || model.getServerProduct() == null) { radioSonarCloud.setSelected(true); if (editing) { sqIcon = SonarLintIcons.toDisabled(sqIcon); @@ -198,7 +195,7 @@ private void validateName() throws CommitStepException { private void validateUrl() throws CommitStepException { if (radioSonarQube.isSelected()) { try { - URL url = new URL(urlText.getText()); + var url = new URL(urlText.getText()); if (SonarLintUtils.isBlank(url.getHost())) { throw new CommitStepException("Please provide a valid URL"); } @@ -210,11 +207,9 @@ private void validateUrl() throws CommitStepException { private void save() { if (radioSonarCloud.isSelected()) { - model.setServerType(WizardModel.ServerType.SONARCLOUD); - model.setServerUrl(SONARCLOUD_URL); + model.setIsSonarCloud(); } else { - model.setServerType(WizardModel.ServerType.SONARQUBE); - model.setServerUrl(urlText.getText().trim()); + model.setIsSonarQube(urlText.getText().trim()); } model.setName(nameField.getText().trim()); } @@ -235,7 +230,7 @@ private void createUIComponents() { sonarcloudText = SwingHelper.createHtmlViewer(false, null, null, null); sonarqubeText = SwingHelper.createHtmlViewer(false, null, null, null); - JBTextField text = new JBTextField(); + var text = new JBTextField(); text.getEmptyText().setText("Example: http://localhost:9000"); urlText = text; diff --git a/src/main/java/org/sonarlint/intellij/config/global/wizard/WizardModel.java b/src/main/java/org/sonarlint/intellij/config/global/wizard/WizardModel.java index 383bf88ad7..179bc17188 100644 --- a/src/main/java/org/sonarlint/intellij/config/global/wizard/WizardModel.java +++ b/src/main/java/org/sonarlint/intellij/config/global/wizard/WizardModel.java @@ -24,8 +24,12 @@ import java.util.List; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import org.sonarlint.intellij.common.util.SonarLintUtils; import org.sonarlint.intellij.config.global.ServerConnection; +import org.sonarlint.intellij.config.global.ServerConnectionCredentials; +import org.sonarlint.intellij.config.global.ServerConnectionWithAuth; +import org.sonarlint.intellij.config.global.SonarCloudConnection; +import org.sonarlint.intellij.config.global.SonarQubeConnection; +import org.sonarlint.intellij.core.SonarProduct; import org.sonarlint.intellij.tasks.CheckNotificationsSupportedTask; import org.sonarlint.intellij.tasks.GetOrganizationTask; import org.sonarlint.intellij.tasks.GetOrganizationsTask; @@ -34,72 +38,64 @@ import static org.sonarlint.intellij.common.util.SonarLintUtils.SONARCLOUD_URL; public class WizardModel { - private ServerType serverType; + private SonarProduct sonarProduct; private String serverUrl; private String token; private String login; private char[] password; private String name; private String organizationKey; - private boolean proxyEnabled; private boolean notificationsDisabled = false; private boolean notificationsSupported = false; private List organizationList = new ArrayList<>(); - public enum ServerType { - SONARCLOUD, - SONARQUBE - } - public WizardModel() { } - public WizardModel(ServerConnection connectionToEdit) { - if (SonarLintUtils.isSonarCloudAlias(connectionToEdit.getHostUrl())) { - serverType = ServerType.SONARCLOUD; - } else { - serverType = ServerType.SONARQUBE; - serverUrl = connectionToEdit.getHostUrl(); + public WizardModel(String serverUrl) { + this.serverUrl = serverUrl; + this.sonarProduct = SonarProduct.fromUrl(serverUrl); + } + + public WizardModel(ServerConnectionWithAuth connectionWithAuth) { + var connection = connectionWithAuth.getConnection(); + this.sonarProduct = connection.getProduct(); + this.notificationsDisabled = connection.getNotificationsDisabled(); + this.serverUrl = connection.getHostUrl(); + if (sonarProduct == SonarProduct.SONARCLOUD) { + this.organizationKey = ((SonarCloudConnection) connection).getOrganizationKey(); } - this.proxyEnabled = connectionToEdit.enableProxy(); - this.token = connectionToEdit.getToken(); - this.login = connectionToEdit.getLogin(); - var pass = connectionToEdit.getPassword(); + this.name = connection.getName(); + var credentials = connectionWithAuth.getCredentials(); + this.token = credentials.getToken(); + this.login = credentials.getLogin(); + var pass = credentials.getPassword(); if (pass != null) { this.password = pass.toCharArray(); } - this.organizationKey = connectionToEdit.getOrganizationKey(); - this.notificationsDisabled = connectionToEdit.isDisableNotifications(); - this.name = connectionToEdit.getName(); } @CheckForNull - public ServerType getServerType() { - return serverType; - } - - public WizardModel setServerType(ServerType serverType) { - this.serverType = serverType; - return this; + public SonarProduct getServerProduct() { + return sonarProduct; } public boolean isSonarCloud() { - return ServerType.SONARCLOUD.equals(serverType); + return sonarProduct == SonarProduct.SONARCLOUD; } public boolean isNotificationsSupported() { return notificationsSupported; } - public WizardModel setNotificationsSupported(boolean notificationsSupported) { + public void setNotificationsSupported(boolean notificationsSupported) { this.notificationsSupported = notificationsSupported; - return this; } public void queryIfNotificationsSupported() throws Exception { - final var partialConnection = createConnectionWithoutOrganization(); + final var partialConnection = createPartialConnection(); var task = new CheckNotificationsSupportedTask(partialConnection); ProgressManager.getInstance().run(task); if (task.getException() != null) { @@ -110,7 +106,7 @@ public void queryIfNotificationsSupported() throws Exception { public void queryOrganizations() throws Exception { if (isSonarCloud()) { - final ServerConnection partialConnection = createConnectionWithoutOrganization(); + final var partialConnection = createPartialConnection(); final var task = buildAndRunGetOrganizationsTask(partialConnection); setOrganizationList(task.organizations()); final var presetOrganizationKey = getOrganizationKey(); @@ -124,7 +120,7 @@ public void queryOrganizations() throws Exception { } } - private static GetOrganizationsTask buildAndRunGetOrganizationsTask(ServerConnection partialConnection) throws Exception { + private static GetOrganizationsTask buildAndRunGetOrganizationsTask(PartialConnection partialConnection) throws Exception { var task = new GetOrganizationsTask(partialConnection); ProgressManager.getInstance().run(task); if (task.getException() != null) { @@ -133,7 +129,7 @@ private static GetOrganizationsTask buildAndRunGetOrganizationsTask(ServerConnec return task; } - private void addPresetOrganization(ServerConnection partialConnection, GetOrganizationsTask task, String presetOrganizationKey) { + private void addPresetOrganization(PartialConnection partialConnection, GetOrganizationsTask task, String presetOrganizationKey) { // the previously configured organization might not be in the list. If that's the case, fetch it and add it to the list. var orgExists = task.organizations().stream().anyMatch(o -> o.getKey().equals(presetOrganizationKey)); if (!orgExists) { @@ -153,27 +149,16 @@ public boolean isNotificationsDisabled() { return notificationsDisabled; } - public WizardModel setNotificationsDisabled(boolean notificationsDisabled) { + public void setNotificationsDisabled(boolean notificationsDisabled) { this.notificationsDisabled = notificationsDisabled; - return this; - } - - public boolean isProxyEnabled() { - return proxyEnabled; - } - - public WizardModel setProxyEnabled(boolean proxyEnabled) { - this.proxyEnabled = proxyEnabled; - return this; } public List getOrganizationList() { return organizationList; } - public WizardModel setOrganizationList(List organizationList) { + public void setOrganizationList(List organizationList) { this.organizationList = organizationList; - return this; } @CheckForNull @@ -181,39 +166,37 @@ public String getServerUrl() { return serverUrl; } - public WizardModel setServerUrl(@Nullable String serverUrl) { - this.serverUrl = serverUrl; - return this; + public void setIsSonarCloud() { + this.sonarProduct = SonarProduct.SONARCLOUD; + this.serverUrl = SONARCLOUD_URL; } - @CheckForNull - public String getToken() { - return token; + public void setIsSonarQube(String serverUrl) { + this.sonarProduct = SonarProduct.SONARQUBE; + this.serverUrl = serverUrl; } - public WizardModel setToken(@Nullable String token) { + public void setToken(@Nullable String token) { this.token = token; - return this; + this.login = null; + this.password = null; } - @CheckForNull - public String getLogin() { - return login; - } - - public WizardModel setLogin(@Nullable String login) { + public void setLoginPassword(String login, char[] password) { this.login = login; - return this; + this.password = password; + this.token = null; } @CheckForNull - public char[] getPassword() { - return password; - } - - public WizardModel setPassword(@Nullable char[] password) { - this.password = password; - return this; + public ServerConnectionCredentials getCredentials() { + if (token != null) { + return new ServerConnectionCredentials(null, null, token); + } + if (login != null && password != null) { + return new ServerConnectionCredentials(login, String.valueOf(password), null); + } + return null; } @CheckForNull @@ -236,42 +219,25 @@ public WizardModel setOrganizationKey(@Nullable String organization) { return this; } - public ServerConnection createConnectionWithoutOrganization() { - return createConnection(null); - } - - public ServerConnection createConnection() { - return createConnection(organizationKey); - } - - private ServerConnection.Builder createUnauthenticatedConnection(@Nullable String organizationKey) { - var builder = ServerConnection.newBuilder() - .setOrganizationKey(organizationKey) - .setEnableProxy(proxyEnabled) - .setName(name); - - if (serverType == ServerType.SONARCLOUD) { - builder.setHostUrl(SONARCLOUD_URL); - - } else { - builder.setHostUrl(serverUrl); + public PartialConnection createPartialConnection() { + String pass = null; + if (password != null) { + pass = String.valueOf(password); } - builder.setDisableNotifications(notificationsDisabled); - return builder; + return new PartialConnection(serverUrl, sonarProduct, organizationKey, new ServerConnectionCredentials(login, pass, token)); } - private ServerConnection createConnection(@Nullable String organizationKey) { - var builder = createUnauthenticatedConnection(organizationKey); - - if (token != null) { - builder.setToken(token) - .setLogin(null) - .setPassword(null); + public ServerConnectionWithAuth createConnectionWithAuth() { + ServerConnection connection; + String pass = null; + if (sonarProduct == SonarProduct.SONARCLOUD) { + connection = new SonarCloudConnection(name, organizationKey, notificationsDisabled); } else { - builder.setToken(null) - .setLogin(login) - .setPassword(new String(password)); + if (password != null) { + pass = String.valueOf(password); + } + connection = new SonarQubeConnection(name, serverUrl, notificationsDisabled); } - return builder.build(); + return new ServerConnectionWithAuth(connection, new ServerConnectionCredentials(login, pass, token)); } } diff --git a/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectBindPanel.java b/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectBindPanel.java index c023ab4e21..a691e7b36b 100644 --- a/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectBindPanel.java +++ b/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectBindPanel.java @@ -64,7 +64,9 @@ import org.apache.commons.lang.StringUtils; import org.sonarlint.intellij.SonarLintIcons; import org.sonarlint.intellij.config.global.ServerConnection; +import org.sonarlint.intellij.config.global.ServerConnectionService; import org.sonarlint.intellij.config.global.SonarLintGlobalConfigurable; +import org.sonarlint.intellij.config.global.SonarQubeConnection; import org.sonarlint.intellij.tasks.BindingStorageUpdateTask; import org.sonarlint.intellij.tasks.ServerDownloadProjectTask; import org.sonarsource.sonarlint.core.serverapi.component.ServerProject; @@ -73,11 +75,10 @@ import static java.awt.GridBagConstraints.NONE; import static java.awt.GridBagConstraints.WEST; import static java.util.Optional.ofNullable; -import static org.sonarlint.intellij.config.Settings.getGlobalSettings; public class SonarLintProjectBindPanel { private static final String CONNECTION_EMPTY_TEXT = ""; - + private static final ServerConnection PLACEHOLDER_CONNECTION = new SonarQubeConnection(CONNECTION_EMPTY_TEXT, "placeholderHost", false); private JPanel rootPanel; private JBCheckBox bindEnable; @@ -152,7 +153,7 @@ private void onConnectionSelected() { projectKeyTextField.setEnabled(isAnyConnectionSelected); projectKeyTextField.setEditable(isAnyConnectionSelected); searchProjectButton.setEnabled(isAnyConnectionSelected); - updateStorageButton.setEnabled(isAnyConnectionSelected && getGlobalSettings().connectionExists(selectedConnection.getName())); + updateStorageButton.setEnabled(isAnyConnectionSelected && ServerConnectionService.getInstance().connectionExists(selectedConnection.getName())); } /** @@ -191,17 +192,14 @@ private void setConnectionList(Collection connections, @Nullab if (connections.isEmpty()) { connectionComboBox.setEnabled(false); - var connection = ServerConnection.newBuilder() - .setName(CONNECTION_EMPTY_TEXT) - .build(); - connectionComboBox.setPrototypeDisplayValue(connection); + connectionComboBox.setPrototypeDisplayValue(PLACEHOLDER_CONNECTION); // ensure this is called, even when nothing is selected } else { connectionComboBox.setEnabled(bindEnable.isSelected()); var i = 0; var selectedIndex = -1; for (var connection : connections) { - if (previousSelectedStorageId != null && connection.getName() != null && previousSelectedStorageId.equals(connection.getName())) { + if (previousSelectedStorageId != null && previousSelectedStorageId.equals(connection.getName())) { selectedIndex = i; } connectionComboBox.setPrototypeDisplayValue(null); diff --git a/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectConfigurable.java b/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectConfigurable.java index 440c654575..5994cf5762 100644 --- a/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectConfigurable.java +++ b/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectConfigurable.java @@ -33,6 +33,7 @@ import org.jetbrains.concurrency.Promise; import org.sonarlint.intellij.analysis.AnalysisSubmitter; import org.sonarlint.intellij.config.global.ServerConnection; +import org.sonarlint.intellij.config.global.ServerConnectionService; import org.sonarlint.intellij.config.global.SonarLintGlobalConfigurable; import org.sonarlint.intellij.core.ProjectBindingManager; import org.sonarlint.intellij.messages.GlobalConfigurationListener; @@ -41,7 +42,6 @@ import org.sonarlint.intellij.trigger.TriggerType; import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; -import static org.sonarlint.intellij.config.Settings.getGlobalSettings; import static org.sonarlint.intellij.config.Settings.getSettingsFor; /** @@ -109,9 +109,9 @@ public void reset() { } getServersFromApplicationConfigurable() - .onProcessed(sonarQubeServers -> { + .onProcessed(connections -> { var projectSettings = getSettingsFor(project); - panel.load(sonarQubeServers != null ? sonarQubeServers : getGlobalSettings().getServerConnections(), + panel.load(connections != null ? connections : ServerConnectionService.getInstance().getConnections(), projectSettings, getService(project, ProjectBindingManager.class).getModuleOverrides()); }); diff --git a/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectSettingsPanel.java b/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectSettingsPanel.java index 83dc01b9a5..5376ce7c26 100644 --- a/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectSettingsPanel.java +++ b/src/main/java/org/sonarlint/intellij/config/project/SonarLintProjectSettingsPanel.java @@ -31,9 +31,9 @@ import javax.swing.JPanel; import org.sonarlint.intellij.config.global.ServerConnection; import org.sonarlint.intellij.core.ProjectBindingManager; +import org.sonarlint.intellij.config.global.ServerConnectionService; import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; -import static org.sonarlint.intellij.config.Settings.getGlobalSettings; public class SonarLintProjectSettingsPanel implements Disposable { private final SonarLintProjectPropertiesPanel propsPanel; @@ -67,9 +67,9 @@ public JPanel getRootPane() { return root; } - public void load(List servers, SonarLintProjectSettings projectSettings, Map moduleOverrides) { + public void load(List connections, SonarLintProjectSettings projectSettings, Map moduleOverrides) { propsPanel.setAnalysisProperties(projectSettings.getAdditionalProperties()); - bindPanel.load(servers, projectSettings, moduleOverrides); + bindPanel.load(connections, projectSettings, moduleOverrides); exclusionsPanel.load(projectSettings); } @@ -82,7 +82,7 @@ public void save(Project project, SonarLintProjectSettings projectSettings) thro if (selectedConnection == null) { throw new ConfigurationException("Connection should not be empty"); } - if (!getGlobalSettings().connectionExists(selectedConnection.getName())) { + if (!ServerConnectionService.getInstance().connectionExists(selectedConnection.getName())) { throw new ConfigurationException("Connection should be saved first"); } if (selectedProjectKey == null || selectedProjectKey.isBlank()) { diff --git a/src/main/java/org/sonarlint/intellij/core/BackendService.kt b/src/main/java/org/sonarlint/intellij/core/BackendService.kt index 673dcefcf8..d1ef18d954 100644 --- a/src/main/java/org/sonarlint/intellij/core/BackendService.kt +++ b/src/main/java/org/sonarlint/intellij/core/BackendService.kt @@ -46,11 +46,14 @@ import org.sonarlint.intellij.common.vcs.VcsService import org.sonarlint.intellij.config.Settings.getGlobalSettings import org.sonarlint.intellij.config.Settings.getSettingsFor import org.sonarlint.intellij.config.global.ServerConnection -import org.sonarlint.intellij.config.global.SonarLintGlobalSettings +import org.sonarlint.intellij.config.global.ServerConnectionService +import org.sonarlint.intellij.config.global.SonarCloudConnection +import org.sonarlint.intellij.config.global.SonarQubeConnection +import org.sonarlint.intellij.config.global.wizard.PartialConnection import org.sonarlint.intellij.finding.LiveFinding import org.sonarlint.intellij.finding.hotspot.LiveSecurityHotspot import org.sonarlint.intellij.finding.issue.LiveIssue -import org.sonarlint.intellij.messages.GlobalConfigurationListener +import org.sonarlint.intellij.messages.ServerConnectionsListener import org.sonarlint.intellij.telemetry.TelemetryManagerProvider import org.sonarlint.intellij.util.GlobalLogOutput import org.sonarlint.intellij.util.ProjectUtils.getRelativePaths @@ -116,11 +119,11 @@ class BackendService @NonInjectable constructor(private val backend: SonarLintBa private val initializedBackend: SonarLintBackend by lazy { migrateStoragePath() - val serverConnections = getGlobalSettings().serverConnections + val serverConnections = ServerConnectionService.getInstance().getConnections() val sonarCloudConnections = - serverConnections.filter { it.isSonarCloud }.map { toSonarCloudBackendConnection(it) } + serverConnections.filterIsInstance().map { toSonarCloudBackendConnection(it) } val sonarQubeConnections = - serverConnections.filter { !it.isSonarCloud }.map { toSonarQubeBackendConnection(it) } + serverConnections.filterIsInstance().map { toSonarQubeBackendConnection(it) } backend.initialize( InitializeParams( ClientInfoDto( @@ -143,18 +146,13 @@ class BackendService @NonInjectable constructor(private val backend: SonarLintBa ) ).thenRun { ApplicationManager.getApplication().messageBus.connect() - .subscribe(GlobalConfigurationListener.TOPIC, object : GlobalConfigurationListener.Adapter() { - override fun applied(previousSettings: SonarLintGlobalSettings, newSettings: SonarLintGlobalSettings) { - connectionsUpdated(newSettings.serverConnections) - val changedConnections = newSettings.serverConnections.filter { connection -> - val previousConnection = previousSettings.getServerConnectionByName(connection.name) - previousConnection.isPresent && !connection.hasSameCredentials(previousConnection.get()) - } - credentialsChanged(changedConnections) + .subscribe(ServerConnectionsListener.TOPIC, object : ServerConnectionsListener.Adapter() { + override fun afterChange(allConnections: List) { + connectionsUpdated(allConnections) } - override fun changed(serverList: MutableList) { - connectionsUpdated(serverList) + override fun credentialsChanged(changedConnections: List) { + notifyCredentialsChanged(changedConnections) } }) ApplicationManager.getApplication().messageBus.connect() @@ -196,28 +194,28 @@ class BackendService @NonInjectable constructor(private val backend: SonarLintBa private fun getLocalStoragePath(): Path = Paths.get(PathManager.getSystemPath()).resolve("sonarlint/storage") fun connectionsUpdated(serverConnections: List) { - val scConnections = serverConnections.filter { it.isSonarCloud }.map { toSonarCloudBackendConnection(it) } - val sqConnections = serverConnections.filter { !it.isSonarCloud }.map { toSonarQubeBackendConnection(it) } + val scConnections = serverConnections.filterIsInstance().map { toSonarCloudBackendConnection(it) } + val sqConnections = serverConnections.filterIsInstance().map { toSonarQubeBackendConnection(it) } initializedBackend.connectionService.didUpdateConnections(DidUpdateConnectionsParams(sqConnections, scConnections)) } - private fun credentialsChanged(connections: List) { + private fun notifyCredentialsChanged(connections: List) { connections.forEach { initializedBackend.connectionService.didChangeCredentials(DidChangeCredentialsParams(it.name)) } } - private fun toSonarQubeBackendConnection(createdConnection: ServerConnection): SonarQubeConnectionConfigurationDto { + private fun toSonarQubeBackendConnection(connection: SonarQubeConnection): SonarQubeConnectionConfigurationDto { return SonarQubeConnectionConfigurationDto( - createdConnection.name, - createdConnection.hostUrl, - createdConnection.isDisableNotifications + connection.name, + connection.hostUrl, + connection.notificationsDisabled ) } - private fun toSonarCloudBackendConnection(createdConnection: ServerConnection): SonarCloudConnectionConfigurationDto { + private fun toSonarCloudBackendConnection(connection: SonarCloudConnection): SonarCloudConnectionConfigurationDto { return SonarCloudConnectionConfigurationDto( - createdConnection.name, - createdConnection.organizationKey!!, - createdConnection.isDisableNotifications + connection.name, + connection.organizationKey, + connection.notificationsDisabled ) } @@ -417,42 +415,39 @@ class BackendService @NonInjectable constructor(private val backend: SonarLintBa ) } - fun checkSmartNotificationsSupported(server: ServerConnection): CompletableFuture { - val credentials: Either = server.token?.let { Either.forLeft(TokenDto(server.token)) } - ?: Either.forRight(UsernamePasswordDto(server.login, server.password)) - val params: CheckSmartNotificationsSupportedParams = if (server.isSonarCloud) { - CheckSmartNotificationsSupportedParams(TransientSonarCloudConnectionDto(server.organizationKey, credentials)) + fun checkSmartNotificationsSupported(connection: PartialConnection): CompletableFuture { + val params: CheckSmartNotificationsSupportedParams = if (connection.sonarProduct == SonarProduct.SONARCLOUD) { + CheckSmartNotificationsSupportedParams(TransientSonarCloudConnectionDto(connection.organizationKey, getCredentials(connection))) } else { - CheckSmartNotificationsSupportedParams(TransientSonarQubeConnectionDto(server.hostUrl, credentials)) + CheckSmartNotificationsSupportedParams(TransientSonarQubeConnectionDto(connection.hostUrl, getCredentials(connection))) } return initializedBackend.connectionService.checkSmartNotificationsSupported(params) } - fun validateConnection(server: ServerConnection): CompletableFuture { - val credentials: Either = server.token?.let { Either.forLeft(TokenDto(server.token)) } - ?: Either.forRight(UsernamePasswordDto(server.login, server.password)) - val params: ValidateConnectionParams = if (server.isSonarCloud) { - ValidateConnectionParams(TransientSonarCloudConnectionDto(server.organizationKey, credentials)) + fun validateConnection(connection: PartialConnection): CompletableFuture { + val params: ValidateConnectionParams = if (connection.sonarProduct == SonarProduct.SONARCLOUD) { + ValidateConnectionParams(TransientSonarCloudConnectionDto(connection.organizationKey, getCredentials(connection))) } else { - ValidateConnectionParams(TransientSonarQubeConnectionDto(server.hostUrl, credentials)) + ValidateConnectionParams(TransientSonarQubeConnectionDto(connection.hostUrl, getCredentials(connection))) } return initializedBackend.connectionService.validateConnection(params) } - fun listUserOrganizations(server: ServerConnection): CompletableFuture { - val credentials: Either = server.token?.let { Either.forLeft(TokenDto(server.token)) } - ?: Either.forRight(UsernamePasswordDto(server.login, server.password)) - val params = ListUserOrganizationsParams(credentials) + fun listUserOrganizations(connection: PartialConnection): CompletableFuture { + val params = ListUserOrganizationsParams(getCredentials(connection)) return initializedBackend.connectionService.listUserOrganizations(params) } - fun getOrganization(server: ServerConnection, organizationKey: String): CompletableFuture { - val credentials: Either = server.token?.let { Either.forLeft(TokenDto(server.token)) } - ?: Either.forRight(UsernamePasswordDto(server.login, server.password)) - val params = GetOrganizationParams(credentials, organizationKey) + fun getOrganization(connection: PartialConnection, organizationKey: String): CompletableFuture { + val params = GetOrganizationParams(getCredentials(connection), organizationKey) return initializedBackend.connectionService.getOrganization(params) } + private fun getCredentials(connection: PartialConnection): Either { + return connection.credentials.token?.let { token -> Either.forLeft(TokenDto(token)) } + ?: Either.forRight(UsernamePasswordDto(connection.credentials.login, connection.credentials.password)) + } + fun trackWithServerIssues( module: Module, liveIssuesByFile: Map>, diff --git a/src/main/java/org/sonarlint/intellij/core/DefaultEngineManager.java b/src/main/java/org/sonarlint/intellij/core/DefaultEngineManager.java index 8ae7051a74..99b082bfb0 100644 --- a/src/main/java/org/sonarlint/intellij/core/DefaultEngineManager.java +++ b/src/main/java/org/sonarlint/intellij/core/DefaultEngineManager.java @@ -28,10 +28,9 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.sonarlint.intellij.config.global.ServerConnection; +import org.sonarlint.intellij.config.global.ServerConnectionService; import org.sonarlint.intellij.exception.InvalidBindingException; import org.sonarlint.intellij.notifications.AnalysisRequirementNotifications; import org.sonarlint.intellij.notifications.SonarLintProjectNotifications; @@ -40,7 +39,6 @@ import org.sonarsource.sonarlint.core.client.api.standalone.StandaloneSonarLintEngine; import static com.intellij.openapi.progress.PerformInBackgroundOption.ALWAYS_BACKGROUND; -import static org.sonarlint.intellij.config.Settings.getGlobalSettings; public class DefaultEngineManager implements EngineManager, Disposable { protected final Map connectedEngines = new HashMap<>(); @@ -114,7 +112,7 @@ public synchronized ConnectedSonarLintEngine getConnectedEngine(String connectio @NotNull private ConnectedSonarLintEngine createConnectedEngine(String connectionId) { - return getGlobalSettings().getServerConnectionByName(connectionId) + return ServerConnectionService.getInstance().getServerConnectionByName(connectionId) .map(connection -> factory.createEngine(connectionId, connection.isSonarCloud())) .orElseThrow(() -> new IllegalStateException("Unable to find a configured connection with id '" + connectionId + "'")); } @@ -143,9 +141,7 @@ public ConnectedSonarLintEngine getConnectedEngine(SonarLintProjectNotifications } private static Set getServerNames() { - return getGlobalSettings().getServerConnections().stream() - .map(ServerConnection::getName) - .collect(Collectors.toSet()); + return ServerConnectionService.getInstance().getServerNames(); } @Override diff --git a/src/main/java/org/sonarlint/intellij/core/ProjectBindingManager.java b/src/main/java/org/sonarlint/intellij/core/ProjectBindingManager.java index 680437bf74..41e3cba0bf 100644 --- a/src/main/java/org/sonarlint/intellij/core/ProjectBindingManager.java +++ b/src/main/java/org/sonarlint/intellij/core/ProjectBindingManager.java @@ -39,6 +39,7 @@ import org.sonarlint.intellij.common.ui.SonarLintConsole; import org.sonarlint.intellij.common.vcs.VcsService; import org.sonarlint.intellij.config.global.ServerConnection; +import org.sonarlint.intellij.config.global.ServerConnectionService; import org.sonarlint.intellij.exception.InvalidBindingException; import org.sonarlint.intellij.messages.ProjectBindingListenerKt; import org.sonarlint.intellij.notifications.SonarLintProjectNotifications; @@ -49,7 +50,6 @@ import static java.util.Objects.requireNonNull; import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; import static org.sonarlint.intellij.common.util.SonarLintUtils.isBlank; -import static org.sonarlint.intellij.config.Settings.getGlobalSettings; import static org.sonarlint.intellij.config.Settings.getSettingsFor; @Service(Service.Level.PROJECT) @@ -145,7 +145,7 @@ public Optional tryGetServerConnection() { return Optional.empty(); } var connectionName = getSettingsFor(myProject).getConnectionName(); - var connections = getGlobalSettings().getServerConnections(); + var connections = ServerConnectionService.getInstance().getConnections(); return connections.stream().filter(s -> s.getName().equals(connectionName)).findAny(); } diff --git a/src/main/java/org/sonarlint/intellij/core/ServerProductDocumentation.kt b/src/main/java/org/sonarlint/intellij/core/ServerProductDocumentation.kt new file mode 100644 index 0000000000..c58f105e3b --- /dev/null +++ b/src/main/java/org/sonarlint/intellij/core/ServerProductDocumentation.kt @@ -0,0 +1,32 @@ +/* + * 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.core + +sealed interface ServerProductDocumentation { + fun smartNotificationsHelp(): String +} + +object SonarCloudDocumentation : ServerProductDocumentation { + override fun smartNotificationsHelp() = "https://docs.sonarcloud.io/advanced-setup/sonarlint-smart-notifications/" +} + +object SonarQubeDocumentation : ServerProductDocumentation { + override fun smartNotificationsHelp() = "https://docs.sonarqube.org/latest/user-guide/sonarlint-connected-mode/" +} diff --git a/src/main/java/org/sonarlint/intellij/core/SonarProduct.kt b/src/main/java/org/sonarlint/intellij/core/SonarProduct.kt new file mode 100644 index 0000000000..1b20e8b67c --- /dev/null +++ b/src/main/java/org/sonarlint/intellij/core/SonarProduct.kt @@ -0,0 +1,33 @@ +/* + * 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.core + +import javax.swing.Icon +import org.sonarlint.intellij.SonarLintIcons +import org.sonarlint.intellij.common.util.SonarLintUtils + +enum class SonarProduct(val productName: String, val icon: Icon, val documentation: ServerProductDocumentation) { + SONARQUBE("SonarQube", SonarLintIcons.ICON_SONARQUBE_16, SonarQubeDocumentation), + SONARCLOUD("SonarCloud", SonarLintIcons.ICON_SONARCLOUD_16, SonarCloudDocumentation); + companion object { + @JvmStatic + fun fromUrl(serverUrl: String) = if (SonarLintUtils.isSonarCloudAlias(serverUrl)) SONARCLOUD else SONARQUBE + } +} \ No newline at end of file diff --git a/src/main/java/org/sonarlint/intellij/messages/ServerConnectionsListener.java b/src/main/java/org/sonarlint/intellij/messages/ServerConnectionsListener.java new file mode 100644 index 0000000000..f8f99dc5ba --- /dev/null +++ b/src/main/java/org/sonarlint/intellij/messages/ServerConnectionsListener.java @@ -0,0 +1,43 @@ +/* + * 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.messages; + +import com.intellij.util.messages.Topic; +import java.util.List; +import org.sonarlint.intellij.config.global.ServerConnection; + +public interface ServerConnectionsListener { + Topic TOPIC = Topic.create("Server connections events", ServerConnectionsListener.class); + + void afterChange(List allConnections); + void credentialsChanged(List changedConnections); + + abstract class Adapter implements ServerConnectionsListener { + @Override + public void afterChange(List allConnections) { + // empty implementation + } + + @Override + public void credentialsChanged(List changedConnections) { + // empty implementation + } + } +} diff --git a/src/main/java/org/sonarlint/intellij/notifications/ConfigureNotificationsAction.kt b/src/main/java/org/sonarlint/intellij/notifications/ConfigureNotificationsAction.kt index 0a3296486d..0322b784c5 100644 --- a/src/main/java/org/sonarlint/intellij/notifications/ConfigureNotificationsAction.kt +++ b/src/main/java/org/sonarlint/intellij/notifications/ConfigureNotificationsAction.kt @@ -25,29 +25,28 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.Project import com.intellij.openapi.wm.WindowManager import org.sonarlint.intellij.common.ui.SonarLintConsole -import org.sonarlint.intellij.config.Settings +import org.sonarlint.intellij.config.global.ServerConnectionService import org.sonarlint.intellij.config.global.wizard.ServerConnectionWizard import org.sonarlint.intellij.ui.UiUtils.Companion.runOnUiThread +import org.sonarlint.intellij.util.runOnPooledThread class ConfigureNotificationsAction(private val connectionName: String, private val project: Project) : NotificationAction("Configure") { override fun actionPerformed(e: AnActionEvent, notification: Notification) { - WindowManager.getInstance().getFrame(e.project) ?: return - runOnUiThread(project) { - val connectionToEdit = Settings.getGlobalSettings().serverConnections.find { it.name == connectionName } - if (connectionToEdit != null) { - val wizard = ServerConnectionWizard.forNotificationsEdition(connectionToEdit) - if (wizard.showAndGet()) { - val editedConnection = wizard.connection - val serverConnections = Settings.getGlobalSettings().serverConnections.toMutableList() - serverConnections[serverConnections.indexOf(connectionToEdit)] = editedConnection - Settings.getGlobalSettings().serverConnections = serverConnections - } - } else if (e.project != null) { - SonarLintConsole.get(e.project!!).error("Unable to find connection with name: $connectionName") - notification.expire() - } + WindowManager.getInstance().getFrame(project) ?: return + runOnPooledThread(project) { + ServerConnectionService.getInstance().getServerConnectionWithAuthByName(connectionName) + .ifPresentOrElse({ + runOnUiThread(project) { + val wizard = ServerConnectionWizard.forNotificationsEdition(it) + if (wizard.showAndGet()) { + runOnPooledThread(project) { ServerConnectionService.getInstance().replaceConnection(wizard.connectionWithAuth) } + } + } + }, { + SonarLintConsole.get(project).error("Unable to find connection with name: $connectionName") + notification.expire() + }) } } - } diff --git a/src/main/java/org/sonarlint/intellij/notifications/SonarLintProjectNotifications.java b/src/main/java/org/sonarlint/intellij/notifications/SonarLintProjectNotifications.java index f6b1163484..6852b8aa2f 100644 --- a/src/main/java/org/sonarlint/intellij/notifications/SonarLintProjectNotifications.java +++ b/src/main/java/org/sonarlint/intellij/notifications/SonarLintProjectNotifications.java @@ -29,8 +29,8 @@ import java.util.Arrays; import java.util.List; import org.sonarlint.intellij.SonarLintIcons; -import org.sonarlint.intellij.config.Settings; import org.sonarlint.intellij.config.global.ServerConnection; +import org.sonarlint.intellij.config.global.ServerConnectionService; import org.sonarlint.intellij.notifications.binding.BindProjectAction; import org.sonarlint.intellij.notifications.binding.BindingSuggestion; import org.sonarlint.intellij.notifications.binding.ChooseBindingSuggestionAction; @@ -144,7 +144,7 @@ public void expireCurrentFindingNotificationIfNeeded() { } public void handle(ShowSmartNotificationParams smartNotificationParams) { - var connection = Settings.getGlobalSettings().getServerConnectionByName(smartNotificationParams.getConnectionId()); + var connection = ServerConnectionService.getInstance().getServerConnectionByName(smartNotificationParams.getConnectionId()); if (connection.isEmpty()) { GlobalLogOutput.get().log("Connection ID of smart notification should not be null", ClientLogOutput.Level.WARN); return; diff --git a/src/main/java/org/sonarlint/intellij/notifications/binding/BindProjectAction.kt b/src/main/java/org/sonarlint/intellij/notifications/binding/BindProjectAction.kt index b8038d5b9c..f72a2fdf3d 100644 --- a/src/main/java/org/sonarlint/intellij/notifications/binding/BindProjectAction.kt +++ b/src/main/java/org/sonarlint/intellij/notifications/binding/BindProjectAction.kt @@ -24,8 +24,8 @@ import com.intellij.openapi.actionSystem.AnActionEvent import org.sonarlint.intellij.actions.AbstractSonarAction import org.sonarlint.intellij.common.ui.SonarLintConsole import org.sonarlint.intellij.common.util.SonarLintUtils -import org.sonarlint.intellij.config.Settings.getGlobalSettings import org.sonarlint.intellij.core.ProjectBindingManager +import org.sonarlint.intellij.config.global.ServerConnectionService class BindProjectAction(private val bindingSuggestion: BindingSuggestion) : AbstractSonarAction("Bind project") { override fun actionPerformed(e: AnActionEvent) { @@ -34,7 +34,7 @@ class BindProjectAction(private val bindingSuggestion: BindingSuggestion) : Abst val project = e.project!! val bindingManager = SonarLintUtils.getService(project, ProjectBindingManager::class.java) val connectionId = bindingSuggestion.connectionId - getGlobalSettings().getServerConnectionByName(connectionId) + ServerConnectionService.getInstance().getServerConnectionByName(connectionId) .ifPresentOrElse( { connection -> bindingManager.bindTo( diff --git a/src/main/java/org/sonarlint/intellij/notifications/binding/ChooseBindingSuggestionAction.kt b/src/main/java/org/sonarlint/intellij/notifications/binding/ChooseBindingSuggestionAction.kt index c411222f03..681d18d8cb 100644 --- a/src/main/java/org/sonarlint/intellij/notifications/binding/ChooseBindingSuggestionAction.kt +++ b/src/main/java/org/sonarlint/intellij/notifications/binding/ChooseBindingSuggestionAction.kt @@ -24,8 +24,8 @@ import com.intellij.notification.NotificationAction import com.intellij.openapi.actionSystem.AnActionEvent import org.sonarlint.intellij.common.ui.SonarLintConsole import org.sonarlint.intellij.common.util.SonarLintUtils.getService -import org.sonarlint.intellij.config.Settings.getGlobalSettings import org.sonarlint.intellij.core.ProjectBindingManager +import org.sonarlint.intellij.config.global.ServerConnectionService import org.sonarlint.intellij.ui.BindingSuggestionSelectionDialog class ChooseBindingSuggestionAction(private val suggestedBindings: List) : @@ -36,7 +36,7 @@ class ChooseBindingSuggestionAction(private val suggestedBindings: List getService(project, ProjectBindingManager::class.java).bindTo( diff --git a/src/main/java/org/sonarlint/intellij/tasks/CheckNotificationsSupportedTask.java b/src/main/java/org/sonarlint/intellij/tasks/CheckNotificationsSupportedTask.java index f8c646723c..1ef2d5c86e 100644 --- a/src/main/java/org/sonarlint/intellij/tasks/CheckNotificationsSupportedTask.java +++ b/src/main/java/org/sonarlint/intellij/tasks/CheckNotificationsSupportedTask.java @@ -26,6 +26,7 @@ import org.sonarlint.intellij.common.ui.SonarLintConsole; import org.sonarlint.intellij.common.util.SonarLintUtils; import org.sonarlint.intellij.config.global.ServerConnection; +import org.sonarlint.intellij.config.global.wizard.PartialConnection; import org.sonarlint.intellij.core.BackendService; import static org.sonarlint.intellij.util.ProgressUtils.waitForFuture; @@ -34,11 +35,11 @@ * Only useful for SonarQube, since we know notifications are available in SonarCloud */ public class CheckNotificationsSupportedTask extends Task.Modal { - private final ServerConnection connection; + private final PartialConnection connection; private Exception exception; private boolean notificationsSupported = false; - public CheckNotificationsSupportedTask(ServerConnection connection) { + public CheckNotificationsSupportedTask(PartialConnection connection) { super(null, "Check if smart notifications are supported", true); this.connection = connection; } diff --git a/src/main/java/org/sonarlint/intellij/tasks/ConnectionTestTask.kt b/src/main/java/org/sonarlint/intellij/tasks/ConnectionTestTask.kt index 423b0837bd..0a150183ac 100644 --- a/src/main/java/org/sonarlint/intellij/tasks/ConnectionTestTask.kt +++ b/src/main/java/org/sonarlint/intellij/tasks/ConnectionTestTask.kt @@ -23,21 +23,21 @@ import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.Task import org.sonarlint.intellij.common.util.SonarLintUtils -import org.sonarlint.intellij.config.global.ServerConnection +import org.sonarlint.intellij.config.global.wizard.PartialConnection import org.sonarlint.intellij.core.BackendService import org.sonarlint.intellij.util.ProgressUtils.waitForFuture import org.sonarsource.sonarlint.core.clientapi.backend.connection.validate.ValidateConnectionResponse -class ConnectionTestTask(private val server: ServerConnection) : +class ConnectionTestTask(private val connection: PartialConnection) : Task.WithResult( - null, "Test Connection to " + if (server.isSonarCloud) "SonarCloud" else "SonarQube", true + null, "Test Connection to " + connection.sonarProduct.productName, true ) { override fun compute(indicator: ProgressIndicator): ValidateConnectionResponse? { - indicator.text = "Connecting to " + server.hostUrl + "..." + indicator.text = "Connecting to " + connection.hostUrl + "..." indicator.isIndeterminate = true return try { - waitForFuture(indicator, SonarLintUtils.getService(BackendService::class.java).validateConnection(server)) + waitForFuture(indicator, SonarLintUtils.getService(BackendService::class.java).validateConnection(connection)) } catch (e: ProcessCanceledException) { null } diff --git a/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationTask.java b/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationTask.java index 427fe8934d..6639981754 100644 --- a/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationTask.java +++ b/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationTask.java @@ -25,21 +25,22 @@ import org.sonarlint.intellij.common.ui.SonarLintConsole; import org.sonarlint.intellij.common.util.SonarLintUtils; import org.sonarlint.intellij.config.global.ServerConnection; +import org.sonarlint.intellij.config.global.wizard.PartialConnection; import org.sonarlint.intellij.core.BackendService; import org.sonarsource.sonarlint.core.clientapi.backend.connection.org.OrganizationDto; import static org.sonarlint.intellij.util.ProgressUtils.waitForFuture; public class GetOrganizationTask extends Task.Modal { - private final ServerConnection server; + private final PartialConnection connection; private final String organizationKey; private Exception exception; private OrganizationDto organization; - public GetOrganizationTask(ServerConnection server, String organizationKey) { + public GetOrganizationTask(PartialConnection connection, String organizationKey) { super(null, "Fetch Organization From SonarCloud", true); - this.server = server; + this.connection = connection; this.organizationKey = organizationKey; } @@ -50,7 +51,7 @@ public void run(@NotNull ProgressIndicator indicator) { try { indicator.setText("Searching organization"); - organization = waitForFuture(indicator, SonarLintUtils.getService(BackendService.class).getOrganization(server, organizationKey)).getOrganization(); + organization = waitForFuture(indicator, SonarLintUtils.getService(BackendService.class).getOrganization(connection, organizationKey)).getOrganization(); } catch (Exception e) { SonarLintConsole.get(myProject).error("Failed to fetch organizations", e); exception = e; diff --git a/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationsTask.java b/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationsTask.java index d941e12dff..3c2c8cda22 100644 --- a/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationsTask.java +++ b/src/main/java/org/sonarlint/intellij/tasks/GetOrganizationsTask.java @@ -26,6 +26,7 @@ import org.sonarlint.intellij.common.ui.SonarLintConsole; import org.sonarlint.intellij.common.util.SonarLintUtils; import org.sonarlint.intellij.config.global.ServerConnection; +import org.sonarlint.intellij.config.global.wizard.PartialConnection; import org.sonarlint.intellij.core.BackendService; import org.sonarsource.sonarlint.core.clientapi.backend.connection.org.OrganizationDto; @@ -35,11 +36,11 @@ * Only useful for SonarCloud */ public class GetOrganizationsTask extends Task.Modal { - private final ServerConnection connection; + private final PartialConnection connection; private Exception exception; private List organizations; - public GetOrganizationsTask(ServerConnection connection) { + public GetOrganizationsTask(PartialConnection connection) { super(null, "Fetch organizations from SonarCloud", true); this.connection = connection; } diff --git a/src/main/java/org/sonarlint/intellij/telemetry/TelemetryClientAttributeProviderImpl.kt b/src/main/java/org/sonarlint/intellij/telemetry/TelemetryClientAttributeProviderImpl.kt index dcdc7bdaff..5af3565190 100644 --- a/src/main/java/org/sonarlint/intellij/telemetry/TelemetryClientAttributeProviderImpl.kt +++ b/src/main/java/org/sonarlint/intellij/telemetry/TelemetryClientAttributeProviderImpl.kt @@ -27,7 +27,8 @@ import java.util.function.Predicate import java.util.stream.Collectors import org.sonarlint.intellij.common.util.SonarLintUtils import org.sonarlint.intellij.config.Settings -import org.sonarlint.intellij.config.global.ServerConnection +import org.sonarlint.intellij.config.global.ServerConnectionService +import org.sonarlint.intellij.config.global.SonarCloudConnection import org.sonarlint.intellij.core.EngineManager import org.sonarlint.intellij.core.NodeJsManager import org.sonarlint.intellij.core.ProjectBindingManager @@ -87,12 +88,12 @@ class TelemetryClientAttributeProviderImpl : TelemetryClientAttributesProvider { private fun isAnyProjectConnectedToSonarCloud(): Boolean = isAnyOpenProjectMatch { p: Project -> val bindingManager = SonarLintUtils.getService(p, ProjectBindingManager::class.java) bindingManager.tryGetServerConnection() - .filter { obj: ServerConnection -> obj.isSonarCloud } + .filter { it is SonarCloudConnection } .isPresent } - private fun isDevNotificationsDisabled(): Boolean = Settings.getGlobalSettings().serverConnections.stream() - .anyMatch { obj: ServerConnection -> obj.isDisableNotifications } + private fun isDevNotificationsDisabled(): Boolean = ServerConnectionService.getInstance().getConnections() + .any { it.notificationsDisabled } private fun isAnyOpenProjectMatch(predicate: Predicate): Boolean { return ProjectManager.getInstance().openProjects.any { predicate.test(it) } diff --git a/src/main/java/org/sonarlint/intellij/ui/BindingSuggestionSelectionDialog.kt b/src/main/java/org/sonarlint/intellij/ui/BindingSuggestionSelectionDialog.kt index be37dd8068..b1fc3adeb7 100644 --- a/src/main/java/org/sonarlint/intellij/ui/BindingSuggestionSelectionDialog.kt +++ b/src/main/java/org/sonarlint/intellij/ui/BindingSuggestionSelectionDialog.kt @@ -24,8 +24,7 @@ import com.intellij.ui.ColoredListCellRenderer import com.intellij.ui.SimpleTextAttributes import com.intellij.ui.components.JBList import javax.swing.JList -import org.sonarlint.intellij.SonarLintIcons -import org.sonarlint.intellij.config.Settings.getGlobalSettings +import org.sonarlint.intellij.config.global.ServerConnectionService import org.sonarlint.intellij.notifications.binding.BindingSuggestion open class BindingSuggestionSelectionDialog(bindingSuggestions: List) : @@ -59,9 +58,9 @@ class BindingSuggestionRenderer : hasFocus: Boolean ) { val attrs = SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES - getGlobalSettings().getServerConnectionByName(suggestion.connectionId) + ServerConnectionService.getInstance().getServerConnectionByName(suggestion.connectionId) .ifPresentOrElse({ - icon = if (it.isSonarCloud) SonarLintIcons.ICON_SONARCLOUD_16 else SonarLintIcons.ICON_SONARQUBE_16 + icon = it.product.icon append(suggestion.projectName, attrs, true) append(" (" + suggestion.projectKey + ")", SimpleTextAttributes.GRAY_ATTRIBUTES, false) append(" on connection [" + suggestion.connectionId + "]", SimpleTextAttributes.GRAY_ATTRIBUTES, false) diff --git a/src/main/java/org/sonarlint/intellij/ui/UiUtils.kt b/src/main/java/org/sonarlint/intellij/ui/UiUtils.kt index 11650fff38..e3955753a2 100644 --- a/src/main/java/org/sonarlint/intellij/ui/UiUtils.kt +++ b/src/main/java/org/sonarlint/intellij/ui/UiUtils.kt @@ -25,6 +25,13 @@ import com.intellij.openapi.project.Project class UiUtils { companion object { + @JvmStatic + fun runOnUiThread(modality: ModalityState, runnable: Runnable) { + ApplicationManager.getApplication().invokeLater({ + runnable.run() + }, modality) + } + @JvmStatic fun runOnUiThread(project: Project, runnable: Runnable) { runOnUiThread(project, ModalityState.defaultModalityState(), runnable) diff --git a/src/main/java/org/sonarlint/intellij/ui/nodes/IssueNode.java b/src/main/java/org/sonarlint/intellij/ui/nodes/IssueNode.java index f714d0067a..afb3726b47 100644 --- a/src/main/java/org/sonarlint/intellij/ui/nodes/IssueNode.java +++ b/src/main/java/org/sonarlint/intellij/ui/nodes/IssueNode.java @@ -82,7 +82,7 @@ private void doRender(TreeCellRenderer renderer) { var connection = serverConnection.get(); renderer.setIconToolTip(impactText + " impact on " + qualityText + " already detected by " + connection.getProductName() + " " + "analysis"); - setIcon(renderer, new CompoundIcon(CompoundIcon.Axis.X_AXIS, gap, connection.getProductIcon(), impactIcon)); + setIcon(renderer, new CompoundIcon(CompoundIcon.Axis.X_AXIS, gap, connection.getProduct().getIcon(), impactIcon)); } else { renderer.setIconToolTip(impactText + " impact on " + qualityText); var serverIconEmptySpace = SonarLintIcons.ICON_SONARQUBE_16.getIconWidth() + gap; @@ -103,7 +103,7 @@ private void doRender(TreeCellRenderer renderer) { if (issue.getServerFindingKey() != null && serverConnection.isPresent()) { var connection = serverConnection.get(); renderer.setIconToolTip(severityText + " " + typeStr + " already detected by " + connection.getProductName() + " analysis"); - setIcon(renderer, new CompoundIcon(CompoundIcon.Axis.X_AXIS, gap, connection.getProductIcon(), typeIcon, severityIcon)); + setIcon(renderer, new CompoundIcon(CompoundIcon.Axis.X_AXIS, gap, connection.getProduct().getIcon(), typeIcon, severityIcon)); } else { renderer.setIconToolTip(severityText + " " + typeStr); var serverIconEmptySpace = SonarLintIcons.ICON_SONARQUBE_16.getIconWidth() + gap; diff --git a/src/main/java/org/sonarlint/intellij/ui/nodes/LiveSecurityHotspotNode.java b/src/main/java/org/sonarlint/intellij/ui/nodes/LiveSecurityHotspotNode.java index 57db7cb019..77e40bd16f 100644 --- a/src/main/java/org/sonarlint/intellij/ui/nodes/LiveSecurityHotspotNode.java +++ b/src/main/java/org/sonarlint/intellij/ui/nodes/LiveSecurityHotspotNode.java @@ -59,7 +59,7 @@ public void render(TreeCellRenderer renderer) { var gap = JBUIScale.isUsrHiDPI() ? 8 : 4; var serverConnection = getService(securityHotspot.project(), ProjectBindingManager.class).tryGetServerConnection(); if (securityHotspot.getServerFindingKey() != null && serverConnection.isPresent()) { - var productIcon = serverConnection.get().getProductIcon(); + var productIcon = serverConnection.get().getProduct().getIcon(); var tooltip = vulnerabilityText + " " + typeStr + " existing on " + serverConnection.get().getProductName(); renderer.setIconToolTip(tooltip); setIcon(renderer, new CompoundIcon(CompoundIcon.Axis.X_AXIS, gap, productIcon, typeIcon)); diff --git a/src/main/java/org/sonarlint/intellij/ui/resolve/MarkAsResolvedDialog.kt b/src/main/java/org/sonarlint/intellij/ui/resolve/MarkAsResolvedDialog.kt index b35a801068..da99aa95b1 100644 --- a/src/main/java/org/sonarlint/intellij/ui/resolve/MarkAsResolvedDialog.kt +++ b/src/main/java/org/sonarlint/intellij/ui/resolve/MarkAsResolvedDialog.kt @@ -22,11 +22,11 @@ package org.sonarlint.intellij.ui.resolve import com.intellij.openapi.application.ModalityState import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogWrapper +import java.awt.event.ActionEvent import org.sonarlint.intellij.config.global.ServerConnection import org.sonarlint.intellij.ui.UiUtils import org.sonarsource.sonarlint.core.clientapi.backend.issue.CheckStatusChangePermittedResponse import org.sonarsource.sonarlint.core.clientapi.backend.issue.ResolutionStatus -import java.awt.event.ActionEvent class MarkAsResolvedDialog( project: Project, diff --git a/src/main/java/org/sonarlint/intellij/ui/resolve/MarkAsResolvedPanel.kt b/src/main/java/org/sonarlint/intellij/ui/resolve/MarkAsResolvedPanel.kt index 015687760e..9bd4f75882 100644 --- a/src/main/java/org/sonarlint/intellij/ui/resolve/MarkAsResolvedPanel.kt +++ b/src/main/java/org/sonarlint/intellij/ui/resolve/MarkAsResolvedPanel.kt @@ -22,17 +22,17 @@ package org.sonarlint.intellij.ui.resolve import com.intellij.openapi.ui.VerticalFlowLayout import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBTextArea -import org.apache.commons.lang.StringEscapeUtils -import org.sonarlint.intellij.config.global.ServerConnection -import org.sonarlint.intellij.ui.options.OptionPanel -import org.sonarlint.intellij.ui.options.addComponents -import org.sonarsource.sonarlint.core.clientapi.backend.issue.ResolutionStatus import java.awt.event.ActionEvent import java.awt.event.ActionListener import javax.swing.ButtonGroup import javax.swing.JPanel import javax.swing.JScrollPane import kotlin.properties.Delegates +import org.apache.commons.lang.StringEscapeUtils +import org.sonarlint.intellij.config.global.ServerConnection +import org.sonarlint.intellij.ui.options.OptionPanel +import org.sonarlint.intellij.ui.options.addComponents +import org.sonarsource.sonarlint.core.clientapi.backend.issue.ResolutionStatus class MarkAsResolvedPanel( @@ -73,7 +73,7 @@ class MarkAsResolvedPanel( rows = 3 }) ) - val link = StringEscapeUtils.escapeHtml(connection.links().formattingSyntaxDoc()) + val link = StringEscapeUtils.escapeHtml(connection.links.formattingSyntaxDoc()) add(JBLabel("Formatting Help: *Bold* ``Code`` * Bulleted point").apply { setCopyable(true) }) } } diff --git a/src/test/java/org/sonarlint/intellij/AbstractSonarLintHeavyTests.kt b/src/test/java/org/sonarlint/intellij/AbstractSonarLintHeavyTests.kt index 396fd19a16..cb1ed6f88c 100644 --- a/src/test/java/org/sonarlint/intellij/AbstractSonarLintHeavyTests.kt +++ b/src/test/java/org/sonarlint/intellij/AbstractSonarLintHeavyTests.kt @@ -33,6 +33,9 @@ import org.junit.jupiter.api.extension.ExtendWith import org.sonarlint.intellij.common.util.SonarLintUtils.getService import org.sonarlint.intellij.config.Settings 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.config.global.SonarLintGlobalSettings import org.sonarlint.intellij.config.project.SonarLintProjectSettings import org.sonarlint.intellij.core.BackendService @@ -81,7 +84,7 @@ abstract class AbstractSonarLintHeavyTests : HeavyPlatformTestCase() { } protected fun connectProjectTo(connection: ServerConnection, projectKey: String) { - Settings.getGlobalSettings().addServerConnection(connection) + ServerConnectionService.getInstance().addServerConnection(ServerConnectionWithAuth(connection, ServerConnectionCredentials(null, null, "token"))) Settings.getSettingsFor(project).bindTo(connection, projectKey) } diff --git a/src/test/java/org/sonarlint/intellij/AbstractSonarLintLightTests.java b/src/test/java/org/sonarlint/intellij/AbstractSonarLintLightTests.java index aae3fed945..39c9dcacb6 100644 --- a/src/test/java/org/sonarlint/intellij/AbstractSonarLintLightTests.java +++ b/src/test/java/org/sonarlint/intellij/AbstractSonarLintLightTests.java @@ -39,6 +39,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.io.file.PathUtils; import org.junit.jupiter.api.AfterAll; @@ -53,6 +54,9 @@ import org.sonarlint.intellij.common.ui.SonarLintConsole; import org.sonarlint.intellij.config.Settings; 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.config.global.SonarLintGlobalSettings; import org.sonarlint.intellij.config.module.SonarLintModuleSettings; import org.sonarlint.intellij.config.project.SonarLintProjectSettings; @@ -68,6 +72,7 @@ import static org.awaitility.Awaitility.await; import static org.sonarlint.intellij.common.util.SonarLintUtils.getService; import static org.sonarlint.intellij.config.Settings.getSettingsFor; +import static org.sonarlint.intellij.fixtures.ServerConnectionFixturesKt.newSonarQubeConnection; @ExtendWith(RunInEdtInterceptor.class) public abstract class AbstractSonarLintLightTests extends BasePlatformTestCase { @@ -94,7 +99,7 @@ final void beforeEachLightTest(TestInfo testInfo) throws Exception { super.setUp(); disposable = Disposer.newDisposable(); getGlobalSettings().setRules(Collections.emptyList()); - getGlobalSettings().setServerConnections(Collections.emptyList()); + setServerConnections(Collections.emptyList()); getGlobalSettings().setFocusOnNewCode(false); setGlobalLevelExclusions(Collections.emptyList()); getProjectSettings().setConnectionName(null); @@ -105,8 +110,7 @@ final void beforeEachLightTest(TestInfo testInfo) throws Exception { getModuleSettings().setIdePathPrefix(""); getModuleSettings().setSqPathPrefix(""); getModuleSettings().clearBindingOverride(); - getService(BackendService.class).connectionsUpdated(Collections.emptyList()); - // the line before might remove connections, let time for the storage to be cleaned up by the backend + // connections might have been removed, let time for the storage to be cleaned up by the backend await().atMost(Duration.ofSeconds(3)) .untilAsserted(() -> assertThat(storageRoot).satisfiesAnyOf( root -> assertThat(root).doesNotExist(), @@ -210,19 +214,19 @@ protected PsiFile createTestPsiFile(String fileName, Language language, String t } protected void connectProjectTo(String hostUrl, String connectionName, String projectKey) { - var connection = ServerConnection.newBuilder().setHostUrl(hostUrl).setName(connectionName).build(); - getGlobalSettings().addServerConnection(connection); + var connection = newSonarQubeConnection(connectionName, hostUrl); + ServerConnectionService.getInstance().addServerConnection(new ServerConnectionWithAuth(connection, new ServerConnectionCredentials(null, null, "token"))); getProjectSettings().bindTo(connection, projectKey); - getService(BackendService.class).connectionsUpdated(getGlobalSettings().getServerConnections()); getService(BackendService.class).projectBound(getProject(), new ProjectBinding(connectionName, projectKey, Collections.emptyMap())); } - protected void connectModuleTo(String projectKey) { - getModuleSettings().setProjectKey(projectKey); + protected void setServerConnections(List 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) { - getGlobalSettings().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/SonarLintIntelliJClientTests.kt b/src/test/java/org/sonarlint/intellij/SonarLintIntelliJClientTests.kt index 3e0e23b811..305cf25b87 100644 --- a/src/test/java/org/sonarlint/intellij/SonarLintIntelliJClientTests.kt +++ b/src/test/java/org/sonarlint/intellij/SonarLintIntelliJClientTests.kt @@ -23,7 +23,7 @@ import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.tuple import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.sonarlint.intellij.config.global.ServerConnection +import org.sonarlint.intellij.fixtures.newSonarQubeConnection import org.sonarsource.sonarlint.core.clientapi.backend.config.binding.BindingSuggestionDto import org.sonarsource.sonarlint.core.clientapi.client.binding.SuggestBindingParams import org.sonarsource.sonarlint.core.clientapi.client.fs.FindFileByNamesInScopeParams @@ -92,7 +92,7 @@ class SonarLintIntelliJClientTests : AbstractSonarLintLightTests() { @Test fun it_should_suggest_exact_binding_if_there_is_one_suggestion() { - globalSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("connectionId").build()) + setServerConnections(listOf(newSonarQubeConnection("connectionId"))) client.suggestBinding( SuggestBindingParams( @@ -113,7 +113,7 @@ class SonarLintIntelliJClientTests : AbstractSonarLintLightTests() { @Test fun it_should_suggest_binding_config_if_there_is_no_suggestion() { - globalSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("connectionId").build()) + setServerConnections(listOf(newSonarQubeConnection("connectionId"))) client.suggestBinding(SuggestBindingParams(mapOf(Pair(projectBackendId, emptyList())))) @@ -127,7 +127,7 @@ class SonarLintIntelliJClientTests : AbstractSonarLintLightTests() { @Test fun it_should_suggest_binding_config_if_there_is_are_several_suggestions() { - globalSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("connectionId").build()) + setServerConnections(listOf(newSonarQubeConnection("connectionId"))) client.suggestBinding( SuggestBindingParams( @@ -153,7 +153,7 @@ class SonarLintIntelliJClientTests : AbstractSonarLintLightTests() { @Test fun it_should_not_suggest_binding_if_the_project_is_unknown() { - globalSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("connectionId").build()) + setServerConnections(listOf(newSonarQubeConnection("connectionId"))) client.suggestBinding( SuggestBindingParams( @@ -174,8 +174,8 @@ class SonarLintIntelliJClientTests : AbstractSonarLintLightTests() { @Test fun it_should_not_suggest_binding_if_the_suggestions_are_disabled_by_user() { - globalSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("connectionId").build()) - projectSettings.setBindingSuggestionsEnabled(false) + setServerConnections(listOf(newSonarQubeConnection("connectionId"))) + projectSettings.isBindingSuggestionsEnabled = false client.suggestBinding( SuggestBindingParams( diff --git a/src/test/java/org/sonarlint/intellij/config/global/ServerConnectionSettingsTests.java b/src/test/java/org/sonarlint/intellij/config/global/ServerConnectionSettingsTests.java new file mode 100644 index 0000000000..edacfb4f73 --- /dev/null +++ b/src/test/java/org/sonarlint/intellij/config/global/ServerConnectionSettingsTests.java @@ -0,0 +1,73 @@ +/* + * 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; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class ServerConnectionSettingsTests { + @Test + void testRoundTrip() { + var server = ServerConnectionSettings.newBuilder() + .setHostUrl("host") + .setName("name") + .build(); + + assertThat(server.getName()).isEqualTo("name"); + assertThat(server.getHostUrl()).isEqualTo("host"); + + assertThat(server).hasToString(server.getName()); + assertThat(server.isSonarCloud()).isFalse(); + } + + @Test + void testSonarCloud() { + var server1 = ServerConnectionSettings.newBuilder() + .setHostUrl("https://sonarqube.com") + .setName("name") + .build(); + assertThat(server1.isSonarCloud()).isTrue(); + } + + @Test + void testEqualsAndHash() { + var server1 = ServerConnectionSettings.newBuilder() + .setHostUrl("host") + .setName("name") + .build(); + + var server2 = ServerConnectionSettings.newBuilder() + .setHostUrl("host") + .setName("name") + .build(); + + var server3 = ServerConnectionSettings.newBuilder() + .setHostUrl("host") + .setName("name2") + .build(); + + assertThat(server1) + .isEqualTo(server2) + .isNotEqualTo(server3) + .isNotEqualTo(null) + .hasSameHashCodeAs(server2); + } +} diff --git a/src/test/java/org/sonarlint/intellij/config/global/ServerConnectionTests.java b/src/test/java/org/sonarlint/intellij/config/global/ServerConnectionTests.java deleted file mode 100644 index 6ac5323463..0000000000 --- a/src/test/java/org/sonarlint/intellij/config/global/ServerConnectionTests.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class ServerConnectionTests { - @Test - void testRoundTrip() { - var server = ServerConnection.newBuilder() - .setHostUrl("host") - .setPassword("pass") - .setToken("token") - .setName("name") - .setLogin("login") - .build(); - - assertThat(server.getName()).isEqualTo("name"); - assertThat(server.getToken()).isEqualTo("token"); - assertThat(server.getLogin()).isEqualTo("login"); - assertThat(server.getPassword()).isEqualTo("pass"); - assertThat(server.getHostUrl()).isEqualTo("host"); - - assertThat(server).hasToString(server.getName()); - assertThat(server.isSonarCloud()).isFalse(); - } - - @Test - void testSonarCloud() { - var server1 = ServerConnection.newBuilder() - .setHostUrl("https://sonarqube.com") - .setPassword("pass") - .setToken("token") - .setName("name") - .setLogin("login") - .build(); - assertThat(server1.isSonarCloud()).isTrue(); - } - - @Test - void testEqualsAndHash() { - var server1 = ServerConnection.newBuilder() - .setHostUrl("host") - .setPassword("pass") - .setToken("token") - .setName("name") - .setLogin("login") - .build(); - - var server2 = ServerConnection.newBuilder() - .setHostUrl("host") - .setPassword("pass") - .setToken("token") - .setName("name") - .setLogin("login") - .build(); - - var server3 = ServerConnection.newBuilder() - .setHostUrl("host") - .setPassword("pass1") - .setToken("token") - .setName("name") - .setLogin("login") - .build(); - - assertThat(server1) - .isEqualTo(server2) - .isNotEqualTo(server3) - .isNotEqualTo(null) - .hasSameHashCodeAs(server2); - } - - @Test - void testSetNullEncodedFields() { - var server = ServerConnection.newBuilder() - .setHostUrl("host") - .setPassword(null) - .setToken(null) - .setName("name") - .setLogin("login") - .build(); - - assertThat(server.getToken()).isNull(); - assertThat(server.getPassword()).isNull(); - } - - @Test - void testEncoded() { - var builder = ServerConnection.newBuilder() - .setPassword("pass") - .setToken("token"); - - ServerConnection connection = builder.build(); - assertThat(connection.getPassword()).isEqualTo("pass"); - assertThat(connection.getToken()).isEqualTo("token"); - } - - @Test - void testEndpointParams() { - var server = ServerConnection.newBuilder() - .setHostUrl("http://myhost") - .setEnableProxy(false) - .setToken("token") - .setOrganizationKey("org") - .build(); - - var endpointParams = server.getEndpointParams(); - - assertThat(endpointParams.getBaseUrl()).isEqualTo("http://myhost"); - assertThat(endpointParams.getOrganization()).isEmpty(); - assertThat(endpointParams.isSonarCloud()).isFalse(); - } -} diff --git a/src/test/java/org/sonarlint/intellij/config/global/SonarLintGlobalSettingsTests.java b/src/test/java/org/sonarlint/intellij/config/global/SonarLintGlobalSettingsTests.java index 872fe55cea..b4cc65af38 100644 --- a/src/test/java/org/sonarlint/intellij/config/global/SonarLintGlobalSettingsTests.java +++ b/src/test/java/org/sonarlint/intellij/config/global/SonarLintGlobalSettingsTests.java @@ -41,7 +41,7 @@ void testRoundTrip() { assertThat(settings.isAutoTrigger()).isTrue(); assertThat(settings.getNodejsPath()).isBlank(); - var server = ServerConnection.newBuilder().setName("name").build(); + var server = ServerConnectionSettings.newBuilder().setName("name").build(); settings.setServerConnections(List.of(server)); assertThat(settings.getServerConnections()).containsOnly(server); @@ -133,23 +133,13 @@ void testResetRuleParam() { } @Test - void testAddConnection() { + void testSetConnections() { var settings = new SonarLintGlobalSettings(); - settings.addServerConnection(ServerConnection.newBuilder().setHostUrl("host").setName("name").build()); + settings.setServerConnections(List.of(ServerConnectionSettings.newBuilder().setHostUrl("host").setName("name").build())); assertThat(settings.getServerConnections()) - .extracting(ServerConnection::getHostUrl) + .extracting(ServerConnectionSettings::getHostUrl) .containsOnly("host"); } - - @Test - void getConnectionTo_should_ignore_trailing_slashes() { - var settings = new SonarLintGlobalSettings(); - settings.addServerConnection(ServerConnection.newBuilder().setHostUrl("http://host/").setName("name").build()); - - assertThat(settings.getConnectionsTo("http://host")) - .extracting(ServerConnection::getName) - .containsOnly("name"); - } } diff --git a/src/test/java/org/sonarlint/intellij/config/global/wizard/WizardModelTests.java b/src/test/java/org/sonarlint/intellij/config/global/wizard/WizardModelTests.java deleted file mode 100644 index 26efd64e99..0000000000 --- a/src/test/java/org/sonarlint/intellij/config/global/wizard/WizardModelTests.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.wizard; - -import org.junit.jupiter.api.Test; -import org.sonarlint.intellij.config.global.ServerConnection; - -import static org.assertj.core.api.Assertions.assertThat; - -class WizardModelTests { - @Test - void testCreateFromConfig() { - var server = ServerConnection.newBuilder() - .setName("name") - .setToken("token") - .setOrganizationKey("org") - .setEnableProxy(true) - .setHostUrl("url") - .build(); - - var model = new WizardModel(server); - assertThat(model.getLogin()).isNull(); - assertThat(model.getPassword()).isNull(); - assertThat(model.getToken()).isEqualTo("token"); - assertThat(model.getOrganizationKey()).isEqualTo("org"); - assertThat(model.getOrganizationList()).isEmpty(); - assertThat(model.getName()).isEqualTo("name"); - assertThat(model.getServerUrl()).isEqualTo("url"); - } - - @Test - void testExportToConfig() { - var model = new WizardModel(); - model.setName("name"); - model.setOrganizationKey("org"); - model.setServerUrl("url"); - model.setLogin("login"); - model.setProxyEnabled(true); - model.setPassword(new char[] {'p', 'a', 's', 's'}); - - model.setServerType(WizardModel.ServerType.SONARQUBE); - - var server = model.createConnection(); - assertThat(server.getHostUrl()).isEqualTo("url"); - assertThat(server.enableProxy()).isTrue(); - assertThat(server.getLogin()).isEqualTo("login"); - assertThat(server.getPassword()).isEqualTo("pass"); - assertThat(server.getToken()).isNull(); - assertThat(server.getOrganizationKey()).isEqualTo("org"); - } - - @Test - void testExportSonarCloud() { - var model = new WizardModel(); - model.setName("name"); - model.setOrganizationKey("org"); - model.setToken("token"); - model.setPassword(new char[] {'p', 'a', 's', 's'}); - - model.setServerType(WizardModel.ServerType.SONARCLOUD); - - var server = model.createConnection(); - assertThat(server.getHostUrl()).isEqualTo("https://sonarcloud.io"); - assertThat(server.getLogin()).isNull(); - assertThat(server.getPassword()).isNull(); - assertThat(server.getToken()).isEqualTo("token"); - assertThat(server.getOrganizationKey()).isEqualTo("org"); - } - - @Test - void testMigrationSonarCloud() { - var server = ServerConnection.newBuilder() - .setName("name") - .setToken("token") - .setOrganizationKey("org") - .setEnableProxy(true) - .setHostUrl("https://www.sonarqube.com") - .build(); - var model = new WizardModel(server); - - server = model.createConnection(); - assertThat(server.enableProxy()).isTrue(); - assertThat(server.getHostUrl()).isEqualTo("https://sonarcloud.io"); - } -} 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 new file mode 100644 index 0000000000..5c24697d59 --- /dev/null +++ b/src/test/java/org/sonarlint/intellij/config/global/wizard/WizardModelTests.kt @@ -0,0 +1,68 @@ +/* + * 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.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 = ServerConnectionWithAuth(SonarCloudConnection("name", "org", false), ServerConnectionCredentials(null, null, "token")) + val model = WizardModel(connection) + assertThat(model.organizationKey).isEqualTo("org") + assertThat(model.organizationList).isEmpty() + assertThat(model.name).isEqualTo("name") + assertThat(model.serverUrl).isEqualTo("https://sonarcloud.io") + } + + @Test + fun testExportSonarQubeToConfig() { + val model = WizardModel() + model.setName("name") + model.setIsSonarQube("url") + model.setLoginPassword("login", charArrayOf('p', 'a', 's', 's')) + + 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() + } + + @Test + fun testExportSonarCloud() { + val model = WizardModel() + model.setName("name") + model.setIsSonarCloud() + model.setOrganizationKey("org") + model.setToken("token") + + 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 aaa6163d78..bff6a77a19 100644 --- a/src/test/java/org/sonarlint/intellij/core/BackendServiceTests.kt +++ b/src/test/java/org/sonarlint/intellij/core/BackendServiceTests.kt @@ -23,6 +23,8 @@ import com.intellij.ide.impl.OpenProjectTask import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.ex.ProjectManagerEx import com.intellij.testFramework.replaceService +import java.nio.file.Path +import java.util.concurrent.CompletableFuture import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.tuple import org.junit.jupiter.api.BeforeEach @@ -38,8 +40,11 @@ import org.mockito.kotlin.never import org.mockito.kotlin.refEq import org.sonarlint.intellij.AbstractSonarLintHeavyTests import org.sonarlint.intellij.config.global.ServerConnection -import org.sonarlint.intellij.config.global.SonarLintGlobalSettings -import org.sonarlint.intellij.messages.GlobalConfigurationListener +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 import org.sonarsource.sonarlint.core.clientapi.backend.config.ConfigurationService import org.sonarsource.sonarlint.core.clientapi.backend.config.binding.DidUpdateBindingParams @@ -49,8 +54,6 @@ import org.sonarsource.sonarlint.core.clientapi.backend.connection.ConnectionSer import org.sonarsource.sonarlint.core.clientapi.backend.connection.config.DidChangeCredentialsParams import org.sonarsource.sonarlint.core.clientapi.backend.connection.config.DidUpdateConnectionsParams import org.sonarsource.sonarlint.core.clientapi.backend.initialize.InitializeParams -import java.nio.file.Path -import java.util.concurrent.CompletableFuture class BackendServiceTests : AbstractSonarLintHeavyTests() { @@ -62,11 +65,8 @@ class BackendServiceTests : AbstractSonarLintHeavyTests() { override fun initApplication() { super.initApplication() - globalSettings.serverConnections = listOf( - ServerConnection.newBuilder().setName("id").setHostUrl("url").build(), - ServerConnection.newBuilder().setName("id").setHostUrl("https://sonarcloud.io").setOrganizationKey("org") - .build() - ) + clearServerConnections() + setServerConnections(newSonarQubeConnection("idSQ", "url"), newSonarCloudConnection("idSC", "org")) backend = mock(SonarLintBackend::class.java) `when`(backend.initialize(any())).thenReturn(CompletableFuture.completedFuture(null)) @@ -89,14 +89,15 @@ class BackendServiceTests : AbstractSonarLintHeavyTests() { val paramsCaptor = argumentCaptor() verify(backend).initialize(paramsCaptor.capture()) assertThat(paramsCaptor.firstValue.sonarQubeConnections).extracting("connectionId", "serverUrl") - .containsExactly(tuple("id", "url")) + .containsExactly(tuple("idSQ", "url")) assertThat(paramsCaptor.firstValue.sonarCloudConnections).extracting("connectionId", "organization") - .containsExactly(tuple("id", "org")) + .containsExactly(tuple("idSC", "org")) } @Test fun test_notify_backend_when_adding_a_sonarqube_connection() { - service.connectionsUpdated(listOf(ServerConnection.newBuilder().setName("id").setHostUrl("url").build())) + reset(backendConnectionService) + service.connectionsUpdated(listOf(newSonarQubeConnection("id", "url"))) val paramsCaptor = argumentCaptor() verify(backendConnectionService).didUpdateConnections(paramsCaptor.capture()) @@ -106,8 +107,8 @@ class BackendServiceTests : AbstractSonarLintHeavyTests() { @Test fun test_notify_backend_when_adding_a_sonarcloud_connection() { - service.connectionsUpdated(listOf(ServerConnection.newBuilder().setName("id").setHostUrl("https://sonarcloud.io").setOrganizationKey("org") - .build())) + reset(backendConnectionService) + service.connectionsUpdated(listOf(newSonarCloudConnection("id", "org"))) val paramsCaptor = argumentCaptor() verify(backendConnectionService).didUpdateConnections(paramsCaptor.capture()) @@ -134,8 +135,8 @@ class BackendServiceTests : AbstractSonarLintHeavyTests() { @Test fun test_notify_backend_when_opening_a_bound_project() { - val connection = ServerConnection.newBuilder().setName("id").setHostUrl("url").build() - globalSettings.serverConnections = listOf(connection) + val connection = newSonarQubeConnection("id", "url") + setServerConnections(connection) projectSettings.bindTo(connection, "key") service.projectOpened(project) @@ -276,60 +277,44 @@ class BackendServiceTests : AbstractSonarLintHeavyTests() { @Test fun test_notify_backend_when_connection_token_changed() { - val previousSettings = SonarLintGlobalSettings() - previousSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("id").setHostUrl("url").setToken("oldToken").build()) - val newSettings = SonarLintGlobalSettings() - newSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("id").setHostUrl("url").setToken("newToken").build()) - - ApplicationManager.getApplication().messageBus.syncPublisher(GlobalConfigurationListener.TOPIC).applied(previousSettings, newSettings) + reset(backendConnectionService) + updateServerCredentials("idSQ", credentials = ServerConnectionCredentials(null, null, "oldToken")) - verify(backendConnectionService).didChangeCredentials(refEq(DidChangeCredentialsParams("id"))) + verify(backendConnectionService).didChangeCredentials(refEq(DidChangeCredentialsParams("idSQ"))) } @Test fun test_notify_backend_when_connection_password_changed() { - val previousSettings = SonarLintGlobalSettings() - previousSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("id").setHostUrl("url").setLogin("login").setPassword("oldPass").build()) - val newSettings = SonarLintGlobalSettings() - newSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("id").setHostUrl("url").setLogin("login").setPassword("newPass").build()) - - ApplicationManager.getApplication().messageBus.syncPublisher(GlobalConfigurationListener.TOPIC).applied(previousSettings, newSettings) + addServerConnectionsWithAuth(newSonarQubeConnection("id"), credentials = ServerConnectionCredentials("login", "oldPass", null)) + reset(backendConnectionService) + updateServerCredentials("id", credentials = ServerConnectionCredentials("login", "newPass", null)) verify(backendConnectionService).didChangeCredentials(refEq(DidChangeCredentialsParams("id"))) } @Test fun test_notify_backend_when_connection_login_changed() { - val previousSettings = SonarLintGlobalSettings() - previousSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("id").setHostUrl("url").setLogin("oldLogin").setPassword("pass").build()) - val newSettings = SonarLintGlobalSettings() - newSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("id").setHostUrl("url").setLogin("newLogin").setPassword("pass").build()) - - ApplicationManager.getApplication().messageBus.syncPublisher(GlobalConfigurationListener.TOPIC).applied(previousSettings, newSettings) + addServerConnectionsWithAuth(newSonarQubeConnection("id"), credentials = ServerConnectionCredentials("oldLogin", "pass", null)) + reset(backendConnectionService) + 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() { - val previousSettings = SonarLintGlobalSettings() - previousSettings.serverConnections = emptyList() - val newSettings = SonarLintGlobalSettings() - newSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("id").setHostUrl("url").setLogin("login").setPassword("newPass").build()) - - ApplicationManager.getApplication().messageBus.syncPublisher(GlobalConfigurationListener.TOPIC).applied(previousSettings, newSettings) + clearServerConnections() + reset(backendConnectionService) + 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() { - val previousSettings = SonarLintGlobalSettings() - previousSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("id").setHostUrl("oldUrl").setToken("token").build()) - val newSettings = SonarLintGlobalSettings() - newSettings.serverConnections = listOf(ServerConnection.newBuilder().setName("id").setHostUrl("newUrl").setToken("token").build()) - - ApplicationManager.getApplication().messageBus.syncPublisher(GlobalConfigurationListener.TOPIC).applied(previousSettings, newSettings) + setServerConnections(newSonarQubeConnection("id", "oldUrl")) + reset(backendConnectionService) + setServerConnections(newSonarQubeConnection("id", "newUrl")) verify(backendConnectionService, never()).didChangeCredentials(refEq(DidChangeCredentialsParams("id"))) } @@ -340,4 +325,25 @@ class BackendServiceTests : AbstractSonarLintHeavyTests() { verify(backend).shutdown() } + + 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().updateServerConnections(globalSettings, ServerConnectionService.getInstance().getConnections().map { it.name }.toSet(), emptyList(), emptyList()) + } } diff --git a/src/test/java/org/sonarlint/intellij/core/ConnectedSonarLintFacadeTests.java b/src/test/java/org/sonarlint/intellij/core/ConnectedSonarLintFacadeTests.java index 825f6d83ac..afb6cd5682 100644 --- a/src/test/java/org/sonarlint/intellij/core/ConnectedSonarLintFacadeTests.java +++ b/src/test/java/org/sonarlint/intellij/core/ConnectedSonarLintFacadeTests.java @@ -24,15 +24,10 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.sonarlint.intellij.AbstractSonarLintLightTests; -import org.sonarlint.intellij.config.global.ServerConnection; import org.sonarsource.sonarlint.core.analysis.api.AnalysisResults; import org.sonarsource.sonarlint.core.client.api.common.analysis.IssueListener; import org.sonarsource.sonarlint.core.client.api.connected.ConnectedAnalysisConfiguration; -import org.sonarsource.sonarlint.core.client.api.connected.ConnectedRuleDetails; import org.sonarsource.sonarlint.core.client.api.connected.ConnectedSonarLintEngine; -import org.sonarsource.sonarlint.core.commons.IssueSeverity; -import org.sonarsource.sonarlint.core.commons.Language; -import org.sonarsource.sonarlint.core.commons.RuleType; import org.sonarsource.sonarlint.core.commons.log.ClientLogOutput; import org.sonarsource.sonarlint.core.commons.progress.ClientProgressMonitor; @@ -60,14 +55,4 @@ void should_start_analysis() { var config = configCaptor.getValue(); assertThat(config.getProjectKey()).isEqualTo("projectKey"); } - - private void bindProject(String projectKey) { - var connection = ServerConnection.newBuilder().setName("connectionName").setHostUrl("http://localhost:9000").build(); - getGlobalSettings().addServerConnection(connection); - getProjectSettings().bindTo(connection, projectKey); - } - - private static ConnectedRuleDetails ruleDetails(String ruleKey) { - return new ConnectedRuleDetails(ruleKey, "ruleName", "ruleHtmlDescription", IssueSeverity.BLOCKER, RuleType.CODE_SMELL, Language.JAVA, "ruleExtendedDescription"); - } } diff --git a/src/test/java/org/sonarlint/intellij/core/DefaultEngineManagerTests.java b/src/test/java/org/sonarlint/intellij/core/DefaultEngineManagerTests.java deleted file mode 100644 index d33653669b..0000000000 --- a/src/test/java/org/sonarlint/intellij/core/DefaultEngineManagerTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.core; - -import java.util.Collections; -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import org.sonarlint.intellij.AbstractSonarLintLightTests; -import org.sonarlint.intellij.config.global.ServerConnection; -import org.sonarlint.intellij.exception.InvalidBindingException; -import org.sonarlint.intellij.notifications.SonarLintProjectNotifications; -import org.sonarsource.sonarlint.core.client.api.connected.ConnectedSonarLintEngine; -import org.sonarsource.sonarlint.core.client.api.standalone.StandaloneSonarLintEngine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -class DefaultEngineManagerTests extends AbstractSonarLintLightTests { - - private DefaultEngineManager manager; - private SonarLintEngineFactory engineFactory; - private SonarLintProjectNotifications notifications; - private ConnectedSonarLintEngine connectedEngine; - private StandaloneSonarLintEngine standaloneEngine; - - @BeforeEach - void before() { - engineFactory = mock(SonarLintEngineFactory.class); - notifications = mock(SonarLintProjectNotifications.class); - connectedEngine = mock(ConnectedSonarLintEngine.class); - standaloneEngine = mock(StandaloneSonarLintEngine.class); - - when(engineFactory.createEngine(anyString(), eq(false))).thenReturn(connectedEngine); - when(engineFactory.createEngine()).thenReturn(standaloneEngine); - - manager = new DefaultEngineManager(engineFactory); - getGlobalSettings().setServerConnections(Collections.emptyList()); - } - - @Test - void should_get_standalone() { - assertThat(manager.getStandaloneEngine()).isEqualTo(standaloneEngine); - assertThat(manager.getStandaloneEngine()).isEqualTo(standaloneEngine); - verify(engineFactory, Mockito.times(1)).createEngine(); - } - - @Test - void should_get_connected() { - getGlobalSettings().setServerConnections(List.of(createConnection("server1"))); - - assertThat(manager.getConnectedEngine("server1")).isEqualTo(connectedEngine); - assertThat(manager.getConnectedEngine("server1")).isEqualTo(connectedEngine); - verify(engineFactory, Mockito.times(1)).createEngine("server1", false); - } - - @Test - void should_fail_invalid_server() { - var throwable = catchThrowable(() -> manager.getConnectedEngine(notifications, "server1", "project1")); - - assertThat(throwable) - .isInstanceOf(InvalidBindingException.class) - .hasMessage("Invalid server name: server1"); - } - - private static ServerConnection createConnection(String name) { - return ServerConnection.newBuilder().setName(name).build(); - } - -} diff --git a/src/test/java/org/sonarlint/intellij/core/DefaultEngineManagerTests.kt b/src/test/java/org/sonarlint/intellij/core/DefaultEngineManagerTests.kt new file mode 100644 index 0000000000..8712c383ec --- /dev/null +++ b/src/test/java/org/sonarlint/intellij/core/DefaultEngineManagerTests.kt @@ -0,0 +1,84 @@ +/* + * 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.core + +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.ArgumentMatchers +import org.mockito.Mockito +import org.sonarlint.intellij.AbstractSonarLintLightTests +import org.sonarlint.intellij.config.global.ServerConnection +import org.sonarlint.intellij.exception.InvalidBindingException +import org.sonarlint.intellij.fixtures.newSonarQubeConnection +import org.sonarlint.intellij.notifications.SonarLintProjectNotifications +import org.sonarsource.sonarlint.core.client.api.connected.ConnectedSonarLintEngine +import org.sonarsource.sonarlint.core.client.api.standalone.StandaloneSonarLintEngine + +internal class DefaultEngineManagerTests : AbstractSonarLintLightTests() { + private lateinit var manager: DefaultEngineManager + private lateinit var engineFactory: SonarLintEngineFactory + private var notifications: SonarLintProjectNotifications? = null + private var connectedEngine: ConnectedSonarLintEngine? = null + private var standaloneEngine: StandaloneSonarLintEngine? = null + + @BeforeEach + fun before() { + engineFactory = Mockito.mock(SonarLintEngineFactory::class.java) + notifications = Mockito.mock(SonarLintProjectNotifications::class.java) + connectedEngine = Mockito.mock(ConnectedSonarLintEngine::class.java) + standaloneEngine = Mockito.mock(StandaloneSonarLintEngine::class.java) + + Mockito.`when`(engineFactory.createEngine(ArgumentMatchers.anyString(), ArgumentMatchers.eq(false))).thenReturn(connectedEngine) + Mockito.`when`(engineFactory.createEngine()).thenReturn(standaloneEngine) + + manager = DefaultEngineManager(engineFactory) + setServerConnections(emptyList()) + } + + @Test + fun should_get_standalone() { + Assertions.assertThat(manager.standaloneEngine).isEqualTo(standaloneEngine) + Assertions.assertThat(manager.standaloneEngine).isEqualTo(standaloneEngine) + Mockito.verify(engineFactory, Mockito.times(1)).createEngine() + } + + @Test + fun should_get_connected() { + setServerConnections(listOf(createConnection("server1"))) + + Assertions.assertThat(manager.getConnectedEngine("server1")).isEqualTo(connectedEngine) + Assertions.assertThat(manager.getConnectedEngine("server1")).isEqualTo(connectedEngine) + Mockito.verify(engineFactory, Mockito.times(1)).createEngine("server1", false) + } + + @Test + fun should_fail_invalid_server() { + val throwable = Assertions.catchThrowable { manager.getConnectedEngine(notifications, "server1", "project1") } + + Assertions.assertThat(throwable) + .isInstanceOf(InvalidBindingException::class.java) + .hasMessage("Invalid server name: server1") + } + + private fun createConnection(name: String): ServerConnection { + return newSonarQubeConnection(name) + } +} diff --git a/src/test/java/org/sonarlint/intellij/core/ModuleBindingManagerTests.kt b/src/test/java/org/sonarlint/intellij/core/ModuleBindingManagerTests.kt index 1d1ac8960a..afd2d6ac59 100644 --- a/src/test/java/org/sonarlint/intellij/core/ModuleBindingManagerTests.kt +++ b/src/test/java/org/sonarlint/intellij/core/ModuleBindingManagerTests.kt @@ -24,7 +24,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.mockito.Mockito.mock import org.sonarlint.intellij.AbstractSonarLintLightTests -import org.sonarlint.intellij.config.global.ServerConnection +import org.sonarlint.intellij.fixtures.newSonarQubeConnection import org.sonarsource.sonarlint.core.client.api.connected.ConnectedSonarLintEngine import org.sonarsource.sonarlint.core.client.api.standalone.StandaloneSonarLintEngine @@ -44,7 +44,7 @@ class ModuleBindingManagerTests : AbstractSonarLintLightTests() { @Test fun should_resolve_project_key_if_module_is_not_bound() { - projectSettings.bindTo(ServerConnection.newBuilder().setName("name").build(), "projectKey") + projectSettings.bindTo(newSonarQubeConnection("name"), "projectKey") val resolvedProjectKey = moduleBindingManager.resolveProjectKey() diff --git a/src/test/java/org/sonarlint/intellij/core/ProjectBindingManagerTests.java b/src/test/java/org/sonarlint/intellij/core/ProjectBindingManagerTests.java deleted file mode 100644 index 343a71e676..0000000000 --- a/src/test/java/org/sonarlint/intellij/core/ProjectBindingManagerTests.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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.core; - -import com.intellij.openapi.progress.ProgressManager; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.sonarlint.intellij.AbstractSonarLintLightTests; -import org.sonarlint.intellij.common.ui.SonarLintConsole; -import org.sonarlint.intellij.config.global.ServerConnection; -import org.sonarlint.intellij.exception.InvalidBindingException; -import org.sonarlint.intellij.notifications.SonarLintProjectNotifications; -import org.sonarsource.sonarlint.core.client.api.connected.ConnectedSonarLintEngine; -import org.sonarsource.sonarlint.core.client.api.standalone.StandaloneSonarLintEngine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; -import static org.mockito.Mockito.mock; - -class ProjectBindingManagerTests extends AbstractSonarLintLightTests { - private ProjectBindingManager projectBindingManager; - - private StandaloneSonarLintEngine standaloneEngine = mock(StandaloneSonarLintEngine.class); - private ConnectedSonarLintEngine connectedEngine = mock(ConnectedSonarLintEngine.class); - - @BeforeEach - void before() { - var console = mock(SonarLintConsole.class); - var notifications = mock(SonarLintProjectNotifications.class); - replaceProjectService(SonarLintConsole.class, console); - replaceProjectService(SonarLintProjectNotifications.class, notifications); - getEngineManager().stopAllEngines(false); - - projectBindingManager = new ProjectBindingManager(getProject(), mock(ProgressManager.class)); - } - - @Test - void should_create_facade_standalone() throws InvalidBindingException { - assertThat(projectBindingManager.getFacade(getModule())).isInstanceOf(StandaloneSonarLintFacade.class); - } - - @Test - void should_get_connected_engine() throws InvalidBindingException { - connectProjectTo(ServerConnection.newBuilder().setName("server1").build(), "project1"); - getEngineManager().registerEngine(connectedEngine, "server1"); - - var engine = projectBindingManager.getConnectedEngine(); - - assertThat(engine).isEqualTo(connectedEngine); - } - - @Test - void fail_get_connected_engine_if_not_connected() { - var throwable = catchThrowable(() -> projectBindingManager.getConnectedEngine()); - - assertThat(throwable).isInstanceOf(IllegalStateException.class); - } - - @Test - void should_create_facade_connected() throws InvalidBindingException { - connectProjectTo(ServerConnection.newBuilder().setName("server1").build(), "project1"); - getEngineManager().registerEngine(connectedEngine, "server1"); - - var facade = projectBindingManager.getFacade(getModule()); - - assertThat(facade).isInstanceOf(ConnectedSonarLintFacade.class); - } - - @Test - void should_find_sq_server() throws InvalidBindingException { - getProjectSettings().setBindingEnabled(true); - getProjectSettings().setProjectKey("project1"); - getProjectSettings().setConnectionName("server1"); - - var server = ServerConnection.newBuilder().setName("server1").build(); - getGlobalSettings().setServerConnections(List.of(server)); - assertThat(projectBindingManager.getServerConnection()).isEqualTo(server); - } - - @Test - void fail_if_cant_find_server() { - getProjectSettings().setBindingEnabled(true); - getProjectSettings().setProjectKey("project1"); - getProjectSettings().setConnectionName("server1"); - var server = ServerConnection.newBuilder().setName("server2").build(); - getGlobalSettings().setServerConnections(List.of(server)); - - var throwable = catchThrowable(() -> projectBindingManager.getServerConnection()); - - assertThat(throwable).isInstanceOf(InvalidBindingException.class); - } - - @Test - void fail_invalid_server_binding() { - getProjectSettings().setBindingEnabled(true); - - var throwable = catchThrowable(() -> projectBindingManager.getFacade(getModule())); - - assertThat(throwable) - .isInstanceOf(InvalidBindingException.class) - .hasMessage("Project has an invalid binding"); - } - - @Test - void fail_invalid_module_binding() { - getProjectSettings().setBindingEnabled(true); - getProjectSettings().setConnectionName("server1"); - getProjectSettings().setProjectKey(null); - - var throwable = catchThrowable(() -> projectBindingManager.getFacade(getModule())); - - assertThat(throwable) - .isInstanceOf(InvalidBindingException.class) - .hasMessage("Project has an invalid binding"); - } - - @Test - void should_return_connected_engine_if_started() { - getProjectSettings().setBindingEnabled(true); - getProjectSettings().setConnectionName("server1"); - getProjectSettings().setProjectKey("key"); - getEngineManager().registerEngine(connectedEngine, "server1"); - - var engine = projectBindingManager.getEngineIfStarted(); - - assertThat(engine).isEqualTo(connectedEngine); - } - - @Test - void should_return_standalone_engine_if_started() { - getProjectSettings().setBindingEnabled(false); - getEngineManager().registerEngine(standaloneEngine); - - var engine = projectBindingManager.getEngineIfStarted(); - - assertThat(engine).isEqualTo(standaloneEngine); - } - - @Test - void should_not_return_connected_engine_if_not_started() { - getProjectSettings().setBindingEnabled(true); - getProjectSettings().setConnectionName("server1"); - getProjectSettings().setProjectKey(null); - - var engine = projectBindingManager.getEngineIfStarted(); - - assertThat(engine).isNull(); - } - - @Test - void should_not_return_standalone_engine_if_not_started() { - getProjectSettings().setBindingEnabled(false); - - var engine = projectBindingManager.getEngineIfStarted(); - - assertThat(engine).isNull(); - } - - @Test - void should_store_project_binding_in_settings() { - var connection = ServerConnection.newBuilder().setName("name").build(); - getGlobalSettings().setServerConnections(List.of(connection)); - - projectBindingManager.bindTo(connection, "projectKey", Collections.emptyMap()); - - assertThat(getProjectSettings().isBoundTo(connection)).isTrue(); - assertThat(getProjectSettings().getProjectKey()).isEqualTo("projectKey"); - } - - @Test - void should_store_project_and_module_bindings_in_settings() { - var connection = ServerConnection.newBuilder().setName("name").build(); - getGlobalSettings().setServerConnections(List.of(connection)); - projectBindingManager.bindTo(connection, "projectKey", Map.of(getModule(), "moduleProjectKey")); - - assertThat(getProjectSettings().isBoundTo(connection)).isTrue(); - assertThat(getProjectSettings().getProjectKey()).isEqualTo("projectKey"); - assertThat(getModuleSettings().isProjectBindingOverridden()).isTrue(); - assertThat(getModuleSettings().getProjectKey()).isEqualTo("moduleProjectKey"); - } - - @Test - void should_clear_project_and_module_binding_settings_when_unbinding() { - getProjectSettings().bindTo(ServerConnection.newBuilder().setName("connection").build(), "projectKey"); - getModuleSettings().setProjectKey("moduleProjectKey"); - - projectBindingManager.unbind(); - - assertThat(getProjectSettings().isBound()).isFalse(); - assertThat(getModuleSettings().isProjectBindingOverridden()).isFalse(); - } -} diff --git a/src/test/java/org/sonarlint/intellij/core/ProjectBindingManagerTests.kt b/src/test/java/org/sonarlint/intellij/core/ProjectBindingManagerTests.kt new file mode 100644 index 0000000000..28139be2b1 --- /dev/null +++ b/src/test/java/org/sonarlint/intellij/core/ProjectBindingManagerTests.kt @@ -0,0 +1,211 @@ +/* + * 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.core + +import com.intellij.openapi.progress.ProgressManager +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mockito +import org.sonarlint.intellij.AbstractSonarLintLightTests +import org.sonarlint.intellij.common.ui.SonarLintConsole +import org.sonarlint.intellij.exception.InvalidBindingException +import org.sonarlint.intellij.fixtures.newSonarQubeConnection +import org.sonarlint.intellij.notifications.SonarLintProjectNotifications +import org.sonarsource.sonarlint.core.client.api.connected.ConnectedSonarLintEngine +import org.sonarsource.sonarlint.core.client.api.standalone.StandaloneSonarLintEngine + +internal class ProjectBindingManagerTests : AbstractSonarLintLightTests() { + private lateinit var projectBindingManager: ProjectBindingManager + + private val standaloneEngine: StandaloneSonarLintEngine = Mockito.mock(StandaloneSonarLintEngine::class.java) + private val connectedEngine: ConnectedSonarLintEngine = Mockito.mock(ConnectedSonarLintEngine::class.java) + + @BeforeEach + fun before() { + val console = Mockito.mock(SonarLintConsole::class.java) + val notifications = Mockito.mock(SonarLintProjectNotifications::class.java) + replaceProjectService(SonarLintConsole::class.java, console) + replaceProjectService(SonarLintProjectNotifications::class.java, notifications) + getEngineManager().stopAllEngines(false) + + projectBindingManager = ProjectBindingManager(project, Mockito.mock(ProgressManager::class.java)) + } + + @Test + @Throws(InvalidBindingException::class) + fun should_create_facade_standalone() { + Assertions.assertThat(projectBindingManager.getFacade(module)).isInstanceOf(StandaloneSonarLintFacade::class.java) + } + + @Test + @Throws(InvalidBindingException::class) + fun should_get_connected_engine() { + connectProjectTo(newSonarQubeConnection("server1"), "project1") + getEngineManager().registerEngine(connectedEngine, "server1") + + val engine = projectBindingManager.connectedEngine + + Assertions.assertThat(engine).isEqualTo(connectedEngine) + } + + @Test + fun fail_get_connected_engine_if_not_connected() { + val throwable = Assertions.catchThrowable { projectBindingManager.connectedEngine } + + Assertions.assertThat(throwable).isInstanceOf(IllegalStateException::class.java) + } + + @Test + @Throws(InvalidBindingException::class) + fun should_create_facade_connected() { + connectProjectTo(newSonarQubeConnection("server1"), "project1") + getEngineManager().registerEngine(connectedEngine, "server1") + + val facade = projectBindingManager.getFacade(module) + + Assertions.assertThat(facade).isInstanceOf(ConnectedSonarLintFacade::class.java) + } + + @Test + @Throws(InvalidBindingException::class) + fun should_find_sq_server() { + projectSettings.isBindingEnabled = true + projectSettings.projectKey = "project1" + projectSettings.connectionName = "server1" + + val server = newSonarQubeConnection("server1") + setServerConnections(listOf(server)) + Assertions.assertThat(projectBindingManager.serverConnection).isEqualTo(server) + } + + @Test + fun fail_if_cant_find_server() { + projectSettings.isBindingEnabled = true + projectSettings.projectKey = "project1" + projectSettings.connectionName = "server1" + val connection = newSonarQubeConnection("server2") + setServerConnections(listOf(connection)) + + val throwable = Assertions.catchThrowable { projectBindingManager.serverConnection } + + Assertions.assertThat(throwable).isInstanceOf(InvalidBindingException::class.java) + } + + @Test + fun fail_invalid_server_binding() { + projectSettings.isBindingEnabled = true + + val throwable = Assertions.catchThrowable { projectBindingManager.getFacade(module) } + + Assertions.assertThat(throwable) + .isInstanceOf(InvalidBindingException::class.java) + .hasMessage("Project has an invalid binding") + } + + @Test + fun fail_invalid_module_binding() { + projectSettings.isBindingEnabled = true + projectSettings.connectionName = "server1" + projectSettings.projectKey = null + + val throwable = Assertions.catchThrowable { projectBindingManager.getFacade(module) } + + Assertions.assertThat(throwable) + .isInstanceOf(InvalidBindingException::class.java) + .hasMessage("Project has an invalid binding") + } + + @Test + fun should_return_connected_engine_if_started() { + projectSettings.isBindingEnabled = true + projectSettings.connectionName = "server1" + projectSettings.projectKey = "key" + getEngineManager().registerEngine(connectedEngine, "server1") + + val engine = projectBindingManager.engineIfStarted + + Assertions.assertThat(engine).isEqualTo(connectedEngine) + } + + @Test + fun should_return_standalone_engine_if_started() { + projectSettings.isBindingEnabled = false + getEngineManager().registerEngine(standaloneEngine) + + val engine = projectBindingManager.engineIfStarted + + Assertions.assertThat(engine).isEqualTo(standaloneEngine) + } + + @Test + fun should_not_return_connected_engine_if_not_started() { + projectSettings.isBindingEnabled = true + projectSettings.connectionName = "server1" + projectSettings.projectKey = null + + val engine = projectBindingManager.engineIfStarted + + Assertions.assertThat(engine).isNull() + } + + @Test + fun should_not_return_standalone_engine_if_not_started() { + projectSettings.isBindingEnabled = false + + val engine = projectBindingManager.engineIfStarted + + Assertions.assertThat(engine).isNull() + } + + @Test + fun should_store_project_binding_in_settings() { + val connection = newSonarQubeConnection("name") + setServerConnections(listOf(connection)) + + projectBindingManager.bindTo(connection, "projectKey", emptyMap()) + + Assertions.assertThat(projectSettings.isBoundTo(connection)).isTrue() + Assertions.assertThat(projectSettings.projectKey).isEqualTo("projectKey") + } + + @Test + fun should_store_project_and_module_bindings_in_settings() { + val connection = newSonarQubeConnection("name") + setServerConnections(listOf(connection)) + projectBindingManager.bindTo(connection, "projectKey", mapOf(module to "moduleProjectKey")) + + Assertions.assertThat(projectSettings.isBoundTo(connection)).isTrue() + Assertions.assertThat(projectSettings.projectKey).isEqualTo("projectKey") + Assertions.assertThat(moduleSettings.isProjectBindingOverridden).isTrue() + Assertions.assertThat(moduleSettings.projectKey).isEqualTo("moduleProjectKey") + } + + @Test + fun should_clear_project_and_module_binding_settings_when_unbinding() { + projectSettings.bindTo(newSonarQubeConnection("connection"), "projectKey") + moduleSettings.projectKey = "moduleProjectKey" + + projectBindingManager.unbind() + + Assertions.assertThat(projectSettings.isBound).isFalse() + Assertions.assertThat(moduleSettings.isProjectBindingOverridden).isFalse() + } +} diff --git a/src/test/java/org/sonarlint/intellij/core/UpdateCheckerTests.java b/src/test/java/org/sonarlint/intellij/core/UpdateCheckerTests.java deleted file mode 100644 index 88d6bdc5dd..0000000000 --- a/src/test/java/org/sonarlint/intellij/core/UpdateCheckerTests.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.core; - -import com.intellij.openapi.progress.DumbProgressIndicator; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.sonarlint.intellij.AbstractSonarLintLightTests; -import org.sonarlint.intellij.config.global.ServerConnection; -import org.sonarlint.intellij.exception.InvalidBindingException; -import org.sonarsource.sonarlint.core.client.api.connected.ConnectedSonarLintEngine; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -class UpdateCheckerTests extends AbstractSonarLintLightTests { - private ConnectedModeStorageSynchronizer connectedModeStorageSynchronizer; - private ServerConnection server; - private ProjectBindingManager bindingManager = mock(ProjectBindingManager.class); - private ConnectedSonarLintEngine engine = mock(ConnectedSonarLintEngine.class); - - @BeforeEach - void before() throws InvalidBindingException { - replaceProjectService(ProjectBindingManager.class, bindingManager); - - getProjectSettings().setProjectKey("key"); - getProjectSettings().setConnectionName("serverId"); - server = createServer(); - when(bindingManager.getServerConnection()).thenReturn(server); - when(bindingManager.getConnectedEngine()).thenReturn(engine); - - connectedModeStorageSynchronizer = new ConnectedModeStorageSynchronizer(getProject()); - } - - @Test - void do_nothing_if_no_engine() throws InvalidBindingException { - when(bindingManager.getConnectedEngine()).thenThrow(new IllegalStateException()); - connectedModeStorageSynchronizer.sync(DumbProgressIndicator.INSTANCE); - - verifyNoInteractions(engine); - } - - private ServerConnection createServer() { - return ServerConnection.newBuilder() - .setHostUrl("http://localhost:9000") - .setName("server1") - .build(); - } -} diff --git a/src/test/java/org/sonarlint/intellij/core/UpdateCheckerTests.kt b/src/test/java/org/sonarlint/intellij/core/UpdateCheckerTests.kt new file mode 100644 index 0000000000..0f3c006feb --- /dev/null +++ b/src/test/java/org/sonarlint/intellij/core/UpdateCheckerTests.kt @@ -0,0 +1,65 @@ +/* + * 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.core + +import com.intellij.openapi.progress.DumbProgressIndicator +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mockito +import org.sonarlint.intellij.AbstractSonarLintLightTests +import org.sonarlint.intellij.config.global.ServerConnection +import org.sonarlint.intellij.core.ProjectBindingManager +import org.sonarlint.intellij.exception.InvalidBindingException +import org.sonarlint.intellij.fixtures.newSonarQubeConnection +import org.sonarsource.sonarlint.core.client.api.connected.ConnectedSonarLintEngine + +internal class UpdateCheckerTests : AbstractSonarLintLightTests() { + private var connectedModeStorageSynchronizer: ConnectedModeStorageSynchronizer? = null + private var server: ServerConnection? = null + private val bindingManager: ProjectBindingManager = Mockito.mock(ProjectBindingManager::class.java) + private val engine: ConnectedSonarLintEngine = Mockito.mock(ConnectedSonarLintEngine::class.java) + + @BeforeEach + @Throws(InvalidBindingException::class) + fun before() { + replaceProjectService(ProjectBindingManager::class.java, bindingManager) + + projectSettings.projectKey = "key" + projectSettings.connectionName = "serverId" + server = createServer() + Mockito.`when`(bindingManager.serverConnection).thenReturn(server) + Mockito.`when`(bindingManager.connectedEngine).thenReturn(engine) + + connectedModeStorageSynchronizer = ConnectedModeStorageSynchronizer(project) + } + + @Test + @Throws(InvalidBindingException::class) + fun do_nothing_if_no_engine() { + Mockito.`when`(bindingManager.connectedEngine).thenThrow(IllegalStateException()) + connectedModeStorageSynchronizer!!.sync(DumbProgressIndicator.INSTANCE) + + Mockito.verifyNoInteractions(engine) + } + + private fun createServer(): ServerConnection { + return newSonarQubeConnection("server1", "http://localhost:9000") + } +} diff --git a/src/test/java/org/sonarlint/intellij/editor/SonarExternalAnnotatorTests.java b/src/test/java/org/sonarlint/intellij/editor/SonarExternalAnnotatorTests.java deleted file mode 100644 index f976265df7..0000000000 --- a/src/test/java/org/sonarlint/intellij/editor/SonarExternalAnnotatorTests.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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.editor; - -import com.intellij.ide.highlighter.JavaFileType; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiFile; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.sonarlint.intellij.AbstractSonarLintLightTests; -import org.sonarlint.intellij.config.Settings; -import org.sonarlint.intellij.config.SonarLintTextAttributes; -import org.sonarlint.intellij.config.global.ServerConnection; -import org.sonarlint.intellij.finding.persistence.FindingsCache; -import org.sonarsource.sonarlint.core.commons.ImpactSeverity; -import org.sonarsource.sonarlint.core.commons.IssueSeverity; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class SonarExternalAnnotatorTests extends AbstractSonarLintLightTests { - private final PsiFile psiFile = mock(PsiFile.class); - private final VirtualFile virtualFile = mock(VirtualFile.class); - private final FindingsCache store = mock(FindingsCache.class); - private final TextRange psiFileRange = new TextRange(0, 100); - - @BeforeEach - void set() { - replaceProjectService(FindingsCache.class, store); - when(psiFile.getTextRange()).thenReturn(psiFileRange); - when(psiFile.getVirtualFile()).thenReturn(virtualFile); - when(psiFile.getFileType()).thenReturn(JavaFileType.INSTANCE); - when(psiFile.getProject()).thenReturn(getProject()); - Settings.getGlobalSettings().setFocusOnNewCode(false); - } - - @Test - void testSeverityMapping() { - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, null, false)).isEqualTo(SonarLintTextAttributes.MEDIUM); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.MAJOR, false)).isEqualTo(SonarLintTextAttributes.MEDIUM); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.MINOR, false)).isEqualTo(SonarLintTextAttributes.LOW); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.BLOCKER, false)).isEqualTo(SonarLintTextAttributes.HIGH); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.CRITICAL, false)).isEqualTo(SonarLintTextAttributes.HIGH); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.INFO, false)).isEqualTo(SonarLintTextAttributes.LOW); - - Settings.getGlobalSettings().setFocusOnNewCode(true); - connectProjectTo(ServerConnection.newBuilder().setName("connection").build(), "projectKey"); - - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.MAJOR, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.MINOR, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.BLOCKER, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.CRITICAL, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.INFO, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE); - - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.MAJOR, true)).isEqualTo(SonarLintTextAttributes.MEDIUM); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.MINOR, true)).isEqualTo(SonarLintTextAttributes.LOW); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.BLOCKER, true)).isEqualTo(SonarLintTextAttributes.HIGH); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.CRITICAL, true)).isEqualTo(SonarLintTextAttributes.HIGH); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, IssueSeverity.INFO, true)).isEqualTo(SonarLintTextAttributes.LOW); - } - - @Test - void testImpactMapping() { - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), null, null, false)).isEqualTo(SonarLintTextAttributes.MEDIUM); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.MEDIUM, IssueSeverity.MAJOR, false)).isEqualTo(SonarLintTextAttributes.MEDIUM); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.LOW, IssueSeverity.MINOR, false)).isEqualTo(SonarLintTextAttributes.LOW); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.HIGH, IssueSeverity.BLOCKER, false)).isEqualTo(SonarLintTextAttributes.HIGH); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.HIGH, IssueSeverity.CRITICAL, false)).isEqualTo(SonarLintTextAttributes.HIGH); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.LOW, IssueSeverity.INFO, false)).isEqualTo(SonarLintTextAttributes.LOW); - - Settings.getGlobalSettings().setFocusOnNewCode(true); - connectProjectTo(ServerConnection.newBuilder().setName("connection").build(), "projectKey"); - - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.MEDIUM, IssueSeverity.MAJOR, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.LOW, IssueSeverity.MINOR, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.HIGH, IssueSeverity.BLOCKER, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.HIGH, IssueSeverity.CRITICAL, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.LOW, IssueSeverity.INFO, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE); - - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.MEDIUM, IssueSeverity.MAJOR, true)).isEqualTo(SonarLintTextAttributes.MEDIUM); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.LOW, IssueSeverity.MINOR, true)).isEqualTo(SonarLintTextAttributes.LOW); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.HIGH, IssueSeverity.BLOCKER, true)).isEqualTo(SonarLintTextAttributes.HIGH); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.HIGH, IssueSeverity.CRITICAL, true)).isEqualTo(SonarLintTextAttributes.HIGH); - assertThat(SonarExternalAnnotator.getTextAttrsKey(getProject(), ImpactSeverity.LOW, IssueSeverity.INFO, true)).isEqualTo(SonarLintTextAttributes.LOW); - } -} diff --git a/src/test/java/org/sonarlint/intellij/editor/SonarExternalAnnotatorTests.kt b/src/test/java/org/sonarlint/intellij/editor/SonarExternalAnnotatorTests.kt new file mode 100644 index 0000000000..d7ad489353 --- /dev/null +++ b/src/test/java/org/sonarlint/intellij/editor/SonarExternalAnnotatorTests.kt @@ -0,0 +1,103 @@ +/* + * 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.editor + +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.openapi.util.TextRange +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiFile +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mockito +import org.sonarlint.intellij.AbstractSonarLintLightTests +import org.sonarlint.intellij.config.Settings +import org.sonarlint.intellij.config.SonarLintTextAttributes +import org.sonarlint.intellij.finding.persistence.FindingsCache +import org.sonarlint.intellij.fixtures.newSonarQubeConnection +import org.sonarsource.sonarlint.core.commons.ImpactSeverity +import org.sonarsource.sonarlint.core.commons.IssueSeverity + +internal class SonarExternalAnnotatorTests : AbstractSonarLintLightTests() { + private val psiFile: PsiFile = Mockito.mock(PsiFile::class.java) + private val virtualFile: VirtualFile = Mockito.mock(VirtualFile::class.java) + private val store: FindingsCache = Mockito.mock(FindingsCache::class.java) + private val psiFileRange = TextRange(0, 100) + + @BeforeEach + fun set() { + replaceProjectService(FindingsCache::class.java, store) + Mockito.`when`(psiFile.textRange).thenReturn(psiFileRange) + Mockito.`when`(psiFile.virtualFile).thenReturn(virtualFile) + Mockito.`when`(psiFile.fileType).thenReturn(JavaFileType.INSTANCE) + Mockito.`when`(psiFile.project).thenReturn(project) + Settings.getGlobalSettings().isFocusOnNewCode = false + } + + @Test + fun testSeverityMapping() { + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, null, false)).isEqualTo(SonarLintTextAttributes.MEDIUM) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.MAJOR, false)).isEqualTo(SonarLintTextAttributes.MEDIUM) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.MINOR, false)).isEqualTo(SonarLintTextAttributes.LOW) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.BLOCKER, false)).isEqualTo(SonarLintTextAttributes.HIGH) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.CRITICAL, false)).isEqualTo(SonarLintTextAttributes.HIGH) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.INFO, false)).isEqualTo(SonarLintTextAttributes.LOW) + + Settings.getGlobalSettings().isFocusOnNewCode = true + connectProjectTo(newSonarQubeConnection("connection"), "projectKey") + + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.MAJOR, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.MINOR, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.BLOCKER, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.CRITICAL, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.INFO, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE) + + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.MAJOR, true)).isEqualTo(SonarLintTextAttributes.MEDIUM) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.MINOR, true)).isEqualTo(SonarLintTextAttributes.LOW) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.BLOCKER, true)).isEqualTo(SonarLintTextAttributes.HIGH) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.CRITICAL, true)).isEqualTo(SonarLintTextAttributes.HIGH) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, IssueSeverity.INFO, true)).isEqualTo(SonarLintTextAttributes.LOW) + } + + @Test + fun testImpactMapping() { + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, null, null, false)).isEqualTo(SonarLintTextAttributes.MEDIUM) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.MEDIUM, IssueSeverity.MAJOR, false)).isEqualTo(SonarLintTextAttributes.MEDIUM) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.LOW, IssueSeverity.MINOR, false)).isEqualTo(SonarLintTextAttributes.LOW) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.HIGH, IssueSeverity.BLOCKER, false)).isEqualTo(SonarLintTextAttributes.HIGH) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.HIGH, IssueSeverity.CRITICAL, false)).isEqualTo(SonarLintTextAttributes.HIGH) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.LOW, IssueSeverity.INFO, false)).isEqualTo(SonarLintTextAttributes.LOW) + + Settings.getGlobalSettings().isFocusOnNewCode = true + connectProjectTo(newSonarQubeConnection("connection"), "projectKey") + + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.MEDIUM, IssueSeverity.MAJOR, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.LOW, IssueSeverity.MINOR, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.HIGH, IssueSeverity.BLOCKER, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.HIGH, IssueSeverity.CRITICAL, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.LOW, IssueSeverity.INFO, false)).isEqualTo(SonarLintTextAttributes.OLD_CODE) + + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.MEDIUM, IssueSeverity.MAJOR, true)).isEqualTo(SonarLintTextAttributes.MEDIUM) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.LOW, IssueSeverity.MINOR, true)).isEqualTo(SonarLintTextAttributes.LOW) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.HIGH, IssueSeverity.BLOCKER, true)).isEqualTo(SonarLintTextAttributes.HIGH) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.HIGH, IssueSeverity.CRITICAL, true)).isEqualTo(SonarLintTextAttributes.HIGH) + assertThat(SonarExternalAnnotator.getTextAttrsKey(project, ImpactSeverity.LOW, IssueSeverity.INFO, true)).isEqualTo(SonarLintTextAttributes.LOW) + } +} diff --git a/src/test/java/org/sonarlint/intellij/fixtures/ServerConnectionFixtures.kt b/src/test/java/org/sonarlint/intellij/fixtures/ServerConnectionFixtures.kt new file mode 100644 index 0000000000..d83da1656f --- /dev/null +++ b/src/test/java/org/sonarlint/intellij/fixtures/ServerConnectionFixtures.kt @@ -0,0 +1,37 @@ +/* + * 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.fixtures + +import org.sonarlint.intellij.config.global.ServerConnection +import org.sonarlint.intellij.config.global.ServerConnectionCredentials +import org.sonarlint.intellij.config.global.SonarCloudConnection +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"): ServerConnection { + return SonarQubeConnection(name, hostUrl, 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 diff --git a/src/test/java/org/sonarlint/intellij/mediumtests/MultiModuleMediumTests.kt b/src/test/java/org/sonarlint/intellij/mediumtests/MultiModuleMediumTests.kt index 29d4a02ec5..c75aec79fd 100644 --- a/src/test/java/org/sonarlint/intellij/mediumtests/MultiModuleMediumTests.kt +++ b/src/test/java/org/sonarlint/intellij/mediumtests/MultiModuleMediumTests.kt @@ -22,15 +22,15 @@ package org.sonarlint.intellij.mediumtests import org.assertj.core.api.Assertions.assertThat import org.sonarlint.intellij.AbstractSonarLintHeavyTests import org.sonarlint.intellij.common.util.SonarLintUtils -import org.sonarlint.intellij.config.global.ServerConnection import org.sonarlint.intellij.core.ModuleBindingManager +import org.sonarlint.intellij.fixtures.newSonarQubeConnection class MultiModuleMediumTests : AbstractSonarLintHeavyTests() { fun test_should_return_project_key_for_module_binding_override() { val secondModule = createModule("foo") - connectProjectTo(ServerConnection.newBuilder().setName("server1").build(), "project1") + connectProjectTo(newSonarQubeConnection("server1"), "project1") connectModuleTo(secondModule, "project2") assertThat(SonarLintUtils.getService(module, ModuleBindingManager::class.java).resolveProjectKey()).isEqualTo("project1") @@ -38,7 +38,7 @@ class MultiModuleMediumTests : AbstractSonarLintHeavyTests() { } fun test_should_consider_module_binding_if_only_one_module_but_was_previously_overriden() { - connectProjectTo(ServerConnection.newBuilder().setName("server1").build(), "project1") + connectProjectTo(newSonarQubeConnection("server1"), "project1") connectModuleTo("overriden") assertThat(SonarLintUtils.getService(module, ModuleBindingManager::class.java).resolveProjectKey()).isEqualTo("overriden") diff --git a/src/test/java/org/sonarlint/intellij/messages/GlobalConfigurationListenerTests.java b/src/test/java/org/sonarlint/intellij/messages/GlobalConfigurationListenerTests.java deleted file mode 100644 index f0bb965921..0000000000 --- a/src/test/java/org/sonarlint/intellij/messages/GlobalConfigurationListenerTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.messages; - -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.sonarlint.intellij.AbstractSonarLintLightTests; -import org.sonarlint.intellij.config.global.ServerConnection; -import org.sonarlint.intellij.config.global.SonarLintGlobalSettings; - -import static org.assertj.core.api.Assertions.assertThat; - -class GlobalConfigurationListenerTests extends AbstractSonarLintLightTests { - private List testList = new LinkedList<>(); - - @BeforeEach - void prepare() { - testList.add(ServerConnection.newBuilder().setName("name").build()); - } - - @Test - void testChanged() { - List servers = new LinkedList<>(); - GlobalConfigurationListener listener = new GlobalConfigurationListener.Adapter() { - @Override - public void changed(List serverList) { - servers.addAll(serverList); - } - }; - - getProject().getMessageBus().connect().subscribe(GlobalConfigurationListener.TOPIC, listener); - getProject().getMessageBus().syncPublisher(GlobalConfigurationListener.TOPIC).changed(testList); - assertThat(servers).isEqualTo(testList); - } - - @Test - void testApplied() { - List servers = new LinkedList<>(); - var isAutoTrigger = new AtomicBoolean(false); - - GlobalConfigurationListener listener = new GlobalConfigurationListener.Adapter() { - @Override - public void applied(SonarLintGlobalSettings previousSettings, SonarLintGlobalSettings newSettings) { - servers.addAll(newSettings.getServerConnections()); - isAutoTrigger.set(newSettings.isAutoTrigger()); - } - }; - - getProject().getMessageBus().connect().subscribe(GlobalConfigurationListener.TOPIC, listener); - var settings = new SonarLintGlobalSettings(); - settings.setServerConnections(testList); - settings.setAutoTrigger(true); - getProject().getMessageBus().syncPublisher(GlobalConfigurationListener.TOPIC).applied(new SonarLintGlobalSettings(), settings); - - assertThat(servers).isEqualTo(testList); - assertThat(isAutoTrigger.get()).isTrue(); - } -} diff --git a/src/test/java/org/sonarlint/intellij/messages/GlobalConfigurationListenerTests.kt b/src/test/java/org/sonarlint/intellij/messages/GlobalConfigurationListenerTests.kt new file mode 100644 index 0000000000..dbabd9fa25 --- /dev/null +++ b/src/test/java/org/sonarlint/intellij/messages/GlobalConfigurationListenerTests.kt @@ -0,0 +1,71 @@ +/* + * 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.messages + +import java.util.LinkedList +import java.util.concurrent.atomic.AtomicBoolean +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.sonarlint.intellij.AbstractSonarLintLightTests +import org.sonarlint.intellij.config.global.ServerConnection +import org.sonarlint.intellij.config.global.SonarLintGlobalSettings +import org.sonarlint.intellij.fixtures.newSonarQubeConnection + +internal class GlobalConfigurationListenerTests : AbstractSonarLintLightTests() { + private val testList: MutableList = LinkedList() + + @BeforeEach + fun prepare() { + testList.add(newSonarQubeConnection("name")) + } + + @Test + fun testChanged() { + val servers: MutableList = LinkedList() + val listener: GlobalConfigurationListener = object : GlobalConfigurationListener.Adapter() { + override fun changed(serverList: List) { + servers.addAll(serverList) + } + } + + project.messageBus.connect().subscribe(GlobalConfigurationListener.TOPIC, listener) + project.messageBus.syncPublisher(GlobalConfigurationListener.TOPIC).changed(testList) + Assertions.assertThat(servers).isEqualTo(testList) + } + + @Test + fun testApplied() { + val isAutoTrigger = AtomicBoolean(false) + + val listener: GlobalConfigurationListener = object : GlobalConfigurationListener.Adapter() { + override fun applied(previousSettings: SonarLintGlobalSettings, newSettings: SonarLintGlobalSettings) { + isAutoTrigger.set(newSettings.isAutoTrigger) + } + } + + project.messageBus.connect().subscribe(GlobalConfigurationListener.TOPIC, listener) + val settings = SonarLintGlobalSettings() + settings.isAutoTrigger = true + project.messageBus.syncPublisher(GlobalConfigurationListener.TOPIC).applied(SonarLintGlobalSettings(), settings) + + Assertions.assertThat(isAutoTrigger.get()).isTrue() + } +} diff --git a/src/test/java/org/sonarlint/intellij/tasks/ConnectionTestTaskTests.kt b/src/test/java/org/sonarlint/intellij/tasks/ConnectionTestTaskTests.kt index dbe23164f8..de8a5713ba 100644 --- a/src/test/java/org/sonarlint/intellij/tasks/ConnectionTestTaskTests.kt +++ b/src/test/java/org/sonarlint/intellij/tasks/ConnectionTestTaskTests.kt @@ -21,20 +21,20 @@ package org.sonarlint.intellij.tasks import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProgressIndicator +import java.util.concurrent.CompletableFuture import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.sonarlint.intellij.AbstractSonarLintLightTests -import org.sonarlint.intellij.config.global.ServerConnection import org.sonarlint.intellij.core.BackendService -import java.util.concurrent.CompletableFuture +import org.sonarlint.intellij.fixtures.newPartialConnection class ConnectionTestTaskTests : AbstractSonarLintLightTests() { @Test fun should_mark_progress_indicator_as_indeterminate() { - val task = ConnectionTestTask(ServerConnection.newBuilder().setHostUrl("invalid_url").build()) + val task = ConnectionTestTask(newPartialConnection()) val progress = mock(ProgressIndicator::class.java) task.run(progress) @@ -44,8 +44,8 @@ class ConnectionTestTaskTests : AbstractSonarLintLightTests() { @Test fun should_not_validate_connection_when_host_does_not_exist() { - val server = ServerConnection.newBuilder().setHostUrl("invalid_url").build() - val task = ConnectionTestTask(server) + val connection = newPartialConnection("invalid_url") + val task = ConnectionTestTask(connection) task.run(mock(ProgressIndicator::class.java)) @@ -56,12 +56,12 @@ class ConnectionTestTaskTests : AbstractSonarLintLightTests() { @Test fun should_return_no_result_if_task_has_been_canceled() { + val connection = newPartialConnection() val progress = mock(ProgressIndicator::class.java) - val server = mock(ServerConnection::class.java) val backendService = mock(BackendService::class.java) replaceProjectService(BackendService::class.java, backendService) - `when`(backendService.validateConnection(server)).thenReturn(CompletableFuture()) - val task = ConnectionTestTask(server) + `when`(backendService.validateConnection(connection)).thenReturn(CompletableFuture()) + val task = ConnectionTestTask(connection) `when`(progress.checkCanceled()).thenThrow(ProcessCanceledException()) task.run(progress) diff --git a/src/test/java/org/sonarlint/intellij/vcs/DefaultVcsServiceTests.kt b/src/test/java/org/sonarlint/intellij/vcs/DefaultVcsServiceTests.kt index 7ab5e4e04a..37efdc1fa7 100644 --- a/src/test/java/org/sonarlint/intellij/vcs/DefaultVcsServiceTests.kt +++ b/src/test/java/org/sonarlint/intellij/vcs/DefaultVcsServiceTests.kt @@ -28,6 +28,7 @@ import com.intellij.openapi.vcs.VcsDirectoryMapping import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl import com.intellij.testFramework.PsiTestUtil import git4idea.GitVcs +import java.nio.file.Paths import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -35,15 +36,14 @@ import org.mockito.Mockito.mock import org.mockito.kotlin.whenever import org.sonarlint.intellij.AbstractSonarLintHeavyTests import org.sonarlint.intellij.common.vcs.VcsService -import org.sonarlint.intellij.config.global.ServerConnection import org.sonarlint.intellij.core.ProjectBinding +import org.sonarlint.intellij.fixtures.newSonarQubeConnection import org.sonarlint.intellij.messages.PROJECT_BINDING_TOPIC import org.sonarlint.intellij.messages.SERVER_BRANCHES_TOPIC import org.sonarlint.intellij.util.ImmediateExecutorService import org.sonarsource.sonarlint.core.client.api.connected.ConnectedSonarLintEngine import org.sonarsource.sonarlint.core.client.api.connected.ProjectBranches import org.sonarsource.sonarlint.core.serverconnection.storage.StorageException -import java.nio.file.Paths internal class DefaultVcsServiceTests : AbstractSonarLintHeavyTests() { @@ -64,7 +64,7 @@ internal class DefaultVcsServiceTests : AbstractSonarLintHeavyTests() { @Test fun test_should_not_resolve_server_branch_when_project_is_not_a_git_repo() { - connectProjectTo(ServerConnection.newBuilder().setName("connection").build(), "projectKey") + connectProjectTo(newSonarQubeConnection("connection"), "projectKey") connectModuleTo(module, "moduleKey") whenever(connectedEngine.getServerBranches("moduleKey")).thenReturn( ProjectBranches( @@ -91,7 +91,7 @@ internal class DefaultVcsServiceTests : AbstractSonarLintHeavyTests() { val module = createModule("aModule") addContentRootWithGitRepo(module, "contentRoot1") addContentRootWithGitRepo(module, "contentRoot2") - connectProjectTo(ServerConnection.newBuilder().setName("connection").build(), "projectKey") + connectProjectTo(newSonarQubeConnection("connection"), "projectKey") connectModuleTo(module, "moduleKey") whenever(connectedEngine.getServerBranches("moduleKey")).thenReturn( ProjectBranches( @@ -110,7 +110,7 @@ internal class DefaultVcsServiceTests : AbstractSonarLintHeavyTests() { fun test_should_resolve_exact_server_branch_when_module_is_bound_and_current_branch_is_known_on_server() { val module = createModule("aModule") addContentRootWithGitRepo(module) - connectProjectTo(ServerConnection.newBuilder().setName("connection").build(), "projectKey") + connectProjectTo(newSonarQubeConnection("connection"), "projectKey") connectModuleTo(module, "moduleKey") whenever(connectedEngine.getServerBranches("moduleKey")).thenReturn( ProjectBranches( @@ -129,7 +129,7 @@ internal class DefaultVcsServiceTests : AbstractSonarLintHeavyTests() { fun test_should_return_cached_value_when_already_resolved() { val module = createModule("aModule") addContentRootWithGitRepo(module) - connectProjectTo(ServerConnection.newBuilder().setName("connection").build(), "projectKey") + connectProjectTo(newSonarQubeConnection("connection"), "projectKey") connectModuleTo(module, "moduleKey") whenever(connectedEngine.getServerBranches("moduleKey")).thenReturn( ProjectBranches( @@ -149,7 +149,7 @@ internal class DefaultVcsServiceTests : AbstractSonarLintHeavyTests() { fun test_should_resolve_closest_server_branch() { val module = createModule("aModule") addContentRootWithGitRepo(module) - connectProjectTo(ServerConnection.newBuilder().setName("connection").build(), "projectKey") + connectProjectTo(newSonarQubeConnection("connection"), "projectKey") connectModuleTo(module, "moduleKey") whenever(connectedEngine.getServerBranches("moduleKey")).thenReturn( ProjectBranches( @@ -169,7 +169,7 @@ internal class DefaultVcsServiceTests : AbstractSonarLintHeavyTests() { fun test_should_clear_cache_when_project_is_unbound() { val module = createModule("aModule") addContentRootWithGitRepo(module) - connectProjectTo(ServerConnection.newBuilder().setName("connection").build(), "projectKey") + connectProjectTo(newSonarQubeConnection("connection"), "projectKey") whenever(connectedEngine.getServerBranches("projectKey")).thenReturn( ProjectBranches( setOf("master", "branch1"), @@ -197,7 +197,7 @@ internal class DefaultVcsServiceTests : AbstractSonarLintHeavyTests() { getEngineManager().registerEngine(connectedEngine, "connection") assertThat(vcsService.getServerBranchName(module)).isNull() - connectProjectTo(ServerConnection.newBuilder().setName("connection").build(), "projectKey") + connectProjectTo(newSonarQubeConnection("connection"), "projectKey") project.messageBus.syncPublisher(PROJECT_BINDING_TOPIC) .bindingChanged(null, ProjectBinding("connection", "projectKey", emptyMap())) @@ -215,7 +215,7 @@ internal class DefaultVcsServiceTests : AbstractSonarLintHeavyTests() { ) ) getEngineManager().registerEngine(connectedEngine, "connection") - connectProjectTo(ServerConnection.newBuilder().setName("connection").build(), "projectKey") + connectProjectTo(newSonarQubeConnection("connection"), "projectKey") assertThat(vcsService.getServerBranchName(module)).isEqualTo("master") whenever(connectedEngine.getServerBranches("projectKey")).thenReturn( @@ -243,7 +243,7 @@ internal class DefaultVcsServiceTests : AbstractSonarLintHeavyTests() { ) ) getEngineManager().registerEngine(connectedEngine, "connection") - connectProjectTo(ServerConnection.newBuilder().setName("connection").build(), "projectKey") + connectProjectTo(newSonarQubeConnection("connection"), "projectKey") assertThat(vcsService.getServerBranchName(module)).isEqualTo("master") ModuleManager.getInstance(project).disposeModule(module)