agentic-qe 3.6.14 → 3.6.16
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/.claude/skills/skills-manifest.json +1 -1
- package/package.json +3 -2
- package/v3/CHANGELOG.md +42 -0
- package/v3/dist/cli/bundle.js +911 -354
- package/v3/dist/cli/commands/test.d.ts.map +1 -1
- package/v3/dist/cli/commands/test.js +6 -3
- package/v3/dist/cli/commands/test.js.map +1 -1
- package/v3/dist/cli/completions/index.d.ts +1 -1
- package/v3/dist/cli/completions/index.d.ts.map +1 -1
- package/v3/dist/cli/completions/index.js +1 -1
- package/v3/dist/cli/completions/index.js.map +1 -1
- package/v3/dist/cli/wizards/test-wizard.d.ts +1 -1
- package/v3/dist/cli/wizards/test-wizard.d.ts.map +1 -1
- package/v3/dist/cli/wizards/test-wizard.js +8 -1
- package/v3/dist/cli/wizards/test-wizard.js.map +1 -1
- package/v3/dist/coordination/task-executor.js.map +1 -1
- package/v3/dist/domains/test-generation/factories/test-generator-factory.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/factories/test-generator-factory.js +4 -1
- package/v3/dist/domains/test-generation/factories/test-generator-factory.js.map +1 -1
- package/v3/dist/domains/test-generation/generators/base-test-generator.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/generators/base-test-generator.js +74 -27
- package/v3/dist/domains/test-generation/generators/base-test-generator.js.map +1 -1
- package/v3/dist/domains/test-generation/generators/index.d.ts +1 -0
- package/v3/dist/domains/test-generation/generators/index.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/generators/index.js +1 -0
- package/v3/dist/domains/test-generation/generators/index.js.map +1 -1
- package/v3/dist/domains/test-generation/generators/jest-vitest-generator.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/generators/jest-vitest-generator.js +77 -21
- package/v3/dist/domains/test-generation/generators/jest-vitest-generator.js.map +1 -1
- package/v3/dist/domains/test-generation/generators/mocha-generator.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/generators/mocha-generator.js +92 -17
- package/v3/dist/domains/test-generation/generators/mocha-generator.js.map +1 -1
- package/v3/dist/domains/test-generation/generators/node-test-generator.d.ts +54 -0
- package/v3/dist/domains/test-generation/generators/node-test-generator.d.ts.map +1 -0
- package/v3/dist/domains/test-generation/generators/node-test-generator.js +222 -0
- package/v3/dist/domains/test-generation/generators/node-test-generator.js.map +1 -0
- package/v3/dist/domains/test-generation/generators/pytest-generator.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/generators/pytest-generator.js +74 -15
- package/v3/dist/domains/test-generation/generators/pytest-generator.js.map +1 -1
- package/v3/dist/domains/test-generation/interfaces/test-generator.interface.d.ts +1 -1
- package/v3/dist/domains/test-generation/interfaces/test-generator.interface.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/interfaces.d.ts +2 -2
- package/v3/dist/domains/test-generation/interfaces.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/plugin.js.map +1 -1
- package/v3/dist/domains/test-generation/services/pattern-matcher.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/services/pattern-matcher.js +34 -7
- package/v3/dist/domains/test-generation/services/pattern-matcher.js.map +1 -1
- package/v3/dist/domains/test-generation/services/test-generator.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/services/test-generator.js +42 -9
- package/v3/dist/domains/test-generation/services/test-generator.js.map +1 -1
- package/v3/dist/mcp/bundle.js +550 -78
- package/v3/dist/mcp/tools/test-generation/generate.d.ts +1 -1
- package/v3/dist/mcp/tools/test-generation/generate.d.ts.map +1 -1
- package/v3/dist/mcp/tools/test-generation/generate.js.map +1 -1
- package/v3/dist/shared/sql-safety.d.ts +3 -0
- package/v3/dist/shared/sql-safety.d.ts.map +1 -1
- package/v3/dist/shared/sql-safety.js +12 -2
- package/v3/dist/shared/sql-safety.js.map +1 -1
- package/v3/dist/sync/cloud/postgres-writer.d.ts +6 -1
- package/v3/dist/sync/cloud/postgres-writer.d.ts.map +1 -1
- package/v3/dist/sync/cloud/postgres-writer.js +121 -46
- package/v3/dist/sync/cloud/postgres-writer.js.map +1 -1
- package/v3/dist/sync/interfaces.d.ts.map +1 -1
- package/v3/dist/sync/interfaces.js +97 -32
- package/v3/dist/sync/interfaces.js.map +1 -1
- package/v3/dist/sync/sync-agent.d.ts.map +1 -1
- package/v3/dist/sync/sync-agent.js +8 -24
- package/v3/dist/sync/sync-agent.js.map +1 -1
- package/v3/package.json +1 -1
package/v3/dist/mcp/bundle.js
CHANGED
|
@@ -38669,7 +38669,7 @@ var BaseTestGenerator = class _BaseTestGenerator {
|
|
|
38669
38669
|
for (const [typeKey, generator] of _BaseTestGenerator.TYPE_VALUE_TABLE) {
|
|
38670
38670
|
if (type.includes(typeKey)) return generator();
|
|
38671
38671
|
}
|
|
38672
|
-
return `
|
|
38672
|
+
return `{} /* TODO: provide ${param.name}: ${param.type || "unknown"} */`;
|
|
38673
38673
|
}
|
|
38674
38674
|
/**
|
|
38675
38675
|
* Generate test cases for a function based on its signature
|
|
@@ -38682,21 +38682,62 @@ var BaseTestGenerator = class _BaseTestGenerator {
|
|
|
38682
38682
|
const testCases = [];
|
|
38683
38683
|
const validParams = fn.parameters.map((p74) => this.generateTestValue(p74)).join(", ");
|
|
38684
38684
|
const fnCall = fn.isAsync ? `await ${fn.name}(${validParams})` : `${fn.name}(${validParams})`;
|
|
38685
|
+
const isVoid = fn.returnType === "void" || fn.returnType === "Promise<void>";
|
|
38686
|
+
let assertion = "expect(result).toBeDefined();";
|
|
38687
|
+
if (!isVoid) {
|
|
38688
|
+
if (/^(is|has|can)[A-Z]/.test(fn.name)) {
|
|
38689
|
+
assertion = "expect(typeof result).toBe('boolean');";
|
|
38690
|
+
} else if (/^(get|fetch|find)[A-Z]/.test(fn.name)) {
|
|
38691
|
+
assertion = "expect(result).not.toBeUndefined();";
|
|
38692
|
+
} else if (/^(create|build|make)[A-Z]/.test(fn.name)) {
|
|
38693
|
+
assertion = "expect(result).toBeTruthy();";
|
|
38694
|
+
} else if (fn.returnType) {
|
|
38695
|
+
const rt3 = fn.returnType.toLowerCase().replace(/promise<(.+)>/, "$1");
|
|
38696
|
+
if (rt3.includes("boolean")) assertion = "expect(typeof result).toBe('boolean');";
|
|
38697
|
+
else if (rt3.includes("number")) assertion = "expect(typeof result).toBe('number');";
|
|
38698
|
+
else if (rt3.includes("string")) assertion = "expect(typeof result).toBe('string');";
|
|
38699
|
+
else if (rt3.includes("[]") || rt3.includes("array")) assertion = "expect(Array.isArray(result)).toBe(true);";
|
|
38700
|
+
}
|
|
38701
|
+
}
|
|
38685
38702
|
testCases.push({
|
|
38686
38703
|
description: "should handle valid input correctly",
|
|
38687
38704
|
type: "happy-path",
|
|
38688
|
-
action: `const result = ${fnCall};`,
|
|
38689
|
-
assertion:
|
|
38705
|
+
action: isVoid ? `${fnCall};` : `const result = ${fnCall};`,
|
|
38706
|
+
assertion: isVoid ? `// void function \u2014 no return value to assert` : assertion
|
|
38690
38707
|
});
|
|
38708
|
+
const bodyText = fn.body || "";
|
|
38709
|
+
const hasExplicitThrow = /\bthrow\b/.test(bodyText) || /\bvalidat/i.test(bodyText);
|
|
38691
38710
|
for (const param of fn.parameters) {
|
|
38692
38711
|
if (!param.optional) {
|
|
38693
38712
|
const paramsWithUndefined = fn.parameters.map((p74) => p74.name === param.name ? "undefined" : this.generateTestValue(p74)).join(", ");
|
|
38694
|
-
|
|
38695
|
-
|
|
38696
|
-
|
|
38697
|
-
|
|
38698
|
-
|
|
38699
|
-
|
|
38713
|
+
if (hasExplicitThrow) {
|
|
38714
|
+
testCases.push({
|
|
38715
|
+
description: `should handle undefined ${param.name}`,
|
|
38716
|
+
type: "error-handling",
|
|
38717
|
+
action: fn.isAsync ? `const action = async () => await ${fn.name}(${paramsWithUndefined});` : `const action = () => ${fn.name}(${paramsWithUndefined});`,
|
|
38718
|
+
assertion: "expect(action).toThrow();"
|
|
38719
|
+
});
|
|
38720
|
+
} else {
|
|
38721
|
+
const undefinedCall = fn.isAsync ? `await ${fn.name}(${paramsWithUndefined})` : `${fn.name}(${paramsWithUndefined})`;
|
|
38722
|
+
testCases.push({
|
|
38723
|
+
description: `should handle undefined ${param.name}`,
|
|
38724
|
+
type: "edge-case",
|
|
38725
|
+
action: fn.isAsync ? `let threw = false;
|
|
38726
|
+
try {
|
|
38727
|
+
await ${fn.name}(${paramsWithUndefined});
|
|
38728
|
+
} catch (e) {
|
|
38729
|
+
threw = true;
|
|
38730
|
+
expect(e).toBeInstanceOf(Error);
|
|
38731
|
+
}` : `let threw = false;
|
|
38732
|
+
try {
|
|
38733
|
+
${fn.name}(${paramsWithUndefined});
|
|
38734
|
+
} catch (e) {
|
|
38735
|
+
threw = true;
|
|
38736
|
+
expect(e).toBeInstanceOf(Error);
|
|
38737
|
+
}`,
|
|
38738
|
+
assertion: `expect(true).toBe(true); // function either handles undefined or throws TypeError`
|
|
38739
|
+
});
|
|
38740
|
+
}
|
|
38700
38741
|
}
|
|
38701
38742
|
testCases.push(...this.generateBoundaryTestCases(fn, param));
|
|
38702
38743
|
}
|
|
@@ -38716,32 +38757,48 @@ var BaseTestGenerator = class _BaseTestGenerator {
|
|
|
38716
38757
|
generateBoundaryTestCases(fn, param) {
|
|
38717
38758
|
const testCases = [];
|
|
38718
38759
|
const type = param.type?.toLowerCase() || "";
|
|
38760
|
+
const wrapBoundaryAction = (call, isAsync) => {
|
|
38761
|
+
if (isAsync) {
|
|
38762
|
+
return `try {
|
|
38763
|
+
const result = await ${call};
|
|
38764
|
+
expect(result).toBeDefined();
|
|
38765
|
+
} catch (e) {
|
|
38766
|
+
expect(e).toBeInstanceOf(Error);
|
|
38767
|
+
}`;
|
|
38768
|
+
}
|
|
38769
|
+
return `try {
|
|
38770
|
+
const result = ${call};
|
|
38771
|
+
expect(result).toBeDefined();
|
|
38772
|
+
} catch (e) {
|
|
38773
|
+
expect(e).toBeInstanceOf(Error);
|
|
38774
|
+
}`;
|
|
38775
|
+
};
|
|
38719
38776
|
if (type.includes("string")) {
|
|
38720
38777
|
const paramsWithEmpty = fn.parameters.map((p74) => p74.name === param.name ? "''" : this.generateTestValue(p74)).join(", ");
|
|
38721
|
-
const emptyCall =
|
|
38778
|
+
const emptyCall = `${fn.name}(${paramsWithEmpty})`;
|
|
38722
38779
|
testCases.push({
|
|
38723
38780
|
description: `should handle empty string for ${param.name}`,
|
|
38724
38781
|
type: "boundary",
|
|
38725
|
-
action:
|
|
38726
|
-
assertion: "
|
|
38782
|
+
action: wrapBoundaryAction(emptyCall, fn.isAsync),
|
|
38783
|
+
assertion: ""
|
|
38727
38784
|
});
|
|
38728
38785
|
}
|
|
38729
38786
|
if (type.includes("number")) {
|
|
38730
38787
|
const paramsWithZero = fn.parameters.map((p74) => p74.name === param.name ? "0" : this.generateTestValue(p74)).join(", ");
|
|
38731
|
-
const zeroCall =
|
|
38788
|
+
const zeroCall = `${fn.name}(${paramsWithZero})`;
|
|
38732
38789
|
testCases.push({
|
|
38733
38790
|
description: `should handle zero for ${param.name}`,
|
|
38734
38791
|
type: "boundary",
|
|
38735
|
-
action:
|
|
38736
|
-
assertion: "
|
|
38792
|
+
action: wrapBoundaryAction(zeroCall, fn.isAsync),
|
|
38793
|
+
assertion: ""
|
|
38737
38794
|
});
|
|
38738
38795
|
const paramsWithNegative = fn.parameters.map((p74) => p74.name === param.name ? "-1" : this.generateTestValue(p74)).join(", ");
|
|
38739
|
-
const negativeCall =
|
|
38796
|
+
const negativeCall = `${fn.name}(${paramsWithNegative})`;
|
|
38740
38797
|
testCases.push({
|
|
38741
38798
|
description: `should handle negative value for ${param.name}`,
|
|
38742
38799
|
type: "edge-case",
|
|
38743
|
-
action:
|
|
38744
|
-
assertion: "
|
|
38800
|
+
action: wrapBoundaryAction(negativeCall, fn.isAsync),
|
|
38801
|
+
assertion: ""
|
|
38745
38802
|
});
|
|
38746
38803
|
}
|
|
38747
38804
|
if (type.includes("[]") || type.includes("array")) {
|
|
@@ -38839,24 +38896,31 @@ ${importStatement}
|
|
|
38839
38896
|
`;
|
|
38840
38897
|
if (dependencies && dependencies.imports.length > 0) {
|
|
38841
38898
|
const mockFn = this.framework === "vitest" ? "vi.fn()" : "jest.fn()";
|
|
38842
|
-
|
|
38899
|
+
const externalDeps = dependencies.imports.filter((dep) => !dep.startsWith("."));
|
|
38900
|
+
if (externalDeps.length > 0) {
|
|
38901
|
+
testCode += `
|
|
38843
38902
|
// Auto-generated mocks from Knowledge Graph dependency analysis
|
|
38844
38903
|
`;
|
|
38845
|
-
|
|
38846
|
-
|
|
38847
|
-
testCode += `${this.framework === "vitest" ? "vi" : "jest"}.mock('${dep}', () => ({ default: ${mockFn} }));
|
|
38904
|
+
for (const dep of externalDeps.slice(0, 10)) {
|
|
38905
|
+
testCode += `${this.framework === "vitest" ? "vi" : "jest"}.mock('${dep}', () => ({ default: ${mockFn} }));
|
|
38848
38906
|
`;
|
|
38849
|
-
|
|
38850
|
-
|
|
38907
|
+
}
|
|
38908
|
+
testCode += `
|
|
38851
38909
|
`;
|
|
38910
|
+
} else {
|
|
38911
|
+
testCode += `
|
|
38912
|
+
`;
|
|
38913
|
+
}
|
|
38852
38914
|
} else {
|
|
38853
38915
|
testCode += `
|
|
38854
38916
|
`;
|
|
38855
38917
|
}
|
|
38856
|
-
|
|
38918
|
+
const exportedFns = analysis.functions.filter((fn) => fn.isExported);
|
|
38919
|
+
const exportedClasses = analysis.classes.filter((cls) => cls.isExported);
|
|
38920
|
+
for (const fn of exportedFns) {
|
|
38857
38921
|
testCode += this.generateFunctionTests(fn, testType);
|
|
38858
38922
|
}
|
|
38859
|
-
for (const cls of
|
|
38923
|
+
for (const cls of exportedClasses) {
|
|
38860
38924
|
testCode += this.generateClassTests(cls, testType);
|
|
38861
38925
|
}
|
|
38862
38926
|
return testCode;
|
|
@@ -38943,24 +39007,68 @@ ${importStatement}
|
|
|
38943
39007
|
const validParams = method.parameters.map((p74) => this.generateTestValue(p74)).join(", ");
|
|
38944
39008
|
const methodCall = method.isAsync ? `await instance.${method.name}(${validParams})` : `instance.${method.name}(${validParams})`;
|
|
38945
39009
|
const asyncPrefix = method.isAsync ? "async " : "";
|
|
39010
|
+
const isVoid = method.returnType === "void" || method.returnType === "Promise<void>";
|
|
39011
|
+
let methodAssertion = "expect(result).toBeDefined();";
|
|
39012
|
+
if (!isVoid) {
|
|
39013
|
+
if (/^(is|has|can)[A-Z]/.test(method.name)) {
|
|
39014
|
+
methodAssertion = "expect(typeof result).toBe('boolean');";
|
|
39015
|
+
} else if (/^(get|fetch|find)[A-Z]/.test(method.name)) {
|
|
39016
|
+
methodAssertion = "expect(result).not.toBeUndefined();";
|
|
39017
|
+
} else if (/^(create|build|make)[A-Z]/.test(method.name)) {
|
|
39018
|
+
methodAssertion = "expect(result).toBeTruthy();";
|
|
39019
|
+
} else if (method.returnType) {
|
|
39020
|
+
const mrt = method.returnType.toLowerCase().replace(/promise<(.+)>/, "$1");
|
|
39021
|
+
if (mrt.includes("boolean")) methodAssertion = "expect(typeof result).toBe('boolean');";
|
|
39022
|
+
else if (mrt.includes("number")) methodAssertion = "expect(typeof result).toBe('number');";
|
|
39023
|
+
else if (mrt.includes("string")) methodAssertion = "expect(typeof result).toBe('string');";
|
|
39024
|
+
else if (mrt.includes("[]") || mrt.includes("array")) methodAssertion = "expect(Array.isArray(result)).toBe(true);";
|
|
39025
|
+
}
|
|
39026
|
+
}
|
|
38946
39027
|
code += ` it('should execute successfully', ${asyncPrefix}() => {
|
|
38947
39028
|
`;
|
|
38948
|
-
|
|
39029
|
+
if (isVoid) {
|
|
39030
|
+
code += ` ${methodCall};
|
|
39031
|
+
`;
|
|
39032
|
+
code += ` // void return \u2014 no assertion on result needed
|
|
39033
|
+
`;
|
|
39034
|
+
} else {
|
|
39035
|
+
code += ` const result = ${methodCall};
|
|
38949
39036
|
`;
|
|
38950
|
-
|
|
39037
|
+
code += ` ${methodAssertion}
|
|
38951
39038
|
`;
|
|
39039
|
+
}
|
|
38952
39040
|
code += ` });
|
|
38953
39041
|
`;
|
|
39042
|
+
const methodBody = method.body || "";
|
|
39043
|
+
const methodThrows = /\bthrow\b/.test(methodBody) || /\bvalidat/i.test(methodBody);
|
|
38954
39044
|
for (const param of method.parameters) {
|
|
38955
39045
|
if (!param.optional) {
|
|
38956
39046
|
const paramsWithUndefined = method.parameters.map((p74) => p74.name === param.name ? "undefined as any" : this.generateTestValue(p74)).join(", ");
|
|
38957
|
-
|
|
38958
|
-
|
|
39047
|
+
if (methodThrows) {
|
|
39048
|
+
code += `
|
|
39049
|
+
it('should handle invalid ${param.name}', ${asyncPrefix}() => {
|
|
38959
39050
|
`;
|
|
38960
|
-
|
|
39051
|
+
code += ` expect(() => instance.${method.name}(${paramsWithUndefined})).toThrow();
|
|
38961
39052
|
`;
|
|
38962
|
-
|
|
39053
|
+
code += ` });
|
|
39054
|
+
`;
|
|
39055
|
+
} else {
|
|
39056
|
+
code += `
|
|
39057
|
+
it('should handle undefined ${param.name}', ${asyncPrefix}() => {
|
|
39058
|
+
`;
|
|
39059
|
+
code += ` try {
|
|
39060
|
+
`;
|
|
39061
|
+
code += ` ${method.isAsync ? "await " : ""}instance.${method.name}(${paramsWithUndefined});
|
|
39062
|
+
`;
|
|
39063
|
+
code += ` } catch (e) {
|
|
38963
39064
|
`;
|
|
39065
|
+
code += ` expect(e).toBeInstanceOf(Error);
|
|
39066
|
+
`;
|
|
39067
|
+
code += ` }
|
|
39068
|
+
`;
|
|
39069
|
+
code += ` });
|
|
39070
|
+
`;
|
|
39071
|
+
}
|
|
38964
39072
|
}
|
|
38965
39073
|
}
|
|
38966
39074
|
code += ` });
|
|
@@ -38980,12 +39088,15 @@ ${importStatement}
|
|
|
38980
39088
|
let mockDeclarations = "";
|
|
38981
39089
|
if (dependencies && dependencies.imports.length > 0) {
|
|
38982
39090
|
const mockFn = this.framework === "vitest" ? "vi.fn()" : "jest.fn()";
|
|
38983
|
-
|
|
39091
|
+
const externalDeps = dependencies.imports.filter((dep) => !dep.startsWith("."));
|
|
39092
|
+
if (externalDeps.length > 0) {
|
|
39093
|
+
mockDeclarations += `
|
|
38984
39094
|
// Auto-generated mocks from Knowledge Graph dependency analysis
|
|
38985
39095
|
`;
|
|
38986
|
-
|
|
38987
|
-
|
|
39096
|
+
for (const dep of externalDeps.slice(0, 10)) {
|
|
39097
|
+
mockDeclarations += `${this.mockUtil}.mock('${dep}', () => ({ default: ${mockFn} }));
|
|
38988
39098
|
`;
|
|
39099
|
+
}
|
|
38989
39100
|
}
|
|
38990
39101
|
}
|
|
38991
39102
|
let similarityComment = "";
|
|
@@ -39262,10 +39373,11 @@ var MochaGenerator = class extends BaseTestGenerator {
|
|
|
39262
39373
|
const importStatement = this.generateImportStatement(exports, importPath, moduleName);
|
|
39263
39374
|
let sinonImport = "";
|
|
39264
39375
|
let stubSetup = "";
|
|
39265
|
-
|
|
39376
|
+
const externalDeps = dependencies?.imports.filter((dep) => !dep.startsWith(".")) || [];
|
|
39377
|
+
if (externalDeps.length > 0) {
|
|
39266
39378
|
sinonImport = `import sinon from 'sinon';
|
|
39267
39379
|
`;
|
|
39268
|
-
const depsToMock =
|
|
39380
|
+
const depsToMock = externalDeps.slice(0, 5);
|
|
39269
39381
|
stubSetup += ` // Auto-generated stubs from Knowledge Graph dependency analysis
|
|
39270
39382
|
`;
|
|
39271
39383
|
stubSetup += ` let stubs;
|
|
@@ -39298,10 +39410,12 @@ ${sinonImport}${importStatement}
|
|
|
39298
39410
|
|
|
39299
39411
|
describe('${moduleName} - ${testType} tests', function() {
|
|
39300
39412
|
${stubSetup}`;
|
|
39301
|
-
|
|
39413
|
+
const exportedFns = analysis.functions.filter((fn) => fn.isExported);
|
|
39414
|
+
const exportedClasses = analysis.classes.filter((cls) => cls.isExported);
|
|
39415
|
+
for (const fn of exportedFns) {
|
|
39302
39416
|
code += this.generateFunctionTests(fn, testType);
|
|
39303
39417
|
}
|
|
39304
|
-
for (const cls of
|
|
39418
|
+
for (const cls of exportedClasses) {
|
|
39305
39419
|
code += this.generateClassTests(cls, testType);
|
|
39306
39420
|
}
|
|
39307
39421
|
code += `});
|
|
@@ -39314,26 +39428,68 @@ ${stubSetup}`;
|
|
|
39314
39428
|
generateFunctionTests(fn, _testType) {
|
|
39315
39429
|
const validParams = fn.parameters.map((p74) => this.generateTestValue(p74)).join(", ");
|
|
39316
39430
|
const fnCall = fn.isAsync ? `await ${fn.name}(${validParams})` : `${fn.name}(${validParams})`;
|
|
39431
|
+
const isVoid = fn.returnType === "void" || fn.returnType === "Promise<void>";
|
|
39317
39432
|
let code = ` describe('${fn.name}', function() {
|
|
39318
39433
|
`;
|
|
39434
|
+
let chaiAssertion = "expect(result).to.not.be.undefined;";
|
|
39435
|
+
if (!isVoid) {
|
|
39436
|
+
if (/^(is|has|can)[A-Z]/.test(fn.name)) {
|
|
39437
|
+
chaiAssertion = "expect(typeof result).to.equal('boolean');";
|
|
39438
|
+
} else if (/^(get|fetch|find)[A-Z]/.test(fn.name)) {
|
|
39439
|
+
chaiAssertion = "expect(result).to.not.be.undefined;";
|
|
39440
|
+
} else if (/^(create|build|make)[A-Z]/.test(fn.name)) {
|
|
39441
|
+
chaiAssertion = "expect(result).to.be.ok;";
|
|
39442
|
+
} else if (fn.returnType) {
|
|
39443
|
+
const rt3 = fn.returnType.toLowerCase().replace(/promise<(.+)>/, "$1");
|
|
39444
|
+
if (rt3.includes("boolean")) chaiAssertion = "expect(typeof result).to.equal('boolean');";
|
|
39445
|
+
else if (rt3.includes("number")) chaiAssertion = "expect(typeof result).to.equal('number');";
|
|
39446
|
+
else if (rt3.includes("string")) chaiAssertion = "expect(typeof result).to.equal('string');";
|
|
39447
|
+
else if (rt3.includes("[]") || rt3.includes("array")) chaiAssertion = "expect(result).to.be.an('array');";
|
|
39448
|
+
}
|
|
39449
|
+
}
|
|
39319
39450
|
code += ` it('should handle valid input', ${fn.isAsync ? "async " : ""}function() {
|
|
39320
39451
|
`;
|
|
39321
|
-
|
|
39452
|
+
if (isVoid) {
|
|
39453
|
+
code += ` ${fnCall};
|
|
39454
|
+
`;
|
|
39455
|
+
} else {
|
|
39456
|
+
code += ` const result = ${fnCall};
|
|
39322
39457
|
`;
|
|
39323
|
-
|
|
39458
|
+
code += ` ${chaiAssertion}
|
|
39324
39459
|
`;
|
|
39460
|
+
}
|
|
39325
39461
|
code += ` });
|
|
39326
39462
|
`;
|
|
39463
|
+
const bodyText = fn.body || "";
|
|
39464
|
+
const hasExplicitThrow = /\bthrow\b/.test(bodyText) || /\bvalidat/i.test(bodyText);
|
|
39327
39465
|
for (const param of fn.parameters) {
|
|
39328
39466
|
if (!param.optional) {
|
|
39329
39467
|
const paramsWithUndefined = fn.parameters.map((p74) => p74.name === param.name ? "undefined" : this.generateTestValue(p74)).join(", ");
|
|
39330
|
-
|
|
39468
|
+
if (hasExplicitThrow) {
|
|
39469
|
+
code += `
|
|
39331
39470
|
it('should handle undefined ${param.name}', function() {
|
|
39332
39471
|
`;
|
|
39333
|
-
|
|
39472
|
+
code += ` expect(function() { ${fn.name}(${paramsWithUndefined}); }).to.throw();
|
|
39334
39473
|
`;
|
|
39335
|
-
|
|
39474
|
+
code += ` });
|
|
39475
|
+
`;
|
|
39476
|
+
} else {
|
|
39477
|
+
code += `
|
|
39478
|
+
it('should handle undefined ${param.name}', function() {
|
|
39479
|
+
`;
|
|
39480
|
+
code += ` try {
|
|
39481
|
+
`;
|
|
39482
|
+
code += ` ${fn.name}(${paramsWithUndefined});
|
|
39483
|
+
`;
|
|
39484
|
+
code += ` } catch (e) {
|
|
39485
|
+
`;
|
|
39486
|
+
code += ` expect(e).to.be.instanceOf(Error);
|
|
39487
|
+
`;
|
|
39488
|
+
code += ` }
|
|
39489
|
+
`;
|
|
39490
|
+
code += ` });
|
|
39336
39491
|
`;
|
|
39492
|
+
}
|
|
39337
39493
|
}
|
|
39338
39494
|
}
|
|
39339
39495
|
code += ` });
|
|
@@ -39365,15 +39521,35 @@ ${stubSetup}`;
|
|
|
39365
39521
|
code += ` });
|
|
39366
39522
|
`;
|
|
39367
39523
|
for (const method of cls.methods) {
|
|
39368
|
-
if (!method.name.startsWith("_")) {
|
|
39524
|
+
if (!method.name.startsWith("_") && !method.name.startsWith("#")) {
|
|
39369
39525
|
const methodParams = method.parameters.map((p74) => this.generateTestValue(p74)).join(", ");
|
|
39526
|
+
const isMethodVoid = method.returnType === "void" || method.returnType === "Promise<void>";
|
|
39527
|
+
let methodAssertion = "expect(result).to.not.be.undefined;";
|
|
39528
|
+
if (!isMethodVoid) {
|
|
39529
|
+
if (/^(is|has|can)[A-Z]/.test(method.name)) {
|
|
39530
|
+
methodAssertion = "expect(typeof result).to.equal('boolean');";
|
|
39531
|
+
} else if (/^(create|build|make)[A-Z]/.test(method.name)) {
|
|
39532
|
+
methodAssertion = "expect(result).to.be.ok;";
|
|
39533
|
+
} else if (method.returnType) {
|
|
39534
|
+
const mrt = method.returnType.toLowerCase().replace(/promise<(.+)>/, "$1");
|
|
39535
|
+
if (mrt.includes("boolean")) methodAssertion = "expect(typeof result).to.equal('boolean');";
|
|
39536
|
+
else if (mrt.includes("number")) methodAssertion = "expect(typeof result).to.equal('number');";
|
|
39537
|
+
else if (mrt.includes("string")) methodAssertion = "expect(typeof result).to.equal('string');";
|
|
39538
|
+
else if (mrt.includes("[]") || mrt.includes("array")) methodAssertion = "expect(result).to.be.an('array');";
|
|
39539
|
+
}
|
|
39540
|
+
}
|
|
39370
39541
|
code += `
|
|
39371
39542
|
it('${method.name} should work', ${method.isAsync ? "async " : ""}function() {
|
|
39372
39543
|
`;
|
|
39373
|
-
|
|
39544
|
+
if (isMethodVoid) {
|
|
39545
|
+
code += ` ${method.isAsync ? "await " : ""}instance.${method.name}(${methodParams});
|
|
39374
39546
|
`;
|
|
39375
|
-
|
|
39547
|
+
} else {
|
|
39548
|
+
code += ` const result = ${method.isAsync ? "await " : ""}instance.${method.name}(${methodParams});
|
|
39549
|
+
`;
|
|
39550
|
+
code += ` ${methodAssertion}
|
|
39376
39551
|
`;
|
|
39552
|
+
}
|
|
39377
39553
|
code += ` });
|
|
39378
39554
|
`;
|
|
39379
39555
|
}
|
|
@@ -39396,10 +39572,11 @@ ${stubSetup}`;
|
|
|
39396
39572
|
let sinonImport = "";
|
|
39397
39573
|
let stubSetup = "";
|
|
39398
39574
|
let stubTeardown = "";
|
|
39399
|
-
|
|
39575
|
+
const stubExternalDeps = dependencies?.imports.filter((dep) => !dep.startsWith(".")) || [];
|
|
39576
|
+
if (stubExternalDeps.length > 0) {
|
|
39400
39577
|
sinonImport = `import sinon from 'sinon';
|
|
39401
39578
|
`;
|
|
39402
|
-
const depsToMock =
|
|
39579
|
+
const depsToMock = stubExternalDeps.slice(0, 5);
|
|
39403
39580
|
stubSetup += `
|
|
39404
39581
|
// Auto-generated stubs from Knowledge Graph dependency analysis
|
|
39405
39582
|
`;
|
|
@@ -39570,12 +39747,13 @@ var PytestGenerator = class extends BaseTestGenerator {
|
|
|
39570
39747
|
const exports = this.extractExports(analysis.functions, analysis.classes);
|
|
39571
39748
|
const pythonImport = importPath.replace(/\//g, ".").replace(/\.(ts|js)$/, "");
|
|
39572
39749
|
const importStatement = exports.length > 0 ? `from ${pythonImport} import ${exports.join(", ")}` : `import ${pythonImport} as ${moduleName}`;
|
|
39750
|
+
const externalDeps = dependencies?.imports.filter((dep) => !dep.startsWith(".")) || [];
|
|
39573
39751
|
let mockImport = "";
|
|
39574
|
-
if (
|
|
39752
|
+
if (externalDeps.length > 0) {
|
|
39575
39753
|
mockImport = `from unittest.mock import patch, MagicMock
|
|
39576
39754
|
`;
|
|
39577
39755
|
}
|
|
39578
|
-
const depsToMock =
|
|
39756
|
+
const depsToMock = externalDeps.slice(0, 5);
|
|
39579
39757
|
const patchDecorators = depsToMock.map((dep) => {
|
|
39580
39758
|
const depModule = dep.replace(/\//g, ".").replace(/\.py$/, "");
|
|
39581
39759
|
return `@patch('${depModule}')`;
|
|
@@ -39588,10 +39766,12 @@ class Test${this.pascalCase(moduleName)}:
|
|
|
39588
39766
|
"""${testType} tests for ${moduleName}"""
|
|
39589
39767
|
|
|
39590
39768
|
`;
|
|
39591
|
-
|
|
39769
|
+
const exportedFns = analysis.functions.filter((fn) => fn.isExported);
|
|
39770
|
+
const exportedClasses = analysis.classes.filter((cls) => cls.isExported);
|
|
39771
|
+
for (const fn of exportedFns) {
|
|
39592
39772
|
code += this.generateFunctionTestsWithPatches(fn, testType, patchDecorators);
|
|
39593
39773
|
}
|
|
39594
|
-
for (const cls of
|
|
39774
|
+
for (const cls of exportedClasses) {
|
|
39595
39775
|
code += this.generateClassTests(cls, testType);
|
|
39596
39776
|
}
|
|
39597
39777
|
return code;
|
|
@@ -39611,15 +39791,36 @@ class Test${this.pascalCase(moduleName)}:
|
|
|
39611
39791
|
const allParams = mockParams ? `self, ${mockParams}` : "self";
|
|
39612
39792
|
const patchPrefix = patchDecorators.map((d74) => ` ${d74}
|
|
39613
39793
|
`).join("");
|
|
39794
|
+
const isVoid = fn.returnType === "void" || fn.returnType === "Promise<void>" || fn.returnType === "None";
|
|
39614
39795
|
let code = `${patchPrefix} def test_${fn.name}_valid_input(${allParams}):
|
|
39615
39796
|
`;
|
|
39616
39797
|
code += ` """Test ${fn.name} with valid input"""
|
|
39617
39798
|
`;
|
|
39618
|
-
|
|
39799
|
+
let pyAssertion = "assert result is not None";
|
|
39800
|
+
if (!isVoid) {
|
|
39801
|
+
if (/^(is|has|can)[A-Z]/.test(fn.name)) {
|
|
39802
|
+
pyAssertion = "assert isinstance(result, bool)";
|
|
39803
|
+
} else if (/^(create|build|make)[A-Z]/.test(fn.name)) {
|
|
39804
|
+
pyAssertion = "assert result";
|
|
39805
|
+
} else if (fn.returnType) {
|
|
39806
|
+
const rt3 = fn.returnType.toLowerCase().replace(/promise<(.+)>/, "$1");
|
|
39807
|
+
if (rt3.includes("boolean") || rt3.includes("bool")) pyAssertion = "assert isinstance(result, bool)";
|
|
39808
|
+
else if (rt3.includes("number") || rt3.includes("int") || rt3.includes("float")) pyAssertion = "assert isinstance(result, (int, float))";
|
|
39809
|
+
else if (rt3.includes("string") || rt3.includes("str")) pyAssertion = "assert isinstance(result, str)";
|
|
39810
|
+
else if (rt3.includes("[]") || rt3.includes("array") || rt3.includes("list")) pyAssertion = "assert isinstance(result, list)";
|
|
39811
|
+
}
|
|
39812
|
+
}
|
|
39813
|
+
if (isVoid) {
|
|
39814
|
+
code += ` ${fn.name}(${validParams}) # void function
|
|
39815
|
+
|
|
39816
|
+
`;
|
|
39817
|
+
} else {
|
|
39818
|
+
code += ` result = ${fn.name}(${validParams})
|
|
39619
39819
|
`;
|
|
39620
|
-
|
|
39820
|
+
code += ` ${pyAssertion}
|
|
39621
39821
|
|
|
39622
39822
|
`;
|
|
39823
|
+
}
|
|
39623
39824
|
for (const param of fn.parameters) {
|
|
39624
39825
|
if (!param.optional && param.type?.includes("str")) {
|
|
39625
39826
|
code += `${patchPrefix} def test_${fn.name}_empty_${param.name}(${allParams}):
|
|
@@ -39660,15 +39861,36 @@ class Test${cls.name}:
|
|
|
39660
39861
|
|
|
39661
39862
|
`;
|
|
39662
39863
|
for (const method of cls.methods) {
|
|
39663
|
-
if (!method.name.startsWith("_")) {
|
|
39864
|
+
if (!method.name.startsWith("_") && !method.name.startsWith("#")) {
|
|
39664
39865
|
const methodParams = method.parameters.map((p74) => this.generatePythonTestValue(p74)).join(", ");
|
|
39866
|
+
const isMethodVoid = method.returnType === "void" || method.returnType === "Promise<void>";
|
|
39867
|
+
let mAssertion = "assert result is not None";
|
|
39868
|
+
if (!isMethodVoid) {
|
|
39869
|
+
if (/^(is|has|can)[A-Z]/.test(method.name)) {
|
|
39870
|
+
mAssertion = "assert isinstance(result, bool)";
|
|
39871
|
+
} else if (/^(create|build|make)[A-Z]/.test(method.name)) {
|
|
39872
|
+
mAssertion = "assert result";
|
|
39873
|
+
} else if (method.returnType) {
|
|
39874
|
+
const mrt = method.returnType.toLowerCase().replace(/promise<(.+)>/, "$1");
|
|
39875
|
+
if (mrt.includes("boolean") || mrt.includes("bool")) mAssertion = "assert isinstance(result, bool)";
|
|
39876
|
+
else if (mrt.includes("number") || mrt.includes("int") || mrt.includes("float")) mAssertion = "assert isinstance(result, (int, float))";
|
|
39877
|
+
else if (mrt.includes("string") || mrt.includes("str")) mAssertion = "assert isinstance(result, str)";
|
|
39878
|
+
else if (mrt.includes("[]") || mrt.includes("array") || mrt.includes("list")) mAssertion = "assert isinstance(result, list)";
|
|
39879
|
+
}
|
|
39880
|
+
}
|
|
39665
39881
|
code += ` def test_${method.name}(self, instance):
|
|
39666
39882
|
`;
|
|
39667
|
-
|
|
39883
|
+
if (isMethodVoid) {
|
|
39884
|
+
code += ` instance.${method.name}(${methodParams})
|
|
39885
|
+
|
|
39668
39886
|
`;
|
|
39669
|
-
|
|
39887
|
+
} else {
|
|
39888
|
+
code += ` result = instance.${method.name}(${methodParams})
|
|
39889
|
+
`;
|
|
39890
|
+
code += ` ${mAssertion}
|
|
39670
39891
|
|
|
39671
39892
|
`;
|
|
39893
|
+
}
|
|
39672
39894
|
}
|
|
39673
39895
|
}
|
|
39674
39896
|
return code;
|
|
@@ -39686,11 +39908,11 @@ class Test${cls.name}:
|
|
|
39686
39908
|
const asyncDef = isAsync ? "async def" : "def";
|
|
39687
39909
|
let mockImports = "";
|
|
39688
39910
|
let mockPatches = "";
|
|
39689
|
-
|
|
39911
|
+
const stubExternalDeps = dependencies?.imports.filter((dep) => !dep.startsWith(".")) || [];
|
|
39912
|
+
if (stubExternalDeps.length > 0) {
|
|
39690
39913
|
mockImports = `from unittest.mock import patch, MagicMock
|
|
39691
39914
|
`;
|
|
39692
|
-
const
|
|
39693
|
-
for (const dep of depsToMock) {
|
|
39915
|
+
for (const dep of stubExternalDeps.slice(0, 5)) {
|
|
39694
39916
|
const depModule = dep.replace(/\//g, ".").replace(/\.py$/, "");
|
|
39695
39917
|
mockPatches += ` @patch('${depModule}')
|
|
39696
39918
|
`;
|
|
@@ -39853,12 +40075,214 @@ class Test${this.pascalCase(moduleName)}Coverage:
|
|
|
39853
40075
|
}
|
|
39854
40076
|
};
|
|
39855
40077
|
|
|
40078
|
+
// src/domains/test-generation/generators/node-test-generator.ts
|
|
40079
|
+
var NodeTestGenerator = class extends BaseTestGenerator {
|
|
40080
|
+
framework = "node-test";
|
|
40081
|
+
/**
|
|
40082
|
+
* Generate complete test file from analysis
|
|
40083
|
+
*/
|
|
40084
|
+
generateTests(context) {
|
|
40085
|
+
const { moduleName, importPath, testType, patterns, analysis } = context;
|
|
40086
|
+
if (!analysis || analysis.functions.length === 0 && analysis.classes.length === 0) {
|
|
40087
|
+
return this.generateStubTests(context);
|
|
40088
|
+
}
|
|
40089
|
+
const patternComment = this.generatePatternComment(patterns);
|
|
40090
|
+
const exportedFunctions = analysis.functions.filter((fn) => fn.isExported);
|
|
40091
|
+
const exportedClasses = analysis.classes.filter((cls) => cls.isExported);
|
|
40092
|
+
const exports = this.extractExports(exportedFunctions, exportedClasses);
|
|
40093
|
+
const importStatement = this.generateImportStatement(exports, importPath, moduleName);
|
|
40094
|
+
let testCode = `${patternComment}import { describe, it, beforeEach } from 'node:test';
|
|
40095
|
+
import assert from 'node:assert';
|
|
40096
|
+
${importStatement}
|
|
40097
|
+
|
|
40098
|
+
`;
|
|
40099
|
+
for (const fn of exportedFunctions) {
|
|
40100
|
+
testCode += this.generateFunctionTests(fn, testType);
|
|
40101
|
+
}
|
|
40102
|
+
for (const cls of exportedClasses) {
|
|
40103
|
+
testCode += this.generateClassTests(cls, testType);
|
|
40104
|
+
}
|
|
40105
|
+
return testCode;
|
|
40106
|
+
}
|
|
40107
|
+
/**
|
|
40108
|
+
* Generate tests for a standalone function
|
|
40109
|
+
*/
|
|
40110
|
+
generateFunctionTests(fn, _testType) {
|
|
40111
|
+
const testCases = this.generateTestCasesForFunction(fn);
|
|
40112
|
+
let code = `describe('${fn.name}', () => {
|
|
40113
|
+
`;
|
|
40114
|
+
for (const testCase of testCases) {
|
|
40115
|
+
if (testCase.setup) {
|
|
40116
|
+
code += ` ${testCase.setup}
|
|
40117
|
+
|
|
40118
|
+
`;
|
|
40119
|
+
}
|
|
40120
|
+
const asyncPrefix = fn.isAsync ? "async " : "";
|
|
40121
|
+
code += ` it('${testCase.description}', ${asyncPrefix}() => {
|
|
40122
|
+
`;
|
|
40123
|
+
code += ` ${this.convertToAssert(testCase.action)}
|
|
40124
|
+
`;
|
|
40125
|
+
code += ` ${this.convertToAssert(testCase.assertion)}
|
|
40126
|
+
`;
|
|
40127
|
+
code += ` });
|
|
40128
|
+
|
|
40129
|
+
`;
|
|
40130
|
+
}
|
|
40131
|
+
code += `});
|
|
40132
|
+
|
|
40133
|
+
`;
|
|
40134
|
+
return code;
|
|
40135
|
+
}
|
|
40136
|
+
/**
|
|
40137
|
+
* Generate tests for a class
|
|
40138
|
+
*/
|
|
40139
|
+
generateClassTests(cls, testType) {
|
|
40140
|
+
let code = `describe('${cls.name}', () => {
|
|
40141
|
+
`;
|
|
40142
|
+
code += ` let instance;
|
|
40143
|
+
|
|
40144
|
+
`;
|
|
40145
|
+
const constructorArgs = cls.constructorParams?.map((p74) => this.generateTestValue(p74)).join(", ") || "";
|
|
40146
|
+
code += ` beforeEach(() => {
|
|
40147
|
+
`;
|
|
40148
|
+
code += ` instance = new ${cls.name}(${constructorArgs});
|
|
40149
|
+
`;
|
|
40150
|
+
code += ` });
|
|
40151
|
+
|
|
40152
|
+
`;
|
|
40153
|
+
code += ` it('should instantiate correctly', () => {
|
|
40154
|
+
`;
|
|
40155
|
+
code += ` assert.ok(instance instanceof ${cls.name});
|
|
40156
|
+
`;
|
|
40157
|
+
code += ` });
|
|
40158
|
+
|
|
40159
|
+
`;
|
|
40160
|
+
for (const method of cls.methods) {
|
|
40161
|
+
if (!method.name.startsWith("_") && !method.name.startsWith("#")) {
|
|
40162
|
+
code += this.generateMethodTest(method);
|
|
40163
|
+
}
|
|
40164
|
+
}
|
|
40165
|
+
code += `});
|
|
40166
|
+
|
|
40167
|
+
`;
|
|
40168
|
+
return code;
|
|
40169
|
+
}
|
|
40170
|
+
/**
|
|
40171
|
+
* Generate test for a class method
|
|
40172
|
+
*/
|
|
40173
|
+
generateMethodTest(method) {
|
|
40174
|
+
const validParams = method.parameters.map((p74) => this.generateTestValue(p74)).join(", ");
|
|
40175
|
+
const isVoid = method.returnType === "void" || method.returnType === "Promise<void>";
|
|
40176
|
+
const asyncPrefix = method.isAsync ? "async " : "";
|
|
40177
|
+
const methodCall = method.isAsync ? `await instance.${method.name}(${validParams})` : `instance.${method.name}(${validParams})`;
|
|
40178
|
+
let assertLine = "assert.ok(result !== undefined);";
|
|
40179
|
+
if (!isVoid) {
|
|
40180
|
+
const mLower = method.name.toLowerCase();
|
|
40181
|
+
if (/^(is|has|can)[A-Z]/.test(method.name)) {
|
|
40182
|
+
assertLine = "assert.strictEqual(typeof result, 'boolean');";
|
|
40183
|
+
} else if (/^(get|fetch|find)[A-Z]/.test(method.name)) {
|
|
40184
|
+
assertLine = "assert.ok(result !== undefined);";
|
|
40185
|
+
} else if (/^(create|build|make)[A-Z]/.test(method.name)) {
|
|
40186
|
+
assertLine = "assert.ok(result);";
|
|
40187
|
+
} else if (method.returnType) {
|
|
40188
|
+
const rt3 = method.returnType.toLowerCase().replace(/promise<(.+)>/, "$1");
|
|
40189
|
+
if (rt3.includes("boolean")) assertLine = "assert.strictEqual(typeof result, 'boolean');";
|
|
40190
|
+
else if (rt3.includes("number")) assertLine = "assert.strictEqual(typeof result, 'number');";
|
|
40191
|
+
else if (rt3.includes("string")) assertLine = "assert.strictEqual(typeof result, 'string');";
|
|
40192
|
+
else if (rt3.includes("[]") || rt3.includes("array")) assertLine = "assert.ok(Array.isArray(result));";
|
|
40193
|
+
}
|
|
40194
|
+
}
|
|
40195
|
+
let code = ` it('${method.name} should execute successfully', ${asyncPrefix}() => {
|
|
40196
|
+
`;
|
|
40197
|
+
if (isVoid) {
|
|
40198
|
+
code += ` ${methodCall};
|
|
40199
|
+
`;
|
|
40200
|
+
} else {
|
|
40201
|
+
code += ` const result = ${methodCall};
|
|
40202
|
+
`;
|
|
40203
|
+
code += ` ${assertLine}
|
|
40204
|
+
`;
|
|
40205
|
+
}
|
|
40206
|
+
code += ` });
|
|
40207
|
+
|
|
40208
|
+
`;
|
|
40209
|
+
return code;
|
|
40210
|
+
}
|
|
40211
|
+
/**
|
|
40212
|
+
* Generate stub tests when no AST analysis is available
|
|
40213
|
+
*/
|
|
40214
|
+
generateStubTests(context) {
|
|
40215
|
+
const { moduleName, importPath, testType, patterns } = context;
|
|
40216
|
+
const patternComment = this.generatePatternComment(patterns);
|
|
40217
|
+
return `${patternComment}import { describe, it } from 'node:test';
|
|
40218
|
+
import assert from 'node:assert';
|
|
40219
|
+
import { ${moduleName} } from '${importPath}';
|
|
40220
|
+
|
|
40221
|
+
describe('${moduleName}', () => {
|
|
40222
|
+
describe('${testType} tests', () => {
|
|
40223
|
+
it('should be defined', () => {
|
|
40224
|
+
assert.ok(${moduleName} !== undefined);
|
|
40225
|
+
});
|
|
40226
|
+
|
|
40227
|
+
it('should handle basic operations', () => {
|
|
40228
|
+
const moduleType = typeof ${moduleName};
|
|
40229
|
+
assert.ok(['function', 'object'].includes(moduleType));
|
|
40230
|
+
});
|
|
40231
|
+
|
|
40232
|
+
it('should handle error conditions', () => {
|
|
40233
|
+
assert.doesNotThrow(() => {
|
|
40234
|
+
const instance = typeof ${moduleName} === 'function'
|
|
40235
|
+
? new ${moduleName}()
|
|
40236
|
+
: ${moduleName};
|
|
40237
|
+
return instance;
|
|
40238
|
+
});
|
|
40239
|
+
});
|
|
40240
|
+
});
|
|
40241
|
+
});
|
|
40242
|
+
`;
|
|
40243
|
+
}
|
|
40244
|
+
/**
|
|
40245
|
+
* Generate coverage-focused tests for specific lines
|
|
40246
|
+
*/
|
|
40247
|
+
generateCoverageTests(moduleName, importPath, lines) {
|
|
40248
|
+
const funcName = this.camelCase(moduleName);
|
|
40249
|
+
const lineRange = this.formatLineRange(lines);
|
|
40250
|
+
return `// Coverage test for ${lineRange} in ${moduleName}
|
|
40251
|
+
import { describe, it } from 'node:test';
|
|
40252
|
+
import assert from 'node:assert';
|
|
40253
|
+
import { ${funcName} } from '${importPath}';
|
|
40254
|
+
|
|
40255
|
+
describe('${moduleName} coverage', () => {
|
|
40256
|
+
it('should exercise code path covering ${lineRange}', () => {
|
|
40257
|
+
const testInput = undefined; // Replace with appropriate input
|
|
40258
|
+
const result = ${funcName}(testInput);
|
|
40259
|
+
assert.ok(result !== undefined || result === undefined);
|
|
40260
|
+
});
|
|
40261
|
+
|
|
40262
|
+
it('should handle edge case for ${lineRange}', () => {
|
|
40263
|
+
assert.doesNotThrow(() => ${funcName}(null));
|
|
40264
|
+
});
|
|
40265
|
+
});
|
|
40266
|
+
`;
|
|
40267
|
+
}
|
|
40268
|
+
// ============================================================================
|
|
40269
|
+
// Helpers
|
|
40270
|
+
// ============================================================================
|
|
40271
|
+
/**
|
|
40272
|
+
* Convert expect() style assertions to node:assert
|
|
40273
|
+
*/
|
|
40274
|
+
convertToAssert(assertion) {
|
|
40275
|
+
return assertion.replace(/expect\(typeof ([^)]+)\)\.toBe\('([^']+)'\);?/g, "assert.strictEqual(typeof $1, '$2');").replace(/expect\(Array\.isArray\(([^)]+)\)\)\.toBe\(true\);?/g, "assert.ok(Array.isArray($1));").replace(/expect\(([^)]+)\)\.toThrow\(\);?/g, "assert.throws($1);").replace(/expect\(([^)]+)\)\.not\.toThrow\(\);?/g, "assert.doesNotThrow($1);").replace(/expect\(([^)]+)\)\.toBeInstanceOf\(([^)]+)\);?/g, "assert.ok($1 instanceof $2);").replace(/expect\(([^)]+)\)\.toBeDefined\(\);?/g, "assert.ok($1 !== undefined);").replace(/expect\(([^)]+)\)\.not\.toBeUndefined\(\);?/g, "assert.ok($1 !== undefined);").replace(/expect\(([^)]+)\)\.toBeUndefined\(\);?/g, "assert.strictEqual($1, undefined);").replace(/expect\(([^)]+)\)\.toBeTruthy\(\);?/g, "assert.ok($1);").replace(/expect\(([^)]+)\)\.toBeFalsy\(\);?/g, "assert.ok(!$1);").replace(/expect\(([^)]+)\)\.toBe\(([^)]+)\);?/g, "assert.strictEqual($1, $2);").replace(/expect\(([^)]+)\)\.toEqual\(([^)]+)\);?/g, "assert.deepStrictEqual($1, $2);");
|
|
40276
|
+
}
|
|
40277
|
+
};
|
|
40278
|
+
|
|
39856
40279
|
// src/domains/test-generation/factories/test-generator-factory.ts
|
|
39857
40280
|
var SUPPORTED_FRAMEWORKS = [
|
|
39858
40281
|
"jest",
|
|
39859
40282
|
"vitest",
|
|
39860
40283
|
"mocha",
|
|
39861
|
-
"pytest"
|
|
40284
|
+
"pytest",
|
|
40285
|
+
"node-test"
|
|
39862
40286
|
];
|
|
39863
40287
|
var DEFAULT_FRAMEWORK = "vitest";
|
|
39864
40288
|
var TestGeneratorFactory = class {
|
|
@@ -39920,6 +40344,8 @@ var TestGeneratorFactory = class {
|
|
|
39920
40344
|
return new MochaGenerator();
|
|
39921
40345
|
case "pytest":
|
|
39922
40346
|
return new PytestGenerator();
|
|
40347
|
+
case "node-test":
|
|
40348
|
+
return new NodeTestGenerator();
|
|
39923
40349
|
default:
|
|
39924
40350
|
throw new Error(`Unsupported test framework: ${framework}`);
|
|
39925
40351
|
}
|
|
@@ -41267,12 +41693,32 @@ ${sourceCode}
|
|
|
41267
41693
|
};
|
|
41268
41694
|
}
|
|
41269
41695
|
extractParameters(params, sourceFile) {
|
|
41270
|
-
|
|
41271
|
-
|
|
41272
|
-
|
|
41273
|
-
|
|
41274
|
-
|
|
41275
|
-
|
|
41696
|
+
let objectDestructureCount = 0;
|
|
41697
|
+
let arrayDestructureCount = 0;
|
|
41698
|
+
return params.map((param) => {
|
|
41699
|
+
let name = param.name.getText(sourceFile);
|
|
41700
|
+
let type = param.type?.getText(sourceFile);
|
|
41701
|
+
if (ts.isObjectBindingPattern(param.name)) {
|
|
41702
|
+
objectDestructureCount++;
|
|
41703
|
+
name = objectDestructureCount > 1 ? `options${objectDestructureCount}` : "options";
|
|
41704
|
+
if (!type) {
|
|
41705
|
+
const props = param.name.elements.map((el) => `${el.name.getText(sourceFile)}: unknown`).join(", ");
|
|
41706
|
+
type = `{ ${props} }`;
|
|
41707
|
+
}
|
|
41708
|
+
} else if (ts.isArrayBindingPattern(param.name)) {
|
|
41709
|
+
arrayDestructureCount++;
|
|
41710
|
+
name = arrayDestructureCount > 1 ? `items${arrayDestructureCount}` : "items";
|
|
41711
|
+
if (!type) {
|
|
41712
|
+
type = "unknown[]";
|
|
41713
|
+
}
|
|
41714
|
+
}
|
|
41715
|
+
return {
|
|
41716
|
+
name,
|
|
41717
|
+
type,
|
|
41718
|
+
optional: param.questionToken !== void 0,
|
|
41719
|
+
defaultValue: param.initializer?.getText(sourceFile)
|
|
41720
|
+
};
|
|
41721
|
+
});
|
|
41276
41722
|
}
|
|
41277
41723
|
calculateComplexity(node) {
|
|
41278
41724
|
let complexity = 1;
|
|
@@ -41331,12 +41777,18 @@ ${sourceCode}
|
|
|
41331
41777
|
const callees = [];
|
|
41332
41778
|
const callers = [];
|
|
41333
41779
|
const tsImports = sourceContent.matchAll(/(?:import|from)\s+['"]([^'"]+)['"]/g);
|
|
41334
|
-
const pyImports = sourceContent.matchAll(/(?:^|\n)\s*(?:from\s+(\S+)\s+import|import\s+(\S+))/g);
|
|
41335
41780
|
for (const match of tsImports) {
|
|
41336
41781
|
imports.push(match[1]);
|
|
41337
41782
|
}
|
|
41338
|
-
|
|
41339
|
-
|
|
41783
|
+
const isPython = filePath.endsWith(".py");
|
|
41784
|
+
if (isPython) {
|
|
41785
|
+
const pyImports = sourceContent.matchAll(/(?:^|\n)\s*(?:from\s+(\S+)\s+import|import\s+(\S+))/g);
|
|
41786
|
+
for (const match of pyImports) {
|
|
41787
|
+
const mod = match[1] || match[2];
|
|
41788
|
+
if (mod && /^[a-zA-Z_][\w.]*$/.test(mod)) {
|
|
41789
|
+
imports.push(mod);
|
|
41790
|
+
}
|
|
41791
|
+
}
|
|
41340
41792
|
}
|
|
41341
41793
|
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
41342
41794
|
const baseName = normalizedPath.split("/").pop()?.replace(/\.(ts|js|tsx|jsx|py)$/, "") || "";
|
|
@@ -42365,7 +42817,7 @@ var PatternMatcherService = class _PatternMatcherService {
|
|
|
42365
42817
|
return template.replace("{{name}}", param.name);
|
|
42366
42818
|
}
|
|
42367
42819
|
}
|
|
42368
|
-
return `
|
|
42820
|
+
return `{} /* TODO: provide ${param.name}: ${param.type || "unknown"} */`;
|
|
42369
42821
|
}
|
|
42370
42822
|
/**
|
|
42371
42823
|
* Estimate number of branches from cyclomatic complexity
|
|
@@ -43281,12 +43733,32 @@ var TypeScriptASTParser = class {
|
|
|
43281
43733
|
* Extract parameter information
|
|
43282
43734
|
*/
|
|
43283
43735
|
extractParameters(params, sourceFile) {
|
|
43284
|
-
|
|
43285
|
-
|
|
43286
|
-
|
|
43287
|
-
|
|
43288
|
-
|
|
43289
|
-
|
|
43736
|
+
let objectDestructureCount = 0;
|
|
43737
|
+
let arrayDestructureCount = 0;
|
|
43738
|
+
return params.map((param) => {
|
|
43739
|
+
let name = param.name.getText(sourceFile);
|
|
43740
|
+
let type = param.type?.getText(sourceFile);
|
|
43741
|
+
if (ts2.isObjectBindingPattern(param.name)) {
|
|
43742
|
+
objectDestructureCount++;
|
|
43743
|
+
name = objectDestructureCount > 1 ? `options${objectDestructureCount}` : "options";
|
|
43744
|
+
if (!type) {
|
|
43745
|
+
const props = param.name.elements.map((el) => `${el.name.getText(sourceFile)}: unknown`).join(", ");
|
|
43746
|
+
type = `{ ${props} }`;
|
|
43747
|
+
}
|
|
43748
|
+
} else if (ts2.isArrayBindingPattern(param.name)) {
|
|
43749
|
+
arrayDestructureCount++;
|
|
43750
|
+
name = arrayDestructureCount > 1 ? `items${arrayDestructureCount}` : "items";
|
|
43751
|
+
if (!type) {
|
|
43752
|
+
type = "unknown[]";
|
|
43753
|
+
}
|
|
43754
|
+
}
|
|
43755
|
+
return {
|
|
43756
|
+
name,
|
|
43757
|
+
type,
|
|
43758
|
+
optional: param.questionToken !== void 0,
|
|
43759
|
+
defaultValue: param.initializer?.getText(sourceFile)
|
|
43760
|
+
};
|
|
43761
|
+
});
|
|
43290
43762
|
}
|
|
43291
43763
|
/**
|
|
43292
43764
|
* Calculate cyclomatic complexity of a node
|