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.
Files changed (69) hide show
  1. package/.claude/skills/skills-manifest.json +1 -1
  2. package/package.json +3 -2
  3. package/v3/CHANGELOG.md +42 -0
  4. package/v3/dist/cli/bundle.js +911 -354
  5. package/v3/dist/cli/commands/test.d.ts.map +1 -1
  6. package/v3/dist/cli/commands/test.js +6 -3
  7. package/v3/dist/cli/commands/test.js.map +1 -1
  8. package/v3/dist/cli/completions/index.d.ts +1 -1
  9. package/v3/dist/cli/completions/index.d.ts.map +1 -1
  10. package/v3/dist/cli/completions/index.js +1 -1
  11. package/v3/dist/cli/completions/index.js.map +1 -1
  12. package/v3/dist/cli/wizards/test-wizard.d.ts +1 -1
  13. package/v3/dist/cli/wizards/test-wizard.d.ts.map +1 -1
  14. package/v3/dist/cli/wizards/test-wizard.js +8 -1
  15. package/v3/dist/cli/wizards/test-wizard.js.map +1 -1
  16. package/v3/dist/coordination/task-executor.js.map +1 -1
  17. package/v3/dist/domains/test-generation/factories/test-generator-factory.d.ts.map +1 -1
  18. package/v3/dist/domains/test-generation/factories/test-generator-factory.js +4 -1
  19. package/v3/dist/domains/test-generation/factories/test-generator-factory.js.map +1 -1
  20. package/v3/dist/domains/test-generation/generators/base-test-generator.d.ts.map +1 -1
  21. package/v3/dist/domains/test-generation/generators/base-test-generator.js +74 -27
  22. package/v3/dist/domains/test-generation/generators/base-test-generator.js.map +1 -1
  23. package/v3/dist/domains/test-generation/generators/index.d.ts +1 -0
  24. package/v3/dist/domains/test-generation/generators/index.d.ts.map +1 -1
  25. package/v3/dist/domains/test-generation/generators/index.js +1 -0
  26. package/v3/dist/domains/test-generation/generators/index.js.map +1 -1
  27. package/v3/dist/domains/test-generation/generators/jest-vitest-generator.d.ts.map +1 -1
  28. package/v3/dist/domains/test-generation/generators/jest-vitest-generator.js +77 -21
  29. package/v3/dist/domains/test-generation/generators/jest-vitest-generator.js.map +1 -1
  30. package/v3/dist/domains/test-generation/generators/mocha-generator.d.ts.map +1 -1
  31. package/v3/dist/domains/test-generation/generators/mocha-generator.js +92 -17
  32. package/v3/dist/domains/test-generation/generators/mocha-generator.js.map +1 -1
  33. package/v3/dist/domains/test-generation/generators/node-test-generator.d.ts +54 -0
  34. package/v3/dist/domains/test-generation/generators/node-test-generator.d.ts.map +1 -0
  35. package/v3/dist/domains/test-generation/generators/node-test-generator.js +222 -0
  36. package/v3/dist/domains/test-generation/generators/node-test-generator.js.map +1 -0
  37. package/v3/dist/domains/test-generation/generators/pytest-generator.d.ts.map +1 -1
  38. package/v3/dist/domains/test-generation/generators/pytest-generator.js +74 -15
  39. package/v3/dist/domains/test-generation/generators/pytest-generator.js.map +1 -1
  40. package/v3/dist/domains/test-generation/interfaces/test-generator.interface.d.ts +1 -1
  41. package/v3/dist/domains/test-generation/interfaces/test-generator.interface.d.ts.map +1 -1
  42. package/v3/dist/domains/test-generation/interfaces.d.ts +2 -2
  43. package/v3/dist/domains/test-generation/interfaces.d.ts.map +1 -1
  44. package/v3/dist/domains/test-generation/plugin.js.map +1 -1
  45. package/v3/dist/domains/test-generation/services/pattern-matcher.d.ts.map +1 -1
  46. package/v3/dist/domains/test-generation/services/pattern-matcher.js +34 -7
  47. package/v3/dist/domains/test-generation/services/pattern-matcher.js.map +1 -1
  48. package/v3/dist/domains/test-generation/services/test-generator.d.ts.map +1 -1
  49. package/v3/dist/domains/test-generation/services/test-generator.js +42 -9
  50. package/v3/dist/domains/test-generation/services/test-generator.js.map +1 -1
  51. package/v3/dist/mcp/bundle.js +550 -78
  52. package/v3/dist/mcp/tools/test-generation/generate.d.ts +1 -1
  53. package/v3/dist/mcp/tools/test-generation/generate.d.ts.map +1 -1
  54. package/v3/dist/mcp/tools/test-generation/generate.js.map +1 -1
  55. package/v3/dist/shared/sql-safety.d.ts +3 -0
  56. package/v3/dist/shared/sql-safety.d.ts.map +1 -1
  57. package/v3/dist/shared/sql-safety.js +12 -2
  58. package/v3/dist/shared/sql-safety.js.map +1 -1
  59. package/v3/dist/sync/cloud/postgres-writer.d.ts +6 -1
  60. package/v3/dist/sync/cloud/postgres-writer.d.ts.map +1 -1
  61. package/v3/dist/sync/cloud/postgres-writer.js +121 -46
  62. package/v3/dist/sync/cloud/postgres-writer.js.map +1 -1
  63. package/v3/dist/sync/interfaces.d.ts.map +1 -1
  64. package/v3/dist/sync/interfaces.js +97 -32
  65. package/v3/dist/sync/interfaces.js.map +1 -1
  66. package/v3/dist/sync/sync-agent.d.ts.map +1 -1
  67. package/v3/dist/sync/sync-agent.js +8 -24
  68. package/v3/dist/sync/sync-agent.js.map +1 -1
  69. package/v3/package.json +1 -1
@@ -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 `mock${param.name.charAt(0).toUpperCase() + param.name.slice(1)}`;
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: "expect(result).toBeDefined();"
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
- testCases.push({
38695
- description: `should handle undefined ${param.name}`,
38696
- type: "error-handling",
38697
- action: fn.isAsync ? `const action = async () => await ${fn.name}(${paramsWithUndefined});` : `const action = () => ${fn.name}(${paramsWithUndefined});`,
38698
- assertion: "expect(action).toThrow();"
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 = fn.isAsync ? `await ${fn.name}(${paramsWithEmpty})` : `${fn.name}(${paramsWithEmpty})`;
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: `const result = ${emptyCall};`,
38726
- assertion: "expect(result).toBeDefined();"
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 = fn.isAsync ? `await ${fn.name}(${paramsWithZero})` : `${fn.name}(${paramsWithZero})`;
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: `const result = ${zeroCall};`,
38736
- assertion: "expect(result).toBeDefined();"
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 = fn.isAsync ? `await ${fn.name}(${paramsWithNegative})` : `${fn.name}(${paramsWithNegative})`;
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: `const result = ${negativeCall};`,
38744
- assertion: "expect(result).toBeDefined();"
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
- testCode += `
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
- for (const dep of dependencies.imports.slice(0, 10)) {
38846
- const depName = dep.split("/").pop()?.replace(/[^a-zA-Z0-9_]/g, "_") || dep;
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
- testCode += `
38907
+ }
38908
+ testCode += `
38851
38909
  `;
38910
+ } else {
38911
+ testCode += `
38912
+ `;
38913
+ }
38852
38914
  } else {
38853
38915
  testCode += `
38854
38916
  `;
38855
38917
  }
38856
- for (const fn of analysis.functions) {
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 analysis.classes) {
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
- code += ` const result = ${methodCall};
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
- code += ` expect(result).toBeDefined();
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
- code += `
38958
- it('should handle invalid ${param.name}', () => {
39047
+ if (methodThrows) {
39048
+ code += `
39049
+ it('should handle invalid ${param.name}', ${asyncPrefix}() => {
38959
39050
  `;
38960
- code += ` expect(() => instance.${method.name}(${paramsWithUndefined})).toThrow();
39051
+ code += ` expect(() => instance.${method.name}(${paramsWithUndefined})).toThrow();
38961
39052
  `;
38962
- code += ` });
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
- mockDeclarations += `
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
- for (const dep of dependencies.imports.slice(0, 10)) {
38987
- mockDeclarations += `${this.mockUtil}.mock('${dep}', () => ({ default: ${mockFn} }));
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
- if (dependencies && dependencies.imports.length > 0) {
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 = dependencies.imports.slice(0, 5);
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
- for (const fn of analysis.functions) {
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 analysis.classes) {
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
- code += ` const result = ${fnCall};
39452
+ if (isVoid) {
39453
+ code += ` ${fnCall};
39454
+ `;
39455
+ } else {
39456
+ code += ` const result = ${fnCall};
39322
39457
  `;
39323
- code += ` expect(result).to.not.be.undefined;
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
- code += `
39468
+ if (hasExplicitThrow) {
39469
+ code += `
39331
39470
  it('should handle undefined ${param.name}', function() {
39332
39471
  `;
39333
- code += ` expect(function() { ${fn.name}(${paramsWithUndefined}); }).to.throw();
39472
+ code += ` expect(function() { ${fn.name}(${paramsWithUndefined}); }).to.throw();
39334
39473
  `;
39335
- code += ` });
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
- code += ` const result = ${method.isAsync ? "await " : ""}instance.${method.name}(${methodParams});
39544
+ if (isMethodVoid) {
39545
+ code += ` ${method.isAsync ? "await " : ""}instance.${method.name}(${methodParams});
39374
39546
  `;
39375
- code += ` expect(result).to.not.be.undefined;
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
- if (dependencies && dependencies.imports.length > 0) {
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 = dependencies.imports.slice(0, 5);
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 (dependencies && dependencies.imports.length > 0) {
39752
+ if (externalDeps.length > 0) {
39575
39753
  mockImport = `from unittest.mock import patch, MagicMock
39576
39754
  `;
39577
39755
  }
39578
- const depsToMock = dependencies?.imports.slice(0, 5) || [];
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
- for (const fn of analysis.functions) {
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 analysis.classes) {
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
- code += ` result = ${fn.name}(${validParams})
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
- code += ` assert result is not None
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
- code += ` result = instance.${method.name}(${methodParams})
39883
+ if (isMethodVoid) {
39884
+ code += ` instance.${method.name}(${methodParams})
39885
+
39668
39886
  `;
39669
- code += ` assert result is not None
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
- if (dependencies && dependencies.imports.length > 0) {
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 depsToMock = dependencies.imports.slice(0, 5);
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
- return params.map((param) => ({
41271
- name: param.name.getText(sourceFile),
41272
- type: param.type?.getText(sourceFile),
41273
- optional: param.questionToken !== void 0,
41274
- defaultValue: param.initializer?.getText(sourceFile)
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
- for (const match of pyImports) {
41339
- imports.push(match[1] || match[2]);
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 `mock${param.name.charAt(0).toUpperCase() + param.name.slice(1)}`;
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
- return params.map((param) => ({
43285
- name: param.name.getText(sourceFile),
43286
- type: param.type?.getText(sourceFile),
43287
- optional: param.questionToken !== void 0,
43288
- defaultValue: param.initializer?.getText(sourceFile)
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