@elench/testkit 0.1.60 → 0.1.62
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/lib/config/database.mjs +53 -0
- package/lib/config/database.test.mjs +29 -0
- package/lib/config/discovery-config.mjs +13 -0
- package/lib/config/env.mjs +55 -0
- package/lib/config/env.test.mjs +40 -0
- package/lib/config/index.mjs +21 -807
- package/lib/config/paths.mjs +28 -0
- package/lib/config/paths.test.mjs +27 -0
- package/lib/config/runtime.mjs +241 -0
- package/lib/config/runtime.test.mjs +56 -0
- package/lib/config/skip-config.mjs +189 -0
- package/lib/config/skip-config.test.mjs +63 -0
- package/lib/config/telemetry.mjs +28 -0
- package/lib/config/validation.mjs +124 -0
- package/lib/coverage/backend-discovery.mjs +183 -0
- package/lib/coverage/backend-discovery.test.mjs +52 -0
- package/lib/coverage/evidence.mjs +147 -0
- package/lib/coverage/evidence.test.mjs +77 -0
- package/lib/coverage/fs-walk.mjs +64 -0
- package/lib/coverage/graph-builder.mjs +181 -0
- package/lib/coverage/index.mjs +1 -816
- package/lib/coverage/index.test.mjs +330 -14
- package/lib/coverage/next-discovery.mjs +174 -0
- package/lib/coverage/next-static-analysis.mjs +763 -0
- package/lib/coverage/routing.mjs +86 -0
- package/lib/coverage/routing.test.mjs +52 -0
- package/lib/coverage/shared.mjs +198 -0
- package/lib/coverage/shared.test.mjs +39 -0
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-bridge/src/index.mjs +156 -13
- package/node_modules/@elench/testkit-bridge/src/index.test.mjs +39 -5
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/testkit-protocol/src/index.d.ts +26 -0
- package/node_modules/@elench/testkit-protocol/src/index.mjs +18 -0
- package/node_modules/@elench/testkit-protocol/src/index.test.mjs +75 -1
- package/package.json +5 -4
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { discoverDataCapabilities } from "./backend-discovery.mjs";
|
|
2
|
+
import { buildEvidenceDetails, inferCoveredNodeIdsForTest } from "./evidence.mjs";
|
|
3
|
+
import { findNextAppRoot, resolveImportToSourceFile, resolveServiceRoot } from "./fs-walk.mjs";
|
|
4
|
+
import { discoverApiRoutes, discoverPageViews, discoverServerActions } from "./next-discovery.mjs";
|
|
5
|
+
import {
|
|
6
|
+
apiRouteLookupKey,
|
|
7
|
+
appendGraph,
|
|
8
|
+
createEmptyGraph,
|
|
9
|
+
createTestFileNode,
|
|
10
|
+
normalizePath,
|
|
11
|
+
toSelectionType,
|
|
12
|
+
} from "./shared.mjs";
|
|
13
|
+
import { DEFAULT_DISCOVERY_EXCLUDES, normalizeDiscoveryConfig } from "../discovery/path-policy.mjs";
|
|
14
|
+
|
|
15
|
+
export function buildCoverageGraph({ productDir, repoDiscovery = {}, services = {}, discoveryFiles = [] }) {
|
|
16
|
+
const graph = createEmptyGraph();
|
|
17
|
+
const serviceContexts = new Map();
|
|
18
|
+
const normalizedRepoDiscovery = normalizeDiscoveryConfig(repoDiscovery, { allowRoots: true });
|
|
19
|
+
|
|
20
|
+
for (const [serviceName, config] of Object.entries(services)) {
|
|
21
|
+
const context = buildServiceCoverageContext(productDir, serviceName, config, normalizedRepoDiscovery);
|
|
22
|
+
if (!context) continue;
|
|
23
|
+
serviceContexts.set(serviceName, context);
|
|
24
|
+
appendGraph(graph, context.graph);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const evidence = [];
|
|
28
|
+
for (const entry of discoveryFiles) {
|
|
29
|
+
const context = serviceContexts.get(entry.serviceName);
|
|
30
|
+
const testNodeId = createTestFileNode(graph, entry);
|
|
31
|
+
if (!context) {
|
|
32
|
+
graph.diagnostics.push({
|
|
33
|
+
level: "warn",
|
|
34
|
+
code: "no-service-context",
|
|
35
|
+
filePath: entry.filePath,
|
|
36
|
+
service: entry.serviceName,
|
|
37
|
+
message: `No coverage context available for service "${entry.serviceName}".`,
|
|
38
|
+
});
|
|
39
|
+
evidence.push(createFallbackEvidence(entry, testNodeId));
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const coveredNodeIds = inferCoveredNodeIdsForTest(entry, context);
|
|
44
|
+
if (coveredNodeIds.length === 0) {
|
|
45
|
+
graph.diagnostics.push({
|
|
46
|
+
level: "info",
|
|
47
|
+
code: "zero-coverage-inferred",
|
|
48
|
+
filePath: entry.filePath,
|
|
49
|
+
service: entry.serviceName,
|
|
50
|
+
message: `No routes, API endpoints, or data capabilities matched for "${entry.filePath}".`,
|
|
51
|
+
});
|
|
52
|
+
evidence.push(createFallbackEvidence(entry, testNodeId));
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const nodeId of coveredNodeIds) {
|
|
57
|
+
graph.edges.push({
|
|
58
|
+
id: `covers:${testNodeId}:${nodeId}`,
|
|
59
|
+
kind: "covers",
|
|
60
|
+
from: testNodeId,
|
|
61
|
+
to: nodeId,
|
|
62
|
+
confidence: "high",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
evidence.push({
|
|
67
|
+
id: `evidence:${entry.serviceName}:${entry.filePath}`,
|
|
68
|
+
source: "convention",
|
|
69
|
+
confidence: "high",
|
|
70
|
+
service: entry.serviceName,
|
|
71
|
+
suiteName: entry.suiteName,
|
|
72
|
+
selectionType: toSelectionType(entry.type, entry.framework),
|
|
73
|
+
framework: entry.framework,
|
|
74
|
+
testFilePath: entry.filePath,
|
|
75
|
+
coveredNodeIds,
|
|
76
|
+
details: buildEvidenceDetails(coveredNodeIds, graph, entry, context),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
graph.evidence = evidence.sort((left, right) => left.id.localeCompare(right.id));
|
|
81
|
+
graph.nodes.sort((left, right) => left.id.localeCompare(right.id));
|
|
82
|
+
graph.edges.sort((left, right) => left.id.localeCompare(right.id));
|
|
83
|
+
return graph;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function buildServiceCoverageContext(productDir, serviceName, config, repoDiscovery = {}) {
|
|
87
|
+
const serviceRoot = resolveServiceRoot(productDir, config);
|
|
88
|
+
const nextAppRoot = findNextAppRoot(serviceRoot);
|
|
89
|
+
if (!nextAppRoot) return null;
|
|
90
|
+
const serviceDiscovery = normalizeDiscoveryConfig(config?.discovery, { allowRoots: true });
|
|
91
|
+
const exclude = [
|
|
92
|
+
...new Set([
|
|
93
|
+
...DEFAULT_DISCOVERY_EXCLUDES,
|
|
94
|
+
...((repoDiscovery.exclude || []).map(normalizePath)),
|
|
95
|
+
...((serviceDiscovery.exclude || []).map(normalizePath)),
|
|
96
|
+
]),
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const graph = createEmptyGraph();
|
|
100
|
+
const discoveryOptions = {
|
|
101
|
+
serviceName,
|
|
102
|
+
serviceRoot,
|
|
103
|
+
nextAppRoot,
|
|
104
|
+
exclude,
|
|
105
|
+
resolveImportToSourceFile,
|
|
106
|
+
};
|
|
107
|
+
const pages = discoverPageViews(discoveryOptions);
|
|
108
|
+
const apiRoutes = discoverApiRoutes(discoveryOptions);
|
|
109
|
+
const serverActions = discoverServerActions({
|
|
110
|
+
serviceName,
|
|
111
|
+
serviceRoot,
|
|
112
|
+
exclude,
|
|
113
|
+
resolveImportToSourceFile,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
for (const node of [...pages.nodes, ...apiRoutes.nodes, ...serverActions.nodes]) graph.nodes.push(node);
|
|
117
|
+
for (const edge of [...pages.edges, ...apiRoutes.edges, ...serverActions.edges]) graph.edges.push(edge);
|
|
118
|
+
|
|
119
|
+
const dataAugmentation = discoverDataCapabilities({ serviceName, serviceRoot, nodes: graph.nodes });
|
|
120
|
+
for (const node of dataAugmentation.nodes) graph.nodes.push(node);
|
|
121
|
+
for (const edge of dataAugmentation.edges) graph.edges.push(edge);
|
|
122
|
+
|
|
123
|
+
const pageByRoute = new Map(pages.pageEntries.map((entry) => [entry.route, entry]));
|
|
124
|
+
const apiRouteByKey = new Map(
|
|
125
|
+
apiRoutes.routeEntries.map((entry) => [apiRouteLookupKey(entry.method, entry.requestPath), entry])
|
|
126
|
+
);
|
|
127
|
+
const serverActionByExportKey = new Map(
|
|
128
|
+
serverActions.actionEntries.map((entry) => [`${entry.sourceFile}#${entry.exportName}`, entry])
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
for (const pageEntry of pages.pageEntries) {
|
|
132
|
+
for (const request of pageEntry.requests) {
|
|
133
|
+
const apiRoute = apiRouteByKey.get(apiRouteLookupKey(request.method, request.path));
|
|
134
|
+
if (!apiRoute) continue;
|
|
135
|
+
graph.edges.push({
|
|
136
|
+
id: `handles:${request.node.id}:${apiRoute.node.id}`,
|
|
137
|
+
kind: "handles",
|
|
138
|
+
from: request.node.id,
|
|
139
|
+
to: apiRoute.node.id,
|
|
140
|
+
confidence: "high",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const actionRef of pageEntry.serverActionRefs) {
|
|
145
|
+
const serverAction = serverActionByExportKey.get(actionRef.exportKey);
|
|
146
|
+
if (!serverAction) continue;
|
|
147
|
+
graph.edges.push({
|
|
148
|
+
id: `triggers:${actionRef.originNodeId}:${serverAction.node.id}`,
|
|
149
|
+
kind: "triggers",
|
|
150
|
+
from: actionRef.originNodeId,
|
|
151
|
+
to: serverAction.node.id,
|
|
152
|
+
confidence: actionRef.confidence,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
serviceName,
|
|
159
|
+
serviceRoot,
|
|
160
|
+
nextAppRoot,
|
|
161
|
+
graph,
|
|
162
|
+
pageByRoute,
|
|
163
|
+
apiRouteByKey,
|
|
164
|
+
serverActionByExportKey,
|
|
165
|
+
dataCapabilities: dataAugmentation.nodes,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function createFallbackEvidence(entry, testNodeId) {
|
|
170
|
+
return {
|
|
171
|
+
id: `evidence:${entry.serviceName}:${entry.filePath}`,
|
|
172
|
+
source: "convention",
|
|
173
|
+
confidence: "medium",
|
|
174
|
+
service: entry.serviceName,
|
|
175
|
+
suiteName: entry.suiteName,
|
|
176
|
+
selectionType: toSelectionType(entry.type, entry.framework),
|
|
177
|
+
framework: entry.framework,
|
|
178
|
+
testFilePath: entry.filePath,
|
|
179
|
+
coveredNodeIds: [testNodeId],
|
|
180
|
+
};
|
|
181
|
+
}
|