Skip to content

Commit c70c65f

Browse files
authored
feat(commands): add min options to stats command (#724)
1 parent aef0a62 commit c70c65f

8 files changed

Lines changed: 126 additions & 6 deletions

File tree

bin/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ prog
139139
prog
140140
.command("stats")
141141
.describe(i18n.getTokenSync("cli.commands.stats.desc"))
142+
.option("-m, --min", i18n.getTokenSync("cli.commands.stats.option_min"), undefined)
142143
.example("nsecure stats")
143144
.action(commands.stats.main);
144145

docs/cli/stats.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@ The `stats` displays the statistics of the last performed scan such as :
1717
```bash
1818
$ nsecure stats
1919
```
20+
21+
## ⚙️ Available Options
22+
23+
| Name | Shortcut | Default Value | Description |
24+
| ----- | -------- | ------------- | -------------------------------------------------------- |
25+
| `--min` | `-m` | `undefined` | Filter API calls with execution time above ceiling (ms) |

i18n/arabic.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@ const cli = {
9494
elapsed: tS`مدة المسح: ${0}`,
9595
stats: tS`عدد استدعاءات API: ${0}`,
9696
error: "يجب إجراء مسح قبل عرض الإحصائيات.",
97-
errors: tS`عدد الأخطاء: ${0}`
97+
errors: tS`عدد الأخطاء: ${0}`,
98+
option_min: "تصفية استدعاءات API ذات وقت التنفيذ أعلى من الحد المحدد (بالمللي ثانية)",
99+
minNotANumber: "خطأ: يجب أن يكون --min رقماً.",
100+
statsCeiling: tS`عدد استدعاءات API فوق ${0}: ${1}`
98101
}
99102
},
100103
startHttp: {

i18n/english.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ const cli = {
9797
elapsed: tS`Scan duration: ${0}`,
9898
stats: tS`API calls count: ${0}`,
9999
error: "A scan must be performed before displaying stats.",
100-
errors: tS`Error count: ${0}`
100+
errors: tS`Error count: ${0}`,
101+
option_min: "Filter API calls with execution time above the specified ceiling (in ms)",
102+
minNotANumber: "Error: --min must be a number.",
103+
statsCeiling: tS`API calls count above ${0}: ${1}`
101104
}
102105
},
103106
startHttp: {

i18n/french.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ const cli = {
9797
elapsed: tS`Durée du scan: ${0}`,
9898
stats: tS`Nombre d'appels API: ${0}`,
9999
error: "Un scan doit être effectué avant d'afficher les statistiques.",
100-
errors: tS`Nombre d'erreurs: ${0}`
100+
errors: tS`Nombre d'erreurs: ${0}`,
101+
option_min: "Filtrer les appels API avec un temps d'exécution supérieur au plafond spécifié (en ms)",
102+
minNotANumber: "Erreur: --min doit être un nombre.",
103+
statsCeiling: tS`Nombre d'appels API au-dessus de ${0}: ${1}`
101104
}
102105
},
103106
startHttp: {

i18n/turkish.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@ const cli = {
9696
elapsed: tS`Tarama süresi: ${0}`,
9797
stats: tS`API çağrı sayısı: ${0}`,
9898
error: "İstatistikleri görüntülemeden önce bir tarama yapılmalıdır.",
99-
errors: tS`Hata sayısı: ${0}`
99+
errors: tS`Hata sayısı: ${0}`,
100+
option_min: "Belirtilen tavan değerinin (ms cinsinden) üzerinde yürütme süresine sahip API çağrılarını filtrele",
101+
minNotANumber: "Hata: --min bir sayı olmalıdır.",
102+
statsCeiling: tS`${0} üzerindeki API çağrıları sayısı: ${1}`
100103
}
101104
},
102105
startHttp: {

src/commands/stats.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,36 @@ import path from "node:path";
66
import { logScannerStat, logScannerError, log, logError, formatMs } from "./loggers/logger.js";
77

88
export async function main(options) {
9-
const { getScanResult = getScanFromFile, logger = {
9+
const { getScanResult = getScanFromFile, min, logger = {
1010
logScannerStat,
1111
logScannerError,
1212
log,
1313
logError
1414
} } = options;
15+
16+
if (min !== undefined && typeof min !== "number") {
17+
logger.logError("cli.commands.stats.minNotANumber");
18+
19+
return;
20+
}
21+
1522
try {
1623
const scanResult = await getScanResult();
1724
const { metadata } = scanResult;
1825

1926
logger.log("cli.commands.stats.elapsed", formatMs(metadata.executionTime));
2027
logger.log("cli.commands.stats.stats", metadata.apiCallsCount);
21-
metadata.apiCalls.forEach((call) => {
28+
const apiCallsToLog = min === undefined ?
29+
metadata.apiCalls : metadata.apiCalls.filter(({ executionTime }) => executionTime > min);
30+
31+
if (typeof min === "number" && apiCallsToLog.length !== metadata.apiCallsCount) {
32+
logger.log("cli.commands.stats.statsCeiling", formatMs(min), apiCallsToLog.length);
33+
}
34+
35+
apiCallsToLog.forEach((call) => {
2236
logger.logScannerStat(call, false);
2337
});
38+
2439
if (metadata.errorCount === 0) {
2540
return;
2641
}

test/commands/stats.test.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,5 +115,91 @@ describe("stats", () => {
115115
}, false]);
116116
assert.equal(logger.log.mock.calls[2], undefined);
117117
});
118+
119+
test("should filter API calls when min parameter is provided", async(t) => {
120+
const scanResult = JSON.parse(await readFile(path.join(import.meta.dirname, "..", "fixtures", "result-test3.json"), "utf8"));
121+
122+
async function getScanResult() {
123+
return Promise.resolve(scanResult);
124+
}
125+
126+
const logger = {
127+
logScannerStat: t.mock.fn(),
128+
logScannerError: t.mock.fn(),
129+
log: t.mock.fn(),
130+
logError: t.mock.fn()
131+
};
132+
133+
await main({
134+
getScanResult,
135+
logger,
136+
min: 50
137+
});
138+
139+
assert.deepEqual(logger.log.mock.calls[0].arguments, ["cli.commands.stats.elapsed", "771ms"]);
140+
assert.deepEqual(logger.log.mock.calls[1].arguments, ["cli.commands.stats.stats", 3]);
141+
assert.deepEqual(logger.log.mock.calls[2].arguments, ["cli.commands.stats.statsCeiling", "50ms", 2]);
142+
assert.deepEqual(logger.logScannerStat.mock.calls.length, 2);
143+
assert.deepEqual(logger.logScannerStat.mock.calls[0].arguments, [{
144+
name: "pacote.extract react@19.2.4",
145+
startedAt: 1774601089529,
146+
executionTime: 83
147+
}, false]);
148+
assert.deepEqual(logger.logScannerStat.mock.calls[1].arguments, [{
149+
name: "tarball.scanDirOrArchive react@19.2.4",
150+
startedAt: 1774601089612,
151+
executionTime: 247
152+
}, false]);
153+
});
154+
155+
test("should not disply the ceiling log if api calls count and api calls count above min are the same", async(t) => {
156+
const scanResult = JSON.parse(await readFile(path.join(import.meta.dirname, "..", "fixtures", "result-test3.json"), "utf8"));
157+
158+
async function getScanResult() {
159+
return Promise.resolve(scanResult);
160+
}
161+
162+
const logger = {
163+
logScannerStat: t.mock.fn(),
164+
logScannerError: t.mock.fn(),
165+
log: t.mock.fn(),
166+
logError: t.mock.fn()
167+
};
168+
169+
await main({
170+
getScanResult,
171+
logger,
172+
min: 10
173+
});
174+
175+
assert.deepEqual(logger.log.mock.calls[0].arguments, ["cli.commands.stats.elapsed", "771ms"]);
176+
assert.deepEqual(logger.log.mock.calls[1].arguments, ["cli.commands.stats.stats", 3]);
177+
assert.deepEqual(logger.log.mock.calls[2].arguments, ["cli.commands.stats.errors", 2]);
178+
});
179+
180+
test("should log error when min parameter is not a number", async(t) => {
181+
const scanResult = JSON.parse(await readFile(path.join(import.meta.dirname, "..", "fixtures", "result-test3.json"), "utf8"));
182+
183+
async function getScanResult() {
184+
return Promise.resolve(scanResult);
185+
}
186+
187+
const logger = {
188+
logScannerStat: t.mock.fn(),
189+
logScannerError: t.mock.fn(),
190+
log: t.mock.fn(),
191+
logError: t.mock.fn()
192+
};
193+
194+
await main({
195+
getScanResult,
196+
logger,
197+
min: "not-a-number"
198+
});
199+
200+
assert.deepEqual(logger.logError.mock.calls[0].arguments, ["cli.commands.stats.minNotANumber"]);
201+
assert.strictEqual(logger.logScannerStat.mock.callCount(), 0);
202+
assert.strictEqual(logger.log.mock.callCount(), 0);
203+
});
118204
});
119205

0 commit comments

Comments
 (0)