-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
test(e2e): add nominal tests for exec & init CLI commands #434
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,5 +65,9 @@ json/ | |
reports/ | ||
preview/ | ||
dist/ | ||
.nodesecurerc | ||
/.nodesecurerc | ||
.DS_Store | ||
|
||
# IDE | ||
.vscode | ||
jsconfig.json |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,68 @@ | ||||||||
import dotenv from "dotenv"; | ||||||||
dotenv.config(); | ||||||||
Comment on lines
+1
to
+2
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Require |
||||||||
|
||||||||
// Import Node.js Dependencies | ||||||||
import { fileURLToPath } from "node:url"; | ||||||||
import path from "node:path"; | ||||||||
import fs from "node:fs/promises"; | ||||||||
import { afterEach, describe, it } from "node:test"; | ||||||||
import assert from "node:assert"; | ||||||||
|
||||||||
// Import Third-party Dependencies | ||||||||
import stripAnsi from "strip-ansi"; | ||||||||
|
||||||||
// Import Internal Dependencies | ||||||||
import { filterProcessStdout } from "../helpers/reportCommandRunner.js"; | ||||||||
import * as CONSTANTS from "../../src/constants.js"; | ||||||||
|
||||||||
// CONSTANTS | ||||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url)); | ||||||||
const processDir = path.join(__dirname, "../.."); | ||||||||
PierreDemailly marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
describe("Report execute command", async() => { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
afterEach(async() => await fs.rm(CONSTANTS.DIRS.CLONES, { | ||||||||
recursive: true, force: true | ||||||||
})); | ||||||||
|
||||||||
it("should execute command on fixture '.nodesecurerc'", async() => { | ||||||||
const options = { | ||||||||
cmd: "node", | ||||||||
args: ["dist/bin/index.js", "execute"], | ||||||||
cwd: processDir | ||||||||
}; | ||||||||
|
||||||||
function byMessage(buffer) { | ||||||||
const message = `.*`; | ||||||||
const afterNonAlphaNum = String.raw`?<=[^a-zA-Z\d\s:]\s`; | ||||||||
const beforeTime = String.raw`?=\s\d{1,5}.\d{1,4}ms`; | ||||||||
const withoutDuplicates = String.raw`(?![\s\S]*\1)`; | ||||||||
|
||||||||
const matchMessage = `(${afterNonAlphaNum})(${message})(${beforeTime})|(${afterNonAlphaNum})(${message})`; | ||||||||
const reg = new RegExp(`(${matchMessage})${withoutDuplicates}`, "g"); | ||||||||
|
||||||||
const matchedMessages = stripAnsi(buffer.toString()).match(reg); | ||||||||
|
||||||||
return matchedMessages ?? [""]; | ||||||||
} | ||||||||
|
||||||||
const expectedLines = [ | ||||||||
"Executing nreport at: C:\\PERSO\\dev\\report", | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
"title: Default report title", | ||||||||
"reporters: html,pdf", | ||||||||
"[Fetcher: NPM] - Fetching NPM packages metadata on the NPM Registry", | ||||||||
"", | ||||||||
"[Fetcher: NPM] - successfully executed in", | ||||||||
"[Fetcher: GIT] - Cloning GIT repositories", | ||||||||
"[Fetcher: GIT] - Fetching repositories metadata on the NPM Registry", | ||||||||
"[Fetcher: GIT] - successfully executed in", | ||||||||
"[Reporter: HTML] - Building template and assets", | ||||||||
"[Reporter: HTML] - successfully executed in", | ||||||||
"[Reporter: PDF] - Using puppeteer to convert HTML content to PDF", | ||||||||
"[Reporter: PDF] - successfully executed in", | ||||||||
"Security report successfully generated! Enjoy 🚀." | ||||||||
]; | ||||||||
|
||||||||
const actualLines = await filterProcessStdout(options, byMessage); | ||||||||
assert.deepEqual(actualLines, expectedLines, "we are expecting these lines"); | ||||||||
}); | ||||||||
}); |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,48 @@ | ||||||||
import dotenv from "dotenv"; | ||||||||
dotenv.config(); | ||||||||
|
||||||||
Comment on lines
+1
to
+3
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Same as above |
||||||||
// Import Node.js Dependencies | ||||||||
import { fileURLToPath } from "node:url"; | ||||||||
import fs from "node:fs/promises"; | ||||||||
import path from "node:path"; | ||||||||
import { beforeEach, describe, it } from "node:test"; | ||||||||
import assert from "node:assert"; | ||||||||
|
||||||||
// Import Third-party Dependencies | ||||||||
import stripAnsi from "strip-ansi"; | ||||||||
|
||||||||
// Import Internal Dependencies | ||||||||
import { runProcess } from "../helpers/reportCommandRunner.js"; | ||||||||
|
||||||||
// CONSTANTS | ||||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url)); | ||||||||
const processDir = path.join(__dirname, "..", "fixtures"); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder why it's prefixed by k ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a convention for all the constants in all our projects: file-scoped constants should be prefixed with AFAIK this convention comes from the Google C++ style guide used in Node.js (it's more used for Symbols tho) |
||||||||
const configFilePath = path.join(processDir, ".nodesecurerc"); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
|
||||||||
describe("Report init command", async() => { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
beforeEach(async() => await fs.unlink(configFilePath)); | ||||||||
Comment on lines
+18
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Il faudrait plutôt créer la configuration dans le path temp pour ne pas avoir de problèmes avec une modification non-prévu du fichier dans /fixtures |
||||||||
it("should create config if not exists", async() => { | ||||||||
const lines = [ | ||||||||
/.*/, | ||||||||
/ > Executing nreport at: .*$/, | ||||||||
/.*/, | ||||||||
/Successfully generated NodeSecure runtime configuration at current location/, | ||||||||
/.*/ | ||||||||
]; | ||||||||
|
||||||||
const processOptions = { | ||||||||
cmd: "node", | ||||||||
args: ["dist/bin/index.js", "initialize"], | ||||||||
cwd: processDir | ||||||||
}; | ||||||||
|
||||||||
for await (const line of runProcess(processOptions)) { | ||||||||
const regexp = lines.shift(); | ||||||||
assert.ok(regexp, "we are expecting this line"); | ||||||||
assert.ok(regexp.test(stripAnsi(line)), `line (${line}) matches ${regexp}`); | ||||||||
} | ||||||||
|
||||||||
// to avoid false positive if no lines have been emitted from process | ||||||||
assert.equal(lines.length, 0); | ||||||||
}); | ||||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
{ | ||
"version": "1.0.0", | ||
"i18n": "english", | ||
"strategy": "github-advisory", | ||
"registry": "https://registry.npmjs.org", | ||
"report": { | ||
"theme": "light", | ||
"includeTransitiveInternal": false, | ||
"reporters": [ | ||
"html", | ||
"pdf" | ||
], | ||
"charts": [ | ||
{ | ||
"name": "Extensions", | ||
"display": true, | ||
"interpolation": "d3.interpolateRainbow", | ||
"type": "bar" | ||
}, | ||
{ | ||
"name": "Licenses", | ||
"display": true, | ||
"interpolation": "d3.interpolateCool", | ||
"type": "bar" | ||
}, | ||
{ | ||
"name": "Warnings", | ||
"display": true, | ||
"type": "horizontalBar", | ||
"interpolation": "d3.interpolateInferno" | ||
}, | ||
{ | ||
"name": "Flags", | ||
"display": true, | ||
"type": "horizontalBar", | ||
"interpolation": "d3.interpolateSinebow" | ||
} | ||
], | ||
"title": "Default report title", | ||
"showFlags": true | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// Import Node.js Dependencies | ||
import { ChildProcess, spawn } from "node:child_process"; | ||
import { createInterface } from "node:readline"; | ||
|
||
// Import Third-party Dependencies | ||
import stripAnsi from "strip-ansi"; | ||
|
||
export async function* runProcess(options) { | ||
const childProcess = spawnedProcess(options); | ||
try { | ||
if (!childProcess.stdout) { | ||
return; | ||
} | ||
|
||
const rStream = createInterface(childProcess.stdout); | ||
|
||
for await (const line of rStream) { | ||
yield stripAnsi(line); | ||
} | ||
} | ||
finally { | ||
childProcess.kill(); | ||
} | ||
} | ||
|
||
export function filterProcessStdout(options, filter): Promise<string[]> { | ||
return new Promise((resolve, reject) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. childprocess was turned into promise in order to collect filtered stdout outputs and emit them all at the end. |
||
const childProcess = spawnedProcess(options); | ||
const output = new Set<string>(); | ||
|
||
childProcess.stdout?.on("data", (buffer) => { | ||
filter(buffer).forEach((filteredData) => { | ||
output.add(filteredData); | ||
}); | ||
}); | ||
|
||
childProcess.on("close", (code) => { | ||
Check failure on line 37 in test/helpers/reportCommandRunner.ts GitHub Actions / test (20.x, ubuntu-latest)
|
||
resolve(Array.from(output)); | ||
}); | ||
|
||
childProcess.on("error", (err) => { | ||
reject(err); | ||
}); | ||
}); | ||
} | ||
|
||
function spawnedProcess(options): ChildProcess { | ||
const { cmd, args = [], cwd = process.cwd() } = options; | ||
|
||
return spawn(cmd, args, { | ||
stdio: ["ignore", "pipe", "pipe", "ipc"], | ||
cwd | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think functional tests are not executed during development as we expect quick feedback from small unit tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fine to have a separate script for e2e tests, however, the glob from existing
test-only
script still match e2e tests files.I think it's simpler to handle this via file names, for instance: