From 45116ccb4728cf80a98f75027238c088f29492cf Mon Sep 17 00:00:00 2001 From: Ilona Shishov Date: Thu, 29 Feb 2024 10:58:03 +0200 Subject: [PATCH] chore: integrate secret storage (#689) * feat: save RHDA Snyk Token configuration in VSCode Secret Storage Signed-off-by: Ilona Shishov * chore: updated the names of commands in the package.json file Signed-off-by: Ilona Shishov * test: updated unit tests Signed-off-by: Ilona Shishov --------- Signed-off-by: Ilona Shishov --- package-lock.json | 10 +- package.json | 58 ++++++----- src/caStatusBarProvider.ts | 4 +- src/commands.ts | 15 +-- src/config.ts | 82 ++++++++++++--- src/constants.ts | 18 ++-- src/exhortServices.ts | 19 ++-- src/extension.ts | 54 +++++++--- src/stackAnalysis.ts | 6 +- src/tokenValidation.ts | 18 ++-- test/caStatusBarProvider.test.ts | 4 +- test/config.test.ts | 159 +++++++++++++++++++++++++++-- test/dependencyReportPanel.test.ts | 6 +- test/exhortServices.test.ts | 32 ++---- test/extension.test.ts | 12 +-- test/stackAnalysis.test.ts | 12 ++- test/tokenValidation.test.ts | 16 +-- 17 files changed, 364 insertions(+), 161 deletions(-) diff --git a/package-lock.json b/package-lock.json index a20c6e91b..45c6b9e6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.9.2", "license": "Apache-2.0", "dependencies": { - "@fabric8-analytics/fabric8-analytics-lsp-server": "^0.9.4-ea.1", + "@fabric8-analytics/fabric8-analytics-lsp-server": "^0.9.4-ea.2", "@redhat-developer/vscode-redhat-telemetry": "^0.7.0", "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.14", "fs": "^0.0.1-security", @@ -82,7 +82,7 @@ }, "../fabric8-analytics-lsp-server": { "name": "@fabric8-analytics/fabric8-analytics-lsp-server", - "version": "0.9.2", + "version": "0.9.4-ea.1", "extraneous": true, "license": "Apache-2.0", "dependencies": { @@ -568,9 +568,9 @@ } }, "node_modules/@fabric8-analytics/fabric8-analytics-lsp-server": { - "version": "0.9.4-ea.1", - "resolved": "https://npm.pkg.github.com/download/@fabric8-analytics/fabric8-analytics-lsp-server/0.9.4-ea.1/bb9fcfb314cd43801691f28cbb24f89f19e084dd", - "integrity": "sha512-jCyN3d9iED3GIUjkHE9U2Borz3ZKdG1glLoDMANX43OF3++5J32igwR5p5PRbHjj9OLGzU4q24zeJycBDo6irQ==", + "version": "0.9.4-ea.2", + "resolved": "https://npm.pkg.github.com/download/@fabric8-analytics/fabric8-analytics-lsp-server/0.9.4-ea.2/ca0ace6cc0f37bdf19993529aa18ce9094654801", + "integrity": "sha512-mdIiga72ISXBzAa0XNtwWLdMOjukGHZXDPpGABvP4E3NyTdfL9wGti3mV2S9oyU5wUy3LsWZBgkLXLDYrboBfA==", "license": "Apache-2.0", "dependencies": { "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.14", diff --git a/package.json b/package.json index 8a279c82f..e239d50f3 100644 --- a/package.json +++ b/package.json @@ -48,12 +48,12 @@ "contributes": { "commands": [ { - "command": "fabric8.stackAnalysis", + "command": "rhda.stackAnalysis", "title": "Red Hat Dependency Analytics Report...", "category": "Red Hat Dependency Analytics" }, { - "command": "fabric8.stackAnalysisFromPieBtn", + "command": "rhda.stackAnalysisFromPieBtn", "title": "Open Red Hat Dependency Analytics Report", "icon": { "light": "icon/report-icon.png", @@ -61,89 +61,94 @@ } }, { - "command": "fabric8.stackAnalysisFromEditor", + "command": "rhda.stackAnalysisFromEditor", "title": "Red Hat Dependency Analytics Report..." }, { - "command": "fabric8.stackAnalysisFromExplorer", + "command": "rhda.stackAnalysisFromExplorer", "title": "Red Hat Dependency Analytics Report..." }, { - "command": "fabric8.fabric8AnalyticsStackLogs", + "command": "rhda.stackLogs", "title": "Debug logs", "category": "Red Hat Dependency Analytics" + }, + { + "command": "rhda.setSnykToken", + "title": "Set Snyk Token", + "category": "Red Hat Dependency Analytics" } ], "menus": { "editor/title": [ { - "command": "fabric8.stackAnalysisFromPieBtn", + "command": "rhda.stackAnalysisFromPieBtn", "when": "resourceFilename == pom.xml", "group": "navigation" }, { - "command": "fabric8.stackAnalysisFromPieBtn", + "command": "rhda.stackAnalysisFromPieBtn", "when": "resourceFilename == package.json", "group": "navigation" }, { - "command": "fabric8.stackAnalysisFromPieBtn", + "command": "rhda.stackAnalysisFromPieBtn", "when": "resourceFilename == go.mod", "group": "navigation" }, { - "command": "fabric8.stackAnalysisFromPieBtn", + "command": "rhda.stackAnalysisFromPieBtn", "when": "resourceFilename == requirements.txt", "group": "navigation" } ], "explorer/context": [ { - "command": "fabric8.stackAnalysisFromExplorer", + "command": "rhda.stackAnalysisFromExplorer", "when": "resourceFilename == package.json" }, { - "command": "fabric8.stackAnalysisFromExplorer", + "command": "rhda.stackAnalysisFromExplorer", "when": "resourceFilename == pom.xml" }, { - "command": "fabric8.stackAnalysisFromExplorer", + "command": "rhda.stackAnalysisFromExplorer", "when": "resourceFilename == go.mod" }, { - "command": "fabric8.stackAnalysisFromExplorer", + "command": "rhda.stackAnalysisFromExplorer", "when": "resourceFilename == requirements.txt" } ], "editor/context": [ { - "command": "fabric8.stackAnalysisFromEditor", + "command": "rhda.stackAnalysisFromEditor", "when": "resourceFilename == package.json" }, { - "command": "fabric8.stackAnalysisFromEditor", + "command": "rhda.stackAnalysisFromEditor", "when": "resourceFilename == pom.xml" }, { - "command": "fabric8.stackAnalysisFromEditor", + "command": "rhda.stackAnalysisFromEditor", "when": "resourceFilename == go.mod" }, { - "command": "fabric8.stackAnalysisFromEditor", + "command": "rhda.stackAnalysisFromEditor", "when": "resourceFilename == requirements.txt" } ], "commandPalette": [ { - "command": "fabric8.stackAnalysisFromPieBtn", + "command": "rhda.stackAnalysisFromPieBtn", "when": "false" }, { - "command": "fabric8.stackAnalysisFromEditor", + "command": "rhda.stackAnalysisFromEditor", "when": "false" }, { - "command": "fabric8.stackAnalysisFromExplorer", + "command": "rhda.stackAnalysisFromExplorer", "when": "false" } ] @@ -168,10 +173,13 @@ "default": "off", "description": "Traces the communication between VSCode and the Red Hat Dependency Analytics Language Server." }, - "redHatDependencyAnalytics.exhortSnykToken": { + "redHatDependencyAnalytics.snykTokenStorage": { "type": "string", - "default": "", - "description": "Red Hat Dependency Analytics authentication token for Snyk.", + "enum": [ + "Always use VS Code's secret storage" + ], + "default": "Always use VS Code's secret storage", + "markdownDescription": "Red Hat Dependency Analytics uses VS Code's [secret storage](https://code.visualstudio.com/api/references/vscode-api#SecretStorage) to safely store the authentication token for Snyk. To set the token, run the VS Code command [RHDA: Set Snyk Token](command:rhda.setSnykToken).", "scope": "window" }, "redHatDependencyAnalytics.matchManifestVersions": { @@ -279,11 +287,11 @@ "webpack-cli": "^5.1.4" }, "dependencies": { - "@fabric8-analytics/fabric8-analytics-lsp-server": "^0.9.4-ea.1", + "@fabric8-analytics/fabric8-analytics-lsp-server": "^0.9.4-ea.2", "@redhat-developer/vscode-redhat-telemetry": "^0.7.0", "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.14", "fs": "^0.0.1-security", "path": "^0.12.7", "vscode-languageclient": "^8.1.0" } -} \ No newline at end of file +} diff --git a/src/caStatusBarProvider.ts b/src/caStatusBarProvider.ts index a47eec172..eee8d4042 100644 --- a/src/caStatusBarProvider.ts +++ b/src/caStatusBarProvider.ts @@ -27,7 +27,7 @@ class CAStatusBarProvider implements Disposable { this.statusBarItem.text = text; this.statusBarItem.command = { title: PromptText.FULL_STACK_PROMPT_TEXT, - command: commands.TRIGGER_FULL_STACK_ANALYSIS_FROM_STATUS_BAR, + command: commands.STACK_ANALYSIS_FROM_STATUS_BAR_COMMAND, arguments: [Uri.parse(uri)] }; this.statusBarItem.tooltip = PromptText.FULL_STACK_PROMPT_TEXT; @@ -41,7 +41,7 @@ class CAStatusBarProvider implements Disposable { this.statusBarItem.text = `$(error) RHDA analysis has failed`; this.statusBarItem.command = { title: PromptText.LSP_FAILURE_TEXT, - command: commands.TRIGGER_STACK_LOGS, + command: commands.STACK_LOGS_COMMAND, }; } diff --git a/src/commands.ts b/src/commands.ts index d3600da01..1ab8c88b9 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -3,10 +3,11 @@ /** * Commonly used commands to trigger Stack Analysis and supporting actions */ -export const TRIGGER_FULL_STACK_ANALYSIS = 'fabric8.stackAnalysis'; -export const TRIGGER_FULL_STACK_ANALYSIS_FROM_STATUS_BAR = 'fabric8.stackAnalysisFromStatusBar'; -export const TRIGGER_FULL_STACK_ANALYSIS_FROM_EXPLORER = 'fabric8.stackAnalysisFromExplorer'; -export const TRIGGER_FULL_STACK_ANALYSIS_FROM_PIE_BTN = 'fabric8.stackAnalysisFromPieBtn'; -export const TRIGGER_FULL_STACK_ANALYSIS_FROM_EDITOR = 'fabric8.stackAnalysisFromEditor'; -export const TRIGGER_STACK_LOGS = 'fabric8.fabric8AnalyticsStackLogs'; -export const TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION = 'fabric8.RHRepositoryRecommendationNotification'; \ No newline at end of file +export const STACK_ANALYSIS_COMMAND = 'rhda.stackAnalysis'; +export const STACK_ANALYSIS_FROM_STATUS_BAR_COMMAND = 'rhda.stackAnalysisFromStatusBar'; +export const STACK_ANALYSIS_FROM_EXPLORER_COMMAND = 'rhda.stackAnalysisFromExplorer'; +export const STACK_ANALYSIS_FROM_PIE_BTN_COMMAND = 'rhda.stackAnalysisFromPieBtn'; +export const STACK_ANALYSIS_FROM_EDITOR_COMMAND = 'rhda.stackAnalysisFromEditor'; +export const STACK_LOGS_COMMAND = 'rhda.stackLogs'; +export const REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION_COMMAND = 'rhda.rhRepositoryRecommendationNotification'; +export const SET_SNYK_TOKEN_COMMAND = 'rhda.setSnykToken'; \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index e64f6aace..f554b6b87 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; -import { GlobalState, defaultRhdaReportFilePath } from './constants'; +import { GlobalState, DEFAULT_RHDA_REPORT_FILE_PATH, SNYK_TOKEN_KEY } from './constants'; import * as commands from './commands'; import { getTelemetryId } from './redhatTelemetry'; @@ -11,10 +11,9 @@ import { getTelemetryId } from './redhatTelemetry'; */ class Config { telemetryId: string; - triggerFullStackAnalysis: string; - triggerRHRepositoryRecommendationNotification: string; + stackAnalysisCommand: string; + rhRepositoryRecommendationNotificationCommand: string; utmSource: string; - exhortSnykToken: string; matchManifestVersions: string; vulnerabilityAlertSeverity: string; exhortMvnPath: string; @@ -25,6 +24,7 @@ class Config { exhortPythonPath: string; exhortPipPath: string; rhdaReportFilePath: string; + secrets: vscode.SecretStorage; private readonly DEFAULT_MVN_EXECUTABLE = 'mvn'; private readonly DEFAULT_NPM_EXECUTABLE = 'npm'; @@ -40,7 +40,6 @@ class Config { */ constructor() { this.loadData(); - this.setProcessEnv(); } /** @@ -58,15 +57,14 @@ class Config { loadData() { const rhdaConfig = this.getRhdaConfig(); - this.triggerFullStackAnalysis = commands.TRIGGER_FULL_STACK_ANALYSIS; - this.triggerRHRepositoryRecommendationNotification = commands.TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION; + this.stackAnalysisCommand = commands.STACK_ANALYSIS_COMMAND; + this.rhRepositoryRecommendationNotificationCommand = commands.REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION_COMMAND; this.utmSource = GlobalState.UTM_SOURCE; - this.exhortSnykToken = rhdaConfig.exhortSnykToken; /* istanbul ignore next */ this.matchManifestVersions = rhdaConfig.matchManifestVersions ? 'true' : 'false'; this.vulnerabilityAlertSeverity = rhdaConfig.vulnerabilityAlertSeverity; /* istanbul ignore next */ - this.rhdaReportFilePath = rhdaConfig.reportFilePath || defaultRhdaReportFilePath; + this.rhdaReportFilePath = rhdaConfig.reportFilePath || DEFAULT_RHDA_REPORT_FILE_PATH; this.exhortMvnPath = rhdaConfig.mvn.executable.path || this.DEFAULT_MVN_EXECUTABLE; this.exhortNpmPath = rhdaConfig.npm.executable.path || this.DEFAULT_NPM_EXECUTABLE; this.exhortGoPath = rhdaConfig.go.executable.path || this.DEFAULT_GO_EXECUTABLE; @@ -80,11 +78,10 @@ class Config { * Sets process environment variables based on configuration settings. * @private */ - private setProcessEnv() { - process.env['VSCEXT_TRIGGER_FULL_STACK_ANALYSIS'] = this.triggerFullStackAnalysis; - process.env['VSCEXT_TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION'] = this.triggerRHRepositoryRecommendationNotification; + private async setProcessEnv(): Promise { + process.env['VSCEXT_STACK_ANALYSIS_COMMAND'] = this.stackAnalysisCommand; + process.env['VSCEXT_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION_COMMAND'] = this.rhRepositoryRecommendationNotificationCommand; process.env['VSCEXT_UTM_SOURCE'] = this.utmSource; - process.env['VSCEXT_EXHORT_SNYK_TOKEN'] = this.exhortSnykToken; process.env['VSCEXT_MATCH_MANIFEST_VERSIONS'] = this.matchManifestVersions; process.env['VSCEXT_VULNERABILITY_ALERT_SEVERITY'] = this.vulnerabilityAlertSeverity; process.env['VSCEXT_EXHORT_MVN_PATH'] = this.exhortMvnPath; @@ -94,15 +91,70 @@ class Config { process.env['VSCEXT_EXHORT_PIP3_PATH'] = this.exhortPip3Path; process.env['VSCEXT_EXHORT_PYTHON_PATH'] = this.exhortPythonPath; process.env['VSCEXT_EXHORT_PIP_PATH'] = this.exhortPipPath; + process.env['VSCEXT_TELEMETRY_ID'] = this.telemetryId; + + const token = await this.getSnykToken(); + process.env['VSCEXT_EXHORT_SNYK_TOKEN'] = token; } /** * Authorizes the RHDA (Red Hat Dependency Analytics) service. * @param context The extension context for authorization. */ - async authorizeRHDA(context) { + async authorizeRHDA(context): Promise { this.telemetryId = await getTelemetryId(context); - process.env['VSCEXT_TELEMETRY_ID'] = this.telemetryId; + await this.setProcessEnv(); + } + + /** + * Links the secret storage to the configuration object. + * @param context The extension context. + */ + linkToSecretStorage(context) { + this.secrets = context.secrets; + } + + /** + * Sets the Snyk token. + * @param token The Snyk token. + * @returns A Promise that resolves when the token is set. + */ + async setSnykToken(token: string | undefined): Promise { + if (!token) { return; } + + try { + await this.secrets.store(SNYK_TOKEN_KEY, token); + } catch (error) { + vscode.window.showErrorMessage(`Failed to save Snyk token to VSCode Secret Storage, Error: ${error.message}`); + } + } + + /** + * Gets the Snyk token. + * @returns A Promise that resolves with the Snyk token. + */ + async getSnykToken(): Promise { + try { + const token = await this.secrets.get(SNYK_TOKEN_KEY); + return token || ''; + } catch (error) { + vscode.window.showErrorMessage(`Failed to get Snyk token from VSCode Secret Storage, Error: ${error.message}`); + await this.clearSnykToken(); + return ''; + } + } + + /** + * Clears the Snyk token. + * @returns A Promise that resolves when the token is cleared. + * @private + */ + private async clearSnykToken(): Promise { + try { + await this.secrets.delete(SNYK_TOKEN_KEY); + } catch (error) { + console.error(`Error while deleting Snyk token from VSCode Secret Storage, Error: ${error.message}`); + } } } diff --git a/src/constants.ts b/src/constants.ts index 9000cc88d..c38131913 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -19,7 +19,7 @@ export enum StatusMessages { } export enum PromptText { - FULL_STACK_PROMPT_TEXT = `Open detailed vulnerability report`, + FULL_STACK_PROMPT_TEXT = `Open Red Hat Dependency Analytics Report`, LSP_FAILURE_TEXT = `Open the output window`, } @@ -29,16 +29,18 @@ export enum Titles { } // Refer `name` from package.json -export const extensionId = 'fabric8-analytics'; +export const EXTENSION_ID = 'fabric8-analytics'; // publisher.name from package.json -export const extensionQualifiedId = `redhat.${extensionId}`; +export const EXTENSION_QUALIFIED_ID = `redhat.${EXTENSION_ID}`; // UTM -export const registrationURL = 'https://app.snyk.io/signup/?utm_medium=Partner&utm_source=RedHat&utm_campaign=Code-Ready-Analytics-2020&utm_content=Register'; +export const REGISTRATION_URL = 'https://app.snyk.io/signup/?utm_medium=Partner&utm_source=RedHat&utm_campaign=Code-Ready-Analytics-2020&utm_content=Register'; +// Key for Snyk token secret +export const SNYK_TOKEN_KEY = 'rhda.snykToken'; // URL to Snyk webpage -export const snykURL = 'https://app.snyk.io/login?utm_campaign=Code-Ready-Analytics-2020&utm_source=code_ready&code_ready=FF1B53D9-57BE-4613-96D7-1D06066C38C9'; +export const SNYK_URL = 'https://app.snyk.io/login?utm_campaign=Code-Ready-Analytics-2020&utm_source=code_ready&code_ready=FF1B53D9-57BE-4613-96D7-1D06066C38C9'; // default Redhat Dependency Analytics report file path -export const defaultRhdaReportFilePath = '/tmp/redhatDependencyAnalyticsReport.html'; +export const DEFAULT_RHDA_REPORT_FILE_PATH = '/tmp/redhatDependencyAnalyticsReport.html'; // Red Hat GA Repository -export const redhatMavenRepository = 'https://maven.repository.redhat.com/ga/'; +export const REDHAT_MAVEN_REPOSITORY = 'https://maven.repository.redhat.com/ga/'; // Red Hat GA Repository documentation -export const redhatMavenRepositoryDocumentationURL = 'https://access.redhat.com/maven-repository'; +export const REDHAT_MAVEN_REPOSITORY_DOCUMENTATION_URL = 'https://access.redhat.com/maven-repository'; diff --git a/src/exhortServices.ts b/src/exhortServices.ts index 984908361..d2d688d3a 100644 --- a/src/exhortServices.ts +++ b/src/exhortServices.ts @@ -1,6 +1,5 @@ 'use strict'; -import * as vscode from 'vscode'; import exhort from '@RHEcosystemAppEng/exhort-javascript-api'; /** @@ -9,7 +8,7 @@ import exhort from '@RHEcosystemAppEng/exhort-javascript-api'; * @param options Additional options for the analysis. * @returns A promise resolving to the stack analysis report in HTML format. */ -function stackAnalysisService(pathToManifest, options) { +function stackAnalysisService(pathToManifest, options): Promise { return new Promise(async (resolve, reject) => { try { // Get stack analysis in HTML format @@ -27,7 +26,7 @@ function stackAnalysisService(pathToManifest, options) { * @param source The source for which the token is being validated. Example values: 'Snyk', 'OSS Index'. * @returns A promise resolving after validating the token. */ -async function tokenValidationService(options, source) { +async function tokenValidationService(options, source): Promise { try { // Get token validation status code @@ -36,28 +35,28 @@ async function tokenValidationService(options, source) { if ( tokenValidationStatus === 200 ) { - vscode.window.showInformationMessage(`${source} Token Validated Successfully`); + return; } else if ( tokenValidationStatus === 400 ) { - vscode.window.showWarningMessage(`Missing token. Please provide a valid ${source} Token in the extension workspace settings. Status: ${tokenValidationStatus}`); + return `Missing token. Please provide a valid ${source} Token in the extension workspace settings. Status: ${tokenValidationStatus}`; } else if ( tokenValidationStatus === 401 ) { - vscode.window.showWarningMessage(`Invalid token. Please provide a valid ${source} Token in the extension workspace settings. Status: ${tokenValidationStatus}`); + return `Invalid token. Please provide a valid ${source} Token in the extension workspace settings. Status: ${tokenValidationStatus}`; } else if ( tokenValidationStatus === 403 ) { - vscode.window.showWarningMessage(`Forbidden. The token does not have permissions. Please provide a valid ${source} Token in the extension workspace settings. Status: ${tokenValidationStatus}`); + return `Forbidden. The token does not have permissions. Please provide a valid ${source} Token in the extension workspace settings. Status: ${tokenValidationStatus}`; } else if ( tokenValidationStatus === 429 ) { - vscode.window.showWarningMessage(`Too many requests. Rate limit exceeded. Please try again in a little while. Status: ${tokenValidationStatus}`); + return `Too many requests. Rate limit exceeded. Please try again in a little while. Status: ${tokenValidationStatus}`; } else { - vscode.window.showWarningMessage(`Failed to validate token. Status: ${tokenValidationStatus}`); + return `Failed to validate token. Status: ${tokenValidationStatus}`; } } catch (error) { - vscode.window.showErrorMessage(`Failed to validate token, Error: ${error.message}`); + return `Failed to validate token, Error: ${error.message}`; } } diff --git a/src/extension.ts b/src/extension.ts index 3f0a17086..3f7031164 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -10,7 +10,7 @@ import { } from 'vscode-languageclient/node'; import * as commands from './commands'; -import { GlobalState, extensionQualifiedId, redhatMavenRepository, redhatMavenRepositoryDocumentationURL } from './constants'; +import { GlobalState, EXTENSION_QUALIFIED_ID, REDHAT_MAVEN_REPOSITORY, REDHAT_MAVEN_REPOSITORY_DOCUMENTATION_URL, SNYK_TOKEN_KEY } from './constants'; import { generateRHDAReport } from './stackAnalysis'; import { globalConfig } from './config'; import { StatusMessages, PromptText } from './constants'; @@ -29,13 +29,15 @@ export let outputChannelDep: DepOutputChannel; * @param context - The extension context. */ export function activate(context: vscode.ExtensionContext) { + globalConfig.linkToSecretStorage(context); + startUp(context); // show welcome message after first install or upgrade showUpdateNotification(context); const disposableStackAnalysisCommand = vscode.commands.registerCommand( - commands.TRIGGER_FULL_STACK_ANALYSIS, + commands.STACK_ANALYSIS_COMMAND, async (uri: vscode.Uri) => { // uri will be null in case the user has used the context menu/file explorer const fileUri = uri ? uri : vscode.window.activeTextEditor.document.uri; @@ -50,7 +52,7 @@ export function activate(context: vscode.ExtensionContext) { ); const disposableStackLogsCommand = vscode.commands.registerCommand( - commands.TRIGGER_STACK_LOGS, + commands.STACK_LOGS_COMMAND, () => { if (outputChannelDep) { outputChannelDep.showOutputChannel(); @@ -61,16 +63,31 @@ export function activate(context: vscode.ExtensionContext) { ); const rhRepositoryRecommendationNotification = vscode.commands.registerCommand( - commands.TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION, + commands.REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION_COMMAND, () => { const msg = `Important: If you apply Red Hat Dependency Analytics recommendations, - make sure the Red Hat GA Repository (${redhatMavenRepository}) has been added to your project configuration. + make sure the Red Hat GA Repository (${REDHAT_MAVEN_REPOSITORY}) has been added to your project configuration. This ensures that the applied dependencies work correctly. - Learn how to add the repository: [Click here](${redhatMavenRepositoryDocumentationURL})`; + Learn how to add the repository: [Click here](${REDHAT_MAVEN_REPOSITORY_DOCUMENTATION_URL})`; vscode.window.showWarningMessage(msg); } ); + const disposableSetSnykToken = vscode.commands.registerCommand( + commands.SET_SNYK_TOKEN_COMMAND, + async () => { + const token = await vscode.window.showInputBox({ + prompt: 'Please enter your Snyk Token:', + placeHolder: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + password: true, + validateInput: validateSnykToken + }); + + if (!token) { return; } + await globalConfig.setSnykToken(token); + } + ); + registerStackAnalysisCommands(context); globalConfig.authorizeRHDA(context) @@ -115,12 +132,13 @@ export function activate(context: vscode.ExtensionContext) { serverOptions, clientOptions ); + lspClient.start().then(() => { const showVulnerabilityFoundPrompt = async (msg: string, fileName: string) => { const selection = await vscode.window.showWarningMessage(`${msg}`, PromptText.FULL_STACK_PROMPT_TEXT); if (selection === PromptText.FULL_STACK_PROMPT_TEXT) { - vscode.commands.executeCommand(commands.TRIGGER_FULL_STACK_ANALYSIS); + vscode.commands.executeCommand(commands.STACK_ANALYSIS_COMMAND); record(context, TelemetryActions.vulnerabilityReportPopupOpened, { manifest: fileName, fileName: fileName }); } else { @@ -148,10 +166,12 @@ export function activate(context: vscode.ExtensionContext) { record(context, TelemetryActions.componentAnalysisFailed, { manifest: path.basename(notification.origin()), fileName: path.basename(notification.origin()), error: notification.errorMsg() }); }); }); + context.subscriptions.push( disposableStackAnalysisCommand, disposableStackLogsCommand, rhRepositoryRecommendationNotification, + disposableSetSnykToken, caStatusBarProvider, ); }) @@ -160,12 +180,14 @@ export function activate(context: vscode.ExtensionContext) { throw (error); }); - vscode.workspace.onDidChangeConfiguration((event) => { - + vscode.workspace.onDidChangeConfiguration(() => { globalConfig.loadData(); + }); - if (event.affectsConfiguration('redHatDependencyAnalytics.exhortSnykToken')) { - validateSnykToken(); + context.secrets.onDidChange(async (e) => { + if (e.key === SNYK_TOKEN_KEY) { + const token = await globalConfig.getSnykToken(); + lspClient.sendNotification('snykTokenModified', token); } }); } @@ -188,7 +210,7 @@ export function deactivate(): Thenable { */ async function showUpdateNotification(context: vscode.ExtensionContext) { - const packageJSON = vscode.extensions.getExtension(extensionQualifiedId).packageJSON; + const packageJSON = vscode.extensions.getExtension(EXTENSION_QUALIFIED_ID).packageJSON; const version = packageJSON.version; const previousVersion = context.globalState.get(GlobalState.VERSION); @@ -240,10 +262,10 @@ function registerStackAnalysisCommands(context: vscode.ExtensionContext) { }; const stackAnalysisCommands = [ - registerCommand(commands.TRIGGER_FULL_STACK_ANALYSIS_FROM_EDITOR, TelemetryActions.vulnerabilityReportEditor), - registerCommand(commands.TRIGGER_FULL_STACK_ANALYSIS_FROM_EXPLORER, TelemetryActions.vulnerabilityReportExplorer), - registerCommand(commands.TRIGGER_FULL_STACK_ANALYSIS_FROM_PIE_BTN, TelemetryActions.vulnerabilityReportPieBtn), - registerCommand(commands.TRIGGER_FULL_STACK_ANALYSIS_FROM_STATUS_BAR, TelemetryActions.vulnerabilityReportStatusBar), + registerCommand(commands.STACK_ANALYSIS_FROM_EDITOR_COMMAND, TelemetryActions.vulnerabilityReportEditor), + registerCommand(commands.STACK_ANALYSIS_FROM_EXPLORER_COMMAND, TelemetryActions.vulnerabilityReportExplorer), + registerCommand(commands.STACK_ANALYSIS_FROM_PIE_BTN_COMMAND, TelemetryActions.vulnerabilityReportPieBtn), + registerCommand(commands.STACK_ANALYSIS_FROM_STATUS_BAR_COMMAND, TelemetryActions.vulnerabilityReportStatusBar), ]; context.subscriptions.push(...stackAnalysisCommands); diff --git a/src/stackAnalysis.ts b/src/stackAnalysis.ts index bcbd210ae..8e0ed33ae 100644 --- a/src/stackAnalysis.ts +++ b/src/stackAnalysis.ts @@ -78,8 +78,10 @@ async function executeStackAnalysis(manifestFilePath): Promise { 'EXHORT_PIP_PATH': globalConfig.exhortPipPath }; - if (globalConfig.exhortSnykToken !== '') { - options['EXHORT_SNYK_TOKEN'] = globalConfig.exhortSnykToken; + const snykToken = await globalConfig.getSnykToken(); + /* istanbul ignore else */ + if (snykToken !== '') { + options['EXHORT_SNYK_TOKEN'] = snykToken; } // execute stack analysis diff --git a/src/tokenValidation.ts b/src/tokenValidation.ts index 6dc1452fc..db0931636 100644 --- a/src/tokenValidation.ts +++ b/src/tokenValidation.ts @@ -1,33 +1,33 @@ 'use strict'; -import * as vscode from 'vscode'; - import { globalConfig } from './config'; -import { snykURL } from './constants'; +import { SNYK_URL } from './constants'; import { tokenValidationService } from './exhortServices'; /** * Validates the Snyk token using the Exhort token validation service. * @returns A Promise that resolves when token has been validated. */ -async function validateSnykToken() { - if (globalConfig.exhortSnykToken !== '') { +async function validateSnykToken(token: string): Promise { + if (token !== '') { // set up configuration options for the token validation request const options = { 'RHDA_TOKEN': globalConfig.telemetryId, 'RHDA_SOURCE': globalConfig.utmSource, - 'EXHORT_SNYK_TOKEN': globalConfig.exhortSnykToken + 'EXHORT_SNYK_TOKEN': token }; // execute token validation - tokenValidationService(options, 'Snyk'); + const response = await tokenValidationService(options, 'Snyk'); + + return response; } else { - vscode.window.showInformationMessage(`Please note that if you fail to provide a valid Snyk Token in the extension workspace settings, + return `Please note that if you fail to provide a valid Snyk Token in the extension workspace settings, Snyk vulnerabilities will not be displayed. - To resolve this issue, please obtain a valid token from the following link: [here](${snykURL}).`); + To resolve this issue, please obtain a valid token from the following link: [here](${SNYK_URL}).`; } } diff --git a/test/caStatusBarProvider.test.ts b/test/caStatusBarProvider.test.ts index 683339b45..8859977f3 100644 --- a/test/caStatusBarProvider.test.ts +++ b/test/caStatusBarProvider.test.ts @@ -31,7 +31,7 @@ suite('CAStatusBarProvider module', () => { expect(caStatusBarProvider['statusBarItem'].tooltip).to.equal(PromptText.FULL_STACK_PROMPT_TEXT); expect(caStatusBarProvider['statusBarItem'].command).to.deep.equal({ title: PromptText.FULL_STACK_PROMPT_TEXT, - command: commands.TRIGGER_FULL_STACK_ANALYSIS_FROM_STATUS_BAR, + command: commands.STACK_ANALYSIS_FROM_STATUS_BAR_COMMAND, arguments: [vscode.Uri.parse(uri)] }); }); @@ -42,7 +42,7 @@ suite('CAStatusBarProvider module', () => { expect(caStatusBarProvider['statusBarItem'].text).to.equal('$(error) RHDA analysis has failed'); expect(caStatusBarProvider['statusBarItem'].command).to.deep.equal({ title: PromptText.LSP_FAILURE_TEXT, - command: commands.TRIGGER_STACK_LOGS, + command: commands.STACK_LOGS_COMMAND, }); }); diff --git a/test/config.test.ts b/test/config.test.ts index 3533bca31..8ea5ba7c4 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -1,9 +1,10 @@ import * as chai from 'chai'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; +import * as vscode from 'vscode'; import { globalConfig } from '../src/config'; -import { GlobalState } from '../src/constants'; +import { GlobalState, SNYK_TOKEN_KEY } from '../src/constants'; import * as commands from '../src/commands'; import * as redhatTelemetry from '../src/redhatTelemetry'; import { context } from './vscontext.mock'; @@ -14,6 +15,10 @@ chai.use(sinonChai); suite('Config module', () => { let sandbox: sinon.SinonSandbox; + const mockToken = 'mockToken'; + const mockId = 'mockId'; + const mockedError = new Error('Mock Error Message'); + setup(() => { sandbox = sinon.createSandbox(); }); @@ -24,10 +29,9 @@ suite('Config module', () => { test('should initialize Config properties with default extension settings', async () => { - expect(globalConfig.triggerFullStackAnalysis).to.eq(commands.TRIGGER_FULL_STACK_ANALYSIS); - expect(globalConfig.triggerRHRepositoryRecommendationNotification).to.eq(commands.TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION); + expect(globalConfig.stackAnalysisCommand).to.eq(commands.STACK_ANALYSIS_COMMAND); + expect(globalConfig.rhRepositoryRecommendationNotificationCommand).to.eq(commands.REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION_COMMAND); expect(globalConfig.utmSource).to.eq(GlobalState.UTM_SOURCE); - expect(globalConfig.exhortSnykToken).to.eq(''); expect(globalConfig.matchManifestVersions).to.eq('true'); expect(globalConfig.vulnerabilityAlertSeverity).to.eq('Error'); expect(globalConfig.rhdaReportFilePath).to.eq('/tmp/redhatDependencyAnalyticsReport.html'); @@ -38,9 +42,25 @@ suite('Config module', () => { expect(globalConfig.exhortPip3Path).to.eq('pip3'); expect(globalConfig.exhortPythonPath).to.eq('python'); expect(globalConfig.exhortPipPath).to.eq('pip'); + }); + + test('should retrieve telemetry parameters from getTelemetryId and set process environment variables', async () => { + sandbox.stub(redhatTelemetry, 'getTelemetryId').resolves(mockId); + + globalConfig.linkToSecretStorage({ + secrets: { + store: () => sandbox.stub(), + get: () => '', + delete: () => sandbox.stub() + } + }) + + await globalConfig.authorizeRHDA(context); + + expect(globalConfig.telemetryId).to.equal(mockId); - expect(process.env['VSCEXT_TRIGGER_FULL_STACK_ANALYSIS']).to.eq(commands.TRIGGER_FULL_STACK_ANALYSIS); - expect(process.env['VSCEXT_TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION']).to.eq(commands.TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION); + expect(process.env['VSCEXT_STACK_ANALYSIS_COMMAND']).to.eq(commands.STACK_ANALYSIS_COMMAND); + expect(process.env['VSCEXT_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION_COMMAND']).to.eq(commands.REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION_COMMAND); expect(process.env['VSCEXT_UTM_SOURCE']).to.eq(GlobalState.UTM_SOURCE); expect(process.env['VSCEXT_EXHORT_SNYK_TOKEN']).to.eq(''); expect(process.env['VSCEXT_MATCH_MANIFEST_VERSIONS']).to.eq('true'); @@ -52,14 +72,131 @@ suite('Config module', () => { expect(process.env['VSCEXT_EXHORT_PIP3_PATH']).to.eq('pip3'); expect(process.env['VSCEXT_EXHORT_PYTHON_PATH']).to.eq('python'); expect(process.env['VSCEXT_EXHORT_PIP_PATH']).to.eq('pip'); + expect(process.env['VSCEXT_TELEMETRY_ID']).to.equal(mockId); + expect(process.env['VSCEXT_EXHORT_SNYK_TOKEN']).to.equal(''); }); - test('should call retrieve telemetry parameters from getTelemetryId', async () => { - sandbox.stub(redhatTelemetry, 'getTelemetryId').resolves('mockId'); + test('should set Snyk token in VSCode SecretStorage', async () => { - await globalConfig.authorizeRHDA(context); + globalConfig.linkToSecretStorage({ + secrets: { + store: () => sandbox.stub(), + get: () => '', + delete: () => sandbox.stub() + } + }); + + const showErrorMessageSpy = sandbox.spy(vscode.window, 'showErrorMessage'); + const storeSecretSpy = sandbox.spy(globalConfig.secrets, 'store'); + + await globalConfig.setSnykToken(mockToken); + + expect(storeSecretSpy).to.have.been.calledWith(SNYK_TOKEN_KEY, mockToken); + expect(showErrorMessageSpy.called).to.be.false; + }); + + test('should fail to set Snyk token in VSCode SecretStorage', async () => { + const showErrorMessageSpy = sandbox.spy(vscode.window, 'showErrorMessage') + + globalConfig.linkToSecretStorage({ + secrets: { + store: async () => { + throw mockedError; + }, + get: () => '', + delete: () => sandbox.stub() + } + }); + + await globalConfig.setSnykToken(mockToken); + + expect(showErrorMessageSpy).to.have.been.calledWith(`Failed to save Snyk token to VSCode Secret Storage, Error: ${mockedError.message}`); + }); + + test('should not set Snyk token in VSCode SecretStorage when token is undefined', async () => { + + globalConfig.linkToSecretStorage({ + secrets: { + store: () => sandbox.stub(), + get: () => '', + delete: () => sandbox.stub() + } + }); + + const showErrorMessageSpy = sandbox.spy(vscode.window, 'showErrorMessage'); + const storeSecretSpy = sandbox.spy(globalConfig.secrets, 'store'); + + await globalConfig.setSnykToken(undefined); + + expect(storeSecretSpy.called).to.be.false; + expect(showErrorMessageSpy.called).to.be.false; + }); + + test('should get Snyk token from VSCode SecretStorage', async () => { + + globalConfig.linkToSecretStorage({ + secrets: { + store: () => sandbox.stub(), + get: () => mockToken, + delete: () => sandbox.stub() + } + }); + + expect(await globalConfig.getSnykToken()).to.equal(mockToken); + }); + + test('should get Snyk token from VSCode SecretStorage and return empty string if token is undefined', async () => { + + globalConfig.linkToSecretStorage({ + secrets: { + store: () => sandbox.stub(), + get: () => undefined, + delete: () => sandbox.stub() + } + }); + + expect(await globalConfig.getSnykToken()).to.equal(''); + }); + + test('should fail to get Snyk token from VSCode SecretStorage', async () => { + + globalConfig.linkToSecretStorage({ + secrets: { + store: () => sandbox.stub(), + get: async () => { + throw mockedError; + }, + delete: () => sandbox.stub() + } + }); + + const showErrorMessageSpy = sandbox.spy(vscode.window, 'showErrorMessage'); + sandbox.spy(globalConfig.secrets, 'delete'); + + expect(await globalConfig.getSnykToken()).to.equal(''); + expect(showErrorMessageSpy).to.have.been.calledWith(`Failed to get Snyk token from VSCode Secret Storage, Error: ${mockedError.message}`); + expect(globalConfig.secrets.delete).to.be.called; + }); + + test('should fail to delete Snyk token from VSCode SecretStorage', async () => { + + globalConfig.linkToSecretStorage({ + secrets: { + store: () => sandbox.stub(), + get: async () => { + throw mockedError; + }, + delete: async () => { + throw mockedError; + }, + } + }); + + const showErrorMessageSpy = sandbox.spy(vscode.window, 'showErrorMessage'); + sandbox.spy(globalConfig.secrets, 'delete'); - expect(globalConfig.telemetryId).to.equal('mockId'); - expect(process.env['VSCEXT_TELEMETRY_ID']).to.equal('mockId'); + expect(await globalConfig.getSnykToken()).to.equal(''); + expect(showErrorMessageSpy).to.have.been.calledWith(`Failed to get Snyk token from VSCode Secret Storage, Error: ${mockedError.message}`); + expect(globalConfig.secrets.delete).to.be.called; }); }); diff --git a/test/dependencyReportPanel.test.ts b/test/dependencyReportPanel.test.ts index a1df5b3fe..075ca05a9 100644 --- a/test/dependencyReportPanel.test.ts +++ b/test/dependencyReportPanel.test.ts @@ -6,7 +6,7 @@ import * as fs from 'fs'; import { DependencyReportPanel } from '../src/dependencyReportPanel'; import * as Templates from '../src/template'; -import { defaultRhdaReportFilePath } from '../src/constants'; +import { DEFAULT_RHDA_REPORT_FILE_PATH } from '../src/constants'; const expect = chai.expect; chai.use(sinonChai); @@ -86,8 +86,8 @@ suite('DependencyReportPanel Modules', () => { DependencyReportPanel.currentPanel.dispose(); - expect(existsSyncStub).to.be.calledWith(defaultRhdaReportFilePath); - expect(unlinkSyncStub).to.be.calledWith(defaultRhdaReportFilePath); + expect(existsSyncStub).to.be.calledWith(DEFAULT_RHDA_REPORT_FILE_PATH); + expect(unlinkSyncStub).to.be.calledWith(DEFAULT_RHDA_REPORT_FILE_PATH); expect(DependencyReportPanel.data).equals(null); expect(DependencyReportPanel.currentPanel).equals(undefined); }); diff --git a/test/exhortServices.test.ts b/test/exhortServices.test.ts index 6e68f327d..a86a6a03d 100644 --- a/test/exhortServices.test.ts +++ b/test/exhortServices.test.ts @@ -19,21 +19,12 @@ suite('ExhortServices module', async () => { } }; - let vscodeMock = { - window: { - showInformationMessage: sinon.spy(), - showWarningMessage: sinon.spy(), - showErrorMessage: sinon.spy(), - } - }; - let exhortServicesRewire; setup(async () => { sandbox = sinon.createSandbox(); exhortServicesRewire = await rewireModule(compiledFilePath); exhortServicesRewire.__Rewire__('exhort_javascript_api_1', exhortMock); - exhortServicesRewire.__Rewire__('vscode', vscodeMock); }); teardown(() => { @@ -49,19 +40,12 @@ suite('ExhortServices module', async () => { }); test('should perform token validation with Exhort Validate Token service', async () => { - await exhortServicesRewire.tokenValidationService(200, 'provider'); - await exhortServicesRewire.tokenValidationService(400, 'provider'); - await exhortServicesRewire.tokenValidationService(401, 'provider'); - await exhortServicesRewire.tokenValidationService(403, 'provider'); - await exhortServicesRewire.tokenValidationService(429, 'provider'); - await exhortServicesRewire.tokenValidationService(500, 'provider'); - - expect(vscodeMock.window.showInformationMessage).to.have.been.calledWith('provider Token Validated Successfully'); - expect(vscodeMock.window.showWarningMessage).to.have.been.calledWith('Missing token. Please provide a valid provider Token in the extension workspace settings. Status: 400'); - expect(vscodeMock.window.showWarningMessage).to.have.been.calledWith('Invalid token. Please provide a valid provider Token in the extension workspace settings. Status: 401'); - expect(vscodeMock.window.showWarningMessage).to.have.been.calledWith('Forbidden. The token does not have permissions. Please provide a valid provider Token in the extension workspace settings. Status: 403'); - expect(vscodeMock.window.showWarningMessage).to.have.been.calledWith('Too many requests. Rate limit exceeded. Please try again in a little while. Status: 429'); - expect(vscodeMock.window.showWarningMessage).to.have.been.calledWith('Failed to validate token. Status: 500'); + expect(await exhortServicesRewire.tokenValidationService(200, 'provider')).to.equal(undefined); + expect(await exhortServicesRewire.tokenValidationService(400, 'provider')).to.equal('Missing token. Please provide a valid provider Token in the extension workspace settings. Status: 400'); + expect(await exhortServicesRewire.tokenValidationService(401, 'provider')).to.equal('Invalid token. Please provide a valid provider Token in the extension workspace settings. Status: 401'); + expect(await exhortServicesRewire.tokenValidationService(403, 'provider')).to.equal('Forbidden. The token does not have permissions. Please provide a valid provider Token in the extension workspace settings. Status: 403'); + expect(await exhortServicesRewire.tokenValidationService(429, 'provider')).to.equal('Too many requests. Rate limit exceeded. Please try again in a little while. Status: 429'); + expect(await exhortServicesRewire.tokenValidationService(500, 'provider')).to.equal('Failed to validate token. Status: 500'); }); test('should fail to generate RHDA report HTML from Exhort Stack Analysis service and reject with error', async () => { @@ -97,8 +81,6 @@ suite('ExhortServices module', async () => { exhortServicesRewire.__Rewire__('exhort_javascript_api_1', exhortMock); - await exhortServicesRewire.tokenValidationService(500, 'provider'); - - expect(vscodeMock.window.showErrorMessage).to.have.been.calledWith('Failed to validate token, Error: Validation Error'); + expect(await exhortServicesRewire.tokenValidationService(500, 'provider')).to.equal('Failed to validate token, Error: Validation Error'); }); }); \ No newline at end of file diff --git a/test/extension.test.ts b/test/extension.test.ts index 32b436cd6..0056c4f7b 100644 --- a/test/extension.test.ts +++ b/test/extension.test.ts @@ -17,12 +17,12 @@ suite('Fabric8 Analytics Extension', () => { test('should register all fabric8 commands', async function () { const FABRIC8_COMMANDS: string[] = [ - Commands.TRIGGER_FULL_STACK_ANALYSIS, - Commands.TRIGGER_STACK_LOGS, - Commands.TRIGGER_FULL_STACK_ANALYSIS_FROM_EDITOR, - Commands.TRIGGER_FULL_STACK_ANALYSIS_FROM_EXPLORER, - Commands.TRIGGER_FULL_STACK_ANALYSIS_FROM_PIE_BTN, - Commands.TRIGGER_FULL_STACK_ANALYSIS_FROM_STATUS_BAR + Commands.STACK_ANALYSIS_COMMAND, + Commands.STACK_LOGS_COMMAND, + Commands.STACK_ANALYSIS_FROM_EDITOR_COMMAND, + Commands.STACK_ANALYSIS_FROM_EXPLORER_COMMAND, + Commands.STACK_ANALYSIS_FROM_PIE_BTN_COMMAND, + Commands.STACK_ANALYSIS_FROM_STATUS_BAR_COMMAND ]; // @ts-ignore assert.ok((await vscode.commands.getCommands(true)).includes(...FABRIC8_COMMANDS)); diff --git a/test/stackAnalysis.test.ts b/test/stackAnalysis.test.ts index b140a6cfe..8fa706249 100644 --- a/test/stackAnalysis.test.ts +++ b/test/stackAnalysis.test.ts @@ -21,6 +21,14 @@ suite('StackAnalysis module', () => { setup(() => { sandbox = sinon.createSandbox(); + + globalConfig.linkToSecretStorage({ + secrets: { + store: () => sandbox.stub(), + get: () => 'mockToken', + delete: () => sandbox.stub() + } + }); }); teardown(() => { @@ -48,8 +56,6 @@ suite('StackAnalysis module', () => { callback(null); }); - globalConfig.exhortSnykToken = 'mockToken'; - await generateRHDAReport(context, MockUri); expect(authorizeRHDAStub.calledOnce).to.be.true; @@ -63,8 +69,6 @@ suite('StackAnalysis module', () => { const authorizeRHDAStub = sandbox.stub(globalConfig, 'authorizeRHDA').resolves(); const stackAnalysisServiceStub = sandbox.stub(exhortServices, 'stackAnalysisService').rejects(new Error('Mock Error')); - globalConfig.exhortSnykToken = ''; - await generateRHDAReport(context, MockUri) .then(() => { throw (new Error('should have thrown error')) diff --git a/test/tokenValidation.test.ts b/test/tokenValidation.test.ts index d03be7b4f..f981a1ece 100644 --- a/test/tokenValidation.test.ts +++ b/test/tokenValidation.test.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { globalConfig } from '../src/config'; import { validateSnykToken } from '../src/tokenValidation' import * as exhortServices from '../src/exhortServices' -import { snykURL } from '../src/constants'; +import { SNYK_URL } from '../src/constants'; const expect = chai.expect; chai.use(sinonChai); @@ -23,7 +23,6 @@ suite('TokenValidation module', () => { }); test('should validate non-empty Snyk token', async () => { - globalConfig.exhortSnykToken = 'mockToken'; globalConfig.telemetryId = 'mockId'; const options = { 'RHDA_TOKEN': 'mockId', @@ -33,21 +32,16 @@ suite('TokenValidation module', () => { const exhortServicesStub = sandbox.stub(exhortServices, 'tokenValidationService'); - await validateSnykToken(); + await validateSnykToken('mockToken'); expect(exhortServicesStub.calledOnceWithExactly(options, 'Snyk')).to.be.true; }); test('should validate empty Snyk token', async () => { - globalConfig.exhortSnykToken = ''; - const expectedMsg = `Please note that if you fail to provide a valid Snyk Token in the extension workspace settings, Snyk vulnerabilities will not be displayed. To resolve this issue, please obtain a valid token from the following link: [here](${snykURL}).`; + const expectedMsg = `Please note that if you fail to provide a valid Snyk Token in the extension workspace settings, Snyk vulnerabilities will not be displayed. To resolve this issue, please obtain a valid token from the following link: [here](${SNYK_URL}).`; - const showInformationMessageStub = sandbox.stub(vscode.window, 'showInformationMessage'); + const message = await validateSnykToken(''); - await validateSnykToken(); - - const showInformationMessageCall = showInformationMessageStub.getCall(0); - const showInformationMessageMsg = showInformationMessageCall.args[0]; - expect(showInformationMessageMsg.replace(/\s+/g, ' ').replace(/\n/g, ' ')).to.equal(expectedMsg); + expect(message.replace(/\s+/g, ' ').replace(/\n/g, ' ')).to.equal(expectedMsg); }); }); \ No newline at end of file