agentic-qe 3.6.14 → 3.6.15

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 (41) hide show
  1. package/.claude/skills/skills-manifest.json +1 -1
  2. package/package.json +3 -2
  3. package/v3/CHANGELOG.md +19 -0
  4. package/v3/dist/cli/bundle.js +561 -338
  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/domains/test-generation/generators/base-test-generator.d.ts.map +1 -1
  9. package/v3/dist/domains/test-generation/generators/base-test-generator.js +49 -27
  10. package/v3/dist/domains/test-generation/generators/base-test-generator.js.map +1 -1
  11. package/v3/dist/domains/test-generation/generators/jest-vitest-generator.d.ts.map +1 -1
  12. package/v3/dist/domains/test-generation/generators/jest-vitest-generator.js +53 -21
  13. package/v3/dist/domains/test-generation/generators/jest-vitest-generator.js.map +1 -1
  14. package/v3/dist/domains/test-generation/generators/mocha-generator.d.ts.map +1 -1
  15. package/v3/dist/domains/test-generation/generators/mocha-generator.js +46 -16
  16. package/v3/dist/domains/test-generation/generators/mocha-generator.js.map +1 -1
  17. package/v3/dist/domains/test-generation/generators/pytest-generator.d.ts.map +1 -1
  18. package/v3/dist/domains/test-generation/generators/pytest-generator.js +31 -14
  19. package/v3/dist/domains/test-generation/generators/pytest-generator.js.map +1 -1
  20. package/v3/dist/domains/test-generation/services/pattern-matcher.d.ts.map +1 -1
  21. package/v3/dist/domains/test-generation/services/pattern-matcher.js +2 -1
  22. package/v3/dist/domains/test-generation/services/pattern-matcher.js.map +1 -1
  23. package/v3/dist/domains/test-generation/services/test-generator.d.ts.map +1 -1
  24. package/v3/dist/domains/test-generation/services/test-generator.js +11 -3
  25. package/v3/dist/domains/test-generation/services/test-generator.js.map +1 -1
  26. package/v3/dist/mcp/bundle.js +201 -63
  27. package/v3/dist/shared/sql-safety.d.ts +3 -0
  28. package/v3/dist/shared/sql-safety.d.ts.map +1 -1
  29. package/v3/dist/shared/sql-safety.js +12 -2
  30. package/v3/dist/shared/sql-safety.js.map +1 -1
  31. package/v3/dist/sync/cloud/postgres-writer.d.ts +6 -1
  32. package/v3/dist/sync/cloud/postgres-writer.d.ts.map +1 -1
  33. package/v3/dist/sync/cloud/postgres-writer.js +121 -46
  34. package/v3/dist/sync/cloud/postgres-writer.js.map +1 -1
  35. package/v3/dist/sync/interfaces.d.ts.map +1 -1
  36. package/v3/dist/sync/interfaces.js +97 -32
  37. package/v3/dist/sync/interfaces.js.map +1 -1
  38. package/v3/dist/sync/sync-agent.d.ts.map +1 -1
  39. package/v3/dist/sync/sync-agent.js +8 -24
  40. package/v3/dist/sync/sync-agent.js.map +1 -1
  41. 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,46 @@ 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>";
38685
38686
  testCases.push({
38686
38687
  description: "should handle valid input correctly",
38687
38688
  type: "happy-path",
38688
- action: `const result = ${fnCall};`,
38689
- assertion: "expect(result).toBeDefined();"
38689
+ action: isVoid ? `${fnCall};` : `const result = ${fnCall};`,
38690
+ assertion: isVoid ? `// void function \u2014 no return value to assert` : "expect(result).toBeDefined();"
38690
38691
  });
38692
+ const bodyText = fn.body || "";
38693
+ const hasExplicitThrow = /\bthrow\b/.test(bodyText) || /\bvalidat/i.test(bodyText);
38691
38694
  for (const param of fn.parameters) {
38692
38695
  if (!param.optional) {
38693
38696
  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
- });
38697
+ if (hasExplicitThrow) {
38698
+ testCases.push({
38699
+ description: `should handle undefined ${param.name}`,
38700
+ type: "error-handling",
38701
+ action: fn.isAsync ? `const action = async () => await ${fn.name}(${paramsWithUndefined});` : `const action = () => ${fn.name}(${paramsWithUndefined});`,
38702
+ assertion: "expect(action).toThrow();"
38703
+ });
38704
+ } else {
38705
+ const undefinedCall = fn.isAsync ? `await ${fn.name}(${paramsWithUndefined})` : `${fn.name}(${paramsWithUndefined})`;
38706
+ testCases.push({
38707
+ description: `should handle undefined ${param.name}`,
38708
+ type: "edge-case",
38709
+ action: fn.isAsync ? `let threw = false;
38710
+ try {
38711
+ await ${fn.name}(${paramsWithUndefined});
38712
+ } catch (e) {
38713
+ threw = true;
38714
+ expect(e).toBeInstanceOf(Error);
38715
+ }` : `let threw = false;
38716
+ try {
38717
+ ${fn.name}(${paramsWithUndefined});
38718
+ } catch (e) {
38719
+ threw = true;
38720
+ expect(e).toBeInstanceOf(Error);
38721
+ }`,
38722
+ assertion: `expect(true).toBe(true); // function either handles undefined or throws TypeError`
38723
+ });
38724
+ }
38700
38725
  }
38701
38726
  testCases.push(...this.generateBoundaryTestCases(fn, param));
38702
38727
  }
@@ -38716,32 +38741,48 @@ var BaseTestGenerator = class _BaseTestGenerator {
38716
38741
  generateBoundaryTestCases(fn, param) {
38717
38742
  const testCases = [];
38718
38743
  const type = param.type?.toLowerCase() || "";
38744
+ const wrapBoundaryAction = (call, isAsync) => {
38745
+ if (isAsync) {
38746
+ return `try {
38747
+ const result = await ${call};
38748
+ expect(result).toBeDefined();
38749
+ } catch (e) {
38750
+ expect(e).toBeInstanceOf(Error);
38751
+ }`;
38752
+ }
38753
+ return `try {
38754
+ const result = ${call};
38755
+ expect(result).toBeDefined();
38756
+ } catch (e) {
38757
+ expect(e).toBeInstanceOf(Error);
38758
+ }`;
38759
+ };
38719
38760
  if (type.includes("string")) {
38720
38761
  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})`;
38762
+ const emptyCall = `${fn.name}(${paramsWithEmpty})`;
38722
38763
  testCases.push({
38723
38764
  description: `should handle empty string for ${param.name}`,
38724
38765
  type: "boundary",
38725
- action: `const result = ${emptyCall};`,
38726
- assertion: "expect(result).toBeDefined();"
38766
+ action: wrapBoundaryAction(emptyCall, fn.isAsync),
38767
+ assertion: ""
38727
38768
  });
38728
38769
  }
38729
38770
  if (type.includes("number")) {
38730
38771
  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})`;
38772
+ const zeroCall = `${fn.name}(${paramsWithZero})`;
38732
38773
  testCases.push({
38733
38774
  description: `should handle zero for ${param.name}`,
38734
38775
  type: "boundary",
38735
- action: `const result = ${zeroCall};`,
38736
- assertion: "expect(result).toBeDefined();"
38776
+ action: wrapBoundaryAction(zeroCall, fn.isAsync),
38777
+ assertion: ""
38737
38778
  });
38738
38779
  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})`;
38780
+ const negativeCall = `${fn.name}(${paramsWithNegative})`;
38740
38781
  testCases.push({
38741
38782
  description: `should handle negative value for ${param.name}`,
38742
38783
  type: "edge-case",
38743
- action: `const result = ${negativeCall};`,
38744
- assertion: "expect(result).toBeDefined();"
38784
+ action: wrapBoundaryAction(negativeCall, fn.isAsync),
38785
+ assertion: ""
38745
38786
  });
38746
38787
  }
38747
38788
  if (type.includes("[]") || type.includes("array")) {
@@ -38839,24 +38880,31 @@ ${importStatement}
38839
38880
  `;
38840
38881
  if (dependencies && dependencies.imports.length > 0) {
38841
38882
  const mockFn = this.framework === "vitest" ? "vi.fn()" : "jest.fn()";
38842
- testCode += `
38883
+ const externalDeps = dependencies.imports.filter((dep) => !dep.startsWith("."));
38884
+ if (externalDeps.length > 0) {
38885
+ testCode += `
38843
38886
  // Auto-generated mocks from Knowledge Graph dependency analysis
38844
38887
  `;
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} }));
38888
+ for (const dep of externalDeps.slice(0, 10)) {
38889
+ testCode += `${this.framework === "vitest" ? "vi" : "jest"}.mock('${dep}', () => ({ default: ${mockFn} }));
38848
38890
  `;
38849
- }
38850
- testCode += `
38891
+ }
38892
+ testCode += `
38893
+ `;
38894
+ } else {
38895
+ testCode += `
38851
38896
  `;
38897
+ }
38852
38898
  } else {
38853
38899
  testCode += `
38854
38900
  `;
38855
38901
  }
38856
- for (const fn of analysis.functions) {
38902
+ const exportedFns = analysis.functions.filter((fn) => fn.isExported);
38903
+ const exportedClasses = analysis.classes.filter((cls) => cls.isExported);
38904
+ for (const fn of exportedFns) {
38857
38905
  testCode += this.generateFunctionTests(fn, testType);
38858
38906
  }
38859
- for (const cls of analysis.classes) {
38907
+ for (const cls of exportedClasses) {
38860
38908
  testCode += this.generateClassTests(cls, testType);
38861
38909
  }
38862
38910
  return testCode;
@@ -38943,24 +38991,52 @@ ${importStatement}
38943
38991
  const validParams = method.parameters.map((p74) => this.generateTestValue(p74)).join(", ");
38944
38992
  const methodCall = method.isAsync ? `await instance.${method.name}(${validParams})` : `instance.${method.name}(${validParams})`;
38945
38993
  const asyncPrefix = method.isAsync ? "async " : "";
38994
+ const isVoid = method.returnType === "void" || method.returnType === "Promise<void>";
38946
38995
  code += ` it('should execute successfully', ${asyncPrefix}() => {
38947
38996
  `;
38948
- code += ` const result = ${methodCall};
38997
+ if (isVoid) {
38998
+ code += ` ${methodCall};
38949
38999
  `;
38950
- code += ` expect(result).toBeDefined();
39000
+ code += ` // void return \u2014 no assertion on result needed
38951
39001
  `;
39002
+ } else {
39003
+ code += ` const result = ${methodCall};
39004
+ `;
39005
+ code += ` expect(result).toBeDefined();
39006
+ `;
39007
+ }
38952
39008
  code += ` });
38953
39009
  `;
39010
+ const methodBody = method.body || "";
39011
+ const methodThrows = /\bthrow\b/.test(methodBody) || /\bvalidat/i.test(methodBody);
38954
39012
  for (const param of method.parameters) {
38955
39013
  if (!param.optional) {
38956
39014
  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}', () => {
39015
+ if (methodThrows) {
39016
+ code += `
39017
+ it('should handle invalid ${param.name}', ${asyncPrefix}() => {
38959
39018
  `;
38960
- code += ` expect(() => instance.${method.name}(${paramsWithUndefined})).toThrow();
39019
+ code += ` expect(() => instance.${method.name}(${paramsWithUndefined})).toThrow();
38961
39020
  `;
38962
- code += ` });
39021
+ code += ` });
39022
+ `;
39023
+ } else {
39024
+ code += `
39025
+ it('should handle undefined ${param.name}', ${asyncPrefix}() => {
39026
+ `;
39027
+ code += ` try {
39028
+ `;
39029
+ code += ` ${method.isAsync ? "await " : ""}instance.${method.name}(${paramsWithUndefined});
39030
+ `;
39031
+ code += ` } catch (e) {
39032
+ `;
39033
+ code += ` expect(e).toBeInstanceOf(Error);
39034
+ `;
39035
+ code += ` }
39036
+ `;
39037
+ code += ` });
38963
39038
  `;
39039
+ }
38964
39040
  }
38965
39041
  }
38966
39042
  code += ` });
@@ -38980,12 +39056,15 @@ ${importStatement}
38980
39056
  let mockDeclarations = "";
38981
39057
  if (dependencies && dependencies.imports.length > 0) {
38982
39058
  const mockFn = this.framework === "vitest" ? "vi.fn()" : "jest.fn()";
38983
- mockDeclarations += `
39059
+ const externalDeps = dependencies.imports.filter((dep) => !dep.startsWith("."));
39060
+ if (externalDeps.length > 0) {
39061
+ mockDeclarations += `
38984
39062
  // Auto-generated mocks from Knowledge Graph dependency analysis
38985
39063
  `;
38986
- for (const dep of dependencies.imports.slice(0, 10)) {
38987
- mockDeclarations += `${this.mockUtil}.mock('${dep}', () => ({ default: ${mockFn} }));
39064
+ for (const dep of externalDeps.slice(0, 10)) {
39065
+ mockDeclarations += `${this.mockUtil}.mock('${dep}', () => ({ default: ${mockFn} }));
38988
39066
  `;
39067
+ }
38989
39068
  }
38990
39069
  }
38991
39070
  let similarityComment = "";
@@ -39262,10 +39341,11 @@ var MochaGenerator = class extends BaseTestGenerator {
39262
39341
  const importStatement = this.generateImportStatement(exports, importPath, moduleName);
39263
39342
  let sinonImport = "";
39264
39343
  let stubSetup = "";
39265
- if (dependencies && dependencies.imports.length > 0) {
39344
+ const externalDeps = dependencies?.imports.filter((dep) => !dep.startsWith(".")) || [];
39345
+ if (externalDeps.length > 0) {
39266
39346
  sinonImport = `import sinon from 'sinon';
39267
39347
  `;
39268
- const depsToMock = dependencies.imports.slice(0, 5);
39348
+ const depsToMock = externalDeps.slice(0, 5);
39269
39349
  stubSetup += ` // Auto-generated stubs from Knowledge Graph dependency analysis
39270
39350
  `;
39271
39351
  stubSetup += ` let stubs;
@@ -39298,10 +39378,12 @@ ${sinonImport}${importStatement}
39298
39378
 
39299
39379
  describe('${moduleName} - ${testType} tests', function() {
39300
39380
  ${stubSetup}`;
39301
- for (const fn of analysis.functions) {
39381
+ const exportedFns = analysis.functions.filter((fn) => fn.isExported);
39382
+ const exportedClasses = analysis.classes.filter((cls) => cls.isExported);
39383
+ for (const fn of exportedFns) {
39302
39384
  code += this.generateFunctionTests(fn, testType);
39303
39385
  }
39304
- for (const cls of analysis.classes) {
39386
+ for (const cls of exportedClasses) {
39305
39387
  code += this.generateClassTests(cls, testType);
39306
39388
  }
39307
39389
  code += `});
@@ -39314,26 +39396,52 @@ ${stubSetup}`;
39314
39396
  generateFunctionTests(fn, _testType) {
39315
39397
  const validParams = fn.parameters.map((p74) => this.generateTestValue(p74)).join(", ");
39316
39398
  const fnCall = fn.isAsync ? `await ${fn.name}(${validParams})` : `${fn.name}(${validParams})`;
39399
+ const isVoid = fn.returnType === "void" || fn.returnType === "Promise<void>";
39317
39400
  let code = ` describe('${fn.name}', function() {
39318
39401
  `;
39319
39402
  code += ` it('should handle valid input', ${fn.isAsync ? "async " : ""}function() {
39320
39403
  `;
39321
- code += ` const result = ${fnCall};
39404
+ if (isVoid) {
39405
+ code += ` ${fnCall};
39406
+ `;
39407
+ } else {
39408
+ code += ` const result = ${fnCall};
39322
39409
  `;
39323
- code += ` expect(result).to.not.be.undefined;
39410
+ code += ` expect(result).to.not.be.undefined;
39324
39411
  `;
39412
+ }
39325
39413
  code += ` });
39326
39414
  `;
39415
+ const bodyText = fn.body || "";
39416
+ const hasExplicitThrow = /\bthrow\b/.test(bodyText) || /\bvalidat/i.test(bodyText);
39327
39417
  for (const param of fn.parameters) {
39328
39418
  if (!param.optional) {
39329
39419
  const paramsWithUndefined = fn.parameters.map((p74) => p74.name === param.name ? "undefined" : this.generateTestValue(p74)).join(", ");
39330
- code += `
39420
+ if (hasExplicitThrow) {
39421
+ code += `
39331
39422
  it('should handle undefined ${param.name}', function() {
39332
39423
  `;
39333
- code += ` expect(function() { ${fn.name}(${paramsWithUndefined}); }).to.throw();
39424
+ code += ` expect(function() { ${fn.name}(${paramsWithUndefined}); }).to.throw();
39334
39425
  `;
39335
- code += ` });
39426
+ code += ` });
39427
+ `;
39428
+ } else {
39429
+ code += `
39430
+ it('should handle undefined ${param.name}', function() {
39431
+ `;
39432
+ code += ` try {
39433
+ `;
39434
+ code += ` ${fn.name}(${paramsWithUndefined});
39435
+ `;
39436
+ code += ` } catch (e) {
39336
39437
  `;
39438
+ code += ` expect(e).to.be.instanceOf(Error);
39439
+ `;
39440
+ code += ` }
39441
+ `;
39442
+ code += ` });
39443
+ `;
39444
+ }
39337
39445
  }
39338
39446
  }
39339
39447
  code += ` });
@@ -39367,13 +39475,19 @@ ${stubSetup}`;
39367
39475
  for (const method of cls.methods) {
39368
39476
  if (!method.name.startsWith("_")) {
39369
39477
  const methodParams = method.parameters.map((p74) => this.generateTestValue(p74)).join(", ");
39478
+ const isVoid = method.returnType === "void" || method.returnType === "Promise<void>";
39370
39479
  code += `
39371
39480
  it('${method.name} should work', ${method.isAsync ? "async " : ""}function() {
39372
39481
  `;
39373
- code += ` const result = ${method.isAsync ? "await " : ""}instance.${method.name}(${methodParams});
39482
+ if (isVoid) {
39483
+ code += ` ${method.isAsync ? "await " : ""}instance.${method.name}(${methodParams});
39374
39484
  `;
39375
- code += ` expect(result).to.not.be.undefined;
39485
+ } else {
39486
+ code += ` const result = ${method.isAsync ? "await " : ""}instance.${method.name}(${methodParams});
39487
+ `;
39488
+ code += ` expect(result).to.not.be.undefined;
39376
39489
  `;
39490
+ }
39377
39491
  code += ` });
39378
39492
  `;
39379
39493
  }
@@ -39396,10 +39510,11 @@ ${stubSetup}`;
39396
39510
  let sinonImport = "";
39397
39511
  let stubSetup = "";
39398
39512
  let stubTeardown = "";
39399
- if (dependencies && dependencies.imports.length > 0) {
39513
+ const stubExternalDeps = dependencies?.imports.filter((dep) => !dep.startsWith(".")) || [];
39514
+ if (stubExternalDeps.length > 0) {
39400
39515
  sinonImport = `import sinon from 'sinon';
39401
39516
  `;
39402
- const depsToMock = dependencies.imports.slice(0, 5);
39517
+ const depsToMock = stubExternalDeps.slice(0, 5);
39403
39518
  stubSetup += `
39404
39519
  // Auto-generated stubs from Knowledge Graph dependency analysis
39405
39520
  `;
@@ -39570,12 +39685,13 @@ var PytestGenerator = class extends BaseTestGenerator {
39570
39685
  const exports = this.extractExports(analysis.functions, analysis.classes);
39571
39686
  const pythonImport = importPath.replace(/\//g, ".").replace(/\.(ts|js)$/, "");
39572
39687
  const importStatement = exports.length > 0 ? `from ${pythonImport} import ${exports.join(", ")}` : `import ${pythonImport} as ${moduleName}`;
39688
+ const externalDeps = dependencies?.imports.filter((dep) => !dep.startsWith(".")) || [];
39573
39689
  let mockImport = "";
39574
- if (dependencies && dependencies.imports.length > 0) {
39690
+ if (externalDeps.length > 0) {
39575
39691
  mockImport = `from unittest.mock import patch, MagicMock
39576
39692
  `;
39577
39693
  }
39578
- const depsToMock = dependencies?.imports.slice(0, 5) || [];
39694
+ const depsToMock = externalDeps.slice(0, 5);
39579
39695
  const patchDecorators = depsToMock.map((dep) => {
39580
39696
  const depModule = dep.replace(/\//g, ".").replace(/\.py$/, "");
39581
39697
  return `@patch('${depModule}')`;
@@ -39588,10 +39704,12 @@ class Test${this.pascalCase(moduleName)}:
39588
39704
  """${testType} tests for ${moduleName}"""
39589
39705
 
39590
39706
  `;
39591
- for (const fn of analysis.functions) {
39707
+ const exportedFns = analysis.functions.filter((fn) => fn.isExported);
39708
+ const exportedClasses = analysis.classes.filter((cls) => cls.isExported);
39709
+ for (const fn of exportedFns) {
39592
39710
  code += this.generateFunctionTestsWithPatches(fn, testType, patchDecorators);
39593
39711
  }
39594
- for (const cls of analysis.classes) {
39712
+ for (const cls of exportedClasses) {
39595
39713
  code += this.generateClassTests(cls, testType);
39596
39714
  }
39597
39715
  return code;
@@ -39611,15 +39729,22 @@ class Test${this.pascalCase(moduleName)}:
39611
39729
  const allParams = mockParams ? `self, ${mockParams}` : "self";
39612
39730
  const patchPrefix = patchDecorators.map((d74) => ` ${d74}
39613
39731
  `).join("");
39732
+ const isVoid = fn.returnType === "void" || fn.returnType === "Promise<void>" || fn.returnType === "None";
39614
39733
  let code = `${patchPrefix} def test_${fn.name}_valid_input(${allParams}):
39615
39734
  `;
39616
39735
  code += ` """Test ${fn.name} with valid input"""
39617
39736
  `;
39618
- code += ` result = ${fn.name}(${validParams})
39737
+ if (isVoid) {
39738
+ code += ` ${fn.name}(${validParams}) # void function
39739
+
39740
+ `;
39741
+ } else {
39742
+ code += ` result = ${fn.name}(${validParams})
39619
39743
  `;
39620
- code += ` assert result is not None
39744
+ code += ` assert result is not None
39621
39745
 
39622
39746
  `;
39747
+ }
39623
39748
  for (const param of fn.parameters) {
39624
39749
  if (!param.optional && param.type?.includes("str")) {
39625
39750
  code += `${patchPrefix} def test_${fn.name}_empty_${param.name}(${allParams}):
@@ -39662,13 +39787,20 @@ class Test${cls.name}:
39662
39787
  for (const method of cls.methods) {
39663
39788
  if (!method.name.startsWith("_")) {
39664
39789
  const methodParams = method.parameters.map((p74) => this.generatePythonTestValue(p74)).join(", ");
39790
+ const isVoid = method.returnType === "void" || method.returnType === "Promise<void>";
39665
39791
  code += ` def test_${method.name}(self, instance):
39666
39792
  `;
39667
- code += ` result = instance.${method.name}(${methodParams})
39793
+ if (isVoid) {
39794
+ code += ` instance.${method.name}(${methodParams})
39795
+
39668
39796
  `;
39669
- code += ` assert result is not None
39797
+ } else {
39798
+ code += ` result = instance.${method.name}(${methodParams})
39799
+ `;
39800
+ code += ` assert result is not None
39670
39801
 
39671
39802
  `;
39803
+ }
39672
39804
  }
39673
39805
  }
39674
39806
  return code;
@@ -39686,11 +39818,11 @@ class Test${cls.name}:
39686
39818
  const asyncDef = isAsync ? "async def" : "def";
39687
39819
  let mockImports = "";
39688
39820
  let mockPatches = "";
39689
- if (dependencies && dependencies.imports.length > 0) {
39821
+ const stubExternalDeps = dependencies?.imports.filter((dep) => !dep.startsWith(".")) || [];
39822
+ if (stubExternalDeps.length > 0) {
39690
39823
  mockImports = `from unittest.mock import patch, MagicMock
39691
39824
  `;
39692
- const depsToMock = dependencies.imports.slice(0, 5);
39693
- for (const dep of depsToMock) {
39825
+ for (const dep of stubExternalDeps.slice(0, 5)) {
39694
39826
  const depModule = dep.replace(/\//g, ".").replace(/\.py$/, "");
39695
39827
  mockPatches += ` @patch('${depModule}')
39696
39828
  `;
@@ -41331,12 +41463,18 @@ ${sourceCode}
41331
41463
  const callees = [];
41332
41464
  const callers = [];
41333
41465
  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
41466
  for (const match of tsImports) {
41336
41467
  imports.push(match[1]);
41337
41468
  }
41338
- for (const match of pyImports) {
41339
- imports.push(match[1] || match[2]);
41469
+ const isPython = filePath.endsWith(".py");
41470
+ if (isPython) {
41471
+ const pyImports = sourceContent.matchAll(/(?:^|\n)\s*(?:from\s+(\S+)\s+import|import\s+(\S+))/g);
41472
+ for (const match of pyImports) {
41473
+ const mod = match[1] || match[2];
41474
+ if (mod && /^[a-zA-Z_][\w.]*$/.test(mod)) {
41475
+ imports.push(mod);
41476
+ }
41477
+ }
41340
41478
  }
41341
41479
  const normalizedPath = filePath.replace(/\\/g, "/");
41342
41480
  const baseName = normalizedPath.split("/").pop()?.replace(/\.(ts|js|tsx|jsx|py)$/, "") || "";
@@ -42365,7 +42503,7 @@ var PatternMatcherService = class _PatternMatcherService {
42365
42503
  return template.replace("{{name}}", param.name);
42366
42504
  }
42367
42505
  }
42368
- return `mock${param.name.charAt(0).toUpperCase() + param.name.slice(1)}`;
42506
+ return `{} /* TODO: provide ${param.name}: ${param.type || "unknown"} */`;
42369
42507
  }
42370
42508
  /**
42371
42509
  * Estimate number of branches from cyclomatic complexity
@@ -19,6 +19,9 @@ export declare function validateTableName(tableName: string): string;
19
19
  * Validate a PostgreSQL identifier (table or column name) against a strict regex.
20
20
  * Prevents SQL injection via dynamically interpolated identifiers (CWE-89).
21
21
  *
22
+ * Supports schema-qualified names like "aqe.qe_patterns" by validating each
23
+ * dot-separated part independently.
24
+ *
22
25
  * @param name - The identifier to validate
23
26
  * @returns The validated identifier string if valid
24
27
  * @throws {Error} If the identifier contains invalid characters or format
@@ -1 +1 @@
1
- {"version":3,"file":"sql-safety.d.ts","sourceRoot":"","sources":["../../src/shared/sql-safety.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,aAuB9B,CAAC;AAEH;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAK3D;AAQD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOvD"}
1
+ {"version":3,"file":"sql-safety.d.ts","sourceRoot":"","sources":["../../src/shared/sql-safety.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,aAuB9B,CAAC;AAEH;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAK3D;AAQD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBvD"}
@@ -52,13 +52,23 @@ const IDENTIFIER_REGEX = /^[a-z_][a-z0-9_]{0,62}$/;
52
52
  * Validate a PostgreSQL identifier (table or column name) against a strict regex.
53
53
  * Prevents SQL injection via dynamically interpolated identifiers (CWE-89).
54
54
  *
55
+ * Supports schema-qualified names like "aqe.qe_patterns" by validating each
56
+ * dot-separated part independently.
57
+ *
55
58
  * @param name - The identifier to validate
56
59
  * @returns The validated identifier string if valid
57
60
  * @throws {Error} If the identifier contains invalid characters or format
58
61
  */
59
62
  export function validateIdentifier(name) {
60
- if (!IDENTIFIER_REGEX.test(name)) {
61
- throw new Error(`Invalid SQL identifier: "${name}" does not match pattern ${IDENTIFIER_REGEX}`);
63
+ // Handle schema-qualified names (e.g., "aqe.qe_patterns")
64
+ const parts = name.split('.');
65
+ if (parts.length > 2) {
66
+ throw new Error(`Invalid SQL identifier: "${name}" has too many parts (max: schema.table)`);
67
+ }
68
+ for (const part of parts) {
69
+ if (!IDENTIFIER_REGEX.test(part)) {
70
+ throw new Error(`Invalid SQL identifier: "${name}" — part "${part}" does not match pattern ${IDENTIFIER_REGEX}`);
71
+ }
62
72
  }
63
73
  return name;
64
74
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sql-safety.js","sourceRoot":"","sources":["../../src/shared/sql-safety.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IACzC,qBAAqB;IACrB,gBAAgB,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa;IACtD,cAAc;IACd,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,sBAAsB;IAClE,uBAAuB;IACvB,eAAe,EAAE,eAAe,EAAE,cAAc,EAAE,gBAAgB;IAClE,oBAAoB;IACpB,aAAa,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,iBAAiB;IAC7E,mBAAmB;IACnB,YAAY,EAAE,mBAAmB,EAAE,gBAAgB;IACnD,gBAAgB;IAChB,kBAAkB,EAAE,gBAAgB,EAAE,sBAAsB;IAC5D,eAAe,EAAE,wBAAwB,EAAE,qBAAqB;IAChE,cAAc;IACd,eAAe;IACf,uBAAuB;IACvB,eAAe,EAAE,kBAAkB,EAAE,mBAAmB;IACxD,cAAc;IACd,UAAU;IACV,oBAAoB;IACpB,qBAAqB,EAAE,uBAAuB,EAAE,0BAA0B;IAC1E,8BAA8B,EAAE,4BAA4B;CAC7D,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,wBAAwB,SAAS,2BAA2B,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAEnD;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,4BAA4B,IAAI,4BAA4B,gBAAgB,EAAE,CAC/E,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"sql-safety.js","sourceRoot":"","sources":["../../src/shared/sql-safety.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IACzC,qBAAqB;IACrB,gBAAgB,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa;IACtD,cAAc;IACd,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,sBAAsB;IAClE,uBAAuB;IACvB,eAAe,EAAE,eAAe,EAAE,cAAc,EAAE,gBAAgB;IAClE,oBAAoB;IACpB,aAAa,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,iBAAiB;IAC7E,mBAAmB;IACnB,YAAY,EAAE,mBAAmB,EAAE,gBAAgB;IACnD,gBAAgB;IAChB,kBAAkB,EAAE,gBAAgB,EAAE,sBAAsB;IAC5D,eAAe,EAAE,wBAAwB,EAAE,qBAAqB;IAChE,cAAc;IACd,eAAe;IACf,uBAAuB;IACvB,eAAe,EAAE,kBAAkB,EAAE,mBAAmB;IACxD,cAAc;IACd,UAAU;IACV,oBAAoB;IACpB,qBAAqB,EAAE,uBAAuB,EAAE,0BAA0B;IAC1E,8BAA8B,EAAE,4BAA4B;CAC7D,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,wBAAwB,SAAS,2BAA2B,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAEnD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,0DAA0D;IAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,4BAA4B,IAAI,0CAA0C,CAC3E,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CACb,4BAA4B,IAAI,aAAa,IAAI,4BAA4B,gBAAgB,EAAE,CAChG,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -73,7 +73,12 @@ export declare class PostgresWriter implements CloudWriter {
73
73
  */
74
74
  private serializeValue;
75
75
  /**
76
- * Infer conflict columns from table name
76
+ * Check if a column is a JSONB column
77
+ */
78
+ private isJsonbColumn;
79
+ /**
80
+ * Infer conflict columns from table name and available columns.
81
+ * Only returns columns if the target table is known to have a matching unique constraint.
77
82
  */
78
83
  private inferConflictColumns;
79
84
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-writer.d.ts","sourceRoot":"","sources":["../../../src/sync/cloud/postgres-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AASzD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,0BAA0B;IAC1B,KAAK,EAAE,WAAW,CAAC;IAEnB,qBAAqB;IACrB,aAAa,EAAE,aAAa,CAAC;IAE7B,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,+BAA+B;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAYD;;GAEG;AACH,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,EAAE,oBAAoB;IAIxC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA4C9B;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAQvC;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ7B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ/B;;OAEG;IACG,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IA8BtF;;OAEG;YACW,WAAW;IAyDzB;;OAEG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAO7D;;OAEG;IACG,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAQ7D;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B;;OAEG;IACH,OAAO,CAAC,cAAc;IA0EtB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA4B5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAezB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,cAAc,CAEjF"}
1
+ {"version":3,"file":"postgres-writer.d.ts","sourceRoot":"","sources":["../../../src/sync/cloud/postgres-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAczD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,0BAA0B;IAC1B,KAAK,EAAE,WAAW,CAAC;IAEnB,qBAAqB;IACrB,aAAa,EAAE,aAAa,CAAC;IAE7B,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,+BAA+B;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAYD;;GAEG;AACH,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,EAAE,oBAAoB;IAIxC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiD9B;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAQvC;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ7B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ/B;;OAEG;IACG,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAoDtF;;OAEG;YACW,WAAW;IA0DzB;;OAEG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAO7D;;OAEG;IACG,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAQ7D;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B;;OAEG;IACH,OAAO,CAAC,cAAc;IA4FtB;;OAEG;IACH,OAAO,CAAC,aAAa;IAQrB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAyC5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAezB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,cAAc,CAEjF"}