qa360 1.0.4 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/history.js +1 -1
- package/dist/commands/pack.js +1 -1
- package/dist/commands/run.d.ts +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +1 -1
- package/dist/commands/secrets.js +1 -1
- package/dist/commands/serve.js +1 -1
- package/dist/commands/verify.js +1 -1
- package/dist/core/adapters/gitleaks-secrets.d.ts +115 -0
- package/dist/core/adapters/gitleaks-secrets.d.ts.map +1 -0
- package/dist/core/adapters/gitleaks-secrets.js +410 -0
- package/dist/core/adapters/k6-perf.d.ts +86 -0
- package/dist/core/adapters/k6-perf.d.ts.map +1 -0
- package/dist/core/adapters/k6-perf.js +398 -0
- package/dist/core/adapters/osv-deps.d.ts +124 -0
- package/dist/core/adapters/osv-deps.d.ts.map +1 -0
- package/dist/core/adapters/osv-deps.js +372 -0
- package/dist/core/adapters/playwright-api.d.ts +82 -0
- package/dist/core/adapters/playwright-api.d.ts.map +1 -0
- package/dist/core/adapters/playwright-api.js +252 -0
- package/dist/core/adapters/playwright-ui.d.ts +115 -0
- package/dist/core/adapters/playwright-ui.d.ts.map +1 -0
- package/dist/core/adapters/playwright-ui.js +346 -0
- package/dist/core/adapters/semgrep-sast.d.ts +100 -0
- package/dist/core/adapters/semgrep-sast.d.ts.map +1 -0
- package/dist/core/adapters/semgrep-sast.js +322 -0
- package/dist/core/adapters/zap-dast.d.ts +134 -0
- package/dist/core/adapters/zap-dast.d.ts.map +1 -0
- package/dist/core/adapters/zap-dast.js +424 -0
- package/dist/core/hooks/compose.d.ts +62 -0
- package/dist/core/hooks/compose.d.ts.map +1 -0
- package/dist/core/hooks/compose.js +225 -0
- package/dist/core/hooks/runner.d.ts +69 -0
- package/dist/core/hooks/runner.d.ts.map +1 -0
- package/dist/core/hooks/runner.js +303 -0
- package/dist/core/index.d.ts +74 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +39 -0
- package/dist/core/pack/migrator.d.ts +52 -0
- package/dist/core/pack/migrator.d.ts.map +1 -0
- package/dist/core/pack/migrator.js +304 -0
- package/dist/core/pack/validator.d.ts +43 -0
- package/dist/core/pack/validator.d.ts.map +1 -0
- package/dist/core/pack/validator.js +292 -0
- package/dist/core/proof/bundle.d.ts +138 -0
- package/dist/core/proof/bundle.d.ts.map +1 -0
- package/dist/core/proof/bundle.js +160 -0
- package/dist/core/proof/canonicalize.d.ts +48 -0
- package/dist/core/proof/canonicalize.d.ts.map +1 -0
- package/dist/core/proof/canonicalize.js +105 -0
- package/dist/core/proof/index.d.ts +14 -0
- package/dist/core/proof/index.d.ts.map +1 -0
- package/dist/core/proof/index.js +18 -0
- package/dist/core/proof/schema.d.ts +218 -0
- package/dist/core/proof/schema.d.ts.map +1 -0
- package/dist/core/proof/schema.js +263 -0
- package/dist/core/proof/signer.d.ts +112 -0
- package/dist/core/proof/signer.d.ts.map +1 -0
- package/dist/core/proof/signer.js +226 -0
- package/dist/core/proof/verifier.d.ts +98 -0
- package/dist/core/proof/verifier.d.ts.map +1 -0
- package/dist/core/proof/verifier.js +302 -0
- package/dist/core/runner/phase3-runner.d.ts +102 -0
- package/dist/core/runner/phase3-runner.d.ts.map +1 -0
- package/dist/core/runner/phase3-runner.js +471 -0
- package/dist/core/secrets/crypto.d.ts +76 -0
- package/dist/core/secrets/crypto.d.ts.map +1 -0
- package/dist/core/secrets/crypto.js +225 -0
- package/dist/core/secrets/manager.d.ts +77 -0
- package/dist/core/secrets/manager.d.ts.map +1 -0
- package/dist/core/secrets/manager.js +219 -0
- package/dist/core/security/redaction-patterns-extended.d.ts +28 -0
- package/dist/core/security/redaction-patterns-extended.d.ts.map +1 -0
- package/dist/core/security/redaction-patterns-extended.js +247 -0
- package/dist/core/security/redactor.d.ts +72 -0
- package/dist/core/security/redactor.d.ts.map +1 -0
- package/dist/core/security/redactor.js +279 -0
- package/dist/core/serve/diagnostics-collector.d.ts +33 -0
- package/dist/core/serve/diagnostics-collector.d.ts.map +1 -0
- package/dist/core/serve/diagnostics-collector.js +149 -0
- package/dist/core/serve/health-checker.d.ts +45 -0
- package/dist/core/serve/health-checker.d.ts.map +1 -0
- package/dist/core/serve/health-checker.js +219 -0
- package/dist/core/serve/index.d.ts +9 -0
- package/dist/core/serve/index.d.ts.map +1 -0
- package/dist/core/serve/index.js +8 -0
- package/dist/core/serve/metrics-collector.d.ts +25 -0
- package/dist/core/serve/metrics-collector.d.ts.map +1 -0
- package/dist/core/serve/metrics-collector.js +322 -0
- package/dist/core/serve/process-manager.d.ts +37 -0
- package/dist/core/serve/process-manager.d.ts.map +1 -0
- package/dist/core/serve/process-manager.js +213 -0
- package/dist/core/serve/server.d.ts +37 -0
- package/dist/core/serve/server.d.ts.map +1 -0
- package/dist/core/serve/server.js +191 -0
- package/dist/core/types/pack-v1.d.ts +162 -0
- package/dist/core/types/pack-v1.d.ts.map +1 -0
- package/dist/core/types/pack-v1.js +5 -0
- package/dist/core/types/trust-score.d.ts +70 -0
- package/dist/core/types/trust-score.d.ts.map +1 -0
- package/dist/core/types/trust-score.js +191 -0
- package/dist/core/vault/cas.d.ts +87 -0
- package/dist/core/vault/cas.d.ts.map +1 -0
- package/dist/core/vault/cas.js +255 -0
- package/dist/core/vault/index.d.ts +205 -0
- package/dist/core/vault/index.d.ts.map +1 -0
- package/dist/core/vault/index.js +631 -0
- package/package.json +13 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k6-perf.d.ts","sourceRoot":"","sources":["../../../src/core/adapters/k6-perf.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE;QACP,qBAAqB,EAAE,MAAM,CAAC;QAC9B,qBAAqB,EAAE,MAAM,CAAC;QAC9B,oBAAoB,EAAE,MAAM,CAAC;QAC7B,eAAe,EAAE,MAAM,CAAC;QACxB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,UAAU,EAAE;QACV,CAAC,GAAG,EAAE,MAAM,GAAG;YACb,MAAM,EAAE,OAAO,CAAC;YAChB,KAAK,EAAE,MAAM,CAAC;YACd,SAAS,EAAE,MAAM,CAAC;SACnB,CAAC;KACH,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAmB;IACnC,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,GAAE,MAAsB;IAK9C;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAuB9D;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAsGxB;;OAEG;YACW,SAAS;IAqEvB;;OAEG;IACH,OAAO,CAAC,cAAc;IA0EtB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAsBxB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAwB3B;;OAEG;IACH,OAAO,CAAC,aAAa;IA0BrB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;OAEG;WACU,WAAW,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAoB3E;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE;CA0BlF"}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 k6 Performance Adapter (Socle OOTB)
|
|
3
|
+
* Short performance tests with P95 calculation
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { writeFileSync, unlinkSync, existsSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { SecurityRedactor } from '../security/redactor.js';
|
|
9
|
+
export class K6PerfAdapter {
|
|
10
|
+
redactor;
|
|
11
|
+
workingDir;
|
|
12
|
+
constructor(workingDir = process.cwd()) {
|
|
13
|
+
this.redactor = SecurityRedactor.forLogs();
|
|
14
|
+
this.workingDir = workingDir;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Execute k6 performance test
|
|
18
|
+
*/
|
|
19
|
+
async runPerfTest(config) {
|
|
20
|
+
const scriptPath = join(this.workingDir, 'k6-test-temp.js');
|
|
21
|
+
try {
|
|
22
|
+
// Generate k6 script
|
|
23
|
+
const script = this.generateK6Script(config);
|
|
24
|
+
writeFileSync(scriptPath, script);
|
|
25
|
+
console.log(`⚡ Running k6 performance test (${config.duration || '30s'}, ${config.vus || 5} VUs)`);
|
|
26
|
+
// Execute k6
|
|
27
|
+
const result = await this.executeK6(scriptPath, config);
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
// Cleanup temp script
|
|
32
|
+
if (existsSync(scriptPath)) {
|
|
33
|
+
unlinkSync(scriptPath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Generate k6 test script
|
|
39
|
+
*/
|
|
40
|
+
generateK6Script(config) {
|
|
41
|
+
const duration = config.duration || '30s';
|
|
42
|
+
const vus = config.vus || 5;
|
|
43
|
+
const p95Budget = config.budgets?.perf_p95_ms || 2000;
|
|
44
|
+
const scenarios = config.scenarios || {
|
|
45
|
+
load_test: {
|
|
46
|
+
executor: 'constant-vus',
|
|
47
|
+
vus: vus,
|
|
48
|
+
duration: duration
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
return `
|
|
52
|
+
import http from 'k6/http';
|
|
53
|
+
import { check, sleep } from 'k6';
|
|
54
|
+
import { Rate } from 'k6/metrics';
|
|
55
|
+
|
|
56
|
+
// Custom metrics
|
|
57
|
+
export let errorRate = new Rate('errors');
|
|
58
|
+
|
|
59
|
+
export let options = {
|
|
60
|
+
scenarios: ${JSON.stringify(scenarios, null, 2)},
|
|
61
|
+
thresholds: {
|
|
62
|
+
'http_req_duration': ['p(95)<${p95Budget}'],
|
|
63
|
+
'http_req_failed': ['rate<0.1'], // Less than 10% errors
|
|
64
|
+
'errors': ['rate<0.1']
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default function() {
|
|
69
|
+
const baseUrl = '${config.baseUrl}';
|
|
70
|
+
|
|
71
|
+
// Main request
|
|
72
|
+
const response = http.get(baseUrl, {
|
|
73
|
+
headers: {
|
|
74
|
+
'User-Agent': 'QA360-k6-Perf/1.0'
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Check response
|
|
79
|
+
const success = check(response, {
|
|
80
|
+
'status is 200-299': (r) => r.status >= 200 && r.status < 300,
|
|
81
|
+
'response time < ${p95Budget}ms': (r) => r.timings.duration < ${p95Budget}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
errorRate.add(!success);
|
|
85
|
+
|
|
86
|
+
// Small delay between requests
|
|
87
|
+
sleep(0.1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function handleSummary(data) {
|
|
91
|
+
return {
|
|
92
|
+
'stdout': textSummary(data, { indent: ' ', enableColors: false }),
|
|
93
|
+
'/tmp/k6-summary.json': JSON.stringify(data, null, 2)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function textSummary(data, options = {}) {
|
|
98
|
+
const indent = options.indent || '';
|
|
99
|
+
const colors = options.enableColors !== false;
|
|
100
|
+
|
|
101
|
+
let output = '';
|
|
102
|
+
|
|
103
|
+
// Test run info
|
|
104
|
+
output += indent + 'Test Run Summary\\n';
|
|
105
|
+
output += indent + '================\\n\\n';
|
|
106
|
+
|
|
107
|
+
// Scenarios
|
|
108
|
+
if (data.root_group && data.root_group.groups) {
|
|
109
|
+
for (const [name, group] of Object.entries(data.root_group.groups)) {
|
|
110
|
+
output += indent + \`Scenario: \${name}\\n\`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Metrics
|
|
115
|
+
output += indent + 'Metrics:\\n';
|
|
116
|
+
for (const [name, metric] of Object.entries(data.metrics)) {
|
|
117
|
+
if (metric.type === 'trend') {
|
|
118
|
+
output += indent + \` \${name}: avg=\${metric.values.avg.toFixed(2)}ms p95=\${metric.values['p(95)'].toFixed(2)}ms\\n\`;
|
|
119
|
+
} else if (metric.type === 'rate') {
|
|
120
|
+
output += indent + \` \${name}: \${(metric.values.rate * 100).toFixed(2)}%\\n\`;
|
|
121
|
+
} else if (metric.type === 'counter') {
|
|
122
|
+
output += indent + \` \${name}: \${metric.values.count}\\n\`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Thresholds
|
|
127
|
+
if (data.thresholds) {
|
|
128
|
+
output += indent + '\\nThresholds:\\n';
|
|
129
|
+
for (const [name, threshold] of Object.entries(data.thresholds)) {
|
|
130
|
+
const status = threshold.ok ? '✓' : '✗';
|
|
131
|
+
output += indent + \` \${status} \${name}\\n\`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return output;
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Execute k6 command
|
|
141
|
+
*/
|
|
142
|
+
async executeK6(scriptPath, config) {
|
|
143
|
+
return new Promise((resolve) => {
|
|
144
|
+
let output = '';
|
|
145
|
+
let error = '';
|
|
146
|
+
const args = [
|
|
147
|
+
'run',
|
|
148
|
+
'--out', 'json=/tmp/k6-results.json',
|
|
149
|
+
'--summary-export', '/tmp/k6-summary.json',
|
|
150
|
+
scriptPath
|
|
151
|
+
];
|
|
152
|
+
const child = spawn('k6', args, {
|
|
153
|
+
cwd: this.workingDir,
|
|
154
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
155
|
+
});
|
|
156
|
+
const timeout = setTimeout(() => {
|
|
157
|
+
child.kill('SIGKILL');
|
|
158
|
+
resolve({
|
|
159
|
+
success: false,
|
|
160
|
+
metrics: this.getDefaultMetrics(),
|
|
161
|
+
thresholds: {},
|
|
162
|
+
error: `k6 test timed out after ${config.timeout || 120000}ms`
|
|
163
|
+
});
|
|
164
|
+
}, config.timeout || 120000);
|
|
165
|
+
if (child.stdout) {
|
|
166
|
+
child.stdout.on('data', (data) => {
|
|
167
|
+
output += data.toString();
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (child.stderr) {
|
|
171
|
+
child.stderr.on('data', (data) => {
|
|
172
|
+
error += data.toString();
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
child.on('close', (code) => {
|
|
176
|
+
clearTimeout(timeout);
|
|
177
|
+
if (code === 0) {
|
|
178
|
+
// Parse results
|
|
179
|
+
const result = this.parseK6Results(output, config);
|
|
180
|
+
resolve(result);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
resolve({
|
|
184
|
+
success: false,
|
|
185
|
+
metrics: this.getDefaultMetrics(),
|
|
186
|
+
thresholds: {},
|
|
187
|
+
error: this.redactor.redact(error || `k6 exited with code ${code}`),
|
|
188
|
+
rawOutput: this.redactor.redact(output)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
child.on('error', (err) => {
|
|
193
|
+
clearTimeout(timeout);
|
|
194
|
+
resolve({
|
|
195
|
+
success: false,
|
|
196
|
+
metrics: this.getDefaultMetrics(),
|
|
197
|
+
thresholds: {},
|
|
198
|
+
error: `Failed to start k6: ${err.message}. Make sure k6 is installed.`
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Parse k6 results from output
|
|
205
|
+
*/
|
|
206
|
+
parseK6Results(output, config) {
|
|
207
|
+
try {
|
|
208
|
+
// Try to read JSON summary if available
|
|
209
|
+
const summaryPath = '/tmp/k6-summary.json';
|
|
210
|
+
let summaryData = null;
|
|
211
|
+
try {
|
|
212
|
+
if (existsSync(summaryPath)) {
|
|
213
|
+
const summaryContent = require('fs').readFileSync(summaryPath, 'utf8');
|
|
214
|
+
summaryData = JSON.parse(summaryContent);
|
|
215
|
+
unlinkSync(summaryPath); // Cleanup
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Continue with text parsing
|
|
220
|
+
}
|
|
221
|
+
let metrics = this.getDefaultMetrics();
|
|
222
|
+
let thresholds = {};
|
|
223
|
+
if (summaryData && summaryData.metrics) {
|
|
224
|
+
// Parse from JSON summary
|
|
225
|
+
const m = summaryData.metrics;
|
|
226
|
+
metrics = {
|
|
227
|
+
http_req_duration_p95: m.http_req_duration?.values?.['p(95)'] || 0,
|
|
228
|
+
http_req_duration_avg: m.http_req_duration?.values?.avg || 0,
|
|
229
|
+
http_req_failed_rate: (m.http_req_failed?.values?.rate || 0) * 100,
|
|
230
|
+
http_reqs_total: m.http_reqs?.values?.count || 0,
|
|
231
|
+
vus_max: m.vus_max?.values?.max || config.vus || 5,
|
|
232
|
+
iterations: m.iterations?.values?.count || 0
|
|
233
|
+
};
|
|
234
|
+
// Parse thresholds
|
|
235
|
+
if (summaryData.thresholds) {
|
|
236
|
+
for (const [name, threshold] of Object.entries(summaryData.thresholds)) {
|
|
237
|
+
thresholds[name] = {
|
|
238
|
+
passed: threshold.ok || false,
|
|
239
|
+
value: 0, // Would need more parsing
|
|
240
|
+
threshold: name
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// Fallback: parse from text output
|
|
247
|
+
metrics = this.parseTextMetrics(output);
|
|
248
|
+
thresholds = this.parseTextThresholds(output);
|
|
249
|
+
}
|
|
250
|
+
// Check if test passed based on budgets
|
|
251
|
+
const p95Budget = config.budgets?.perf_p95_ms || 2000;
|
|
252
|
+
const success = metrics.http_req_duration_p95 <= p95Budget &&
|
|
253
|
+
metrics.http_req_failed_rate < 10;
|
|
254
|
+
const junit = this.generateJUnit(metrics, success, config);
|
|
255
|
+
return {
|
|
256
|
+
success,
|
|
257
|
+
metrics,
|
|
258
|
+
thresholds,
|
|
259
|
+
rawOutput: this.redactor.redact(output),
|
|
260
|
+
junit
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
return {
|
|
265
|
+
success: false,
|
|
266
|
+
metrics: this.getDefaultMetrics(),
|
|
267
|
+
thresholds: {},
|
|
268
|
+
error: `Failed to parse k6 results: ${error}`,
|
|
269
|
+
rawOutput: this.redactor.redact(output)
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Parse metrics from text output
|
|
275
|
+
*/
|
|
276
|
+
parseTextMetrics(output) {
|
|
277
|
+
const metrics = this.getDefaultMetrics();
|
|
278
|
+
// Parse common patterns from k6 output
|
|
279
|
+
const patterns = {
|
|
280
|
+
http_req_duration_p95: /http_req_duration.*p\(95\)=([0-9.]+)ms/,
|
|
281
|
+
http_req_duration_avg: /http_req_duration.*avg=([0-9.]+)ms/,
|
|
282
|
+
http_req_failed_rate: /http_req_failed[.\s]*:\s*([0-9.]+)%/,
|
|
283
|
+
http_reqs_total: /http_reqs[.\s]*:\s*([0-9]+)/,
|
|
284
|
+
iterations: /iterations[.\s]*:\s*([0-9]+)/
|
|
285
|
+
};
|
|
286
|
+
for (const [key, pattern] of Object.entries(patterns)) {
|
|
287
|
+
const match = output.match(pattern);
|
|
288
|
+
if (match) {
|
|
289
|
+
metrics[key] = parseFloat(match[1]);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return metrics;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Parse thresholds from text output
|
|
296
|
+
*/
|
|
297
|
+
parseTextThresholds(output) {
|
|
298
|
+
const thresholds = {};
|
|
299
|
+
// Look for threshold results (✓ or ✗)
|
|
300
|
+
const thresholdLines = output.split('\n').filter(line => line.includes('✓') || line.includes('✗') || line.includes('PASS') || line.includes('FAIL'));
|
|
301
|
+
for (const line of thresholdLines) {
|
|
302
|
+
const passed = line.includes('✓') || line.includes('PASS');
|
|
303
|
+
const name = line.replace(/[✓✗]/g, '').trim();
|
|
304
|
+
if (name) {
|
|
305
|
+
thresholds[name] = {
|
|
306
|
+
passed,
|
|
307
|
+
value: 0,
|
|
308
|
+
threshold: name
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return thresholds;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Generate JUnit XML
|
|
316
|
+
*/
|
|
317
|
+
generateJUnit(metrics, success, config) {
|
|
318
|
+
const timestamp = new Date().toISOString();
|
|
319
|
+
const duration = (metrics.http_req_duration_avg / 1000).toFixed(3);
|
|
320
|
+
let junit = `<?xml version="1.0" encoding="UTF-8"?>
|
|
321
|
+
<testsuite name="k6 Performance Test" tests="1" failures="${success ? 0 : 1}" time="${duration}" timestamp="${timestamp}">
|
|
322
|
+
<testcase name="Performance Test: ${config.baseUrl}" time="${duration}">
|
|
323
|
+
`;
|
|
324
|
+
if (!success) {
|
|
325
|
+
const p95Budget = config.budgets?.perf_p95_ms || 2000;
|
|
326
|
+
junit += ` <failure message="Performance budget exceeded">
|
|
327
|
+
P95 response time: ${metrics.http_req_duration_p95}ms (budget: ${p95Budget}ms)
|
|
328
|
+
Average response time: ${metrics.http_req_duration_avg}ms
|
|
329
|
+
Failed requests: ${metrics.http_req_failed_rate}%
|
|
330
|
+
Total requests: ${metrics.http_reqs_total}
|
|
331
|
+
</failure>
|
|
332
|
+
`;
|
|
333
|
+
}
|
|
334
|
+
junit += ` </testcase>
|
|
335
|
+
</testsuite>`;
|
|
336
|
+
return junit;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Get default metrics structure
|
|
340
|
+
*/
|
|
341
|
+
getDefaultMetrics() {
|
|
342
|
+
return {
|
|
343
|
+
http_req_duration_p95: 0,
|
|
344
|
+
http_req_duration_avg: 0,
|
|
345
|
+
http_req_failed_rate: 0,
|
|
346
|
+
http_reqs_total: 0,
|
|
347
|
+
vus_max: 0,
|
|
348
|
+
iterations: 0
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Check if k6 is available
|
|
353
|
+
*/
|
|
354
|
+
static async isAvailable() {
|
|
355
|
+
return new Promise((resolve) => {
|
|
356
|
+
const child = spawn('k6', ['version'], { stdio: 'pipe' });
|
|
357
|
+
child.on('close', (code) => {
|
|
358
|
+
resolve({
|
|
359
|
+
available: code === 0,
|
|
360
|
+
error: code !== 0 ? 'k6 not found. Install from https://k6.io/docs/get-started/installation/' : undefined
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
child.on('error', () => {
|
|
364
|
+
resolve({
|
|
365
|
+
available: false,
|
|
366
|
+
error: 'k6 not found. Install from https://k6.io/docs/get-started/installation/'
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Validate performance test configuration
|
|
373
|
+
*/
|
|
374
|
+
static validateConfig(config) {
|
|
375
|
+
const errors = [];
|
|
376
|
+
if (!config.baseUrl) {
|
|
377
|
+
errors.push('Performance test requires baseUrl');
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
try {
|
|
381
|
+
new URL(config.baseUrl);
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
errors.push('Performance test baseUrl must be a valid URL');
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (config.vus !== undefined && (config.vus <= 0 || config.vus > 1000)) {
|
|
388
|
+
errors.push('VUs must be between 1 and 1000');
|
|
389
|
+
}
|
|
390
|
+
if (config.duration && !/^\d+[smh]$/.test(config.duration)) {
|
|
391
|
+
errors.push('Duration must be in format like "30s", "5m", "1h"');
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
valid: errors.length === 0,
|
|
395
|
+
errors
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 OSV Dependencies Adapter (Sécurité Réelle)
|
|
3
|
+
* Scan vulnerabilities in package-lock.json / pnpm-lock.yaml
|
|
4
|
+
*/
|
|
5
|
+
import { PackSecurity } from '../types/pack-v1.js';
|
|
6
|
+
export interface OsvScanConfig {
|
|
7
|
+
workingDir: string;
|
|
8
|
+
security?: PackSecurity;
|
|
9
|
+
timeout?: number;
|
|
10
|
+
lockFiles?: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface OsvVulnerability {
|
|
13
|
+
id: string;
|
|
14
|
+
summary: string;
|
|
15
|
+
severity: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW' | 'UNKNOWN';
|
|
16
|
+
package: {
|
|
17
|
+
name: string;
|
|
18
|
+
version: string;
|
|
19
|
+
ecosystem: string;
|
|
20
|
+
};
|
|
21
|
+
affected: Array<{
|
|
22
|
+
package: {
|
|
23
|
+
name: string;
|
|
24
|
+
ecosystem: string;
|
|
25
|
+
};
|
|
26
|
+
ranges: Array<{
|
|
27
|
+
type: string;
|
|
28
|
+
events: Array<{
|
|
29
|
+
introduced?: string;
|
|
30
|
+
fixed?: string;
|
|
31
|
+
}>;
|
|
32
|
+
}>;
|
|
33
|
+
}>;
|
|
34
|
+
references: Array<{
|
|
35
|
+
type: string;
|
|
36
|
+
url: string;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
export interface OsvScanResult {
|
|
40
|
+
success: boolean;
|
|
41
|
+
vulnerabilities: OsvVulnerability[];
|
|
42
|
+
summary: {
|
|
43
|
+
total: number;
|
|
44
|
+
critical: number;
|
|
45
|
+
high: number;
|
|
46
|
+
medium: number;
|
|
47
|
+
low: number;
|
|
48
|
+
unknown: number;
|
|
49
|
+
};
|
|
50
|
+
budgetCheck: {
|
|
51
|
+
critical_passed: boolean;
|
|
52
|
+
high_passed: boolean;
|
|
53
|
+
medium_passed: boolean;
|
|
54
|
+
};
|
|
55
|
+
scannedFiles: string[];
|
|
56
|
+
error?: string;
|
|
57
|
+
rawOutput?: string;
|
|
58
|
+
junit?: string;
|
|
59
|
+
errorCode?: string;
|
|
60
|
+
}
|
|
61
|
+
export declare class OsvDepsAdapter {
|
|
62
|
+
private redactor;
|
|
63
|
+
private workingDir;
|
|
64
|
+
constructor(workingDir?: string);
|
|
65
|
+
/**
|
|
66
|
+
* Execute OSV dependency scan
|
|
67
|
+
*/
|
|
68
|
+
runDependencyScan(config: OsvScanConfig): Promise<OsvScanResult>;
|
|
69
|
+
/**
|
|
70
|
+
* Find lock files in working directory
|
|
71
|
+
*/
|
|
72
|
+
private findLockFiles;
|
|
73
|
+
/**
|
|
74
|
+
* Execute OSV scanner on lock files
|
|
75
|
+
*/
|
|
76
|
+
private executeOsvScan;
|
|
77
|
+
/**
|
|
78
|
+
* Parse OSV scanner results
|
|
79
|
+
*/
|
|
80
|
+
private parseOsvResults;
|
|
81
|
+
/**
|
|
82
|
+
* Fallback API-based scanning when osv-scanner not available
|
|
83
|
+
*/
|
|
84
|
+
private fallbackApiScan;
|
|
85
|
+
/**
|
|
86
|
+
* Map severity from various formats to standard levels
|
|
87
|
+
*/
|
|
88
|
+
private mapSeverity;
|
|
89
|
+
/**
|
|
90
|
+
* Calculate vulnerability summary
|
|
91
|
+
*/
|
|
92
|
+
private calculateSummary;
|
|
93
|
+
/**
|
|
94
|
+
* Generate budget check based on security config
|
|
95
|
+
*/
|
|
96
|
+
private generateBudgetCheck;
|
|
97
|
+
/**
|
|
98
|
+
* Get empty summary structure
|
|
99
|
+
*/
|
|
100
|
+
private getEmptySummary;
|
|
101
|
+
/**
|
|
102
|
+
* Get default budget check structure
|
|
103
|
+
*/
|
|
104
|
+
private getDefaultBudgetCheck;
|
|
105
|
+
/**
|
|
106
|
+
* Generate JUnit XML report
|
|
107
|
+
*/
|
|
108
|
+
private generateJUnit;
|
|
109
|
+
/**
|
|
110
|
+
* Validate OSV scan configuration
|
|
111
|
+
*/
|
|
112
|
+
static validateConfig(config: OsvScanConfig): {
|
|
113
|
+
valid: boolean;
|
|
114
|
+
errors: string[];
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Check if OSV scanner is available
|
|
118
|
+
*/
|
|
119
|
+
static isAvailable(): Promise<{
|
|
120
|
+
available: boolean;
|
|
121
|
+
error?: string;
|
|
122
|
+
}>;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=osv-deps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"osv-deps.d.ts","sourceRoot":"","sources":["../../../src/core/adapters/osv-deps.ts"],"names":[],"mappings":"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"}
|