Skip to content

Commit

Permalink
test: use rewire to test exhort services
Browse files Browse the repository at this point in the history
Signed-off-by: Ilona Shishov <[email protected]>
  • Loading branch information
IlonaShishov committed Feb 22, 2024
1 parent 3f9a400 commit e5ae4c6
Show file tree
Hide file tree
Showing 12 changed files with 312 additions and 99 deletions.
185 changes: 96 additions & 89 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@
"@typescript-eslint/eslint-plugin": "^6.8.0",
"@typescript-eslint/parser": "^6.8.0",
"@vscode/test-electron": "^2.3.5",
"babel-plugin-rewire": "^1.2.0",
"chai": "^4.3.10",
"decache": "^4.6.2",
"eslint": "^8.51.0",
Expand All @@ -278,7 +279,7 @@
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@fabric8-analytics/fabric8-analytics-lsp-server": "^0.9.3",
"@fabric8-analytics/fabric8-analytics-lsp-server": "^0.9.4-ea.0",
"@redhat-developer/vscode-redhat-telemetry": "^0.7.0",
"@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.14",
"fs": "^0.0.1-security",
Expand Down
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ class Config {
this.triggerRHRepositoryRecommendationNotification = commands.TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION;
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.exhortMvnPath = rhdaConfig.mvn.executable.path || this.DEFAULT_MVN_EXECUTABLE;
this.exhortNpmPath = rhdaConfig.npm.executable.path || this.DEFAULT_NPM_EXECUTABLE;
Expand Down
2 changes: 2 additions & 0 deletions src/dependencyReportPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class DependencyReportPanel {
* Creates or shows the webview panel.
*/
public static createOrShowWebviewPanel() {
/* istanbul ignore next */
const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;
Expand Down Expand Up @@ -113,6 +114,7 @@ export class DependencyReportPanel {
DependencyReportPanel.data = null;
while (this._disposables.length) {
const x = this._disposables.pop();
/* istanbul ignore else */
if (x) {
x.dispose();
}
Expand Down
2 changes: 1 addition & 1 deletion src/exhortServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ async function tokenValidationService(options, source) {
vscode.window.showWarningMessage(`Failed to validate token. Status: ${tokenValidationStatus}`);
}
} catch (error) {
vscode.window.showErrorMessage(`Failed to validate token, Error: ${error}`);
vscode.window.showErrorMessage(`Failed to validate token, Error: ${error.message}`);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/stackAnalysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const supportedFiles = [
* @param data The data to update the panel with.
*/
function updateWebviewPanel(data) {
/* istanbul ignore else */
if (DependencyReportPanel.currentPanel) {
DependencyReportPanel.currentPanel.doUpdatePanel(data);
}
Expand Down Expand Up @@ -133,6 +134,7 @@ async function generateRHDAReport(context, uri) {

await triggerWebviewPanel(context);
const resp = await executeStackAnalysis(uri.fsPath);
/* istanbul ignore else */
if (DependencyReportPanel.currentPanel) {
await writeReportToFile(resp);
}
Expand Down
1 change: 0 additions & 1 deletion test/caStatusBarProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ suite('CAStatusBarProvider module', () => {
const uri = 'file:///mock/path';

caStatusBarProvider.showSummary(text, uri);
console.log(caStatusBarProvider['statusBarItem'].command);

expect(caStatusBarProvider['statusBarItem'].text).to.equal(text);
expect(caStatusBarProvider['statusBarItem'].tooltip).to.equal(PromptText.FULL_STACK_PROMPT_TEXT);
Expand Down
104 changes: 104 additions & 0 deletions test/exhortServices.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as chai from 'chai';
import * as sinon from 'sinon';
import * as sinonChai from 'sinon-chai';
import { rewireModule, cleanupRewireFiles } from './utils';

const expect = chai.expect;
chai.use(sinonChai);

suite('ExhortServices module', async () => {
let sandbox: sinon.SinonSandbox;

const compiledFilePath = 'out/src/exhortServices';
const stackAnalysisReportHtmlMock = '<html>RHDA Report Mock</html>';

let exhortMock = {
default: {
stackAnalysis: async () => stackAnalysisReportHtmlMock,
validateToken: async (statusCode) => statusCode,
}
};

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(() => {
sandbox.restore();
cleanupRewireFiles(compiledFilePath);
});

test('should generate RHDA report HTML from Exhort Stack Analysis service', async () => {
await exhortServicesRewire.stackAnalysisService('mock/path/to/manifest', {})
.then((result) => {
expect(result).to.equal(stackAnalysisReportHtmlMock);
})
});

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');
});

test('should fail to generate RHDA report HTML from Exhort Stack Analysis service and reject with error', async () => {

let exhortMock = {
default: {
stackAnalysis: async () => {
throw new Error('Analysis Error');
},
}
};

exhortServicesRewire.__Rewire__('exhort_javascript_api_1', exhortMock);

await exhortServicesRewire.stackAnalysisService('mock/path/to/manifest', {})
.then(() => {
throw new Error('should have thrown Analysis Error')
})
.catch((error) => {
expect(error.message).to.equal('Analysis Error');
})
});

test('should fail to perform token validation with Exhort Validate Token service and display error message', async () => {

let exhortMock = {
default: {
validateToken: async () => {
throw new Error('Validation Error');
},
}
};

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');
});
});
3 changes: 2 additions & 1 deletion test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export async function run(): Promise<void> {
hookRunInThisContext: true,
reportDir: join(__dirname, "..", "..", 'coverage'), // remove attribute for report to be saved in ./out/src/
tempDir: join(__dirname, "..", "..", '.nyc_output'), // remove attribute for output to be saved in ./out/src/
exclude: ['exhortServices.js', 'exhortServices_rewire.js', 'redhatTelemetry.js', 'redhatTelemetry_rewire.js']
})
await nyc.wrap()

Expand All @@ -39,7 +40,7 @@ export async function run(): Promise<void> {
})

// Add all files to the test suite
const files = sync("**/**.test.js", { cwd: testsRoot, ignore: ['**/stackAnalysisService.test.js'] })
const files = sync("**/**.test.js", { cwd: testsRoot })
files.forEach(f => mocha.addFile(resolve(testsRoot, f)))

const failures: number = await new Promise(executor => mocha.run(executor))
Expand Down
64 changes: 64 additions & 0 deletions test/redhatTelemetry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as chai from 'chai';
import * as sinon from 'sinon';
import * as sinonChai from 'sinon-chai';
import { rewireModule, cleanupRewireFiles } from './utils';

const expect = chai.expect;
chai.use(sinonChai);

suite('RedhatTelemetry module', async () => {
let sandbox: sinon.SinonSandbox;

const compiledFilePath = 'out/src/redhatTelemetry';
const redHatUUIDMock = 'Mock UUID';

let getRedHatUUIDMock = {
getRedHatUUID: async () => redHatUUIDMock
}

let sendEventMock = {
sendStartupEvent: sinon.spy(),
send: sinon.spy()
}

let getIdProviderMock = {
getIdProvider: async () => getRedHatUUIDMock,
getTelemetryService: async () => sendEventMock
}

let getRedHatServiceMock = {
getRedHatService: async () => getIdProviderMock
};

let redhatTelemetryRewire;

setup(async () => {
sandbox = sinon.createSandbox();
redhatTelemetryRewire = await rewireModule(compiledFilePath);
redhatTelemetryRewire.__Rewire__('vscode_redhat_telemetry_1', getRedHatServiceMock);
});

teardown(() => {
sandbox.restore();
cleanupRewireFiles(compiledFilePath);
});

test('should get UUID from vscode redhat telemetry service', async () => {
let telemetryId = await redhatTelemetryRewire.getTelemetryId({})
expect(telemetryId).to.equal(redHatUUIDMock);
});

test('should send statup telemetry event', async () => {
await redhatTelemetryRewire.startUp({})
expect(sendEventMock.sendStartupEvent).to.have.been.calledOnce;
});

test('should record telemetry event', async () => {
await redhatTelemetryRewire.record({}, 'telemetry_event_mock', { mockProp: 'mockValue' })
expect(sendEventMock.send).to.have.been.calledWith({
type: 'track',
name: 'telemetry_event_mock',
properties: { mockProp: 'mockValue' }
});
});
});
37 changes: 37 additions & 0 deletions test/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as babelCore from "@babel/core";
import * as fs from "fs";

/**
* Asynchronously imports a module.
*
* @param filepath Path to the module to be imported.
* @returns A promise that resolves to the module's exports.
*/
async function dynamicImportProvider(filepath) {
return await import(filepath)
}

/**
* Rewires a module for testing purposes.
* @param filepath path to the compiled JavaScript file of the tested module.
* @return A module instance that exposes private methods/functions/properties to be mocked/stubbed.
*/
export function rewireModule(filepath) {
let moduleBuffeer = fs.readFileSync(filepath + ".js")
let moduleSource = babelCore.transform(moduleBuffeer, { plugins: ["babel-plugin-rewire"] }).code;
fs.writeFileSync(filepath + "_rewire.js", moduleSource)
return dynamicImportProvider("../../" + filepath + "_rewire.js")
}

/**
* Removes rewired modules from file system.
* @param filepath path to the compiled JavaScript file of the tested module.
*/
export function cleanupRewireFiles(filepath) {
const fileToRemove = filepath + "_rewire.js"
try {
fs.unlinkSync(fileToRemove);
} catch (err) {
console.error(`Error deleting rewire module ${fileToRemove}: ${err.message}`);
}
}
6 changes: 0 additions & 6 deletions webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,8 @@ module.exports = (env, argv) => {
},
{
test: /\.ts$/,
exclude: /node_modules/,
use: [{
loader: 'ts-loader',
options: {
compilerOptions: {
"module": "es6" // override `tsconfig.json` so that TypeScript emits native JavaScript modules.
}
}
}]
}]
},
Expand Down

0 comments on commit e5ae4c6

Please sign in to comment.