api-tests-coverage 1.0.15 → 1.0.17
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/dist/dashboard/dist/assets/_basePickBy-C2jmWITn.js +1 -0
- package/dist/dashboard/dist/assets/_baseUniq-DE6cyzJb.js +1 -0
- package/dist/dashboard/dist/assets/arc-B-Q4nGPT.js +1 -0
- package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-C_5dqWCI.js +36 -0
- package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-DbGIO6Kt.js +122 -0
- package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-CAFpcejP.js +10 -0
- package/dist/dashboard/dist/assets/channel-Di9el3wE.js +1 -0
- package/dist/dashboard/dist/assets/chunk-4BX2VUAB-DY1boKsq.js +1 -0
- package/dist/dashboard/dist/assets/chunk-55IACEB6-BSL35gyW.js +1 -0
- package/dist/dashboard/dist/assets/chunk-B4BG7PRW-eTDXrKrv.js +165 -0
- package/dist/dashboard/dist/assets/chunk-DI55MBZ5-M-8I3jEy.js +220 -0
- package/dist/dashboard/dist/assets/chunk-FMBD7UC4-bSA0XiS0.js +15 -0
- package/dist/dashboard/dist/assets/chunk-QN33PNHL-BrOIYUBs.js +1 -0
- package/dist/dashboard/dist/assets/chunk-QZHKN3VN-CliaQGD4.js +1 -0
- package/dist/dashboard/dist/assets/chunk-TZMSLE5B-CyhcxGB1.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-BkGN4Cpz.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-BkGN4Cpz.js +1 -0
- package/dist/dashboard/dist/assets/clone-Cvq8JuOb.js +1 -0
- package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-BUkL7Wtq.js +1 -0
- package/dist/dashboard/dist/assets/dagre-6UL2VRFP-B8oEROJc.js +4 -0
- package/dist/dashboard/dist/assets/diagram-PSM6KHXK-5uki9Dw8.js +24 -0
- package/dist/dashboard/dist/assets/diagram-QEK2KX5R-BRNhmby2.js +43 -0
- package/dist/dashboard/dist/assets/diagram-S2PKOQOG-D-ku_X8U.js +24 -0
- package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-DGl6gPe2.js +60 -0
- package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-Co89qYBD.js +162 -0
- package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-2r3WpWQC.js +267 -0
- package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-CuJ5l3TK.js +65 -0
- package/dist/dashboard/dist/assets/graph-ZtgwAPQj.js +1 -0
- package/dist/dashboard/dist/assets/index-D3sRJga7.js +777 -0
- package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-ujnMqVz3.js +2 -0
- package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-DQzfeBIo.js +139 -0
- package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-ueIaoeks.js +89 -0
- package/dist/dashboard/dist/assets/layout-B1fTYUMj.js +1 -0
- package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-B7wYeLe1.js +68 -0
- package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-Bf8vKEOf.js +30 -0
- package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-CM8qiFLR.js +7 -0
- package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-DPTtP4Ve.js +64 -0
- package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-DEVTdH0h.js +10 -0
- package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-Bjr5wgXg.js +145 -0
- package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-DDrhZYly.js +1 -0
- package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-Im6pH8C-.js +1 -0
- package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-DAT3r9va.js +61 -0
- package/dist/dashboard/dist/assets/treemap-GDKQZRPO-BlA8rg0m.js +162 -0
- package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-7aSkQtVu.js +7 -0
- package/dist/src/ast/astTypes.d.ts +86 -0
- package/dist/src/ast/astTypes.d.ts.map +1 -1
- package/dist/src/discovery/fileClassifier.d.ts.map +1 -1
- package/dist/src/discovery/fileClassifier.js +15 -16
- package/dist/src/discovery/frameworkDetector.d.ts +28 -0
- package/dist/src/discovery/frameworkDetector.d.ts.map +1 -0
- package/dist/src/discovery/frameworkDetector.js +189 -0
- package/dist/src/discovery/projectDiscovery.d.ts +5 -1
- package/dist/src/discovery/projectDiscovery.d.ts.map +1 -1
- package/dist/src/discovery/projectDiscovery.js +8 -1
- package/dist/src/inference/routeInference.d.ts.map +1 -1
- package/dist/src/inference/routeInference.js +224 -1
- package/dist/src/languages/java/graphqlSchemaParser.d.ts +65 -0
- package/dist/src/languages/java/graphqlSchemaParser.d.ts.map +1 -0
- package/dist/src/languages/java/graphqlSchemaParser.js +164 -0
- package/dist/src/languages/java/mybatisXmlParser.d.ts +52 -0
- package/dist/src/languages/java/mybatisXmlParser.d.ts.map +1 -0
- package/dist/src/languages/java/mybatisXmlParser.js +107 -0
- package/dist/src/languages/java/semanticBuilder.d.ts.map +1 -1
- package/dist/src/languages/java/semanticBuilder.js +69 -12
- package/dist/src/languages/javascript/angularDetector.d.ts +74 -0
- package/dist/src/languages/javascript/angularDetector.d.ts.map +1 -0
- package/dist/src/languages/javascript/angularDetector.js +227 -0
- package/dist/src/languages/javascript/assertionResolver.js +6 -4
- package/dist/src/languages/javascript/hapiDetector.d.ts +40 -0
- package/dist/src/languages/javascript/hapiDetector.d.ts.map +1 -0
- package/dist/src/languages/javascript/hapiDetector.js +174 -0
- package/dist/src/languages/javascript/mongooseDetector.d.ts +65 -0
- package/dist/src/languages/javascript/mongooseDetector.d.ts.map +1 -0
- package/dist/src/languages/javascript/mongooseDetector.js +237 -0
- package/dist/src/languages/javascript/vueDetector.d.ts +42 -0
- package/dist/src/languages/javascript/vueDetector.d.ts.map +1 -0
- package/dist/src/languages/javascript/vueDetector.js +109 -0
- package/dist/src/languages/python/index.d.ts +6 -2
- package/dist/src/languages/python/index.d.ts.map +1 -1
- package/dist/src/languages/python/index.js +200 -5
- package/dist/src/languages/python/testPatternDetector.d.ts +70 -0
- package/dist/src/languages/python/testPatternDetector.d.ts.map +1 -0
- package/dist/src/languages/python/testPatternDetector.js +201 -0
- package/dist/src/pipeline/confidence.d.ts +6 -1
- package/dist/src/pipeline/confidence.d.ts.map +1 -1
- package/dist/src/pipeline/confidence.js +8 -3
- package/dist/src/pipeline/graph.d.ts.map +1 -1
- package/dist/src/pipeline/graph.js +16 -4
- package/dist/src/pipeline/stages/ast/astStage.d.ts.map +1 -1
- package/dist/src/pipeline/stages/ast/astStage.js +51 -1
- package/dist/src/pipeline/stages/ast/baseUrlComposer.d.ts +44 -0
- package/dist/src/pipeline/stages/ast/baseUrlComposer.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/baseUrlComposer.js +97 -0
- package/dist/src/pipeline/stages/ast/crossFileResolutionPass.d.ts +54 -0
- package/dist/src/pipeline/stages/ast/crossFileResolutionPass.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/crossFileResolutionPass.js +88 -0
- package/dist/src/pipeline/stages/ast/crossFileResolver.d.ts.map +1 -1
- package/dist/src/pipeline/stages/ast/crossFileResolver.js +39 -1
- package/dist/src/pipeline/stages/ast/graphBuilder.d.ts.map +1 -1
- package/dist/src/pipeline/stages/ast/graphBuilder.js +81 -0
- package/dist/src/pipeline/stages/ast/optionalAuthUnifier.d.ts +41 -0
- package/dist/src/pipeline/stages/ast/optionalAuthUnifier.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/optionalAuthUnifier.js +101 -0
- package/dist/src/pipeline/stages/ast/resolvers/angularInjectionResolver.d.ts +18 -0
- package/dist/src/pipeline/stages/ast/resolvers/angularInjectionResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/resolvers/angularInjectionResolver.js +96 -0
- package/dist/src/pipeline/stages/ast/resolvers/dddLayerResolver.d.ts +46 -0
- package/dist/src/pipeline/stages/ast/resolvers/dddLayerResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/resolvers/dddLayerResolver.js +314 -0
- package/dist/src/pipeline/stages/ast/resolvers/expressRouterResolver.d.ts +17 -0
- package/dist/src/pipeline/stages/ast/resolvers/expressRouterResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/resolvers/expressRouterResolver.js +65 -0
- package/dist/src/pipeline/stages/ast/resolvers/flaskBlueprintResolver.d.ts +17 -0
- package/dist/src/pipeline/stages/ast/resolvers/flaskBlueprintResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/resolvers/flaskBlueprintResolver.js +114 -0
- package/dist/src/pipeline/stages/ast/resolvers/mybatisResolver.d.ts +27 -0
- package/dist/src/pipeline/stages/ast/resolvers/mybatisResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/resolvers/mybatisResolver.js +130 -0
- package/dist/src/pipeline/stages/ast/resolvers/vuexActionResolver.d.ts +17 -0
- package/dist/src/pipeline/stages/ast/resolvers/vuexActionResolver.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/resolvers/vuexActionResolver.js +80 -0
- package/dist/src/pipeline/stages/ast/rulesEnforcer.d.ts +24 -0
- package/dist/src/pipeline/stages/ast/rulesEnforcer.d.ts.map +1 -0
- package/dist/src/pipeline/stages/ast/rulesEnforcer.js +411 -0
- package/dist/src/pipeline/stages/ast/types.d.ts +114 -1
- package/dist/src/pipeline/stages/ast/types.d.ts.map +1 -1
- package/dist/src/pipeline/stages/merge/conflictDetector.d.ts +2 -0
- package/dist/src/pipeline/stages/merge/conflictDetector.d.ts.map +1 -1
- package/dist/src/pipeline/stages/merge/conflictDetector.js +54 -2
- package/dist/src/pipeline/stages/merge/coverageMappingBuilder.d.ts.map +1 -1
- package/dist/src/pipeline/stages/merge/coverageMappingBuilder.js +67 -3
- package/dist/src/pipeline/stages/tia/mockBoundaryDetector.d.ts.map +1 -1
- package/dist/src/pipeline/stages/tia/mockBoundaryDetector.js +8 -1
- package/dist/src/pipeline/stages/tia/parameterizedTestExpander.js +8 -4
- package/dist/src/pipeline/stages/tia/testLayerClassifier.d.ts.map +1 -1
- package/dist/src/pipeline/stages/tia/testLayerClassifier.js +41 -10
- package/dist/src/pipeline/types.d.ts +1 -1
- package/dist/src/pipeline/types.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.PythonAnalyzer = void 0;
|
|
7
|
+
exports.classifyFlaskSecurity = classifyFlaskSecurity;
|
|
7
8
|
const parserRegistry_1 = require("../../ast/parserRegistry");
|
|
8
9
|
const treeSitterUtils_1 = require("../shared/treeSitterUtils");
|
|
9
10
|
const resolvePaths_1 = require("../../coverage/deep-analysis/resolvePaths");
|
|
11
|
+
const testPatternDetector_1 = require("./testPatternDetector");
|
|
10
12
|
// ─── Parser ───────────────────────────────────────────────────────────────────
|
|
11
13
|
let cachedParser = undefined;
|
|
12
14
|
let parserLoaded = false;
|
|
@@ -44,7 +46,7 @@ class PythonAnalyzer {
|
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
buildSemanticModel(parsed, _context) {
|
|
47
|
-
var _a, _b;
|
|
49
|
+
var _a, _b, _c, _d;
|
|
48
50
|
const root = (_b = (_a = parsed.ast) === null || _a === void 0 ? void 0 : _a.rootNode) !== null && _b !== void 0 ? _b : parsed.ast;
|
|
49
51
|
if (!root)
|
|
50
52
|
return emptyModel(parsed.filePath);
|
|
@@ -52,6 +54,23 @@ class PythonAnalyzer {
|
|
|
52
54
|
const functions = extractPythonFunctions(root, constants);
|
|
53
55
|
const assertions = extractPythonAssertions(root);
|
|
54
56
|
const businessRuleRefs = extractPythonBusinessRefs(root);
|
|
57
|
+
// Feature 27: Flask/FastAPI pattern detection
|
|
58
|
+
const decoratorStacks = extractFlaskDecoratorStacks(root, parsed.filePath);
|
|
59
|
+
const routeRegistrations = extractFlaskRouteRegistrations(root, parsed.filePath);
|
|
60
|
+
// Feature 27: webtest API call detection
|
|
61
|
+
const sourceText = (_d = (_c = root.text) !== null && _c !== void 0 ? _c : parsed.content) !== null && _d !== void 0 ? _d : '';
|
|
62
|
+
const webtestCalls = (0, testPatternDetector_1.detectWebtestCalls)(sourceText, parsed.filePath);
|
|
63
|
+
if (webtestCalls.length > 0) {
|
|
64
|
+
const httpCalls = (0, testPatternDetector_1.webtestCallsToHttpCalls)(webtestCalls);
|
|
65
|
+
// Merge webtest HTTP calls into the functions map under a synthetic entry
|
|
66
|
+
const webtestFunc = {
|
|
67
|
+
name: '__webtest_calls__',
|
|
68
|
+
parameters: [],
|
|
69
|
+
bodyHttpCalls: httpCalls,
|
|
70
|
+
calledFunctions: [],
|
|
71
|
+
};
|
|
72
|
+
functions.set('__webtest_calls__', webtestFunc);
|
|
73
|
+
}
|
|
55
74
|
return {
|
|
56
75
|
filePath: parsed.filePath,
|
|
57
76
|
language: 'python',
|
|
@@ -63,6 +82,8 @@ class PythonAnalyzer {
|
|
|
63
82
|
assertions,
|
|
64
83
|
businessRuleRefs,
|
|
65
84
|
flowRefs: [],
|
|
85
|
+
decoratorStacks,
|
|
86
|
+
routeRegistrations,
|
|
66
87
|
};
|
|
67
88
|
}
|
|
68
89
|
extractHttpInteractions(model, _ctx) {
|
|
@@ -97,8 +118,23 @@ class PythonAnalyzer {
|
|
|
97
118
|
extractBusinessRuleRefs(model) {
|
|
98
119
|
return model.businessRuleRefs;
|
|
99
120
|
}
|
|
100
|
-
extractFlowRefs(
|
|
101
|
-
|
|
121
|
+
extractFlowRefs(model) {
|
|
122
|
+
// Feature 27: Resolve pytest fixture chains
|
|
123
|
+
const refs = [...model.flowRefs];
|
|
124
|
+
// Use function parameter names to discover fixture dependencies
|
|
125
|
+
// In pytest, function parameters that aren't built-in fixtures are fixture references
|
|
126
|
+
const builtinFixtures = new Set([
|
|
127
|
+
'request', 'tmp_path', 'tmpdir', 'capsys', 'capfd', 'monkeypatch',
|
|
128
|
+
'pytestconfig', 'recwarn', 'caplog', 'cache', 'self',
|
|
129
|
+
]);
|
|
130
|
+
for (const [funcName, func] of model.functions) {
|
|
131
|
+
for (const param of func.parameters) {
|
|
132
|
+
if (param && !builtinFixtures.has(param)) {
|
|
133
|
+
refs.push({ flowId: `fixture:${param}`, source: 'tag' });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return refs;
|
|
102
138
|
}
|
|
103
139
|
}
|
|
104
140
|
exports.PythonAnalyzer = PythonAnalyzer;
|
|
@@ -179,10 +215,12 @@ function extractPythonHttpCalls(body, constants, out) {
|
|
|
179
215
|
if (!funcNode)
|
|
180
216
|
continue;
|
|
181
217
|
const funcText = (_c = funcNode.text) !== null && _c !== void 0 ? _c : '';
|
|
182
|
-
const match = funcText.match(/(?:requests|httpx|self\.client|client|self\.app|app)\.(get|post|put|patch|delete|head|options)/i);
|
|
218
|
+
const match = funcText.match(/(?:requests|httpx|self\.client|client|self\.app|app|testapp|self\.testapp)\.(get|post|post_json|put|put_json|patch|patch_json|delete|delete_json|head|options)/i);
|
|
183
219
|
if (!match)
|
|
184
220
|
continue;
|
|
185
221
|
const [, methodName] = match;
|
|
222
|
+
// Normalize webtest methods: post_json → POST, put_json → PUT, etc.
|
|
223
|
+
const normalizedMethod = methodName.replace(/_json$/i, '').toUpperCase();
|
|
186
224
|
const argList = (_e = (_d = call.childForFieldName) === null || _d === void 0 ? void 0 : _d.call(call, 'arguments')) !== null && _e !== void 0 ? _e : (0, treeSitterUtils_1.firstChildOfType)(call, 'argument_list');
|
|
187
225
|
if (!argList)
|
|
188
226
|
continue;
|
|
@@ -195,7 +233,7 @@ function extractPythonHttpCalls(body, constants, out) {
|
|
|
195
233
|
// Resolve f-string variables against constants
|
|
196
234
|
const resolvedPath = rawPath.includes('{') ? resolveFString(rawPath, constants) : rawPath;
|
|
197
235
|
out.push({
|
|
198
|
-
method:
|
|
236
|
+
method: normalizedMethod,
|
|
199
237
|
rawPathArg: rawPath,
|
|
200
238
|
resolvedPath,
|
|
201
239
|
normalizedPath: resolvedPath.startsWith('/') ? (0, resolvePaths_1.normalizePathToTemplate)(resolvedPath) : undefined,
|
|
@@ -290,4 +328,161 @@ function emptyModel(filePath) {
|
|
|
290
328
|
flowRefs: [],
|
|
291
329
|
};
|
|
292
330
|
}
|
|
331
|
+
// ─── Flask / FastAPI pattern extraction (Feature 27) ─────────────────────────
|
|
332
|
+
/**
|
|
333
|
+
* Extract decorator stacks for Flask/FastAPI route functions.
|
|
334
|
+
* Groups decorators by the function they decorate:
|
|
335
|
+
* @blueprint.route('/articles', methods=['GET'])
|
|
336
|
+
* @use_kwargs({...})
|
|
337
|
+
* @marshal_with(ArticleSchema)
|
|
338
|
+
* @jwt_required
|
|
339
|
+
* def get_articles():
|
|
340
|
+
* → DecoratorStack for 'get_articles' with 4 decorators
|
|
341
|
+
*/
|
|
342
|
+
function extractFlaskDecoratorStacks(root, filePath) {
|
|
343
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
344
|
+
const stacks = [];
|
|
345
|
+
const fnDefs = (0, treeSitterUtils_1.findNodes)(root, ['function_definition']);
|
|
346
|
+
for (const fn of fnDefs) {
|
|
347
|
+
const nameNode = (_b = (_a = fn.childForFieldName) === null || _a === void 0 ? void 0 : _a.call(fn, 'name')) !== null && _b !== void 0 ? _b : (0, treeSitterUtils_1.firstChildOfType)(fn, 'identifier');
|
|
348
|
+
const funcName = (_c = nameNode === null || nameNode === void 0 ? void 0 : nameNode.text) !== null && _c !== void 0 ? _c : '';
|
|
349
|
+
if (!funcName)
|
|
350
|
+
continue;
|
|
351
|
+
const decorators = [];
|
|
352
|
+
for (let i = 0; i < ((_d = fn === null || fn === void 0 ? void 0 : fn.childCount) !== null && _d !== void 0 ? _d : 0); i++) {
|
|
353
|
+
const child = fn.child(i);
|
|
354
|
+
if ((child === null || child === void 0 ? void 0 : child.type) !== 'decorator')
|
|
355
|
+
continue;
|
|
356
|
+
const decText = (_e = child.text) !== null && _e !== void 0 ? _e : '';
|
|
357
|
+
const dec = parseFlaskDecorator(decText, (_f = child.startPosition) === null || _f === void 0 ? void 0 : _f.row);
|
|
358
|
+
if (dec)
|
|
359
|
+
decorators.push(dec);
|
|
360
|
+
}
|
|
361
|
+
if (decorators.length > 0) {
|
|
362
|
+
stacks.push({
|
|
363
|
+
functionName: funcName,
|
|
364
|
+
decorators,
|
|
365
|
+
sourceFile: filePath,
|
|
366
|
+
line: ((_g = fn.startPosition) === null || _g === void 0 ? void 0 : _g.row) ? fn.startPosition.row + 1 : undefined,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return stacks;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Parse a single Flask/FastAPI decorator into a DecoratorInfo.
|
|
374
|
+
*/
|
|
375
|
+
function parseFlaskDecorator(text, line) {
|
|
376
|
+
// @blueprint.route('/path', methods=['GET', 'POST'])
|
|
377
|
+
const routeMatch = text.match(/@(\w+)\.route\s*\(\s*['"]([^'"]+)['"]/);
|
|
378
|
+
if (routeMatch) {
|
|
379
|
+
const args = { path: routeMatch[2] };
|
|
380
|
+
const methodsMatch = text.match(/methods\s*=\s*\[([^\]]+)\]/);
|
|
381
|
+
if (methodsMatch)
|
|
382
|
+
args.methods = methodsMatch[1].replace(/['"]/g, '').trim();
|
|
383
|
+
return { name: 'route', fullText: text, args, line };
|
|
384
|
+
}
|
|
385
|
+
// @use_kwargs({...}) or @use_kwargs(SchemaClass)
|
|
386
|
+
const useKwargsMatch = text.match(/@use_kwargs\s*\((.+)\)/s);
|
|
387
|
+
if (useKwargsMatch) {
|
|
388
|
+
return { name: 'use_kwargs', fullText: text, args: { schema: useKwargsMatch[1].trim() }, line };
|
|
389
|
+
}
|
|
390
|
+
// @marshal_with(SchemaClass)
|
|
391
|
+
const marshalMatch = text.match(/@marshal_with\s*\(\s*(\w+)/);
|
|
392
|
+
if (marshalMatch) {
|
|
393
|
+
return { name: 'marshal_with', fullText: text, args: { schema: marshalMatch[1] }, line };
|
|
394
|
+
}
|
|
395
|
+
// @jwt_required, @jwt_required(), @jwt_required(optional=True), @jwt_optional
|
|
396
|
+
if (text.includes('@jwt_required') || text.includes('@jwt_optional')) {
|
|
397
|
+
const isOptional = text.includes('jwt_optional') || text.includes('optional=True') || text.includes('optional = True');
|
|
398
|
+
return {
|
|
399
|
+
name: isOptional ? 'jwt_optional' : 'jwt_required',
|
|
400
|
+
fullText: text,
|
|
401
|
+
args: { optional: isOptional ? 'true' : 'false' },
|
|
402
|
+
line,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
// @login_required
|
|
406
|
+
if (text.includes('@login_required')) {
|
|
407
|
+
return { name: 'login_required', fullText: text, args: {}, line };
|
|
408
|
+
}
|
|
409
|
+
// Generic decorator
|
|
410
|
+
const genericMatch = text.match(/@(\w[\w.]*)/);
|
|
411
|
+
if (genericMatch) {
|
|
412
|
+
return { name: genericMatch[1], fullText: text, line };
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Extract Flask Blueprint route registrations.
|
|
418
|
+
* Detects: Blueprint() constructors, register_blueprint() calls,
|
|
419
|
+
* @blueprint.route() registrations.
|
|
420
|
+
*/
|
|
421
|
+
function extractFlaskRouteRegistrations(root, filePath) {
|
|
422
|
+
var _a, _b, _c;
|
|
423
|
+
const registrations = [];
|
|
424
|
+
// Use regex on the full source text since tree-sitter node traversal
|
|
425
|
+
// for call arguments is complex. This is a targeted pattern match.
|
|
426
|
+
const sourceText = (_a = root.text) !== null && _a !== void 0 ? _a : '';
|
|
427
|
+
const lines = sourceText.split('\n');
|
|
428
|
+
for (let i = 0; i < lines.length; i++) {
|
|
429
|
+
const line = lines[i];
|
|
430
|
+
// Blueprint constructor: bp = Blueprint('name', __name__, url_prefix='/api')
|
|
431
|
+
const bpMatch = line.match(/(\w+)\s*=\s*Blueprint\s*\(\s*['"](\w+)['"](?:[^)]*?url_prefix\s*=\s*['"]([^'"]+)['"])?\s*\)/);
|
|
432
|
+
if (bpMatch) {
|
|
433
|
+
registrations.push({
|
|
434
|
+
registrarName: bpMatch[1],
|
|
435
|
+
path: (_b = bpMatch[3]) !== null && _b !== void 0 ? _b : '',
|
|
436
|
+
sourceFile: filePath,
|
|
437
|
+
line: i + 1,
|
|
438
|
+
});
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
// register_blueprint(bp, url_prefix='/api/articles')
|
|
442
|
+
const regMatch = line.match(/register_blueprint\s*\(\s*(\w+)(?:\s*,\s*url_prefix\s*=\s*['"]([^'"]+)['"])?\s*\)/);
|
|
443
|
+
if (regMatch) {
|
|
444
|
+
registrations.push({
|
|
445
|
+
registrarName: regMatch[1],
|
|
446
|
+
path: (_c = regMatch[2]) !== null && _c !== void 0 ? _c : '',
|
|
447
|
+
targetModule: regMatch[1],
|
|
448
|
+
sourceFile: filePath,
|
|
449
|
+
line: i + 1,
|
|
450
|
+
});
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
// @blueprint.route('/path', methods=[...])
|
|
454
|
+
const routeMatch = line.match(/@(\w+)\.route\s*\(\s*['"]([^'"]+)['"]/);
|
|
455
|
+
if (routeMatch) {
|
|
456
|
+
const methodsMatch = line.match(/methods\s*=\s*\[([^\]]+)\]/);
|
|
457
|
+
const methods = methodsMatch
|
|
458
|
+
? methodsMatch[1].replace(/['"]/g, '').split(',').map((m) => m.trim().toUpperCase())
|
|
459
|
+
: ['GET'];
|
|
460
|
+
registrations.push({
|
|
461
|
+
registrarName: routeMatch[1],
|
|
462
|
+
path: routeMatch[2],
|
|
463
|
+
methods,
|
|
464
|
+
sourceFile: filePath,
|
|
465
|
+
line: i + 1,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return registrations;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Classify JWT/auth security from decorator information.
|
|
473
|
+
*/
|
|
474
|
+
function classifyFlaskSecurity(decorators) {
|
|
475
|
+
for (const dec of decorators) {
|
|
476
|
+
if (dec.name === 'jwt_required') {
|
|
477
|
+
return { type: 'jwt', required: true, optional: false, sourcePattern: '@jwt_required' };
|
|
478
|
+
}
|
|
479
|
+
if (dec.name === 'jwt_optional') {
|
|
480
|
+
return { type: 'jwt', required: false, optional: true, sourcePattern: '@jwt_optional' };
|
|
481
|
+
}
|
|
482
|
+
if (dec.name === 'login_required') {
|
|
483
|
+
return { type: 'session', required: true, optional: false, sourcePattern: '@login_required' };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
293
488
|
(0, parserRegistry_1.registerAnalyzer)('python', () => new PythonAnalyzer());
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python test pattern detector (Feature 27, Sub-PR 4)
|
|
3
|
+
*
|
|
4
|
+
* Detects:
|
|
5
|
+
* 1. Factory Boy factories (factory.Factory subclasses, SubFactory, Meta.model)
|
|
6
|
+
* 2. webtest API calls (TestApp, testapp.get/post_json/put_json/delete)
|
|
7
|
+
* 3. pytest fixture chains (@pytest.fixture, fixture dependencies)
|
|
8
|
+
*/
|
|
9
|
+
import type { SemanticHttpCall } from '../../ast/astTypes';
|
|
10
|
+
export interface FactoryBoyFactory {
|
|
11
|
+
/** The factory class name (e.g. 'ArticleFactory') */
|
|
12
|
+
className: string;
|
|
13
|
+
/** The model the factory creates (from Meta.model) */
|
|
14
|
+
modelName?: string;
|
|
15
|
+
/** SubFactory references */
|
|
16
|
+
subFactories: string[];
|
|
17
|
+
/** RelatedFactoryList references */
|
|
18
|
+
relatedFactoryLists: string[];
|
|
19
|
+
sourceFile: string;
|
|
20
|
+
line?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Detect Factory Boy factory classes from source text.
|
|
24
|
+
* Looks for `class XFactory(factory.Factory):` and parses Meta.model, SubFactory, RelatedFactoryList.
|
|
25
|
+
*/
|
|
26
|
+
export declare function detectFactoryBoyFactories(sourceText: string, filePath: string): FactoryBoyFactory[];
|
|
27
|
+
export interface WebtestApiCall {
|
|
28
|
+
method: string;
|
|
29
|
+
path: string;
|
|
30
|
+
sourceFile: string;
|
|
31
|
+
line?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Detect webtest HTTP calls from source text.
|
|
35
|
+
* Looks for `testapp.get('/path')`, `testapp.post_json('/path', {...})`, etc.
|
|
36
|
+
*/
|
|
37
|
+
export declare function detectWebtestCalls(sourceText: string, filePath: string): WebtestApiCall[];
|
|
38
|
+
/**
|
|
39
|
+
* Convert webtest API calls into SemanticHttpCall objects.
|
|
40
|
+
*/
|
|
41
|
+
export declare function webtestCallsToHttpCalls(calls: WebtestApiCall[]): SemanticHttpCall[];
|
|
42
|
+
export interface PytestFixture {
|
|
43
|
+
/** Name of the fixture function */
|
|
44
|
+
name: string;
|
|
45
|
+
/** Scope: 'function' (default), 'class', 'module', 'session' */
|
|
46
|
+
scope: string;
|
|
47
|
+
/** Parameter names that are dependencies on other fixtures */
|
|
48
|
+
dependencies: string[];
|
|
49
|
+
sourceFile: string;
|
|
50
|
+
line?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Detect @pytest.fixture functions and their parameter dependencies.
|
|
54
|
+
*/
|
|
55
|
+
export declare function detectPytestFixtures(sourceText: string, filePath: string): PytestFixture[];
|
|
56
|
+
/**
|
|
57
|
+
* Detect if a file creates a webtest TestApp instance.
|
|
58
|
+
* Looks for `TestApp(app)` or `TestApp(...)`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function hasWebtestTestApp(sourceText: string): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Check if a file uses real DB fixtures (not mocked).
|
|
63
|
+
* Looks for pytest fixtures that reference db, database, session, engine, etc.
|
|
64
|
+
*/
|
|
65
|
+
export declare function hasRealDbFixtures(sourceText: string): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Check if a file uses Factory Boy factories.
|
|
68
|
+
*/
|
|
69
|
+
export declare function usesFactoryBoy(sourceText: string): boolean;
|
|
70
|
+
//# sourceMappingURL=testPatternDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testPatternDetector.d.ts","sourceRoot":"","sources":["../../../../src/languages/python/testPatternDetector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAmB,MAAM,oBAAoB,CAAC;AAK5E,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oCAAoC;IACpC,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAiEnG;AAID,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAiBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,EAAE,CA6BzF;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,gBAAgB,EAAE,CAUnF;AAID,MAAM,WAAW,aAAa;IAC5B,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CAyC1F;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAO7D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE1D"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Python test pattern detector (Feature 27, Sub-PR 4)
|
|
4
|
+
*
|
|
5
|
+
* Detects:
|
|
6
|
+
* 1. Factory Boy factories (factory.Factory subclasses, SubFactory, Meta.model)
|
|
7
|
+
* 2. webtest API calls (TestApp, testapp.get/post_json/put_json/delete)
|
|
8
|
+
* 3. pytest fixture chains (@pytest.fixture, fixture dependencies)
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.detectFactoryBoyFactories = detectFactoryBoyFactories;
|
|
12
|
+
exports.detectWebtestCalls = detectWebtestCalls;
|
|
13
|
+
exports.webtestCallsToHttpCalls = webtestCallsToHttpCalls;
|
|
14
|
+
exports.detectPytestFixtures = detectPytestFixtures;
|
|
15
|
+
exports.hasWebtestTestApp = hasWebtestTestApp;
|
|
16
|
+
exports.hasRealDbFixtures = hasRealDbFixtures;
|
|
17
|
+
exports.usesFactoryBoy = usesFactoryBoy;
|
|
18
|
+
/**
|
|
19
|
+
* Detect Factory Boy factory classes from source text.
|
|
20
|
+
* Looks for `class XFactory(factory.Factory):` and parses Meta.model, SubFactory, RelatedFactoryList.
|
|
21
|
+
*/
|
|
22
|
+
function detectFactoryBoyFactories(sourceText, filePath) {
|
|
23
|
+
const factories = [];
|
|
24
|
+
const lines = sourceText.split('\n');
|
|
25
|
+
// Regex for class definition inheriting from factory.Factory or factory.DjangoModelFactory etc.
|
|
26
|
+
const classPattern = /^class\s+(\w+)\s*\(\s*(?:factory\.(?:Factory|DjangoModelFactory|SQLAlchemyModelFactory|MongoEngineFactory)|Factory)\s*\)/;
|
|
27
|
+
const metaModelPattern = /model\s*=\s*(\w+)/;
|
|
28
|
+
const subFactoryPattern = /=\s*(?:factory\.)?SubFactory\s*\(\s*['"]?(\w+)/g;
|
|
29
|
+
const relatedFactoryPattern = /=\s*(?:factory\.)?RelatedFactoryList\s*\(\s*['"]?(\w+)/g;
|
|
30
|
+
let currentFactory = null;
|
|
31
|
+
let classIndent = -1;
|
|
32
|
+
for (let i = 0; i < lines.length; i++) {
|
|
33
|
+
const line = lines[i];
|
|
34
|
+
const indent = line.search(/\S/);
|
|
35
|
+
// If we're inside a factory class, check for end of class
|
|
36
|
+
if (currentFactory && indent >= 0 && indent <= classIndent && line.trim().length > 0) {
|
|
37
|
+
factories.push(currentFactory);
|
|
38
|
+
currentFactory = null;
|
|
39
|
+
classIndent = -1;
|
|
40
|
+
}
|
|
41
|
+
const classMatch = line.match(classPattern);
|
|
42
|
+
if (classMatch) {
|
|
43
|
+
if (currentFactory)
|
|
44
|
+
factories.push(currentFactory);
|
|
45
|
+
currentFactory = {
|
|
46
|
+
className: classMatch[1],
|
|
47
|
+
subFactories: [],
|
|
48
|
+
relatedFactoryLists: [],
|
|
49
|
+
sourceFile: filePath,
|
|
50
|
+
line: i + 1,
|
|
51
|
+
};
|
|
52
|
+
classIndent = indent;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (currentFactory) {
|
|
56
|
+
// Meta.model
|
|
57
|
+
const metaMatch = line.match(metaModelPattern);
|
|
58
|
+
if (metaMatch) {
|
|
59
|
+
currentFactory.modelName = metaMatch[1];
|
|
60
|
+
}
|
|
61
|
+
// SubFactory
|
|
62
|
+
let sfMatch;
|
|
63
|
+
subFactoryPattern.lastIndex = 0;
|
|
64
|
+
while ((sfMatch = subFactoryPattern.exec(line)) !== null) {
|
|
65
|
+
currentFactory.subFactories.push(sfMatch[1]);
|
|
66
|
+
}
|
|
67
|
+
// RelatedFactoryList
|
|
68
|
+
let rfMatch;
|
|
69
|
+
relatedFactoryPattern.lastIndex = 0;
|
|
70
|
+
while ((rfMatch = relatedFactoryPattern.exec(line)) !== null) {
|
|
71
|
+
currentFactory.relatedFactoryLists.push(rfMatch[1]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Push last factory if still open
|
|
76
|
+
if (currentFactory)
|
|
77
|
+
factories.push(currentFactory);
|
|
78
|
+
return factories;
|
|
79
|
+
}
|
|
80
|
+
/** Map webtest methods to HTTP methods */
|
|
81
|
+
const WEBTEST_METHOD_MAP = {
|
|
82
|
+
get: 'GET',
|
|
83
|
+
post: 'POST',
|
|
84
|
+
post_json: 'POST',
|
|
85
|
+
put: 'PUT',
|
|
86
|
+
put_json: 'PUT',
|
|
87
|
+
patch: 'PATCH',
|
|
88
|
+
patch_json: 'PATCH',
|
|
89
|
+
delete: 'DELETE',
|
|
90
|
+
delete_json: 'DELETE',
|
|
91
|
+
head: 'HEAD',
|
|
92
|
+
options: 'OPTIONS',
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Detect webtest HTTP calls from source text.
|
|
96
|
+
* Looks for `testapp.get('/path')`, `testapp.post_json('/path', {...})`, etc.
|
|
97
|
+
*/
|
|
98
|
+
function detectWebtestCalls(sourceText, filePath) {
|
|
99
|
+
const calls = [];
|
|
100
|
+
const lines = sourceText.split('\n');
|
|
101
|
+
// Pattern: any_var.METHOD('/path'...) where METHOD is a webtest HTTP method
|
|
102
|
+
const webtestMethods = Object.keys(WEBTEST_METHOD_MAP).join('|');
|
|
103
|
+
const callPattern = new RegExp(`\\w+\\.(${webtestMethods})\\s*\\(\\s*['"]([^'"]+)['"]`, 'g');
|
|
104
|
+
for (let i = 0; i < lines.length; i++) {
|
|
105
|
+
const line = lines[i];
|
|
106
|
+
callPattern.lastIndex = 0;
|
|
107
|
+
let match;
|
|
108
|
+
while ((match = callPattern.exec(line)) !== null) {
|
|
109
|
+
const method = WEBTEST_METHOD_MAP[match[1]];
|
|
110
|
+
if (method) {
|
|
111
|
+
calls.push({
|
|
112
|
+
method,
|
|
113
|
+
path: match[2],
|
|
114
|
+
sourceFile: filePath,
|
|
115
|
+
line: i + 1,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return calls;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Convert webtest API calls into SemanticHttpCall objects.
|
|
124
|
+
*/
|
|
125
|
+
function webtestCallsToHttpCalls(calls) {
|
|
126
|
+
return calls.map((call) => ({
|
|
127
|
+
method: call.method,
|
|
128
|
+
rawPathArg: call.path,
|
|
129
|
+
resolvedPath: call.path,
|
|
130
|
+
normalizedPath: call.path.startsWith('/') ? call.path : undefined,
|
|
131
|
+
resolutionType: 'direct',
|
|
132
|
+
confidence: 'high',
|
|
133
|
+
line: call.line,
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Detect @pytest.fixture functions and their parameter dependencies.
|
|
138
|
+
*/
|
|
139
|
+
function detectPytestFixtures(sourceText, filePath) {
|
|
140
|
+
const fixtures = [];
|
|
141
|
+
const lines = sourceText.split('\n');
|
|
142
|
+
for (let i = 0; i < lines.length; i++) {
|
|
143
|
+
const line = lines[i];
|
|
144
|
+
// @pytest.fixture or @pytest.fixture(scope='...')
|
|
145
|
+
const fixtureMatch = line.match(/@pytest\.fixture(?:\s*\(([^)]*)\))?/);
|
|
146
|
+
if (!fixtureMatch)
|
|
147
|
+
continue;
|
|
148
|
+
// Parse scope from decorator args
|
|
149
|
+
let scope = 'function';
|
|
150
|
+
if (fixtureMatch[1]) {
|
|
151
|
+
const scopeMatch = fixtureMatch[1].match(/scope\s*=\s*['"](\w+)['"]/);
|
|
152
|
+
if (scopeMatch)
|
|
153
|
+
scope = scopeMatch[1];
|
|
154
|
+
}
|
|
155
|
+
// Find the next def line (typically the line right after the decorator)
|
|
156
|
+
for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
|
|
157
|
+
const defMatch = lines[j].match(/def\s+(\w+)\s*\(([^)]*)\)/);
|
|
158
|
+
if (defMatch) {
|
|
159
|
+
const name = defMatch[1];
|
|
160
|
+
const params = defMatch[2]
|
|
161
|
+
.split(',')
|
|
162
|
+
.map((p) => p.trim().split(':')[0].split('=')[0].trim())
|
|
163
|
+
.filter((p) => p.length > 0 && p !== 'request');
|
|
164
|
+
fixtures.push({
|
|
165
|
+
name,
|
|
166
|
+
scope,
|
|
167
|
+
dependencies: params,
|
|
168
|
+
sourceFile: filePath,
|
|
169
|
+
line: j + 1,
|
|
170
|
+
});
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return fixtures;
|
|
176
|
+
}
|
|
177
|
+
// ─── TestApp Detection ──────────────────────────────────────────────────────
|
|
178
|
+
/**
|
|
179
|
+
* Detect if a file creates a webtest TestApp instance.
|
|
180
|
+
* Looks for `TestApp(app)` or `TestApp(...)`.
|
|
181
|
+
*/
|
|
182
|
+
function hasWebtestTestApp(sourceText) {
|
|
183
|
+
return /TestApp\s*\(/.test(sourceText);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Check if a file uses real DB fixtures (not mocked).
|
|
187
|
+
* Looks for pytest fixtures that reference db, database, session, engine, etc.
|
|
188
|
+
*/
|
|
189
|
+
function hasRealDbFixtures(sourceText) {
|
|
190
|
+
// Check for DB fixture names in function parameters
|
|
191
|
+
const dbFixturePattern = /def\s+\w+\s*\([^)]*\b(db|database|dbsession|db_session|engine|session|connection)\b/;
|
|
192
|
+
// Also check for common DB setup patterns
|
|
193
|
+
const dbSetupPattern = /(?:create_all|Base\.metadata|sessionmaker|create_engine|init_db|setup_database)/;
|
|
194
|
+
return dbFixturePattern.test(sourceText) || dbSetupPattern.test(sourceText);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Check if a file uses Factory Boy factories.
|
|
198
|
+
*/
|
|
199
|
+
function usesFactoryBoy(sourceText) {
|
|
200
|
+
return /(?:import\s+factory|from\s+factory\s+import|factory\.Factory|factory\.SubFactory)/.test(sourceText);
|
|
201
|
+
}
|
|
@@ -65,6 +65,11 @@ export declare function hasPartialResolution(graph: CoverageKnowledgeGraph, path
|
|
|
65
65
|
/**
|
|
66
66
|
* Build a ConfidenceEvidence object from a coverage path in the graph.
|
|
67
67
|
* Inspects the nodes and their source stages to determine what evidence exists.
|
|
68
|
+
*
|
|
69
|
+
* Optional `iastConfirmed` and `dastConfirmed` flags allow the caller to
|
|
70
|
+
* inject runtime confirmation state that may not be discoverable by walking
|
|
71
|
+
* `pathNodeIds` alone (e.g. when IAST/DAST nodes are not directly on the
|
|
72
|
+
* endpoint→test path in the graph).
|
|
68
73
|
*/
|
|
69
|
-
export declare function buildConfidenceEvidence(graph: CoverageKnowledgeGraph, pathNodeIds: string[], isStaticOnlyMode: boolean): ConfidenceEvidence;
|
|
74
|
+
export declare function buildConfidenceEvidence(graph: CoverageKnowledgeGraph, pathNodeIds: string[], isStaticOnlyMode: boolean, iastConfirmed?: boolean, dastConfirmed?: boolean): ConfidenceEvidence;
|
|
70
75
|
//# sourceMappingURL=confidence.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confidence.d.ts","sourceRoot":"","sources":["../../../src/pipeline/confidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAa,MAAM,SAAS,CAAC;AACjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAYtD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,kBAAkB,CAwBtF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,kBAAkB,EAC7B,QAAQ,EAAE,kBAAkB,GAC3B,kBAAkB,CAoBpB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,kBAAkB,CAGlF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,kBAAkB,GAC3B,kBAAkB,CAIpB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,kBAAkB,EACrB,CAAC,EAAE,kBAAkB,GACpB,MAAM,CAER;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,kBAAkB,CAS9E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,sBAAsB,EAC7B,WAAW,EAAE,MAAM,EAAE,GACpB,OAAO,CAMT;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,sBAAsB,EAC7B,WAAW,EAAE,MAAM,EAAE,GACpB,OAAO,CAMT;AAED
|
|
1
|
+
{"version":3,"file":"confidence.d.ts","sourceRoot":"","sources":["../../../src/pipeline/confidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAa,MAAM,SAAS,CAAC;AACjF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAYtD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,kBAAkB,CAwBtF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,kBAAkB,EAC7B,QAAQ,EAAE,kBAAkB,GAC3B,kBAAkB,CAoBpB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,kBAAkB,CAGlF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,kBAAkB,GAC3B,kBAAkB,CAIpB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,kBAAkB,EACrB,CAAC,EAAE,kBAAkB,GACpB,MAAM,CAER;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,kBAAkB,CAS9E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,sBAAsB,EAC7B,WAAW,EAAE,MAAM,EAAE,GACpB,OAAO,CAMT;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,sBAAsB,EAC7B,WAAW,EAAE,MAAM,EAAE,GACpB,OAAO,CAMT;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,sBAAsB,EAC7B,WAAW,EAAE,MAAM,EAAE,EACrB,gBAAgB,EAAE,OAAO,EACzB,aAAa,CAAC,EAAE,OAAO,EACvB,aAAa,CAAC,EAAE,OAAO,GACtB,kBAAkB,CA6CpB"}
|
|
@@ -151,8 +151,13 @@ function hasPartialResolution(graph, pathNodeIds) {
|
|
|
151
151
|
/**
|
|
152
152
|
* Build a ConfidenceEvidence object from a coverage path in the graph.
|
|
153
153
|
* Inspects the nodes and their source stages to determine what evidence exists.
|
|
154
|
+
*
|
|
155
|
+
* Optional `iastConfirmed` and `dastConfirmed` flags allow the caller to
|
|
156
|
+
* inject runtime confirmation state that may not be discoverable by walking
|
|
157
|
+
* `pathNodeIds` alone (e.g. when IAST/DAST nodes are not directly on the
|
|
158
|
+
* endpoint→test path in the graph).
|
|
154
159
|
*/
|
|
155
|
-
function buildConfidenceEvidence(graph, pathNodeIds, isStaticOnlyMode) {
|
|
160
|
+
function buildConfidenceEvidence(graph, pathNodeIds, isStaticOnlyMode, iastConfirmed, dastConfirmed) {
|
|
156
161
|
var _a, _b;
|
|
157
162
|
const sourceStages = new Set();
|
|
158
163
|
let hasIastConfirmation = false;
|
|
@@ -187,8 +192,8 @@ function buildConfidenceEvidence(graph, pathNodeIds, isStaticOnlyMode) {
|
|
|
187
192
|
}
|
|
188
193
|
return {
|
|
189
194
|
sourceStages: Array.from(sourceStages),
|
|
190
|
-
hasIastConfirmation,
|
|
191
|
-
hasDastConfirmation,
|
|
195
|
+
hasIastConfirmation: hasIastConfirmation || (iastConfirmed !== null && iastConfirmed !== void 0 ? iastConfirmed : false),
|
|
196
|
+
hasDastConfirmation: hasDastConfirmation || (dastConfirmed !== null && dastConfirmed !== void 0 ? dastConfirmed : false),
|
|
192
197
|
hasAssertionConfirmation,
|
|
193
198
|
hasMockBoundary,
|
|
194
199
|
hasPartialResolution: hasPartial,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../../src/pipeline/graph.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,SAAS,EACT,aAAa,EACb,aAAa,EACb,YAAY,EACZ,SAAS,EACV,MAAM,SAAS,CAAC;AAEjB,qBAAa,sBAAsB;IACjC,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,KAAK,CAAqC;IAIlD,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAI9B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI1C,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI5B,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;
|
|
1
|
+
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../../src/pipeline/graph.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,SAAS,EACT,aAAa,EACb,aAAa,EACb,YAAY,EACZ,SAAS,EACV,MAAM,SAAS,CAAC;AAEjB,qBAAa,sBAAsB;IACjC,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,KAAK,CAAqC;IAIlD,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAI9B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI1C,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI5B,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAa/B,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,EAAE;IAIhD,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,EAAE;IAI9C,WAAW,IAAI,SAAS,EAAE;IAI1B,IAAI,SAAS,IAAI,MAAM,CAEtB;IAID,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAI9B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI1C,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI5B,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI/B,mDAAmD;IACnD,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE;IAIzC,8CAA8C;IAC9C,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE;IAIvC,sEAAsE;IACtE,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE;IAQ9D,wCAAwC;IACxC,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,EAAE;IAIhD,WAAW,IAAI,SAAS,EAAE;IAI1B,IAAI,SAAS,IAAI,MAAM,CAEtB;IAID;;;OAGG;IACH,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,aAAa,EAAE,GAAG,SAAS,EAAE;IA4B5E;;;OAGG;IACH,iBAAiB,CACf,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,aAAa,GACtB,OAAO;IAmCV;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,sBAAsB,GAAG,YAAY,EAAE;IAwCpD,cAAc,IAAI;QAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAAC,KAAK,EAAE,SAAS,EAAE,CAAA;KAAE;IAO5D,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAAC,KAAK,EAAE,SAAS,EAAE,CAAA;KAAE,GAAG,sBAAsB;IAajG,KAAK,IAAI,IAAI;CAId"}
|
|
@@ -23,7 +23,16 @@ class CoverageKnowledgeGraph {
|
|
|
23
23
|
return this.nodes.has(id);
|
|
24
24
|
}
|
|
25
25
|
removeNode(id) {
|
|
26
|
-
|
|
26
|
+
const deleted = this.nodes.delete(id);
|
|
27
|
+
if (deleted) {
|
|
28
|
+
// Remove all edges that reference this node to avoid dangling edges
|
|
29
|
+
for (const [edgeId, edge] of this.edges) {
|
|
30
|
+
if (edge.sourceNodeId === id || edge.targetNodeId === id) {
|
|
31
|
+
this.edges.delete(edgeId);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return deleted;
|
|
27
36
|
}
|
|
28
37
|
getNodesByType(type) {
|
|
29
38
|
return Array.from(this.nodes.values()).filter((n) => n.type === type);
|
|
@@ -112,9 +121,11 @@ class CoverageKnowledgeGraph {
|
|
|
112
121
|
while (queue.length > 0) {
|
|
113
122
|
const path = queue.shift();
|
|
114
123
|
const currentId = path[path.length - 1];
|
|
115
|
-
|
|
124
|
+
// Don't skip endId — we need to check every path that arrives at it
|
|
125
|
+
if (currentId !== endId && visited.has(currentId))
|
|
116
126
|
continue;
|
|
117
|
-
|
|
127
|
+
if (currentId !== endId)
|
|
128
|
+
visited.add(currentId);
|
|
118
129
|
if (currentId === endId) {
|
|
119
130
|
// Check interior nodes (not start or end)
|
|
120
131
|
for (let i = 1; i < path.length - 1; i++) {
|
|
@@ -122,7 +133,8 @@ class CoverageKnowledgeGraph {
|
|
|
122
133
|
if ((node === null || node === void 0 ? void 0 : node.type) === nodeType)
|
|
123
134
|
return true;
|
|
124
135
|
}
|
|
125
|
-
return false
|
|
136
|
+
// Don't return false here — other paths may still contain the node type
|
|
137
|
+
continue;
|
|
126
138
|
}
|
|
127
139
|
const outEdges = this.getEdgesFrom(currentId);
|
|
128
140
|
for (const edge of outEdges) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"astStage.d.ts","sourceRoot":"","sources":["../../../../../src/pipeline/stages/ast/astStage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"astStage.d.ts","sourceRoot":"","sources":["../../../../../src/pipeline/stages/ast/astStage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AA2B9C,qBAAa,QAAS,YAAW,aAAa,CAAC,cAAc,CAAC;IAC5D,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;IAC/B,QAAQ,CAAC,QAAQ,SAAS;IAEpB,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;CAoNjE"}
|