@qa-ai-stlc/core 0.0.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 +13 -0
- package/dist/auth-session-store.d.ts +15 -0
- package/dist/auth-session-store.d.ts.map +1 -0
- package/dist/auth-session-store.js +34 -0
- package/dist/auth-session-store.js.map +1 -0
- package/dist/auth.d.ts +28 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +77 -0
- package/dist/auth.js.map +1 -0
- package/dist/browser-doctor.d.ts +29 -0
- package/dist/browser-doctor.d.ts.map +1 -0
- package/dist/browser-doctor.js +130 -0
- package/dist/browser-doctor.js.map +1 -0
- package/dist/config-loader.d.ts +9 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/config-loader.js +40 -0
- package/dist/config-loader.js.map +1 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +18 -0
- package/dist/errors.js.map +1 -0
- package/dist/evidence-store.d.ts +43 -0
- package/dist/evidence-store.d.ts.map +1 -0
- package/dist/evidence-store.js +81 -0
- package/dist/evidence-store.js.map +1 -0
- package/dist/hash.d.ts +6 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +19 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/json-file.d.ts +6 -0
- package/dist/json-file.d.ts.map +1 -0
- package/dist/json-file.js +29 -0
- package/dist/json-file.js.map +1 -0
- package/dist/manifest-store.d.ts +27 -0
- package/dist/manifest-store.d.ts.map +1 -0
- package/dist/manifest-store.js +65 -0
- package/dist/manifest-store.js.map +1 -0
- package/dist/paths.d.ts +15 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +33 -0
- package/dist/paths.js.map +1 -0
- package/dist/ports/browser-launcher.d.ts +31 -0
- package/dist/ports/browser-launcher.d.ts.map +1 -0
- package/dist/ports/browser-launcher.js +8 -0
- package/dist/ports/browser-launcher.js.map +1 -0
- package/dist/ports/clock.d.ts +6 -0
- package/dist/ports/clock.d.ts.map +1 -0
- package/dist/ports/clock.js +6 -0
- package/dist/ports/clock.js.map +1 -0
- package/dist/ports/file-system.d.ts +13 -0
- package/dist/ports/file-system.d.ts.map +1 -0
- package/dist/ports/file-system.js +25 -0
- package/dist/ports/file-system.js.map +1 -0
- package/dist/ports/http-client.d.ts +13 -0
- package/dist/ports/http-client.d.ts.map +1 -0
- package/dist/ports/http-client.js +12 -0
- package/dist/ports/http-client.js.map +1 -0
- package/dist/ports/logger.d.ts +9 -0
- package/dist/ports/logger.d.ts.map +1 -0
- package/dist/ports/logger.js +9 -0
- package/dist/ports/logger.js.map +1 -0
- package/dist/ports/process-runner.d.ts +17 -0
- package/dist/ports/process-runner.d.ts.map +1 -0
- package/dist/ports/process-runner.js +24 -0
- package/dist/ports/process-runner.js.map +1 -0
- package/dist/qa-store.d.ts +33 -0
- package/dist/qa-store.d.ts.map +1 -0
- package/dist/qa-store.js +65 -0
- package/dist/qa-store.js.map +1 -0
- package/dist/redaction.d.ts +11 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +53 -0
- package/dist/redaction.js.map +1 -0
- package/dist/secret-scan.d.ts +7 -0
- package/dist/secret-scan.d.ts.map +1 -0
- package/dist/secret-scan.js +21 -0
- package/dist/secret-scan.js.map +1 -0
- package/dist/test-support/fake-browser-launcher.d.ts +16 -0
- package/dist/test-support/fake-browser-launcher.d.ts.map +1 -0
- package/dist/test-support/fake-browser-launcher.js +64 -0
- package/dist/test-support/fake-browser-launcher.js.map +1 -0
- package/dist/test-support/fake-file-system.d.ts +12 -0
- package/dist/test-support/fake-file-system.d.ts.map +1 -0
- package/dist/test-support/fake-file-system.js +33 -0
- package/dist/test-support/fake-file-system.js.map +1 -0
- package/package.json +39 -0
package/dist/qa-store.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Copyright The QA-AI-STLC Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { readJsonFile, writeJsonFile } from './json-file.js';
|
|
5
|
+
import { nodeFileSystem } from './ports/file-system.js';
|
|
6
|
+
import { resolveRelativePath, toRelativePath } from './paths.js';
|
|
7
|
+
// The footprint documented in the development plan section 3.2. `state.json` is owned by the
|
|
8
|
+
// state machine (P2-01) and `config.yaml` is written by `qa init` (P1-04); this store only
|
|
9
|
+
// guarantees the directories they land in exist.
|
|
10
|
+
const STORE_SUBDIRECTORIES = ['artifacts', 'selectors', 'runs', 'evidence', 'reports'];
|
|
11
|
+
/**
|
|
12
|
+
* The `.qa/` store: resolves an artifact's project-relative path to a real file under `.qa/`,
|
|
13
|
+
* creates the documented directory layout, and reads or writes JSON through the injected
|
|
14
|
+
* `FileSystem` port so callers never touch `node:fs` directly (AGENTS.md 5.3).
|
|
15
|
+
*/
|
|
16
|
+
export class QaStore {
|
|
17
|
+
projectRoot;
|
|
18
|
+
qaDir;
|
|
19
|
+
fs;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.projectRoot = options.projectRoot;
|
|
22
|
+
this.qaDir = join(options.projectRoot, '.qa');
|
|
23
|
+
this.fs = options.fs ?? nodeFileSystem;
|
|
24
|
+
}
|
|
25
|
+
/** Resolves a project-relative artifact path (e.g. `manifest.json`) to an absolute path. */
|
|
26
|
+
resolve(relativePath) {
|
|
27
|
+
return resolveRelativePath(this.qaDir, relativePath);
|
|
28
|
+
}
|
|
29
|
+
/** Converts an absolute path under `.qa/` back to the project-relative form artifacts use. */
|
|
30
|
+
toRelativePath(absolutePath) {
|
|
31
|
+
return toRelativePath(this.qaDir, absolutePath);
|
|
32
|
+
}
|
|
33
|
+
async exists() {
|
|
34
|
+
return this.fs.pathExists(this.qaDir);
|
|
35
|
+
}
|
|
36
|
+
/** Creates `.qa/` and every committed subdirectory from the documented layout, if missing. */
|
|
37
|
+
async ensureLayout() {
|
|
38
|
+
await this.fs.mkdir(this.qaDir);
|
|
39
|
+
await Promise.all(STORE_SUBDIRECTORIES.map((name) => this.fs.mkdir(join(this.qaDir, name))));
|
|
40
|
+
}
|
|
41
|
+
async readJson(relativePath, schema) {
|
|
42
|
+
return readJsonFile(this.fs, this.resolve(relativePath), schema);
|
|
43
|
+
}
|
|
44
|
+
async writeJson(relativePath, value) {
|
|
45
|
+
const absolutePath = this.resolve(relativePath);
|
|
46
|
+
await this.fs.mkdir(dirname(absolutePath));
|
|
47
|
+
await writeJsonFile(this.fs, absolutePath, value);
|
|
48
|
+
}
|
|
49
|
+
async pathExists(relativePath) {
|
|
50
|
+
return this.fs.pathExists(this.resolve(relativePath));
|
|
51
|
+
}
|
|
52
|
+
async readText(relativePath) {
|
|
53
|
+
return this.fs.readFile(this.resolve(relativePath));
|
|
54
|
+
}
|
|
55
|
+
async writeText(relativePath, content) {
|
|
56
|
+
await this.writeBytes(relativePath, content);
|
|
57
|
+
}
|
|
58
|
+
/** Like `writeText`, but also accepts raw bytes for binary artifacts such as evidence. */
|
|
59
|
+
async writeBytes(relativePath, content) {
|
|
60
|
+
const absolutePath = this.resolve(relativePath);
|
|
61
|
+
await this.fs.mkdir(dirname(absolutePath));
|
|
62
|
+
await this.fs.writeFile(absolutePath, content);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=qa-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qa-store.js","sourceRoot":"","sources":["../src/qa-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strips authorization headers, cookies and set-cookie headers from a captured HAR document
|
|
3
|
+
* before it is ever written to disk (development plan section 6.4, AGENTS.md 5.8). Content that
|
|
4
|
+
* fails to parse as JSON is returned unchanged, `redacted: false` — the secret scan is the
|
|
5
|
+
* fallback safety net for anything this proactive pass could not understand.
|
|
6
|
+
*/
|
|
7
|
+
export declare function redactHar(harJson: string): {
|
|
8
|
+
readonly content: string;
|
|
9
|
+
readonly redacted: boolean;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=redaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AA6CA;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAqBnG"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Copyright The QA-AI-STLC Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
const REDACTED = '[REDACTED]';
|
|
4
|
+
const SENSITIVE_HEADER_NAMES = new Set(['authorization', 'cookie', 'set-cookie']);
|
|
5
|
+
function redactHeaders(headers) {
|
|
6
|
+
if (!Array.isArray(headers)) {
|
|
7
|
+
return headers;
|
|
8
|
+
}
|
|
9
|
+
return headers.map((header) => {
|
|
10
|
+
const name = typeof header.name === 'string' ? header.name.toLowerCase() : '';
|
|
11
|
+
return SENSITIVE_HEADER_NAMES.has(name) ? { ...header, value: REDACTED } : header;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function redactCookies(cookies) {
|
|
15
|
+
if (!Array.isArray(cookies)) {
|
|
16
|
+
return cookies;
|
|
17
|
+
}
|
|
18
|
+
return cookies.map((cookie) => ({ ...cookie, value: REDACTED }));
|
|
19
|
+
}
|
|
20
|
+
function redactMessage(message) {
|
|
21
|
+
if (message === undefined) {
|
|
22
|
+
return message;
|
|
23
|
+
}
|
|
24
|
+
return { ...message, headers: redactHeaders(message.headers), cookies: redactCookies(message.cookies) };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Strips authorization headers, cookies and set-cookie headers from a captured HAR document
|
|
28
|
+
* before it is ever written to disk (development plan section 6.4, AGENTS.md 5.8). Content that
|
|
29
|
+
* fails to parse as JSON is returned unchanged, `redacted: false` — the secret scan is the
|
|
30
|
+
* fallback safety net for anything this proactive pass could not understand.
|
|
31
|
+
*/
|
|
32
|
+
export function redactHar(harJson) {
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(harJson);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return { content: harJson, redacted: false };
|
|
39
|
+
}
|
|
40
|
+
const har = parsed;
|
|
41
|
+
const entries = har.log?.entries;
|
|
42
|
+
if (!Array.isArray(entries)) {
|
|
43
|
+
return { content: harJson, redacted: false };
|
|
44
|
+
}
|
|
45
|
+
const redactedEntries = entries.map((entry) => ({
|
|
46
|
+
...entry,
|
|
47
|
+
request: redactMessage(entry.request),
|
|
48
|
+
response: redactMessage(entry.response),
|
|
49
|
+
}));
|
|
50
|
+
const redactedHar = { ...har, log: { ...har.log, entries: redactedEntries } };
|
|
51
|
+
return { content: JSON.stringify(redactedHar), redacted: true };
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAEtC,MAAM,QAAQ,GAAG,YAAY,CAAC;AAC9B,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC,CAAC,eAAe,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;AAiBlF,SAAS,aAAa,CAAC,OAAgB;IACrC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAiB,EAAE,EAAE;QACvC,MAAM,IAAI,GAAG,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9E,OAAO,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACpF,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CAAC,OAAgB;IACrC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAA+B,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;AAC5F,CAAC;AAED,SAAS,aAAa,CAAC,OAA+B;IACpD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;AAC1G,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC/C,CAAC;IAED,MAAM,GAAG,GAAG,MAA4C,CAAC;IACzD,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC;IACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC/C,CAAC;IAED,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC9C,GAAG,KAAK;QACR,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC;QACrC,QAAQ,EAAE,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC;KACxC,CAAC,CAAC,CAAC;IACJ,MAAM,WAAW,GAAG,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,EAAE,GAAG,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE,CAAC;IAC9E,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAClE,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface SecretMatch {
|
|
2
|
+
/** A stable, public name for the kind of secret found — never the matched value itself. */
|
|
3
|
+
readonly pattern: string;
|
|
4
|
+
}
|
|
5
|
+
/** Scans text for well-known secret shapes before it is registered as evidence (AGENTS.md 12.5). */
|
|
6
|
+
export declare function scanForSecrets(content: string): readonly SecretMatch[];
|
|
7
|
+
//# sourceMappingURL=secret-scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-scan.d.ts","sourceRoot":"","sources":["../src/secret-scan.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,2FAA2F;IAC3F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAiBD,oGAAoG;AACpG,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,WAAW,EAAE,CAEtE"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright The QA-AI-STLC Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
// Deliberately conservative: each pattern targets a specific, well-known secret shape (AWS access
|
|
4
|
+
// key IDs, GitHub/Slack tokens, JWTs, Bearer headers) or an explicit `password`/`apiKey`
|
|
5
|
+
// assignment, rather than a generic high-entropy-string heuristic. Evidence content is real
|
|
6
|
+
// application data (network bodies, console output); a scanner that flags too eagerly makes every
|
|
7
|
+
// run untrustworthy and trains operators to ignore quarantine receipts.
|
|
8
|
+
const SECRET_PATTERNS = [
|
|
9
|
+
{ pattern: 'aws-access-key-id', regex: /\bAKIA[0-9A-Z]{16}\b/ },
|
|
10
|
+
{ pattern: 'github-token', regex: /\bgh[pousr]_[A-Za-z0-9]{36}\b/ },
|
|
11
|
+
{ pattern: 'slack-token', regex: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/ },
|
|
12
|
+
{ pattern: 'jwt', regex: /\beyJ[\w-]+\.[\w-]+\.[\w-]+\b/ },
|
|
13
|
+
{ pattern: 'bearer-token', regex: /\bBearer\s+[A-Za-z0-9\-._~+/]{16,}=*/i },
|
|
14
|
+
{ pattern: 'password-assignment', regex: /["']?password["']?\s*[:=]\s*["'][^"'\s]{4,}["']/i },
|
|
15
|
+
{ pattern: 'api-key-assignment', regex: /["']?api[_-]?key["']?\s*[:=]\s*["'][A-Za-z0-9_-]{16,}["']/i },
|
|
16
|
+
];
|
|
17
|
+
/** Scans text for well-known secret shapes before it is registered as evidence (AGENTS.md 12.5). */
|
|
18
|
+
export function scanForSecrets(content) {
|
|
19
|
+
return SECRET_PATTERNS.filter(({ regex }) => regex.test(content)).map(({ pattern }) => ({ pattern }));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=secret-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-scan.js","sourceRoot":"","sources":["../src/secret-scan.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAOtC,kGAAkG;AAClG,yFAAyF;AACzF,4FAA4F;AAC5F,kGAAkG;AAClG,wEAAwE;AACxE,MAAM,eAAe,GAAoE;IACvF,EAAE,OAAO,EAAE,mBAAmB,EAAE,KAAK,EAAE,sBAAsB,EAAE;IAC/D,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,+BAA+B,EAAE;IACnE,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,kCAAkC,EAAE;IACrE,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,+BAA+B,EAAE;IAC1D,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,uCAAuC,EAAE;IAC3E,EAAE,OAAO,EAAE,qBAAqB,EAAE,KAAK,EAAE,kDAAkD,EAAE;IAC7F,EAAE,OAAO,EAAE,oBAAoB,EAAE,KAAK,EAAE,4DAA4D,EAAE;CACvG,CAAC;AAEF,oGAAoG;AACpG,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;AACxG,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AuthBrowserContext, BrowserLauncher, StorageState } from '../ports/browser-launcher.js';
|
|
2
|
+
export interface FakePageCall {
|
|
3
|
+
readonly method: 'goto' | 'fill' | 'click' | 'waitForLoadState';
|
|
4
|
+
readonly args: readonly unknown[];
|
|
5
|
+
}
|
|
6
|
+
export interface FakeBrowserLauncherOptions {
|
|
7
|
+
readonly storageState?: StorageState;
|
|
8
|
+
readonly contexts?: readonly AuthBrowserContext[];
|
|
9
|
+
}
|
|
10
|
+
export interface FakeBrowserLauncher extends BrowserLauncher {
|
|
11
|
+
readonly pageCalls: FakePageCall[];
|
|
12
|
+
readonly closedBrowsers: number;
|
|
13
|
+
}
|
|
14
|
+
/** A fake `BrowserLauncher` for unit tests: no real browser is ever launched (AGENTS.md 5.3, 13). */
|
|
15
|
+
export declare function createFakeBrowserLauncher(options?: FakeBrowserLauncherOptions): FakeBrowserLauncher;
|
|
16
|
+
//# sourceMappingURL=fake-browser-launcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-browser-launcher.d.ts","sourceRoot":"","sources":["../../src/test-support/fake-browser-launcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAEV,kBAAkB,EAElB,eAAe,EACf,YAAY,EACb,MAAM,8BAA8B,CAAC;AAItC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,kBAAkB,CAAC;IAChE,QAAQ,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACnD;AAED,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D,QAAQ,CAAC,SAAS,EAAE,YAAY,EAAE,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAuBD,qGAAqG;AACrG,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,0BAA+B,GAAG,mBAAmB,CAwCvG"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Copyright The QA-AI-STLC Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
const EMPTY_STORAGE_STATE = { cookies: [], origins: [] };
|
|
4
|
+
function createFakePage(calls) {
|
|
5
|
+
return {
|
|
6
|
+
goto: (...args) => {
|
|
7
|
+
calls.push({ method: 'goto', args });
|
|
8
|
+
return Promise.resolve(undefined);
|
|
9
|
+
},
|
|
10
|
+
fill: (...args) => {
|
|
11
|
+
calls.push({ method: 'fill', args });
|
|
12
|
+
return Promise.resolve();
|
|
13
|
+
},
|
|
14
|
+
click: (...args) => {
|
|
15
|
+
calls.push({ method: 'click', args });
|
|
16
|
+
return Promise.resolve();
|
|
17
|
+
},
|
|
18
|
+
waitForLoadState: (...args) => {
|
|
19
|
+
calls.push({ method: 'waitForLoadState', args });
|
|
20
|
+
return Promise.resolve();
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** A fake `BrowserLauncher` for unit tests: no real browser is ever launched (AGENTS.md 5.3, 13). */
|
|
25
|
+
export function createFakeBrowserLauncher(options = {}) {
|
|
26
|
+
const pageCalls = [];
|
|
27
|
+
let closedBrowsers = 0;
|
|
28
|
+
const storageState = options.storageState ?? EMPTY_STORAGE_STATE;
|
|
29
|
+
const launcher = {
|
|
30
|
+
pageCalls,
|
|
31
|
+
get closedBrowsers() {
|
|
32
|
+
return closedBrowsers;
|
|
33
|
+
},
|
|
34
|
+
launch: () => {
|
|
35
|
+
const context = {
|
|
36
|
+
newPage: () => Promise.resolve(createFakePage(pageCalls)),
|
|
37
|
+
storageState: () => Promise.resolve(storageState),
|
|
38
|
+
close: () => Promise.resolve(),
|
|
39
|
+
};
|
|
40
|
+
const browser = {
|
|
41
|
+
newContext: () => Promise.resolve(context),
|
|
42
|
+
contexts: () => [context],
|
|
43
|
+
close: () => {
|
|
44
|
+
closedBrowsers += 1;
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
return Promise.resolve(browser);
|
|
49
|
+
},
|
|
50
|
+
connectOverCdp: () => {
|
|
51
|
+
const browser = {
|
|
52
|
+
newContext: () => Promise.reject(new Error('newContext is not available on an attached browser in this fake')),
|
|
53
|
+
contexts: () => options.contexts ?? [],
|
|
54
|
+
close: () => {
|
|
55
|
+
closedBrowsers += 1;
|
|
56
|
+
return Promise.resolve();
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
return Promise.resolve(browser);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
return launcher;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=fake-browser-launcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-browser-launcher.js","sourceRoot":"","sources":["../../src/test-support/fake-browser-launcher.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAUtC,MAAM,mBAAmB,GAAiB,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAiBvE,SAAS,cAAc,CAAC,KAAqB;IAC3C,OAAO;QACL,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE;YAChB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACrC,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE;YAChB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YACrC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QACD,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE;YACjB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QACD,gBAAgB,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE;YAC5B,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC;YACjD,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,yBAAyB,CAAC,UAAsC,EAAE;IAChF,MAAM,SAAS,GAAmB,EAAE,CAAC;IACrC,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,mBAAmB,CAAC;IAEjE,MAAM,QAAQ,GAAwB;QACpC,SAAS;QACT,IAAI,cAAc;YAChB,OAAO,cAAc,CAAC;QACxB,CAAC;QACD,MAAM,EAAE,GAAG,EAAE;YACX,MAAM,OAAO,GAAuB;gBAClC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;gBACzD,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC;gBACjD,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE;aAC/B,CAAC;YACF,MAAM,OAAO,GAAgB;gBAC3B,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;gBAC1C,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC;gBACzB,KAAK,EAAE,GAAG,EAAE;oBACV,cAAc,IAAI,CAAC,CAAC;oBACpB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;gBAC3B,CAAC;aACF,CAAC;YACF,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC;QACD,cAAc,EAAE,GAAG,EAAE;YACnB,MAAM,OAAO,GAAgB;gBAC3B,UAAU,EAAE,GAAG,EAAE,CACf,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;gBAC9F,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE;gBACtC,KAAK,EAAE,GAAG,EAAE;oBACV,cAAc,IAAI,CAAC,CAAC;oBACpB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;gBAC3B,CAAC;aACF,CAAC;YACF,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC;KACF,CAAC;IACF,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FileSystem } from '../ports/file-system.js';
|
|
2
|
+
export interface FakeFileSystem extends FileSystem {
|
|
3
|
+
/** Returns exactly what was written (string or raw bytes), for tests that need to tell them apart. */
|
|
4
|
+
getRawFile(absolutePath: string): string | Uint8Array | undefined;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* An in-memory `FileSystem` for unit tests that exercise store and manifest logic without
|
|
8
|
+
* touching a real disk (AGENTS.md 5.3, 13). Keys are absolute paths as given by the caller;
|
|
9
|
+
* there is no path normalization, matching what the real Node adapter receives from `paths.ts`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createFakeFileSystem(initialFiles?: Readonly<Record<string, string>>): FakeFileSystem;
|
|
12
|
+
//# sourceMappingURL=fake-file-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-file-system.d.ts","sourceRoot":"","sources":["../../src/test-support/fake-file-system.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,sGAAsG;IACtG,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;CACnE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GAAG,cAAc,CAyBxG"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Copyright The QA-AI-STLC Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* An in-memory `FileSystem` for unit tests that exercise store and manifest logic without
|
|
5
|
+
* touching a real disk (AGENTS.md 5.3, 13). Keys are absolute paths as given by the caller;
|
|
6
|
+
* there is no path normalization, matching what the real Node adapter receives from `paths.ts`.
|
|
7
|
+
*/
|
|
8
|
+
export function createFakeFileSystem(initialFiles = {}) {
|
|
9
|
+
const files = new Map(Object.entries(initialFiles));
|
|
10
|
+
const directories = new Set();
|
|
11
|
+
return {
|
|
12
|
+
readFile: (absolutePath) => {
|
|
13
|
+
const content = files.get(absolutePath);
|
|
14
|
+
if (content === undefined) {
|
|
15
|
+
const error = new Error(`ENOENT: no such file, open '${absolutePath}'`);
|
|
16
|
+
error.code = 'ENOENT';
|
|
17
|
+
return Promise.reject(error);
|
|
18
|
+
}
|
|
19
|
+
return Promise.resolve(typeof content === 'string' ? content : Buffer.from(content).toString('utf-8'));
|
|
20
|
+
},
|
|
21
|
+
writeFile: (absolutePath, content) => {
|
|
22
|
+
files.set(absolutePath, content);
|
|
23
|
+
return Promise.resolve();
|
|
24
|
+
},
|
|
25
|
+
mkdir: (absolutePath) => {
|
|
26
|
+
directories.add(absolutePath);
|
|
27
|
+
return Promise.resolve();
|
|
28
|
+
},
|
|
29
|
+
pathExists: (absolutePath) => Promise.resolve(files.has(absolutePath) || directories.has(absolutePath)),
|
|
30
|
+
getRawFile: (absolutePath) => files.get(absolutePath),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=fake-file-system.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fake-file-system.js","sourceRoot":"","sources":["../../src/test-support/fake-file-system.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAStC;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,eAAiD,EAAE;IACtF,MAAM,KAAK,GAAG,IAAI,GAAG,CAA8B,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;IACjF,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IAEtC,OAAO;QACL,QAAQ,EAAE,CAAC,YAAY,EAAE,EAAE;YACzB,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACxC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,+BAA+B,YAAY,GAAG,CAA0B,CAAC;gBACjG,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAC;gBACtB,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC/B,CAAC;YACD,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QACzG,CAAC;QACD,SAAS,EAAE,CAAC,YAAY,EAAE,OAAO,EAAE,EAAE;YACnC,KAAK,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YACjC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QACD,KAAK,EAAE,CAAC,YAAY,EAAE,EAAE;YACtB,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC9B,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QACD,UAAU,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACvG,UAAU,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC;KACtD,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qa-ai-stlc/core",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "The QA-AI-STLC engine: .qa/ store, manifest hashing, config loading, and the injected clock/file-system/logger ports every other package builds on.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"homepage": "https://github.com/Klim-101/QA-AI-STLC/tree/main/packages/core#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Klim-101/QA-AI-STLC.git",
|
|
11
|
+
"directory": "packages/core"
|
|
12
|
+
},
|
|
13
|
+
"bugs": "https://github.com/Klim-101/QA-AI-STLC/issues",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"pretypecheck": "npm run build --workspace @qa-ai-stlc/schemas --workspace @qa-ai-stlc/test-utils",
|
|
22
|
+
"typecheck": "tsc --noEmit -p .",
|
|
23
|
+
"pretest": "npm run build --workspace @qa-ai-stlc/schemas --workspace @qa-ai-stlc/test-utils",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"pretest:coverage": "npm run build --workspace @qa-ai-stlc/schemas --workspace @qa-ai-stlc/test-utils",
|
|
26
|
+
"test:coverage": "vitest run --coverage",
|
|
27
|
+
"build": "tsc -b tsconfig.build.json",
|
|
28
|
+
"clean": "node ../../scripts/clean-dir.mjs dist"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@qa-ai-stlc/schemas": "0.0.0",
|
|
32
|
+
"playwright": "1.63.0",
|
|
33
|
+
"yaml": "2.9.1",
|
|
34
|
+
"zod": "4.6.5"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@qa-ai-stlc/test-utils": "0.0.0"
|
|
38
|
+
}
|
|
39
|
+
}
|