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.
- package/.claude/skills/skills-manifest.json +1 -1
- package/package.json +3 -2
- package/v3/CHANGELOG.md +19 -0
- package/v3/dist/cli/bundle.js +561 -338
- 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/domains/test-generation/generators/base-test-generator.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/generators/base-test-generator.js +49 -27
- package/v3/dist/domains/test-generation/generators/base-test-generator.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 +53 -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 +46 -16
- package/v3/dist/domains/test-generation/generators/mocha-generator.js.map +1 -1
- package/v3/dist/domains/test-generation/generators/pytest-generator.d.ts.map +1 -1
- package/v3/dist/domains/test-generation/generators/pytest-generator.js +31 -14
- package/v3/dist/domains/test-generation/generators/pytest-generator.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 +2 -1
- 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 +11 -3
- package/v3/dist/domains/test-generation/services/test-generator.js.map +1 -1
- package/v3/dist/mcp/bundle.js +201 -63
- 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,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
|
-
|
|
38695
|
-
|
|
38696
|
-
|
|
38697
|
-
|
|
38698
|
-
|
|
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 =
|
|
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:
|
|
38726
|
-
assertion: "
|
|
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 =
|
|
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:
|
|
38736
|
-
assertion: "
|
|
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 =
|
|
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:
|
|
38744
|
-
assertion: "
|
|
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
|
-
|
|
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
|
-
|
|
38846
|
-
|
|
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
|
-
|
|
38891
|
+
}
|
|
38892
|
+
testCode += `
|
|
38893
|
+
`;
|
|
38894
|
+
} else {
|
|
38895
|
+
testCode += `
|
|
38851
38896
|
`;
|
|
38897
|
+
}
|
|
38852
38898
|
} else {
|
|
38853
38899
|
testCode += `
|
|
38854
38900
|
`;
|
|
38855
38901
|
}
|
|
38856
|
-
|
|
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
|
|
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
|
-
|
|
38997
|
+
if (isVoid) {
|
|
38998
|
+
code += ` ${methodCall};
|
|
38949
38999
|
`;
|
|
38950
|
-
|
|
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
|
-
|
|
38958
|
-
|
|
39015
|
+
if (methodThrows) {
|
|
39016
|
+
code += `
|
|
39017
|
+
it('should handle invalid ${param.name}', ${asyncPrefix}() => {
|
|
38959
39018
|
`;
|
|
38960
|
-
|
|
39019
|
+
code += ` expect(() => instance.${method.name}(${paramsWithUndefined})).toThrow();
|
|
38961
39020
|
`;
|
|
38962
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38987
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
39404
|
+
if (isVoid) {
|
|
39405
|
+
code += ` ${fnCall};
|
|
39406
|
+
`;
|
|
39407
|
+
} else {
|
|
39408
|
+
code += ` const result = ${fnCall};
|
|
39322
39409
|
`;
|
|
39323
|
-
|
|
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
|
-
|
|
39420
|
+
if (hasExplicitThrow) {
|
|
39421
|
+
code += `
|
|
39331
39422
|
it('should handle undefined ${param.name}', function() {
|
|
39332
39423
|
`;
|
|
39333
|
-
|
|
39424
|
+
code += ` expect(function() { ${fn.name}(${paramsWithUndefined}); }).to.throw();
|
|
39334
39425
|
`;
|
|
39335
|
-
|
|
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
|
-
|
|
39482
|
+
if (isVoid) {
|
|
39483
|
+
code += ` ${method.isAsync ? "await " : ""}instance.${method.name}(${methodParams});
|
|
39374
39484
|
`;
|
|
39375
|
-
|
|
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
|
-
|
|
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 =
|
|
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 (
|
|
39690
|
+
if (externalDeps.length > 0) {
|
|
39575
39691
|
mockImport = `from unittest.mock import patch, MagicMock
|
|
39576
39692
|
`;
|
|
39577
39693
|
}
|
|
39578
|
-
const depsToMock =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
39737
|
+
if (isVoid) {
|
|
39738
|
+
code += ` ${fn.name}(${validParams}) # void function
|
|
39739
|
+
|
|
39740
|
+
`;
|
|
39741
|
+
} else {
|
|
39742
|
+
code += ` result = ${fn.name}(${validParams})
|
|
39619
39743
|
`;
|
|
39620
|
-
|
|
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
|
-
|
|
39793
|
+
if (isVoid) {
|
|
39794
|
+
code += ` instance.${method.name}(${methodParams})
|
|
39795
|
+
|
|
39668
39796
|
`;
|
|
39669
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
41339
|
-
|
|
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 `
|
|
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
|
|
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
|
-
|
|
61
|
-
|
|
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
|
|
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
|
-
*
|
|
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;
|
|
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"}
|