From 225266ab49cf7bf1f8515b1e59cc99d7ec766b9e Mon Sep 17 00:00:00 2001 From: Bastian Doetsch Date: Thu, 3 Aug 2023 07:59:28 +0200 Subject: [PATCH] chore: update IntelliJ compatibility and Icons chore: icons updated chore: update workflows chore: fix async test chore: fix tests (wip) chore: fix tests (wip) chore: fix tests (wip) chore: update icons chore: wip chore: upgrade & make the new config compilable --- .github/workflows/build.yml | 2 +- .github/workflows/detekt.yml | 2 +- .github/workflows/release.yml | 4 +- CHANGELOG.md | 6 + build.gradle.kts | 68 ++--- gradle.properties | 12 +- gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 269 +++++++++++------- gradlew.bat | 34 +-- settings.gradle.kts | 6 + .../kotlin/io/snyk/plugin/TestUtils.kt | 54 ---- src/main/kotlin/icons/SnykIcons.kt | 78 ++--- src/main/kotlin/io/snyk/plugin/Severity.kt | 2 +- .../io/snyk/plugin/SnykPostStartupActivity.kt | 6 +- src/main/kotlin/io/snyk/plugin/Utils.kt | 14 +- .../kotlin/io/snyk/plugin/cli/Platform.kt | 3 +- .../io/snyk/plugin/net/SnykApiClient.kt | 8 +- .../plugin/services/SnykAnalyticsService.kt | 16 +- .../plugin/services/SnykTaskQueueService.kt | 2 +- .../plugin/services/download/CliDownloader.kt | 5 +- .../SnykProjectSettingsConfigurable.kt | 12 +- .../snyk/plugin/snykcode/SnykCodeResults.kt | 12 +- .../ui/SnykBalloonNotificationHelper.kt | 10 +- .../plugin/ui/SnykBalloonNotifications.kt | 16 +- src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt | 5 +- .../snyk/plugin/ui/settings/ScanTypesPanel.kt | 4 +- .../plugin/ui/toolwindow/SnykToolWindow.kt | 3 +- .../ui/toolwindow/SnykTreeCellRenderer.kt | 6 +- .../ui/toolwindow/panels/SnykAuthPanel.kt | 15 +- .../panels/SuggestionDescriptionPanel.kt | 9 +- .../plugin/ui/toolwindow/panels/TreePanel.kt | 4 +- src/main/kotlin/snyk/WelcomeNotifyActivity.kt | 5 +- .../snyk/advisor/api/AdvisorApiClient.kt | 13 +- .../api/AmplitudeExperimentApiClient.kt | 3 +- .../kotlin/snyk/container/ContainerResult.kt | 4 +- .../kotlin/snyk/container/ContainerService.kt | 2 +- .../snyk/container/KubernetesImageCache.kt | 34 ++- .../annotator/ContainerYamlAnnotator.kt | 2 +- .../ui/BaseImageRemediationDetailPanel.kt | 2 +- .../snyk/errorHandler/SentryErrorReporter.kt | 2 +- .../errorHandler/SnykErrorReportSubmitter.kt | 2 +- src/main/kotlin/snyk/iac/IacResult.kt | 4 +- .../snyk/iac/annotator/IacHclAnnotator.kt | 6 +- src/main/kotlin/snyk/oss/OssResult.kt | 4 +- .../snyk/oss/annotator/AnnotatorHelper.kt | 4 +- src/main/kotlin/snyk/trust/TrustedProjects.kt | 11 +- src/main/kotlin/snyk/whoami/WhoamiResult.kt | 2 +- src/main/resources/META-INF/plugin.xml | 6 +- src/main/resources/icons/code.svg | 21 +- src/main/resources/icons/code_dark.svg | 5 - src/main/resources/icons/code_disabled.svg | 23 +- .../resources/icons/code_disabled_dark.svg | 7 - src/main/resources/icons/container.svg | 21 +- src/main/resources/icons/container_dark.svg | 5 - .../resources/icons/container_disabled.svg | 23 +- .../icons/container_disabled_dark.svg | 7 - src/main/resources/icons/iac.svg | 21 +- src/main/resources/icons/iac_dark.svg | 6 - src/main/resources/icons/iac_disabled.svg | 24 +- .../resources/icons/iac_disabled_dark.svg | 8 - src/main/resources/icons/oss.svg | 21 +- src/main/resources/icons/oss_dark.svg | 5 - src/main/resources/icons/oss_disabled.svg | 23 +- .../resources/icons/oss_disabled_dark.svg | 7 - .../javaclient/core/AnalysisDataTest.java | 10 +- .../core/DeepCodeIgnoreInfoHolderTest.java | 6 +- src/test/kotlin/io/snyk/plugin/TestUtils.kt | 44 ++- .../plugin/cli/ConsoleCommandRunnerTest.kt | 24 +- .../core/IgnoreInfoHolderPlatformTestCase.kt | 26 +- .../kotlin/io/snyk/plugin}/core/PDUTest.kt | 4 +- .../io/snyk/plugin/net/SnykApiClientTest.kt | 6 +- .../io/snyk/plugin/services/CliAdapterTest.kt | 3 - .../services/SnykTaskQueueServiceHeavyTest.kt | 3 - .../services/SnykTaskQueueServiceTest.kt | 24 +- .../CliDownloaderErrorHandlerIntegTest.kt | 0 .../download/CliDownloaderServiceIntegTest.kt | 0 .../ReportFalsePositiveDialogIntegTest.kt | 2 +- .../ui/toolwindow/SnykAuthPanelIntegTest.kt | 4 +- .../SnykToolWindowPanelIntegTest.kt | 17 +- .../SuggestionDescriptionPanelIntegTest.kt | 2 +- .../toolwindow/settings/ScanTypesPanelTest.kt | 2 +- .../snyk/advisor/PackageNameProviderTest.kt | 0 .../snyk/advisor/SnykAdvisorModelTest.kt | 0 .../snyk/advisor/api/AdvisorApiClientTest.kt | 116 ++++---- .../code/annotator/SnykCodeAnnotatorTest.kt | 0 .../snyk/common/AnnotatorCommonIntegTest.kt | 0 ...AvailableReplacementIntentionActionTest.kt | 0 .../ContainerBulkFileListenerTest.kt | 54 ++-- .../container/ContainerServiceIntegTest.kt | 4 +- .../KubernetesImageCacheIntegTest.kt | 30 +- .../kotlin/snyk/container/TestYamls.kt | 0 .../annotator/BaseImageRemediationFixTest.kt | 0 .../annotator/ContainerYamlAnnotatorTest.kt | 0 .../snyk/iac/IacBulkFileListenerTest.kt | 0 .../kotlin/snyk/iac/IacServiceTest.kt | 0 .../iac/annotator/IacBaseAnnotatorCase.kt | 0 .../snyk/iac/annotator/IacHclAnnotatorTest.kt | 0 .../iac/annotator/IacJsonAnnotatorTest.kt | 0 .../iac/annotator/IacYamlAnnotatorTest.kt | 0 .../snyk/oss/OssBulkFileListenerTest.kt | 0 .../kotlin/snyk/oss/OssServiceTest.kt | 0 .../oss/annotator/OSSGoModAnnotatorTest.kt | 10 - .../oss/annotator/OSSGradleAnnotatorTest.kt | 1 - .../oss/annotator/OSSMavenAnnotatorTest.kt | 1 - .../snyk/oss/annotator/OSSNpmAnnotatorTest.kt | 0 .../WorkspaceTrustServiceIntegrationTest.kt} | 2 +- .../resources/AnnotatorTest.java | 0 .../advisor-build-files/package.json | 0 .../advisor-build-files/requirements.txt | 0 .../container-double-jenkins-with-path.json | 0 .../debian-nginx-fake_critical_only.json | 0 .../nginx-no-remediation.json | 0 .../nginx-with-remediation.json | 0 .../group-vulnerabilities-goof-test.json | 0 .../resources/group-vulnerabilities-test.json | 0 .../resources/iac-test-results/fargate.json | 0 .../infrastructure-as-code-goof.json | 0 .../resources/licence-vulnerabilities.json | 0 .../misformed-vulnerabilities-test.json | 0 .../org.mockito.plugins.MockMaker | 0 .../oss-test-results/oss-result-go-mod.json | 0 .../oss-result-gradle-kts.json | 0 .../oss-test-results/oss-result-gradle.json | 0 .../oss-test-results/oss-result-maven.json | 0 .../oss-result-package-no-remediation.json | 0 .../oss-test-results/oss-result-package.json | 0 .../test-fixtures/code/annotator/app.js | 0 .../annotator/kubernetes-deployment.yaml | 0 .../annotator/cloudformation-deployment.yaml | 0 .../iac/annotator/kubernetes-deployment.yaml | 0 .../iac/annotator/terraform-main.tf | 0 .../test-fixtures/oss/annotator/build.gradle | 0 .../oss/annotator/build.gradle.kts | 0 .../test-fixtures/oss/annotator/go.mod | 0 .../test-fixtures/oss/annotator/package.json | 0 .../test-fixtures/oss/annotator/pom.xml | 0 .../vulnerabilities-array-cli-result.json | 0 ...ties-array-with-error-and-result-test.json | 0 139 files changed, 763 insertions(+), 655 deletions(-) delete mode 100644 src/integTest/kotlin/io/snyk/plugin/TestUtils.kt delete mode 100644 src/main/resources/icons/code_dark.svg delete mode 100644 src/main/resources/icons/code_disabled_dark.svg delete mode 100644 src/main/resources/icons/container_dark.svg delete mode 100644 src/main/resources/icons/container_disabled_dark.svg delete mode 100644 src/main/resources/icons/iac_dark.svg delete mode 100644 src/main/resources/icons/iac_disabled_dark.svg delete mode 100644 src/main/resources/icons/oss_dark.svg delete mode 100644 src/main/resources/icons/oss_disabled_dark.svg rename src/{integTest => test}/kotlin/io/snyk/plugin/cli/ConsoleCommandRunnerTest.kt (97%) rename src/{integTest/kotlin/io/snyk/plugin/snykcode => test/kotlin/io/snyk/plugin}/core/IgnoreInfoHolderPlatformTestCase.kt (82%) rename src/{integTest/kotlin/io/snyk/plugin/snykcode => test/kotlin/io/snyk/plugin}/core/PDUTest.kt (91%) rename src/{integTest => test}/kotlin/io/snyk/plugin/services/CliAdapterTest.kt (97%) rename src/{integTest => test}/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceHeavyTest.kt (94%) rename src/{integTest => test}/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt (92%) rename src/{integTest => test}/kotlin/io/snyk/plugin/services/download/CliDownloaderErrorHandlerIntegTest.kt (100%) rename src/{integTest => test}/kotlin/io/snyk/plugin/services/download/CliDownloaderServiceIntegTest.kt (100%) rename src/{integTest => test}/kotlin/io/snyk/plugin/ui/toolwindow/ReportFalsePositiveDialogIntegTest.kt (100%) rename src/{integTest => test}/kotlin/io/snyk/plugin/ui/toolwindow/SnykAuthPanelIntegTest.kt (99%) rename src/{integTest => test}/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt (98%) rename src/{integTest => test}/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelIntegTest.kt (100%) rename src/{integTest => test}/kotlin/io/snyk/plugin/ui/toolwindow/settings/ScanTypesPanelTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/advisor/PackageNameProviderTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/advisor/SnykAdvisorModelTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/code/annotator/SnykCodeAnnotatorTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/common/AnnotatorCommonIntegTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/common/intentionactions/AlwaysAvailableReplacementIntentionActionTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/container/ContainerBulkFileListenerTest.kt (89%) rename src/{integTest => test}/kotlin/snyk/container/ContainerServiceIntegTest.kt (98%) rename src/{integTest => test}/kotlin/snyk/container/KubernetesImageCacheIntegTest.kt (92%) rename src/{integTest => test}/kotlin/snyk/container/TestYamls.kt (100%) rename src/{integTest => test}/kotlin/snyk/container/annotator/BaseImageRemediationFixTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/container/annotator/ContainerYamlAnnotatorTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/iac/IacBulkFileListenerTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/iac/IacServiceTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/iac/annotator/IacBaseAnnotatorCase.kt (100%) rename src/{integTest => test}/kotlin/snyk/iac/annotator/IacHclAnnotatorTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/iac/annotator/IacJsonAnnotatorTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/iac/annotator/IacYamlAnnotatorTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/oss/OssBulkFileListenerTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/oss/OssServiceTest.kt (100%) rename src/{integTest => test}/kotlin/snyk/oss/annotator/OSSGoModAnnotatorTest.kt (97%) rename src/{integTest => test}/kotlin/snyk/oss/annotator/OSSGradleAnnotatorTest.kt (99%) rename src/{integTest => test}/kotlin/snyk/oss/annotator/OSSMavenAnnotatorTest.kt (98%) rename src/{integTest => test}/kotlin/snyk/oss/annotator/OSSNpmAnnotatorTest.kt (100%) rename src/{integTest/kotlin/snyk/trust/WorkspaceTrustServiceTest.kt => test/kotlin/snyk/trust/WorkspaceTrustServiceIntegrationTest.kt} (95%) rename src/{integTest => test}/resources/AnnotatorTest.java (100%) rename src/{integTest => test}/resources/advisor-build-files/package.json (100%) rename src/{integTest => test}/resources/advisor-build-files/requirements.txt (100%) rename src/{integTest => test}/resources/container-test-results/container-double-jenkins-with-path.json (100%) rename src/{integTest => test}/resources/container-test-results/debian-nginx-fake_critical_only.json (100%) rename src/{integTest => test}/resources/container-test-results/nginx-no-remediation.json (100%) rename src/{integTest => test}/resources/container-test-results/nginx-with-remediation.json (100%) rename src/{integTest => test}/resources/group-vulnerabilities-goof-test.json (100%) rename src/{integTest => test}/resources/group-vulnerabilities-test.json (100%) rename src/{integTest => test}/resources/iac-test-results/fargate.json (100%) rename src/{integTest => test}/resources/iac-test-results/infrastructure-as-code-goof.json (100%) rename src/{integTest => test}/resources/licence-vulnerabilities.json (100%) rename src/{integTest => test}/resources/misformed-vulnerabilities-test.json (100%) rename src/{integTest => test}/resources/mockito-extensions/org.mockito.plugins.MockMaker (100%) rename src/{integTest => test}/resources/oss-test-results/oss-result-go-mod.json (100%) rename src/{integTest => test}/resources/oss-test-results/oss-result-gradle-kts.json (100%) rename src/{integTest => test}/resources/oss-test-results/oss-result-gradle.json (100%) rename src/{integTest => test}/resources/oss-test-results/oss-result-maven.json (100%) rename src/{integTest => test}/resources/oss-test-results/oss-result-package-no-remediation.json (100%) rename src/{integTest => test}/resources/oss-test-results/oss-result-package.json (100%) rename src/{integTest => test}/resources/test-fixtures/code/annotator/app.js (100%) rename src/{integTest => test}/resources/test-fixtures/container/annotator/kubernetes-deployment.yaml (100%) rename src/{integTest => test}/resources/test-fixtures/iac/annotator/cloudformation-deployment.yaml (100%) rename src/{integTest => test}/resources/test-fixtures/iac/annotator/kubernetes-deployment.yaml (100%) rename src/{integTest => test}/resources/test-fixtures/iac/annotator/terraform-main.tf (100%) rename src/{integTest => test}/resources/test-fixtures/oss/annotator/build.gradle (100%) rename src/{integTest => test}/resources/test-fixtures/oss/annotator/build.gradle.kts (100%) rename src/{integTest => test}/resources/test-fixtures/oss/annotator/go.mod (100%) rename src/{integTest => test}/resources/test-fixtures/oss/annotator/package.json (100%) rename src/{integTest => test}/resources/test-fixtures/oss/annotator/pom.xml (100%) rename src/{integTest => test}/resources/vulnerabilities-array-cli-result.json (100%) rename src/{integTest => test}/resources/vulnerabilities-array-with-error-and-result-test.json (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb1b47c98..78b33548a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: 17 - name: Setup Gradle Cache uses: actions/cache@v2 diff --git a/.github/workflows/detekt.yml b/.github/workflows/detekt.yml index 1d82b5641..781c0e887 100644 --- a/.github/workflows/detekt.yml +++ b/.github/workflows/detekt.yml @@ -14,7 +14,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: 17 - name: Setup Gradle Cache uses: actions/cache@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 430ed7057..2ba62db28 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: 17 - name: Setup Gradle Cache uses: actions/cache@v2 @@ -44,7 +44,7 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} SNYK_ORG_NAME: ${{ secrets.SNYK_ORG_NAME }} run: | - ./gradlew test integTest verifyPlugin clean + ./gradlew test verifyPlugin clean git clean -d --force git reset --hard diff --git a/CHANGELOG.md b/CHANGELOG.md index a7acab370..6703dd556 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Snyk Changelog +## [2.5.0] + +### Changed + +- Fix compatibility issues; only support Jetbrains 2023.x for plugin versions > 2.5.0 + ## [2.4.63] ### Fixed diff --git a/build.gradle.kts b/build.gradle.kts index 0abb212b9..bc6f1baf0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,7 @@ import io.gitlab.arturbosch.detekt.Detekt import org.apache.tools.ant.filters.ReplaceTokens import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.jetbrains.changelog.Changelog import org.jetbrains.changelog.markdownToHTML import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -8,10 +9,10 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile fun properties(key: String) = project.findProperty(key).toString() plugins { - id("org.jetbrains.changelog") version "1.2.1" - id("org.jetbrains.intellij") version "1.1.2" - id("org.jetbrains.kotlin.jvm") version "1.8.0" - id("io.gitlab.arturbosch.detekt") version ("1.21.0") + id("org.jetbrains.changelog") version "2.1.2" + id("org.jetbrains.intellij") version "1.15.0" + id("org.jetbrains.kotlin.jvm") version "1.9.0" + id("io.gitlab.arturbosch.detekt") version ("1.23.1") id("pl.allegro.tech.build.axion-release") version "1.13.6" } @@ -20,7 +21,7 @@ version = scmVersion.version group = properties("pluginGroup") description = properties("pluginName") -val jdk = "1.8" +val jdk = "17" repositories { mavenCentral() @@ -28,26 +29,35 @@ repositories { } dependencies { - implementation("org.commonmark:commonmark:0.19.0") - implementation("com.google.code.gson:gson:2.9.0") - implementation("com.segment.analytics.java:analytics:3.3.1") - implementation("io.sentry:sentry:6.4.2") + implementation(platform("com.squareup.okhttp3:okhttp-bom:4.10.0")) + implementation("org.commonmark:commonmark:0.21.0") + implementation("com.google.code.gson:gson:2.10.1") + implementation("com.segment.analytics.java:analytics:3.4.0") + implementation("io.sentry:sentry:6.27.0") + implementation("javax.xml.bind:jaxb-api:2.3.1") // necessary because since JDK 9 not included + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.okhttp3:okhttp") + implementation("com.squareup.okhttp3:logging-interceptor") implementation("ly.iterative.itly:plugin-iteratively:1.2.11") implementation("ly.iterative.itly:plugin-schema-validator:1.2.11") { exclude(group = "org.slf4j") } - implementation("ly.iterative.itly:sdk-jvm:1.2.11") - testImplementation("com.google.jimfs:jimfs:1.2") - testImplementation("com.squareup.okhttp3:mockwebserver:4.10.0") + implementation("ly.iterative.itly:sdk-jvm:1.2.11") { + exclude(group = "org.slf4j") + } + implementation("com.segment.analytics.java:analytics:3.4.0") + + testImplementation("com.google.jimfs:jimfs:1.3.0") + testImplementation("com.squareup.okhttp3:mockwebserver") + testImplementation("junit:junit:4.13.2") { exclude(group = "org.hamcrest") } testImplementation("org.hamcrest:hamcrest:2.2") - testImplementation("io.mockk:mockk:1.12.2") // updating this breaks tests + testImplementation("io.mockk:mockk:1.13.5") testImplementation("org.awaitility:awaitility:4.2.0") - runtimeOnly("org.jetbrains.kotlin:kotlin-reflect:1.4.32") - detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.21.0") + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.1") } // configuration for gradle-intellij-plugin plugin. @@ -64,7 +74,7 @@ intellij { // configure for detekt plugin. // read more: https://detekt.github.io/detekt/kotlindsl.html detekt { - config = files("$projectDir/.github/detekt/detekt-config.yml") + config.setFrom("$projectDir/.github/detekt/detekt-config.yml") baseline = file("$projectDir/.github/detekt/detekt-baseline.xml") buildUponDefaultConfig = true } @@ -72,7 +82,7 @@ detekt { tasks { withType { kotlinOptions.jvmTarget = jdk - kotlinOptions.languageVersion = "1.3" + kotlinOptions.languageVersion = "1.9" } withType { @@ -136,7 +146,7 @@ tasks { }.joinToString("\n").run { markdownToHTML(this) } ) - changeNotes.set(provider { changelog.getLatest().toHTML() }) + changeNotes.set(provider { changelog.renderItem(changelog.getLatest(), Changelog.OutputType.HTML) }) } publishPlugin { @@ -160,27 +170,5 @@ tasks { org.jetbrains.intellij.tasks.RunPluginVerifierTask.FailureLevel.INVALID_PLUGIN ) ) - verificationReportsDir.set("$rootDir.path/reports/pluginVerifier") } } - -sourceSets { - create("integTest") { - compileClasspath += sourceSets.main.get().output - runtimeClasspath += sourceSets.main.get().output - } -} -val integTestImplementation: Configuration by configurations.getting { - extendsFrom(configurations.implementation.get(), configurations.testImplementation.get()) -} -configurations["integTestRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get()) - -val integTest = task("integTest") { - description = "Runs the integration tests." - group = "verification" - - testClassesDirs = sourceSets["integTest"].output.classesDirs - classpath = sourceSets["integTest"].runtimeClasspath - shouldRunAfter("test") -} -tasks.check { dependsOn(integTest) } diff --git a/gradle.properties b/gradle.properties index f7b22d631..f97603b53 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,22 +3,18 @@ pluginName=Snyk Security - Code, Open Source, Container, IaC Configurations # for insight into build numbers and IntelliJ Platform versions # see https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html -pluginSinceBuild=202 +pluginSinceBuild=231 pluginUntilBuild=232.* - -platformVersion=2020.2 +platformVersion=2023.1 platformDownloadSources=true # plugin dependencies (comma-separated) # example: platformPlugins = com.intellij.java, org.jetbrains.plugins.yaml # see https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html -platformPlugins=org.intellij.plugins.hcl:0.7.10,org.jetbrains.plugins.yaml,org.jetbrains.kotlin,com.intellij.java,org.intellij.groovy - +platformPlugins=org.intellij.plugins.hcl:231.8109.91,org.jetbrains.plugins.yaml,org.jetbrains.kotlin,com.intellij.java,org.intellij.groovy # list of versions for which to check the plugin for api compatibility -pluginVerifierIdeVersions=2020.3.1,2021.1.1,2021.3,2022.1 - +pluginVerifierIdeVersions=2023.1,2023.2 localIdeDirectory= - # opt-out flag for bundling Kotlin standard library # see https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library kotlin.stdlib.default.dependency=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 39133 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 Ih2Pfy0TaOAA^-pY delta 36560 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 Fx;w9QV4r z|C*cNPYu>;jvqNIN32p`#N-2mu|Kq< z#r*y5rC>ix)Ok!ZYWRH&-oI4FAYq}fUYtdD`q5UN(Bv1y-T={{v`DT6RKN{amGFNh z=w2vmwO{rh(j}a_d0Os+q~Db(=Bj5OrMFpIVtU+ecC6vAV!6~Xc&yH2ZUkc*72-+gv5{rwZ>1c$qNlBSJ#-okiya4gk`%$3??G-y}$ z>gIVf^6jhoEp64&7c1kw4OuTaCp;+X+9nxS5TKlMRFY_Aq}0|Qw05NX*JDdR-zfrlO1t)EtUOnH`M^{i}PV7L@;%c_E_Oq_xl z`JvvMc1&Qj)rfZ6$tBOJx6JhD$v*CZ1KU8Pd_MmXQgRJ8lxUl*?BN%*HV z^E-+AmZuPdb!n;0ieU~B4V&zU3jWxs*l=9-e9GHDunIwM1=ipgZwris-%b^-t<);k*K+vaT4QEbu<0T zwzVjNM6Gsz6!xurk&^V#ok}kI5figuC}c|||0Hn&yP6ueJ+S79rH1_66|5+3Zh!9W z#~C)`)>SrPC|-4k`WP2axTP!E#qKG!BSu~1W!cw8;i$E;En3v!8>e_2ni?#plIX!Zl;414h747*VDv;tMsN@%xmQ{)RC`XQ$(L??y{r8oSCHk)%rSJ_AipxN zlAn5FXh!5++za)rzGj%|D^YX!Sp3Vg*Y6T3!j34-DFwuQAXO+#saJRYYHl@gCp}=gyt#p#+A@NtlWBdcZa}b zW|u4fbk0w9qB+}>f~nBzFek5i_sXT%sqG>HNBGCiOHEiDFJ%TC|LCjJm{!LPyR{Ns zWpJuN*P`g-=fZaT3;jJkEm&+t4yzxehr1cv#~c(B(~WI%Bo+8D@{0AnObXJJ2XfOc zR8nRQ9XM+|KSgr~qP;C1_Zxjnpw+BY_;A$Z32AkN8w`$;y{$Tfwky#X3hU;zB|$d` zv>JiIXQm5Er9ak%$kv6TYsOt6$qvMD&(y0eu_9}o8a(OiVe#*HN`pUgsHUC8 zc&hbG*`a}%By&=@*Cj=2!|!_DZw!CqcM^*|$f=~zxZFLve83s&UAFuvEb+)2;_Vau zPh*1W;@5LLZQd6-}CloFo8^Fd`mN!XG z+EgSHau*dsVm#=><%$W?Iqnf=`oy?g$Er{vBO5i2LPA=5@wb+PR<<=h>EqZ<%ieJ#Umjzj5P3i!#t8f`o2` zx$_0*mA#jBIsKVuawCivpUWuHZKcxJI;6gCx7c~nc$c|zt}UH#7MxpMjGoHuM=q0rDb>Uf>VWV`PW8?R<*@DG!W+~p6_d|gjanA=E zK{!AUB8E1v zERxR-1Q!Q;XVl@DmrP!f=49ic0-tfw;Dg802=_rYw3bFx`FB>xjcgQh_g zkem~Rl+;;GmU9yDuOpz4B{?ZJJ&Xnm`ku=La_4r#|9?~ivN`^*D~nX1{|hvr2m+pu zD41;<{a@%t$1fZHgn5DeM=W&OhE+}b2*Yf)hr|X>*#881 z!I^nw$k~Neoo(6!Xo%kb_IKdmVDEu1VFSFw#KLUp@@%%fF8r6_;xY<$a}5me20p`R z_*Y68!b-F8bbpYuQwE3)VW9&7U^kCEBEleHh!64!0OTP&lo&E1(0hI(^;w(o~oo)F|udyc%;2iYvr|6+CE{gGW8 zR$;b9aW+sap9Lx(6=#8Li{fmcbrAwe0pCewsGtT+pOFR4lURt_jEGVIGo=9yw*iYO zcc`=jAX!}-!$ \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index a9f778a7a..6689b85be 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +65,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,17 +72,19 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/settings.gradle.kts b/settings.gradle.kts index bda28a4ff..8ef6ba01e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,7 @@ rootProject.name = "snyk-intellij-plugin" +pluginManagement { + repositories { + maven("https://oss.sonatype.org/content/repositories/snapshots/") + gradlePluginPortal() + } +} diff --git a/src/integTest/kotlin/io/snyk/plugin/TestUtils.kt b/src/integTest/kotlin/io/snyk/plugin/TestUtils.kt deleted file mode 100644 index 89be37cc2..000000000 --- a/src/integTest/kotlin/io/snyk/plugin/TestUtils.kt +++ /dev/null @@ -1,54 +0,0 @@ -package io.snyk.plugin - -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.project.Project -import com.intellij.testFramework.replaceService -import com.intellij.util.io.RequestBuilder -import io.mockk.every -import io.mockk.justRun -import io.mockk.mockk -import io.mockk.mockkObject -import io.snyk.plugin.services.SnykApplicationSettingsStateService -import io.snyk.plugin.services.SnykProjectSettingsStateService -import io.snyk.plugin.services.download.CliDownloader -import io.snyk.plugin.services.download.HttpRequestHelper - -fun setupDummyCliFile() { - val cliFile = getCliFile() - - if (!cliFile.exists()) { - if (!cliFile.parentFile.exists()) cliFile.mkdirs() - cliFile.createNewFile() - } -} - -fun removeDummyCliFile() { - val cliFile = getCliFile() - if (cliFile.exists()) { - cliFile.delete() - } -} - -fun resetSettings(project: Project?) { - val application = ApplicationManager.getApplication() - application.replaceService( - SnykApplicationSettingsStateService::class.java, - SnykApplicationSettingsStateService(), - application - ) - project?.replaceService( - SnykProjectSettingsStateService::class.java, - SnykProjectSettingsStateService(), - project - ) -} - -/** low level avoiding download the CLI file */ -fun mockCliDownload(): RequestBuilder { - val requestBuilderMockk = mockk(relaxed = true) - justRun { requestBuilderMockk.saveToFile(any(), any()) } - mockkObject(HttpRequestHelper) - every { HttpRequestHelper.createRequest(CliDownloader.LATEST_RELEASE_DOWNLOAD_URL) } returns requestBuilderMockk - every { HttpRequestHelper.createRequest(CliDownloader.LATEST_RELEASES_URL) } returns requestBuilderMockk - return requestBuilderMockk -} diff --git a/src/main/kotlin/icons/SnykIcons.kt b/src/main/kotlin/icons/SnykIcons.kt index c8c1e70eb..44a1efd01 100644 --- a/src/main/kotlin/icons/SnykIcons.kt +++ b/src/main/kotlin/icons/SnykIcons.kt @@ -9,55 +9,56 @@ object SnykIcons { private fun getIconFromResources(name: String): ImageIcon = ImageIcon(this::class.java.getResource(name)) @JvmField - val TOOL_WINDOW = getIcon("/icons/snyk-dog.svg") + val TOOL_WINDOW = getIcon("/icons/snyk-dog.svg", SnykIcons::class.java) - val LOGO = getIcon("/icons/logo_snyk.png") + val LOGO = getIcon("/icons/logo_snyk.png", SnykIcons::class.java) - val VULNERABILITY_16 = getIconFromResources("/icons/vulnerability_16.png") - val VULNERABILITY_24 = getIconFromResources("/icons/vulnerability.png") + private val VULNERABILITY_16 = getIconFromResources("/icons/vulnerability_16.png") + private val VULNERABILITY_24 = getIconFromResources("/icons/vulnerability.png") - val OPEN_SOURCE_SECURITY = getIcon("/icons/oss.svg") - val OPEN_SOURCE_SECURITY_DISABLED = getIcon("/icons/oss_disabled.svg") - val SNYK_CODE = getIcon("/icons/code.svg") - val SNYK_CODE_DISABLED = getIcon("/icons/code_disabled.svg") - val IAC = getIcon("/icons/iac.svg") - val IAC_DISABLED = getIcon("/icons/iac_disabled.svg") + val OPEN_SOURCE_SECURITY = getIcon("/icons/oss.svg", SnykIcons::class.java) + val OPEN_SOURCE_SECURITY_DISABLED = getIcon("/icons/oss_disabled.svg", SnykIcons::class.java) + val SNYK_CODE = getIcon("/icons/code.svg", SnykIcons::class.java) + val SNYK_CODE_DISABLED = getIcon("/icons/code_disabled.svg", SnykIcons::class.java) + val IAC = getIcon("/icons/iac.svg", SnykIcons::class.java) + val IAC_DISABLED = getIcon("/icons/iac_disabled.svg", SnykIcons::class.java) val CONTAINER = getIcon("/icons/container.svg", SnykIcons::class.java) val CONTAINER_DISABLED = getIcon("/icons/container_disabled.svg", SnykIcons::class.java) + val CONTAINER_IMAGE = getIcon("/icons/container_image.svg", SnykIcons::class.java) val CONTAINER_IMAGE_24 = getIcon("/icons/container_image_24.svg", SnykIcons::class.java) - val GRADLE = getIcon("/icons/gradle.svg") - val MAVEN = getIcon("/icons/maven.svg") - val NPM = getIcon("/icons/npm.svg") - val PYTHON = getIcon("/icons/python.svg") - val RUBY_GEMS = getIcon("/icons/rubygems.svg") - val YARN = getIcon("/icons/yarn.svg") - val SBT = getIcon("/icons/sbt.svg") - val GOlANG_DEP = getIcon("/icons/golangdep.svg") - val GO_VENDOR = getIcon("/icons/govendor.svg") - val GOLANG = getIcon("/icons/golang.svg") - val NUGET = getIcon("/icons/nuget.svg") - val PAKET = getIcon("/icons/paket.svg") - val COMPOSER = getIcon("/icons/composer.svg") - val LINUX = getIcon("/icons/linux.svg") - val DEB = getIcon("/icons/deb.svg") - val APK = getIcon("/icons/apk.svg") - val COCOAPODS = getIcon("/icons/cocoapods.svg") - val RPM = getIcon("/icons/rpm.svg") - val DOCKER = getIcon("/icons/docker.svg") + val GRADLE = getIcon("/icons/gradle.svg", SnykIcons::class.java) + val MAVEN = getIcon("/icons/maven.svg", SnykIcons::class.java) + val NPM = getIcon("/icons/npm.svg", SnykIcons::class.java) + val PYTHON = getIcon("/icons/python.svg", SnykIcons::class.java) + val RUBY_GEMS = getIcon("/icons/rubygems.svg", SnykIcons::class.java) + val YARN = getIcon("/icons/yarn.svg", SnykIcons::class.java) + val SBT = getIcon("/icons/sbt.svg", SnykIcons::class.java) + val GOlANG_DEP = getIcon("/icons/golangdep.svg", SnykIcons::class.java) + val GO_VENDOR = getIcon("/icons/govendor.svg", SnykIcons::class.java) + val GOLANG = getIcon("/icons/golang.svg", SnykIcons::class.java) + val NUGET = getIcon("/icons/nuget.svg", SnykIcons::class.java) + val PAKET = getIcon("/icons/paket.svg", SnykIcons::class.java) + val COMPOSER = getIcon("/icons/composer.svg", SnykIcons::class.java) + val LINUX = getIcon("/icons/linux.svg", SnykIcons::class.java) + val DEB = getIcon("/icons/deb.svg", SnykIcons::class.java) + val APK = getIcon("/icons/apk.svg", SnykIcons::class.java) + val COCOAPODS = getIcon("/icons/cocoapods.svg", SnykIcons::class.java) + val RPM = getIcon("/icons/rpm.svg", SnykIcons::class.java) + val DOCKER = getIcon("/icons/docker.svg", SnykIcons::class.java) // copy of FeaturesTrainerIcons.Img.GreenCheckmark from https://jetbrains.github.io/ui/resources/icons_list/ - val CHECKMARK_GREEN = getIcon("/icons/greenCheckmark.svg") + val CHECKMARK_GREEN = getIcon("/icons/greenCheckmark.svg", SnykIcons::class.java) - private val CRITICAL_SEVERITY_16 = getIcon("/icons/severity_critical_16.svg") - private val CRITICAL_SEVERITY_32 = getIcon("/icons/severity_critical_32.svg") - private val HIGH_SEVERITY_16 = getIcon("/icons/severity_high_16.svg") - private val HIGH_SEVERITY_32 = getIcon("/icons/severity_high_32.svg") - private val LOW_SEVERITY_16 = getIcon("/icons/severity_low_16.svg") - private val LOW_SEVERITY_32 = getIcon("/icons/severity_low_32.svg") - private val MEDIUM_SEVERITY_16 = getIcon("/icons/severity_medium_16.svg") - private val MEDIUM_SEVERITY_32 = getIcon("/icons/severity_medium_32.svg") + private val CRITICAL_SEVERITY_16 = getIcon("/icons/severity_critical_16.svg", SnykIcons::class.java) + private val CRITICAL_SEVERITY_32 = getIcon("/icons/severity_critical_32.svg", SnykIcons::class.java) + private val HIGH_SEVERITY_16 = getIcon("/icons/severity_high_16.svg", SnykIcons::class.java) + private val HIGH_SEVERITY_32 = getIcon("/icons/severity_high_32.svg", SnykIcons::class.java) + private val LOW_SEVERITY_16 = getIcon("/icons/severity_low_16.svg", SnykIcons::class.java) + private val LOW_SEVERITY_32 = getIcon("/icons/severity_low_32.svg", SnykIcons::class.java) + private val MEDIUM_SEVERITY_16 = getIcon("/icons/severity_medium_16.svg", SnykIcons::class.java) + private val MEDIUM_SEVERITY_32 = getIcon("/icons/severity_medium_32.svg", SnykIcons::class.java) fun getSeverityIcon(severity: Severity, iconSize: IconSize = IconSize.SIZE16): Icon { return when (severity) { @@ -65,6 +66,7 @@ object SnykIcons { IconSize.SIZE16 -> CRITICAL_SEVERITY_16 IconSize.SIZE32 -> CRITICAL_SEVERITY_32 } + Severity.HIGH -> when (iconSize) { IconSize.SIZE16 -> HIGH_SEVERITY_16 IconSize.SIZE32 -> HIGH_SEVERITY_32 diff --git a/src/main/kotlin/io/snyk/plugin/Severity.kt b/src/main/kotlin/io/snyk/plugin/Severity.kt index c4d48f98b..88a4f0e16 100644 --- a/src/main/kotlin/io/snyk/plugin/Severity.kt +++ b/src/main/kotlin/io/snyk/plugin/Severity.kt @@ -15,7 +15,7 @@ enum class Severity { HIGH, CRITICAL; - override fun toString(): String = super.toString().toLowerCase() + override fun toString(): String = super.toString().lowercase() fun toPresentableString(): String = when (this) { diff --git a/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt b/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt index 777bc8275..db9098267 100644 --- a/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt +++ b/src/main/kotlin/io/snyk/plugin/SnykPostStartupActivity.kt @@ -7,7 +7,7 @@ import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager -import com.intellij.openapi.startup.StartupActivity +import com.intellij.openapi.startup.ProjectActivity import com.intellij.openapi.vfs.VirtualFileManager import io.snyk.plugin.snykcode.SnykCodeBulkFileListener import io.snyk.plugin.snykcode.core.AnalysisData @@ -27,12 +27,12 @@ import java.util.Date private val LOG = logger() -class SnykPostStartupActivity : StartupActivity.DumbAware { +class SnykPostStartupActivity : ProjectActivity { private var listenersActivated = false val settings = pluginSettings() - override fun runActivity(project: Project) { + override suspend fun execute(project: Project) { PluginInstaller.addStateListener(UninstallListener()) // clean up left-overs in case project wasn't properly closed before diff --git a/src/main/kotlin/io/snyk/plugin/Utils.kt b/src/main/kotlin/io/snyk/plugin/Utils.kt index df7e45932..b6ca4031e 100644 --- a/src/main/kotlin/io/snyk/plugin/Utils.kt +++ b/src/main/kotlin/io/snyk/plugin/Utils.kt @@ -53,6 +53,7 @@ import snyk.oss.OssService import snyk.oss.OssTextRangeFinder import snyk.whoami.WhoamiService import java.io.File +import java.net.URI import java.net.URL import java.nio.file.Path import java.security.KeyStore @@ -136,7 +137,7 @@ private inline fun getApplicationService(): T { ?: throw RuntimeException("Cannot find service ${serviceClass.name} (classloader=${serviceClass.classLoader})") } -fun getSyncPublisher(project: Project, topic: Topic): L? { +fun getSyncPublisher(project: Project, topic: Topic): L? { val messageBus = project.messageBus if (messageBus.isDisposed) return null return messageBus.syncPublisher(topic) @@ -152,8 +153,8 @@ fun isUrlValid(url: String?): Boolean { url.isNullOrEmpty() && return true return try { - val uri = URL(url).toURI() - return uri.isSnykTenant() + val uri = url?.let { URI.create(it) } + return uri?.isSnykTenant() ?: false } catch (throwable: Throwable) { false } @@ -291,10 +292,9 @@ fun navigateToSource( selectionEndOffset: Int? = null ) { if (!virtualFile.isValid) return - val psiFile = RunUtils.computeInReadActionInSmartMode( - project, - Computable { PsiManager.getInstance(project).findFile(virtualFile) } - ) ?: return + val psiFile = RunUtils.computeInReadActionInSmartMode(project) { + PsiManager.getInstance(project).findFile(virtualFile) + } ?: return val textLength = psiFile.textLength if (selectionStartOffset in (0 until textLength)) { // jump to Source diff --git a/src/main/kotlin/io/snyk/plugin/cli/Platform.kt b/src/main/kotlin/io/snyk/plugin/cli/Platform.kt index 9778389ee..3f2aec301 100644 --- a/src/main/kotlin/io/snyk/plugin/cli/Platform.kt +++ b/src/main/kotlin/io/snyk/plugin/cli/Platform.kt @@ -2,7 +2,6 @@ package io.snyk.plugin.cli import java.io.IOException import java.nio.file.Paths -import java.util.Locale import java.util.Properties class Platform(val snykWrapperFileName: String) { @@ -18,7 +17,7 @@ class Platform(val snykWrapperFileName: String) { @Suppress("MoveVariableDeclarationIntoWhen") @Throws(PlatformDetectionException::class) fun detect(systemProperties: Properties): Platform { - val architectureName = (systemProperties["os.name"] as String).toLowerCase(Locale.ENGLISH) + val architectureName = (systemProperties["os.name"] as String).lowercase() return when (architectureName) { "linux" -> if (Paths.get("/etc/alpine-release").toFile().exists()) LINUX_ALPINE else LINUX "mac os x", "darwin", "osx" -> MAC_OS diff --git a/src/main/kotlin/io/snyk/plugin/net/SnykApiClient.kt b/src/main/kotlin/io/snyk/plugin/net/SnykApiClient.kt index c88bfeb0b..f8d268268 100644 --- a/src/main/kotlin/io/snyk/plugin/net/SnykApiClient.kt +++ b/src/main/kotlin/io/snyk/plugin/net/SnykApiClient.kt @@ -25,18 +25,18 @@ class SnykApiClient( private fun executeRequest(apiName: String, retrofitCall: Call, retryCounter: Int = 2): T? { if (retryCounter < 0) return null - try { + return try { log.debug("Executing request to $apiName") val response = retrofitCall.execute() if (!response.isSuccessful) { log.warn("Failed to execute `$apiName` call: ${response.errorBody()?.string()}") - return executeRequest(apiName, retrofitCall.clone(), retryCounter - 1) + executeRequest(apiName, retrofitCall.clone(), retryCounter - 1) } else { - return response.body() + response.body() } } catch (t: Throwable) { log.warn("Failed to execute '$apiName' network request: ${t.message}", t) - return executeRequest(apiName, retrofitCall.clone(), retryCounter - 1) + executeRequest(apiName, retrofitCall.clone(), retryCounter - 1) } } diff --git a/src/main/kotlin/io/snyk/plugin/services/SnykAnalyticsService.kt b/src/main/kotlin/io/snyk/plugin/services/SnykAnalyticsService.kt index bc4ff7fff..f51a1482a 100644 --- a/src/main/kotlin/io/snyk/plugin/services/SnykAnalyticsService.kt +++ b/src/main/kotlin/io/snyk/plugin/services/SnykAnalyticsService.kt @@ -17,7 +17,6 @@ import snyk.analytics.HealthScoreIsClicked import snyk.analytics.IssueInTreeIsClicked import snyk.analytics.PluginIsInstalled import snyk.analytics.PluginIsUninstalled -import snyk.analytics.ProductSelectionIsViewed import snyk.analytics.QuickFixIsDisplayed import snyk.analytics.QuickFixIsTriggered import snyk.analytics.WelcomeIsViewed @@ -36,7 +35,7 @@ class SnykAnalyticsService : Disposable { private var userId = "" init { - userId = obtainUserId(settings.token) + if (settings.usageAnalyticsEnabled) userId = obtainUserId(settings.token) } fun initAnalyticsReporter(project: Project) = project.messageBus.connect().subscribe( @@ -146,6 +145,9 @@ class SnykAnalyticsService : Disposable { } fun obtainUserId(token: String?): String { + if (!settings.usageAnalyticsEnabled) { + return "" + } if (token.isNullOrBlank()) { log.warn("Token is null or empty, user public id will not be obtained.") return "" @@ -176,16 +178,6 @@ class SnykAnalyticsService : Disposable { } } - fun logProductSelectionIsViewed(event: ProductSelectionIsViewed) { - if (!settings.usageAnalyticsEnabled || userId.isBlank()) { - return - } - - catchAll(log, "productSelectionIsViewed") { - itly.logProductSelectionIsViewed(userId, event) - } - } - fun logAnalysisIsTriggered(event: AnalysisIsTriggered) { if (!settings.usageAnalyticsEnabled || userId.isBlank()) { return diff --git a/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt b/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt index a3a0968a6..e6c84499c 100644 --- a/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt +++ b/src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt @@ -76,7 +76,7 @@ class SnykTaskQueueService(val project: Project) { taskQueue.run(object : Task.Backgroundable(project, "Snyk wait for changed files to be saved on disk", true) { override fun run(indicator: ProgressIndicator) { project.basePath?.let { - if (!confirmScanningAndSetWorkspaceTrustedStateIfNeeded(Paths.get(it))) return + if (!confirmScanningAndSetWorkspaceTrustedStateIfNeeded(project, Paths.get(it))) return } ApplicationManager.getApplication().invokeAndWait { diff --git a/src/main/kotlin/io/snyk/plugin/services/download/CliDownloader.kt b/src/main/kotlin/io/snyk/plugin/services/download/CliDownloader.kt index 9cd0e68d5..962ba5e50 100644 --- a/src/main/kotlin/io/snyk/plugin/services/download/CliDownloader.kt +++ b/src/main/kotlin/io/snyk/plugin/services/download/CliDownloader.kt @@ -8,6 +8,7 @@ import java.nio.file.AtomicMoveNotSupportedException import java.nio.file.Files import java.nio.file.StandardCopyOption import java.security.MessageDigest +import java.util.Locale import javax.xml.bind.DatatypeConverter class CliDownloader { @@ -21,13 +22,13 @@ class CliDownloader { fun calculateSha256(bytes: ByteArray): String { return DatatypeConverter.printHexBinary( MessageDigest.getInstance("SHA-256").digest(bytes) - ).toLowerCase() + ).lowercase(Locale.getDefault()) } @Throws(ChecksumVerificationException::class) fun verifyChecksum(expectedSha: String, bytes: ByteArray) { val sha256 = calculateSha256(bytes) - if (sha256.toLowerCase() != expectedSha.toLowerCase()) { + if (sha256.lowercase(Locale.getDefault()) != expectedSha.lowercase(Locale.getDefault())) { throw ChecksumVerificationException("Expected $expectedSha, but downloaded file has $sha256") } } diff --git a/src/main/kotlin/io/snyk/plugin/settings/SnykProjectSettingsConfigurable.kt b/src/main/kotlin/io/snyk/plugin/settings/SnykProjectSettingsConfigurable.kt index 41d78da45..a6c1bac0e 100644 --- a/src/main/kotlin/io/snyk/plugin/settings/SnykProjectSettingsConfigurable.kt +++ b/src/main/kotlin/io/snyk/plugin/settings/SnykProjectSettingsConfigurable.kt @@ -101,11 +101,13 @@ class SnykProjectSettingsConfigurable(val project: Project) : SearchableConfigur getSyncPublisher(project, SnykProductsOrSeverityListener.SNYK_ENABLEMENT_TOPIC)?.enablementChanged() } - runBackgroundableTask("Identifying with Analytics service", project, true) { - val analytics = getSnykAnalyticsService() - val userId = analytics.obtainUserId(snykSettingsDialog.getToken()) - analytics.setUserId(userId) - getAmplitudeExperimentService().fetch(ExperimentUser(userId)) + if (pluginSettings().usageAnalyticsEnabled) { + runBackgroundableTask("Identifying with Analytics service", project, true) { + val analytics = getSnykAnalyticsService() + val userId = analytics.obtainUserId(snykSettingsDialog.getToken()) + analytics.setUserId(userId) + getAmplitudeExperimentService().fetch(ExperimentUser(userId)) + } } } diff --git a/src/main/kotlin/io/snyk/plugin/snykcode/SnykCodeResults.kt b/src/main/kotlin/io/snyk/plugin/snykcode/SnykCodeResults.kt index 465460737..fc9734164 100644 --- a/src/main/kotlin/io/snyk/plugin/snykcode/SnykCodeResults.kt +++ b/src/main/kotlin/io/snyk/plugin/snykcode/SnykCodeResults.kt @@ -11,15 +11,15 @@ class SnykCodeResults( private val files: Set by lazy { file2suggestions.keys } - val totalCount: Int by lazy { files.sumBy { getCount(it, null) } } + val totalCount: Int by lazy { files.sumOf { getCount(it, null) } } - val totalCriticalCount: Int by lazy { files.sumBy { criticalCount(it) } } + val totalCriticalCount: Int by lazy { files.sumOf { criticalCount(it) } } - val totalErrorsCount: Int by lazy { files.sumBy { errorsCount(it) } } + val totalErrorsCount: Int by lazy { files.sumOf { errorsCount(it) } } - val totalWarnsCount: Int by lazy { files.sumBy { warnsCount(it) } } + val totalWarnsCount: Int by lazy { files.sumOf { warnsCount(it) } } - val totalInfosCount: Int by lazy { files.sumBy { infosCount(it) } } + val totalInfosCount: Int by lazy { files.sumOf { infosCount(it) } } fun cloneFiltered(filter: (SuggestionForFile) -> Boolean): SnykCodeResults { return SnykCodeResults( @@ -65,7 +65,7 @@ class SnykCodeResults( private fun getCount(file: SnykCodeFile, severity: Severity?) = suggestions(file) .filter { severity == null || it.getSeverityAsEnum() == severity } - .sumBy { it.ranges.size } + .sumOf { it.ranges.size } override fun equals(other: Any?): Boolean { return other is SnykCodeResults && diff --git a/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotificationHelper.kt b/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotificationHelper.kt index 2bef11f9a..cb2501d29 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotificationHelper.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotificationHelper.kt @@ -2,8 +2,6 @@ package io.snyk.plugin.ui import com.intellij.icons.AllIcons import com.intellij.notification.Notification -import com.intellij.notification.NotificationDisplayType -import com.intellij.notification.NotificationGroup import com.intellij.notification.NotificationType import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent @@ -14,7 +12,9 @@ import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.ui.MessageType import com.intellij.openapi.ui.popup.Balloon import com.intellij.openapi.ui.popup.JBPopupFactory +import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.notificationGroup import com.intellij.ui.awt.RelativePoint +import icons.SnykIcons import java.awt.Color import java.awt.Component import java.awt.Point @@ -24,9 +24,7 @@ object SnykBalloonNotificationHelper { private val logger = logger() const val title = "Snyk" - private const val groupNeedAction = "SnykNeedAction" private const val groupAutoHide = "SnykAutoHide" - val GROUP = NotificationGroup(groupNeedAction, NotificationDisplayType.STICKY_BALLOON) fun showError(message: String, project: Project?, vararg actions: AnAction) { showNotification(message, project, NotificationType.ERROR, *actions) @@ -52,10 +50,12 @@ object SnykBalloonNotificationHelper { val notification = if (actions.isEmpty()) { Notification(groupAutoHide, title, message, type) } else { - GROUP.createNotification(title, message, type).apply { + notificationGroup.createNotification(title, message, type).apply { actions.forEach { this.addAction(it) } } } + + notification.setIcon(SnykIcons.TOOL_WINDOW) // workaround for https://youtrack.jetbrains.com/issue/IDEA-220408/notifications-with-project=null-is-not-shown-anymore if (project != null) { notification.notify(project) diff --git a/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotifications.kt b/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotifications.kt index a34da9e7a..c3a61cbed 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotifications.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotifications.kt @@ -3,6 +3,7 @@ package io.snyk.plugin.ui import com.intellij.ide.BrowserUtil import com.intellij.notification.Notification import com.intellij.notification.NotificationAction +import com.intellij.notification.NotificationGroupManager import com.intellij.notification.NotificationType import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.options.ShowSettingsUtil @@ -16,7 +17,6 @@ import io.snyk.plugin.pluginSettings import io.snyk.plugin.settings.SnykProjectSettingsConfigurable import io.snyk.plugin.snykToolWindow import io.snyk.plugin.startSastEnablementCheckLoop -import io.snyk.plugin.ui.SnykBalloonNotificationHelper.GROUP import snyk.common.toSnykCodeSettingsUrl import java.awt.event.MouseEvent @@ -30,14 +30,16 @@ object SnykBalloonNotifications { const val sastForOrgEnablementMessage = "Snyk Code is disabled by your organisation's configuration." const val networkErrorAlertMessage = "Not able to connect to Snyk server." + private val notificationGroup = NotificationGroupManager.getInstance().getNotificationGroup("Snyk") + fun showWelcomeNotification(project: Project) { val welcomeMessage = "Welcome to Snyk! Check out our tool window to start analyzing your code" logger.info(welcomeMessage) - val notification = GROUP.createNotification( + val notification = notificationGroup.createNotification( welcomeMessage, NotificationType.INFORMATION ).addAction( - NotificationAction.createSimpleExpiring("Configure Snyk\u2026") { + NotificationAction.createSimpleExpiring("CONFIGURE SNYK\u2026") { snykToolWindow(project)?.show() } ) @@ -48,7 +50,7 @@ object SnykBalloonNotifications { return SnykBalloonNotificationHelper.showInfo( sastForLocalCodeEngineMessage, project, - NotificationAction.createSimpleExpiring("Snyk Settings") { + NotificationAction.createSimpleExpiring("SNYK SETTINGS") { ShowSettingsUtil.getInstance() .showSettingsDialog(project, SnykProjectSettingsConfigurable::class.java) } @@ -59,9 +61,9 @@ object SnykBalloonNotifications { val notification = SnykBalloonNotificationHelper.showInfo( "$sastForOrgEnablementMessage To enable navigate to ", project, - NotificationAction.createSimpleExpiring("Snyk > Settings > Snyk Code") { + NotificationAction.createSimpleExpiring("SNYK > SETTINGS > SNYK CODE") { BrowserUtil.browse(toSnykCodeSettingsUrl(pluginSettings().customEndpointUrl)) - startSastEnablementCheckLoop(project) + startSastEnablementCheckLoop(parentDisposable = project) } ) var currentAttempt = 1 @@ -95,7 +97,7 @@ object SnykBalloonNotifications { fun showNetworkErrorAlert(project: Project) = SnykBalloonNotificationHelper.showError( "$networkErrorAlertMessage Check connection and network settings.", project, - NotificationAction.createSimpleExpiring("Snyk Settings") { + NotificationAction.createSimpleExpiring("SNYK SETTINGS") { ShowSettingsUtil.getInstance() .showSettingsDialog(project, SnykProjectSettingsConfigurable::class.java) } diff --git a/src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt b/src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt index 1369af5c3..7be40954d 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt @@ -9,6 +9,7 @@ import com.intellij.uiDesigner.core.GridConstraints import com.intellij.uiDesigner.core.GridLayoutManager import com.intellij.uiDesigner.core.Spacer import com.intellij.util.Alarm +import com.intellij.util.ui.HTMLEditorKitBuilder import com.intellij.util.ui.JBHtmlEditorKit import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil @@ -124,7 +125,7 @@ fun getReadOnlyClickableHtmlJEditorPaneFixedSize( // don't remove that! // Some magic (side-effect? customStyleSheet?) happens when JBHtmlEditorKit() initializing // that make html tags like ,

,

    etc. be treated properly inside JEditorPane - JBHtmlEditorKit() + HTMLEditorKitBuilder.simple() return JEditorPane( "text/html", @@ -137,7 +138,7 @@ fun getReadOnlyClickableHtmlJEditorPaneFixedSize( // instead of the value in javax.swing.text.html.default.css val fontColor = UIUtil.getTextFieldForeground() val bodyRule = UIUtil.displayPropertiesToCSS(font, fontColor) + - "a { color: #${ColorUtil.toHex(JBUI.CurrentTheme.Link.linkColor())}; }" + "a { color: #${ColorUtil.toHex(JBUI.CurrentTheme.Link.Foreground.ENABLED)}; }" (document as HTMLDocument).styleSheet.addRule(bodyRule) (document as HTMLDocument).styleSheet.addRule( diff --git a/src/main/kotlin/io/snyk/plugin/ui/settings/ScanTypesPanel.kt b/src/main/kotlin/io/snyk/plugin/ui/settings/ScanTypesPanel.kt index ee8669d74..ce23fd1b5 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/settings/ScanTypesPanel.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/settings/ScanTypesPanel.kt @@ -6,8 +6,8 @@ import com.intellij.openapi.progress.runBackgroundableTask import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.Balloon import com.intellij.ui.HyperlinkLabel +import com.intellij.ui.components.ActionLink import com.intellij.ui.components.JBCheckBox -import com.intellij.ui.components.labels.LinkLabel import com.intellij.ui.layout.panel import com.intellij.util.Alarm import com.intellij.util.ui.JBUI @@ -45,7 +45,7 @@ class ScanTypesPanel( private var codeQualityCheckbox: JBCheckBox? = null private var snykCodeComment: JLabel? = null private var snykCodeAlertHyperLinkLabel = HyperlinkLabel() - private var snykCodeReCheckLinkLabel = LinkLabel.create("Check again") { + private var snykCodeReCheckLinkLabel = ActionLink("Check again") { runBackgroundableTask("Checking Snyk Code enablement in organisation", project, true) { checkSastEnabled() } diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindow.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindow.kt index 034bcb964..1b9ef3598 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindow.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindow.kt @@ -46,7 +46,7 @@ class SnykToolWindow(private val project: Project) : SimpleToolWindowPanel(false val expandNodeChildActionsGroup = DefaultActionGroup() expandNodeChildActionsGroup.add(ExpandNodeChildAction(tree)) - PopupHandler.installPopupHandler(tree, expandNodeChildActionsGroup, "SnykTree", actionManager) + PopupHandler.installPopupMenu(tree, expandNodeChildActionsGroup, "SnykTree") actionGroup.addAll(actionManager.getAction("io.snyk.plugin.ScanActions") as DefaultActionGroup) actionGroup.addSeparator() @@ -56,6 +56,7 @@ class SnykToolWindow(private val project: Project) : SimpleToolWindowPanel(false actionGroup.addAll(actionManager.getAction("io.snyk.plugin.MiscActions") as DefaultActionGroup) actionToolbar = actionManager.createActionToolbar("Snyk Toolbar", actionGroup, false) + actionToolbar.targetComponent = this initialiseToolbarUpdater() toolbar = actionToolbar.component 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 e67f57a31..c6ed1d8d5 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykTreeCellRenderer.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/SnykTreeCellRenderer.kt @@ -38,6 +38,7 @@ 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 javax.swing.Icon import javax.swing.JTree @@ -69,7 +70,7 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() { } is FileTreeNode -> { val fileVulns = value.userObject as OssVulnerabilitiesForFile - nodeIcon = PackageManagerIconProvider.getIcon(fileVulns.packageManager.toLowerCase()) + nodeIcon = PackageManagerIconProvider.getIcon(fileVulns.packageManager.lowercase(Locale.getDefault())) text = fileVulns.sanitizedTargetFile + ProductType.OSS.getCountText(value.childCount) val snykCachedResults = getSnykCachedResults(value.project) @@ -106,7 +107,8 @@ class SnykTreeCellRenderer : ColoredTreeCellRenderer() { } is IacFileTreeNode -> { val iacVulnerabilitiesForFile = value.userObject as IacIssuesForFile - nodeIcon = PackageManagerIconProvider.getIcon(iacVulnerabilitiesForFile.packageManager.toLowerCase()) + nodeIcon = + PackageManagerIconProvider.getIcon(iacVulnerabilitiesForFile.packageManager.lowercase(Locale.getDefault())) text = iacVulnerabilitiesForFile.targetFile + ProductType.IAC.getCountText(value.childCount) val snykCachedResults = getSnykCachedResults(value.project) diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SnykAuthPanel.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SnykAuthPanel.kt index 47314cba7..0890bfb7d 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SnykAuthPanel.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SnykAuthPanel.kt @@ -3,6 +3,7 @@ package io.snyk.plugin.ui.toolwindow.panels import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.service +import com.intellij.openapi.progress.runBackgroundableTask import com.intellij.openapi.project.Project import com.intellij.uiDesigner.core.GridConstraints.ANCHOR_EAST import com.intellij.uiDesigner.core.GridConstraints.ANCHOR_NORTHWEST @@ -59,11 +60,15 @@ class SnykAuthPanel(val project: Project) : JPanel(), Disposable { service().addTrustedPath(Paths.get(it)) } - val userId = analytics.obtainUserId(token) - if (userId.isNotBlank()) { - analytics.setUserId(userId) - analytics.identify() - getAmplitudeExperimentService().fetch(ExperimentUser(userId)) + if (pluginSettings().usageAnalyticsEnabled) { + val userId = analytics.obtainUserId(token) + if (userId.isNotBlank()) { + runBackgroundableTask("Snyk: Fetching experiments", project, true) { + analytics.setUserId(userId) + analytics.identify() + getAmplitudeExperimentService().fetch(ExperimentUser(userId)) + } + } } getSyncPublisher(project, SnykSettingsListener.SNYK_SETTINGS_TOPIC)?.settingsChanged() } diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanel.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanel.kt index 090dd6cd5..398c5e660 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanel.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/SuggestionDescriptionPanel.kt @@ -180,14 +180,14 @@ class SuggestionDescriptionPanel( val maxFilenameLength = markers.asSequence() .filter { it.file.isNotEmpty() } .map { it.file.substringAfterLast('/', "").length } - .max() + .maxOrNull() ?: 0 val allStepPanels = mutableListOf() markers.forEachIndexed { index, markerRange -> val stepPanel = stepPanel( index = index, markerRange = markerRange, - maxFilenameLength = max(snykCodeFile.virtualFile.name.length, maxFilenameLength ?: 0), + maxFilenameLength = max(snykCodeFile.virtualFile.name.length, maxFilenameLength), allStepPanels = allStepPanels ) @@ -290,10 +290,7 @@ class SuggestionDescriptionPanel( panel.add(tabbedPanel, panelGridConstraints(2, indent = 1)) - val maxRowCount = fixes.take(examplesCount) - .map { it.lines.size } - .max() - ?: 0 + val maxRowCount = fixes.take(examplesCount).maxOfOrNull { it.lines.size } ?: 0 fixes.take(examplesCount).forEach { exampleCommitFix -> val shortURL = exampleCommitFix.commitURL .removePrefix("https://") diff --git a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/TreePanel.kt b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/TreePanel.kt index 731a239e8..4f4115106 100644 --- a/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/TreePanel.kt +++ b/src/main/kotlin/io/snyk/plugin/ui/toolwindow/panels/TreePanel.kt @@ -19,7 +19,9 @@ class TreePanel(tree: Tree) : SimpleToolWindowPanel(true, true) { name = "treePanel" val severityToolbarPanel = JPanel(BorderLayout()) severityToolbarPanel.add(JLabel(" Severity: "), BorderLayout.WEST) - severityToolbarPanel.add(getSeverityToolbar().component, BorderLayout.CENTER) + val severityToolbar = getSeverityToolbar() + severityToolbar.targetComponent = this + severityToolbarPanel.add(severityToolbar.component, BorderLayout.CENTER) val toolBarPanel = JPanel(BorderLayout()) toolBarPanel.add(severityToolbarPanel, BorderLayout.CENTER) diff --git a/src/main/kotlin/snyk/WelcomeNotifyActivity.kt b/src/main/kotlin/snyk/WelcomeNotifyActivity.kt index b583f7552..495d8d2ec 100644 --- a/src/main/kotlin/snyk/WelcomeNotifyActivity.kt +++ b/src/main/kotlin/snyk/WelcomeNotifyActivity.kt @@ -1,6 +1,7 @@ package snyk import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.ProjectActivity import com.intellij.openapi.startup.StartupActivity import io.snyk.plugin.pluginSettings import io.snyk.plugin.services.SnykApplicationSettingsStateService @@ -10,8 +11,8 @@ import io.snyk.plugin.ui.SnykBalloonNotifications * Shows a welcome notification once when Snyk plugin is installed and * [SnykApplicationSettingsStateService.pluginFirstRun] property is `true`. */ -class WelcomeNotifyActivity : StartupActivity { - override fun runActivity(project: Project) { +class WelcomeNotifyActivity : ProjectActivity { + override suspend fun execute(project: Project) { val settings = pluginSettings() if (settings.pluginFirstRun) { diff --git a/src/main/kotlin/snyk/advisor/api/AdvisorApiClient.kt b/src/main/kotlin/snyk/advisor/api/AdvisorApiClient.kt index 629383d5a..b6fb108cf 100644 --- a/src/main/kotlin/snyk/advisor/api/AdvisorApiClient.kt +++ b/src/main/kotlin/snyk/advisor/api/AdvisorApiClient.kt @@ -2,11 +2,14 @@ package snyk.advisor.api import com.intellij.openapi.diagnostic.logger import io.snyk.plugin.net.RetrofitClientFactory +import io.snyk.plugin.pluginSettings +import io.snyk.plugin.services.SnykApplicationSettingsStateService import retrofit2.Retrofit import snyk.advisor.AdvisorPackageManager -class AdvisorApiClient constructor( - private val baseUrl: String = "https://api.snyk.io/unstable/advisor/" +class AdvisorApiClient( + private val baseUrl: String = "https://api.snyk.io/unstable/advisor/", + private val settings: SnykApplicationSettingsStateService = pluginSettings() ) { private lateinit var retrofit: Retrofit private lateinit var scoreServiceEndpoint: ScoreService @@ -32,14 +35,14 @@ class AdvisorApiClient constructor( internal fun scoreService(): ScoreService { if (!::scoreServiceEndpoint.isInitialized) { - scoreServiceEndpoint = createRetrofitIfNeeded().create(ScoreService::class.java) + scoreServiceEndpoint = createRetrofitIfNeeded(settings).create(ScoreService::class.java) } return scoreServiceEndpoint } - private fun createRetrofitIfNeeded(): Retrofit { + private fun createRetrofitIfNeeded(settings: SnykApplicationSettingsStateService): Retrofit { if (!::retrofit.isInitialized) { - retrofit = RetrofitClientFactory.getInstance().createRetrofit(baseUrl) + retrofit = RetrofitClientFactory.getInstance().createRetrofit(baseUrl, settings = settings) } return retrofit } diff --git a/src/main/kotlin/snyk/amplitude/api/AmplitudeExperimentApiClient.kt b/src/main/kotlin/snyk/amplitude/api/AmplitudeExperimentApiClient.kt index 998c39bb2..4b1c0bcb4 100644 --- a/src/main/kotlin/snyk/amplitude/api/AmplitudeExperimentApiClient.kt +++ b/src/main/kotlin/snyk/amplitude/api/AmplitudeExperimentApiClient.kt @@ -71,7 +71,8 @@ class AmplitudeExperimentApiClient private constructor( baseUrl: String = "https://api.lab.amplitude.com/", apiKey: String ): AmplitudeExperimentApiClient { - return AmplitudeExperimentApiClient(baseUrl, apiKey) + val client = AmplitudeExperimentApiClient(baseUrl, apiKey) + return client } } diff --git a/src/main/kotlin/snyk/container/ContainerResult.kt b/src/main/kotlin/snyk/container/ContainerResult.kt index 4dcd9d199..0d5dbab55 100644 --- a/src/main/kotlin/snyk/container/ContainerResult.kt +++ b/src/main/kotlin/snyk/container/ContainerResult.kt @@ -11,10 +11,10 @@ class ContainerResult( var rescanNeeded: Boolean = false - override val issuesCount: Int? get() = allCliIssues?.sumBy { it.uniqueCount } + override val issuesCount: Int? get() = allCliIssues?.sumOf { it.uniqueCount } override fun countBySeverity(severity: Severity): Int? { - return allCliIssues?.sumBy { issuesForFile -> + return allCliIssues?.sumOf { issuesForFile -> issuesForFile.vulnerabilities .filter { it.getSeverity() == severity } .distinctBy { it.id } diff --git a/src/main/kotlin/snyk/container/ContainerService.kt b/src/main/kotlin/snyk/container/ContainerService.kt index b43763953..9804fff1c 100644 --- a/src/main/kotlin/snyk/container/ContainerService.kt +++ b/src/main/kotlin/snyk/container/ContainerService.kt @@ -74,7 +74,7 @@ class ContainerService(project: Project) : CliAdapter jenkins/jenkins/jenkins // jenkins/jenkins:lts -> jenkins/jenkins:lts/jenkins // so for `jenkins/jenkins:lts/jenkins` we should choose latter (longest match) - nameCandidates.size > 1 -> nameCandidates.maxBy { it.length }!! + nameCandidates.size > 1 -> nameCandidates.maxByOrNull { it.length }!! // fallback in case our sanitizing fail else -> cliReturnedImageName } diff --git a/src/main/kotlin/snyk/container/KubernetesImageCache.kt b/src/main/kotlin/snyk/container/KubernetesImageCache.kt index 4951598e1..fbf4dec6b 100644 --- a/src/main/kotlin/snyk/container/KubernetesImageCache.kt +++ b/src/main/kotlin/snyk/container/KubernetesImageCache.kt @@ -1,12 +1,16 @@ package snyk.container +import com.intellij.openapi.application.ReadAction import com.intellij.openapi.components.Service import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.progress.runBackgroundableTask -import com.intellij.openapi.project.DumbService +import com.intellij.openapi.progress.PerformInBackgroundOption +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.RunnableCallable +import com.intellij.util.concurrency.NonUrgentExecutor import com.jetbrains.rd.util.concurrentMapOf @Service @@ -19,15 +23,23 @@ class KubernetesImageCache(val project: Project) { } fun scanProjectForKubernetesFiles() { - val title = "Snyk: Scanning For Kubernetes Files..." - runBackgroundableTask(title, project, true) { progress -> - DumbService.getInstance(project).runReadActionInSmartMode { - ProjectRootManager.getInstance(project).fileIndex.iterateContent { virtualFile -> - this.extractFromFile(virtualFile) - !progress.isCanceled - } + val callable = RunnableCallable { + ProjectRootManager.getInstance(project).fileIndex.iterateContent { virtualFile -> + extractFromFileAndAddToCache(virtualFile) + true } } + object : Task.Backgroundable( + project, + "Scanning project for Kubernetes files", + true, + PerformInBackgroundOption.ALWAYS_BACKGROUND + ) { + override fun run(indicator: ProgressIndicator) { + ReadAction.nonBlocking(callable).wrapProgress(indicator).submit(NonUrgentExecutor.getInstance()) + } + }.queue() + } fun getKubernetesWorkloadFilesFromCache(): Set = images.keys @@ -46,12 +58,12 @@ class KubernetesImageCache(val project: Project) { fun updateCache(files: Set) { files.forEach { file -> - extractFromFile(file) + extractFromFileAndAddToCache(file) } } /** public for Tests only */ - fun extractFromFile(file: VirtualFile) { + fun extractFromFileAndAddToCache(file: VirtualFile) { val extractFromFile = YAMLImageExtractor.extractFromFile(file, project) if (extractFromFile.isNotEmpty()) { logger.debug("${if (images.contains(file)) "updated" else "added"} $file in cache") diff --git a/src/main/kotlin/snyk/container/annotator/ContainerYamlAnnotator.kt b/src/main/kotlin/snyk/container/annotator/ContainerYamlAnnotator.kt index 4e7655654..338a14ecc 100644 --- a/src/main/kotlin/snyk/container/annotator/ContainerYamlAnnotator.kt +++ b/src/main/kotlin/snyk/container/annotator/ContainerYamlAnnotator.kt @@ -42,7 +42,7 @@ class ContainerYamlAnnotator : ExternalAnnotator() { .forEach { forImage -> val severityToShow = forImage.getSeverities() .filter { AnnotatorCommon.isSeverityToShow(it) } - .max() ?: Severity.UNKNOWN + .maxOrNull() ?: Severity.UNKNOWN val annotationMessage = annotationMessage(forImage) forImage.workloadImages .filter { it.virtualFile == psiFile.virtualFile } diff --git a/src/main/kotlin/snyk/container/ui/BaseImageRemediationDetailPanel.kt b/src/main/kotlin/snyk/container/ui/BaseImageRemediationDetailPanel.kt index f24d26ce6..87ab9a729 100644 --- a/src/main/kotlin/snyk/container/ui/BaseImageRemediationDetailPanel.kt +++ b/src/main/kotlin/snyk/container/ui/BaseImageRemediationDetailPanel.kt @@ -30,7 +30,7 @@ class BaseImageRemediationDetailPanel( private val imageIssues: ContainerIssuesForImage ) : IssueDescriptionPanelBase( title = imageIssues.imageName, - severity = imageIssues.getSeverities().max() ?: Severity.UNKNOWN + severity = imageIssues.getSeverities().maxOrNull() ?: Severity.UNKNOWN ) { private val targetImages: List = getKubernetesImageCache(project) diff --git a/src/main/kotlin/snyk/errorHandler/SentryErrorReporter.kt b/src/main/kotlin/snyk/errorHandler/SentryErrorReporter.kt index 9b1d2e999..40ea0dbac 100644 --- a/src/main/kotlin/snyk/errorHandler/SentryErrorReporter.kt +++ b/src/main/kotlin/snyk/errorHandler/SentryErrorReporter.kt @@ -59,7 +59,7 @@ object SentryErrorReporter { fun submitErrorReport( error: Throwable, - consumer: Consumer, + consumer: Consumer, attachments: List = emptyList(), description: String = "" ) { diff --git a/src/main/kotlin/snyk/errorHandler/SnykErrorReportSubmitter.kt b/src/main/kotlin/snyk/errorHandler/SnykErrorReportSubmitter.kt index d884fc1dc..8f9672eb8 100644 --- a/src/main/kotlin/snyk/errorHandler/SnykErrorReportSubmitter.kt +++ b/src/main/kotlin/snyk/errorHandler/SnykErrorReportSubmitter.kt @@ -18,7 +18,7 @@ class SnykErrorReportSubmitter : ErrorReportSubmitter() { events: Array, additionalInfo: String?, parentComponent: Component, - consumer: Consumer + consumer: Consumer ): Boolean { for (event in events) { var throwable = event.throwable diff --git a/src/main/kotlin/snyk/iac/IacResult.kt b/src/main/kotlin/snyk/iac/IacResult.kt index ce80b69b4..3f4ef49a6 100644 --- a/src/main/kotlin/snyk/iac/IacResult.kt +++ b/src/main/kotlin/snyk/iac/IacResult.kt @@ -22,10 +22,10 @@ class IacResult( var iacScanNeeded: Boolean = false - override val issuesCount get() = allCliIssues?.sumBy { it.uniqueCount } + override val issuesCount get() = allCliIssues?.sumOf { it.uniqueCount } override fun countBySeverity(severity: Severity): Int? { - return allCliIssues?.sumBy { issuesForFile -> + return allCliIssues?.sumOf { issuesForFile -> issuesForFile.infrastructureAsCodeIssues .filter { it.getSeverity() == severity } .distinctBy { it.id } diff --git a/src/main/kotlin/snyk/iac/annotator/IacHclAnnotator.kt b/src/main/kotlin/snyk/iac/annotator/IacHclAnnotator.kt index 314eadf02..e0eee8879 100644 --- a/src/main/kotlin/snyk/iac/annotator/IacHclAnnotator.kt +++ b/src/main/kotlin/snyk/iac/annotator/IacHclAnnotator.kt @@ -7,9 +7,9 @@ import com.intellij.psi.PsiFile import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.psi.util.elementType -import org.intellij.plugins.hcl.psi.HCLBlock -import org.intellij.plugins.hcl.psi.HCLElement -import org.intellij.plugins.hcl.psi.HCLProperty +import org.intellij.terraform.hcl.psi.HCLBlock +import org.intellij.terraform.hcl.psi.HCLElement +import org.intellij.terraform.hcl.psi.HCLProperty import snyk.iac.IacIssue private val LOG = logger() diff --git a/src/main/kotlin/snyk/oss/OssResult.kt b/src/main/kotlin/snyk/oss/OssResult.kt index 944fa0ff1..15fcd4c54 100644 --- a/src/main/kotlin/snyk/oss/OssResult.kt +++ b/src/main/kotlin/snyk/oss/OssResult.kt @@ -9,10 +9,10 @@ class OssResult( errors: List = emptyList() ) : CliResult(allOssVulnerabilities, errors) { - override val issuesCount = allOssVulnerabilities?.sumBy { it.uniqueCount } + override val issuesCount = allOssVulnerabilities?.sumOf { it.uniqueCount } override fun countBySeverity(severity: Severity): Int? { - return allCliIssues?.sumBy { vulnerabilitiesForFile -> + return allCliIssues?.sumOf { vulnerabilitiesForFile -> vulnerabilitiesForFile.vulnerabilities .filter { it.getSeverity() == severity } .distinctBy { it.id } diff --git a/src/main/kotlin/snyk/oss/annotator/AnnotatorHelper.kt b/src/main/kotlin/snyk/oss/annotator/AnnotatorHelper.kt index 9e70fdd47..00c01bfcc 100644 --- a/src/main/kotlin/snyk/oss/annotator/AnnotatorHelper.kt +++ b/src/main/kotlin/snyk/oss/annotator/AnnotatorHelper.kt @@ -1,5 +1,7 @@ package snyk.oss.annotator +import java.util.Locale + object AnnotatorHelper { fun isFileSupported(filePath: String): Boolean = @@ -28,5 +30,5 @@ object AnnotatorHelper { "poetry.lock", "mix.exs", "mix.lock" - ).any { filePath.toLowerCase().endsWith(it) } + ).any { filePath.lowercase(Locale.getDefault()).endsWith(it) } } diff --git a/src/main/kotlin/snyk/trust/TrustedProjects.kt b/src/main/kotlin/snyk/trust/TrustedProjects.kt index 2c548c61f..f23c6795b 100644 --- a/src/main/kotlin/snyk/trust/TrustedProjects.kt +++ b/src/main/kotlin/snyk/trust/TrustedProjects.kt @@ -4,6 +4,7 @@ package snyk.trust import com.intellij.openapi.application.invokeAndWaitIfNeeded import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project import com.intellij.openapi.ui.MessageDialogBuilder import com.intellij.openapi.ui.Messages import snyk.SnykBundle @@ -18,14 +19,14 @@ private val LOG = Logger.getInstance("snyk.trust.TrustedProjects") * * @return `false` if the user chose not to scan the project at all; `true` otherwise */ -fun confirmScanningAndSetWorkspaceTrustedStateIfNeeded(projectFileOrDir: Path): Boolean { +fun confirmScanningAndSetWorkspaceTrustedStateIfNeeded(project: Project, projectFileOrDir: Path): Boolean { val projectDir = if (Files.isDirectory(projectFileOrDir)) projectFileOrDir else projectFileOrDir.parent val trustService = service() val trustedState = trustService.isPathTrusted(projectDir) if (!trustedState) { LOG.info("Asking user to trust the project ${projectDir.fileName}") - return when (confirmScanningUntrustedProject(projectDir)) { + return when (confirmScanningUntrustedProject(project, projectDir)) { ScanUntrustedProjectChoice.TRUST_AND_SCAN -> { trustService.addTrustedPath(projectDir) true @@ -38,7 +39,7 @@ fun confirmScanningAndSetWorkspaceTrustedStateIfNeeded(projectFileOrDir: Path): return true } -private fun confirmScanningUntrustedProject(projectDir: Path): ScanUntrustedProjectChoice { +private fun confirmScanningUntrustedProject(project: Project, projectDir: Path): ScanUntrustedProjectChoice { val fileName = projectDir.fileName ?: projectDir.toString() val title = SnykBundle.message("snyk.trust.dialog.warning.title", fileName) val message = SnykBundle.message("snyk.trust.dialog.warning.text") @@ -53,9 +54,9 @@ private fun confirmScanningUntrustedProject(projectDir: Path): ScanUntrustedProj .icon(Messages.getWarningIcon()) .yesText(trustButton) .noText(distrustButton) - .show() + .ask(project) - choice = if (result == Messages.YES) { + choice = if (result) { LOG.info("User trusts the project $fileName for scans") ScanUntrustedProjectChoice.TRUST_AND_SCAN } else { diff --git a/src/main/kotlin/snyk/whoami/WhoamiResult.kt b/src/main/kotlin/snyk/whoami/WhoamiResult.kt index 7c73ca140..d01550e31 100644 --- a/src/main/kotlin/snyk/whoami/WhoamiResult.kt +++ b/src/main/kotlin/snyk/whoami/WhoamiResult.kt @@ -9,7 +9,7 @@ class WhoamiResult( errors: List = emptyList() ) : CliResult(allWhoamiOutputs, errors) { - override val issuesCount = allWhoamiOutputs?.sumBy { it.uniqueCount } + override val issuesCount = allWhoamiOutputs?.sumOf { it.uniqueCount } override fun countBySeverity(severity: Severity): Int { return 0 diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 0aac7015c..a030417dd 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -6,8 +6,8 @@ com.intellij.modules.lang com.intellij.modules.platform - com.intellij.modules.php-capable - com.intellij.modules.go + com.jetbrains.php + org.jetbrains.plugins.go org.jetbrains.kotlin com.intellij.java com.intellij.modules.rider @@ -50,6 +50,8 @@ description="Preview: Enable new refactored issues tree." restartRequired="true"/> + + diff --git a/src/main/resources/icons/code.svg b/src/main/resources/icons/code.svg index 00e339570..591681080 100644 --- a/src/main/resources/icons/code.svg +++ b/src/main/resources/icons/code.svg @@ -1,5 +1,20 @@ - - - + + + + + + + + + + + + diff --git a/src/main/resources/icons/code_dark.svg b/src/main/resources/icons/code_dark.svg deleted file mode 100644 index 165864e4e..000000000 --- a/src/main/resources/icons/code_dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/main/resources/icons/code_disabled.svg b/src/main/resources/icons/code_disabled.svg index c00628fa3..6a12ab798 100644 --- a/src/main/resources/icons/code_disabled.svg +++ b/src/main/resources/icons/code_disabled.svg @@ -1,7 +1,22 @@ - - - - + + + + + + + + + + + + + diff --git a/src/main/resources/icons/code_disabled_dark.svg b/src/main/resources/icons/code_disabled_dark.svg deleted file mode 100644 index 7b356df65..000000000 --- a/src/main/resources/icons/code_disabled_dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/main/resources/icons/container.svg b/src/main/resources/icons/container.svg index 28ba04c06..e276de5b9 100644 --- a/src/main/resources/icons/container.svg +++ b/src/main/resources/icons/container.svg @@ -1,5 +1,20 @@ - - - + + + + + + + + + + + + diff --git a/src/main/resources/icons/container_dark.svg b/src/main/resources/icons/container_dark.svg deleted file mode 100644 index 14e5ecf48..000000000 --- a/src/main/resources/icons/container_dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/main/resources/icons/container_disabled.svg b/src/main/resources/icons/container_disabled.svg index ee0678651..f5d947835 100644 --- a/src/main/resources/icons/container_disabled.svg +++ b/src/main/resources/icons/container_disabled.svg @@ -1,7 +1,22 @@ - - - - + + + + + + + + + + + + + diff --git a/src/main/resources/icons/container_disabled_dark.svg b/src/main/resources/icons/container_disabled_dark.svg deleted file mode 100644 index 0fe3e6d48..000000000 --- a/src/main/resources/icons/container_disabled_dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/main/resources/icons/iac.svg b/src/main/resources/icons/iac.svg index b27f3a5e6..8a7e1c284 100644 --- a/src/main/resources/icons/iac.svg +++ b/src/main/resources/icons/iac.svg @@ -1,6 +1,19 @@ - - - - + + + + + + + + + + + + diff --git a/src/main/resources/icons/iac_dark.svg b/src/main/resources/icons/iac_dark.svg deleted file mode 100644 index e44456566..000000000 --- a/src/main/resources/icons/iac_dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/main/resources/icons/iac_disabled.svg b/src/main/resources/icons/iac_disabled.svg index eb2f18cae..eb0bf78d1 100644 --- a/src/main/resources/icons/iac_disabled.svg +++ b/src/main/resources/icons/iac_disabled.svg @@ -1,8 +1,22 @@ - - - - - + + + + + + + + + + + + + diff --git a/src/main/resources/icons/iac_disabled_dark.svg b/src/main/resources/icons/iac_disabled_dark.svg deleted file mode 100644 index 667dcf457..000000000 --- a/src/main/resources/icons/iac_disabled_dark.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/icons/oss.svg b/src/main/resources/icons/oss.svg index 417b8e622..f6a8adc0d 100644 --- a/src/main/resources/icons/oss.svg +++ b/src/main/resources/icons/oss.svg @@ -1,5 +1,20 @@ - - - + + + + + + + + + + + + diff --git a/src/main/resources/icons/oss_dark.svg b/src/main/resources/icons/oss_dark.svg deleted file mode 100644 index 5bc1c4833..000000000 --- a/src/main/resources/icons/oss_dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/main/resources/icons/oss_disabled.svg b/src/main/resources/icons/oss_disabled.svg index a7861e61f..68bdb0416 100644 --- a/src/main/resources/icons/oss_disabled.svg +++ b/src/main/resources/icons/oss_disabled.svg @@ -1,7 +1,22 @@ - - - - + + + + + + + + + + + + + diff --git a/src/main/resources/icons/oss_disabled_dark.svg b/src/main/resources/icons/oss_disabled_dark.svg deleted file mode 100644 index e971fd554..000000000 --- a/src/main/resources/icons/oss_disabled_dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/test/java/ai/deepcode/javaclient/core/AnalysisDataTest.java b/src/test/java/ai/deepcode/javaclient/core/AnalysisDataTest.java index 2c453bd3a..56623314b 100644 --- a/src/test/java/ai/deepcode/javaclient/core/AnalysisDataTest.java +++ b/src/test/java/ai/deepcode/javaclient/core/AnalysisDataTest.java @@ -17,11 +17,10 @@ import java.io.File; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.Queue; @@ -71,9 +70,6 @@ private static class RestApiMockWithBrokenFileUpload extends DeepCodeRestApiMock protected final CreateBundleResponse bundleResponseWithMissedFile = new CreateBundleResponse("bundleHash", Collections.singletonList("/filePath")); - protected final CreateBundleResponse bundleResponseWithoutMissedFile = - new CreateBundleResponse("bundleHash", Collections.emptyList()); - @Override public @NotNull CreateBundleResponse createBundle(String orgName, FileHashRequest files) { return bundleResponseWithMissedFile; @@ -186,7 +182,7 @@ public void reupload_files_if_initial_upload_does_not_succeed() throws IOExcepti File file = File.createTempFile("analysisDataTest", "tmp"); file.deleteOnExit(); - Files.write(file.toPath(), "testtestest".getBytes(StandardCharsets.UTF_8)); + Files.writeString(file.toPath(), "testtestest"); analysisData.updateCachedResultsForFiles(project, Collections.singleton(file), progress); @@ -237,7 +233,7 @@ public void getAnalysis_recover_during_polling_if_operation_sometimes_does_not_s nonCompleteAnalysisResponse.setStatusCode(200); nonCompleteAnalysisResponse.setStatusDescription("Fake non-complete getAnalysis"); - final Queue responses = new LinkedList<>(Arrays.asList( + final Queue responses = new ArrayDeque<>(Arrays.asList( nonCompleteAnalysisResponse, nonCompleteAnalysisResponse, failed404AnalysisResponse, diff --git a/src/test/java/ai/deepcode/javaclient/core/DeepCodeIgnoreInfoHolderTest.java b/src/test/java/ai/deepcode/javaclient/core/DeepCodeIgnoreInfoHolderTest.java index db72812a7..6ce8547fc 100644 --- a/src/test/java/ai/deepcode/javaclient/core/DeepCodeIgnoreInfoHolderTest.java +++ b/src/test/java/ai/deepcode/javaclient/core/DeepCodeIgnoreInfoHolderTest.java @@ -15,12 +15,10 @@ public class DeepCodeIgnoreInfoHolderTest { - private final File basicIgnoreFile = - new File(getClass().getClassLoader().getResource("basic/.dcignore").getPath()); + private final File basicIgnoreFile = new File("src/test/resources/basic/.dcignore"); private final File basicProject = basicIgnoreFile.getParentFile(); - private final File fullDcignoreFile = - new File(getClass().getClassLoader().getResource("full/.dcignore").getPath()); + private final File fullDcignoreFile = new File("src/test/resources/full/.dcignore"); private final File fullDcignoreProject = fullDcignoreFile.getParentFile(); @Test diff --git a/src/test/kotlin/io/snyk/plugin/TestUtils.kt b/src/test/kotlin/io/snyk/plugin/TestUtils.kt index 9d0bcd57d..107a96942 100644 --- a/src/test/kotlin/io/snyk/plugin/TestUtils.kt +++ b/src/test/kotlin/io/snyk/plugin/TestUtils.kt @@ -1,17 +1,57 @@ package io.snyk.plugin +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.testFramework.replaceService import com.intellij.util.io.RequestBuilder import io.mockk.every import io.mockk.justRun import io.mockk.mockk import io.mockk.mockkObject +import io.snyk.plugin.services.SnykApplicationSettingsStateService +import io.snyk.plugin.services.SnykProjectSettingsStateService import io.snyk.plugin.services.download.CliDownloader import io.snyk.plugin.services.download.HttpRequestHelper +import java.io.File +import java.nio.file.Path + +fun setupDummyCliFile() { + val cliFile = getCliFile() + + if (!cliFile.exists()) { + if (!cliFile.parentFile.exists()) cliFile.mkdirs() + cliFile.createNewFile() + } +} + +fun removeDummyCliFile() { + val cliFile = getCliFile() + if (cliFile.exists()) { + cliFile.delete() + } +} + +fun resetSettings(project: Project?) { + val application = ApplicationManager.getApplication() + application.replaceService( + SnykApplicationSettingsStateService::class.java, + SnykApplicationSettingsStateService(), + application + ) + project?.replaceService( + SnykProjectSettingsStateService::class.java, + SnykProjectSettingsStateService(), + project + ) +} /** low level avoiding download the CLI file */ -fun mockCliDownload() { +fun mockCliDownload(): RequestBuilder { val requestBuilderMockk = mockk(relaxed = true) - justRun { requestBuilderMockk.saveToFile(any(), any()) } + justRun { requestBuilderMockk.saveToFile(any(), any()) } + justRun { requestBuilderMockk.saveToFile(any(), any()) } mockkObject(HttpRequestHelper) every { HttpRequestHelper.createRequest(CliDownloader.LATEST_RELEASE_DOWNLOAD_URL) } returns requestBuilderMockk + every { HttpRequestHelper.createRequest(CliDownloader.LATEST_RELEASES_URL) } returns requestBuilderMockk + return requestBuilderMockk } diff --git a/src/integTest/kotlin/io/snyk/plugin/cli/ConsoleCommandRunnerTest.kt b/src/test/kotlin/io/snyk/plugin/cli/ConsoleCommandRunnerTest.kt similarity index 97% rename from src/integTest/kotlin/io/snyk/plugin/cli/ConsoleCommandRunnerTest.kt rename to src/test/kotlin/io/snyk/plugin/cli/ConsoleCommandRunnerTest.kt index 86e5d4319..e415c146b 100644 --- a/src/integTest/kotlin/io/snyk/plugin/cli/ConsoleCommandRunnerTest.kt +++ b/src/test/kotlin/io/snyk/plugin/cli/ConsoleCommandRunnerTest.kt @@ -28,15 +28,13 @@ import io.snyk.plugin.removeDummyCliFile import io.snyk.plugin.resetSettings import io.snyk.plugin.services.download.SnykCliDownloaderService import io.snyk.plugin.setupDummyCliFile -import org.junit.After -import org.junit.Before -import org.junit.Test import snyk.PLUGIN_ID import snyk.common.isOauth import snyk.errorHandler.SentryErrorReporter import snyk.oss.OssService import java.net.URI import java.net.URLEncoder +import java.util.Locale import java.util.UUID import java.util.concurrent.TimeUnit @@ -45,7 +43,6 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { private val ossService: OssService get() = getOssService(project) ?: throw IllegalStateException("OSS service should be available") - @Before override fun setUp() { super.setUp() unmockkAll() @@ -56,7 +53,6 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { every { SentryErrorReporter.captureException(any()) } returns SentryId() } - @After override fun tearDown() { unmockkAll() resetSettings(project) @@ -64,7 +60,6 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { super.tearDown() } - @Test fun testSetupCliEnvironmentVariablesWithCustomEndpoint() { val oldEndpoint = pluginSettings().customEndpointUrl try { @@ -80,7 +75,6 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { } } - @Test fun testSetupCliEnvironmentVariablesWithOAuthEndpoint() { val oldEndpoint = pluginSettings().customEndpointUrl try { @@ -103,7 +97,6 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { } } - @Test fun testSetupCliEnvironmentVariablesWithNonOAuthEndpoint() { val oldEndpoint = pluginSettings().customEndpointUrl try { @@ -126,7 +119,6 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { } } - @Test fun testSetupCliEnvironmentVariablesWithCustomEndpointNoTrailingSlash() { val oldEndpoint = pluginSettings().customEndpointUrl try { @@ -142,7 +134,6 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { } } - @Test fun testSetupCliEnvironmentVariablesWithDisabledUsageAnalytics() { val originalUsageAnalyticsEnabled = pluginSettings().usageAnalyticsEnabled try { @@ -156,7 +147,6 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { } } - @Test fun testSetupCliEnvironmentVariablesWithProxyWithoutAuth() { val httpConfigurable = HttpConfigurable.getInstance() val originalProxyHost = httpConfigurable.PROXY_HOST @@ -180,7 +170,6 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { } } - @Test fun testSetupCliEnvironmentVariablesWithProxyWithAuth() { val httpConfigurable = HttpConfigurable.getInstance() val originalProxyHost = httpConfigurable.PROXY_HOST @@ -201,7 +190,8 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { val generalCommandLine = GeneralCommandLine("") val expectedProxy = - "http://${encodedLogin}:${encodedPassword}@${httpConfigurable.PROXY_HOST}:${httpConfigurable.PROXY_PORT}" + "http://${encodedLogin}:${encodedPassword}@" + + "${httpConfigurable.PROXY_HOST}:${httpConfigurable.PROXY_PORT}" ConsoleCommandRunner().setupCliEnvironmentVariables(generalCommandLine, "") @@ -216,7 +206,6 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { } } - @Test fun testSetupCliEnvironmentVariablesWithProxyWithCancelledAuth() { val httpConfigurable = HttpConfigurable.getInstance() val originalProxyHost = httpConfigurable.PROXY_HOST @@ -251,7 +240,6 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { } } - @Test fun testSetupCliEnvironmentVariables() { val generalCommandLine = GeneralCommandLine("") val snykPluginVersion = PluginManagerCore.getPlugin(PluginId.getId(PLUGIN_ID))?.version ?: "UNKNOWN" @@ -262,11 +250,10 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { assertEquals("JETBRAINS_IDE", generalCommandLine.environment["SNYK_INTEGRATION_NAME"]) assertEquals(snykPluginVersion, generalCommandLine.environment["SNYK_INTEGRATION_VERSION"]) assertEquals("INTELLIJ IDEA IC", generalCommandLine.environment["SNYK_INTEGRATION_ENVIRONMENT"]) - assertEquals("2020.2", generalCommandLine.environment["SNYK_INTEGRATION_ENVIRONMENT_VERSION"]) + assertEquals("2023.1", generalCommandLine.environment["SNYK_INTEGRATION_ENVIRONMENT_VERSION"]) } @Suppress("SwallowedException") - @Test fun testCommandExecutionRequestWhileCliIsDownloading() { val cliFile = getCliFile() cliFile.delete() @@ -325,14 +312,13 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() { verify(exactly = 0) { SentryErrorReporter.captureException(any()) } } - @Test fun testErrorReportedWhenExecutionTimeoutExpire() { val registryValue = Registry.get("snyk.timeout.results.waiting") val defaultValue = registryValue.asInteger() assertEquals(DEFAULT_TIMEOUT_FOR_SCAN_WAITING_MS, defaultValue) registryValue.setValue(100) val seconds = "3" - val sleepCommand = if (System.getProperty("os.name").toLowerCase().contains("win")) { + val sleepCommand = if (System.getProperty("os.name").lowercase(Locale.getDefault()).contains("win")) { // see https://www.ibm.com/support/pages/timeout-command-run-batch-job-exits-immediately-and-returns-error-input-redirection-not-supported-exiting-process-immediately listOf("ping", "-n", seconds, "localhost") } else { diff --git a/src/integTest/kotlin/io/snyk/plugin/snykcode/core/IgnoreInfoHolderPlatformTestCase.kt b/src/test/kotlin/io/snyk/plugin/core/IgnoreInfoHolderPlatformTestCase.kt similarity index 82% rename from src/integTest/kotlin/io/snyk/plugin/snykcode/core/IgnoreInfoHolderPlatformTestCase.kt rename to src/test/kotlin/io/snyk/plugin/core/IgnoreInfoHolderPlatformTestCase.kt index 3e622eee2..9dab77fdc 100644 --- a/src/integTest/kotlin/io/snyk/plugin/snykcode/core/IgnoreInfoHolderPlatformTestCase.kt +++ b/src/test/kotlin/io/snyk/plugin/core/IgnoreInfoHolderPlatformTestCase.kt @@ -1,16 +1,24 @@ -package io.snyk.plugin.snykcode.core +package io.snyk.plugin.core import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ModuleRootModificationUtil +import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.testFramework.HeavyPlatformTestCase import com.intellij.testFramework.PlatformTestUtil import io.snyk.plugin.getSnykCode import io.snyk.plugin.resetSettings +import io.snyk.plugin.snykcode.core.SnykCodeFile +import io.snyk.plugin.snykcode.core.SnykCodeIgnoreInfoHolder +import io.snyk.plugin.snykcode.core.SnykCodeUtils +import org.awaitility.Awaitility.await import java.io.File import java.io.FileNotFoundException +import java.io.IOException +import java.util.concurrent.TimeUnit // `Heavy` tests should be used due to Project partial re-usage/erasure: // .dcignore is deleted between calls but Project is not reopened in BasePlatformTestCase @@ -20,6 +28,9 @@ class IgnoreInfoHolderPlatformTestCase : HeavyPlatformTestCase() { override fun setUp() { super.setUp() resetSettings(project) + if (!FileUtil.exists(project.basePath)) { + project.basePath?.let { File(it) }?.let { FileUtil.createDirectory(it) } + } ModuleRootModificationUtil.addContentRoot(module, project.basePath!!) } @@ -55,6 +66,7 @@ class IgnoreInfoHolderPlatformTestCase : HeavyPlatformTestCase() { private fun refreshVirtualFileSystem() { ApplicationManager.getApplication().runWriteAction { + FileDocumentManager.getInstance().saveAllDocuments() VirtualFileManager.getInstance().syncRefresh() } } @@ -83,10 +95,11 @@ class IgnoreInfoHolderPlatformTestCase : HeavyPlatformTestCase() { File(dcignoreFilePath).writeText("2.js") // trigger BulkFileListener to proceed .dcignore file change refreshVirtualFileSystem() - assertTrue( - "File $filePathToCheck should be excluded(ignored) by updated .dcignore", + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() + + await().atMost(5, TimeUnit.SECONDS).until { SnykCodeIgnoreInfoHolder.instance.isIgnoredFile(file) - ) + } } fun testIgnoreCacheUpdateOnProjectClose() { @@ -103,8 +116,9 @@ class IgnoreInfoHolderPlatformTestCase : HeavyPlatformTestCase() { private fun setUpTest(): SnykCodeFile { val filePathToCheck = project.basePath + "/node_modules/1.js" - File(filePathToCheck).parentFile.mkdir() - File(filePathToCheck).createNewFile() + File(filePathToCheck).parentFile.mkdirs() + && File(filePathToCheck).createNewFile() + || throw IOException("Failed to create file $filePathToCheck") val fileToCheck = findFile(project, filePathToCheck) initiateAllMissedIgnoreFilesRescan() diff --git a/src/integTest/kotlin/io/snyk/plugin/snykcode/core/PDUTest.kt b/src/test/kotlin/io/snyk/plugin/core/PDUTest.kt similarity index 91% rename from src/integTest/kotlin/io/snyk/plugin/snykcode/core/PDUTest.kt rename to src/test/kotlin/io/snyk/plugin/core/PDUTest.kt index 6122dce09..4fe7f4938 100644 --- a/src/integTest/kotlin/io/snyk/plugin/snykcode/core/PDUTest.kt +++ b/src/test/kotlin/io/snyk/plugin/core/PDUTest.kt @@ -1,9 +1,11 @@ -package io.snyk.plugin.snykcode.core +package io.snyk.plugin.core import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.testFramework.LightPlatformTestCase import com.intellij.testFramework.PlatformTestUtil +import io.snyk.plugin.snykcode.core.PDU +import io.snyk.plugin.snykcode.core.SnykCodeFile import java.io.File @Suppress("FunctionName") diff --git a/src/test/kotlin/io/snyk/plugin/net/SnykApiClientTest.kt b/src/test/kotlin/io/snyk/plugin/net/SnykApiClientTest.kt index d4d37c020..5806780a7 100644 --- a/src/test/kotlin/io/snyk/plugin/net/SnykApiClientTest.kt +++ b/src/test/kotlin/io/snyk/plugin/net/SnykApiClientTest.kt @@ -4,21 +4,19 @@ import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertTrue import okhttp3.mockwebserver.MockWebServer import org.junit.Before -import org.junit.Rule import org.junit.Test import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import snyk.net.HttpClient class SnykApiClientTest { - @JvmField - @Rule - val server: MockWebServer = MockWebServer() + private lateinit var server: MockWebServer private lateinit var apiClientUnderTest: SnykApiClient @Before fun setUp() { + server = MockWebServer() val httpClient = HttpClient().apply { connectTimeout = 1 readTimeout = 1 diff --git a/src/integTest/kotlin/io/snyk/plugin/services/CliAdapterTest.kt b/src/test/kotlin/io/snyk/plugin/services/CliAdapterTest.kt similarity index 97% rename from src/integTest/kotlin/io/snyk/plugin/services/CliAdapterTest.kt rename to src/test/kotlin/io/snyk/plugin/services/CliAdapterTest.kt index 73142ebe3..d519349b2 100644 --- a/src/integTest/kotlin/io/snyk/plugin/services/CliAdapterTest.kt +++ b/src/test/kotlin/io/snyk/plugin/services/CliAdapterTest.kt @@ -7,7 +7,6 @@ import io.snyk.plugin.pluginSettings import io.snyk.plugin.removeDummyCliFile import io.snyk.plugin.resetSettings import io.snyk.plugin.setupDummyCliFile -import org.junit.Test import snyk.common.SnykError class CliAdapterTest : LightPlatformTestCase() { @@ -38,7 +37,6 @@ class CliAdapterTest : LightPlatformTestCase() { super.tearDown() } - @Test fun testBuildCliCommandsListWithInsecureParameter() { setupDummyCliFile() @@ -49,7 +47,6 @@ class CliAdapterTest : LightPlatformTestCase() { assertTrue(defaultCommands.contains("--insecure")) } - @Test fun testBuildCliCommandsListWithOrganizationParameter() { setupDummyCliFile() diff --git a/src/integTest/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceHeavyTest.kt b/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceHeavyTest.kt similarity index 94% rename from src/integTest/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceHeavyTest.kt rename to src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceHeavyTest.kt index 439460eb1..8b23ccfa1 100644 --- a/src/integTest/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceHeavyTest.kt +++ b/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceHeavyTest.kt @@ -5,9 +5,7 @@ import com.intellij.testFramework.PlatformTestUtil import io.mockk.unmockkAll import io.snyk.plugin.getSnykTaskQueueService import io.snyk.plugin.resetSettings -import org.junit.Test -@Suppress("FunctionName") class SnykTaskQueueServiceHeavyTest : HeavyPlatformTestCase() { override fun setUp() { @@ -22,7 +20,6 @@ class SnykTaskQueueServiceHeavyTest : HeavyPlatformTestCase() { super.tearDown() } - @Test fun `test service request for disposed project`() { PlatformTestUtil.forceCloseProjectWithoutSaving(project) diff --git a/src/integTest/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt b/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt similarity index 92% rename from src/integTest/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt rename to src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt index df0f86e3e..60902bed4 100644 --- a/src/integTest/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt +++ b/src/test/kotlin/io/snyk/plugin/services/SnykTaskQueueServiceTest.kt @@ -32,7 +32,6 @@ import io.snyk.plugin.setupDummyCliFile import org.awaitility.Awaitility.await import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.MatcherAssert.assertThat -import org.junit.Test import snyk.container.ContainerResult import snyk.iac.IacResult import snyk.oss.OssResult @@ -60,7 +59,7 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { "testTag" ) every { getSnykCliDownloaderService() } returns downloaderServiceMock - every { confirmScanningAndSetWorkspaceTrustedStateIfNeeded(any()) } returns true + every { confirmScanningAndSetWorkspaceTrustedStateIfNeeded(any(), any()) } returns true } private fun mockSnykApiServiceSastEnabled() { @@ -84,24 +83,23 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { super.tearDown() } - @Test fun testSnykTaskQueueService() { setupDummyCliFile() val snykTaskQueueService = project.service() snykTaskQueueService.scan() + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertTrue(snykTaskQueueService.getTaskQueue().isEmpty) snykTaskQueueService.downloadLatestRelease() + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertTrue(snykTaskQueueService.getTaskQueue().isEmpty) - assertNull(snykTaskQueueService.ossScanProgressIndicator) } - @Test fun testCliDownloadBeforeScanIfNeeded() { val cliFile = getCliFile() val downloaderMock = setupMockForDownloadTest() @@ -111,13 +109,13 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { val snykTaskQueueService = project.service() snykTaskQueueService.scan() + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertTrue(snykTaskQueueService.getTaskQueue().isEmpty) verify { downloaderMock.downloadFile(any(), any(), any()) } } - @Test fun testDontDownloadCLIIfUpdatesDisabled() { val downloaderMock = setupMockForDownloadTest() val settings = setupAppSettingsForDownloadTests() @@ -125,6 +123,7 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { val snykTaskQueueService = project.service() snykTaskQueueService.scan() + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertTrue(snykTaskQueueService.getTaskQueue().isEmpty) @@ -152,7 +151,6 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { return downloaderMock } - @Test fun testProjectClosedWhileTaskRunning() { val snykTaskQueueService = project.service() @@ -163,7 +161,6 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { snykTaskQueueService.downloadLatestRelease() } - @Test fun testSastEnablementCheckInScan() { val snykTaskQueueService = project.service() val settings = pluginSettings() @@ -173,30 +170,30 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { settings.token = "testToken" snykTaskQueueService.scan() + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() verify { snykApiServiceMock.getSastSettings() } } - @Test fun `test reportFalsePositivesEnabled should be unknown in initial settings state`() { val settings = pluginSettings() assertNull(settings.reportFalsePositivesEnabled) } - @Test fun `test LCE should be unknown in initial settings state`() { val settings = pluginSettings() assertNull(settings.localCodeEngineEnabled) } - @Test fun `test should disable Code settings when LCE is enabled`() { val snykTaskQueueService = project.service() val settings = pluginSettings() settings.sastOnServerEnabled = true settings.localCodeEngineEnabled = true + settings.snykCodeQualityIssuesScanEnable = true + settings.snykCodeSecurityIssuesScanEnable = true settings.token = "testToken" // overwrite default setup snykApiServiceMock = mockk(relaxed = true) @@ -208,6 +205,7 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { ) snykTaskQueueService.scan() + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertThat(settings.snykCodeSecurityIssuesScanEnable, equalTo(false)) assertThat(settings.snykCodeQualityIssuesScanEnable, equalTo(false)) @@ -218,7 +216,6 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { application.replaceService(SnykApiService::class.java, snykApiServiceMock, application) } - @Test fun testIacScanTriggeredAndProduceResults() { val snykTaskQueueService = project.service() val settings = pluginSettings() @@ -236,11 +233,11 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { every { getIacService(project)?.scan() } returns fakeIacResult snykTaskQueueService.scan() + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() assertEquals(fakeIacResult, getSnykCachedResults(project)?.currentIacResult) } - @Test fun testContainerScanTriggeredAndProduceResults() { mockkStatic("io.snyk.plugin.UtilsKt") every { isContainerEnabled() } returns true @@ -259,6 +256,7 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() { getSnykCachedResults(project)?.currentContainerResult = null snykTaskQueueService.scan() + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() await().atMost(2, TimeUnit.SECONDS).until { getSnykCachedResults(project)?.currentContainerResult != null diff --git a/src/integTest/kotlin/io/snyk/plugin/services/download/CliDownloaderErrorHandlerIntegTest.kt b/src/test/kotlin/io/snyk/plugin/services/download/CliDownloaderErrorHandlerIntegTest.kt similarity index 100% rename from src/integTest/kotlin/io/snyk/plugin/services/download/CliDownloaderErrorHandlerIntegTest.kt rename to src/test/kotlin/io/snyk/plugin/services/download/CliDownloaderErrorHandlerIntegTest.kt diff --git a/src/integTest/kotlin/io/snyk/plugin/services/download/CliDownloaderServiceIntegTest.kt b/src/test/kotlin/io/snyk/plugin/services/download/CliDownloaderServiceIntegTest.kt similarity index 100% rename from src/integTest/kotlin/io/snyk/plugin/services/download/CliDownloaderServiceIntegTest.kt rename to src/test/kotlin/io/snyk/plugin/services/download/CliDownloaderServiceIntegTest.kt diff --git a/src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/ReportFalsePositiveDialogIntegTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/ReportFalsePositiveDialogIntegTest.kt similarity index 100% rename from src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/ReportFalsePositiveDialogIntegTest.kt rename to src/test/kotlin/io/snyk/plugin/ui/toolwindow/ReportFalsePositiveDialogIntegTest.kt index 06ff547d9..ed40299f3 100644 --- a/src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/ReportFalsePositiveDialogIntegTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/ReportFalsePositiveDialogIntegTest.kt @@ -1,6 +1,5 @@ package io.snyk.plugin.ui.toolwindow -import snyk.common.UIComponentFinder import com.intellij.CommonBundle import com.intellij.openapi.ui.DialogWrapper.CLOSE_EXIT_CODE import com.intellij.testFramework.fixtures.BasePlatformTestCase @@ -9,6 +8,7 @@ import io.snyk.plugin.ui.toolwindow.ReportFalsePositiveDialog.Companion.REPORT_F import io.snyk.plugin.ui.toolwindow.ReportFalsePositiveDialog.Companion.TITLE_TEXT import io.snyk.plugin.ui.toolwindow.ReportFalsePositiveDialog.Companion.WARN_MESSAGE_TEXT import org.junit.Test +import snyk.common.UIComponentFinder import javax.swing.Action import javax.swing.JPanel diff --git a/src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/SnykAuthPanelIntegTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykAuthPanelIntegTest.kt similarity index 99% rename from src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/SnykAuthPanelIntegTest.kt rename to src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykAuthPanelIntegTest.kt index 4a5cf6195..1cf52633f 100644 --- a/src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/SnykAuthPanelIntegTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykAuthPanelIntegTest.kt @@ -1,6 +1,5 @@ package io.snyk.plugin.ui.toolwindow -import snyk.common.UIComponentFinder import com.intellij.openapi.application.ApplicationManager import com.intellij.testFramework.LightPlatform4TestCase import com.intellij.testFramework.replaceService @@ -13,6 +12,7 @@ import io.snyk.plugin.services.SnykAnalyticsService import io.snyk.plugin.services.SnykCliAuthenticationService import io.snyk.plugin.ui.toolwindow.panels.SnykAuthPanel import org.junit.Test +import snyk.common.UIComponentFinder import snyk.trust.WorkspaceTrustService import snyk.trust.confirmScanningAndSetWorkspaceTrustedStateIfNeeded import javax.swing.JButton @@ -28,7 +28,7 @@ class SnykAuthPanelIntegTest : LightPlatform4TestCase() { super.setUp() unmockkAll() mockkStatic("snyk.trust.TrustedProjectsKt") - every { confirmScanningAndSetWorkspaceTrustedStateIfNeeded(any()) } returns true + every { confirmScanningAndSetWorkspaceTrustedStateIfNeeded(any(), any()) } returns true val application = ApplicationManager.getApplication() application.replaceService(SnykAnalyticsService::class.java, analyticsService, application) application.replaceService(WorkspaceTrustService::class.java, workspaceTrustServiceMock, application) diff --git a/src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt similarity index 98% rename from src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt rename to src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt index b22c33f8f..a759928d0 100644 --- a/src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SnykToolWindowPanelIntegTest.kt @@ -1,10 +1,8 @@ package io.snyk.plugin.ui.toolwindow import ai.deepcode.javaclient.core.SuggestionForFile -import snyk.common.UIComponentFinder import com.intellij.mock.MockVirtualFile import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.application.impl.ApplicationInfoImpl import com.intellij.openapi.components.service import com.intellij.testFramework.HeavyPlatformTestCase import com.intellij.testFramework.PlatformTestUtil @@ -47,6 +45,7 @@ import io.snyk.plugin.ui.toolwindow.panels.SuggestionDescriptionPanel import io.snyk.plugin.ui.toolwindow.panels.VulnerabilityDescriptionPanel import org.junit.Test import snyk.common.SnykError +import snyk.common.UIComponentFinder import snyk.container.ContainerIssue import snyk.container.ContainerIssuesForImage import snyk.container.ContainerResult @@ -102,7 +101,7 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { // also we MUST do it *before* any actual test code due to initialisation of SnykScanListener in init{} toolWindowPanel = project.service() setupDummyCliFile() - every { confirmScanningAndSetWorkspaceTrustedStateIfNeeded(any()) } returns true + every { confirmScanningAndSetWorkspaceTrustedStateIfNeeded(any(), any()) } returns true } override fun tearDown() { @@ -556,7 +555,7 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { val mediumSeverityFilterAction = ActionManager.getInstance().getAction("io.snyk.plugin.ui.actions.SnykTreeMediumSeverityFilterAction") as SnykTreeMediumSeverityFilterAction - mediumSeverityFilterAction.setSelected(TestActionEvent(), false) + mediumSeverityFilterAction.setSelected(TestActionEvent.createTestEvent(), false) PlatformTestUtil.waitWhileBusy(toolWindowPanel.getTree()) @@ -885,15 +884,7 @@ class SnykToolWindowPanelIntegTest : HeavyPlatformTestCase() { private fun waitWhileTreeBusy() { val tree = toolWindowPanel.getTree() - // hack to avoid "File accessed outside allowed roots" check in tests - // needed due to com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess.assertAccessInTests - val prevIsInStressTest = ApplicationInfoImpl.isInStressTest() - ApplicationInfoImpl.setInStressTest(true) - try { - PlatformTestUtil.waitWhileBusy(tree) - } finally { - ApplicationInfoImpl.setInStressTest(prevIsInStressTest) - } + PlatformTestUtil.waitWhileBusy(tree) } @Test diff --git a/src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelIntegTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelIntegTest.kt similarity index 100% rename from src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelIntegTest.kt rename to src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelIntegTest.kt index f923cb3ed..c65ddd77e 100644 --- a/src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelIntegTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/SuggestionDescriptionPanelIntegTest.kt @@ -1,6 +1,5 @@ package io.snyk.plugin.ui.toolwindow -import snyk.common.UIComponentFinder import ai.deepcode.javaclient.core.SuggestionForFile import com.intellij.openapi.util.registry.Registry import com.intellij.testFramework.fixtures.BasePlatformTestCase @@ -9,6 +8,7 @@ import io.snyk.plugin.snykcode.core.SnykCodeFile import io.snyk.plugin.ui.toolwindow.ReportFalsePositiveDialog.Companion.REPORT_FALSE_POSITIVE_TEXT import io.snyk.plugin.ui.toolwindow.panels.SuggestionDescriptionPanel import org.junit.Test +import snyk.common.UIComponentFinder import javax.swing.JButton @Suppress("FunctionName") diff --git a/src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/settings/ScanTypesPanelTest.kt b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/settings/ScanTypesPanelTest.kt similarity index 100% rename from src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/settings/ScanTypesPanelTest.kt rename to src/test/kotlin/io/snyk/plugin/ui/toolwindow/settings/ScanTypesPanelTest.kt index 6083b6ecf..d8de9549c 100644 --- a/src/integTest/kotlin/io/snyk/plugin/ui/toolwindow/settings/ScanTypesPanelTest.kt +++ b/src/test/kotlin/io/snyk/plugin/ui/toolwindow/settings/ScanTypesPanelTest.kt @@ -1,6 +1,5 @@ package io.snyk.plugin.ui.toolwindow.settings -import snyk.common.UIComponentFinder.getComponentByName import com.intellij.openapi.Disposable import com.intellij.openapi.util.Disposer import com.intellij.testFramework.LightPlatform4TestCase @@ -17,6 +16,7 @@ import io.snyk.plugin.resetSettings import io.snyk.plugin.ui.settings.ScanTypesPanel import org.junit.Test import snyk.common.ProductType +import snyk.common.UIComponentFinder.getComponentByName import snyk.common.isSnykCodeAvailable import snyk.container.KubernetesImageCache diff --git a/src/integTest/kotlin/snyk/advisor/PackageNameProviderTest.kt b/src/test/kotlin/snyk/advisor/PackageNameProviderTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/advisor/PackageNameProviderTest.kt rename to src/test/kotlin/snyk/advisor/PackageNameProviderTest.kt diff --git a/src/integTest/kotlin/snyk/advisor/SnykAdvisorModelTest.kt b/src/test/kotlin/snyk/advisor/SnykAdvisorModelTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/advisor/SnykAdvisorModelTest.kt rename to src/test/kotlin/snyk/advisor/SnykAdvisorModelTest.kt diff --git a/src/test/kotlin/snyk/advisor/api/AdvisorApiClientTest.kt b/src/test/kotlin/snyk/advisor/api/AdvisorApiClientTest.kt index d3e95cdf4..a0db908ee 100644 --- a/src/test/kotlin/snyk/advisor/api/AdvisorApiClientTest.kt +++ b/src/test/kotlin/snyk/advisor/api/AdvisorApiClientTest.kt @@ -1,30 +1,42 @@ package snyk.advisor.api import com.google.gson.Gson +import io.mockk.every +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.snyk.plugin.services.SnykApplicationSettingsStateService import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import okio.buffer import okio.source -import org.hamcrest.collection.IsCollectionWithSize.hasSize -import org.hamcrest.core.IsEqual.equalTo -import org.hamcrest.core.IsNull.notNullValue -import org.hamcrest.core.IsNull.nullValue -import org.junit.Assert.assertThat +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull import org.junit.Before -import org.junit.Rule import org.junit.Test +import snyk.PluginInformation import snyk.advisor.AdvisorPackageManager +import snyk.pluginInfo import java.nio.charset.StandardCharsets class AdvisorApiClientTest { - @JvmField - @Rule - val server: MockWebServer = MockWebServer() - private lateinit var clientUnderTest: AdvisorApiClient + private val server = MockWebServer() + private lateinit var settings: SnykApplicationSettingsStateService + @Before fun setUp() { - clientUnderTest = AdvisorApiClient(server.url("/").toString()) + unmockkAll() + mockkStatic("snyk.PluginInformationKt") + val pluginInformation = PluginInformation( + "testIntegrationName", + "testIntegrationVersion", + "testIntegrationEnvironment", + "testIntegrationEnvironmentVersion" + ) + every { pluginInfo } returns pluginInformation + settings = SnykApplicationSettingsStateService() + clientUnderTest = AdvisorApiClient(server.url("/").toString(), settings) } @Test @@ -34,7 +46,7 @@ class AdvisorApiClientTest { val response = advisorScoreService.scoresNpmPackages(listOf()).execute() - assertThat(response.code(), equalTo(404)) + assertEquals(404, response.code()) } @Test @@ -51,25 +63,25 @@ class AdvisorApiClientTest { val response = advisorScoreService.scoresNpmPackages(listOf()).execute() // asserts - assertThat(response.code(), equalTo(200)) + assertEquals(200, response.code()) val npmPackagesInfo = response.body() - assertThat(npmPackagesInfo, notNullValue()) - assertThat(npmPackagesInfo, hasSize(2)) + assertNotNull(npmPackagesInfo) + assertEquals(2, npmPackagesInfo?.size) val jqueryPackageInfo = npmPackagesInfo?.first() - assertThat(jqueryPackageInfo?.name, equalTo("jquery")) - assertThat(jqueryPackageInfo?.score, equalTo(0.97142857)) - assertThat(jqueryPackageInfo?.pending, equalTo(false)) - assertThat(jqueryPackageInfo?.error, nullValue()) - assertThat(jqueryPackageInfo?.labels, equalTo(expectedPackageInfoLabels)) + assertEquals("jquery", jqueryPackageInfo?.name) + assertEquals(0.97142857, jqueryPackageInfo?.score) + assertEquals(false, jqueryPackageInfo?.pending) + assertNull(jqueryPackageInfo?.error) + assertEquals(expectedPackageInfoLabels, jqueryPackageInfo?.labels) val vuePackageInfo = npmPackagesInfo?.last() - assertThat(vuePackageInfo?.name, equalTo("vue")) - assertThat(vuePackageInfo?.score, equalTo(0.9475)) - assertThat(vuePackageInfo?.pending, equalTo(true)) - assertThat(vuePackageInfo?.error, nullValue()) - assertThat(vuePackageInfo?.labels, equalTo(expectedPackageInfoLabels)) + assertEquals("vue", vuePackageInfo?.name) + assertEquals(0.9475, vuePackageInfo?.score) + assertEquals(true, vuePackageInfo?.pending) + assertNull(vuePackageInfo?.error) + assertEquals(expectedPackageInfoLabels, vuePackageInfo?.labels) } @Test @@ -80,15 +92,15 @@ class AdvisorApiClientTest { val response = advisorScoreService.scoresPythonPackages(listOf("non-existing-package")).execute() // asserts - assertThat(response.code(), equalTo(200)) + assertEquals(200, response.code()) val npmPackagesInfo = response.body() - assertThat(npmPackagesInfo, notNullValue()) - assertThat(npmPackagesInfo, hasSize(1)) + assertNotNull(npmPackagesInfo) + assertEquals(1, npmPackagesInfo?.size) val nonExistingPackageInfo = npmPackagesInfo?.first() - assertThat(nonExistingPackageInfo?.name, equalTo("non-existing-package")) - assertThat(nonExistingPackageInfo?.error, equalTo("not found")) + assertEquals("non-existing-package", nonExistingPackageInfo?.name) + assertEquals("not found", nonExistingPackageInfo?.error) } @Test @@ -98,7 +110,7 @@ class AdvisorApiClientTest { val response = advisorScoreService.scoresPythonPackages(listOf()).execute() - assertThat(response.code(), equalTo(404)) + assertEquals(404, response.code()) } @Test @@ -115,25 +127,25 @@ class AdvisorApiClientTest { val response = advisorScoreService.scoresNpmPackages(listOf()).execute() // asserts - assertThat(response.code(), equalTo(200)) + assertEquals(200, response.code()) val pythonPackageInfo = response.body() - assertThat(pythonPackageInfo, notNullValue()) - assertThat(pythonPackageInfo, hasSize(2)) + assertNotNull(pythonPackageInfo) + assertEquals(2, pythonPackageInfo?.size) val djangoPackageInfo = pythonPackageInfo?.first() - assertThat(djangoPackageInfo?.name, equalTo("django")) - assertThat(djangoPackageInfo?.score, equalTo(0.9114285714285715)) - assertThat(djangoPackageInfo?.pending, equalTo(false)) - assertThat(djangoPackageInfo?.error, nullValue()) - assertThat(djangoPackageInfo?.labels, equalTo(expectedPackageInfoLabels)) + assertEquals("django", djangoPackageInfo?.name) + assertEquals(0.9114285714285715, djangoPackageInfo?.score) + assertEquals(false, djangoPackageInfo?.pending) + assertNull(djangoPackageInfo?.error) + assertEquals(expectedPackageInfoLabels, djangoPackageInfo?.labels) val tomlPackageInfo = pythonPackageInfo?.last() - assertThat(tomlPackageInfo?.name, equalTo("toml")) - assertThat(tomlPackageInfo?.score, equalTo(0.8678571428571429)) - assertThat(tomlPackageInfo?.pending, equalTo(true)) - assertThat(tomlPackageInfo?.error, nullValue()) - assertThat(tomlPackageInfo?.labels, equalTo(expectedPackageInfoLabels)) + assertEquals("toml", tomlPackageInfo?.name) + assertEquals(0.8678571428571429, tomlPackageInfo?.score) + assertEquals(true, tomlPackageInfo?.pending) + assertNull(tomlPackageInfo?.error) + assertEquals(expectedPackageInfoLabels, tomlPackageInfo?.labels) } @Test @@ -144,15 +156,15 @@ class AdvisorApiClientTest { val response = advisorScoreService.scoresPythonPackages(listOf("non-existing-package")).execute() // asserts - assertThat(response.code(), equalTo(200)) + assertEquals(200, response.code()) val pythonPackagesInfo = response.body() - assertThat(pythonPackagesInfo, notNullValue()) - assertThat(pythonPackagesInfo, hasSize(1)) + assertNotNull(pythonPackagesInfo) + assertEquals(1, pythonPackagesInfo?.size) val nonExistingPackageInfo = pythonPackagesInfo?.first() - assertThat(nonExistingPackageInfo?.name, equalTo("non-existing-package")) - assertThat(nonExistingPackageInfo?.error, equalTo("not found")) + assertEquals("non-existing-package", nonExistingPackageInfo?.name) + assertEquals("not found", nonExistingPackageInfo?.error) } @Test @@ -175,11 +187,11 @@ class AdvisorApiClientTest { } private fun assertInfos(expectedInfos: List, actualInfos: List?) { - assertThat(actualInfos, notNullValue()) - assertThat(actualInfos, hasSize(expectedInfos.size)) + assertNotNull(actualInfos) + assertEquals(expectedInfos.size, actualInfos?.size) actualInfos?.forEachIndexed { index, actual -> - assertThat(actual, equalTo(expectedInfos[index])) + assertEquals(expectedInfos[index], actual) } } } diff --git a/src/integTest/kotlin/snyk/code/annotator/SnykCodeAnnotatorTest.kt b/src/test/kotlin/snyk/code/annotator/SnykCodeAnnotatorTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/code/annotator/SnykCodeAnnotatorTest.kt rename to src/test/kotlin/snyk/code/annotator/SnykCodeAnnotatorTest.kt diff --git a/src/integTest/kotlin/snyk/common/AnnotatorCommonIntegTest.kt b/src/test/kotlin/snyk/common/AnnotatorCommonIntegTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/common/AnnotatorCommonIntegTest.kt rename to src/test/kotlin/snyk/common/AnnotatorCommonIntegTest.kt diff --git a/src/integTest/kotlin/snyk/common/intentionactions/AlwaysAvailableReplacementIntentionActionTest.kt b/src/test/kotlin/snyk/common/intentionactions/AlwaysAvailableReplacementIntentionActionTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/common/intentionactions/AlwaysAvailableReplacementIntentionActionTest.kt rename to src/test/kotlin/snyk/common/intentionactions/AlwaysAvailableReplacementIntentionActionTest.kt diff --git a/src/integTest/kotlin/snyk/container/ContainerBulkFileListenerTest.kt b/src/test/kotlin/snyk/container/ContainerBulkFileListenerTest.kt similarity index 89% rename from src/integTest/kotlin/snyk/container/ContainerBulkFileListenerTest.kt rename to src/test/kotlin/snyk/container/ContainerBulkFileListenerTest.kt index ae8cc69b9..8bcd70fc0 100644 --- a/src/integTest/kotlin/snyk/container/ContainerBulkFileListenerTest.kt +++ b/src/test/kotlin/snyk/container/ContainerBulkFileListenerTest.kt @@ -10,22 +10,23 @@ import com.intellij.openapi.vfs.newvfs.events.VFileEvent import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager +import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.intellij.util.io.createDirectories import com.intellij.util.io.delete -import com.intellij.util.io.exists import io.mockk.unmockkAll import io.snyk.plugin.getKubernetesImageCache import io.snyk.plugin.getSnykCachedResults import io.snyk.plugin.resetSettings import io.snyk.plugin.ui.toolwindow.SnykToolWindowPanel import org.awaitility.Awaitility.await -import org.junit.Test import snyk.container.ui.ContainerImageTreeNode import java.io.File import java.nio.file.Files +import java.nio.file.LinkOption import java.nio.file.Paths import java.util.concurrent.TimeUnit +import kotlin.io.path.notExists @Suppress("FunctionName") class ContainerBulkFileListenerTest : BasePlatformTestCase() { @@ -50,13 +51,12 @@ class ContainerBulkFileListenerTest : BasePlatformTestCase() { private fun createNewFileInProjectRoot(name: String): File { val projectPath = Paths.get(project.basePath!!) - if (!projectPath.exists()) { + if (projectPath.notExists(LinkOption.NOFOLLOW_LINKS)) { projectPath.createDirectories() } return File(project.basePath + File.separator + name).apply { createNewFile() } } - @Test fun `test Container should update image cache when yaml file is changed`() { setUpContainerTest() val path = createNewFileInProjectRoot("kubernetes-test.yaml").toPath() @@ -71,10 +71,13 @@ class ContainerBulkFileListenerTest : BasePlatformTestCase() { ?.setText(TestYamls.podYaml()) } FileDocumentManager.getInstance().saveAllDocuments() + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() - val kubernetesWorkloadImages = imageCache.getKubernetesWorkloadImages() - - assertNotNull(kubernetesWorkloadImages) + var kubernetesWorkloadImages = imageCache.getKubernetesWorkloadImages() + await().atMost(5, TimeUnit.SECONDS).until { + kubernetesWorkloadImages = imageCache.getKubernetesWorkloadImages() + kubernetesWorkloadImages.isNotEmpty() + } assertNotEmpty(kubernetesWorkloadImages) assertEquals(1, kubernetesWorkloadImages.size) assertEquals(path, kubernetesWorkloadImages.first().virtualFile.toNioPath()) @@ -82,7 +85,6 @@ class ContainerBulkFileListenerTest : BasePlatformTestCase() { virtualFile.toNioPath().delete(true) } - @Test fun `test Container should delete images from cache when yaml file is deleted`() { setUpContainerTest() val file = myFixture.addFileToProject("kubernetes-test.yaml", "") @@ -92,13 +94,23 @@ class ContainerBulkFileListenerTest : BasePlatformTestCase() { ?.setText(TestYamls.podYaml()) } FileDocumentManager.getInstance().saveAllDocuments() - assertNotEmpty(imageCache.getKubernetesWorkloadImages()) + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() + + var kubernetesWorkloadImages: Set + await().atMost(5, TimeUnit.SECONDS).until { + kubernetesWorkloadImages = imageCache.getKubernetesWorkloadImages() + kubernetesWorkloadImages.isNotEmpty() + } ApplicationManager.getApplication().runWriteAction { file.virtualFile.delete(null) } FileDocumentManager.getInstance().saveAllDocuments() - assertEmpty(imageCache.getKubernetesWorkloadImages()) + PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() + + await().atMost(5, TimeUnit.SECONDS).until { + imageCache.getKubernetesWorkloadImages().isEmpty() + } } /** @@ -132,20 +144,21 @@ class ContainerBulkFileListenerTest : BasePlatformTestCase() { uniqueCount = 1, error = null, imageName = "nginx", - workloadImages = listOf(KubernetesWorkloadImage( - image = "nginx", - virtualFile = addedPsiFile.virtualFile - )) + workloadImages = listOf( + KubernetesWorkloadImage( + image = "nginx", + virtualFile = addedPsiFile.virtualFile + ) + ) ) val fakeContainerResult = ContainerResult(listOf(issuesForImage)) val toolWindowPanel = project.service() getSnykCachedResults(project)?.currentContainerResult = fakeContainerResult toolWindowPanel.getRootContainerIssuesTreeNode().add(ContainerImageTreeNode(issuesForImage, project) {}) - getKubernetesImageCache(project)?.extractFromFile(addedPsiFile.virtualFile) + getKubernetesImageCache(project)?.extractFromFileAndAddToCache(addedPsiFile.virtualFile) } - @Test fun `test ContainerResults should drop cache and mark rescanNeeded when Container supported file CHANGED`() { setUpContainerTest() val psiFile = myFixture.configureByText("existing.yaml", TestYamls.podYaml()) @@ -162,7 +175,6 @@ class ContainerBulkFileListenerTest : BasePlatformTestCase() { await().atMost(2, TimeUnit.SECONDS).until { containerCacheInvalidatedForFile(psiFile) } } - @Test fun `test ContainerResults should mark rescanNeeded when Container supported file CREATED`() { setUpContainerTest() createFakeContainerResultInCache() @@ -178,7 +190,6 @@ class ContainerBulkFileListenerTest : BasePlatformTestCase() { ) } - @Test fun `test ContainerResults should mark rescanNeeded when Container supported file COPIED`() { setUpContainerTest() val originalFile = myFixture.addFileToProject("existing.yaml", TestYamls.podYaml()) @@ -199,7 +210,6 @@ class ContainerBulkFileListenerTest : BasePlatformTestCase() { ) } - @Test fun `test ContainerResults should drop cache and mark rescanNeeded when Container supported file MOVED`() { setUpContainerTest() val originalFile = myFixture.addFileToProject("existing.yaml", TestYamls.podYaml()) @@ -219,7 +229,6 @@ class ContainerBulkFileListenerTest : BasePlatformTestCase() { ) } - @Test fun `test ContainerResults should drop cache and mark rescanNeeded when Container supported file RENAMED`() { setUpContainerTest() val originalFile = myFixture.addFileToProject("existing.yaml", TestYamls.podYaml()) @@ -238,7 +247,6 @@ class ContainerBulkFileListenerTest : BasePlatformTestCase() { ) } - @Test fun `test ContainerResults should drop cache and mark rescanNeeded when Container supported file DELETED`() { setUpContainerTest() val originalFile = myFixture.addFileToProject("existing.yaml", TestYamls.podYaml()) @@ -257,7 +265,6 @@ class ContainerBulkFileListenerTest : BasePlatformTestCase() { ) } - @Test fun `test ContainerResults should drop cache and mark rescanNeeded when cached file wiped it content`() { setUpContainerTest() val psiFile = myFixture.addFileToProject("existing.yaml", TestYamls.podYaml()) @@ -276,7 +283,6 @@ class ContainerBulkFileListenerTest : BasePlatformTestCase() { ) } - @Test fun `test Container should update cache even if any other cache update fail with Exception`() { setUpContainerTest() val psiFile = myFixture.configureByText("existing.yaml", TestYamls.podYaml()) @@ -294,7 +300,7 @@ class ContainerBulkFileListenerTest : BasePlatformTestCase() { exceptionThrown = try { FileDocumentManager.getInstance().saveAllDocuments() false - } catch (e: ControlException) { + } catch (e: RuntimeException) { true } } finally { diff --git a/src/integTest/kotlin/snyk/container/ContainerServiceIntegTest.kt b/src/test/kotlin/snyk/container/ContainerServiceIntegTest.kt similarity index 98% rename from src/integTest/kotlin/snyk/container/ContainerServiceIntegTest.kt rename to src/test/kotlin/snyk/container/ContainerServiceIntegTest.kt index 0ba4f3c9a..03949b80a 100644 --- a/src/integTest/kotlin/snyk/container/ContainerServiceIntegTest.kt +++ b/src/test/kotlin/snyk/container/ContainerServiceIntegTest.kt @@ -101,7 +101,7 @@ class ContainerServiceIntegTest : LightPlatform4TestCase() { verify { cache.getKubernetesWorkloadImages() } assertEquals( expectedContainerResult.issuesCount, - scanResult.allCliIssues?.sumBy { it.groupedVulnsById.size } + scanResult.allCliIssues?.sumOf { it.groupedVulnsById.size } ) return Pair(expectedContainerResult, scanResult) } @@ -111,7 +111,7 @@ class ContainerServiceIntegTest : LightPlatform4TestCase() { val file = createFile(fileName, podYaml()) val cache = spyk(KubernetesImageCache(project)) cut.setKubernetesImageCache(cache) - cache.extractFromFile(file.virtualFile) + cache.extractFromFileAndAddToCache(file.virtualFile) return cache } diff --git a/src/integTest/kotlin/snyk/container/KubernetesImageCacheIntegTest.kt b/src/test/kotlin/snyk/container/KubernetesImageCacheIntegTest.kt similarity index 92% rename from src/integTest/kotlin/snyk/container/KubernetesImageCacheIntegTest.kt rename to src/test/kotlin/snyk/container/KubernetesImageCacheIntegTest.kt index b1bba33bc..af7a85968 100644 --- a/src/integTest/kotlin/snyk/container/KubernetesImageCacheIntegTest.kt +++ b/src/test/kotlin/snyk/container/KubernetesImageCacheIntegTest.kt @@ -51,7 +51,7 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { @Test fun `extractFromFile should find yaml files and extract images`() { val file = createFile(fileName, podYaml()).virtualFile - cut.extractFromFile(file) + cut.extractFromFileAndAddToCache(file) val images = cut.getKubernetesWorkloadImages() assertEquals(1, images.size) @@ -149,7 +149,7 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { fun `should return Kubernetes Workload Files`() { val file = createFile(fileName, podYaml()).virtualFile - cut.extractFromFile(file) + cut.extractFromFileAndAddToCache(file) val files: Set = cut.getKubernetesWorkloadFilesFromCache() assertEquals(1, files.size) @@ -160,7 +160,7 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { fun `should not parse non yaml files`() { val file = createFile("not-a-yaml-file.zaml", podYaml()).virtualFile - cut.extractFromFile(file) + cut.extractFromFileAndAddToCache(file) val files: Set = cut.getKubernetesWorkloadFilesFromCache() assertEmpty(files) @@ -170,7 +170,7 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { fun `should return Kubernetes Workload Image Names`() { val file = createFile(fileName, podYaml()).virtualFile - cut.extractFromFile(file) + cut.extractFromFileAndAddToCache(file) val images: Set = cut.getKubernetesWorkloadImageNamesFromCache() assertEquals(1, images.size) @@ -181,7 +181,7 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { fun `should return Kubernetes Workload Images with correct line number`() { val file = createFile(fileName, podYaml()).virtualFile - cut.extractFromFile(file) + cut.extractFromFileAndAddToCache(file) val images: Set = cut.getKubernetesWorkloadImages() assertEquals(1, images.size) @@ -192,7 +192,7 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { fun `should remove file from cache when all images from file (cached before) been removed`() { val psiFile = createFile(fileName, podYaml()) val virtualFile = psiFile.virtualFile - cut.extractFromFile(virtualFile) + cut.extractFromFileAndAddToCache(virtualFile) val controlImages: Set = cut.getKubernetesWorkloadImages() assertEquals(1, controlImages.size) @@ -201,7 +201,7 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { ?.setText("") } FileDocumentManager.getInstance().saveAllDocuments() - cut.extractFromFile(virtualFile) + cut.extractFromFileAndAddToCache(virtualFile) val images = cut.getKubernetesWorkloadImages() assertEquals(0, images.size) @@ -212,14 +212,14 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { private fun executeExtract(yaml: String): Set { val file = createFile(fileName, yaml).virtualFile - cut.extractFromFile(file) + cut.extractFromFileAndAddToCache(file) return cut.getKubernetesWorkloadImageNamesFromCache() } @Test fun `extract images from Helm generated yaml with image names inside quotes`() { val file = createFile(fileName, helmYaml()).virtualFile - cut.extractFromFile(file) + cut.extractFromFileAndAddToCache(file) val images = cut.getKubernetesWorkloadImages() assertEquals(1, images.size) @@ -229,7 +229,7 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { @Test fun `no images extracted from yaml with invalid image names`() { val file = createFile(fileName, singleQuoteImageNameBrokenYaml()).virtualFile - cut.extractFromFile(file) + cut.extractFromFileAndAddToCache(file) val images = cut.getKubernetesWorkloadImages() assertEquals(0, images.size) @@ -239,7 +239,7 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { fun `distinct Kubernetes Workload Image Names for calling CLI`() { val file = createFile(fileName, duplicatedImageNameYaml()).virtualFile - cut.extractFromFile(file) + cut.extractFromFileAndAddToCache(file) val images: Set = cut.getKubernetesWorkloadImageNamesFromCache() assertEquals(1, images.size) @@ -250,7 +250,7 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { fun `image Path followed by comment is extracted`() { val file = createFile(fileName, imagePathFollowedByCommentYaml()).virtualFile - cut.extractFromFile(file) + cut.extractFromFileAndAddToCache(file) val images: Set = cut.getKubernetesWorkloadImageNamesFromCache() assertEquals(1, images.size) @@ -261,7 +261,7 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { fun `image Path commented is NOT extracted`() { val file = createFile(fileName, imagePathCommentedYaml()).virtualFile - cut.extractFromFile(file) + cut.extractFromFileAndAddToCache(file) val images: Set = cut.getKubernetesWorkloadImageNamesFromCache() assertEquals(0, images.size) @@ -271,7 +271,7 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { fun `image Path with port and tag is extracted`() { val file = createFile(fileName, imagePathWithPortAndTagYaml()).virtualFile - cut.extractFromFile(file) + cut.extractFromFileAndAddToCache(file) val images: Set = cut.getKubernetesWorkloadImageNamesFromCache() assertEquals(1, images.size) @@ -282,7 +282,7 @@ class KubernetesImageCacheIntegTest : LightPlatform4TestCase() { fun `image Path with digest is extracted`() { val file = createFile(fileName, imagePathWithDigestYaml()).virtualFile - cut.extractFromFile(file) + cut.extractFromFileAndAddToCache(file) val images: Set = cut.getKubernetesWorkloadImageNamesFromCache() assertEquals(1, images.size) diff --git a/src/integTest/kotlin/snyk/container/TestYamls.kt b/src/test/kotlin/snyk/container/TestYamls.kt similarity index 100% rename from src/integTest/kotlin/snyk/container/TestYamls.kt rename to src/test/kotlin/snyk/container/TestYamls.kt diff --git a/src/integTest/kotlin/snyk/container/annotator/BaseImageRemediationFixTest.kt b/src/test/kotlin/snyk/container/annotator/BaseImageRemediationFixTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/container/annotator/BaseImageRemediationFixTest.kt rename to src/test/kotlin/snyk/container/annotator/BaseImageRemediationFixTest.kt diff --git a/src/integTest/kotlin/snyk/container/annotator/ContainerYamlAnnotatorTest.kt b/src/test/kotlin/snyk/container/annotator/ContainerYamlAnnotatorTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/container/annotator/ContainerYamlAnnotatorTest.kt rename to src/test/kotlin/snyk/container/annotator/ContainerYamlAnnotatorTest.kt diff --git a/src/integTest/kotlin/snyk/iac/IacBulkFileListenerTest.kt b/src/test/kotlin/snyk/iac/IacBulkFileListenerTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/iac/IacBulkFileListenerTest.kt rename to src/test/kotlin/snyk/iac/IacBulkFileListenerTest.kt diff --git a/src/integTest/kotlin/snyk/iac/IacServiceTest.kt b/src/test/kotlin/snyk/iac/IacServiceTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/iac/IacServiceTest.kt rename to src/test/kotlin/snyk/iac/IacServiceTest.kt diff --git a/src/integTest/kotlin/snyk/iac/annotator/IacBaseAnnotatorCase.kt b/src/test/kotlin/snyk/iac/annotator/IacBaseAnnotatorCase.kt similarity index 100% rename from src/integTest/kotlin/snyk/iac/annotator/IacBaseAnnotatorCase.kt rename to src/test/kotlin/snyk/iac/annotator/IacBaseAnnotatorCase.kt diff --git a/src/integTest/kotlin/snyk/iac/annotator/IacHclAnnotatorTest.kt b/src/test/kotlin/snyk/iac/annotator/IacHclAnnotatorTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/iac/annotator/IacHclAnnotatorTest.kt rename to src/test/kotlin/snyk/iac/annotator/IacHclAnnotatorTest.kt diff --git a/src/integTest/kotlin/snyk/iac/annotator/IacJsonAnnotatorTest.kt b/src/test/kotlin/snyk/iac/annotator/IacJsonAnnotatorTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/iac/annotator/IacJsonAnnotatorTest.kt rename to src/test/kotlin/snyk/iac/annotator/IacJsonAnnotatorTest.kt diff --git a/src/integTest/kotlin/snyk/iac/annotator/IacYamlAnnotatorTest.kt b/src/test/kotlin/snyk/iac/annotator/IacYamlAnnotatorTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/iac/annotator/IacYamlAnnotatorTest.kt rename to src/test/kotlin/snyk/iac/annotator/IacYamlAnnotatorTest.kt diff --git a/src/integTest/kotlin/snyk/oss/OssBulkFileListenerTest.kt b/src/test/kotlin/snyk/oss/OssBulkFileListenerTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/oss/OssBulkFileListenerTest.kt rename to src/test/kotlin/snyk/oss/OssBulkFileListenerTest.kt diff --git a/src/integTest/kotlin/snyk/oss/OssServiceTest.kt b/src/test/kotlin/snyk/oss/OssServiceTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/oss/OssServiceTest.kt rename to src/test/kotlin/snyk/oss/OssServiceTest.kt diff --git a/src/integTest/kotlin/snyk/oss/annotator/OSSGoModAnnotatorTest.kt b/src/test/kotlin/snyk/oss/annotator/OSSGoModAnnotatorTest.kt similarity index 97% rename from src/integTest/kotlin/snyk/oss/annotator/OSSGoModAnnotatorTest.kt rename to src/test/kotlin/snyk/oss/annotator/OSSGoModAnnotatorTest.kt index a0d2b34f6..90488cdb8 100644 --- a/src/integTest/kotlin/snyk/oss/annotator/OSSGoModAnnotatorTest.kt +++ b/src/test/kotlin/snyk/oss/annotator/OSSGoModAnnotatorTest.kt @@ -18,8 +18,6 @@ import org.hamcrest.collection.IsCollectionWithSize import org.junit.Assert import org.junit.Assert.assertNotEquals import org.junit.Assert.assertThat -import org.junit.Before -import org.junit.Test import snyk.common.SnykCachedResults import snyk.common.intentionactions.AlwaysAvailableReplacementIntentionAction import snyk.oss.OssResult @@ -47,7 +45,6 @@ class OSSGoModAnnotatorTest : BasePlatformTestCase() { override fun isWriteActionRequired(): Boolean = true - @Before override fun setUp() { super.setUp() unmockkAll() @@ -64,14 +61,12 @@ class OSSGoModAnnotatorTest : BasePlatformTestCase() { super.tearDown() } - @Test fun `test getPackageName`() { val issue = createOssResultWithIssues().allCliIssues!!.first().vulnerabilities[0] val actualPackageName = cut.getIntroducingPackage(issue) assertEquals("github.com/gin-gonic/gin", actualPackageName) } - @Test fun `test getIssues should not return any issue if no oss issue exists`() { every { snykCachedResults.currentOssResults } returns null @@ -80,7 +75,6 @@ class OSSGoModAnnotatorTest : BasePlatformTestCase() { Assert.assertEquals(null, issues) } - @Test fun `test getIssues should return issues if they exist`() { every { snykCachedResults.currentOssResults } returns createOssResultWithIssues() @@ -90,7 +84,6 @@ class OSSGoModAnnotatorTest : BasePlatformTestCase() { assertThat(issues!!.vulnerabilities, IsCollectionWithSize.hasSize(11)) } - @Test fun `test apply should trigger newAnnotation call`() { every { snykCachedResults.currentOssResults } returns createOssResultWithIssues() @@ -99,7 +92,6 @@ class OSSGoModAnnotatorTest : BasePlatformTestCase() { verify { annotationHolderMock.newAnnotation(any(), any()) } } - @Test fun `test textRange`() { val ossResult = createOssResultWithIssues() val issue = ossResult.allCliIssues!!.first().vulnerabilities[0] @@ -112,7 +104,6 @@ class OSSGoModAnnotatorTest : BasePlatformTestCase() { assertEquals(expectedRange, actualRange) } - @Test fun `test annotation message should contain issue title`() { val vulnerability = createOssResultWithIssues().allCliIssues!!.first().vulnerabilities[0] @@ -121,7 +112,6 @@ class OSSGoModAnnotatorTest : BasePlatformTestCase() { assertTrue(actual.contains(vulnerability.title) && actual.contains(vulnerability.name)) } - @Test fun `test apply should not add a quickfix`() { val builderMock = mockk(relaxed = true) val result = createOssResultWithIssues() diff --git a/src/integTest/kotlin/snyk/oss/annotator/OSSGradleAnnotatorTest.kt b/src/test/kotlin/snyk/oss/annotator/OSSGradleAnnotatorTest.kt similarity index 99% rename from src/integTest/kotlin/snyk/oss/annotator/OSSGradleAnnotatorTest.kt rename to src/test/kotlin/snyk/oss/annotator/OSSGradleAnnotatorTest.kt index 1d44002c3..f1ef0aa80 100644 --- a/src/integTest/kotlin/snyk/oss/annotator/OSSGradleAnnotatorTest.kt +++ b/src/test/kotlin/snyk/oss/annotator/OSSGradleAnnotatorTest.kt @@ -1,7 +1,6 @@ package snyk.oss.annotator import com.google.gson.Gson -import com.intellij.codeInsight.intention.IntentionAction import com.intellij.lang.annotation.AnnotationBuilder import com.intellij.lang.annotation.AnnotationHolder import com.intellij.openapi.util.TextRange diff --git a/src/integTest/kotlin/snyk/oss/annotator/OSSMavenAnnotatorTest.kt b/src/test/kotlin/snyk/oss/annotator/OSSMavenAnnotatorTest.kt similarity index 98% rename from src/integTest/kotlin/snyk/oss/annotator/OSSMavenAnnotatorTest.kt rename to src/test/kotlin/snyk/oss/annotator/OSSMavenAnnotatorTest.kt index 9d3f8e881..70652e834 100644 --- a/src/integTest/kotlin/snyk/oss/annotator/OSSMavenAnnotatorTest.kt +++ b/src/test/kotlin/snyk/oss/annotator/OSSMavenAnnotatorTest.kt @@ -1,7 +1,6 @@ package snyk.oss.annotator import com.google.gson.Gson -import com.intellij.codeInsight.intention.IntentionAction import com.intellij.lang.annotation.AnnotationBuilder import com.intellij.lang.annotation.AnnotationHolder import com.intellij.openapi.application.WriteAction diff --git a/src/integTest/kotlin/snyk/oss/annotator/OSSNpmAnnotatorTest.kt b/src/test/kotlin/snyk/oss/annotator/OSSNpmAnnotatorTest.kt similarity index 100% rename from src/integTest/kotlin/snyk/oss/annotator/OSSNpmAnnotatorTest.kt rename to src/test/kotlin/snyk/oss/annotator/OSSNpmAnnotatorTest.kt diff --git a/src/integTest/kotlin/snyk/trust/WorkspaceTrustServiceTest.kt b/src/test/kotlin/snyk/trust/WorkspaceTrustServiceIntegrationTest.kt similarity index 95% rename from src/integTest/kotlin/snyk/trust/WorkspaceTrustServiceTest.kt rename to src/test/kotlin/snyk/trust/WorkspaceTrustServiceIntegrationTest.kt index f7e033817..03dbf5b7e 100644 --- a/src/integTest/kotlin/snyk/trust/WorkspaceTrustServiceTest.kt +++ b/src/test/kotlin/snyk/trust/WorkspaceTrustServiceIntegrationTest.kt @@ -15,7 +15,7 @@ import org.junit.Before import org.junit.Test import java.nio.file.Paths -class WorkspaceTrustServiceTest : BasePlatformTestCase() { +class WorkspaceTrustServiceIntegrationTest : BasePlatformTestCase() { private val workspaceTrustSettingsMock = mockk() private lateinit var cut: WorkspaceTrustService diff --git a/src/integTest/resources/AnnotatorTest.java b/src/test/resources/AnnotatorTest.java similarity index 100% rename from src/integTest/resources/AnnotatorTest.java rename to src/test/resources/AnnotatorTest.java diff --git a/src/integTest/resources/advisor-build-files/package.json b/src/test/resources/advisor-build-files/package.json similarity index 100% rename from src/integTest/resources/advisor-build-files/package.json rename to src/test/resources/advisor-build-files/package.json diff --git a/src/integTest/resources/advisor-build-files/requirements.txt b/src/test/resources/advisor-build-files/requirements.txt similarity index 100% rename from src/integTest/resources/advisor-build-files/requirements.txt rename to src/test/resources/advisor-build-files/requirements.txt diff --git a/src/integTest/resources/container-test-results/container-double-jenkins-with-path.json b/src/test/resources/container-test-results/container-double-jenkins-with-path.json similarity index 100% rename from src/integTest/resources/container-test-results/container-double-jenkins-with-path.json rename to src/test/resources/container-test-results/container-double-jenkins-with-path.json diff --git a/src/integTest/resources/container-test-results/debian-nginx-fake_critical_only.json b/src/test/resources/container-test-results/debian-nginx-fake_critical_only.json similarity index 100% rename from src/integTest/resources/container-test-results/debian-nginx-fake_critical_only.json rename to src/test/resources/container-test-results/debian-nginx-fake_critical_only.json diff --git a/src/integTest/resources/container-test-results/nginx-no-remediation.json b/src/test/resources/container-test-results/nginx-no-remediation.json similarity index 100% rename from src/integTest/resources/container-test-results/nginx-no-remediation.json rename to src/test/resources/container-test-results/nginx-no-remediation.json diff --git a/src/integTest/resources/container-test-results/nginx-with-remediation.json b/src/test/resources/container-test-results/nginx-with-remediation.json similarity index 100% rename from src/integTest/resources/container-test-results/nginx-with-remediation.json rename to src/test/resources/container-test-results/nginx-with-remediation.json diff --git a/src/integTest/resources/group-vulnerabilities-goof-test.json b/src/test/resources/group-vulnerabilities-goof-test.json similarity index 100% rename from src/integTest/resources/group-vulnerabilities-goof-test.json rename to src/test/resources/group-vulnerabilities-goof-test.json diff --git a/src/integTest/resources/group-vulnerabilities-test.json b/src/test/resources/group-vulnerabilities-test.json similarity index 100% rename from src/integTest/resources/group-vulnerabilities-test.json rename to src/test/resources/group-vulnerabilities-test.json diff --git a/src/integTest/resources/iac-test-results/fargate.json b/src/test/resources/iac-test-results/fargate.json similarity index 100% rename from src/integTest/resources/iac-test-results/fargate.json rename to src/test/resources/iac-test-results/fargate.json diff --git a/src/integTest/resources/iac-test-results/infrastructure-as-code-goof.json b/src/test/resources/iac-test-results/infrastructure-as-code-goof.json similarity index 100% rename from src/integTest/resources/iac-test-results/infrastructure-as-code-goof.json rename to src/test/resources/iac-test-results/infrastructure-as-code-goof.json diff --git a/src/integTest/resources/licence-vulnerabilities.json b/src/test/resources/licence-vulnerabilities.json similarity index 100% rename from src/integTest/resources/licence-vulnerabilities.json rename to src/test/resources/licence-vulnerabilities.json diff --git a/src/integTest/resources/misformed-vulnerabilities-test.json b/src/test/resources/misformed-vulnerabilities-test.json similarity index 100% rename from src/integTest/resources/misformed-vulnerabilities-test.json rename to src/test/resources/misformed-vulnerabilities-test.json diff --git a/src/integTest/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker similarity index 100% rename from src/integTest/resources/mockito-extensions/org.mockito.plugins.MockMaker rename to src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/src/integTest/resources/oss-test-results/oss-result-go-mod.json b/src/test/resources/oss-test-results/oss-result-go-mod.json similarity index 100% rename from src/integTest/resources/oss-test-results/oss-result-go-mod.json rename to src/test/resources/oss-test-results/oss-result-go-mod.json diff --git a/src/integTest/resources/oss-test-results/oss-result-gradle-kts.json b/src/test/resources/oss-test-results/oss-result-gradle-kts.json similarity index 100% rename from src/integTest/resources/oss-test-results/oss-result-gradle-kts.json rename to src/test/resources/oss-test-results/oss-result-gradle-kts.json diff --git a/src/integTest/resources/oss-test-results/oss-result-gradle.json b/src/test/resources/oss-test-results/oss-result-gradle.json similarity index 100% rename from src/integTest/resources/oss-test-results/oss-result-gradle.json rename to src/test/resources/oss-test-results/oss-result-gradle.json diff --git a/src/integTest/resources/oss-test-results/oss-result-maven.json b/src/test/resources/oss-test-results/oss-result-maven.json similarity index 100% rename from src/integTest/resources/oss-test-results/oss-result-maven.json rename to src/test/resources/oss-test-results/oss-result-maven.json diff --git a/src/integTest/resources/oss-test-results/oss-result-package-no-remediation.json b/src/test/resources/oss-test-results/oss-result-package-no-remediation.json similarity index 100% rename from src/integTest/resources/oss-test-results/oss-result-package-no-remediation.json rename to src/test/resources/oss-test-results/oss-result-package-no-remediation.json diff --git a/src/integTest/resources/oss-test-results/oss-result-package.json b/src/test/resources/oss-test-results/oss-result-package.json similarity index 100% rename from src/integTest/resources/oss-test-results/oss-result-package.json rename to src/test/resources/oss-test-results/oss-result-package.json diff --git a/src/integTest/resources/test-fixtures/code/annotator/app.js b/src/test/resources/test-fixtures/code/annotator/app.js similarity index 100% rename from src/integTest/resources/test-fixtures/code/annotator/app.js rename to src/test/resources/test-fixtures/code/annotator/app.js diff --git a/src/integTest/resources/test-fixtures/container/annotator/kubernetes-deployment.yaml b/src/test/resources/test-fixtures/container/annotator/kubernetes-deployment.yaml similarity index 100% rename from src/integTest/resources/test-fixtures/container/annotator/kubernetes-deployment.yaml rename to src/test/resources/test-fixtures/container/annotator/kubernetes-deployment.yaml diff --git a/src/integTest/resources/test-fixtures/iac/annotator/cloudformation-deployment.yaml b/src/test/resources/test-fixtures/iac/annotator/cloudformation-deployment.yaml similarity index 100% rename from src/integTest/resources/test-fixtures/iac/annotator/cloudformation-deployment.yaml rename to src/test/resources/test-fixtures/iac/annotator/cloudformation-deployment.yaml diff --git a/src/integTest/resources/test-fixtures/iac/annotator/kubernetes-deployment.yaml b/src/test/resources/test-fixtures/iac/annotator/kubernetes-deployment.yaml similarity index 100% rename from src/integTest/resources/test-fixtures/iac/annotator/kubernetes-deployment.yaml rename to src/test/resources/test-fixtures/iac/annotator/kubernetes-deployment.yaml diff --git a/src/integTest/resources/test-fixtures/iac/annotator/terraform-main.tf b/src/test/resources/test-fixtures/iac/annotator/terraform-main.tf similarity index 100% rename from src/integTest/resources/test-fixtures/iac/annotator/terraform-main.tf rename to src/test/resources/test-fixtures/iac/annotator/terraform-main.tf diff --git a/src/integTest/resources/test-fixtures/oss/annotator/build.gradle b/src/test/resources/test-fixtures/oss/annotator/build.gradle similarity index 100% rename from src/integTest/resources/test-fixtures/oss/annotator/build.gradle rename to src/test/resources/test-fixtures/oss/annotator/build.gradle diff --git a/src/integTest/resources/test-fixtures/oss/annotator/build.gradle.kts b/src/test/resources/test-fixtures/oss/annotator/build.gradle.kts similarity index 100% rename from src/integTest/resources/test-fixtures/oss/annotator/build.gradle.kts rename to src/test/resources/test-fixtures/oss/annotator/build.gradle.kts diff --git a/src/integTest/resources/test-fixtures/oss/annotator/go.mod b/src/test/resources/test-fixtures/oss/annotator/go.mod similarity index 100% rename from src/integTest/resources/test-fixtures/oss/annotator/go.mod rename to src/test/resources/test-fixtures/oss/annotator/go.mod diff --git a/src/integTest/resources/test-fixtures/oss/annotator/package.json b/src/test/resources/test-fixtures/oss/annotator/package.json similarity index 100% rename from src/integTest/resources/test-fixtures/oss/annotator/package.json rename to src/test/resources/test-fixtures/oss/annotator/package.json diff --git a/src/integTest/resources/test-fixtures/oss/annotator/pom.xml b/src/test/resources/test-fixtures/oss/annotator/pom.xml similarity index 100% rename from src/integTest/resources/test-fixtures/oss/annotator/pom.xml rename to src/test/resources/test-fixtures/oss/annotator/pom.xml diff --git a/src/integTest/resources/vulnerabilities-array-cli-result.json b/src/test/resources/vulnerabilities-array-cli-result.json similarity index 100% rename from src/integTest/resources/vulnerabilities-array-cli-result.json rename to src/test/resources/vulnerabilities-array-cli-result.json diff --git a/src/integTest/resources/vulnerabilities-array-with-error-and-result-test.json b/src/test/resources/vulnerabilities-array-with-error-and-result-test.json similarity index 100% rename from src/integTest/resources/vulnerabilities-array-with-error-and-result-test.json rename to src/test/resources/vulnerabilities-array-with-error-and-result-test.json