From da5e95aaf9e6cb94a5c23fbb06ec64f1afd099e9 Mon Sep 17 00:00:00 2001 From: jamesmcnamara Date: Fri, 18 Oct 2024 17:28:49 -0700 Subject: [PATCH 1/4] only clear credentials if the cody app was uninstalled --- src/main/kotlin/com/sourcegraph/cody/agent/CodyAgent.kt | 4 +--- .../com/sourcegraph/cody/config/ui/CheckUpdatesTask.kt | 6 +++--- .../sourcegraph/cody/initialization/UninstallListener.kt | 5 +++++ src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt | 5 ++++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgent.kt b/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgent.kt index 65f3e23a5d..5820cc64d4 100644 --- a/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgent.kt +++ b/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgent.kt @@ -3,7 +3,6 @@ package com.sourcegraph.cody.agent import com.intellij.ide.plugins.PluginManagerCore import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.project.Project import com.intellij.openapi.util.SystemInfoRt import com.intellij.util.net.HttpConfigurable @@ -92,7 +91,6 @@ private constructor( companion object { private val logger = Logger.getInstance(CodyAgent::class.java) - private val PLUGIN_ID = PluginId.getId("com.sourcegraph.jetbrains") private const val DEFAULT_AGENT_DEBUG_PORT = 3113 // Also defined in agent/src/cli/jsonrpc.ts @JvmField val executorService: ExecutorService = Executors.newCachedThreadPool() @@ -345,7 +343,7 @@ private constructor( return if (fromProperty.isNotEmpty()) { Paths.get(fromProperty) } else { - PluginManagerCore.getPlugin(PLUGIN_ID)?.pluginPath + PluginManagerCore.getPlugin(ConfigUtil.getPluginId())?.pluginPath } } diff --git a/src/main/kotlin/com/sourcegraph/cody/config/ui/CheckUpdatesTask.kt b/src/main/kotlin/com/sourcegraph/cody/config/ui/CheckUpdatesTask.kt index 8845c39478..fc26273798 100644 --- a/src/main/kotlin/com/sourcegraph/cody/config/ui/CheckUpdatesTask.kt +++ b/src/main/kotlin/com/sourcegraph/cody/config/ui/CheckUpdatesTask.kt @@ -7,7 +7,6 @@ import com.intellij.notification.NotificationAction import com.intellij.notification.NotificationType import com.intellij.notification.impl.NotificationFullContent import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.Task @@ -16,13 +15,15 @@ import com.intellij.openapi.updateSettings.impl.PluginDownloader import com.intellij.openapi.updateSettings.impl.UpdateChecker import com.intellij.openapi.util.BuildNumber import com.sourcegraph.common.NotificationGroups +import com.sourcegraph.config.ConfigUtil import java.lang.reflect.InvocationTargetException class CheckUpdatesTask(project: Project) : Task.Backgroundable(project, "Checking for Sourcegraph Cody + Code Search update...", false) { override fun run(indicator: ProgressIndicator) { - val availableUpdate = getAvailablePluginDownloaders(indicator).find { it.id == pluginId } + val availableUpdate = + getAvailablePluginDownloaders(indicator).find { it.id == ConfigUtil.getPluginId() } if (availableUpdate != null) { CustomPluginRepositoryService.getInstance().clearCache() notifyAboutTheUpdate(project) @@ -31,7 +32,6 @@ class CheckUpdatesTask(project: Project) : companion object { private val logger = Logger.getInstance(CheckUpdatesTask::class.java) - private val pluginId = PluginId.getId("com.sourcegraph.jetbrains") fun getAvailablePluginDownloaders(indicator: ProgressIndicator): Collection { try { diff --git a/src/main/kotlin/com/sourcegraph/cody/initialization/UninstallListener.kt b/src/main/kotlin/com/sourcegraph/cody/initialization/UninstallListener.kt index a4f8ccfd6d..1d63357f84 100644 --- a/src/main/kotlin/com/sourcegraph/cody/initialization/UninstallListener.kt +++ b/src/main/kotlin/com/sourcegraph/cody/initialization/UninstallListener.kt @@ -12,6 +12,7 @@ import com.sourcegraph.cody.agent.protocol.BillingProduct import com.sourcegraph.cody.agent.protocol.TelemetryEventParameters import com.sourcegraph.cody.config.CodyAuthenticationManager import com.sourcegraph.cody.telemetry.TelemetryV2 +import com.sourcegraph.config.ConfigUtil import java.util.concurrent.TimeUnit class UninstallListener : StartupActivity { @@ -19,6 +20,10 @@ class UninstallListener : StartupActivity { PluginInstaller.addStateListener( object : PluginStateListener { override fun uninstall(descriptor: IdeaPluginDescriptor) { + // Only run for this plugin + if (descriptor.pluginId != ConfigUtil.getPluginId()) { + return + } val authManager = CodyAuthenticationManager.getInstance() authManager.setActiveAccount(null) authManager.removeAll() diff --git a/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt b/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt index 93abe6a557..3de60ada2a 100644 --- a/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt +++ b/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt @@ -29,6 +29,7 @@ object ConfigUtil { const val CODY_DISPLAY_NAME = "Cody" const val CODE_SEARCH_DISPLAY_NAME = "Code Search" const val SOURCEGRAPH_DISPLAY_NAME = "Sourcegraph" + const val PLUGIN_ID = "com.sourcegraph.jetbrains" private const val FEATURE_FLAGS_ENV_VAR = "CODY_JETBRAINS_FEATURES" private val logger = Logger.getInstance(ConfigUtil::class.java) @@ -149,11 +150,13 @@ object ConfigUtil { return settingsProperties + additionalProperties } + @JvmStatic @Contract(pure = true) fun getPluginId(): PluginId = PluginId.getId(PLUGIN_ID) + @JvmStatic @Contract(pure = true) fun getPluginVersion(): String { // Internal version - val plugin = PluginManagerCore.getPlugin(PluginId.getId("com.sourcegraph.jetbrains")) + val plugin = PluginManagerCore.getPlugin(getPluginId()) return if (plugin != null) plugin.version else "unknown" } From 7984cb2b4b723d432e1521904b3b7b61d8499907 Mon Sep 17 00:00:00 2001 From: jamesmcnamara Date: Fri, 25 Oct 2024 16:04:25 -0700 Subject: [PATCH 2/4] added unit test --- build.gradle.kts | 1 + .../initialization/UninstallListenerTest.kt | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/test/kotlin/com/sourcegraph/cody/initialization/UninstallListenerTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index c00c9a63bb..2a15508b09 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -135,6 +135,7 @@ dependencies { testImplementation("net.java.dev.jna:jna:5.10.0") testImplementation("org.awaitility:awaitility-kotlin:4.2.1") testImplementation("org.mockito:mockito-core:5.12.0") + testImplementation("io.mockk:mockk:1.13.13") testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:2.0.0") testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1") diff --git a/src/test/kotlin/com/sourcegraph/cody/initialization/UninstallListenerTest.kt b/src/test/kotlin/com/sourcegraph/cody/initialization/UninstallListenerTest.kt new file mode 100644 index 0000000000..6e1eb58d11 --- /dev/null +++ b/src/test/kotlin/com/sourcegraph/cody/initialization/UninstallListenerTest.kt @@ -0,0 +1,45 @@ +package com.sourcegraph.cody.initialization + +import com.intellij.ide.plugins.* +import com.intellij.testFramework.fixtures.BasePlatformTestCase +import com.sourcegraph.cody.config.CodyAuthenticationManager +import com.sourcegraph.cody.telemetry.TelemetryV2 +import com.sourcegraph.config.ConfigUtil.getPluginId +import io.mockk.* + +@Suppress("UnstableApiUsage") +class UninstallListenerTest : BasePlatformTestCase() { + + fun `test plugin uninstall cleans up resources`() { + val uninstallListener = UninstallListener() + + // Mock dependencies + // relaxed = true to allow unit returning methods to auto-mock + val authManager = mockk(relaxed = true) + mockkObject(CodyAuthenticationManager) + every { + CodyAuthenticationManager.getInstance() + } returns authManager + mockkObject(TelemetryV2) + every { + TelemetryV2.sendTelemetryEvent(any(), any(), any()) + } returns Unit + + + // Execute uninstall + uninstallListener.runActivity(project) + val plugin = PluginManagerCore.findPlugin(getPluginId()) + plugin ?: throw Exception("Plugin not found") + PluginInstaller.prepareToUninstall(plugin) + verify { + authManager.setActiveAccount(null) + authManager.removeAll() + TelemetryV2.sendTelemetryEvent( + any(), + "cody.extension", + "uninstalled", + any() + ) + } + } +} From 1c05f8799d5a8783b30f9917b302096013d0b3f4 Mon Sep 17 00:00:00 2001 From: jamesmcnamara Date: Fri, 25 Oct 2024 16:26:34 -0700 Subject: [PATCH 3/4] added unit test --- .../initialization/UninstallListenerTest.kt | 61 +++++++++++++------ 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/src/test/kotlin/com/sourcegraph/cody/initialization/UninstallListenerTest.kt b/src/test/kotlin/com/sourcegraph/cody/initialization/UninstallListenerTest.kt index 6e1eb58d11..94de9990a4 100644 --- a/src/test/kotlin/com/sourcegraph/cody/initialization/UninstallListenerTest.kt +++ b/src/test/kotlin/com/sourcegraph/cody/initialization/UninstallListenerTest.kt @@ -1,45 +1,66 @@ package com.sourcegraph.cody.initialization import com.intellij.ide.plugins.* +import com.intellij.openapi.extensions.PluginId import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.sourcegraph.cody.config.CodyAuthenticationManager import com.sourcegraph.cody.telemetry.TelemetryV2 -import com.sourcegraph.config.ConfigUtil.getPluginId +import com.sourcegraph.config.ConfigUtil import io.mockk.* @Suppress("UnstableApiUsage") class UninstallListenerTest : BasePlatformTestCase() { - fun `test plugin uninstall cleans up resources`() { - val uninstallListener = UninstallListener() + private val uninstallListener = UninstallListener() + + // Mock dependencies + // relaxed = true to allow unit returning methods to auto-mock + private val authManager = mockk(relaxed = true) - // Mock dependencies - // relaxed = true to allow unit returning methods to auto-mock - val authManager = mockk(relaxed = true) + override fun setUp() { + super.setUp() + + // setup mock objects mockkObject(CodyAuthenticationManager) - every { - CodyAuthenticationManager.getInstance() - } returns authManager + every { CodyAuthenticationManager.getInstance() } returns authManager mockkObject(TelemetryV2) - every { - TelemetryV2.sendTelemetryEvent(any(), any(), any()) - } returns Unit + every { TelemetryV2.sendTelemetryEvent(any(), any(), any()) } returns Unit + } + private fun getPlugin() = + PluginManagerCore.findPlugin(ConfigUtil.getPluginId()) ?: throw Exception("Plugin not found") + fun `test plugin uninstall cleans up resources`() { // Execute uninstall uninstallListener.runActivity(project) - val plugin = PluginManagerCore.findPlugin(getPluginId()) - plugin ?: throw Exception("Plugin not found") + val plugin = getPlugin() PluginInstaller.prepareToUninstall(plugin) verify { authManager.setActiveAccount(null) authManager.removeAll() - TelemetryV2.sendTelemetryEvent( - any(), - "cody.extension", - "uninstalled", - any() - ) + TelemetryV2.sendTelemetryEvent(any(), "cody.extension", "uninstalled", any()) + } + } + + fun `test plugin uninstall does nothing for unrelated plugins`() { + // Execute uninstall + val plugin = getPlugin() + uninstallListener.runActivity(project) + + // Now mock out config util so that it returns a different plugin id + // so that the UninstallListener thinks it's a different plugin + mockkStatic(ConfigUtil::getPluginId) + every { ConfigUtil.getPluginId() } returns PluginId.getId("com.sourcegraph.cody.test") + + PluginInstaller.prepareToUninstall(plugin) + // Remove the static method mock so that it doesn't interfere with other tests + unmockkStatic(ConfigUtil::getPluginId) + + // Verify that the uninstall listener didn't do anything + verify(exactly = 0) { + authManager.setActiveAccount(null) + authManager.removeAll() + TelemetryV2.sendTelemetryEvent(any(), "cody.extension", "uninstalled", any()) } } } From 994b4decb1ba82206d3de2f48ca1ca20aba569de Mon Sep 17 00:00:00 2001 From: jamesmcnamara Date: Fri, 25 Oct 2024 16:35:06 -0700 Subject: [PATCH 4/4] unreverted changes --- src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentServer.kt | 2 ++ .../com/sourcegraph/cody/config/CodyAuthenticationManager.kt | 4 ++++ src/main/resources/META-INF/plugin.xml | 2 ++ 3 files changed, 8 insertions(+) diff --git a/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentServer.kt b/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentServer.kt index f882c485b9..41cc186393 100644 --- a/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentServer.kt +++ b/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentServer.kt @@ -171,4 +171,6 @@ interface _LegacyAgentServer { @JsonRequest("testing/requestErrors") fun testingRequestErrors(): CompletableFuture> + + @JsonRequest("extension/reset") fun extension_reset(params: Null?): CompletableFuture } diff --git a/src/main/kotlin/com/sourcegraph/cody/config/CodyAuthenticationManager.kt b/src/main/kotlin/com/sourcegraph/cody/config/CodyAuthenticationManager.kt index 7b86b87751..bd75be4b83 100644 --- a/src/main/kotlin/com/sourcegraph/cody/config/CodyAuthenticationManager.kt +++ b/src/main/kotlin/com/sourcegraph/cody/config/CodyAuthenticationManager.kt @@ -273,6 +273,10 @@ class CodyAuthenticationManager : fun showInvalidAccessTokenError() = getIsTokenInvalid().getNow(null) == true + fun removeAll() { + accountManager.accounts.forEach { accountManager.removeAccount(it) } + } + override fun dispose() { scheduler.shutdown() } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index eac6850783..06ae3a4a6c 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -66,6 +66,8 @@ serviceImplementation="com.sourcegraph.find.FindService"/> +