diff --git a/package-lock.json b/package-lock.json index 51d3e2cd..426d29d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.9.0", "license": "Apache-2.0", "dependencies": { - "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.5", + "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.14", "@xml-tools/ast": "^5.0.5", "@xml-tools/parser": "^1.0.11", "json-to-ast": "^2.1.0", @@ -837,14 +837,15 @@ } }, "node_modules/@RHEcosystemAppEng/exhort-javascript-api": { - "version": "0.1.1-ea.5", - "resolved": "https://npm.pkg.github.com/download/@RHEcosystemAppEng/exhort-javascript-api/0.1.1-ea.5/2cbd94284336733cbb2e3aa8c931ae6b2c1f5d8d", - "integrity": "sha512-VBIbeUvBw8DjTNHrBTVvqt2UrdHybApTsopXurlLWjsbmK4R8SJO0zUiQXcxDOyxY40moiNr2gncrcMN3tTRVA==", + "version": "0.1.1-ea.14", + "resolved": "https://npm.pkg.github.com/download/@RHEcosystemAppEng/exhort-javascript-api/0.1.1-ea.14/b7f01baf0e01d8d697a2b5264e4f374d001004fd", + "integrity": "sha512-B0bnokCaz37eS3dIQPuPuwrAGU/y3TM4DT8u7xMMN9hjK9kSOq4UsWPgkJIaYstUzChgomQnXTRQhd2kNfvIIw==", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.23.2", "@cyclonedx/cyclonedx-library": "^4.0.0", "fast-xml-parser": "^4.2.4", + "help": "^3.0.2", "packageurl-js": "^1.0.2", "yargs": "^17.7.2" }, @@ -2714,6 +2715,11 @@ "he": "bin/he" } }, + "node_modules/help": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/help/-/help-3.0.2.tgz", + "integrity": "sha512-jDd0MU+9xzvOQRC6CIzdjvb+agCvpzQY/Fp11quDnugDO4QQzh134EsLkRQMvFIJBleFkvnXagHFm4MTefkkpA==" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", diff --git a/package.json b/package.json index b2955997..866742c4 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "dist" ], "dependencies": { - "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.5", + "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.14", "@xml-tools/ast": "^5.0.5", "@xml-tools/parser": "^1.0.11", "json-to-ast": "^2.1.0", diff --git a/src/codeActionHandler.ts b/src/codeActionHandler.ts index 88545c6f..893c4c51 100644 --- a/src/codeActionHandler.ts +++ b/src/codeActionHandler.ts @@ -10,30 +10,35 @@ import { CodeAction, CodeActionKind, Diagnostic } from 'vscode-languageserver/no import { globalConfig } from './config'; import { RHDA_DIAGNOSTIC_SOURCE } from './constants'; -let codeActionsMap: Map = new Map(); +const codeActionsMap: Map> = new Map>(); /** * Gets the code actions map. */ -function getCodeActionsMap(): Map { +function getCodeActionsMap(): Map> { return codeActionsMap; } /** - * Clears the code actions map. + * Clears code actions related to a specific file URI from the code actions map. + * @param uri - The file URI key to remove from the code actions map. */ -function clearCodeActionsMap() { - codeActionsMap = new Map(); +function clearCodeActionsMap(uri: string) { + codeActionsMap.delete(uri); } /** * Registers a code action. - * @param key - The key to register the code action against. + * @param uri - The file uri to register the file code action map (inner map) against. + * @param loc - The location in file to register the file code action against. * @param codeAction - The code action to be registered. */ -function registerCodeAction(key: string, codeAction: CodeAction) { - codeActionsMap[key] = codeActionsMap[key] || []; - codeActionsMap[key].push(codeAction); +function registerCodeAction(uri: string, loc: string, codeAction: CodeAction) { + codeActionsMap.set(uri, codeActionsMap.get(uri) || new Map()); + + const innerMap = codeActionsMap.get(uri); + innerMap.set(loc, innerMap.get(loc) || []); + innerMap.get(loc).push(codeAction); } /** @@ -42,19 +47,20 @@ function registerCodeAction(key: string, codeAction: CodeAction) { * @param uri - The URI of the file being analyzed. * @returns An array of CodeAction objects to be made available to the user. */ -function getDiagnosticsCodeActions(diagnostics: Diagnostic[]): CodeAction[] { +function getDiagnosticsCodeActions(diagnostics: Diagnostic[], uri: string): CodeAction[] { let hasRhdaDiagonostic: boolean = false; const codeActions: CodeAction[] = []; - + for (const diagnostic of diagnostics) { - const key = `${diagnostic.range.start.line}|${diagnostic.range.start.character}`; - const diagnosticCodeActions = codeActionsMap[key] || []; + const fileCodeActionsMap = codeActionsMap.get(uri) || new Map(); + const loc = `${diagnostic.range.start.line}|${diagnostic.range.start.character}`; + const diagnosticCodeActions = fileCodeActionsMap.get(loc) || []; codeActions.push(...diagnosticCodeActions); hasRhdaDiagonostic ||= diagnostic.source === RHDA_DIAGNOSTIC_SOURCE; } - + if (globalConfig.triggerFullStackAnalysis && hasRhdaDiagonostic) { codeActions.push(generateFullStackAnalysisAction()); } diff --git a/src/componentAnalysis.ts b/src/componentAnalysis.ts index 39f4b976..00111d59 100644 --- a/src/componentAnalysis.ts +++ b/src/componentAnalysis.ts @@ -9,7 +9,7 @@ import exhort from '@RHEcosystemAppEng/exhort-javascript-api'; import { connection } from './server'; import { globalConfig } from './config'; -import { isDefined } from './utils'; +import { isDefined, decodeUriPath } from './utils'; /** * Represents a source object with an ID and dependencies array. @@ -177,7 +177,7 @@ async function executeComponentAnalysis (diagnosticFilePath: string, contents: s options['EXHORT_SNYK_TOKEN'] = globalConfig.exhortSnykToken; } - const componentAnalysisJson = await exhort.componentAnalysis(path.basename(diagnosticFilePath), contents, options); // Execute component analysis + const componentAnalysisJson = await exhort.componentAnalysis(path.basename(diagnosticFilePath), contents, options, decodeUriPath(diagnosticFilePath)); // Execute component analysis return new AnalysisResponse(componentAnalysisJson, diagnosticFilePath); } diff --git a/src/config.ts b/src/config.ts index d00b8405..361625ae 100644 --- a/src/config.ts +++ b/src/config.ts @@ -16,6 +16,7 @@ class Config utmSource: string; exhortSnykToken: string; matchManifestVersions: string; + vulnerabilityAlertSeverity: string; exhortMvnPath: string; exhortNpmPath: string; exhortGoPath: string; @@ -34,6 +35,7 @@ class Config this.utmSource = process.env.VSCEXT_UTM_SOURCE || ''; this.exhortSnykToken = process.env.VSCEXT_EXHORT_SNYK_TOKEN || ''; this.matchManifestVersions = process.env.VSCEXT_MATCH_MANIFEST_VERSIONS || 'true'; + this.vulnerabilityAlertSeverity = process.env.VSCEXT_VULNERABILITY_ALERT_SEVERITY || 'Error'; this.exhortMvnPath = process.env.VSCEXT_EXHORT_MVN_PATH || 'mvn'; this.exhortNpmPath = process.env.VSCEXT_EXHORT_NPM_PATH || 'npm'; this.exhortGoPath = process.env.VSCEXT_EXHORT_GO_PATH || 'go'; @@ -47,11 +49,10 @@ class Config * Updates the global configuration with provided data from extension workspace settings. * @param data - The data from extension workspace settings to update the global configuration with. */ - updateConfig( data: any ) { - const rhdaData = data.redHatDependencyAnalytics; - + updateConfig( rhdaData: any ) { this.exhortSnykToken = rhdaData.exhortSnykToken; this.matchManifestVersions = rhdaData.matchManifestVersions ? 'true' : 'false'; + this.vulnerabilityAlertSeverity = rhdaData.vulnerabilityAlertSeverity; this.exhortMvnPath = rhdaData.mvn.executable.path || 'mvn'; this.exhortNpmPath = rhdaData.npm.executable.path || 'npm'; this.exhortGoPath = rhdaData.go.executable.path || 'go'; diff --git a/src/diagnosticsHandler.ts b/src/diagnosticsHandler.ts index 98ba4fe7..756eb107 100644 --- a/src/diagnosticsHandler.ts +++ b/src/diagnosticsHandler.ts @@ -83,7 +83,7 @@ class DiagnosticsPipeline implements IDiagnosticsPipeline { dependencyData.forEach(dd => { - const actionRef = vulnerabilityDiagnostic.severity === 1 ? dd.remediationRef : dd.recommendationRef; + const actionRef = vulnerabilityDiagnostic.severity < 3 ? dd.remediationRef : dd.recommendationRef; if (actionRef) { this.createCodeAction(loc, actionRef, dependency.context, dd.sourceId, vulnerabilityDiagnostic); @@ -112,7 +112,7 @@ class DiagnosticsPipeline implements IDiagnosticsPipeline { const versionReplacementString = context ? context.value.replace(VERSION_PLACEHOLDER, switchToVersion) : switchToVersion; const title = `Switch to version ${switchToVersion} for ${sourceId}`; const codeAction = generateSwitchToRecommendedVersionAction(title, versionReplacementString, vulnerabilityDiagnostic, this.diagnosticFilePath); - registerCodeAction(loc, codeAction); + registerCodeAction(this.diagnosticFilePath, loc, codeAction); } } @@ -133,7 +133,7 @@ async function performDiagnostics(diagnosticFilePath: string, contents: string, const response = await executeComponentAnalysis(diagnosticFilePath, contents); - clearCodeActionsMap(); + clearCodeActionsMap(diagnosticFilePath); diagnosticsPipeline.runDiagnostics(response.dependencies); diff --git a/src/server.ts b/src/server.ts index dd8fb142..a3d82105 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,12 +5,12 @@ 'use strict'; import { TextDocumentSyncKind, Connection, DidChangeConfigurationNotification } from 'vscode-languageserver'; -import { createConnection, TextDocuments, InitializeResult, CodeAction, ProposedFeatures } from 'vscode-languageserver/node'; +import { createConnection, TextDocuments, InitializeParams, InitializeResult, CodeAction, ProposedFeatures } from 'vscode-languageserver/node'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { globalConfig } from './config'; import { AnalysisLSPServer } from './fileHandler'; -import { getDiagnosticsCodeActions } from './codeActionHandler'; +import { getDiagnosticsCodeActions, clearCodeActionsMap } from './codeActionHandler'; /** * Declares timeout identifier to track delays for server.handleFileEvent execution @@ -18,12 +18,17 @@ import { getDiagnosticsCodeActions } from './codeActionHandler'; let checkDelay: NodeJS.Timeout; /** - * Represents the connection used for the server, using Node's IPC as a transport. + * Create a connection for the server, using Node's IPC as a transport. */ const connection: Connection = createConnection(ProposedFeatures.all); /** - * Represents the documents managed by the server. + * Declares servers configuration capability + */ +let hasConfigurationCapability: boolean = false; + +/** + * Create a text document manager. */ const documents: TextDocuments = new TextDocuments(TextDocument); documents.listen(connection); @@ -31,8 +36,8 @@ documents.listen(connection); /** * Sets up the connection's initialization event handler. */ -let hasConfigurationCapability: boolean = false; -connection.onInitialize((params): InitializeResult => { +connection.onInitialize((params: InitializeParams): InitializeResult => { + const capabilities = params.capabilities; hasConfigurationCapability = !!( capabilities.workspace && !!capabilities.workspace.configuration @@ -88,8 +93,9 @@ connection.onDidChangeTextDocument((params) => { /** * On close document event handler */ -connection.onDidCloseTextDocument(() => { +connection.onDidCloseTextDocument((params) => { clearTimeout(checkDelay); + clearCodeActionsMap(params.textDocument.uri); }); /** @@ -97,9 +103,9 @@ connection.onDidCloseTextDocument(() => { */ connection.onDidChangeConfiguration(() => { if (hasConfigurationCapability) { - server.conn.workspace.getConfiguration() - .then((data) => { - globalConfig.updateConfig(data); + server.conn.workspace.getConfiguration('redHatDependencyAnalytics') + .then((rhdaData) => { + globalConfig.updateConfig(rhdaData); }); } }); @@ -108,7 +114,7 @@ connection.onDidChangeConfiguration(() => { * Handles code action requests from client. */ connection.onCodeAction((params): CodeAction[] => { - return getDiagnosticsCodeActions(params.context.diagnostics); + return getDiagnosticsCodeActions(params.context.diagnostics, params.textDocument.uri); }); connection.listen(); diff --git a/src/utils.ts b/src/utils.ts index f667b37a..50db1839 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -18,4 +18,15 @@ export function isDefined(obj: any, ...keys: string[]): boolean { obj = obj[key]; } return true; +} + +/** + * Decodes the URI path from a given URI string. + * @param uri - The URI string to process. + * @returns The decoded URI path. + */ +export function decodeUriPath(uri: string): string { + const url = new URL(uri); + const decodedUri = decodeURIComponent(url.pathname); + return decodedUri; } \ No newline at end of file diff --git a/src/vulnerability.ts b/src/vulnerability.ts index 64127001..300e27ad 100644 --- a/src/vulnerability.ts +++ b/src/vulnerability.ts @@ -9,6 +9,7 @@ import { Range } from 'vscode-languageserver'; import { IDependencyProvider } from './collector'; import { DependencyData } from './componentAnalysis'; import { RHDA_DIAGNOSTIC_SOURCE } from './constants'; +import { globalConfig } from './config'; /** * Stores vulnerability data of a specific dependency. @@ -57,9 +58,21 @@ Recommendation: ${this.provider.resolveDependencyFromReference(dependencyData.re */ getDiagnostic(): Diagnostic { - const hasIssues = this.dependencyData.some(data => data.issuesCount > 0); + let vulnerabilityAlertSeverity: DiagnosticSeverity; + switch (globalConfig.vulnerabilityAlertSeverity) { + case 'Error': + vulnerabilityAlertSeverity = DiagnosticSeverity.Error; + break; + case 'Warning': + vulnerabilityAlertSeverity = DiagnosticSeverity.Warning; + break; + default: + vulnerabilityAlertSeverity = DiagnosticSeverity.Error; + break; + } - const diagnosticSeverity = hasIssues ? DiagnosticSeverity.Error : DiagnosticSeverity.Information; + const hasIssues = this.dependencyData.some(data => data.issuesCount > 0); + const diagnosticSeverity = hasIssues ? vulnerabilityAlertSeverity : DiagnosticSeverity.Information; const messages = this.dependencyData.map(dd => { if (hasIssues && dd.issuesCount > 0) { diff --git a/test/codeActionHandler.test.ts b/test/codeActionHandler.test.ts index 1b6fa9f0..7916f32a 100644 --- a/test/codeActionHandler.test.ts +++ b/test/codeActionHandler.test.ts @@ -11,6 +11,10 @@ import * as codeActionHandler from '../src/codeActionHandler'; describe('Code Action Handler tests', () => { + const mockUri = 'mock/path'; + const mockLoc = 'mockLocation'; + const mockCodeAction = { title: 'Mock Action' }; + const mockRange0: Range = { start: { line: 123, @@ -51,35 +55,57 @@ describe('Code Action Handler tests', () => { } ]; - it('should register code actions in codeActionsMap for the same key', () => { - const key0 = 'mockKey0'; + it('should register code action in codeActionsMap under URI and location keys', () => { + codeActionHandler.registerCodeAction(mockUri, mockLoc, mockCodeAction); + + expect(codeActionHandler.getCodeActionsMap().get(mockUri)?.get(mockLoc)).to.deep.equal([mockCodeAction]); + }); + + it('should remove code action from codeActionsMap under URI and location keys', () => { + expect(codeActionHandler.getCodeActionsMap().get(mockUri)?.get(mockLoc)).to.deep.equal([mockCodeAction]); + + codeActionHandler.clearCodeActionsMap(mockUri); + + expect(codeActionHandler.getCodeActionsMap().has(mockUri)).to.be.false; + }); + + it('should register code actions in codeActionsMap for same URI key and same location key', () => { const codeAction1 = { title: 'Mock Action1' }; const codeAction2 = { title: 'Mock Action2' }; - codeActionHandler.registerCodeAction(key0, codeAction1); - codeActionHandler.registerCodeAction(key0, codeAction2); + codeActionHandler.registerCodeAction(mockUri, mockLoc, codeAction1); + codeActionHandler.registerCodeAction(mockUri, mockLoc, codeAction2); - expect(codeActionHandler.getCodeActionsMap()[key0]).to.deep.equal([codeAction1, codeAction2]); + expect(codeActionHandler.getCodeActionsMap().get(mockUri)?.get(mockLoc)).to.deep.equal([codeAction1, codeAction2]); + codeActionHandler.clearCodeActionsMap(mockUri); }); - it('should register code actions in codeActionsMap for different keys', () => { - const key1 = 'mockKey1'; - const key2 = 'mockKey2'; + it('should register code actions in codeActionsMap for same URI key and different location keys', () => { + const loc1 = 'mockLocation/1'; + const loc2 = 'mockLocation/2'; const codeAction1 = { title: 'Mock Action1' }; const codeAction2 = { title: 'Mock Action2' }; - codeActionHandler.registerCodeAction(key1, codeAction1); - codeActionHandler.registerCodeAction(key2, codeAction2); + codeActionHandler.registerCodeAction(mockUri, loc1, codeAction1); + codeActionHandler.registerCodeAction(mockUri, loc2, codeAction2); - const codeActionsMap = codeActionHandler.getCodeActionsMap(); - expect(codeActionsMap[key1]).to.deep.equal([codeAction1]); - expect(codeActionsMap[key2]).to.deep.equal([codeAction2]); + expect(codeActionHandler.getCodeActionsMap().get(mockUri)?.get(loc1)).to.deep.equal([codeAction1]); + expect(codeActionHandler.getCodeActionsMap().get(mockUri)?.get(loc2)).to.deep.equal([codeAction2]); + codeActionHandler.clearCodeActionsMap(mockUri); }); - it('should clear codeActionsMap', () => { - expect(Object.keys(codeActionHandler.getCodeActionsMap()).length).greaterThan(0); - - codeActionHandler.clearCodeActionsMap(); + it('should register code actions in codeActionsMap for different URI keys', () => { + const uri1 = 'mock/path/1'; + const uri2 = 'mock/path/2'; + const loc1 = 'mockLocation/1'; + const loc2 = 'mockLocation/2'; + const codeAction1 = { title: 'Mock Action1' }; + const codeAction2 = { title: 'Mock Action2' }; + codeActionHandler.registerCodeAction(uri1, loc1, codeAction1); + codeActionHandler.registerCodeAction(uri2, loc2, codeAction2); - expect(Object.keys(codeActionHandler.getCodeActionsMap()).length).to.equal(0); + expect(codeActionHandler.getCodeActionsMap().get(uri1)?.get(loc1)).to.deep.equal([codeAction1]); + expect(codeActionHandler.getCodeActionsMap().get(uri2)?.get(loc2)).to.deep.equal([codeAction2]); + codeActionHandler.clearCodeActionsMap(uri1); + codeActionHandler.clearCodeActionsMap(uri2); }); it('should return an empty array if no RHDA diagnostics are present and full stack analysis action is provided', () => { @@ -89,7 +115,7 @@ describe('Code Action Handler tests', () => { }; sinon.stub(config, 'globalConfig').value(globalConfig); - const codeActions = codeActionHandler.getDiagnosticsCodeActions(diagnostics); + const codeActions = codeActionHandler.getDiagnosticsCodeActions(diagnostics, mockUri); expect(codeActions).to.be.an('array').that.is.empty; }); @@ -101,87 +127,84 @@ describe('Code Action Handler tests', () => { }; sinon.stub(config, 'globalConfig').value(globalConfig); - const codeActions = codeActionHandler.getDiagnosticsCodeActions(diagnostics); + const codeActions = codeActionHandler.getDiagnosticsCodeActions(diagnostics, mockUri); expect(codeActions).to.be.an('array').that.is.empty; }); + it('should return an empty array if RHDA diagnostics are present but no matching URI is found in codeActionsMap', () => { + const uri1 = 'mock/path/1'; + codeActionHandler.registerCodeAction(mockUri, mockLoc, mockCodeAction); + + let globalConfig = { + triggerFullStackAnalysis: 'mockTriggerFullStackAnalysis' + }; + sinon.stub(config, 'globalConfig').value(globalConfig); + + const codeActions = codeActionHandler.getDiagnosticsCodeActions(mockDiagnostic1, uri1); + + expect(codeActions).to.be.an('array').that.is.empty; + codeActionHandler.clearCodeActionsMap(mockUri); + }); + it('should return an empty array if RHDA diagnostics are present but no matching code actions are found', () => { - const key = 'mockKey'; - const codeAction = { title: 'Mock Action' }; - codeActionHandler.registerCodeAction(key, codeAction); + codeActionHandler.registerCodeAction(mockUri, mockLoc, mockCodeAction); let globalConfig = { triggerFullStackAnalysis: 'mockTriggerFullStackAnalysis' }; sinon.stub(config, 'globalConfig').value(globalConfig); - const codeActions = codeActionHandler.getDiagnosticsCodeActions(mockDiagnostic1); + const codeActions = codeActionHandler.getDiagnosticsCodeActions(mockDiagnostic1, mockUri); expect(codeActions).to.be.an('array').that.is.empty; + codeActionHandler.clearCodeActionsMap(mockUri); }); it('should generate code actions for RHDA diagnostics without full stack analysis action setting in globalConfig', async () => { - const key = `${mockDiagnostic0[0].range.start.line}|${mockDiagnostic0[0].range.start.character}`; - const codeAction = { title: 'Mock Action' }; - codeActionHandler.clearCodeActionsMap(); - codeActionHandler.registerCodeAction(key, codeAction); + const loc = `${mockDiagnostic0[0].range.start.line}|${mockDiagnostic0[0].range.start.character}`; + codeActionHandler.registerCodeAction(mockUri, loc, mockCodeAction); let globalConfig = { triggerFullStackAnalysis: '' }; sinon.stub(config, 'globalConfig').value(globalConfig); - const codeActions: CodeAction[] = codeActionHandler.getDiagnosticsCodeActions(mockDiagnostic0); + const codeActions: CodeAction[] = codeActionHandler.getDiagnosticsCodeActions(mockDiagnostic0, mockUri); - expect(codeActions).to.deep.equal( - [ - { - title: 'Mock Action', - } - ] - ); + expect(codeActions).to.deep.equal([mockCodeAction]); + codeActionHandler.clearCodeActionsMap(mockUri); }); it('should generate code actions for RHDA diagnostics without RHDA Diagonostic source', async () => { - const key = `${mockDiagnostic1[0].range.start.line}|${mockDiagnostic1[0].range.start.character}`; - const codeAction = { title: 'Mock Action' }; - codeActionHandler.clearCodeActionsMap(); - codeActionHandler.registerCodeAction(key, codeAction); + const loc = `${mockDiagnostic1[0].range.start.line}|${mockDiagnostic1[0].range.start.character}`; + codeActionHandler.registerCodeAction(mockUri, loc, mockCodeAction); let globalConfig = { triggerFullStackAnalysis: 'mockTriggerFullStackAnalysis' }; sinon.stub(config, 'globalConfig').value(globalConfig); - const codeActions: CodeAction[] = codeActionHandler.getDiagnosticsCodeActions(mockDiagnostic1); + const codeActions: CodeAction[] = codeActionHandler.getDiagnosticsCodeActions(mockDiagnostic1, mockUri); - expect(codeActions).to.deep.equal( - [ - { - title: 'Mock Action', - } - ] - ); + expect(codeActions).to.deep.equal([mockCodeAction]); + codeActionHandler.clearCodeActionsMap(mockUri); }); it('should generate code actions for RHDA diagnostics with full stack analysis action', async () => { - const key = `${mockDiagnostic0[0].range.start.line}|${mockDiagnostic0[0].range.start.character}`; - const codeAction = { title: 'Mock Action' }; - codeActionHandler.registerCodeAction(key, codeAction); + const loc = `${mockDiagnostic0[0].range.start.line}|${mockDiagnostic0[0].range.start.character}`; + codeActionHandler.registerCodeAction(mockUri, loc, mockCodeAction); let globalConfig = { triggerFullStackAnalysis: 'mockTriggerFullStackAnalysis' }; sinon.stub(config, 'globalConfig').value(globalConfig); - const codeActions: CodeAction[] = codeActionHandler.getDiagnosticsCodeActions(mockDiagnostic0); + const codeActions: CodeAction[] = codeActionHandler.getDiagnosticsCodeActions(mockDiagnostic0, mockUri); expect(codeActions).to.deep.equal( [ - { - title: 'Mock Action', - }, + mockCodeAction, { title: 'Detailed Vulnerability Report', kind: CodeActionKind.QuickFix, @@ -192,6 +215,7 @@ describe('Code Action Handler tests', () => { } ] ); + codeActionHandler.clearCodeActionsMap(mockUri); }); it('should return a switch to recommended version code action without RedHat repository recommendation', async () => { diff --git a/test/config.test.ts b/test/config.test.ts index beb56d78..bf8398e2 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -8,59 +8,55 @@ describe('Config tests', () => { const mockConfig = new Config(); - const data = { - redHatDependencyAnalytics: { - exhortSnykToken: 'mockToken', - matchManifestVersions: false, - mvn: { - executable: { path: 'mockPath' } - }, - npm: { - executable: { path: 'mockPath' } - }, - go: { - executable: { path: 'mockPath' } - }, - python3: { - executable: { path: 'mockPath' } - }, - pip3: { - executable: { path: 'mockPath' } - }, - python: { - executable: { path: 'mockPath' } - }, - pip: { - executable: { path: 'mockPath' } - }, + const rhdaData = { + exhortSnykToken: 'mockToken', + matchManifestVersions: false, + mvn: { + executable: { path: 'mockPath' } + }, + npm: { + executable: { path: 'mockPath' } + }, + go: { + executable: { path: 'mockPath' } + }, + python3: { + executable: { path: 'mockPath' } + }, + pip3: { + executable: { path: 'mockPath' } + }, + python: { + executable: { path: 'mockPath' } + }, + pip: { + executable: { path: 'mockPath' } }, }; - const partialData = { - redHatDependencyAnalytics: { - exhortSnykToken: 'mockToken', - matchManifestVersions: true, - mvn: { - executable: { path: '' } - }, - npm: { - executable: { path: '' } - }, - go: { - executable: { path: '' } - }, - python3: { - executable: { path: '' } - }, - pip3: { - executable: { path: '' } - }, - python: { - executable: { path: '' } - }, - pip: { - executable: { path: '' } - }, + const partialRhdaData = { + exhortSnykToken: 'mockToken', + matchManifestVersions: true, + mvn: { + executable: { path: '' } + }, + npm: { + executable: { path: '' } + }, + go: { + executable: { path: '' } + }, + python3: { + executable: { path: '' } + }, + pip3: { + executable: { path: '' } + }, + python: { + executable: { path: '' } + }, + pip: { + executable: { path: '' } }, }; @@ -82,7 +78,7 @@ describe('Config tests', () => { it('should update configuration based on provided data', () => { - mockConfig.updateConfig(data); + mockConfig.updateConfig(rhdaData); expect(mockConfig.exhortSnykToken).to.eq('mockToken'); expect(mockConfig.matchManifestVersions).to.eq('false'); @@ -97,7 +93,7 @@ describe('Config tests', () => { it('should update configuration based on provided partial data', () => { - mockConfig.updateConfig(partialData); + mockConfig.updateConfig(partialRhdaData); expect(mockConfig.exhortSnykToken).to.eq('mockToken'); expect(mockConfig.matchManifestVersions).to.eq('true'); diff --git a/test/utils.test.ts b/test/utils.test.ts index 060607b0..cba60766 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; -import { isDefined } from '../src/utils'; +import { isDefined, decodeUriPath } from '../src/utils'; describe('Utils tests', () => { @@ -61,4 +61,12 @@ describe('Utils tests', () => { }; expect(isDefined(obj, 'a', 'b', 'c')).to.be.false; }); + + it('should decode the URI path correctly', () => { + const uriString = 'file:///mock/path%20with%20spaces'; + + const decodedPath = decodeUriPath(uriString); + + expect(decodedPath).to.equal('/mock/path with spaces'); + }); }); \ No newline at end of file diff --git a/test/vulnerability.test.ts b/test/vulnerability.test.ts index a7ef0614..0322f577 100644 --- a/test/vulnerability.test.ts +++ b/test/vulnerability.test.ts @@ -1,10 +1,12 @@ 'use strict'; import { expect } from 'chai'; -import { Range } from 'vscode-languageserver'; +import * as sinon from 'sinon'; +import { DiagnosticSeverity, Range } from 'vscode-languageserver'; import { DependencyProvider } from '../src/providers/pom.xml'; import { Vulnerability } from '../src/vulnerability'; +import * as config from '../src/config'; describe('Vulnerability tests', () => { const mavenDependencyProvider: DependencyProvider = new DependencyProvider(); @@ -195,4 +197,64 @@ describe('Vulnerability tests', () => { Highest severity: HIGH `.replace(/\s/g, "")); }); + + it('should return diagnostic with diagnostic severity set to Error', async () => { + let globalConfig = { + vulnerabilityAlertSeverity: 'Error' + }; + sinon.stub(config, 'globalConfig').value(globalConfig); + + const mockDependencyData: MockDependencyData[] = [ + new MockDependencyData('snyk(snyk)', 2, '', '', 'HIGH') + ]; + let vulnerability = new Vulnerability( + mavenDependencyProvider, + mockRange, + mockMavenRef, + mockDependencyData + ); + + const diagnostic = vulnerability.getDiagnostic(); + expect(diagnostic.severity).to.eql(DiagnosticSeverity.Error); + }); + + it('should return diagnostic with diagnostic severity set to Warning', async () => { + let globalConfig = { + vulnerabilityAlertSeverity: 'Warning' + }; + sinon.stub(config, 'globalConfig').value(globalConfig); + + const mockDependencyData: MockDependencyData[] = [ + new MockDependencyData('snyk(snyk)', 2, '', '', 'HIGH') + ]; + let vulnerability = new Vulnerability( + mavenDependencyProvider, + mockRange, + mockMavenRef, + mockDependencyData + ); + + const diagnostic = vulnerability.getDiagnostic(); + expect(diagnostic.severity).to.eql(DiagnosticSeverity.Warning); + }); + + it('should return diagnostic with diagnostic severity set to default', async () => { + let globalConfig = { + vulnerabilityAlertSeverity: '' + }; + sinon.stub(config, 'globalConfig').value(globalConfig); + + const mockDependencyData: MockDependencyData[] = [ + new MockDependencyData('snyk(snyk)', 2, '', '', 'HIGH') + ]; + let vulnerability = new Vulnerability( + mavenDependencyProvider, + mockRange, + mockMavenRef, + mockDependencyData + ); + + const diagnostic = vulnerability.getDiagnostic(); + expect(diagnostic.severity).to.eql(DiagnosticSeverity.Error); + }); });