-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(e2e): add nominal tests for exec & init CLI commands
no breaking changes use childprocess to collect CLI stdout missing limit case tests fix #395
- Loading branch information
1 parent
317e21d
commit bae3766
Showing
6 changed files
with
228 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import dotenv from "dotenv"; | ||
dotenv.config(); | ||
|
||
// 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, "../.."); | ||
|
||
describe("Report execute command", async() => { | ||
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", | ||
"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 🚀." | ||
]; | ||
|
||
let actualLines: string[] = []; | ||
|
||
try { | ||
actualLines = await filterProcessStdout(options, byMessage); | ||
} | ||
catch (error) { | ||
console.log(error); | ||
|
||
assert.fail("Execute command should not throw"); | ||
} | ||
|
||
assert.deepEqual(actualLines, expectedLines, "we are expecting these lines"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import dotenv from "dotenv"; | ||
dotenv.config(); | ||
|
||
// 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.ts"; | ||
|
||
// CONSTANTS | ||
const __dirname = path.dirname(fileURLToPath(import.meta.url)); | ||
const processDir = path.join(__dirname, "..", "fixtures"); | ||
const configFilePath = path.join(processDir, ".nodesecurerc"); | ||
|
||
describe("Report init command", async() => { | ||
beforeEach(async() => await fs.unlink(configFilePath)); | ||
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) => { | ||
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) => { | ||
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 | ||
}); | ||
} |