assertloom 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +393 -2
- package/dist/ai/anthropic.d.ts +11 -0
- package/dist/ai/anthropic.js +68 -0
- package/dist/ai/anthropic.js.map +1 -0
- package/dist/ai/index.d.ts +12 -0
- package/dist/ai/index.js +23 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/openai.d.ts +9 -0
- package/dist/ai/openai.js +57 -0
- package/dist/ai/openai.js.map +1 -0
- package/dist/ai/openaiCompatible.d.ts +13 -0
- package/dist/ai/openaiCompatible.js +62 -0
- package/dist/ai/openaiCompatible.js.map +1 -0
- package/dist/ai/pricing.d.ts +38 -0
- package/dist/ai/pricing.js +105 -0
- package/dist/ai/pricing.js.map +1 -0
- package/dist/ai/prompts.d.ts +27 -0
- package/dist/ai/prompts.js +89 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/ai/provider.d.ts +26 -0
- package/dist/ai/provider.js +4 -0
- package/dist/ai/provider.js.map +1 -0
- package/dist/ai/redact.d.ts +5 -0
- package/dist/ai/redact.js +31 -0
- package/dist/ai/redact.js.map +1 -0
- package/dist/analyzer/dependencies.d.ts +16 -0
- package/dist/analyzer/dependencies.js +168 -0
- package/dist/analyzer/dependencies.js.map +1 -0
- package/dist/analyzer/format.d.ts +3 -0
- package/dist/analyzer/format.js +14 -0
- package/dist/analyzer/format.js.map +1 -0
- package/dist/analyzer/functions.d.ts +4 -0
- package/dist/analyzer/functions.js +324 -0
- package/dist/analyzer/functions.js.map +1 -0
- package/dist/analyzer/index.d.ts +5 -0
- package/dist/analyzer/index.js +29 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/analyzer/parser.d.ts +11 -0
- package/dist/analyzer/parser.js +38 -0
- package/dist/analyzer/parser.js.map +1 -0
- package/dist/analyzer/usage.d.ts +16 -0
- package/dist/analyzer/usage.js +159 -0
- package/dist/analyzer/usage.js.map +1 -0
- package/dist/ci/report.d.ts +88 -0
- package/dist/ci/report.js +139 -0
- package/dist/ci/report.js.map +1 -0
- package/dist/ci/run.d.ts +70 -0
- package/dist/ci/run.js +198 -0
- package/dist/ci/run.js.map +1 -0
- package/dist/ci/select.d.ts +58 -0
- package/dist/ci/select.js +238 -0
- package/dist/ci/select.js.map +1 -0
- package/dist/cli/commands/analyze.d.ts +2 -0
- package/dist/cli/commands/analyze.js +49 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/ci.d.ts +37 -0
- package/dist/cli/commands/ci.js +258 -0
- package/dist/cli/commands/ci.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +3 -0
- package/dist/cli/commands/generate.js +201 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/run.d.ts +4 -0
- package/dist/cli/commands/run.js +53 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +16 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/program.d.ts +2 -0
- package/dist/cli/program.js +20 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/report.d.ts +3 -0
- package/dist/cli/report.js +36 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/validateFile.d.ts +5 -0
- package/dist/cli/validateFile.js +43 -0
- package/dist/cli/validateFile.js.map +1 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +8 -0
- package/dist/errors.js.map +1 -0
- package/dist/generator/diff.d.ts +5 -0
- package/dist/generator/diff.js +56 -0
- package/dist/generator/diff.js.map +1 -0
- package/dist/generator/importPlan.d.ts +15 -0
- package/dist/generator/importPlan.js +62 -0
- package/dist/generator/importPlan.js.map +1 -0
- package/dist/generator/index.d.ts +5 -0
- package/dist/generator/index.js +6 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/mocks.d.ts +48 -0
- package/dist/generator/mocks.js +356 -0
- package/dist/generator/mocks.js.map +1 -0
- package/dist/generator/schema.d.ts +40 -0
- package/dist/generator/schema.js +15 -0
- package/dist/generator/schema.js.map +1 -0
- package/dist/generator/testGenerator.d.ts +36 -0
- package/dist/generator/testGenerator.js +85 -0
- package/dist/generator/testGenerator.js.map +1 -0
- package/dist/generator/testWriter.d.ts +22 -0
- package/dist/generator/testWriter.js +93 -0
- package/dist/generator/testWriter.js.map +1 -0
- package/dist/generator/validate.d.ts +31 -0
- package/dist/generator/validate.js +180 -0
- package/dist/generator/validate.js.map +1 -0
- package/dist/repair/classifyFailure.d.ts +11 -0
- package/dist/repair/classifyFailure.js +26 -0
- package/dist/repair/classifyFailure.js.map +1 -0
- package/dist/repair/index.d.ts +2 -0
- package/dist/repair/index.js +3 -0
- package/dist/repair/index.js.map +1 -0
- package/dist/repair/repairLoop.d.ts +74 -0
- package/dist/repair/repairLoop.js +219 -0
- package/dist/repair/repairLoop.js.map +1 -0
- package/dist/repair/schema.d.ts +17 -0
- package/dist/repair/schema.js +14 -0
- package/dist/repair/schema.js.map +1 -0
- package/dist/runner/coverage.d.ts +5 -0
- package/dist/runner/coverage.js +42 -0
- package/dist/runner/coverage.js.map +1 -0
- package/dist/runner/index.d.ts +5 -0
- package/dist/runner/index.js +5 -0
- package/dist/runner/index.js.map +1 -0
- package/dist/runner/jestConfig.d.ts +11 -0
- package/dist/runner/jestConfig.js +103 -0
- package/dist/runner/jestConfig.js.map +1 -0
- package/dist/runner/jestRunner.d.ts +17 -0
- package/dist/runner/jestRunner.js +154 -0
- package/dist/runner/jestRunner.js.map +1 -0
- package/dist/runner/networkGuard.d.ts +4 -0
- package/dist/runner/networkGuard.js +55 -0
- package/dist/runner/networkGuard.js.map +1 -0
- package/dist/runner/project.d.ts +30 -0
- package/dist/runner/project.js +86 -0
- package/dist/runner/project.js.map +1 -0
- package/dist/runner/results.d.ts +32 -0
- package/dist/runner/results.js +58 -0
- package/dist/runner/results.js.map +1 -0
- package/dist/runner/types.d.ts +39 -0
- package/dist/runner/types.js +2 -0
- package/dist/runner/types.js.map +1 -0
- package/dist/runner/vitest.d.ts +24 -0
- package/dist/runner/vitest.js +105 -0
- package/dist/runner/vitest.js.map +1 -0
- package/dist/types/index.d.ts +96 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +67 -4
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { classifySource, scanImports } from "../analyzer/dependencies.js";
|
|
5
|
+
import { parseSource } from "../analyzer/parser.js";
|
|
6
|
+
import { collectImportUsage, emptyShape } from "../analyzer/usage.js";
|
|
7
|
+
import { SUPPORTED_EXTENSIONS } from "../types/index.js";
|
|
8
|
+
/** Built-ins that are deterministic and side-effect free, so they stay real. */
|
|
9
|
+
export const PURE_BUILTINS = new Set(["assert", "buffer", "path", "path/posix", "path/win32", "querystring", "string_decoder", "url", "util"]);
|
|
10
|
+
/** Utility packages without I/O. Mocking them would break the code under test, so they stay real. */
|
|
11
|
+
export const PURE_PACKAGES = new Set([
|
|
12
|
+
"@babel/runtime", "big.js", "bignumber.js", "change-case", "classnames", "clsx", "date-fns", "dayjs", "decimal.js",
|
|
13
|
+
"deepmerge", "fast-deep-equal", "immer", "joi", "lodash", "lodash-es", "ms", "qs", "ramda", "semver", "tslib",
|
|
14
|
+
"validator", "yup", "zod",
|
|
15
|
+
]);
|
|
16
|
+
const MAX_LOCAL_DEPTH = 5;
|
|
17
|
+
const TYPESCRIPT_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts"];
|
|
18
|
+
async function isFile(file) {
|
|
19
|
+
try {
|
|
20
|
+
return (await stat(file)).isFile();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function resolveLocal(fromFile, specifier) {
|
|
27
|
+
if (specifier.startsWith("#"))
|
|
28
|
+
return undefined;
|
|
29
|
+
const base = path.resolve(path.dirname(fromFile), specifier);
|
|
30
|
+
const candidates = [base];
|
|
31
|
+
const jsExtension = /\.[cm]?jsx?$/.exec(base);
|
|
32
|
+
if (jsExtension)
|
|
33
|
+
candidates.push(...TYPESCRIPT_EXTENSIONS.map((ext) => base.slice(0, -jsExtension[0].length) + ext));
|
|
34
|
+
candidates.push(...SUPPORTED_EXTENSIONS.map((ext) => base + ext), ...SUPPORTED_EXTENSIONS.map((ext) => path.join(base, `index${ext}`)));
|
|
35
|
+
for (const candidate of candidates)
|
|
36
|
+
if (await isFile(candidate))
|
|
37
|
+
return candidate;
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
function isInstalled(fromFile, specifier) {
|
|
41
|
+
try {
|
|
42
|
+
createRequire(fromFile).resolve(specifier);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Why a local file has side effects (its first impure dependency), or undefined if it has none. */
|
|
50
|
+
async function localImpurity(file, visited, depth) {
|
|
51
|
+
if (visited.has(file))
|
|
52
|
+
return undefined;
|
|
53
|
+
visited.add(file);
|
|
54
|
+
const extension = path.extname(file);
|
|
55
|
+
if (!SUPPORTED_EXTENSIONS.includes(extension))
|
|
56
|
+
return undefined;
|
|
57
|
+
if (depth > MAX_LOCAL_DEPTH)
|
|
58
|
+
return "deeply nested local modules";
|
|
59
|
+
let ast;
|
|
60
|
+
try {
|
|
61
|
+
ast = parseSource(await readFile(file, "utf8"), extension);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return `${path.basename(file)}, which could not be parsed`;
|
|
65
|
+
}
|
|
66
|
+
const { globals } = collectImportUsage(ast);
|
|
67
|
+
if (globals.length)
|
|
68
|
+
return `global ${globals[0]}`;
|
|
69
|
+
for (const entry of scanImports(ast).imports) {
|
|
70
|
+
if (entry.typeOnly)
|
|
71
|
+
continue;
|
|
72
|
+
const decision = await decide(entry.source, entry.category, entry.packageName, file, visited, depth + 1);
|
|
73
|
+
if (decision.mock)
|
|
74
|
+
return entry.category === "local" ? `${entry.source} (${decision.reason})` : entry.source;
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
async function decide(source, category, packageName, fromFile, visited, depth) {
|
|
79
|
+
if (category === "builtin") {
|
|
80
|
+
return PURE_BUILTINS.has(source.replace(/^node:/, ""))
|
|
81
|
+
? { mock: false, virtual: false, reason: "pure Node built-in" }
|
|
82
|
+
: { mock: true, virtual: false, reason: "Node built-in with side effects" };
|
|
83
|
+
}
|
|
84
|
+
if (category === "external") {
|
|
85
|
+
if (packageName && PURE_PACKAGES.has(packageName))
|
|
86
|
+
return { mock: false, virtual: false, reason: "utility library without I/O" };
|
|
87
|
+
const installed = isInstalled(fromFile, source);
|
|
88
|
+
return { mock: true, virtual: !installed, reason: installed ? "external package" : "external package, not installed" };
|
|
89
|
+
}
|
|
90
|
+
const file = await resolveLocal(fromFile, source);
|
|
91
|
+
if (!file)
|
|
92
|
+
return { mock: true, virtual: true, reason: "local module not found" };
|
|
93
|
+
const impurity = await localImpurity(file, visited, depth);
|
|
94
|
+
return impurity
|
|
95
|
+
? { mock: true, virtual: false, reason: `depends on ${impurity}` }
|
|
96
|
+
: { mock: false, virtual: false, reason: "local module without side effects" };
|
|
97
|
+
}
|
|
98
|
+
function matches(list, source, packageName) {
|
|
99
|
+
return !!list?.some((entry) => entry === source || entry === packageName || entry.replace(/^node:/, "") === source.replace(/^node:/, ""));
|
|
100
|
+
}
|
|
101
|
+
function specifierFromTest(source, sourcePath, testPath) {
|
|
102
|
+
if (!source.startsWith("."))
|
|
103
|
+
return source;
|
|
104
|
+
const relative = path.relative(path.dirname(testPath), path.resolve(path.dirname(sourcePath), source)).replaceAll("\\", "/");
|
|
105
|
+
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
106
|
+
}
|
|
107
|
+
function mergeShapes(a, b) {
|
|
108
|
+
const merged = a
|
|
109
|
+
? { ...a, members: { ...a.members }, resultNames: [...a.resultNames] }
|
|
110
|
+
: { ...emptyShape(), members: {}, resultNames: [] };
|
|
111
|
+
merged.called ||= b.called;
|
|
112
|
+
merged.constructed ||= b.constructed;
|
|
113
|
+
merged.instanceOf ||= b.instanceOf;
|
|
114
|
+
merged.createdAtLoad ||= b.createdAtLoad;
|
|
115
|
+
for (const name of b.resultNames)
|
|
116
|
+
if (!merged.resultNames.includes(name))
|
|
117
|
+
merged.resultNames.push(name);
|
|
118
|
+
for (const [key, value] of Object.entries(b.members))
|
|
119
|
+
merged.members[key] = mergeShapes(merged.members[key], value);
|
|
120
|
+
if (b.result)
|
|
121
|
+
merged.result = mergeShapes(merged.result, b.result);
|
|
122
|
+
return merged;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Constructors, and calls made while the module loads (`const client = createClient()`), return one
|
|
126
|
+
* shared mock object, because tests cannot configure them before they run. Other calls are plain
|
|
127
|
+
* jest.fn() mocks whose return values tests set themselves.
|
|
128
|
+
*/
|
|
129
|
+
function sharesInstance(shape) {
|
|
130
|
+
return !!shape.result && (shape.constructed || shape.createdAtLoad);
|
|
131
|
+
}
|
|
132
|
+
const isCallable = (shape) => shape.called || shape.constructed || shape.instanceOf;
|
|
133
|
+
function propertyKey(name) {
|
|
134
|
+
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
|
|
135
|
+
}
|
|
136
|
+
class FactoryWriter {
|
|
137
|
+
locals = [];
|
|
138
|
+
count = 0;
|
|
139
|
+
members(shape) {
|
|
140
|
+
return Object.entries(shape.members).map(([key, value]) => `${propertyKey(key)}: ${this.value(value)}`);
|
|
141
|
+
}
|
|
142
|
+
/** A member that is only read (never called) is still a jest.fn(), the safest stand-in for an unknown value. */
|
|
143
|
+
value(shape) {
|
|
144
|
+
const members = this.members(shape);
|
|
145
|
+
const withMembers = (base) => (members.length ? `Object.assign(${base}, { ${members.join(", ")} })` : base);
|
|
146
|
+
if (sharesInstance(shape)) {
|
|
147
|
+
const name = `instance${++this.count}`;
|
|
148
|
+
this.locals.push(`const ${name} = ${this.object(shape.result)};`);
|
|
149
|
+
return withMembers(`function () { return ${name}; }`);
|
|
150
|
+
}
|
|
151
|
+
if (shape.instanceOf && !shape.called && !shape.constructed)
|
|
152
|
+
return withMembers("class {}");
|
|
153
|
+
if (shape.called || shape.constructed || members.length === 0)
|
|
154
|
+
return withMembers("jest.fn()");
|
|
155
|
+
return `{ ${members.join(", ")} }`;
|
|
156
|
+
}
|
|
157
|
+
object(shape) {
|
|
158
|
+
const members = this.members(shape);
|
|
159
|
+
return members.length ? `{ ${members.join(", ")} }` : "{}";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function modelModule(usages) {
|
|
163
|
+
let defaultShape;
|
|
164
|
+
let moduleShape = emptyShape();
|
|
165
|
+
for (const usage of usages) {
|
|
166
|
+
if (usage.imported === "default" && usage.style === "esm")
|
|
167
|
+
defaultShape = mergeShapes(defaultShape, usage.shape);
|
|
168
|
+
else if (usage.imported === "*")
|
|
169
|
+
moduleShape = mergeShapes(moduleShape, usage.shape);
|
|
170
|
+
else
|
|
171
|
+
moduleShape.members[usage.imported] = mergeShapes(moduleShape.members[usage.imported], usage.shape);
|
|
172
|
+
}
|
|
173
|
+
return { defaultShape, moduleShape, hasEsm: usages.some((u) => u.style === "esm"), callableRoot: !defaultShape && isCallable(moduleShape) };
|
|
174
|
+
}
|
|
175
|
+
function factoryFor(model) {
|
|
176
|
+
const writer = new FactoryWriter();
|
|
177
|
+
let value;
|
|
178
|
+
if (model.callableRoot) {
|
|
179
|
+
value = writer.value(model.moduleShape);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
const members = [
|
|
183
|
+
...(model.hasEsm ? ["__esModule: true"] : []),
|
|
184
|
+
...(model.defaultShape ? [`default: ${writer.value(model.defaultShape)}`] : []),
|
|
185
|
+
...writer.members(model.moduleShape),
|
|
186
|
+
];
|
|
187
|
+
value = members.length ? `{ ${members.join(", ")} }` : "{}";
|
|
188
|
+
}
|
|
189
|
+
if (writer.locals.length === 0)
|
|
190
|
+
return `() => (${value})`;
|
|
191
|
+
return `() => {\n ${writer.locals.join("\n ")}\n return ${value};\n}`;
|
|
192
|
+
}
|
|
193
|
+
function shapeFor(model, usage) {
|
|
194
|
+
if (usage.imported === "default" && usage.style === "esm")
|
|
195
|
+
return model.defaultShape;
|
|
196
|
+
if (usage.imported === "*")
|
|
197
|
+
return model.moduleShape;
|
|
198
|
+
return model.moduleShape.members[usage.imported];
|
|
199
|
+
}
|
|
200
|
+
class NameAllocator {
|
|
201
|
+
used;
|
|
202
|
+
constructor(reserved) {
|
|
203
|
+
this.used = new Set(reserved);
|
|
204
|
+
}
|
|
205
|
+
has(name) {
|
|
206
|
+
return this.used.has(name);
|
|
207
|
+
}
|
|
208
|
+
claim(preferred) {
|
|
209
|
+
let name = preferred;
|
|
210
|
+
for (let n = 2; this.used.has(name); n++)
|
|
211
|
+
name = `${preferred}Mock${n === 2 ? "" : n}`;
|
|
212
|
+
this.used.add(name);
|
|
213
|
+
return name;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function describeShape(shape, expr, names, out, isRoot) {
|
|
217
|
+
if (sharesInstance(shape)) {
|
|
218
|
+
const lastSegment = expr.split(/[.()\s]/).filter(Boolean).pop() ?? "mock";
|
|
219
|
+
const name = names.claim(shape.resultNames.find((n) => !names.has(n)) ?? `${lastSegment[0].toLowerCase()}${lastSegment.slice(1)}Instance`);
|
|
220
|
+
const init = shape.constructed ? `new ${expr}()` : `${expr}()`;
|
|
221
|
+
out.handles.push({ name, init });
|
|
222
|
+
out.lines.push(`\`${init}\` returns one shared mock object, available in tests as \`${name}\``);
|
|
223
|
+
describeShape(shape.result, name, names, out, false);
|
|
224
|
+
}
|
|
225
|
+
else if (shape.called || shape.constructed || (!isRoot && Object.keys(shape.members).length === 0)) {
|
|
226
|
+
out.fns.push(expr);
|
|
227
|
+
}
|
|
228
|
+
for (const [key, member] of Object.entries(shape.members)) {
|
|
229
|
+
const access = /^[A-Za-z_$][\w$]*$/.test(key) ? `${expr}.${key}` : `${expr}[${JSON.stringify(key)}]`;
|
|
230
|
+
describeShape(member, access, names, out, false);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function importStatement(usage, specifier, model, commonjs) {
|
|
234
|
+
const spec = JSON.stringify(specifier);
|
|
235
|
+
const local = usage.local;
|
|
236
|
+
if (commonjs) {
|
|
237
|
+
if (usage.imported === "default" && usage.style === "esm")
|
|
238
|
+
return `const ${local} = require(${spec}).default;`;
|
|
239
|
+
if (usage.imported === "*")
|
|
240
|
+
return `const ${local} = require(${spec});`;
|
|
241
|
+
return `const { ${usage.imported === local ? local : `${propertyKey(usage.imported)}: ${local}`} } = require(${spec});`;
|
|
242
|
+
}
|
|
243
|
+
if (usage.imported === "default" && usage.style === "esm")
|
|
244
|
+
return `import ${local} from ${spec};`;
|
|
245
|
+
if (usage.imported === "*")
|
|
246
|
+
return model.callableRoot ? `import ${local} from ${spec};` : `import * as ${local} from ${spec};`;
|
|
247
|
+
return `import { ${usage.imported === local ? local : `${JSON.stringify(usage.imported)} as ${local}`} } from ${spec};`;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Decides which imports to mock and writes the mocks. Assertloom writes every jest.mock() factory
|
|
251
|
+
* itself from how the source uses each import; the model only configures return values in tests.
|
|
252
|
+
*/
|
|
253
|
+
export async function planMocks(options) {
|
|
254
|
+
const { analysis, sourcePath, testPath } = options;
|
|
255
|
+
const commonjs = analysis.moduleSystem === "commonjs";
|
|
256
|
+
const typescript = analysis.language === "typescript";
|
|
257
|
+
const names = new NameAllocator(options.reserved);
|
|
258
|
+
const mocked = [];
|
|
259
|
+
const real = [];
|
|
260
|
+
const seen = new Set();
|
|
261
|
+
for (const entry of analysis.imports) {
|
|
262
|
+
if (entry.typeOnly || (entry.kind !== "import" && entry.kind !== "require") || seen.has(entry.source))
|
|
263
|
+
continue;
|
|
264
|
+
seen.add(entry.source);
|
|
265
|
+
let decision;
|
|
266
|
+
if (matches(options.keepReal, entry.source, entry.packageName)) {
|
|
267
|
+
decision = { mock: false, virtual: false, reason: "kept real with --keep-real" };
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
decision = await decide(entry.source, entry.category, entry.packageName, sourcePath, new Set([sourcePath]), 1);
|
|
271
|
+
if (!decision.mock && matches(options.mock, entry.source, entry.packageName)) {
|
|
272
|
+
const missing = entry.category === "external" ? !isInstalled(sourcePath, entry.source) : entry.category === "local" && !(await resolveLocal(sourcePath, entry.source));
|
|
273
|
+
decision = { mock: true, virtual: missing, reason: "mocked with --mock" };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (decision.mock) {
|
|
277
|
+
mocked.push({
|
|
278
|
+
source: entry.source,
|
|
279
|
+
specifier: specifierFromTest(entry.source, sourcePath, testPath),
|
|
280
|
+
category: classifySource(entry.source).category,
|
|
281
|
+
virtual: decision.virtual,
|
|
282
|
+
reason: decision.reason,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
real.push({ source: entry.source, reason: decision.reason });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const mockCalls = [];
|
|
290
|
+
const imports = [];
|
|
291
|
+
const handles = [];
|
|
292
|
+
const summary = [];
|
|
293
|
+
for (const module of mocked) {
|
|
294
|
+
const usages = analysis.importUsage.filter((u) => u.source === module.source);
|
|
295
|
+
const model = modelModule(usages);
|
|
296
|
+
const virtual = module.virtual ? ", { virtual: true }" : "";
|
|
297
|
+
mockCalls.push(`jest.mock(${JSON.stringify(module.specifier)}, ${factoryFor(model)}${virtual});`);
|
|
298
|
+
const visible = usages.filter((u) => u.local && !names.has(u.local));
|
|
299
|
+
const locals = [...new Map(visible.map((u) => [u.local, u])).values()];
|
|
300
|
+
for (const usage of locals) {
|
|
301
|
+
names.claim(usage.local);
|
|
302
|
+
imports.push(importStatement(usage, module.specifier, model, commonjs));
|
|
303
|
+
}
|
|
304
|
+
const description = { handles: [], lines: [], fns: [] };
|
|
305
|
+
for (const usage of locals) {
|
|
306
|
+
const shape = shapeFor(model, usage);
|
|
307
|
+
if (shape)
|
|
308
|
+
describeShape(shape, usage.local, names, description, usage.imported === "*" || usage.imported === "default");
|
|
309
|
+
}
|
|
310
|
+
handles.push(...description.handles.map((h) => `const ${h.name} = ${h.init};`));
|
|
311
|
+
const detail = module.virtual ? `${module.reason}; virtual mock` : module.reason;
|
|
312
|
+
summary.push(`- ${JSON.stringify(module.source)} (${detail}). Imported in the test file as: ${locals.map((u) => `\`${u.local}\``).join(", ") || "(nothing)"}`);
|
|
313
|
+
for (const line of description.lines)
|
|
314
|
+
summary.push(` - ${line}`);
|
|
315
|
+
if (description.fns.length)
|
|
316
|
+
summary.push(` - jest.fn() mocks: ${[...new Set(description.fns)].map((f) => `\`${f}\``).join(", ")}`);
|
|
317
|
+
}
|
|
318
|
+
// Pure built-ins such as `path` are also imported into the test, so expected values can be built portably.
|
|
319
|
+
const helpers = [];
|
|
320
|
+
for (const module of real) {
|
|
321
|
+
if (classifySource(module.source).category !== "builtin")
|
|
322
|
+
continue;
|
|
323
|
+
const usages = analysis.importUsage.filter((u) => u.source === module.source && u.local && !names.has(u.local));
|
|
324
|
+
const model = modelModule(usages);
|
|
325
|
+
for (const usage of [...new Map(usages.map((u) => [u.local, u])).values()]) {
|
|
326
|
+
names.claim(usage.local);
|
|
327
|
+
imports.push(importStatement(usage, module.source, model, commonjs));
|
|
328
|
+
helpers.push(`\`${usage.local}\` (${module.source})`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (helpers.length)
|
|
332
|
+
summary.push(`- Not mocked, and also imported in the test file for building expected values: ${helpers.join(", ")}`);
|
|
333
|
+
const otherReal = real.filter((r) => classifySource(r.source).category !== "builtin");
|
|
334
|
+
if (otherReal.length)
|
|
335
|
+
summary.push(`- Not mocked (real code runs): ${otherReal.map((r) => JSON.stringify(r.source)).join(", ")}`);
|
|
336
|
+
const globals = analysis.globals;
|
|
337
|
+
const globalLines = [];
|
|
338
|
+
for (const name of globals) {
|
|
339
|
+
const original = names.claim(`original${name[0].toUpperCase()}${name.slice(1)}`);
|
|
340
|
+
globalLines.push(`const ${original} = globalThis.${name};`, `globalThis.${name} = jest.fn()${typescript ? ` as unknown as typeof ${name}` : ""};`, `afterAll(() => {\n globalThis.${name} = ${original};\n});`);
|
|
341
|
+
summary.push(`- global \`${name}\` is a jest.fn() mock for every test.`);
|
|
342
|
+
}
|
|
343
|
+
const setup = [handles.join("\n"), globalLines.join("\n")].filter(Boolean);
|
|
344
|
+
if (mocked.length > 0 || globals.length > 0) {
|
|
345
|
+
setup.push("beforeEach(() => {\n jest.resetAllMocks();\n});", "afterEach(() => {\n jest.restoreAllMocks();\n});");
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
mocked,
|
|
349
|
+
real,
|
|
350
|
+
globals,
|
|
351
|
+
imports: [...mockCalls, ...(mockCalls.length ? [""] : []), ...imports],
|
|
352
|
+
setup,
|
|
353
|
+
summary: summary.join("\n"),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=mocks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mocks.js","sourceRoot":"","sources":["../../src/generator/mocks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const TEST_CATEGORIES: readonly ["normal", "edge", "error"];
|
|
3
|
+
export declare const GeneratedTestCaseSchema: z.ZodObject<{
|
|
4
|
+
name: z.ZodString;
|
|
5
|
+
category: z.ZodEnum<{
|
|
6
|
+
edge: "edge";
|
|
7
|
+
error: "error";
|
|
8
|
+
normal: "normal";
|
|
9
|
+
}>;
|
|
10
|
+
body: z.ZodString;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
export declare const GeneratedSuiteSchema: z.ZodObject<{
|
|
13
|
+
functionName: z.ZodString;
|
|
14
|
+
cases: z.ZodArray<z.ZodObject<{
|
|
15
|
+
name: z.ZodString;
|
|
16
|
+
category: z.ZodEnum<{
|
|
17
|
+
edge: "edge";
|
|
18
|
+
error: "error";
|
|
19
|
+
normal: "normal";
|
|
20
|
+
}>;
|
|
21
|
+
body: z.ZodString;
|
|
22
|
+
}, z.core.$strip>>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
export declare const GenerationSchema: z.ZodObject<{
|
|
25
|
+
suites: z.ZodArray<z.ZodObject<{
|
|
26
|
+
functionName: z.ZodString;
|
|
27
|
+
cases: z.ZodArray<z.ZodObject<{
|
|
28
|
+
name: z.ZodString;
|
|
29
|
+
category: z.ZodEnum<{
|
|
30
|
+
edge: "edge";
|
|
31
|
+
error: "error";
|
|
32
|
+
normal: "normal";
|
|
33
|
+
}>;
|
|
34
|
+
body: z.ZodString;
|
|
35
|
+
}, z.core.$strip>>;
|
|
36
|
+
}, z.core.$strip>>;
|
|
37
|
+
}, z.core.$strip>;
|
|
38
|
+
export type GeneratedTestCase = z.infer<typeof GeneratedTestCaseSchema>;
|
|
39
|
+
export type GeneratedSuite = z.infer<typeof GeneratedSuiteSchema>;
|
|
40
|
+
export type Generation = z.infer<typeof GenerationSchema>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const TEST_CATEGORIES = ["normal", "edge", "error"];
|
|
3
|
+
export const GeneratedTestCaseSchema = z.object({
|
|
4
|
+
name: z.string().describe("Short description of the behavior under test, used as the `it` title"),
|
|
5
|
+
category: z.enum(TEST_CATEGORIES).describe("normal: typical input; edge: boundaries and unusual input; error: throws or rejects"),
|
|
6
|
+
body: z.string().describe("Statements inside the test callback, without the it() wrapper"),
|
|
7
|
+
});
|
|
8
|
+
export const GeneratedSuiteSchema = z.object({
|
|
9
|
+
functionName: z.string().describe("Binding name of the function under test, exactly as listed in the prompt"),
|
|
10
|
+
cases: z.array(GeneratedTestCaseSchema),
|
|
11
|
+
});
|
|
12
|
+
export const GenerationSchema = z.object({
|
|
13
|
+
suites: z.array(GeneratedSuiteSchema),
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/generator/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAU,CAAC;AAEpE,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sEAAsE,CAAC;IACjG,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,QAAQ,CAAC,qFAAqF,CAAC;IACjI,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,+DAA+D,CAAC;CAC3F,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0EAA0E,CAAC;IAC7G,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,uBAAuB,CAAC;CACxC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,oBAAoB,CAAC;CACtC,CAAC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Budget } from "../ai/pricing.js";
|
|
2
|
+
import type { LLMProvider, TokenUsage } from "../ai/provider.js";
|
|
3
|
+
import { UserError } from "../errors.js";
|
|
4
|
+
import type { FileAnalysis, SourceFile } from "../types/index.js";
|
|
5
|
+
import { type MockPlan } from "./mocks.js";
|
|
6
|
+
import { type RejectedCase, type ValidatedSuite } from "./validate.js";
|
|
7
|
+
export declare class GenerationError extends UserError {
|
|
8
|
+
}
|
|
9
|
+
/** The next model call could exceed the spending limit, so it was not made. */
|
|
10
|
+
export declare class BudgetExceededError extends GenerationError {
|
|
11
|
+
}
|
|
12
|
+
export interface GenerationResult {
|
|
13
|
+
analysis: FileAnalysis;
|
|
14
|
+
testPath: string;
|
|
15
|
+
content: string;
|
|
16
|
+
/** Mocks, imports, and setup that precede the describe blocks. */
|
|
17
|
+
preamble: string[];
|
|
18
|
+
mocks: MockPlan;
|
|
19
|
+
suites: ValidatedSuite[];
|
|
20
|
+
/** The source as sent to the model, with secrets redacted. */
|
|
21
|
+
promptSource: string;
|
|
22
|
+
rejected: RejectedCase[];
|
|
23
|
+
model: string;
|
|
24
|
+
usage: TokenUsage;
|
|
25
|
+
redactions: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function generateTests(options: {
|
|
28
|
+
source: SourceFile;
|
|
29
|
+
provider: LLMProvider;
|
|
30
|
+
testPath?: string;
|
|
31
|
+
/** Modules to mock even if Assertloom would keep them real. */
|
|
32
|
+
mock?: string[];
|
|
33
|
+
/** Modules to keep real even if Assertloom would mock them. */
|
|
34
|
+
keepReal?: string[];
|
|
35
|
+
budget?: Budget;
|
|
36
|
+
}): Promise<GenerationResult>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { GENERATION_SYSTEM_PROMPT, buildGenerationPrompt } from "../ai/prompts.js";
|
|
4
|
+
import { formatUsd, promptTokens } from "../ai/pricing.js";
|
|
5
|
+
import { redactSecrets } from "../ai/redact.js";
|
|
6
|
+
import { analyzeSource } from "../analyzer/index.js";
|
|
7
|
+
import { UserError } from "../errors.js";
|
|
8
|
+
import { planImports } from "./importPlan.js";
|
|
9
|
+
import { planMocks } from "./mocks.js";
|
|
10
|
+
import { GenerationSchema } from "./schema.js";
|
|
11
|
+
import { previewPathFor, renderTestFile } from "./testWriter.js";
|
|
12
|
+
import { validateGeneration } from "./validate.js";
|
|
13
|
+
export class GenerationError extends UserError {
|
|
14
|
+
}
|
|
15
|
+
/** The next model call could exceed the spending limit, so it was not made. */
|
|
16
|
+
export class BudgetExceededError extends GenerationError {
|
|
17
|
+
}
|
|
18
|
+
const MAX_SOURCE_CHARS = 60_000;
|
|
19
|
+
const MAX_OUTPUT_TOKENS = 32_000;
|
|
20
|
+
export async function generateTests(options) {
|
|
21
|
+
const { source, provider } = options;
|
|
22
|
+
const testPath = options.testPath ?? previewPathFor(source.absolutePath);
|
|
23
|
+
const code = await readFile(source.absolutePath, "utf8");
|
|
24
|
+
const analysis = analyzeSource(code, source);
|
|
25
|
+
const targets = analysis.functions.filter((fn) => fn.exported);
|
|
26
|
+
if (targets.length === 0)
|
|
27
|
+
throw new GenerationError(`No exported functions in ${analysis.file}.`);
|
|
28
|
+
if (code.length > MAX_SOURCE_CHARS) {
|
|
29
|
+
throw new GenerationError(`${analysis.file} is ${code.length} characters; the limit for sending to a model is ${MAX_SOURCE_CHARS}.`);
|
|
30
|
+
}
|
|
31
|
+
const plan = planImports(analysis, targets, source.absolutePath, testPath);
|
|
32
|
+
const mocks = await planMocks({
|
|
33
|
+
analysis,
|
|
34
|
+
sourcePath: source.absolutePath,
|
|
35
|
+
testPath,
|
|
36
|
+
extension: source.extension,
|
|
37
|
+
reserved: plan.bindings.map((b) => b.binding),
|
|
38
|
+
mock: options.mock,
|
|
39
|
+
keepReal: options.keepReal,
|
|
40
|
+
});
|
|
41
|
+
const redacted = redactSecrets(code);
|
|
42
|
+
const prompt = buildGenerationPrompt({
|
|
43
|
+
analysis,
|
|
44
|
+
code: redacted.code,
|
|
45
|
+
bindings: plan.bindings,
|
|
46
|
+
testFileName: path.basename(testPath),
|
|
47
|
+
mockSummary: mocks.summary,
|
|
48
|
+
});
|
|
49
|
+
const { budget } = options;
|
|
50
|
+
if (budget && !budget.allows({ inputTokens: promptTokens(GENERATION_SYSTEM_PROMPT, prompt), outputTokens: MAX_OUTPUT_TOKENS })) {
|
|
51
|
+
throw new BudgetExceededError(budget.priced
|
|
52
|
+
? `Generating tests for ${analysis.file} could exceed the spending limit of ${formatUsd(budget.maxUsd)}; raise it with --max-cost.`
|
|
53
|
+
: `Generating tests for ${analysis.file} could exceed the spending limit of ${budget.describeLimit()}.`);
|
|
54
|
+
}
|
|
55
|
+
const response = await provider.generateStructured({
|
|
56
|
+
system: GENERATION_SYSTEM_PROMPT,
|
|
57
|
+
prompt,
|
|
58
|
+
schema: GenerationSchema,
|
|
59
|
+
schemaName: "assertloom_tests",
|
|
60
|
+
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
61
|
+
});
|
|
62
|
+
budget?.add(response.usage);
|
|
63
|
+
const { suites, rejected } = validateGeneration(response.data, plan.bindings, source.extension);
|
|
64
|
+
if (suites.length === 0) {
|
|
65
|
+
throw new GenerationError(`The model returned no usable test cases (${rejected.length} rejected).`);
|
|
66
|
+
}
|
|
67
|
+
const preamble = [...mocks.imports, ...plan.statements];
|
|
68
|
+
if (mocks.setup.length)
|
|
69
|
+
preamble.push("", mocks.setup.join("\n\n"));
|
|
70
|
+
const content = renderTestFile({ sourceFile: analysis.file, preamble, suites, extension: source.extension });
|
|
71
|
+
return {
|
|
72
|
+
analysis,
|
|
73
|
+
testPath,
|
|
74
|
+
content,
|
|
75
|
+
preamble,
|
|
76
|
+
mocks,
|
|
77
|
+
suites,
|
|
78
|
+
promptSource: redacted.code,
|
|
79
|
+
rejected,
|
|
80
|
+
model: response.model,
|
|
81
|
+
usage: response.usage,
|
|
82
|
+
redactions: redacted.redactions,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=testGenerator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testGenerator.js","sourceRoot":"","sources":["../../src/generator/testGenerator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { UserError } from "../errors.js";
|
|
2
|
+
import type { SupportedExtension } from "../types/index.js";
|
|
3
|
+
import type { ValidatedSuite } from "./validate.js";
|
|
4
|
+
export declare const GENERATED_HEADER = "// Generated by Assertloom.";
|
|
5
|
+
export declare class TestFileExistsError extends UserError {
|
|
6
|
+
}
|
|
7
|
+
/** `src/math.ts` → `src/math.test.preview.ts`. Jest's default pattern ignores it until it has been run. */
|
|
8
|
+
export declare function previewPathFor(sourcePath: string): string;
|
|
9
|
+
/** `src/math.ts` → `src/math.test.ts`. */
|
|
10
|
+
export declare function testPathFor(sourcePath: string): string;
|
|
11
|
+
export declare function renderTestFile(options: {
|
|
12
|
+
sourceFile: string;
|
|
13
|
+
/** Mocks, imports, and setup that precede the describe blocks. */
|
|
14
|
+
preamble: string[];
|
|
15
|
+
suites: ValidatedSuite[];
|
|
16
|
+
extension: SupportedExtension;
|
|
17
|
+
}): string;
|
|
18
|
+
export declare function writeTestFile(filePath: string, content: string, force?: boolean,
|
|
19
|
+
/** Called with the changes before an existing file with different content is overwritten. */
|
|
20
|
+
onOverwrite?: (diff: string[]) => void): Promise<{
|
|
21
|
+
overwritten: boolean;
|
|
22
|
+
}>;
|