@qulib/core 0.7.0 → 0.9.0
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/README.md +30 -5
- package/bin/qulib.js +2 -3
- package/dist/__tests__/fixtures/api-fixture-repo/app/api/orders/route.d.ts +7 -0
- package/dist/__tests__/fixtures/api-fixture-repo/app/api/orders/route.d.ts.map +1 -0
- package/dist/__tests__/fixtures/api-fixture-repo/app/api/orders/route.js +7 -0
- package/dist/__tests__/fixtures/api-fixture-repo/app/api/users/route.d.ts +10 -0
- package/dist/__tests__/fixtures/api-fixture-repo/app/api/users/route.d.ts.map +1 -0
- package/dist/__tests__/fixtures/api-fixture-repo/app/api/users/route.js +9 -0
- package/dist/__tests__/fixtures/api-fixture-repo/pages/api/health.d.ts +9 -0
- package/dist/__tests__/fixtures/api-fixture-repo/pages/api/health.d.ts.map +1 -0
- package/dist/__tests__/fixtures/api-fixture-repo/pages/api/health.js +10 -0
- package/dist/__tests__/playwright-available.d.ts +32 -0
- package/dist/__tests__/playwright-available.d.ts.map +1 -0
- package/dist/__tests__/playwright-available.js +35 -0
- package/dist/adapters/api-adapter.d.ts +26 -0
- package/dist/adapters/api-adapter.d.ts.map +1 -1
- package/dist/adapters/api-adapter.js +156 -2
- package/dist/adapters/ci-results-adapter.d.ts +67 -0
- package/dist/adapters/ci-results-adapter.d.ts.map +1 -0
- package/dist/adapters/ci-results-adapter.js +143 -0
- package/dist/adapters/cypress-e2e-adapter.d.ts.map +1 -1
- package/dist/adapters/cypress-e2e-adapter.js +25 -2
- package/dist/adapters/playwright-adapter.d.ts.map +1 -1
- package/dist/adapters/playwright-adapter.js +94 -2
- package/dist/adapters/pr-metadata-adapter.d.ts +75 -0
- package/dist/adapters/pr-metadata-adapter.d.ts.map +1 -0
- package/dist/adapters/pr-metadata-adapter.js +146 -0
- package/dist/adapters/validate-specs.d.ts +55 -0
- package/dist/adapters/validate-specs.d.ts.map +1 -0
- package/dist/adapters/validate-specs.js +67 -0
- package/dist/baseline/baseline.d.ts +54 -0
- package/dist/baseline/baseline.d.ts.map +1 -0
- package/dist/baseline/baseline.js +252 -0
- package/dist/baseline/baseline.schema.d.ts +233 -0
- package/dist/baseline/baseline.schema.d.ts.map +1 -0
- package/dist/baseline/baseline.schema.js +59 -0
- package/dist/cli/confidence-run.d.ts +16 -0
- package/dist/cli/confidence-run.d.ts.map +1 -0
- package/dist/cli/confidence-run.js +158 -0
- package/dist/cli/index.d.ts +11 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +80 -4
- package/dist/cli/scaffold-run.d.ts +86 -0
- package/dist/cli/scaffold-run.d.ts.map +1 -0
- package/dist/cli/scaffold-run.js +232 -0
- package/dist/cli/score-automation-run.d.ts +25 -0
- package/dist/cli/score-automation-run.d.ts.map +1 -0
- package/dist/cli/score-automation-run.js +123 -0
- package/dist/examples/notquality-dogfood/fixture.d.ts +166 -0
- package/dist/examples/notquality-dogfood/fixture.d.ts.map +1 -0
- package/dist/examples/notquality-dogfood/fixture.js +174 -0
- package/dist/examples/notquality-dogfood/run.d.ts +34 -0
- package/dist/examples/notquality-dogfood/run.d.ts.map +1 -0
- package/dist/examples/notquality-dogfood/run.js +139 -0
- package/dist/index.d.ts +18 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/recipes/a11y.d.ts +36 -0
- package/dist/recipes/a11y.d.ts.map +1 -0
- package/dist/recipes/a11y.js +118 -0
- package/dist/recipes/auth.d.ts +38 -0
- package/dist/recipes/auth.d.ts.map +1 -0
- package/dist/recipes/auth.js +156 -0
- package/dist/recipes/index.d.ts +26 -0
- package/dist/recipes/index.d.ts.map +1 -0
- package/dist/recipes/index.js +41 -0
- package/dist/recipes/nav.d.ts +34 -0
- package/dist/recipes/nav.d.ts.map +1 -0
- package/dist/recipes/nav.js +128 -0
- package/dist/recipes/seed.d.ts +34 -0
- package/dist/recipes/seed.d.ts.map +1 -0
- package/dist/recipes/seed.js +87 -0
- package/dist/scaffold-tests.d.ts +21 -0
- package/dist/scaffold-tests.d.ts.map +1 -1
- package/dist/scaffold-tests.js +12 -2
- package/dist/schemas/automation-maturity.schema.d.ts +8 -8
- package/dist/schemas/automation-maturity.schema.d.ts.map +1 -1
- package/dist/schemas/automation-maturity.schema.js +1 -0
- package/dist/schemas/confidence.schema.d.ts +526 -0
- package/dist/schemas/confidence.schema.d.ts.map +1 -0
- package/dist/schemas/confidence.schema.js +161 -0
- package/dist/schemas/gap-analysis.schema.d.ts +8 -8
- package/dist/schemas/gap-analysis.schema.js +1 -1
- package/dist/schemas/index.d.ts +3 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +3 -0
- package/dist/schemas/public-surface.schema.d.ts +5 -5
- package/dist/schemas/recipe.schema.d.ts +66 -0
- package/dist/schemas/recipe.schema.d.ts.map +1 -0
- package/dist/schemas/recipe.schema.js +45 -0
- package/dist/schemas/repo-analysis.schema.d.ts +7 -7
- package/dist/schemas/views.schema.d.ts +234 -0
- package/dist/schemas/views.schema.d.ts.map +1 -0
- package/dist/schemas/views.schema.js +82 -0
- package/dist/tools/repo/api-surface.d.ts +59 -0
- package/dist/tools/repo/api-surface.d.ts.map +1 -0
- package/dist/tools/repo/api-surface.js +414 -0
- package/dist/tools/scoring/api-coverage.d.ts +74 -0
- package/dist/tools/scoring/api-coverage.d.ts.map +1 -0
- package/dist/tools/scoring/api-coverage.js +158 -0
- package/dist/tools/scoring/automation-maturity.d.ts +11 -1
- package/dist/tools/scoring/automation-maturity.d.ts.map +1 -1
- package/dist/tools/scoring/automation-maturity.js +43 -9
- package/dist/tools/scoring/confidence-from-qulib.d.ts +34 -0
- package/dist/tools/scoring/confidence-from-qulib.d.ts.map +1 -0
- package/dist/tools/scoring/confidence-from-qulib.js +206 -0
- package/dist/tools/scoring/confidence-views.d.ts +40 -0
- package/dist/tools/scoring/confidence-views.d.ts.map +1 -0
- package/dist/tools/scoring/confidence-views.js +163 -0
- package/dist/tools/scoring/confidence.d.ts +32 -0
- package/dist/tools/scoring/confidence.d.ts.map +1 -0
- package/dist/tools/scoring/confidence.js +180 -0
- package/dist/tools/scoring/levels.d.ts +15 -0
- package/dist/tools/scoring/levels.d.ts.map +1 -0
- package/dist/tools/scoring/levels.js +21 -0
- package/package.json +15 -7
package/README.md
CHANGED
|
@@ -140,16 +140,38 @@ After a normal **`analyze`**, `output/report.json` includes **`gapAnalysis.costI
|
|
|
140
140
|
Re-print that block from disk:
|
|
141
141
|
|
|
142
142
|
```bash
|
|
143
|
-
|
|
144
|
-
# or:
|
|
143
|
+
qulib cost doctor
|
|
144
|
+
# or: qulib cost doctor --report output/report.json
|
|
145
145
|
```
|
|
146
146
|
|
|
147
147
|
## CLI (from npm)
|
|
148
148
|
|
|
149
|
+
**Release confidence — the flagship command:**
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npx @qulib/core confidence --url https://example.com
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Returns ship / caution / hold / block with a 0–100 score, top risks, and recommended next checks. Add `--repo` to also score test-automation maturity and API coverage.
|
|
156
|
+
|
|
157
|
+
**Analyze (full gap report):**
|
|
158
|
+
|
|
149
159
|
```bash
|
|
150
160
|
npx @qulib/core analyze --url https://example.com
|
|
151
161
|
```
|
|
152
162
|
|
|
163
|
+
**Scaffold a test suite:**
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
npx @qulib/core scaffold --url https://example.com --framework cypress-e2e
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Score automation maturity (repo-only, no URL needed):**
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
npx @qulib/core score-automation --repo /path/to/repo
|
|
173
|
+
```
|
|
174
|
+
|
|
153
175
|
Use `npx playwright install chromium` the first time you scan (Playwright is a dependency).
|
|
154
176
|
|
|
155
177
|
## Programmatic API
|
|
@@ -344,10 +366,13 @@ Use these as conservative reference numbers:
|
|
|
344
366
|
|
|
345
367
|
| Tool | When to use | Key input |
|
|
346
368
|
|---|---|---|
|
|
347
|
-
|
|
|
348
|
-
| `
|
|
369
|
+
| **`qulib_score_confidence`** | **Flagship.** Fused verdict (ship/caution/hold/block) from all collectors | `url` and/or `repoPath`, optional `includeViews.replay` |
|
|
370
|
+
| `analyze_app` | Live-app QA scan: release confidence + gaps + a11y | `url`, optional `auth`, optional LLM knobs |
|
|
371
|
+
| `qulib_score_automation` | Score local repo test-automation maturity | absolute `repoPath`, optional `includeFullDimensions` |
|
|
372
|
+
| `qulib_score_api` | Discover API endpoints and score their test coverage | absolute `repoPath`, optional `enableTier3`, `includeEndpointDetail` |
|
|
373
|
+
| `qulib_scaffold_tests` | Generate Cypress/Playwright scaffold from a live URL | `url`, optional `framework`, `maxPagesToScan`, `recipes` |
|
|
349
374
|
| `explore_auth` | Deeper auth-path discovery on unfamiliar apps | `url`, optional `timeoutMs` |
|
|
350
|
-
| `
|
|
375
|
+
| `detect_auth` | Fast single-pass auth pattern guess | `url`, optional `timeoutMs` |
|
|
351
376
|
|
|
352
377
|
## Output directories
|
|
353
378
|
|
package/bin/qulib.js
CHANGED
|
@@ -5,10 +5,9 @@ import { dirname, resolve } from 'node:path';
|
|
|
5
5
|
|
|
6
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
7
|
const __dirname = dirname(__filename);
|
|
8
|
-
const cliPath = resolve(__dirname, '../
|
|
8
|
+
const cliPath = resolve(__dirname, '../dist/cli/index.js');
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
const child = spawn(npxCmd, ['tsx', cliPath, ...process.argv.slice(2)], {
|
|
10
|
+
const child = spawn(process.execPath, [cliPath, ...process.argv.slice(2)], {
|
|
12
11
|
stdio: 'inherit',
|
|
13
12
|
});
|
|
14
13
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/__tests__/fixtures/api-fixture-repo/app/api/orders/route.ts"],"names":[],"mappings":"AAGA,wBAAsB,MAAM,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE;;;GAIpD"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Fixture: Next.js App Router API route for orders (DELETE only — high severity untested)
|
|
2
|
+
// This file is a static analysis fixture only — not compiled.
|
|
3
|
+
export async function DELETE(request) {
|
|
4
|
+
const url = new URL(request.url);
|
|
5
|
+
const id = url.searchParams.get('id');
|
|
6
|
+
return { deleted: true, id };
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/__tests__/fixtures/api-fixture-repo/app/api/users/route.ts"],"names":[],"mappings":"AAGA,wBAAsB,GAAG;;GAExB;AAED,wBAAsB,IAAI,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE;;;GAGnE"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Fixture: Next.js App Router API route for users
|
|
2
|
+
// This file is a static analysis fixture only — not compiled.
|
|
3
|
+
export async function GET() {
|
|
4
|
+
return { users: [] };
|
|
5
|
+
}
|
|
6
|
+
export async function POST(request) {
|
|
7
|
+
const body = await request.json();
|
|
8
|
+
return { created: true, user: body };
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../../../../../src/__tests__/fixtures/api-fixture-repo/pages/api/health.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,OAAO,UAAU,OAAO,CAC7B,GAAG,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EACxB,GAAG,EAAE;IAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;QAAC,GAAG,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;CAAE,QAOtF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Fixture: Next.js Pages API route
|
|
2
|
+
// This file is a static analysis fixture only — not compiled.
|
|
3
|
+
export default function handler(req, res) {
|
|
4
|
+
if (req.method === 'GET') {
|
|
5
|
+
res.status(200).json({ status: 'ok' });
|
|
6
|
+
}
|
|
7
|
+
else {
|
|
8
|
+
res.status(405).end();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight helper used by test files that require a Playwright Chromium
|
|
3
|
+
* installation. Checks for the browser at the filesystem level — no side
|
|
4
|
+
* effects, no subprocess launch.
|
|
5
|
+
*
|
|
6
|
+
* Two ways to skip:
|
|
7
|
+
* 1. Set PLAYWRIGHT_SKIP=1 in the environment — explicit opt-out, useful for
|
|
8
|
+
* fresh-clone CI jobs that intentionally skip browser tests.
|
|
9
|
+
* 2. The Playwright Chromium executable is simply absent from the expected
|
|
10
|
+
* install path (e.g. the developer never ran `npx playwright install`).
|
|
11
|
+
*
|
|
12
|
+
* Usage in test files:
|
|
13
|
+
*
|
|
14
|
+
* import { chromiumAvailable, CHROMIUM_SKIP_REASON } from './playwright-available.js';
|
|
15
|
+
*
|
|
16
|
+
* test('my browser test', { skip: !chromiumAvailable, skipMessage: CHROMIUM_SKIP_REASON }, () => { ... });
|
|
17
|
+
*
|
|
18
|
+
* Or for a subtree of tests (t.before guard):
|
|
19
|
+
*
|
|
20
|
+
* if (!chromiumAvailable) { t.skip(); return; }
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* True when a Playwright Chromium browser binary is present on disk AND the
|
|
24
|
+
* caller has not set PLAYWRIGHT_SKIP=1.
|
|
25
|
+
*/
|
|
26
|
+
export declare const chromiumAvailable: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Human-readable reason string passed to `t.skip()` / `skipMessage` so the
|
|
29
|
+
* skip is self-documenting in test output.
|
|
30
|
+
*/
|
|
31
|
+
export declare const CHROMIUM_SKIP_REASON: string;
|
|
32
|
+
//# sourceMappingURL=playwright-available.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwright-available.d.ts","sourceRoot":"","sources":["../../src/__tests__/playwright-available.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,OAC0C,CAAC;AAE3E;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAEqE,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight helper used by test files that require a Playwright Chromium
|
|
3
|
+
* installation. Checks for the browser at the filesystem level — no side
|
|
4
|
+
* effects, no subprocess launch.
|
|
5
|
+
*
|
|
6
|
+
* Two ways to skip:
|
|
7
|
+
* 1. Set PLAYWRIGHT_SKIP=1 in the environment — explicit opt-out, useful for
|
|
8
|
+
* fresh-clone CI jobs that intentionally skip browser tests.
|
|
9
|
+
* 2. The Playwright Chromium executable is simply absent from the expected
|
|
10
|
+
* install path (e.g. the developer never ran `npx playwright install`).
|
|
11
|
+
*
|
|
12
|
+
* Usage in test files:
|
|
13
|
+
*
|
|
14
|
+
* import { chromiumAvailable, CHROMIUM_SKIP_REASON } from './playwright-available.js';
|
|
15
|
+
*
|
|
16
|
+
* test('my browser test', { skip: !chromiumAvailable, skipMessage: CHROMIUM_SKIP_REASON }, () => { ... });
|
|
17
|
+
*
|
|
18
|
+
* Or for a subtree of tests (t.before guard):
|
|
19
|
+
*
|
|
20
|
+
* if (!chromiumAvailable) { t.skip(); return; }
|
|
21
|
+
*/
|
|
22
|
+
import { chromium } from '@playwright/test';
|
|
23
|
+
import { existsSync } from 'node:fs';
|
|
24
|
+
/**
|
|
25
|
+
* True when a Playwright Chromium browser binary is present on disk AND the
|
|
26
|
+
* caller has not set PLAYWRIGHT_SKIP=1.
|
|
27
|
+
*/
|
|
28
|
+
export const chromiumAvailable = !process.env['PLAYWRIGHT_SKIP'] && existsSync(chromium.executablePath());
|
|
29
|
+
/**
|
|
30
|
+
* Human-readable reason string passed to `t.skip()` / `skipMessage` so the
|
|
31
|
+
* skip is self-documenting in test output.
|
|
32
|
+
*/
|
|
33
|
+
export const CHROMIUM_SKIP_REASON = process.env['PLAYWRIGHT_SKIP']
|
|
34
|
+
? 'PLAYWRIGHT_SKIP=1 is set — browser tests opted out. Unset PLAYWRIGHT_SKIP to enable.'
|
|
35
|
+
: 'Playwright Chromium is not installed. Run `npx playwright install chromium` to enable these tests.';
|
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
import type { TestAdapter } from './adapter.interface.js';
|
|
2
2
|
import type { NeutralScenario, GeneratedTest } from '../schemas/gap-analysis.schema.js';
|
|
3
|
+
import type { ApiSurface } from '../tools/repo/api-surface.js';
|
|
4
|
+
/**
|
|
5
|
+
* TestAdapter implementation for API testing via supertest.
|
|
6
|
+
*
|
|
7
|
+
* `render` / `renderAll`: convert gap-analysis NeutralScenarios that carry
|
|
8
|
+
* `api-call` steps into supertest specs. Used by the standard adapter pipeline.
|
|
9
|
+
*
|
|
10
|
+
* `scaffoldApiTests`: separate entry point for the repo-first API toolshed flow.
|
|
11
|
+
* Accepts discovered endpoints (ApiSurface) and generates a ready-to-run
|
|
12
|
+
* supertest test file — NOT URL-based.
|
|
13
|
+
*/
|
|
3
14
|
export declare class ApiAdapter implements TestAdapter {
|
|
4
15
|
readonly adapterType = "api";
|
|
5
16
|
render(scenario: NeutralScenario): GeneratedTest;
|
|
6
17
|
renderAll(scenarios: NeutralScenario[]): GeneratedTest[];
|
|
18
|
+
/**
|
|
19
|
+
* Generate a supertest-based test file from discovered API endpoints.
|
|
20
|
+
* This is the repo-first entry point — does NOT require a running URL.
|
|
21
|
+
*
|
|
22
|
+
* Endpoints are grouped into a single test file. Each endpoint gets one
|
|
23
|
+
* `it` block that:
|
|
24
|
+
* - Makes the correct HTTP method call
|
|
25
|
+
* - Asserts status < 500 (smoke-level assertion, safely runnable against a live app)
|
|
26
|
+
* - POST/PUT/PATCH endpoints include a TODO for request body
|
|
27
|
+
*
|
|
28
|
+
* The file is NOT associated with a NeutralScenario; it uses a fixed scenarioId.
|
|
29
|
+
*/
|
|
30
|
+
scaffoldApiTests(apiSurface: ApiSurface, options?: {
|
|
31
|
+
appImportPath?: string;
|
|
32
|
+
}): GeneratedTest;
|
|
7
33
|
}
|
|
8
34
|
//# sourceMappingURL=api-adapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/api-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"api-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/api-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACxF,OAAO,KAAK,EAAsB,UAAU,EAAE,MAAM,8BAA8B,CAAC;AASnF;;;;;;;;;GASG;AACH,qBAAa,UAAW,YAAW,WAAW;IAC5C,QAAQ,CAAC,WAAW,SAAS;IAE7B,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,aAAa;IAuDhD,SAAS,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,aAAa,EAAE;IAIxD;;;;;;;;;;;OAWG;IACH,gBAAgB,CACd,UAAU,EAAE,UAAU,EACtB,OAAO,GAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAO,GACvC,aAAa;CAoDjB"}
|
|
@@ -1,9 +1,163 @@
|
|
|
1
|
+
function slugify(title) {
|
|
2
|
+
return title
|
|
3
|
+
.toLowerCase()
|
|
4
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
5
|
+
.replace(/^-|-$/g, '');
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* TestAdapter implementation for API testing via supertest.
|
|
9
|
+
*
|
|
10
|
+
* `render` / `renderAll`: convert gap-analysis NeutralScenarios that carry
|
|
11
|
+
* `api-call` steps into supertest specs. Used by the standard adapter pipeline.
|
|
12
|
+
*
|
|
13
|
+
* `scaffoldApiTests`: separate entry point for the repo-first API toolshed flow.
|
|
14
|
+
* Accepts discovered endpoints (ApiSurface) and generates a ready-to-run
|
|
15
|
+
* supertest test file — NOT URL-based.
|
|
16
|
+
*/
|
|
1
17
|
export class ApiAdapter {
|
|
2
18
|
adapterType = 'api';
|
|
3
19
|
render(scenario) {
|
|
4
|
-
|
|
20
|
+
const slug = slugify(scenario.title);
|
|
21
|
+
const filename = `${slug}.api.test.ts`;
|
|
22
|
+
const stepLines = scenario.steps
|
|
23
|
+
.map((step) => {
|
|
24
|
+
if (step.action === 'api-call') {
|
|
25
|
+
const path = step.target ?? step.value ?? '/';
|
|
26
|
+
return [
|
|
27
|
+
` // ${step.description}`,
|
|
28
|
+
` const res = await request(app).get(${JSON.stringify(path)});`,
|
|
29
|
+
` expect(res.status).toBe(200);`,
|
|
30
|
+
].join('\n');
|
|
31
|
+
}
|
|
32
|
+
if (step.action === 'navigate') {
|
|
33
|
+
const path = step.target ?? step.value ?? '/';
|
|
34
|
+
return [
|
|
35
|
+
` // ${step.description}`,
|
|
36
|
+
` const res = await request(app).get(${JSON.stringify(path)});`,
|
|
37
|
+
` expect(res.status).toBeLessThan(500);`,
|
|
38
|
+
].join('\n');
|
|
39
|
+
}
|
|
40
|
+
return ` // TODO (${step.action}): ${step.description}`;
|
|
41
|
+
})
|
|
42
|
+
.join('\n');
|
|
43
|
+
const code = [
|
|
44
|
+
`// ${scenario.description}`,
|
|
45
|
+
`// qulib-generated — scenario: ${scenario.id}`,
|
|
46
|
+
``,
|
|
47
|
+
`import request from 'supertest';`,
|
|
48
|
+
`import { describe, it, expect } from 'vitest';`,
|
|
49
|
+
``,
|
|
50
|
+
`// TODO: import or create your Express/Fastify/Hono app here`,
|
|
51
|
+
`// import { app } from '../src/app.js';`,
|
|
52
|
+
`declare const app: unknown;`,
|
|
53
|
+
``,
|
|
54
|
+
`describe(${JSON.stringify(scenario.title)}, () => {`,
|
|
55
|
+
` it(${JSON.stringify(scenario.description)}, async () => {`,
|
|
56
|
+
stepLines || ` // no api-call steps — add assertions for: ${scenario.targetPath}`,
|
|
57
|
+
` });`,
|
|
58
|
+
`});`,
|
|
59
|
+
``,
|
|
60
|
+
].join('\n');
|
|
61
|
+
return {
|
|
62
|
+
scenarioId: scenario.id,
|
|
63
|
+
adapter: 'api',
|
|
64
|
+
filename,
|
|
65
|
+
code,
|
|
66
|
+
source: 'template',
|
|
67
|
+
outputPath: `tests/api/${filename}`,
|
|
68
|
+
};
|
|
5
69
|
}
|
|
6
70
|
renderAll(scenarios) {
|
|
7
|
-
|
|
71
|
+
return scenarios.map((s) => this.render(s));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Generate a supertest-based test file from discovered API endpoints.
|
|
75
|
+
* This is the repo-first entry point — does NOT require a running URL.
|
|
76
|
+
*
|
|
77
|
+
* Endpoints are grouped into a single test file. Each endpoint gets one
|
|
78
|
+
* `it` block that:
|
|
79
|
+
* - Makes the correct HTTP method call
|
|
80
|
+
* - Asserts status < 500 (smoke-level assertion, safely runnable against a live app)
|
|
81
|
+
* - POST/PUT/PATCH endpoints include a TODO for request body
|
|
82
|
+
*
|
|
83
|
+
* The file is NOT associated with a NeutralScenario; it uses a fixed scenarioId.
|
|
84
|
+
*/
|
|
85
|
+
scaffoldApiTests(apiSurface, options = {}) {
|
|
86
|
+
const appImport = options.appImportPath ?? '../src/app.js';
|
|
87
|
+
const endpoints = apiSurface.endpoints;
|
|
88
|
+
if (endpoints.length === 0) {
|
|
89
|
+
const code = [
|
|
90
|
+
`// qulib-generated API scaffold — no endpoints discovered`,
|
|
91
|
+
`// qulib-generated — repo: ${apiSurface.repoPath}`,
|
|
92
|
+
``,
|
|
93
|
+
`// No API endpoints were discovered in this repository.`,
|
|
94
|
+
`// If your app has REST endpoints, ensure they are declared in a supported`,
|
|
95
|
+
`// framework (Next.js route.ts, Express, Fastify, NestJS) or an OpenAPI spec.`,
|
|
96
|
+
``,
|
|
97
|
+
].join('\n');
|
|
98
|
+
return {
|
|
99
|
+
scenarioId: 'qulib-api-scaffold',
|
|
100
|
+
adapter: 'api',
|
|
101
|
+
filename: 'api-scaffold.test.ts',
|
|
102
|
+
code,
|
|
103
|
+
source: 'template',
|
|
104
|
+
outputPath: 'tests/api/api-scaffold.test.ts',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const itBlocks = endpoints.map((ep) => renderEndpointTest(ep)).join('\n\n');
|
|
108
|
+
const code = [
|
|
109
|
+
`// qulib-generated API scaffold — ${endpoints.length} endpoint(s) discovered`,
|
|
110
|
+
`// qulib-generated — repo: ${apiSurface.repoPath}`,
|
|
111
|
+
`// Discovery tier breakdown: ${describeDiscoveryTiers(endpoints)}`,
|
|
112
|
+
``,
|
|
113
|
+
`import request from 'supertest';`,
|
|
114
|
+
`import { describe, it, expect, beforeAll, afterAll } from 'vitest';`,
|
|
115
|
+
``,
|
|
116
|
+
`// TODO: replace with your actual app export`,
|
|
117
|
+
`import { app } from ${JSON.stringify(appImport)};`,
|
|
118
|
+
``,
|
|
119
|
+
`describe('API surface smoke tests (qulib-generated)', () => {`,
|
|
120
|
+
itBlocks,
|
|
121
|
+
`});`,
|
|
122
|
+
``,
|
|
123
|
+
].join('\n');
|
|
124
|
+
return {
|
|
125
|
+
scenarioId: 'qulib-api-scaffold',
|
|
126
|
+
adapter: 'api',
|
|
127
|
+
filename: 'api-scaffold.test.ts',
|
|
128
|
+
code,
|
|
129
|
+
source: 'template',
|
|
130
|
+
outputPath: 'tests/api/api-scaffold.test.ts',
|
|
131
|
+
};
|
|
8
132
|
}
|
|
9
133
|
}
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Internal helpers
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
function renderEndpointTest(ep) {
|
|
138
|
+
const method = ep.method === 'unknown' ? 'GET' : ep.method;
|
|
139
|
+
const methodLower = method.toLowerCase();
|
|
140
|
+
const hasBody = method === 'POST' || method === 'PUT' || method === 'PATCH';
|
|
141
|
+
const sourceLine = ` // Source: ${ep.sourceFile} (${ep.sourceTier}, confidence: ${ep.confidence})`;
|
|
142
|
+
const itTitle = `${method} ${ep.path}`;
|
|
143
|
+
const requestLine = hasBody
|
|
144
|
+
? ` const res = await request(app).${methodLower}(${JSON.stringify(ep.path)});\n // TODO: add request body — e.g. .send({ ... })`
|
|
145
|
+
: ` const res = await request(app).${methodLower}(${JSON.stringify(ep.path)});`;
|
|
146
|
+
const summaryLine = ep.summary ? ` // ${ep.summary}\n` : '';
|
|
147
|
+
return [
|
|
148
|
+
summaryLine + sourceLine,
|
|
149
|
+
` it(${JSON.stringify(itTitle)}, async () => {`,
|
|
150
|
+
requestLine,
|
|
151
|
+
` expect(res.status).toBeLessThan(500);`,
|
|
152
|
+
` });`,
|
|
153
|
+
].join('\n');
|
|
154
|
+
}
|
|
155
|
+
function describeDiscoveryTiers(endpoints) {
|
|
156
|
+
const counts = { openapi: 0, framework: 0, heuristic: 0 };
|
|
157
|
+
for (const ep of endpoints)
|
|
158
|
+
counts[ep.sourceTier]++;
|
|
159
|
+
return Object.entries(counts)
|
|
160
|
+
.filter(([, v]) => v > 0)
|
|
161
|
+
.map(([k, v]) => `${v} ${k}`)
|
|
162
|
+
.join(', ');
|
|
163
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI-results evidence adapter (P4 — evidence collectors).
|
|
3
|
+
*
|
|
4
|
+
* Maps a CI run summary (test pass/fail counts, build status, optional flakiness
|
|
5
|
+
* data) into an `EvidenceItem` for `computeReleaseConfidence`, using the
|
|
6
|
+
* `ci-results` source kind reserved in confidence.schema.ts.
|
|
7
|
+
*
|
|
8
|
+
* Design:
|
|
9
|
+
* - Pure function — no I/O, no side effects. The caller owns fetching the CI data
|
|
10
|
+
* (e.g. from the `gh` API or a CI provider webhook); this adapter scores + maps.
|
|
11
|
+
* - Applicability:
|
|
12
|
+
* `applicable` — a real run with ≥1 test executed
|
|
13
|
+
* `not_applicable` — no CI run on record for this ref (e.g. no CI configured)
|
|
14
|
+
* `unknown` — run exists but results are incomplete/in-progress
|
|
15
|
+
* - Score formula: pass-rate * build-weight * freshness-factor
|
|
16
|
+
* pass-rate = passed / (passed + failed + errored) [0..1]
|
|
17
|
+
* build-weight = 0.85 if build succeeded, 0.0 if build failed (a build failure
|
|
18
|
+
* overrides the test pass-rate — tests that never ran are not "passing")
|
|
19
|
+
* freshness = 1.0 when ageSeconds < FRESH_THRESHOLD, decaying linearly to
|
|
20
|
+
* MIN_FRESHNESS over STALE_THRESHOLD. Beyond STALE_THRESHOLD the
|
|
21
|
+
* applicability is coerced to `unknown` (matches §2.6 rule 3 of the spec).
|
|
22
|
+
* - A build failure OR 0 tests passing marks evidence ['critical'] and forces a
|
|
23
|
+
* blocking recommendation (the aggregator uses recommendations for narrative).
|
|
24
|
+
* - Multi-tenant: tenantId flows through unchanged (caller sets it on the EvidenceItem
|
|
25
|
+
* via the `collector.tool` string; the full tenant stamp is the subject, not the item).
|
|
26
|
+
*/
|
|
27
|
+
import type { EvidenceItem } from '../schemas/confidence.schema.js';
|
|
28
|
+
/**
|
|
29
|
+
* Raw CI run data the caller provides (from `gh run view --json`, provider API, etc.).
|
|
30
|
+
* Only the counts matter for scoring; everything else is for evidence strings + provenance.
|
|
31
|
+
*/
|
|
32
|
+
export interface CiRunInput {
|
|
33
|
+
/** ISO-8601 timestamp when the run completed. */
|
|
34
|
+
completedAt: string;
|
|
35
|
+
/** Whether the CI build step itself succeeded (compilation, lint, etc. — before tests). */
|
|
36
|
+
buildPassed: boolean;
|
|
37
|
+
/** Number of test cases that passed. */
|
|
38
|
+
testsPassed: number;
|
|
39
|
+
/** Number of test cases that failed (hard). */
|
|
40
|
+
testsFailed: number;
|
|
41
|
+
/** Number of test cases that errored (infra/setup failure, not assertion failure). */
|
|
42
|
+
testsErrored: number;
|
|
43
|
+
/**
|
|
44
|
+
* Optional: number of tests that were flaky (passed on retry). Presence lowers score
|
|
45
|
+
* slightly but doesn't fail the run — flaky tests are a warn, not a block.
|
|
46
|
+
*/
|
|
47
|
+
testsFlaky?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Optional: CI provider URL for the run (e.g. `https://github.com/…/actions/runs/…`).
|
|
50
|
+
* Never fabricated — omit rather than invent.
|
|
51
|
+
*/
|
|
52
|
+
runUrl?: string;
|
|
53
|
+
/** Optional: CI workflow/pipeline name for the evidence string. */
|
|
54
|
+
workflowName?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Optional: collector freshness budget override (seconds). Defaults to 24 h for stale.
|
|
57
|
+
*/
|
|
58
|
+
staleAfterSeconds?: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Produce a `ci-results` EvidenceItem from a raw CI run summary.
|
|
62
|
+
*
|
|
63
|
+
* Deterministic and pure. Returns `not_applicable` when the run is absent, `unknown`
|
|
64
|
+
* when stale or incomplete, and `applicable` with a real score otherwise.
|
|
65
|
+
*/
|
|
66
|
+
export declare function ciResultsToEvidence(run: CiRunInput, collectedAt?: string): EvidenceItem;
|
|
67
|
+
//# sourceMappingURL=ci-results-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci-results-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/ci-results-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAsB,MAAM,iCAAiC,CAAC;AASxF;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,2FAA2F;IAC3F,WAAW,EAAE,OAAO,CAAC;IACrB,wCAAwC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,sFAAsF;IACtF,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,YAAY,CA+GvF"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI-results evidence adapter (P4 — evidence collectors).
|
|
3
|
+
*
|
|
4
|
+
* Maps a CI run summary (test pass/fail counts, build status, optional flakiness
|
|
5
|
+
* data) into an `EvidenceItem` for `computeReleaseConfidence`, using the
|
|
6
|
+
* `ci-results` source kind reserved in confidence.schema.ts.
|
|
7
|
+
*
|
|
8
|
+
* Design:
|
|
9
|
+
* - Pure function — no I/O, no side effects. The caller owns fetching the CI data
|
|
10
|
+
* (e.g. from the `gh` API or a CI provider webhook); this adapter scores + maps.
|
|
11
|
+
* - Applicability:
|
|
12
|
+
* `applicable` — a real run with ≥1 test executed
|
|
13
|
+
* `not_applicable` — no CI run on record for this ref (e.g. no CI configured)
|
|
14
|
+
* `unknown` — run exists but results are incomplete/in-progress
|
|
15
|
+
* - Score formula: pass-rate * build-weight * freshness-factor
|
|
16
|
+
* pass-rate = passed / (passed + failed + errored) [0..1]
|
|
17
|
+
* build-weight = 0.85 if build succeeded, 0.0 if build failed (a build failure
|
|
18
|
+
* overrides the test pass-rate — tests that never ran are not "passing")
|
|
19
|
+
* freshness = 1.0 when ageSeconds < FRESH_THRESHOLD, decaying linearly to
|
|
20
|
+
* MIN_FRESHNESS over STALE_THRESHOLD. Beyond STALE_THRESHOLD the
|
|
21
|
+
* applicability is coerced to `unknown` (matches §2.6 rule 3 of the spec).
|
|
22
|
+
* - A build failure OR 0 tests passing marks evidence ['critical'] and forces a
|
|
23
|
+
* blocking recommendation (the aggregator uses recommendations for narrative).
|
|
24
|
+
* - Multi-tenant: tenantId flows through unchanged (caller sets it on the EvidenceItem
|
|
25
|
+
* via the `collector.tool` string; the full tenant stamp is the subject, not the item).
|
|
26
|
+
*/
|
|
27
|
+
// Freshness constants (seconds). Configurable at call-site via CiRunInput.
|
|
28
|
+
const DEFAULT_FRESH_THRESHOLD_S = 60 * 60 * 4; // 4 h
|
|
29
|
+
const DEFAULT_STALE_THRESHOLD_S = 60 * 60 * 24; // 24 h
|
|
30
|
+
const MIN_FRESHNESS = 0.5; // floor before we coerce to unknown
|
|
31
|
+
const CI_WEIGHT = 0.10; // matches DEFAULT_WEIGHTS['ci-results'] in confidence.ts
|
|
32
|
+
/**
|
|
33
|
+
* Produce a `ci-results` EvidenceItem from a raw CI run summary.
|
|
34
|
+
*
|
|
35
|
+
* Deterministic and pure. Returns `not_applicable` when the run is absent, `unknown`
|
|
36
|
+
* when stale or incomplete, and `applicable` with a real score otherwise.
|
|
37
|
+
*/
|
|
38
|
+
export function ciResultsToEvidence(run, collectedAt) {
|
|
39
|
+
const now = collectedAt ?? new Date().toISOString();
|
|
40
|
+
const source = 'ci-results';
|
|
41
|
+
const weight = CI_WEIGHT;
|
|
42
|
+
// Freshness computation.
|
|
43
|
+
const ageMs = Date.parse(now) - Date.parse(run.completedAt);
|
|
44
|
+
const ageS = Math.max(0, ageMs / 1000);
|
|
45
|
+
const staleThreshold = run.staleAfterSeconds ?? DEFAULT_STALE_THRESHOLD_S;
|
|
46
|
+
if (ageS > staleThreshold) {
|
|
47
|
+
return {
|
|
48
|
+
source,
|
|
49
|
+
score: 0,
|
|
50
|
+
weight,
|
|
51
|
+
applicability: 'unknown',
|
|
52
|
+
blocking: false,
|
|
53
|
+
evidence: [
|
|
54
|
+
`CI run completed at ${run.completedAt} — stale (${Math.round(ageS / 3600)}h > ${staleThreshold / 3600}h threshold).`,
|
|
55
|
+
],
|
|
56
|
+
recommendations: ['Re-run CI against the current commit before shipping.'],
|
|
57
|
+
reason: `CI run is stale (${Math.round(ageS / 3600)}h old, threshold ${staleThreshold / 3600}h).`,
|
|
58
|
+
collectedAt: now,
|
|
59
|
+
collector: { tool: 'qulib.ci-results-adapter' },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const total = run.testsPassed + run.testsFailed + run.testsErrored;
|
|
63
|
+
// Build failure: tests may not even have run — score 0, blocking recommendation.
|
|
64
|
+
if (!run.buildPassed) {
|
|
65
|
+
const evidence = [`Build FAILED (${run.workflowName ?? 'CI workflow'}).`];
|
|
66
|
+
if (total > 0)
|
|
67
|
+
evidence.push(`${run.testsPassed}/${total} tests passed before build failure.`);
|
|
68
|
+
if (run.runUrl)
|
|
69
|
+
evidence.push(`Run: ${run.runUrl}`);
|
|
70
|
+
return {
|
|
71
|
+
source,
|
|
72
|
+
score: 0,
|
|
73
|
+
weight,
|
|
74
|
+
applicability: 'applicable',
|
|
75
|
+
blocking: false, // the aggregator decides blocking; we report honestly
|
|
76
|
+
evidence,
|
|
77
|
+
recommendations: ['Fix the build failure before shipping.'],
|
|
78
|
+
collectedAt: now,
|
|
79
|
+
collector: { tool: 'qulib.ci-results-adapter', inputRef: run.runUrl },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// No tests ran at all — cannot score honestly.
|
|
83
|
+
if (total === 0) {
|
|
84
|
+
return {
|
|
85
|
+
source,
|
|
86
|
+
score: 0,
|
|
87
|
+
weight,
|
|
88
|
+
applicability: 'unknown',
|
|
89
|
+
blocking: false,
|
|
90
|
+
evidence: [
|
|
91
|
+
`Build passed (${run.workflowName ?? 'CI workflow'}) but 0 tests executed.`,
|
|
92
|
+
...(run.runUrl ? [`Run: ${run.runUrl}`] : []),
|
|
93
|
+
],
|
|
94
|
+
recommendations: ['Add a test suite to CI for a meaningful confidence signal.'],
|
|
95
|
+
reason: 'Build passed but zero tests were executed — no test signal.',
|
|
96
|
+
collectedAt: now,
|
|
97
|
+
collector: { tool: 'qulib.ci-results-adapter', inputRef: run.runUrl },
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// Normal case: compute pass-rate.
|
|
101
|
+
const passRate = run.testsPassed / total; // 0..1
|
|
102
|
+
const freshnessRatio = ageS <= DEFAULT_FRESH_THRESHOLD_S
|
|
103
|
+
? 1.0
|
|
104
|
+
: MIN_FRESHNESS +
|
|
105
|
+
(1 - MIN_FRESHNESS) *
|
|
106
|
+
(1 - (ageS - DEFAULT_FRESH_THRESHOLD_S) / (staleThreshold - DEFAULT_FRESH_THRESHOLD_S));
|
|
107
|
+
const rawScore = passRate * freshnessRatio;
|
|
108
|
+
const score = Math.round(Math.max(0, Math.min(100, rawScore * 100)));
|
|
109
|
+
const evidence = [];
|
|
110
|
+
evidence.push(`${run.testsPassed}/${total} tests passed` +
|
|
111
|
+
(run.testsFailed > 0 ? ` (${run.testsFailed} failed)` : '') +
|
|
112
|
+
(run.testsErrored > 0 ? ` (${run.testsErrored} errored)` : '') +
|
|
113
|
+
` — ${Math.round(passRate * 100)}% pass-rate.`);
|
|
114
|
+
if (run.workflowName)
|
|
115
|
+
evidence.push(`Workflow: ${run.workflowName}.`);
|
|
116
|
+
if (run.testsFlaky && run.testsFlaky > 0) {
|
|
117
|
+
evidence.push(`${run.testsFlaky} test(s) flaky (passed on retry).`);
|
|
118
|
+
}
|
|
119
|
+
if (run.runUrl)
|
|
120
|
+
evidence.push(`Run: ${run.runUrl}`);
|
|
121
|
+
if (freshnessRatio < 1.0) {
|
|
122
|
+
evidence.push(`Freshness factor ${freshnessRatio.toFixed(2)} applied (run age ${Math.round(ageS / 3600)}h).`);
|
|
123
|
+
}
|
|
124
|
+
const recommendations = [];
|
|
125
|
+
if (run.testsFailed > 0)
|
|
126
|
+
recommendations.push(`Fix ${run.testsFailed} failing test(s) before shipping.`);
|
|
127
|
+
if (run.testsErrored > 0)
|
|
128
|
+
recommendations.push(`Investigate ${run.testsErrored} errored test(s) (infra/setup).`);
|
|
129
|
+
if (run.testsFlaky && run.testsFlaky > 0) {
|
|
130
|
+
recommendations.push(`Stabilize ${run.testsFlaky} flaky test(s) to improve signal quality.`);
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
source,
|
|
134
|
+
score,
|
|
135
|
+
weight,
|
|
136
|
+
applicability: 'applicable',
|
|
137
|
+
blocking: false,
|
|
138
|
+
evidence,
|
|
139
|
+
recommendations,
|
|
140
|
+
collectedAt: now,
|
|
141
|
+
collector: { tool: 'qulib.ci-results-adapter', inputRef: run.runUrl },
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cypress-e2e-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/cypress-e2e-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAY,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"cypress-e2e-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/cypress-e2e-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAY,MAAM,mCAAmC,CAAC;AA+DlG,qBAAa,iBAAkB,YAAW,WAAW;IACnD,QAAQ,CAAC,WAAW,iBAAiB;IAErC,MAAM,CAAC,QAAQ,EAAE,eAAe,GAAG,aAAa;IAiChD,SAAS,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,aAAa,EAAE;CAGzD"}
|