From bf46cf72e2ee70bd0301b6fff7dfc9bed3846a3b Mon Sep 17 00:00:00 2001 From: Bastian Doetsch Date: Mon, 9 Oct 2023 16:50:10 +0200 Subject: [PATCH] feat: display up to 60 chars of the filepath in tree view (#451) * feat: display up to 70 chars of file path in tree view for file nodes * docs: update CHANGELOG.md * chore: update linter rules and fix some linter warnings * chore: update linter rules * fix: outofmemory and formatting in SnykToolWindowPanelIntegTest * fix: SnykToolWindowPanelIntegTest * fix: OssServiceTest * fix: detekt rule file --- .editorconfig | 2 +- .github/detekt/detekt-config.yml | 6 + CHANGELOG.md | 1 + .../ui/toolwindow/SnykTreeCellRenderer.kt | 104 +++++++---- src/main/kotlin/snyk/iac/IacIssuesForFile.kt | 5 + .../snyk/oss/OssVulnerabilitiesForFile.kt | 5 + .../SnykToolWindowPanelIntegTest.kt | 98 +++++------ src/test/kotlin/snyk/oss/OssServiceTest.kt | 164 ++++++++++-------- 8 files changed, 232 insertions(+), 153 deletions(-) diff --git a/.editorconfig b/.editorconfig index 335b029be..e76e0f324 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,13 +7,13 @@ indent_size = 2 indent_style = space insert_final_newline = true trim_trailing_whitespace = true +max_line_length = 120 [*.md] trim_trailing_whitespace = false [*.{kt,kts}] indent_size = 4 -wildcard_import_limit = 999 ij_kotlin_packages_to_use_import_on_demand = ^ ij_kotlin_name_count_to_use_star_import = 999 ij_kotlin_name_count_to_use_star_import_for_members = 999 diff --git a/.github/detekt/detekt-config.yml b/.github/detekt/detekt-config.yml index 306f63e59..3df734417 100644 --- a/.github/detekt/detekt-config.yml +++ b/.github/detekt/detekt-config.yml @@ -4,6 +4,12 @@ formatting: Indentation: continuationIndentSize: 8 + MaxLineLength: + active: false + ParameterListWrapping: + active: false + NoWildcardImports: + active: false complexity: # don't count private & deprecated diff --git a/CHANGELOG.md b/CHANGELOG.md index 12cbe1568..393442e3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - support for arm64 CLI on macOS - support to configure base URL for CLI downloads +- display up to 70 characters of the filepath in the tree view ## [2.5.3] diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykTreeCellRenderer.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykTreeCellRenderer.kt index a67910c49..4abc07b77 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykTreeCellRenderer.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykTreeCellRenderer.kt @@ -2,6 +2,7 @@ package io.snyk.plugin.ui.toolwindow import ai.deepcode.javaclient.core.SuggestionForFile import com.intellij.icons.AllIcons +import com.intellij.ide.util.gotoByName.GotoFileCellRenderer import com.intellij.openapi.util.Iconable import com.intellij.ui.ColoredTreeCellRenderer import com.intellij.ui.SimpleTextAttributes @@ -26,6 +27,7 @@ import io.snyk.plugin.ui.toolwindow.nodes.root.RootSecurityIssuesTreeNode import io.snyk.plugin.ui.toolwindow.nodes.secondlevel.ErrorTreeNode import io.snyk.plugin.ui.toolwindow.nodes.secondlevel.FileTreeNode import io.snyk.plugin.ui.toolwindow.nodes.secondlevel.SnykCodeFileTreeNode +import org.jetbrains.kotlin.idea.base.util.letIf import snyk.common.ProductType import snyk.common.SnykError import snyk.container.ContainerIssue @@ -38,21 +40,17 @@ import snyk.iac.ui.toolwindow.IacFileTreeNode import snyk.iac.ui.toolwindow.IacIssueTreeNode import snyk.oss.OssVulnerabilitiesForFile import snyk.oss.Vulnerability -import java.util.Locale +import java.util.* import javax.swing.Icon import javax.swing.JTree +private const val MAX_FILE_TREE_NODE_LENGTH = 60 + class SnykTreeCellRenderer : ColoredTreeCellRenderer() { + @Suppress("UNCHECKED_CAST") override fun customizeCellRenderer( - tree: JTree, - value: Any, - selected: Boolean, - expanded: Boolean, - leaf: Boolean, - row: Int, - hasFocus: Boolean + tree: JTree, value: Any, selected: Boolean, expanded: Boolean, leaf: Boolean, row: Int, hasFocus: Boolean ) { - var nodeIcon: Icon? = null var text: String? = null var attributes = SimpleTextAttributes.REGULAR_ATTRIBUTES @@ -68,18 +66,32 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() { nodeIcon = getDisabledIcon(nodeIcon) } } + is FileTreeNode -> { val fileVulns = value.userObject as OssVulnerabilitiesForFile nodeIcon = PackageManagerIconProvider.getIcon(fileVulns.packageManager.lowercase(Locale.getDefault())) - text = fileVulns.sanitizedTargetFile + ProductType.OSS.getCountText(value.childCount) + val relativePath = fileVulns.virtualFile?.let { + GotoFileCellRenderer.getRelativePath( + fileVulns.virtualFile, value.project + ) + } ?: "" + toolTipText = + relativePath + fileVulns.sanitizedTargetFile + ProductType.OSS.getCountText(value.childCount) + + text = toolTipText.letIf(toolTipText.length > MAX_FILE_TREE_NODE_LENGTH) { + "..." + it.substring( + it.length - MAX_FILE_TREE_NODE_LENGTH, it.length + ) + } val snykCachedResults = getSnykCachedResults(value.project) if (snykCachedResults?.currentOssResults == null) { attributes = SimpleTextAttributes.GRAYED_ATTRIBUTES - text += obsoleteSuffix + text += OBSOLETE_SUFFIX nodeIcon = getDisabledIcon(nodeIcon) } } + is SuggestionTreeNode -> { val (suggestion, index) = value.userObject as Pair nodeIcon = SnykIcons.getSeverityIcon(suggestion.getSeverityAsEnum()) @@ -94,31 +106,55 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() { nodeIcon = getDisabledIcon(nodeIcon) } } + is SnykCodeFileTreeNode -> { val (file, productType) = value.userObject as Pair - text = PDU.toSnykCodeFile(file).virtualFile.name + productType.getCountText(value.childCount) + toolTipText = + GotoFileCellRenderer.getRelativePath(file.virtualFile, file.project) + productType.getCountText( + value.childCount + ) + + text = toolTipText.letIf(toolTipText.length > MAX_FILE_TREE_NODE_LENGTH) { + "..." + it.substring( + it.length - MAX_FILE_TREE_NODE_LENGTH, it.length + ) + } + val psiFile = PDU.toPsiFile(file) nodeIcon = psiFile?.getIcon(Iconable.ICON_FLAG_READ_STATUS) if (!AnalysisData.instance.isFileInCache(file)) { attributes = SimpleTextAttributes.GRAYED_ATTRIBUTES - text += obsoleteSuffix + text += OBSOLETE_SUFFIX nodeIcon = getDisabledIcon(nodeIcon) } } + is IacFileTreeNode -> { val iacVulnerabilitiesForFile = value.userObject as IacIssuesForFile nodeIcon = PackageManagerIconProvider.getIcon( iacVulnerabilitiesForFile.packageManager.lowercase(Locale.getDefault()) ) - text = iacVulnerabilitiesForFile.targetFile + ProductType.IAC.getCountText(value.childCount) + val relativePath = iacVulnerabilitiesForFile.virtualFile?.let { + GotoFileCellRenderer.getRelativePath( + iacVulnerabilitiesForFile.virtualFile, value.project + ) + } ?: iacVulnerabilitiesForFile.targetFilePath + toolTipText = relativePath + ProductType.IAC.getCountText(value.childCount) + + text = toolTipText.letIf(toolTipText.length > MAX_FILE_TREE_NODE_LENGTH) { + "..." + it.substring( + it.length - MAX_FILE_TREE_NODE_LENGTH, it.length + ) + } val snykCachedResults = getSnykCachedResults(value.project) if (snykCachedResults?.currentIacResult == null || iacVulnerabilitiesForFile.obsolete) { attributes = SimpleTextAttributes.GRAYED_ATTRIBUTES nodeIcon = getDisabledIcon(nodeIcon) - text += obsoleteSuffix + text += OBSOLETE_SUFFIX } } + is ContainerImageTreeNode -> { val issuesForImage = value.userObject as ContainerIssuesForImage nodeIcon = SnykIcons.CONTAINER_IMAGE @@ -128,22 +164,24 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() { if (snykCachedResults?.currentContainerResult == null || issuesForImage.obsolete) { attributes = SimpleTextAttributes.GRAYED_ATTRIBUTES nodeIcon = getDisabledIcon(nodeIcon) - text += obsoleteSuffix + text += OBSOLETE_SUFFIX } } + is ErrorTreeNode -> { val snykError = value.userObject as SnykError text = snykError.path + " - " + snykError.message nodeIcon = AllIcons.General.Error } + is IacIssueTreeNode -> { val issue = (value.userObject as IacIssue) val snykCachedResults = getSnykCachedResults(value.project) nodeIcon = SnykIcons.getSeverityIcon(issue.getSeverity()) val prefix = if (issue.lineNumber > 0) "line ${issue.lineNumber}: " else "" text = prefix + issue.title + when { - issue.ignored -> ignoredSuffix - snykCachedResults?.currentIacResult == null || issue.obsolete -> obsoleteSuffix + issue.ignored -> IGNORED_SUFFIX + snykCachedResults?.currentIacResult == null || issue.obsolete -> OBSOLETE_SUFFIX else -> "" } if (snykCachedResults?.currentIacResult == null || issue.ignored || issue.obsolete) { @@ -151,13 +189,14 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() { nodeIcon = getDisabledIcon(nodeIcon) } } + is ContainerIssueTreeNode -> { val issue = value.userObject as ContainerIssue val snykCachedResults = getSnykCachedResults(value.project) nodeIcon = SnykIcons.getSeverityIcon(issue.getSeverity()) text = issue.title + when { - issue.ignored -> ignoredSuffix - snykCachedResults?.currentContainerResult == null || issue.obsolete -> obsoleteSuffix + issue.ignored -> IGNORED_SUFFIX + snykCachedResults?.currentContainerResult == null || issue.obsolete -> OBSOLETE_SUFFIX else -> "" } if (snykCachedResults?.currentContainerResult == null || issue.ignored || issue.obsolete) { @@ -165,6 +204,7 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() { nodeIcon = getDisabledIcon(nodeIcon) } } + is RootOssTreeNode -> { val settings = pluginSettings() if (settings.ossScanEnable && settings.treeFiltering.ossResults) { @@ -177,9 +217,10 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() { text = if (settings.ossScanEnable) { value.userObject.toString() } else { - SnykToolWindowPanel.OSS_ROOT_TEXT + disabledSuffix + SnykToolWindowPanel.OSS_ROOT_TEXT + DISABLED_SUFFIX } } + is RootSecurityIssuesTreeNode -> { val settings = pluginSettings() if (settings.snykCodeSecurityIssuesScanEnable && settings.treeFiltering.codeSecurityResults) { @@ -192,10 +233,11 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() { text = if (settings.snykCodeSecurityIssuesScanEnable) { value.userObject.toString() } else { - SnykToolWindowPanel.CODE_SECURITY_ROOT_TEXT + - snykCodeAvailabilityPostfix().ifEmpty { disabledSuffix } + SnykToolWindowPanel.CODE_SECURITY_ROOT_TEXT + snykCodeAvailabilityPostfix() + .ifEmpty { DISABLED_SUFFIX } } } + is RootQualityIssuesTreeNode -> { val settings = pluginSettings() if (settings.snykCodeQualityIssuesScanEnable && settings.treeFiltering.codeQualityResults) { @@ -208,10 +250,11 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() { text = if (settings.snykCodeQualityIssuesScanEnable) { value.userObject.toString() } else { - SnykToolWindowPanel.CODE_QUALITY_ROOT_TEXT + - snykCodeAvailabilityPostfix().ifEmpty { disabledSuffix } + SnykToolWindowPanel.CODE_QUALITY_ROOT_TEXT + snykCodeAvailabilityPostfix() + .ifEmpty { DISABLED_SUFFIX } } } + is RootIacIssuesTreeNode -> { val settings = pluginSettings() if (settings.iacScanEnabled && settings.treeFiltering.iacResults) { @@ -224,9 +267,10 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() { text = if (settings.iacScanEnabled) { value.userObject.toString() } else { - SnykToolWindowPanel.IAC_ROOT_TEXT + disabledSuffix + SnykToolWindowPanel.IAC_ROOT_TEXT + DISABLED_SUFFIX } } + is RootContainerIssuesTreeNode -> { val settings = pluginSettings() if (settings.containerScanEnabled && settings.treeFiltering.containerResults) { @@ -239,7 +283,7 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() { text = if (settings.containerScanEnabled) { value.userObject.toString() } else { - SnykToolWindowPanel.CONTAINER_ROOT_TEXT + disabledSuffix + SnykToolWindowPanel.CONTAINER_ROOT_TEXT + DISABLED_SUFFIX } } } @@ -249,8 +293,8 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() { } companion object { - private const val obsoleteSuffix = " (obsolete)" - private const val ignoredSuffix = " (ignored)" - private const val disabledSuffix = " (disabled in Settings)" + private const val OBSOLETE_SUFFIX = " (obsolete)" + private const val IGNORED_SUFFIX = " (ignored)" + private const val DISABLED_SUFFIX = " (disabled in Settings)" } } diff --git a/src/main/kotlin/snyk/iac/IacIssuesForFile.kt b/src/main/kotlin/snyk/iac/IacIssuesForFile.kt index e2d7bc1d9..a0cb7b370 100644 --- a/src/main/kotlin/snyk/iac/IacIssuesForFile.kt +++ b/src/main/kotlin/snyk/iac/IacIssuesForFile.kt @@ -1,5 +1,8 @@ package snyk.iac +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VirtualFile + data class IacIssuesForFile( val infrastructureAsCodeIssues: List, val targetFile: String, @@ -9,6 +12,8 @@ data class IacIssuesForFile( val obsolete: Boolean get() = infrastructureAsCodeIssues.any { it.obsolete } val ignored: Boolean get() = infrastructureAsCodeIssues.all { it.ignored } val uniqueCount: Int get() = infrastructureAsCodeIssues.groupBy { it.id }.size + + val virtualFile: VirtualFile? = LocalFileSystem.getInstance().findFileByPath(this.targetFilePath) } /* Real json Example: src/integTest/resources/iac-test-results/infrastructure-as-code-goof.json */ diff --git a/src/main/kotlin/snyk/oss/OssVulnerabilitiesForFile.kt b/src/main/kotlin/snyk/oss/OssVulnerabilitiesForFile.kt index b86d47af3..e616bcc19 100644 --- a/src/main/kotlin/snyk/oss/OssVulnerabilitiesForFile.kt +++ b/src/main/kotlin/snyk/oss/OssVulnerabilitiesForFile.kt @@ -1,5 +1,8 @@ package snyk.oss +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VirtualFile + data class OssVulnerabilitiesForFile( val vulnerabilities: List, private val displayTargetFile: String, @@ -11,6 +14,8 @@ data class OssVulnerabilitiesForFile( val sanitizedTargetFile: String get() = displayTargetFile.replace("-lock", "") + val virtualFile: VirtualFile? = LocalFileSystem.getInstance().findFileByPath(this.path) + fun toGroupedResult(): OssGroupedResult { val id2vulnerabilities = vulnerabilities.groupBy({ it.id }, { it }) val uniqueCount = id2vulnerabilities.keys.size diff --git a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt index a759928d0..9ab72bb37 100644 --- a/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt @@ -1,6 +1,7 @@ package io.snyk.plugin.ui.toolwindow import ai.deepcode.javaclient.core.SuggestionForFile +import com.intellij.ide.util.gotoByName.GotoFileCellRenderer import com.intellij.mock.MockVirtualFile import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.components.service @@ -73,7 +74,6 @@ import javax.swing.JPanel import javax.swing.JTextArea import javax.swing.tree.TreeNode -@Suppress("FunctionName") class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { private val iacGoofJson = getResourceAsString("iac-test-results/infrastructure-as-code-goof.json") @@ -102,6 +102,8 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { toolWindowPanel = project.service() setupDummyCliFile() every { confirmScanningAndSetWorkspaceTrustedStateIfNeeded(any(), any()) } returns true + mockkStatic(GotoFileCellRenderer::class) + every { GotoFileCellRenderer.getRelativePath(any(), any()) } returns "abc/" } override fun tearDown() { @@ -183,19 +185,32 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { } private val fakeSuggestionForFile = SuggestionForFile( - /* id = */ "id", - /* rule = */ "rule", - /* message = */ "message", - /* title = */ "title", - /* text = */ "text", - /* severity = */ 2, - /* repoDatasetSize = */ 0, - /* exampleCommitDescriptions = */ emptyList(), - /* exampleCommitFixes = */ emptyList(), - /* ranges = */ listOf(mockk(relaxed = true)), - /* categories = */ emptyList(), - /* tags = */ emptyList(), - /* cwe = */ emptyList() + /* id = */ + "id", + /* rule = */ + "rule", + /* message = */ + "message", + /* title = */ + "title", + /* text = */ + "text", + /* severity = */ + 2, + /* repoDatasetSize = */ + 0, + /* exampleCommitDescriptions = */ + emptyList(), + /* exampleCommitFixes = */ + emptyList(), + /* ranges = */ + listOf(mockk(relaxed = true)), + /* categories = */ + emptyList(), + /* tags = */ + emptyList(), + /* cwe = */ + emptyList() ) private val fakeContainerIssue1 = ContainerIssue( id = "fakeId1", @@ -245,7 +260,8 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { uniqueCount = 1, error = null, imageName = "fake-image-name1" - )), + ) + ), listOf( SnykError( message = "fake error message", @@ -254,7 +270,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { ) ) - @Test fun `test when no OSS supported file found should display special text (not error) in node and description`() { mockkObject(SnykBalloonNotificationHelper) @@ -295,7 +310,8 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { fun `test when no IAC supported file found should display special text (not error) in node and description`() { mockkObject(SnykBalloonNotificationHelper) - val snykError = SnykError(SnykToolWindowPanel.NO_IAC_FILES, project.basePath.toString(), IacError.NO_IAC_FILES_CODE) + val snykError = + SnykError(SnykToolWindowPanel.NO_IAC_FILES, project.basePath.toString(), IacError.NO_IAC_FILES_CODE) val snykErrorControl = SnykError("control", project.basePath.toString()) scanPublisher.scanningIacError(snykErrorControl) @@ -339,16 +355,20 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { ) } - @Test fun `test should ignore IaC failures in IaC scan results (issues found)`() { mockkObject(SnykBalloonNotificationHelper) val iacIssue = IacIssue( id = "SNYK-CC-TF-74", title = "Credentials are configured via provider attributes", lineNumber = 1, - severity = "", publicId = "", documentation = "", issue = "", impact = "" - ) - val iacIssuesForFile = IacIssuesForFile(listOf(iacIssue), "k8s-deployment.yaml", "src/k8s-deployment.yaml", "npm") + severity = "", + publicId = "", + documentation = "", + issue = "", + impact = "" + ) + val iacIssuesForFile = + IacIssuesForFile(listOf(iacIssue), "k8s-deployment.yaml", "src/k8s-deployment.yaml", "npm") val jsonError = SnykError("Failed to parse JSON file", project.basePath.toString(), 1021) val iacResult = IacResult(listOf(iacIssuesForFile), listOf(jsonError)) scanPublisher.scanningIacFinished(iacResult) @@ -359,7 +379,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { ) } - @Test fun `test should display NO_CONTAINER_IMAGES_FOUND after scan when no Container images found`() { mockkObject(SnykBalloonNotificationHelper) @@ -381,7 +400,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { ) } - @Test fun `test should display CONTAINER_NO_IMAGES_FOUND_TEXT after scan when no Container images found and Container node selected`() { val snykError = ContainerService.NO_IMAGES_TO_SCAN_ERROR @@ -393,11 +411,11 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { val noImagesFoundPane = UIComponentFinder.getComponentByName( toolWindowPanel.getDescriptionPanel(), JEditorPane::class, - SnykToolWindowPanel.CONTAINER_NO_IMAGES_FOUND_TEXT) + SnykToolWindowPanel.CONTAINER_NO_IMAGES_FOUND_TEXT + ) assertNotNull(noImagesFoundPane) } - @Test fun `test should display CONTAINER_NO_ISSUES_FOUND_TEXT after scan when no Container issues found and Container node selected`() { scanPublisher.scanningContainerFinished(ContainerResult(emptyList())) PlatformTestUtil.dispatchAllEventsInIdeEventQueue() @@ -407,11 +425,11 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { val noIssuesFoundPane = UIComponentFinder.getComponentByName( toolWindowPanel.getDescriptionPanel(), JEditorPane::class, - SnykToolWindowPanel.CONTAINER_NO_ISSUES_FOUND_TEXT) + SnykToolWindowPanel.CONTAINER_NO_ISSUES_FOUND_TEXT + ) assertNotNull(noIssuesFoundPane) } - @Test fun `test should display CONTAINER_SCAN_START_TEXT before any scan performed and Container node selected`() { TreeUtil.selectNode(toolWindowPanel.getTree(), toolWindowPanel.getRootContainerIssuesTreeNode()) PlatformTestUtil.waitWhileBusy(toolWindowPanel.getTree()) @@ -419,11 +437,11 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { val startContainerScanPane = UIComponentFinder.getComponentByName( toolWindowPanel.getDescriptionPanel(), JEditorPane::class, - SnykToolWindowPanel.CONTAINER_SCAN_START_TEXT) + SnykToolWindowPanel.CONTAINER_SCAN_START_TEXT + ) assertNotNull(startContainerScanPane) } - @Test fun `test should display CONTAINER_SCAN_RUNNING_TEXT before any scan performed and Container node selected`() { TreeUtil.selectNode(toolWindowPanel.getTree(), toolWindowPanel.getRootContainerIssuesTreeNode()) PlatformTestUtil.waitWhileBusy(toolWindowPanel.getTree()) @@ -433,11 +451,11 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { val containerScanRunningPane = UIComponentFinder.getComponentByName( toolWindowPanel.getDescriptionPanel(), JEditorPane::class, - SnykToolWindowPanel.CONTAINER_SCAN_RUNNING_TEXT) + SnykToolWindowPanel.CONTAINER_SCAN_RUNNING_TEXT + ) assertNotNull(containerScanRunningPane) } - @Test fun `test OSS scan should redirect to Auth panel if token is invalid`() { mockkObject(SnykBalloonNotificationHelper) val snykErrorControl = SnykError("control", project.basePath.toString()) @@ -462,7 +480,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { assertNotNull(authPanel) } - @Test fun `test Container scan should redirect to Auth panel if token is invalid`() { mockkObject(SnykBalloonNotificationHelper) val snykErrorControl = SnykError("control", project.basePath.toString()) @@ -487,7 +504,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { assertNotNull(authPanel) } - @Test fun `test Container node should show no child when disabled`() { mockkObject(SnykBalloonNotificationHelper) val rootContainerNode = toolWindowPanel.getRootContainerIssuesTreeNode() @@ -507,7 +523,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { assertTrue(rootContainerNode.childCount == 0) } - @Test fun `test should display '(error)' in OSS root tree node when result is empty and error occurs`() { val snykError = SnykError("an error", project.basePath.toString()) scanPublisher.scanningOssError(snykError) @@ -521,7 +536,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { ) } - @Test fun `test should display 'scanning' in OSS root tree node when it is scanning`() { mockkStatic("io.snyk.plugin.UtilsKt") every { isOssRunning(project) } returns true @@ -535,7 +549,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { ) } - @Test fun testSeverityFilterForIacResult() { // pre-test setup prepareTreeWithFakeIacResults() @@ -562,7 +575,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { assertFalse("Medium severity IaC results should NOT be shown after filtering", isMediumSeverityShown()) } - @Test fun testIacErrorShown() { // pre-test setup setUpIacTest() @@ -597,7 +609,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { assertTrue(pathTextArea?.text == iacError.path) } - @Test fun test_WhenIacIssueIgnored_ThenItMarkedIgnored_AndButtonRemainsDisabled() { // pre-test setup prepareTreeWithFakeIacResults() @@ -652,7 +663,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { ) } - @Test fun `test all root nodes are shown`() { setUpIacTest() setUpContainerTest(null) @@ -675,7 +685,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { } } - @Test fun `test container error shown`() { // mock Container results val containerError = SnykError("fake error", "fake path") @@ -706,7 +715,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { assertTrue(pathTextArea?.text == containerError.path) } - @Test fun `test container image nodes with description shown`() { // pre-test setup setUpContainerTest(fakeContainerResult) @@ -740,7 +748,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { assertNotNull(baseImageRemediationDetailPanel) } - @Test fun `test container image node and Failed-to-scan image shown`() { // pre-test setup setUpContainerTest(fakeContainerResultWithError) @@ -774,7 +781,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { } } - @Test fun `test container issue nodes with description shown`() { // pre-test setup setUpContainerTest(fakeContainerResult) @@ -804,7 +810,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { assertNotNull("ContainerIssueDetailPanel should be shown on issue node selection", containerIssueDetailPanel) } - @Test fun `test container image nodes with remediation description shown`() { prepareContainerTreeNodesAndCaches(containerResultWithRemediationJson) @@ -869,7 +874,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { return containerResult } - @Test fun `test container image node has correct amount of leaf(issue) nodes`() { val containerResult = prepareContainerTreeNodesAndCaches(containerResultWithRemediationJson) @@ -887,7 +891,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { PlatformTestUtil.waitWhileBusy(tree) } - @Test fun `test IaC node selected and Description shown on external request`() { // pre-test setup prepareTreeWithFakeIacResults() @@ -915,7 +918,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { assertNotNull("IacSuggestionDescriptionPanel should not be null", iacDescriptionPanel) } - @Test fun `test OSS node selected and Description shown on external request`() { prepareTreeWithFakeOssResults() @@ -941,7 +943,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { assertNotNull("VulnerabilityDescriptionPanel should not be null", vulnerabilityDescriptionPanel) } - @Test fun `test Code node selected and Description shown on external request`() { prepareTreeWithFakeCodeResults() @@ -968,7 +969,6 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { assertNotNull("SuggestionDescriptionPanel should not be null", suggestionDescriptionPanel) } - @Test fun `test Container node selected and Description shown on external request`() { // prepare Tree with fake Container results setUpContainerTest(null) diff --git a/src/test/kotlin/snyk/oss/OssServiceTest.kt b/src/test/kotlin/snyk/oss/OssServiceTest.kt index b71b08635..4d484f6d1 100644 --- a/src/test/kotlin/snyk/oss/OssServiceTest.kt +++ b/src/test/kotlin/snyk/oss/OssServiceTest.kt @@ -1,5 +1,6 @@ package snyk.oss +import com.intellij.ide.util.gotoByName.GotoFileCellRenderer import com.intellij.openapi.components.service import com.intellij.testFramework.LightPlatformTestCase import io.mockk.every @@ -16,7 +17,6 @@ import io.snyk.plugin.removeDummyCliFile import io.snyk.plugin.resetSettings import io.snyk.plugin.services.SnykProjectSettingsStateService import io.snyk.plugin.setupDummyCliFile -import org.junit.Test import snyk.PluginInformation import snyk.errorHandler.SentryErrorReporter import snyk.oss.OssService.Companion.ALL_PROJECTS_PARAM @@ -41,6 +41,8 @@ class OssServiceTest : LightPlatformTestCase() { settingsStateService.organization = "" project.service().additionalParameters = "" + mockkStatic(GotoFileCellRenderer::class) + every { GotoFileCellRenderer.getRelativePath(any(), any()) } returns "abc/" } override fun tearDown() { @@ -53,7 +55,6 @@ class OssServiceTest : LightPlatformTestCase() { private val ossService: OssService get() = getOssService(project) ?: throw IllegalStateException("OSS service should be available") - @Test fun testBuildCliCommandsListWithDefaults() { setupDummyCliFile() @@ -64,7 +65,6 @@ class OssServiceTest : LightPlatformTestCase() { assertTrue(cliCommands.contains("--json")) } - @Test fun testBuildCliCommandsListWithFileParameter() { setupDummyCliFile() @@ -75,7 +75,6 @@ class OssServiceTest : LightPlatformTestCase() { assertTrue(cliCommands.contains("--file=package.json")) } - @Test fun testBuildCliCommandsListWithMultiAdditionalParameters() { setupDummyCliFile() @@ -89,7 +88,6 @@ class OssServiceTest : LightPlatformTestCase() { assertTrue(cliCommands.contains("--sub-project=snyk")) } - @Test fun testBuildCliCommandsListWithRiderIde() { setupDummyCliFile() val pluginInformation = PluginInformation( @@ -106,7 +104,6 @@ class OssServiceTest : LightPlatformTestCase() { assertTrue(cliCommands.contains("--all-projects")) } - @Test fun testBuildCliCommandsListDoNotAddAllProjectsTwice() { setupDummyCliFile() val pluginInformation = PluginInformation( @@ -120,13 +117,12 @@ class OssServiceTest : LightPlatformTestCase() { project.service().additionalParameters = "--file=package.json --configuration-matching='iamaRegex' --all-projects" - val countAllProjectsParams = ossService.buildCliCommandsList_TEST_ONLY(listOf("fake_cli_command")) - .count{ i -> i == ALL_PROJECTS_PARAM } + val countAllProjectsParams = + ossService.buildCliCommandsList_TEST_ONLY(listOf("fake_cli_command")).count { i -> i == ALL_PROJECTS_PARAM } assertTrue(countAllProjectsParams == 1) } - @Test fun testGroupVulnerabilities() { val cli = ossService @@ -138,7 +134,6 @@ class OssServiceTest : LightPlatformTestCase() { assertEquals(36, cliGroupedResult.pathsCount) } - @Test fun testGroupVulnerabilitiesForGoof() { val cli = ossService @@ -150,14 +145,15 @@ class OssServiceTest : LightPlatformTestCase() { assertEquals(310, cliGroupedResult.pathsCount) } - @Test fun testScanWithErrorResult() { setupDummyCliFile() val mockRunner = mockk() every { - mockRunner.execute(listOf(getCliFile().absolutePath, "test", "--json"), project.basePath!!, project = project) + mockRunner.execute( + listOf(getCliFile().absolutePath, "test", "--json"), project.basePath!!, project = project + ) } returns """ { "ok": false, @@ -173,18 +169,20 @@ class OssServiceTest : LightPlatformTestCase() { assertFalse(cliResult.isSuccessful()) assertEquals( "Missing node_modules folder: we can't test without dependencies.\nPlease run 'npm install' first.", - cliResult.getFirstError()!!.message) + cliResult.getFirstError()!!.message + ) assertEquals("/Users/user/Desktop/example-npm-project", cliResult.getFirstError()!!.path) } - @Test fun testScanWithSuccessfulCliResult() { setupDummyCliFile() val mockRunner = mockk() every { - mockRunner.execute(listOf(getCliFile().absolutePath, "test", "--json"), project.basePath!!, project = project) + mockRunner.execute( + listOf(getCliFile().absolutePath, "test", "--json"), project.basePath!!, project = project + ) } returns getResourceAsString("group-vulnerabilities-test.json") ossService.setConsoleCommandRunner(mockRunner) @@ -202,14 +200,15 @@ class OssServiceTest : LightPlatformTestCase() { assertTrue(vulnerabilityIds.contains("npm:qs:20140806-1")) } - @Test fun testScanWithLicenseVulnerabilities() { setupDummyCliFile() val mockRunner = mockk() every { - mockRunner.execute(listOf(getCliFile().absolutePath, "test", "--json"), project.basePath!!, project = project) + mockRunner.execute( + listOf(getCliFile().absolutePath, "test", "--json"), project.basePath!!, project = project + ) } returns getResourceAsString("licence-vulnerabilities.json") ossService.setConsoleCommandRunner(mockRunner) @@ -224,110 +223,123 @@ class OssServiceTest : LightPlatformTestCase() { assertTrue(vulnerabilityIds.contains("snyk:lic:pip:six:MIT")) } - @Test fun testConvertRawCliStringToCliResult() { - val sigleObjectCliResult = ossService - .convertRawCliStringToCliResult(getResourceAsString("group-vulnerabilities-test.json")) - assertTrue(sigleObjectCliResult.isSuccessful()) + val singleObjectCliResult = + ossService.convertRawCliStringToCliResult(getResourceAsString("group-vulnerabilities-test.json")) + assertTrue(singleObjectCliResult.isSuccessful()) - val arrayObjectCliResult = ossService - .convertRawCliStringToCliResult(getResourceAsString("vulnerabilities-array-cli-result.json")) + val arrayObjectCliResult = + ossService.convertRawCliStringToCliResult(getResourceAsString("vulnerabilities-array-cli-result.json")) assertTrue(arrayObjectCliResult.isSuccessful()) - val jsonErrorCliResult = ossService.convertRawCliStringToCliResult(""" + val jsonErrorCliResult = ossService.convertRawCliStringToCliResult( + """ { "ok": false, "error": "Missing node_modules folder: we can't test without dependencies.\nPlease run 'npm install' first.", "path": "/Users/user/Desktop/example-npm-project" } - """.trimIndent()) + """.trimIndent() + ) assertFalse(jsonErrorCliResult.isSuccessful()) - assertEquals("Missing node_modules folder: we can't test without dependencies.\nPlease run 'npm install' first.", - jsonErrorCliResult.getFirstError()!!.message) + assertEquals( + "Missing node_modules folder: we can't test without dependencies.\nPlease run 'npm install' first.", + jsonErrorCliResult.getFirstError()!!.message + ) assertEquals("/Users/user/Desktop/example-npm-project", jsonErrorCliResult.getFirstError()!!.path) - val rawErrorCliResult = ossService.convertRawCliStringToCliResult(""" + val rawErrorCliResult = ossService.convertRawCliStringToCliResult( + """ Missing node_modules folder: we can't test without dependencies. Please run 'npm install' first. - """.trimIndent()) + """.trimIndent() + ) assertFalse(rawErrorCliResult.isSuccessful()) - assertEquals("Missing node_modules folder: we can't test without dependencies. Please run 'npm install' first.", - rawErrorCliResult.getFirstError()!!.message) + assertEquals( + "Missing node_modules folder: we can't test without dependencies. Please run 'npm install' first.", + rawErrorCliResult.getFirstError()!!.message + ) assertEquals(project.basePath, rawErrorCliResult.getFirstError()!!.path) } - @Test fun testConvertRawCliStringWithLicenseVulnsToCliResult() { val rawMissedFixedInFieldCliString = getResourceAsString("licence-vulnerabilities.json") val cliResult = ossService.convertRawCliStringToCliResult(rawMissedFixedInFieldCliString) assertTrue(cliResult.isSuccessful()) assertNotNull(cliResult.allCliIssues?.find { it -> - it.vulnerabilities - .any { vulnerability -> vulnerability.fixedIn == null } + it.vulnerabilities.any { vulnerability -> vulnerability.fixedIn == null } }) touchAllFields(cliResult) } - @Test fun testConvertRawCliStringToCliResultWithEmptyRawString() { val cliResult = ossService.convertRawCliStringToCliResult("") assertFalse(cliResult.isSuccessful()) } - @Test fun testConvertMisformedErrorAsArrayJson() { - val cliResult = ossService.convertRawCliStringToCliResult(""" + val cliResult = ossService.convertRawCliStringToCliResult( + """ { "ok": false, "error": ["could not be","array here"], "path": "some/path/here" } - """.trimIndent()) + """.trimIndent() + ) assertFalse(cliResult.isSuccessful()) - assertTrue(cliResult.getFirstError()!!.message.contains( - "Expected a string but was BEGIN_ARRAY" - )) + assertTrue( + cliResult.getFirstError()!!.message.contains( + "Expected a string but was BEGIN_ARRAY" + ) + ) } - @Test fun testConvertMisformedErrorPathTagJson() { - val cliResult2 = ossService.convertRawCliStringToCliResult(""" + val cliResult2 = ossService.convertRawCliStringToCliResult( + """ { "ok": false, "error": "error", "path_not_provided": "" } - """.trimIndent()) + """.trimIndent() + ) assertFalse(cliResult2.isSuccessful()) - assertTrue(cliResult2.getFirstError()!!.message.contains( - "Parameter specified as non-null is null: method snyk.common.SnykError., parameter path" - )) + assertTrue( + cliResult2.getFirstError()!!.message.contains( + "Parameter specified as non-null is null: method snyk.common.SnykError., parameter path" + ) + ) } - @Test fun testConvertMisformedResultArrayJson() { - val cliResult1 = ossService.convertRawCliStringToCliResult(""" + val cliResult1 = ossService.convertRawCliStringToCliResult( + """ { "vulnerabilities": "SHOULD_BE_ARRAY_HERE", "packageManager": "npm", "displayTargetFile": "package-lock.json", "path": "D:\\TestProjects\\goof" } - """.trimIndent()) + """.trimIndent() + ) assertFalse(cliResult1.isSuccessful()) - assertTrue(cliResult1.getFirstError()!!.message.contains( - "Expected BEGIN_ARRAY but was STRING" - )) + assertTrue( + cliResult1.getFirstError()!!.message.contains( + "Expected BEGIN_ARRAY but was STRING" + ) + ) } - @Test fun testConvertMisformedResultNestedJson() { - val cliResult2 = ossService.convertRawCliStringToCliResult(""" + val cliResult2 = ossService.convertRawCliStringToCliResult( + """ { "vulnerabilities": [ { @@ -338,27 +350,30 @@ class OssServiceTest : LightPlatformTestCase() { "displayTargetFile": "package-lock.json", "path": "D:\\TestProjects\\goof" } - """.trimIndent()) + """.trimIndent() + ) assertFalse(cliResult2.isSuccessful()) - assertTrue(cliResult2.getFirstError()!!.message.contains( - "Parameter specified as non-null is null: method snyk.oss.Vulnerability.copy, parameter id" - )) + assertTrue( + cliResult2.getFirstError()!!.message.contains( + "Parameter specified as non-null is null: method snyk.oss.Vulnerability.copy, parameter id" + ) + ) } - @Test fun testConvertMisformedResultRootTagJson() { val rawCliString = getResourceAsString("misformed-vulnerabilities-test.json") val cliResult3 = ossService.convertRawCliStringToCliResult(rawCliString) assertFalse(cliResult3.isSuccessful()) - assertTrue(cliResult3.getFirstError()!!.message.contains( - "Parameter specified as non-null is null: method snyk.oss.OssVulnerabilitiesForFile.copy, parameter displayTargetFile" - )) + assertTrue( + cliResult3.getFirstError()!!.message.contains( + "Parameter specified as non-null is null: method snyk.oss.OssVulnerabilitiesForFile.copy, parameter displayTargetFile" + ) + ) } - @Test fun testConvertGoodAndMisformedResultJson() { mockkObject(SentryErrorReporter) val rawCliString = getResourceAsString("vulnerabilities-array-with-error-and-result-test.json") @@ -368,19 +383,22 @@ class OssServiceTest : LightPlatformTestCase() { assertTrue(cliResult3.isSuccessful()) assertTrue(cliResult3.allCliIssues?.size == 1) assertTrue(cliResult3.errors.size == 2) - assertTrue(cliResult3.errors[0].message.contains( - "Expected a string but was BEGIN_ARRAY" - )) - assertTrue(cliResult3.errors[1].message.contains( - "Parameter specified as non-null is null" - )) + assertTrue( + cliResult3.errors[0].message.contains( + "Expected a string but was BEGIN_ARRAY" + ) + ) + assertTrue( + cliResult3.errors[1].message.contains( + "Parameter specified as non-null is null" + ) + ) // only one error reported for all json array parsing exceptions verify(exactly = 1, timeout = 2000) { SentryErrorReporter.captureException(any()) } } - @Test fun testConvertRawCliStringToCliResultFieldsInitialisation() { val rawCliString = getResourceAsString("group-vulnerabilities-goof-test.json") val cliResult = ossService.convertRawCliStringToCliResult(rawCliString) @@ -419,8 +437,8 @@ class OssServiceTest : LightPlatformTestCase() { } } - private fun getResourceAsString(resourceName: String): String = javaClass.classLoader - .getResource(resourceName)!!.readText(Charsets.UTF_8) + private fun getResourceAsString(resourceName: String): String = + javaClass.classLoader.getResource(resourceName)!!.readText(Charsets.UTF_8) private fun mockPluginInformation(pluginInfoMock: PluginInformation) { mockkStatic("snyk.PluginInformationKt")