diff --git a/src/componentAnalysis.ts b/src/componentAnalysis.ts index ffc20dff..b806a40f 100644 --- a/src/componentAnalysis.ts +++ b/src/componentAnalysis.ts @@ -4,12 +4,13 @@ * ------------------------------------------------------------------------------------------ */ 'use strict'; +import * as path from 'path'; +import exhort from '@RHEcosystemAppEng/exhort-javascript-api'; + import { connection } from './server'; import { globalConfig } from './config'; import { isDefined } from './utils'; -import exhort from '@RHEcosystemAppEng/exhort-javascript-api'; - /** * Represents a source object with an ID and dependencies array. */ @@ -61,7 +62,7 @@ interface IAnalysisResponse { class AnalysisResponse implements IAnalysisResponse { dependencies: Map = new Map(); - constructor(resData: exhort.AnalysisReport) { + constructor(resData: exhort.AnalysisReport, diagnosticFilePath: string) { const failedProviders: string[] = []; const sources: Source[] = []; @@ -79,8 +80,11 @@ class AnalysisResponse implements IAnalysisResponse { if (failedProviders.length !== 0) { const errMsg = `The component analysis couldn't fetch data from the following providers: [${failedProviders.join(', ')}]`; - connection.console.warn(errMsg); - connection.sendNotification('caSimpleWarning', errMsg); + connection.console.warn(`Component Analysis Error: ${errMsg}`); + connection.sendNotification('caError', { + error: errMsg, + uri: diagnosticFilePath, + }); } sources.forEach(source => { @@ -104,13 +108,12 @@ class AnalysisResponse implements IAnalysisResponse { * @param contents - The contents of the manifest file to analyze. * @returns A Promise resolving to an AnalysisResponse object. */ -async function componentAnalysisService (fileType: string, contents: string): Promise { +async function executeComponentAnalysis (diagnosticFilePath: string, contents: string): Promise { // Define configuration options for the component analysis request const options = { 'RHDA_TOKEN': globalConfig.telemetryId, 'RHDA_SOURCE': globalConfig.utmSource, - 'EXHORT_DEV_MODE': globalConfig.exhortDevMode, 'MATCH_MANIFEST_VERSIONS': globalConfig.matchManifestVersions, 'EXHORT_MVN_PATH': globalConfig.exhortMvnPath, 'EXHORT_NPM_PATH': globalConfig.exhortNpmPath, @@ -128,9 +131,9 @@ async function componentAnalysisService (fileType: string, contents: string): Pr options['EXHORT_OSS_INDEX_TOKEN'] = globalConfig.exhortOSSIndexToken; } - const componentAnalysisJson = await exhort.componentAnalysis(fileType, contents, options); // Execute component analysis + const componentAnalysisJson = await exhort.componentAnalysis(path.basename(diagnosticFilePath), contents, options); // Execute component analysis - return new AnalysisResponse(componentAnalysisJson); + return new AnalysisResponse(componentAnalysisJson, diagnosticFilePath); } -export { componentAnalysisService, DependencyData }; \ No newline at end of file +export { executeComponentAnalysis, DependencyData }; \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index 108c2780..f6493e7b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -8,23 +8,22 @@ /** * Represents the global configuration settings. */ -export class Config +class Config { triggerFullStackAnalysis: string; - telemetryId: string; - utmSource: string; - exhortDevMode: string; - exhortSnykToken: string; - exhortOSSIndexUser: string; - exhortOSSIndexToken: string; - matchManifestVersions: string; - exhortMvnPath: string; - exhortNpmPath: string; - exhortGoPath: string; - exhortPython3Path: string; - exhortPip3Path: string; - exhortPythonPath: string; - exhortPipPath: string; + telemetryId: string; + utmSource: string; + exhortSnykToken: string; + exhortOSSIndexUser: string; + exhortOSSIndexToken: string; + matchManifestVersions: string; + exhortMvnPath: string; + exhortNpmPath: string; + exhortGoPath: string; + exhortPython3Path: string; + exhortPip3Path: string; + exhortPythonPath: string; + exhortPipPath: string; /** * Initializes a new instance of the Config class with default values from the parent process environment variable data. @@ -33,7 +32,6 @@ export class Config this.triggerFullStackAnalysis = process.env.VSCEXT_TRIGGER_FULL_STACK_ANALYSIS || ''; this.telemetryId = process.env.VSCEXT_TELEMETRY_ID || ''; this.utmSource = process.env.VSCEXT_UTM_SOURCE || ''; - this.exhortDevMode = process.env.VSCEXT_EXHORT_DEV_MODE || 'false'; this.exhortSnykToken = process.env.VSCEXT_EXHORT_SNYK_TOKEN || ''; this.exhortOSSIndexUser = process.env.VSCEXT_EXHORT_OSS_INDEX_USER || ''; this.exhortOSSIndexToken = process.env.VSCEXT_EXHORT_OSS_INDEX_TOKEN || ''; @@ -71,4 +69,4 @@ export class Config */ const globalConfig = new Config(); -export { globalConfig }; +export { Config, globalConfig }; diff --git a/src/diagnosticsHandler.ts b/src/diagnosticsHandler.ts index 8deedc11..046016a9 100644 --- a/src/diagnosticsHandler.ts +++ b/src/diagnosticsHandler.ts @@ -5,12 +5,10 @@ 'use strict'; import { Diagnostic } from 'vscode-languageserver'; -import { DependencyMap, IDependencyProvider } from './collector'; -import { componentAnalysisService, DependencyData } from './componentAnalysis'; +import { DependencyMap, IDependencyProvider, getRange } from './collector'; +import { executeComponentAnalysis, DependencyData } from './componentAnalysis'; import { Vulnerability } from './vulnerability'; -import { getRange } from './collector'; import { connection } from './server'; -import * as path from 'path'; /** * Diagnostics Pipeline specification. @@ -101,9 +99,9 @@ async function performDiagnostics(diagnosticFilePath: string, contents: string, let dependencies = null; dependencies = await provider.collect(contents) .catch(error => { - connection.console.warn(`Error: ${error}`); + connection.console.warn(`Component Analysis Error: ${error}`); connection.sendNotification('caError', { - data: error, + error: error, uri: diagnosticFilePath, }); return; @@ -114,14 +112,16 @@ async function performDiagnostics(diagnosticFilePath: string, contents: string, diagnosticsPipeline.clearDiagnostics(); - const analysis = componentAnalysisService(path.basename(diagnosticFilePath), contents) + const analysis = executeComponentAnalysis(diagnosticFilePath, contents) .then(response => { diagnosticsPipeline.runDiagnostics(response.dependencies); }) .catch(error => { - const errMsg = `Component Analysis error. ${error}`; - connection.console.warn(errMsg); - connection.sendNotification('caSimpleWarning', errMsg); + connection.console.warn(`Component Analysis Error: ${error}`); + connection.sendNotification('caError', { + error: error, + uri: diagnosticFilePath, + }); return; }); diff --git a/test/codeActionHandler.test.ts b/test/codeActionHandler.test.ts index 3267d378..cd3dbe49 100644 --- a/test/codeActionHandler.test.ts +++ b/test/codeActionHandler.test.ts @@ -4,11 +4,12 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; import { Range } from 'vscode-languageserver'; import { CodeAction, CodeActionKind, Diagnostic } from 'vscode-languageserver/node'; + import * as config from '../src/config'; import { RHDA_DIAGNOSTIC_SOURCE } from '../src/constants'; import { getDiagnosticsCodeActions } from '../src/codeActionHandler'; -describe('Code Action Handler test', () => { +describe('Code Action Handler tests', () => { const mockRange: Range = { start: { diff --git a/test/collector.test.ts b/test/collector.test.ts index 93abb9c4..6ea062d0 100644 --- a/test/collector.test.ts +++ b/test/collector.test.ts @@ -1,10 +1,11 @@ 'use strict'; import { expect } from 'chai'; + import { Dependency, DependencyMap, getRange } from '../src/collector'; import { DependencyProvider } from '../src/providers/pom.xml'; -describe('Collector util test', () => { +describe('Collector tests', () => { // Mock manifest dependency collection const reqDeps: Dependency[] = [ @@ -22,6 +23,13 @@ describe('Collector util test', () => { }); }); + it('should create empty map', async () => { + + const depMap = new DependencyMap([]); + + expect(Object.keys(depMap.mapper).length).to.eql(0); + }); + it('should get dependency from dependency map', async () => { const depMap = new DependencyMap(reqDeps); diff --git a/test/config.test.ts b/test/config.test.ts new file mode 100644 index 00000000..12b3feb0 --- /dev/null +++ b/test/config.test.ts @@ -0,0 +1,74 @@ +'use strict'; + +import { expect } from 'chai'; + +import { Config } from '../src/config'; + +describe('Config tests', () => { + + const mockConfig = new Config(); + + const data = { + redHatDependencyAnalytics: { + exhortSnykToken: 'mockToken', + exhortOSSIndexUser: 'mockUser', + exhortOSSIndexToken: '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' } + }, + }; + + it('should initialize with default values when environment variables are not set', () => { + expect(mockConfig.triggerFullStackAnalysis).to.eq(''); + expect(mockConfig.telemetryId).to.eq(''); + expect(mockConfig.utmSource).to.eq(''); + expect(mockConfig.exhortSnykToken).to.eq(''); + expect(mockConfig.exhortOSSIndexUser).to.eq(''); + expect(mockConfig.exhortOSSIndexToken).to.eq(''); + expect(mockConfig.matchManifestVersions).to.eq('true'); + expect(mockConfig.exhortMvnPath).to.eq('mvn'); + expect(mockConfig.exhortNpmPath).to.eq('npm'); + expect(mockConfig.exhortGoPath).to.eq('go'); + expect(mockConfig.exhortPython3Path).to.eq('python3'); + expect(mockConfig.exhortPip3Path).to.eq('pip3'); + expect(mockConfig.exhortPythonPath).to.eq('python'); + expect(mockConfig.exhortPipPath).to.eq('pip'); + }); + + it('should update configuration based on provided data', () => { + + mockConfig.updateConfig(data); + + expect(mockConfig.exhortSnykToken).to.eq('mockToken'); + expect(mockConfig.exhortOSSIndexUser).to.eq('mockUser'); + expect(mockConfig.exhortOSSIndexToken).to.eq('mockToken'); + expect(mockConfig.matchManifestVersions).to.eq('false'); + expect(mockConfig.exhortMvnPath).to.eq('mockPath'); + expect(mockConfig.exhortNpmPath).to.eq('mockPath'); + expect(mockConfig.exhortGoPath).to.eq('mockPath'); + expect(mockConfig.exhortPython3Path).to.eq('mockPath'); + expect(mockConfig.exhortPip3Path).to.eq('mockPath'); + expect(mockConfig.exhortPythonPath).to.eq('mockPath'); + expect(mockConfig.exhortPipPath).to.eq('mockPath'); + }); +}); \ No newline at end of file diff --git a/test/providers/go.mod.test.ts b/test/providers/go.mod.test.ts index a5ab450d..862ddd20 100644 --- a/test/providers/go.mod.test.ts +++ b/test/providers/go.mod.test.ts @@ -1,9 +1,10 @@ 'use strict'; import { expect } from 'chai'; + import { DependencyProvider } from '../../src/providers/go.mod'; -describe('Golang Gomudules go.mod parser test', () => { +describe('Golang Gomodules go.mod parser tests', () => { let dependencyProvider: DependencyProvider; beforeEach(() => { @@ -15,7 +16,7 @@ describe('Golang Gomudules go.mod parser test', () => { expect(deps).is.eql([]); }); - it('tests require statement in go.mod', async () => { + it('tests require statements in go.mod', async () => { const deps = await dependencyProvider.collect(` module github.com/alecthomas/kingpin @@ -91,7 +92,7 @@ describe('Golang Gomudules go.mod parser test', () => { ]); }); - it('tests empty lines in go.mod', async () => { + it('tests go.mod with empty lines', async () => { const deps = await dependencyProvider.collect(` module github.com/alecthomas/kingpin @@ -119,7 +120,7 @@ describe('Golang Gomudules go.mod parser test', () => { ]); }); - it('tests deps with spaces before and after dep name and version', async () => { + it('tests go.mod with dependencies which have spaces before and after package name and version', async () => { const deps = await dependencyProvider.collect(` module github.com/alecthomas/kingpin require ( @@ -150,7 +151,7 @@ describe('Golang Gomudules go.mod parser test', () => { ]); }); - it('tests deps with alpha, beta and extra for version', async () => { + it('tests go.mod with dependencies which have extra sufixes in version', async () => { const deps = await dependencyProvider.collect(` module github.com/alecthomas/kingpin @@ -273,7 +274,7 @@ describe('Golang Gomudules go.mod parser test', () => { ]); }); - it('tests multiple module points to same replace module in go.mod', async () => { + it('tests go.mod with multiple packages replaced by the same package in go.mod', async () => { const deps = await dependencyProvider.collect(` module github.com/alecthomas/kingpin go 1.13 diff --git a/test/providers/package.json.test.ts b/test/providers/package.json.test.ts index 482e4595..ee5e8c37 100644 --- a/test/providers/package.json.test.ts +++ b/test/providers/package.json.test.ts @@ -2,9 +2,10 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; + import { DependencyProvider } from '../../src/providers/package.json'; -describe('Javascript NPM package.json parser test', () => { +describe('Javascript NPM package.json parser tests', () => { let dependencyProvider: DependencyProvider; beforeEach(() => { @@ -21,7 +22,7 @@ describe('Javascript NPM package.json parser test', () => { expect(deps.length).equal(0); }); - it('tests invalid token package.json', async () => { + it('tests invalid token', async () => { const deps = await dependencyProvider.collect(` { <<<<< @@ -54,7 +55,7 @@ describe('Javascript NPM package.json parser test', () => { expect(deps).eql([]); }); - it('tests single dependency ', async () => { + it('tests single dependency', async () => { const deps = await dependencyProvider.collect(` { "hello":{}, @@ -134,7 +135,7 @@ describe('Javascript NPM package.json parser test', () => { ]); }); - it('tests 3 dependencies with spaces', async () => { + it('tests dependencies with spaces', async () => { const deps = await dependencyProvider.collect(` { "hello":{}, diff --git a/test/providers/pom.xml.test.ts b/test/providers/pom.xml.test.ts index f6ca02ca..3d9f0a93 100644 --- a/test/providers/pom.xml.test.ts +++ b/test/providers/pom.xml.test.ts @@ -1,9 +1,10 @@ 'use strict'; import { expect } from 'chai'; + import { DependencyProvider } from '../../src/providers/pom.xml'; -describe('Java Maven pom.xml parser test', () => { +describe('Java Maven pom.xml parser tests', () => { let dependencyProvider: DependencyProvider; beforeEach(() => { diff --git a/test/providers/requirements.txt.test.ts b/test/providers/requirements.txt.test.ts index 64078f34..d753f1e9 100644 --- a/test/providers/requirements.txt.test.ts +++ b/test/providers/requirements.txt.test.ts @@ -1,9 +1,10 @@ 'use strict'; import { expect } from 'chai'; + import { DependencyProvider } from '../../src/providers/requirements.txt'; -describe('Python PyPi requirements.txt parser test', () => { +describe('Python PyPi requirements.txt parser tests', () => { let dependencyProvider: DependencyProvider; beforeEach(() => { @@ -63,7 +64,7 @@ describe('Python PyPi requirements.txt parser test', () => { ]); }); - it('tests empty lines', async () => { + it('tests requirements.txt with empty lines', async () => { const deps = await dependencyProvider.collect(` a==1 @@ -77,7 +78,7 @@ describe('Python PyPi requirements.txt parser test', () => { ]); }); - it('tests deps with spaces before and after comparators', async () => { + it('tests dependencies with spaces before and after package name and version', async () => { const deps = await dependencyProvider.collect(` a ==1 diff --git a/test/utils.test.ts b/test/utils.test.ts new file mode 100644 index 00000000..060607b0 --- /dev/null +++ b/test/utils.test.ts @@ -0,0 +1,64 @@ +'use strict'; + +import { expect } from 'chai'; + +import { isDefined } from '../src/utils'; + +describe('Utils tests', () => { + + it('should return true when all keys are defined in the object (with key request)', () => { + const obj = { + a: { + b: { + c: 10, + }, + }, + }; + expect(isDefined(obj, 'a', 'b', 'c')).to.be.true; + }); + + it('should return true when all keys are defined in the object (without key requests)', () => { + const obj = { + a: { + b: { + c: 10, + }, + }, + }; + expect(isDefined(obj)).to.be.true; + }); + + it('should return false if any key is not defined in the object', () => { + const obj = { + a: { + b: { + c: 10, + }, + }, + }; + expect(isDefined(obj, 'a', 'b', 'd')).to.be.false; + }); + + it('should return false if the object itself is not defined', () => { + const obj = null; + expect(isDefined(obj, 'a', 'b', 'c')).to.be.false; + }); + + it('should return false if any intermediate key in the object chain is not defined', () => { + const obj = { + a: { + b: null + }, + }; + expect(isDefined(obj, 'a', 'b', 'c')).to.be.false; + }); + + it('should return false if any intermediate key in the object chain is undefined', () => { + const obj = { + a: { + b: undefined + }, + }; + expect(isDefined(obj, 'a', 'b', 'c')).to.be.false; + }); +}); \ No newline at end of file diff --git a/test/vulnerability.test.ts b/test/vulnerability.test.ts index 9fdfd4a9..5b01e159 100644 --- a/test/vulnerability.test.ts +++ b/test/vulnerability.test.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { Range } from 'vscode-languageserver'; + import { DependencyProvider } from '../src/providers/pom.xml'; import { Vulnerability } from '../src/vulnerability';