qa360 2.2.14 → 2.2.18
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/cli/dist/commands/ai.js +1 -1
- package/cli/dist/commands/ask.js +362 -36
- package/cli/dist/commands/coverage.js +1 -1
- package/cli/dist/commands/crawl.js +1 -1
- package/cli/dist/commands/doctor.js +2 -2
- package/cli/dist/commands/explain.js +2 -2
- package/cli/dist/commands/flakiness.js +1 -1
- package/cli/dist/commands/generate.js +1 -1
- package/cli/dist/commands/history.js +1 -1
- package/cli/dist/commands/monitor.js +3 -3
- package/cli/dist/commands/ollama.js +1 -1
- package/cli/dist/commands/pack.js +2 -2
- package/cli/dist/commands/regression.js +1 -1
- package/cli/dist/commands/repair.js +1 -1
- package/cli/dist/commands/retry.js +1 -1
- package/cli/dist/commands/run.d.ts +1 -1
- package/cli/dist/commands/run.js +2 -1
- package/cli/dist/commands/secrets.js +1 -1
- package/cli/dist/commands/serve.js +1 -1
- package/cli/dist/commands/slo.js +1 -1
- package/cli/dist/commands/verify.js +1 -1
- package/cli/dist/core/ai/ollama-provider.js +3 -15
- package/cli/dist/core/core/coverage/analyzer.d.ts +101 -0
- package/cli/dist/core/core/coverage/analyzer.js +415 -0
- package/cli/dist/core/core/coverage/collector.d.ts +74 -0
- package/cli/dist/core/core/coverage/collector.js +459 -0
- package/cli/dist/core/core/coverage/config.d.ts +37 -0
- package/cli/dist/core/core/coverage/config.js +156 -0
- package/cli/dist/core/core/coverage/index.d.ts +11 -0
- package/cli/dist/core/core/coverage/index.js +15 -0
- package/cli/dist/core/core/coverage/types.d.ts +267 -0
- package/cli/dist/core/core/coverage/types.js +6 -0
- package/cli/dist/core/core/coverage/vault.d.ts +95 -0
- package/cli/dist/core/core/coverage/vault.js +405 -0
- package/cli/dist/core/crawler/selector-generator.js +3 -72
- package/cli/dist/core/generation/crawler-pack-generator.d.ts +1 -1
- package/cli/dist/core/generation/crawler-pack-generator.js +143 -31
- package/cli/dist/core/pack/validator.js +2 -2
- package/cli/dist/core/pack-v2/migrator.d.ts +0 -5
- package/cli/dist/core/pack-v2/migrator.js +6 -74
- package/cli/dist/core/pack-v2/validator.js +3 -4
- package/cli/dist/core/runner/phase3-runner.js +1 -12
- package/cli/dist/utils/config.d.ts +1 -1
- package/cli/dist/utils/config.js +11 -5
- package/package.json +24 -30
- package/cli/CHANGELOG.md +0 -84
- package/cli/LICENSE +0 -24
- package/cli/package.json +0 -76
- package/core/LICENSE +0 -24
- package/core/package.json +0 -81
package/cli/dist/commands/ai.js
CHANGED
|
@@ -10,7 +10,7 @@ import { Command } from 'commander';
|
|
|
10
10
|
import chalk from 'chalk';
|
|
11
11
|
import ora from 'ora';
|
|
12
12
|
import Table from 'cli-table3';
|
|
13
|
-
import { createBest, getProviderInfo, getProviderStatus, createLLMProvider, DeepSeekProvider, DeepSeekError, OllamaProvider, OllamaError, OpenAIProvider, OpenAIError, AnthropicProvider, AnthropicError } from '
|
|
13
|
+
import { createBest, getProviderInfo, getProviderStatus, createLLMProvider, DeepSeekProvider, DeepSeekError, OllamaProvider, OllamaError, OpenAIProvider, OpenAIError, AnthropicProvider, AnthropicError } from 'qa360-core';
|
|
14
14
|
/**
|
|
15
15
|
* List all available AI providers
|
|
16
16
|
*/
|
package/cli/dist/commands/ask.js
CHANGED
|
@@ -49,9 +49,136 @@ export class QA360Ask {
|
|
|
49
49
|
name: 'target',
|
|
50
50
|
message: 'URL ou endpoint cible:',
|
|
51
51
|
validate: (input) => input.length > 0 || 'URL requise'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: 'confirm',
|
|
55
|
+
name: 'needsAuth',
|
|
56
|
+
message: 'L\'API require-t-elle une authentification?',
|
|
57
|
+
default: false
|
|
52
58
|
}
|
|
53
59
|
]);
|
|
54
|
-
|
|
60
|
+
// Ask for authentication details if needed
|
|
61
|
+
let authConfig;
|
|
62
|
+
if (answers.needsAuth) {
|
|
63
|
+
authConfig = await inquirer.prompt([
|
|
64
|
+
{
|
|
65
|
+
type: 'list',
|
|
66
|
+
name: 'type',
|
|
67
|
+
message: 'Type d\'authentification:',
|
|
68
|
+
choices: [
|
|
69
|
+
{ name: 'Bearer Token (JWT, OAuth2)', value: 'bearer' },
|
|
70
|
+
{ name: 'API Key (x-api-key, etc.)', value: 'api_key' },
|
|
71
|
+
{ name: 'Basic Auth (username/password)', value: 'basic' },
|
|
72
|
+
{ name: 'Custom Header', value: 'custom' }
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: 'input',
|
|
77
|
+
name: 'bearerToken',
|
|
78
|
+
message: 'Bearer Token (laissez vide pour utiliser variable):',
|
|
79
|
+
default: '${API_TOKEN}',
|
|
80
|
+
when: (answers) => answers.type === 'bearer'
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: 'input',
|
|
84
|
+
name: 'headerName',
|
|
85
|
+
message: 'Nom du header (ex: x-api-key):',
|
|
86
|
+
default: 'x-api-key',
|
|
87
|
+
when: (answers) => answers.type === 'api_key' || answers.type === 'custom'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: 'input',
|
|
91
|
+
name: 'tokenPrefix',
|
|
92
|
+
message: 'Prefix du token (ex: "Bearer " pour "Bearer xxx"):',
|
|
93
|
+
default: 'Bearer ',
|
|
94
|
+
when: (answers) => answers.type === 'custom'
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
type: 'input',
|
|
98
|
+
name: 'apiKey',
|
|
99
|
+
message: 'Valeur de la cle API (laissez vide pour variable):',
|
|
100
|
+
default: '${API_KEY}',
|
|
101
|
+
when: (answers) => answers.type === 'api_key'
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
type: 'input',
|
|
105
|
+
name: 'username',
|
|
106
|
+
message: 'Nom d\'utilisateur:',
|
|
107
|
+
when: (answers) => answers.type === 'basic'
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
type: 'password',
|
|
111
|
+
name: 'password',
|
|
112
|
+
message: 'Mot de passe:',
|
|
113
|
+
mask: '*',
|
|
114
|
+
when: (answers) => answers.type === 'basic'
|
|
115
|
+
}
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
// Ask for specific endpoints if API type
|
|
119
|
+
let endpoints = [];
|
|
120
|
+
if (answers.type === 'api' || answers.type === 'complete') {
|
|
121
|
+
const addEndpoints = await inquirer.prompt([
|
|
122
|
+
{
|
|
123
|
+
type: 'confirm',
|
|
124
|
+
name: 'addEndpoints',
|
|
125
|
+
message: 'Voulez-vous specifier des endpoints specifiques a tester?',
|
|
126
|
+
default: true
|
|
127
|
+
}
|
|
128
|
+
]);
|
|
129
|
+
if (addEndpoints.addEndpoints) {
|
|
130
|
+
let adding = true;
|
|
131
|
+
while (adding) {
|
|
132
|
+
const endpoint = await inquirer.prompt([
|
|
133
|
+
{
|
|
134
|
+
type: 'list',
|
|
135
|
+
name: 'method',
|
|
136
|
+
message: 'Methode HTTP:',
|
|
137
|
+
choices: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
|
138
|
+
default: 'GET'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
type: 'input',
|
|
142
|
+
name: 'path',
|
|
143
|
+
message: 'Chemin (ex: /api/users):',
|
|
144
|
+
validate: (input) => input.length > 0 || 'Chemin requis'
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: 'number',
|
|
148
|
+
name: 'expectedStatus',
|
|
149
|
+
message: 'Code HTTP attendu:',
|
|
150
|
+
default: 200
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
type: 'confirm',
|
|
154
|
+
name: 'addBodyValidation',
|
|
155
|
+
message: 'Voulez-vous valider le corps de la reponse?',
|
|
156
|
+
default: false
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
type: 'input',
|
|
160
|
+
name: 'bodyValidation',
|
|
161
|
+
message: 'Champ JSON a valider (ex: data.users.0.email, ou "status:ok" pour valeur exacte):',
|
|
162
|
+
when: (answers) => answers.addBodyValidation
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
type: 'confirm',
|
|
166
|
+
name: 'addAnother',
|
|
167
|
+
message: 'Ajouter un autre endpoint?',
|
|
168
|
+
default: false
|
|
169
|
+
}
|
|
170
|
+
]);
|
|
171
|
+
endpoints.push({
|
|
172
|
+
method: endpoint.method,
|
|
173
|
+
path: endpoint.path,
|
|
174
|
+
expectedStatus: endpoint.expectedStatus,
|
|
175
|
+
bodyValidation: endpoint.bodyValidation
|
|
176
|
+
});
|
|
177
|
+
adding = endpoint.addAnother;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return this.generatePackFromAnswers({ ...answers, authConfig, endpoints });
|
|
55
182
|
}
|
|
56
183
|
async parseNaturalLanguage(query) {
|
|
57
184
|
console.log(chalk.gray(`Analyse: "${query}"`));
|
|
@@ -101,36 +228,77 @@ export class QA360Ask {
|
|
|
101
228
|
const baseUrl = `\${BASE_URL:-${answers.target}}`;
|
|
102
229
|
switch (answers.type) {
|
|
103
230
|
case 'api':
|
|
104
|
-
return this.generateApiPack(answers.name, answers.description, baseUrl);
|
|
231
|
+
return this.generateApiPack(answers.name, answers.description, baseUrl, answers.authConfig, answers.endpoints);
|
|
105
232
|
case 'web':
|
|
106
|
-
return this.generateWebPack(answers.name, answers.description, baseUrl);
|
|
233
|
+
return this.generateWebPack(answers.name, answers.description, baseUrl, answers.authConfig);
|
|
107
234
|
case 'performance':
|
|
108
|
-
return this.generatePerformancePack(answers.name, answers.description, baseUrl);
|
|
235
|
+
return this.generatePerformancePack(answers.name, answers.description, baseUrl, answers.authConfig);
|
|
109
236
|
case 'security':
|
|
110
|
-
return this.generateSecurityPack(answers.name, answers.description, baseUrl);
|
|
237
|
+
return this.generateSecurityPack(answers.name, answers.description, baseUrl, answers.authConfig);
|
|
111
238
|
case 'accessibility':
|
|
112
|
-
return this.generateAccessibilityPack(answers.name, answers.description, baseUrl);
|
|
239
|
+
return this.generateAccessibilityPack(answers.name, answers.description, baseUrl, answers.authConfig);
|
|
113
240
|
case 'complete':
|
|
114
|
-
return this.generateCompletePack(answers.name, answers.description, baseUrl);
|
|
241
|
+
return this.generateCompletePack(answers.name, answers.description, baseUrl, answers.authConfig, answers.endpoints);
|
|
115
242
|
default:
|
|
116
|
-
return this.generateApiPack(answers.name, answers.description, baseUrl);
|
|
243
|
+
return this.generateApiPack(answers.name, answers.description, baseUrl, answers.authConfig, answers.endpoints);
|
|
117
244
|
}
|
|
118
245
|
}
|
|
119
|
-
generateApiPack(name, description, baseUrl) {
|
|
246
|
+
generateApiPack(name, description, baseUrl, authConfig, endpoints) {
|
|
247
|
+
// Build smoke tests from provided endpoints or use defaults
|
|
248
|
+
let smokeTests;
|
|
249
|
+
let requests;
|
|
250
|
+
if (endpoints && endpoints.length > 0) {
|
|
251
|
+
// User provided specific endpoints - use both smoke format and request format with validation
|
|
252
|
+
smokeTests = endpoints.map(e => `${e.method} ${e.path} -> ${e.expectedStatus}`);
|
|
253
|
+
// Also build request format with response validation
|
|
254
|
+
requests = endpoints.map(e => {
|
|
255
|
+
const request = {
|
|
256
|
+
method: e.method,
|
|
257
|
+
path: e.path,
|
|
258
|
+
expect: {
|
|
259
|
+
status: e.expectedStatus
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
// Add body validation if specified
|
|
263
|
+
if (e.bodyValidation) {
|
|
264
|
+
// Parse body validation - could be "field:value" or "field.nested.path:value"
|
|
265
|
+
const colonIndex = e.bodyValidation.indexOf(':');
|
|
266
|
+
if (colonIndex !== -1) {
|
|
267
|
+
const fieldPath = e.bodyValidation.substring(0, colonIndex);
|
|
268
|
+
const expectedValue = e.bodyValidation.substring(colonIndex + 1);
|
|
269
|
+
request.expect.body = parseBodyValidation(fieldPath, expectedValue);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
// If no colon, just check field exists
|
|
273
|
+
request.expect.body = parseBodyValidation(e.bodyValidation, null);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return request;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// Use default generic endpoints
|
|
281
|
+
smokeTests = [
|
|
282
|
+
'GET /health -> 200',
|
|
283
|
+
'GET /status -> 200',
|
|
284
|
+
];
|
|
285
|
+
}
|
|
286
|
+
// Build auth config
|
|
287
|
+
const auth = buildAuthConfig(authConfig);
|
|
120
288
|
return {
|
|
121
289
|
version: 2,
|
|
122
290
|
name,
|
|
123
291
|
description,
|
|
292
|
+
auth: auth.config,
|
|
124
293
|
gates: {
|
|
125
294
|
'api-health': {
|
|
126
295
|
adapter: 'playwright-api',
|
|
127
296
|
enabled: true,
|
|
297
|
+
auth: auth.profileName,
|
|
128
298
|
config: {
|
|
129
299
|
baseUrl,
|
|
130
|
-
smoke:
|
|
131
|
-
|
|
132
|
-
'GET /status -> 200',
|
|
133
|
-
]
|
|
300
|
+
smoke: smokeTests,
|
|
301
|
+
...(requests && requests.length > 0 ? { requests } : {})
|
|
134
302
|
},
|
|
135
303
|
options: {
|
|
136
304
|
timeout: 10000,
|
|
@@ -146,15 +314,18 @@ export class QA360Ask {
|
|
|
146
314
|
}
|
|
147
315
|
};
|
|
148
316
|
}
|
|
149
|
-
generateWebPack(name, description, baseUrl) {
|
|
317
|
+
generateWebPack(name, description, baseUrl, authConfig) {
|
|
318
|
+
const auth = buildAuthConfig(authConfig);
|
|
150
319
|
return {
|
|
151
320
|
version: 2,
|
|
152
321
|
name,
|
|
153
322
|
description,
|
|
323
|
+
auth: auth.config,
|
|
154
324
|
gates: {
|
|
155
325
|
'ui-smoke': {
|
|
156
326
|
adapter: 'playwright-ui',
|
|
157
327
|
enabled: true,
|
|
328
|
+
auth: auth.profileName,
|
|
158
329
|
config: {
|
|
159
330
|
baseUrl,
|
|
160
331
|
pages: [
|
|
@@ -190,15 +361,18 @@ export class QA360Ask {
|
|
|
190
361
|
}
|
|
191
362
|
};
|
|
192
363
|
}
|
|
193
|
-
generatePerformancePack(name, description, baseUrl) {
|
|
364
|
+
generatePerformancePack(name, description, baseUrl, authConfig) {
|
|
365
|
+
const auth = buildAuthConfig(authConfig);
|
|
194
366
|
return {
|
|
195
367
|
version: 2,
|
|
196
368
|
name,
|
|
197
369
|
description,
|
|
370
|
+
auth: auth.config,
|
|
198
371
|
gates: {
|
|
199
372
|
'api-health': {
|
|
200
373
|
adapter: 'playwright-api',
|
|
201
374
|
enabled: true,
|
|
375
|
+
auth: auth.profileName,
|
|
202
376
|
config: {
|
|
203
377
|
baseUrl,
|
|
204
378
|
smoke: ['GET /health -> 200']
|
|
@@ -242,15 +416,18 @@ export default function () {
|
|
|
242
416
|
}
|
|
243
417
|
};
|
|
244
418
|
}
|
|
245
|
-
generateSecurityPack(name, description, baseUrl) {
|
|
419
|
+
generateSecurityPack(name, description, baseUrl, authConfig) {
|
|
420
|
+
const auth = buildAuthConfig(authConfig);
|
|
246
421
|
return {
|
|
247
422
|
version: 2,
|
|
248
423
|
name,
|
|
249
424
|
description,
|
|
425
|
+
auth: auth.config,
|
|
250
426
|
gates: {
|
|
251
427
|
'api-health': {
|
|
252
428
|
adapter: 'playwright-api',
|
|
253
429
|
enabled: true,
|
|
430
|
+
auth: auth.profileName,
|
|
254
431
|
config: {
|
|
255
432
|
baseUrl,
|
|
256
433
|
smoke: ['GET / -> 200']
|
|
@@ -301,15 +478,18 @@ export default function () {
|
|
|
301
478
|
}
|
|
302
479
|
};
|
|
303
480
|
}
|
|
304
|
-
generateAccessibilityPack(name, description, baseUrl) {
|
|
481
|
+
generateAccessibilityPack(name, description, baseUrl, authConfig) {
|
|
482
|
+
const auth = buildAuthConfig(authConfig);
|
|
305
483
|
return {
|
|
306
484
|
version: 2,
|
|
307
485
|
name,
|
|
308
486
|
description,
|
|
487
|
+
auth: auth.config,
|
|
309
488
|
gates: {
|
|
310
489
|
'ui-smoke': {
|
|
311
490
|
adapter: 'playwright-ui',
|
|
312
491
|
enabled: true,
|
|
492
|
+
auth: auth.profileName,
|
|
313
493
|
config: {
|
|
314
494
|
baseUrl,
|
|
315
495
|
pages: [
|
|
@@ -343,34 +523,64 @@ export default function () {
|
|
|
343
523
|
}
|
|
344
524
|
};
|
|
345
525
|
}
|
|
346
|
-
generateCompletePack(name, description, baseUrl) {
|
|
526
|
+
generateCompletePack(name, description, baseUrl, authConfig, endpoints) {
|
|
527
|
+
const auth = buildAuthConfig(authConfig);
|
|
528
|
+
// Build smoke tests from provided endpoints or use defaults
|
|
529
|
+
let apiSmokeTests;
|
|
530
|
+
let apiCrudTests;
|
|
531
|
+
let requests;
|
|
532
|
+
if (endpoints && endpoints.length > 0) {
|
|
533
|
+
// User provided specific endpoints
|
|
534
|
+
apiSmokeTests = endpoints.filter(e => e.method === 'GET').map(e => `${e.method} ${e.path} -> ${e.expectedStatus}`);
|
|
535
|
+
apiCrudTests = endpoints.map(e => `${e.method} ${e.path} -> ${e.expectedStatus}`);
|
|
536
|
+
// Build request format with response validation
|
|
537
|
+
requests = endpoints.map(e => {
|
|
538
|
+
const request = {
|
|
539
|
+
method: e.method,
|
|
540
|
+
path: e.path,
|
|
541
|
+
expect: {
|
|
542
|
+
status: e.expectedStatus
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
if (e.bodyValidation) {
|
|
546
|
+
const colonIndex = e.bodyValidation.indexOf(':');
|
|
547
|
+
if (colonIndex !== -1) {
|
|
548
|
+
const fieldPath = e.bodyValidation.substring(0, colonIndex);
|
|
549
|
+
const expectedValue = e.bodyValidation.substring(colonIndex + 1);
|
|
550
|
+
request.expect.body = parseBodyValidation(fieldPath, expectedValue);
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
request.expect.body = parseBodyValidation(e.bodyValidation, null);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return request;
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
// Use default generic endpoints
|
|
561
|
+
apiSmokeTests = ['GET /health -> 200', 'GET /api/v1/status -> 200'];
|
|
562
|
+
apiCrudTests = [
|
|
563
|
+
'GET /api/v1/users -> 200',
|
|
564
|
+
'POST /api/v1/users -> 201',
|
|
565
|
+
'PUT /api/v1/users/1 -> 200',
|
|
566
|
+
'DELETE /api/v1/users/1 -> 204'
|
|
567
|
+
];
|
|
568
|
+
}
|
|
347
569
|
return {
|
|
348
570
|
version: 2,
|
|
349
571
|
name,
|
|
350
572
|
description,
|
|
351
|
-
auth:
|
|
352
|
-
api: 'default-bearer',
|
|
353
|
-
profiles: {
|
|
354
|
-
'default-bearer': {
|
|
355
|
-
type: 'bearer',
|
|
356
|
-
config: {
|
|
357
|
-
token: '${API_TOKEN:-your-token-here}'
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
},
|
|
573
|
+
auth: auth.config,
|
|
362
574
|
gates: {
|
|
363
575
|
// API Health Check
|
|
364
576
|
'api-health': {
|
|
365
577
|
adapter: 'playwright-api',
|
|
366
578
|
enabled: true,
|
|
367
|
-
auth:
|
|
579
|
+
auth: auth.profileName,
|
|
368
580
|
config: {
|
|
369
581
|
baseUrl,
|
|
370
|
-
smoke: [
|
|
371
|
-
|
|
372
|
-
'GET /api/v1/status -> 200'
|
|
373
|
-
]
|
|
582
|
+
smoke: apiSmokeTests.length > 0 ? apiSmokeTests : ['GET /health -> 200', 'GET /api/v1/status -> 200'],
|
|
583
|
+
...(requests && requests.length > 0 ? { requests } : {})
|
|
374
584
|
},
|
|
375
585
|
options: {
|
|
376
586
|
timeout: 5000,
|
|
@@ -381,10 +591,10 @@ export default function () {
|
|
|
381
591
|
'api-crud': {
|
|
382
592
|
adapter: 'playwright-api',
|
|
383
593
|
enabled: true,
|
|
384
|
-
auth:
|
|
594
|
+
auth: auth.profileName,
|
|
385
595
|
config: {
|
|
386
596
|
baseUrl,
|
|
387
|
-
smoke: [
|
|
597
|
+
smoke: apiCrudTests.length > 0 ? apiCrudTests : [
|
|
388
598
|
'GET /api/v1/users -> 200',
|
|
389
599
|
'POST /api/v1/users -> 201',
|
|
390
600
|
'PUT /api/v1/users/1 -> 200',
|
|
@@ -566,6 +776,122 @@ export default function () {
|
|
|
566
776
|
}
|
|
567
777
|
}
|
|
568
778
|
}
|
|
779
|
+
/**
|
|
780
|
+
* Build auth config from user answers
|
|
781
|
+
*/
|
|
782
|
+
function buildAuthConfig(authConfig) {
|
|
783
|
+
if (!authConfig || !authConfig.type) {
|
|
784
|
+
return { config: undefined, profileName: undefined };
|
|
785
|
+
}
|
|
786
|
+
const profiles = {};
|
|
787
|
+
let profileName = 'default-auth';
|
|
788
|
+
switch (authConfig.type) {
|
|
789
|
+
case 'bearer':
|
|
790
|
+
profileName = 'bearer-auth';
|
|
791
|
+
profiles[profileName] = {
|
|
792
|
+
type: 'bearer',
|
|
793
|
+
config: {
|
|
794
|
+
token: authConfig.bearerToken || '${API_TOKEN}'
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
break;
|
|
798
|
+
case 'api_key':
|
|
799
|
+
profileName = 'api-key-auth';
|
|
800
|
+
profiles[profileName] = {
|
|
801
|
+
type: 'api_key',
|
|
802
|
+
config: {
|
|
803
|
+
header_name: authConfig.headerName || 'x-api-key',
|
|
804
|
+
value: authConfig.apiKey || '${API_KEY}'
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
break;
|
|
808
|
+
case 'basic':
|
|
809
|
+
profileName = 'basic-auth';
|
|
810
|
+
profiles[profileName] = {
|
|
811
|
+
type: 'basic',
|
|
812
|
+
config: {
|
|
813
|
+
username: authConfig.username || '${BASIC_USERNAME}',
|
|
814
|
+
password: authConfig.password || '${BASIC_PASSWORD}'
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
break;
|
|
818
|
+
case 'custom':
|
|
819
|
+
profileName = 'custom-auth';
|
|
820
|
+
profiles[profileName] = {
|
|
821
|
+
type: 'bearer',
|
|
822
|
+
config: {
|
|
823
|
+
token: authConfig.tokenPrefix ? `${authConfig.tokenPrefix}\${CUSTOM_TOKEN}` : '${CUSTOM_TOKEN}'
|
|
824
|
+
},
|
|
825
|
+
cache: {
|
|
826
|
+
enabled: true,
|
|
827
|
+
ttl: 300
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
832
|
+
return {
|
|
833
|
+
config: {
|
|
834
|
+
api: profileName,
|
|
835
|
+
profiles
|
|
836
|
+
},
|
|
837
|
+
profileName
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Parse body validation string into nested object
|
|
842
|
+
* Format: "field.nested.path:value" or just "field.nested.path"
|
|
843
|
+
*
|
|
844
|
+
* Examples:
|
|
845
|
+
* - "data.users.0.email" -> checks if field exists
|
|
846
|
+
* - "status:ok" -> checks if status == "ok"
|
|
847
|
+
* - "data.count:5" -> checks if data.count == 5
|
|
848
|
+
* - "data.items.length:>0" -> checks if data.items.length > 0
|
|
849
|
+
*/
|
|
850
|
+
function parseBodyValidation(fieldPath, expectedValue) {
|
|
851
|
+
const parts = fieldPath.split('.');
|
|
852
|
+
const result = {};
|
|
853
|
+
// Build nested object from path
|
|
854
|
+
let current = result;
|
|
855
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
856
|
+
current[parts[i]] = {};
|
|
857
|
+
current = current[parts[i]];
|
|
858
|
+
}
|
|
859
|
+
// Set the final value
|
|
860
|
+
const lastKey = parts[parts.length - 1];
|
|
861
|
+
if (expectedValue === null) {
|
|
862
|
+
// Just check field exists - use special sentinel
|
|
863
|
+
current[lastKey] = '__exists__';
|
|
864
|
+
}
|
|
865
|
+
else if (expectedValue.startsWith('>')) {
|
|
866
|
+
// Greater than comparison
|
|
867
|
+
const numValue = parseFloat(expectedValue.substring(1));
|
|
868
|
+
current[lastKey] = { $gt: isNaN(numValue) ? expectedValue.substring(1) : numValue };
|
|
869
|
+
}
|
|
870
|
+
else if (expectedValue.startsWith('<')) {
|
|
871
|
+
// Less than comparison
|
|
872
|
+
const numValue = parseFloat(expectedValue.substring(1));
|
|
873
|
+
current[lastKey] = { $lt: isNaN(numValue) ? expectedValue.substring(1) : numValue };
|
|
874
|
+
}
|
|
875
|
+
else if (expectedValue === 'true') {
|
|
876
|
+
current[lastKey] = true;
|
|
877
|
+
}
|
|
878
|
+
else if (expectedValue === 'false') {
|
|
879
|
+
current[lastKey] = false;
|
|
880
|
+
}
|
|
881
|
+
else if (/^\d+$/.test(expectedValue)) {
|
|
882
|
+
// Numeric value
|
|
883
|
+
current[lastKey] = parseInt(expectedValue, 10);
|
|
884
|
+
}
|
|
885
|
+
else if (/^\d+\.\d+$/.test(expectedValue)) {
|
|
886
|
+
// Float value
|
|
887
|
+
current[lastKey] = parseFloat(expectedValue);
|
|
888
|
+
}
|
|
889
|
+
else {
|
|
890
|
+
// String value
|
|
891
|
+
current[lastKey] = expectedValue;
|
|
892
|
+
}
|
|
893
|
+
return result;
|
|
894
|
+
}
|
|
569
895
|
export async function askCommand(query) {
|
|
570
896
|
const ask = new QA360Ask();
|
|
571
897
|
try {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import { existsSync } from 'fs';
|
|
8
|
-
import { CoverageCollector, CoverageAnalyzer, parseCoverageThreshold, formatCoverageThreshold } from '
|
|
8
|
+
import { CoverageCollector, CoverageAnalyzer, parseCoverageThreshold, formatCoverageThreshold } from 'qa360-core';
|
|
9
9
|
export const coverageCommand = new Command('coverage');
|
|
10
10
|
coverageCommand.description('Coverage analytics and reporting');
|
|
11
11
|
// Show coverage for a specific file or run
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import ora from 'ora';
|
|
9
|
-
import { generatePackFromCrawl, quickCrawl } from '
|
|
9
|
+
import { generatePackFromCrawl, quickCrawl } from 'qa360-core';
|
|
10
10
|
/**
|
|
11
11
|
* Create crawl command
|
|
12
12
|
*/
|
|
@@ -255,7 +255,7 @@ export class QA360Doctor {
|
|
|
255
255
|
}
|
|
256
256
|
async checkProofKeys() {
|
|
257
257
|
try {
|
|
258
|
-
const { ensureProofKeys } = await import('
|
|
258
|
+
const { ensureProofKeys } = await import('qa360-core');
|
|
259
259
|
const result = await ensureProofKeys(this.qa360Dir);
|
|
260
260
|
// Validate result
|
|
261
261
|
if (!result) {
|
|
@@ -300,7 +300,7 @@ export class QA360Doctor {
|
|
|
300
300
|
}
|
|
301
301
|
async checkProofRoundtrip() {
|
|
302
302
|
try {
|
|
303
|
-
const coreModule = await import('
|
|
303
|
+
const coreModule = await import('qa360-core');
|
|
304
304
|
const { generateKeys, createProofBundle, verifyProofBundle } = coreModule;
|
|
305
305
|
// Guard: Verify exports are functions
|
|
306
306
|
if (typeof generateKeys !== 'function') {
|
|
@@ -17,8 +17,8 @@ import chalk from 'chalk';
|
|
|
17
17
|
import ora from 'ora';
|
|
18
18
|
import { join } from 'path';
|
|
19
19
|
import { existsSync } from 'fs';
|
|
20
|
-
import { createBest, createLLMProvider } from '
|
|
21
|
-
import { EvidenceVault } from '
|
|
20
|
+
import { createBest, createLLMProvider } from 'qa360-core';
|
|
21
|
+
import { EvidenceVault } from 'qa360-core';
|
|
22
22
|
/**
|
|
23
23
|
* Static error code database
|
|
24
24
|
*/
|
|
@@ -8,7 +8,7 @@ import chalk from 'chalk';
|
|
|
8
8
|
import Table from 'cli-table3';
|
|
9
9
|
import { join } from 'path';
|
|
10
10
|
import { existsSync } from 'fs';
|
|
11
|
-
import { EvidenceVault, FLAKINESS_CATEGORIES } from '
|
|
11
|
+
import { EvidenceVault, FLAKINESS_CATEGORIES } from 'qa360-core';
|
|
12
12
|
/**
|
|
13
13
|
* Get the vault directory path
|
|
14
14
|
*/
|
|
@@ -11,7 +11,7 @@ import chalk from 'chalk';
|
|
|
11
11
|
import ora from 'ora';
|
|
12
12
|
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
13
13
|
import { dirname, resolve } from 'path';
|
|
14
|
-
import { generateTests, generateApiTestsFromOpenAPI, generateUiTestsFromUrl, generatePerfTests, generateUnitTests, checkGenerationAvailability, } from '
|
|
14
|
+
import { generateTests, generateApiTestsFromOpenAPI, generateUiTestsFromUrl, generatePerfTests, generateUnitTests, checkGenerationAvailability, } from 'qa360-core';
|
|
15
15
|
/**
|
|
16
16
|
* Create generate commands group
|
|
17
17
|
*/
|
|
@@ -7,7 +7,7 @@ import { existsSync, createWriteStream, readFileSync, statSync, readdirSync, unl
|
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import AdmZip from 'adm-zip';
|
|
10
|
-
import { EvidenceVault } from '
|
|
10
|
+
import { EvidenceVault } from 'qa360-core';
|
|
11
11
|
export class QA360History {
|
|
12
12
|
vault;
|
|
13
13
|
constructor() { }
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Command } from 'commander';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
-
import { TUIMonitor } from '
|
|
8
|
-
import { DashboardServer, EvidenceVault } from '
|
|
9
|
-
import { Phase3Runner } from '
|
|
7
|
+
import { TUIMonitor } from 'qa360-core';
|
|
8
|
+
import { DashboardServer, EvidenceVault } from 'qa360-core';
|
|
9
|
+
import { Phase3Runner } from 'qa360-core';
|
|
10
10
|
import { existsSync } from 'fs';
|
|
11
11
|
import { join } from 'path';
|
|
12
12
|
import { loadPackConfig } from '../utils/config.js';
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { Command } from 'commander';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import ora from 'ora';
|
|
10
|
-
import { checkOllamaSetup, OllamaProvider, OllamaError } from '
|
|
10
|
+
import { checkOllamaSetup, OllamaProvider, OllamaError } from 'qa360-core';
|
|
11
11
|
import { loadQA360Config, updateQA360Config, getConfigPath } from '../utils/config.js';
|
|
12
12
|
/**
|
|
13
13
|
* Test Ollama connection
|
|
@@ -9,8 +9,8 @@ import { resolve } from 'path';
|
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
import ora from 'ora';
|
|
11
11
|
import * as yaml from 'js-yaml';
|
|
12
|
-
import { PackValidator, PackMigrator as PackMigratorV1 } from '
|
|
13
|
-
import { PackValidatorV2, PackMigrator, PackLoaderV2 } from '
|
|
12
|
+
import { PackValidator, PackMigrator as PackMigratorV1 } from 'qa360-core';
|
|
13
|
+
import { PackValidatorV2, PackMigrator, PackLoaderV2 } from 'qa360-core';
|
|
14
14
|
export class QA360Pack {
|
|
15
15
|
qa360Dir = resolve(process.cwd(), '.qa360');
|
|
16
16
|
packPath = resolve(this.qa360Dir, 'pack.yml');
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* CLI command for regression detection and analysis.
|
|
5
5
|
*/
|
|
6
6
|
import { Command } from 'commander';
|
|
7
|
-
import { createRegressionDetector, createRegressionTrendAnalyzer } from '
|
|
7
|
+
import { createRegressionDetector, createRegressionTrendAnalyzer } from 'qa360-core';
|
|
8
8
|
export const regressionCommand = new Command('regression');
|
|
9
9
|
regressionCommand.description('Regression detection and analysis');
|
|
10
10
|
// Detect regressions
|
|
@@ -7,7 +7,7 @@ import { Command } from 'commander';
|
|
|
7
7
|
import { readFile } from 'fs/promises';
|
|
8
8
|
import { existsSync } from 'fs';
|
|
9
9
|
import { resolve } from 'path';
|
|
10
|
-
import { TestRepairer, analyzeTestErrors, getQuickFixes } from '
|
|
10
|
+
import { TestRepairer, analyzeTestErrors, getQuickFixes } from 'qa360-core';
|
|
11
11
|
/**
|
|
12
12
|
* Parse test output to extract errors
|
|
13
13
|
*/
|