diff --git a/src/main/kotlin/io/snyk/plugin/services/SnykCliAuthenticationService.kt b/src/main/kotlin/io/snyk/plugin/services/SnykCliAuthenticationService.kt index 5b94d19ab..1d05bc1bb 100644 --- a/src/main/kotlin/io/snyk/plugin/services/SnykCliAuthenticationService.kt +++ b/src/main/kotlin/io/snyk/plugin/services/SnykCliAuthenticationService.kt @@ -16,7 +16,7 @@ import com.intellij.util.PlatformIcons import io.snyk.plugin.cli.ConsoleCommandRunner import io.snyk.plugin.getCliFile import io.snyk.plugin.getPluginPath -import io.snyk.plugin.getSnykCliDownloaderService +import io.snyk.plugin.getSnykTaskQueueService import io.snyk.plugin.pluginSettings import io.snyk.plugin.ui.SnykBalloonNotificationHelper import io.snyk.plugin.ui.getReadOnlyClickableHtmlJEditorPane @@ -55,7 +55,7 @@ class SnykCliAuthenticationService(val project: Project) { val downloadCliTask: () -> Unit = { if (!getCliFile().exists()) { val currentIndicator = ProgressManager.getInstance().progressIndicator - getSnykCliDownloaderService().downloadLatestRelease(currentIndicator, project) + getSnykTaskQueueService(project)?.downloadLatestRelease(currentIndicator) } else { logger.debug("Skip CLI download, since it was already downloaded") } diff --git a/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt b/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt index 4a8a96874..a6bf9d20b 100644 --- a/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt +++ b/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt @@ -28,6 +28,7 @@ import io.snyk.plugin.isSnykCodeRunning import io.snyk.plugin.net.ClientException import io.snyk.plugin.pluginSettings import io.snyk.plugin.snykcode.core.RunUtils +import io.snyk.plugin.ui.SnykBalloonNotificationHelper import io.snyk.plugin.ui.SnykBalloonNotifications import org.jetbrains.annotations.TestOnly import snyk.common.SnykError @@ -86,6 +87,7 @@ class SnykTaskQueueService(val project: Project) { fun scan() { taskQueue.run(object : Task.Backgroundable(project, "Snyk: initializing...", true) { override fun run(indicator: ProgressIndicator) { + // FIXME: this should be using content roots instead of basePath project.basePath?.let { if (!confirmScanningAndSetWorkspaceTrustedStateIfNeeded(project, Paths.get(it))) return } @@ -115,7 +117,7 @@ class SnykTaskQueueService(val project: Project) { fun waitUntilCliDownloadedIfNeeded(indicator: ProgressIndicator) { indicator.text = "Snyk waits for CLI to be downloaded..." - downloadLatestRelease() + downloadLatestRelease(indicator) do { indicator.checkCanceled() Thread.sleep(WAIT_FOR_DOWNLOAD_MILLIS) @@ -228,7 +230,7 @@ class SnykTaskQueueService(val project: Project) { if (ossResult.isSuccessful()) { scanPublisher?.scanningOssFinished(ossResult) } else { - scanPublisher?.scanningOssError(ossResult.getFirstError()!!) + ossResult.getFirstError()?.let { scanPublisher?.scanningOssError(it) } } } DaemonCodeAnalyzer.getInstance(project).restart() @@ -279,12 +281,24 @@ class SnykTaskQueueService(val project: Project) { }) } - fun downloadLatestRelease() { + fun downloadLatestRelease(indicator: ProgressIndicator) { + // abort even before submitting a task + if (!pluginSettings().manageBinariesAutomatically) { + if (!isCliInstalled()) { + val msg = + "The plugin cannot scan without Snyk CLI, but automatic download is disabled. " + + "Please put a Snyk CLI executable in ${pluginSettings().cliPath} and retry." + SnykBalloonNotificationHelper.showError(msg, project) + } + indicator.cancel() + return + } + val cliDownloader = getSnykCliDownloaderService() + taskQueue.run(object : Task.Backgroundable(project, "Check Snyk CLI presence", true) { override fun run(indicator: ProgressIndicator) { cliDownloadPublisher.checkCliExistsStarted() if (project.isDisposed) return - val cliDownloader = getSnykCliDownloaderService() if (!isCliInstalled()) { cliDownloader.downloadLatestRelease(indicator, project) diff --git a/src/main/kotlin/io/snyk/plugin/services/download/CliDownloaderService.kt b/src/main/kotlin/io/snyk/plugin/services/download/CliDownloaderService.kt index 48502a0d1..aa5dcf9d0 100644 --- a/src/main/kotlin/io/snyk/plugin/services/download/CliDownloaderService.kt +++ b/src/main/kotlin/io/snyk/plugin/services/download/CliDownloaderService.kt @@ -9,11 +9,9 @@ import com.intellij.util.io.HttpRequests import io.snyk.plugin.cli.Platform import io.snyk.plugin.events.SnykCliDownloadListener import io.snyk.plugin.getCliFile -import io.snyk.plugin.isCliInstalled import io.snyk.plugin.pluginSettings import io.snyk.plugin.services.download.HttpRequestHelper.createRequest import io.snyk.plugin.tail -import io.snyk.plugin.ui.SnykBalloonNotificationHelper import java.io.IOException import java.time.LocalDate import java.time.temporal.ChronoUnit @@ -60,18 +58,9 @@ class SnykCliDownloaderService { } fun downloadLatestRelease(indicator: ProgressIndicator, project: Project) { - if (!pluginSettings().manageBinariesAutomatically) { - if (!isCliInstalled()) { - val msg = - "The plugin cannot scan without Snyk CLI, but automatic download is disabled. " + - "Please put a Snyk CLI executable in ${pluginSettings().cliPath} and retry." - SnykBalloonNotificationHelper.showError(msg, project) - } - return - } + currentProgressIndicator = indicator cliDownloadPublisher.cliDownloadStarted() indicator.isIndeterminate = true - currentProgressIndicator = indicator var succeeded = false val cliFile = getCliFile() try { diff --git a/src/test/java/snyk/common/lsp/LanguageServerWrapperTest.kt b/src/test/java/snyk/common/lsp/LanguageServerWrapperTest.kt index 60680be19..b9298c481 100644 --- a/src/test/java/snyk/common/lsp/LanguageServerWrapperTest.kt +++ b/src/test/java/snyk/common/lsp/LanguageServerWrapperTest.kt @@ -62,6 +62,10 @@ class LanguageServerWrapperTest { @Test fun `sendReportAnalyticsCommand should send a reportAnalytics command to the language server`() { + cut.languageClient = mockk(relaxed = true) + val processMock = mockk(relaxed = true) + cut.process = processMock + every { processMock.info().startInstant().isPresent } returns true every { lsMock.workspaceService.executeCommand(any()) } returns CompletableFuture.completedFuture(null) diff --git a/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt b/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt index b756d1ff3..94d0b0219 100644 --- a/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt +++ b/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt @@ -2,11 +2,13 @@ package io.snyk.plugin.services import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.service +import com.intellij.openapi.progress.EmptyProgressIndicator import com.intellij.testFramework.LightPlatformTestCase import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.replaceService import io.mockk.every import io.mockk.mockk +import io.mockk.mockkObject import io.mockk.mockkStatic import io.mockk.spyk import io.mockk.unmockkAll @@ -47,21 +49,29 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { super.setUp() unmockkAll() resetSettings(project) + ossServiceMock = mockk(relaxed = true) project.replaceService(OssService::class.java, ossServiceMock, project) + mockSnykApiServiceSastEnabled() replaceSnykApiServiceMockInContainer() + mockkStatic("io.snyk.plugin.UtilsKt") mockkStatic("snyk.trust.TrustedProjectsKt") + downloaderServiceMock = spyk(SnykCliDownloaderService()) every { downloaderServiceMock.requestLatestReleasesInformation() } returns LatestReleaseInfo( "http://testUrl", "testReleaseInfo", "testTag" ) + every { getSnykCliDownloaderService() } returns downloaderServiceMock every { downloaderServiceMock.isFourDaysPassedSinceLastCheck() } returns false every { confirmScanningAndSetWorkspaceTrustedStateIfNeeded(any(), any()) } returns true + + mockkObject(SnykTaskQueueService.Companion) + every { SnykTaskQueueService.ls } returns mockk(relaxed = true) } private fun mockSnykApiServiceSastEnabled() { @@ -94,7 +104,7 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { assertTrue(snykTaskQueueService.getTaskQueue().isEmpty) - snykTaskQueueService.downloadLatestRelease() + snykTaskQueueService.downloadLatestRelease(EmptyProgressIndicator()) PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertTrue(snykTaskQueueService.getTaskQueue().isEmpty) @@ -102,32 +112,27 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { } fun testCliDownloadBeforeScanIfNeeded() { - val cliFile = getCliFile() - val downloaderMock = setupMockForDownloadTest() - every { downloaderMock.expectedSha() } returns "test" - every { downloaderMock.downloadFile(any(), any(), any()) } returns cliFile setupAppSettingsForDownloadTests() + every { isCliInstalled() } returns true val snykTaskQueueService = project.service() snykTaskQueueService.scan() - PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertTrue(snykTaskQueueService.getTaskQueue().isEmpty) - - verify { downloaderMock.downloadFile(any(), any(), any()) } + verify { isCliInstalled() } } fun testDontDownloadCLIIfUpdatesDisabled() { val downloaderMock = setupMockForDownloadTest() val settings = setupAppSettingsForDownloadTests() settings.manageBinariesAutomatically = false - val snykTaskQueueService = project.service() + snykTaskQueueService.scan() - PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertTrue(snykTaskQueueService.getTaskQueue().isEmpty) - verify(exactly = 0) { downloaderMock.downloadFile(any(), any(), any()) } } @@ -159,7 +164,7 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { setProject(null) // to avoid double disposing effort in tearDown // the Task should roll out gracefully without any Exception or Error - snykTaskQueueService.downloadLatestRelease() + snykTaskQueueService.downloadLatestRelease(EmptyProgressIndicator()) } fun testSastEnablementCheckInScan() { diff --git a/src/test/kotlin/io/snyk/plugin/services/download/SnykCliDownloaderServiceTest.kt b/src/test/kotlin/io/snyk/plugin/services/download/SnykCliDownloaderServiceTest.kt index 8e6d26d6a..d655d14e8 100644 --- a/src/test/kotlin/io/snyk/plugin/services/download/SnykCliDownloaderServiceTest.kt +++ b/src/test/kotlin/io/snyk/plugin/services/download/SnykCliDownloaderServiceTest.kt @@ -2,17 +2,12 @@ package io.snyk.plugin.services.download import io.mockk.confirmVerified import io.mockk.every -import io.mockk.justRun import io.mockk.mockk -import io.mockk.mockkObject import io.mockk.mockkStatic import io.mockk.unmockkAll -import io.mockk.unmockkObject import io.mockk.verify -import io.snyk.plugin.isCliInstalled import io.snyk.plugin.pluginSettings import io.snyk.plugin.services.SnykApplicationSettingsStateService -import io.snyk.plugin.ui.SnykBalloonNotificationHelper import org.junit.After import org.junit.Assert.assertNull import org.junit.Before @@ -43,27 +38,4 @@ class SnykCliDownloaderServiceTest { verify { settingsStateService.manageBinariesAutomatically } confirmVerified(settingsStateService) } - - @Test - fun `downloadLatestRelease should return without doing anything if updates disabled`() { - val cut = SnykCliDownloaderService() - every { settingsStateService.manageBinariesAutomatically } returns false - every { settingsStateService.cliPath } returns "dummyPath" - every { isCliInstalled() } returns false - mockkObject(SnykBalloonNotificationHelper) - justRun { SnykBalloonNotificationHelper.showError(any(), any()) } - - cut.downloadLatestRelease(mockk(), mockk()) - - verify { settingsStateService.manageBinariesAutomatically } - verify { settingsStateService.cliPath } - verify(exactly = 1) { - SnykBalloonNotificationHelper.showError( - any(), any() - ) - } - confirmVerified(settingsStateService) // this makes sure, no publisher, no cli path, nothing is used - confirmVerified(SnykBalloonNotificationHelper) - unmockkObject(SnykBalloonNotificationHelper) - } }