adfinem 0.0.0 → 0.1.1
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/.env.example +13 -0
- package/CHANGELOG.md +17 -0
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +29 -0
- package/LICENSE +21 -0
- package/README.md +97 -3
- package/SECURITY.md +13 -0
- package/catalogs/.gitkeep +0 -0
- package/catalogs/api-operations.yaml +21 -0
- package/catalogs/batches.yaml +74 -0
- package/catalogs/queries.yaml +75 -0
- package/config/environments.yaml +13 -0
- package/dist/actions/assert-db.js +3 -0
- package/dist/actions/run-eod.js +3 -0
- package/dist/adapters/api/api-collections.js +296 -0
- package/dist/adapters/api/body-utils.js +9 -0
- package/dist/adapters/api/rest-client.js +557 -0
- package/dist/adapters/api/soap-client.js +5 -0
- package/dist/adapters/db/assertions.js +87 -0
- package/dist/adapters/db/oracle-client.js +115 -0
- package/dist/adapters/db/query-catalog.js +75 -0
- package/dist/adapters/unix/batch-catalog.js +71 -0
- package/dist/adapters/unix/batch-input-files.js +36 -0
- package/dist/adapters/unix/batch-runner.js +382 -0
- package/dist/adapters/unix/ssh-client.js +228 -0
- package/dist/app/server.js +827 -0
- package/dist/cli.js +516 -0
- package/dist/config/environments.js +138 -0
- package/dist/config/registry.js +18 -0
- package/dist/config/secrets.js +123 -0
- package/dist/dsl/parser.js +20 -0
- package/dist/dsl/schema.js +182 -0
- package/dist/dsl/types.js +1 -0
- package/dist/dsl/validator.js +264 -0
- package/dist/engine/captures.js +68 -0
- package/dist/engine/context.js +69 -0
- package/dist/engine/evidence.js +33 -0
- package/dist/engine/known-errors.js +129 -0
- package/dist/engine/retry.js +13 -0
- package/dist/engine/runner.js +710 -0
- package/dist/engine/step-result.js +58 -0
- package/dist/flows/catalog-normalizer.js +72 -0
- package/dist/flows/compiler.js +237 -0
- package/dist/flows/concat.js +130 -0
- package/dist/flows/parser.js +21 -0
- package/dist/flows/schema.js +142 -0
- package/dist/flows/types.js +1 -0
- package/dist/flows/validator.js +470 -0
- package/dist/reports/html-report.js +112 -0
- package/dist/reports/junit-report.js +48 -0
- package/docs/.gitkeep +0 -0
- package/docs/DB_UNIX_OPERATIONS.md +118 -0
- package/docs/FLOW_BUILDER.md +87 -0
- package/flows/account_processing_cycle.flow.yaml +88 -0
- package/flows/new_flow.flow.yaml +22 -0
- package/package.json +98 -11
- package/scenarios/smoke/account-processing-smoke.yaml +44 -0
- package/scenarios/smoke/api-db-batch-check.yaml +40 -0
- package/src/actions/assert-db.ts +6 -0
- package/src/actions/run-eod.ts +6 -0
- package/src/adapters/api/api-collections.ts +375 -0
- package/src/adapters/api/body-utils.ts +10 -0
- package/src/adapters/api/rest-client.ts +587 -0
- package/src/adapters/api/soap-client.ts +7 -0
- package/src/adapters/db/assertions.ts +83 -0
- package/src/adapters/db/oracle-client.ts +133 -0
- package/src/adapters/db/query-catalog.ts +80 -0
- package/src/adapters/unix/batch-catalog.ts +81 -0
- package/src/adapters/unix/batch-input-files.ts +39 -0
- package/src/adapters/unix/batch-runner.ts +456 -0
- package/src/adapters/unix/ssh-client.ts +248 -0
- package/src/app/server.ts +914 -0
- package/src/cli.ts +517 -0
- package/src/config/environments.ts +193 -0
- package/src/config/registry.ts +23 -0
- package/src/config/secrets.ts +128 -0
- package/src/dsl/parser.ts +24 -0
- package/src/dsl/schema.ts +189 -0
- package/src/dsl/types.ts +371 -0
- package/src/dsl/validator.ts +282 -0
- package/src/engine/captures.ts +66 -0
- package/src/engine/context.ts +76 -0
- package/src/engine/evidence.ts +35 -0
- package/src/engine/known-errors.ts +145 -0
- package/src/engine/retry.ts +11 -0
- package/src/engine/runner.ts +746 -0
- package/src/engine/step-result.ts +64 -0
- package/src/flows/catalog-normalizer.ts +86 -0
- package/src/flows/compiler.ts +247 -0
- package/src/flows/concat.ts +149 -0
- package/src/flows/parser.ts +27 -0
- package/src/flows/schema.ts +154 -0
- package/src/flows/types.ts +130 -0
- package/src/flows/validator.ts +468 -0
- package/src/llm/system-prompt.md +9 -0
- package/src/reports/html-report.ts +113 -0
- package/src/reports/junit-report.ts +55 -0
- package/src/types/oracledb.d.ts +1 -0
- package/templates/.gitkeep +0 -0
- package/templates/api/create-test-case.json +5 -0
- package/templates/api/record-test-activity.json +6 -0
- package/tsconfig.json +15 -0
- package/vite.config.ts +17 -0
- package/web/index.html +12 -0
- package/web/src/App.tsx +6588 -0
- package/web/src/main.tsx +10 -0
- package/web/src/styles.css +3147 -0
- package/web-dist/assets/elk.bundled-ChwRCIWJ.js +24 -0
- package/web-dist/assets/index-CArbX4zm.css +1 -0
- package/web-dist/assets/index-vDCbj8xB.js +28 -0
- package/web-dist/index.html +13 -0
- package/index.js +0 -1
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
export function apiCollectionsPath(rootDir) {
|
|
5
|
+
return join(rootDir, "catalogs", "api-collections.json");
|
|
6
|
+
}
|
|
7
|
+
export async function loadApiCollections(rootDir) {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(await readFile(apiCollectionsPath(rootDir), "utf8"));
|
|
10
|
+
return normalizeCollectionFile(parsed);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
const err = error;
|
|
14
|
+
if (err.code === "ENOENT")
|
|
15
|
+
return { version: 1, collections: [] };
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function writeApiCollections(rootDir, file) {
|
|
20
|
+
const outputPath = apiCollectionsPath(rootDir);
|
|
21
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
22
|
+
await writeFile(outputPath, `${JSON.stringify(normalizeCollectionFile(file), null, 2)}\n`, "utf8");
|
|
23
|
+
}
|
|
24
|
+
export async function importPostmanCollection(rootDir, payload) {
|
|
25
|
+
const collection = parsePostmanCollection(payload);
|
|
26
|
+
const file = await loadApiCollections(rootDir);
|
|
27
|
+
const remaining = file.collections.filter((item) => item.id !== collection.id);
|
|
28
|
+
await writeApiCollections(rootDir, { version: 1, collections: [...remaining, collection] });
|
|
29
|
+
return collection;
|
|
30
|
+
}
|
|
31
|
+
export function parsePostmanCollection(payload) {
|
|
32
|
+
const source = asObject(payload);
|
|
33
|
+
const name = String(source.info?.name || "Imported API Collection").trim();
|
|
34
|
+
const collectionId = sanitizeId(source.info?._postman_id || `${name}_${shortHash(JSON.stringify(payload))}`);
|
|
35
|
+
const variables = postmanVariables(source.variable);
|
|
36
|
+
const requests = [];
|
|
37
|
+
const rootAuth = source.auth;
|
|
38
|
+
function visit(items, folderPath, inheritedAuth) {
|
|
39
|
+
for (const item of items ?? []) {
|
|
40
|
+
if (Array.isArray(item.item)) {
|
|
41
|
+
visit(item.item, [...folderPath, String(item.name || "Folder")], inheritedAuth);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const request = typeof item.request === "string" ? undefined : item.request;
|
|
45
|
+
if (!request)
|
|
46
|
+
continue;
|
|
47
|
+
const requestId = uniqueRequestId(collectionId, folderPath, item.name || request.method || "request", requests.length);
|
|
48
|
+
const spec = postmanRequestSpec(request, inheritedAuth);
|
|
49
|
+
const variableNames = [...new Set([
|
|
50
|
+
...findTemplateVariables(spec),
|
|
51
|
+
...Object.keys(variables)
|
|
52
|
+
])].sort((a, b) => a.localeCompare(b));
|
|
53
|
+
requests.push({
|
|
54
|
+
id: requestId,
|
|
55
|
+
name: String(item.name || requestId),
|
|
56
|
+
folderPath,
|
|
57
|
+
operationKey: `pm_${collectionId}_${requestId}`,
|
|
58
|
+
description: descriptionText(request.description),
|
|
59
|
+
request: spec,
|
|
60
|
+
variables,
|
|
61
|
+
variableNames
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
visit(source.item, [], rootAuth);
|
|
66
|
+
return {
|
|
67
|
+
id: collectionId,
|
|
68
|
+
name,
|
|
69
|
+
importedAt: new Date().toISOString(),
|
|
70
|
+
variables,
|
|
71
|
+
requestCount: requests.length,
|
|
72
|
+
requests
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function previewPostmanCollection(payload) {
|
|
76
|
+
const collection = parsePostmanCollection(payload);
|
|
77
|
+
return {
|
|
78
|
+
name: collection.name,
|
|
79
|
+
id: collection.id,
|
|
80
|
+
requestCount: collection.requestCount,
|
|
81
|
+
variables: collection.variables ?? {},
|
|
82
|
+
folders: [...new Set(collection.requests.map((request) => request.folderPath.join(" / ")).filter(Boolean))].sort(),
|
|
83
|
+
requests: collection.requests.map((request) => ({
|
|
84
|
+
id: request.id,
|
|
85
|
+
name: request.name,
|
|
86
|
+
folderPath: request.folderPath,
|
|
87
|
+
operationKey: request.operationKey,
|
|
88
|
+
variableNames: request.variableNames,
|
|
89
|
+
...request.request
|
|
90
|
+
}))
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export function importedOperationsFromCollections(file) {
|
|
94
|
+
const operations = {};
|
|
95
|
+
for (const collection of file.collections) {
|
|
96
|
+
for (const request of collection.requests) {
|
|
97
|
+
operations[request.operationKey] = {
|
|
98
|
+
type: "rest",
|
|
99
|
+
description: request.description || `${collection.name}${request.folderPath.length ? ` / ${request.folderPath.join(" / ")}` : ""} / ${request.name}`,
|
|
100
|
+
...request.request,
|
|
101
|
+
params: paramsFromVariables(request.variableNames),
|
|
102
|
+
source: {
|
|
103
|
+
collectionId: collection.id,
|
|
104
|
+
collectionName: collection.name,
|
|
105
|
+
requestId: request.id,
|
|
106
|
+
folderPath: request.folderPath
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return operations;
|
|
112
|
+
}
|
|
113
|
+
export function operationToRequestSpec(operation) {
|
|
114
|
+
if (!operation)
|
|
115
|
+
return {};
|
|
116
|
+
return {
|
|
117
|
+
method: operation.method,
|
|
118
|
+
path: operation.path,
|
|
119
|
+
headers: operation.headers,
|
|
120
|
+
query: operation.query,
|
|
121
|
+
body: operation.body,
|
|
122
|
+
rawBody: operation.rawBody,
|
|
123
|
+
bodyMode: operation.bodyMode,
|
|
124
|
+
auth: operation.auth,
|
|
125
|
+
acceptStatuses: operation.acceptStatuses
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
export function mergeApiRequest(base, override) {
|
|
129
|
+
const source = operationToRequestSpec(base);
|
|
130
|
+
return cleanRequest({
|
|
131
|
+
...source,
|
|
132
|
+
...(override ?? {}),
|
|
133
|
+
headers: mergeRecord(source.headers, override?.headers),
|
|
134
|
+
query: mergeRecord(source.query, override?.query),
|
|
135
|
+
acceptStatuses: override?.acceptStatuses ?? source.acceptStatuses
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function postmanRequestSpec(request, inheritedAuth) {
|
|
139
|
+
const url = parsePostmanUrl(request.url);
|
|
140
|
+
const body = parsePostmanBody(request.body);
|
|
141
|
+
return cleanRequest({
|
|
142
|
+
method: normalizeMethod(request.method),
|
|
143
|
+
path: url.path,
|
|
144
|
+
query: url.query,
|
|
145
|
+
headers: mergeRecord(generatedPostmanHeaders(body), keyValueRecord(request.header)),
|
|
146
|
+
auth: request.auth ?? inheritedAuth,
|
|
147
|
+
...body
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function parsePostmanUrl(value) {
|
|
151
|
+
if (typeof value === "string")
|
|
152
|
+
return pathAndQueryFromRaw(value);
|
|
153
|
+
const url = asObject(value);
|
|
154
|
+
const raw = optionalString(url.raw);
|
|
155
|
+
const query = keyValueRecord(url.query);
|
|
156
|
+
if (raw) {
|
|
157
|
+
const parsed = pathAndQueryFromRaw(raw);
|
|
158
|
+
return { path: parsed.path, query: mergeRecord(parsed.query, query) };
|
|
159
|
+
}
|
|
160
|
+
const pathParts = Array.isArray(url.path) ? url.path.map(String) : [];
|
|
161
|
+
return {
|
|
162
|
+
path: pathParts.length ? `/${pathParts.join("/")}` : undefined,
|
|
163
|
+
query
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function pathAndQueryFromRaw(raw) {
|
|
167
|
+
const trimmed = raw.trim();
|
|
168
|
+
const withoutBase = trimmed.replace(/^\{\{[^}]+\}\}/, "");
|
|
169
|
+
try {
|
|
170
|
+
const url = new URL(trimmed.replace(/^\{\{[^}]+\}\}/, "http://postman.local"));
|
|
171
|
+
return { path: url.pathname, query: Object.fromEntries(url.searchParams.entries()) };
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
const [path, qs] = withoutBase.split("?");
|
|
175
|
+
return {
|
|
176
|
+
path: path.startsWith("/") ? path : `/${path}`,
|
|
177
|
+
query: qs ? Object.fromEntries(new URLSearchParams(qs).entries()) : undefined
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function parsePostmanBody(body) {
|
|
182
|
+
if (!body?.mode)
|
|
183
|
+
return { bodyMode: "none" };
|
|
184
|
+
if (body.mode === "raw") {
|
|
185
|
+
const rawBody = body.raw ?? "";
|
|
186
|
+
const trimmed = rawBody.trim();
|
|
187
|
+
if (trimmed && looksLikeJson(trimmed)) {
|
|
188
|
+
return { bodyMode: "json", rawBody };
|
|
189
|
+
}
|
|
190
|
+
return { bodyMode: "raw", rawBody };
|
|
191
|
+
}
|
|
192
|
+
if (body.mode === "urlencoded") {
|
|
193
|
+
return { bodyMode: "urlencoded", body: keyValueRecord(body.urlencoded) ?? {} };
|
|
194
|
+
}
|
|
195
|
+
if (body.mode === "formdata") {
|
|
196
|
+
return { bodyMode: "formdata", body: keyValueRecord(body.formdata) ?? {} };
|
|
197
|
+
}
|
|
198
|
+
return { bodyMode: "raw", rawBody: body.raw ?? "" };
|
|
199
|
+
}
|
|
200
|
+
function normalizeMethod(value) {
|
|
201
|
+
const method = String(value || "GET").toUpperCase();
|
|
202
|
+
if (["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].includes(method)) {
|
|
203
|
+
return method;
|
|
204
|
+
}
|
|
205
|
+
return "GET";
|
|
206
|
+
}
|
|
207
|
+
function keyValueRecord(items) {
|
|
208
|
+
const entries = (items ?? [])
|
|
209
|
+
.filter((item) => !item.disabled && item.key)
|
|
210
|
+
.map((item) => [String(item.key), String(item.value ?? "")]);
|
|
211
|
+
return entries.length ? Object.fromEntries(entries) : undefined;
|
|
212
|
+
}
|
|
213
|
+
function generatedPostmanHeaders(body) {
|
|
214
|
+
const headers = {
|
|
215
|
+
Accept: "*/*",
|
|
216
|
+
"Cache-Control": "no-cache"
|
|
217
|
+
};
|
|
218
|
+
if (body.bodyMode === "json")
|
|
219
|
+
headers["Content-Type"] = "application/json";
|
|
220
|
+
if (body.bodyMode === "urlencoded")
|
|
221
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
222
|
+
return Object.keys(headers).length ? headers : undefined;
|
|
223
|
+
}
|
|
224
|
+
function postmanVariables(items) {
|
|
225
|
+
return Object.fromEntries((items ?? [])
|
|
226
|
+
.filter((item) => !item.disabled && item.key)
|
|
227
|
+
.map((item) => [String(item.key), item.value ?? ""]));
|
|
228
|
+
}
|
|
229
|
+
function findTemplateVariables(value) {
|
|
230
|
+
const names = [];
|
|
231
|
+
const visit = (current) => {
|
|
232
|
+
if (typeof current === "string") {
|
|
233
|
+
for (const match of current.matchAll(/\{\{([A-Za-z0-9_.-]+)\}\}/g))
|
|
234
|
+
names.push(match[1]);
|
|
235
|
+
for (const match of current.matchAll(/\$\{([A-Za-z0-9_.-]+)\}/g))
|
|
236
|
+
names.push(match[1]);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (Array.isArray(current))
|
|
240
|
+
current.forEach(visit);
|
|
241
|
+
if (current && typeof current === "object")
|
|
242
|
+
Object.values(current).forEach(visit);
|
|
243
|
+
};
|
|
244
|
+
visit(value);
|
|
245
|
+
return names;
|
|
246
|
+
}
|
|
247
|
+
function paramsFromVariables(variableNames) {
|
|
248
|
+
if (!variableNames.length)
|
|
249
|
+
return undefined;
|
|
250
|
+
return Object.fromEntries(variableNames.map((name) => [name, { required: false, type: "string" }]));
|
|
251
|
+
}
|
|
252
|
+
function normalizeCollectionFile(file) {
|
|
253
|
+
return {
|
|
254
|
+
version: 1,
|
|
255
|
+
collections: Array.isArray(file.collections) ? file.collections.map((collection) => ({
|
|
256
|
+
...collection,
|
|
257
|
+
requestCount: collection.requests?.length ?? collection.requestCount ?? 0,
|
|
258
|
+
requests: collection.requests ?? []
|
|
259
|
+
})) : []
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function cleanRequest(request) {
|
|
263
|
+
return Object.fromEntries(Object.entries(request).filter(([, value]) => value !== undefined && value !== null && !(typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0)));
|
|
264
|
+
}
|
|
265
|
+
function mergeRecord(base, override) {
|
|
266
|
+
const merged = { ...(base ?? {}), ...(override ?? {}) };
|
|
267
|
+
return Object.keys(merged).length ? merged : undefined;
|
|
268
|
+
}
|
|
269
|
+
function asObject(value) {
|
|
270
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
271
|
+
}
|
|
272
|
+
function optionalString(value) {
|
|
273
|
+
return value === undefined || value === null ? undefined : String(value);
|
|
274
|
+
}
|
|
275
|
+
function looksLikeJson(value) {
|
|
276
|
+
return value.startsWith("{") || value.startsWith("[");
|
|
277
|
+
}
|
|
278
|
+
function descriptionText(value) {
|
|
279
|
+
if (typeof value === "string")
|
|
280
|
+
return value;
|
|
281
|
+
const description = asObject(value);
|
|
282
|
+
return optionalString(description.content);
|
|
283
|
+
}
|
|
284
|
+
function uniqueRequestId(collectionId, folderPath, name, index) {
|
|
285
|
+
return sanitizeId(`${folderPath.join("_")}_${name}_${index || ""}`) || `${collectionId}_request_${index + 1}`;
|
|
286
|
+
}
|
|
287
|
+
function sanitizeId(value) {
|
|
288
|
+
return String(value ?? "")
|
|
289
|
+
.toLowerCase()
|
|
290
|
+
.replace(/[^a-z0-9_-]+/g, "_")
|
|
291
|
+
.replace(/^_+|_+$/g, "")
|
|
292
|
+
.slice(0, 80) || "collection";
|
|
293
|
+
}
|
|
294
|
+
function shortHash(value) {
|
|
295
|
+
return createHash("sha1").update(value).digest("hex").slice(0, 8);
|
|
296
|
+
}
|