common-qa-mcp 0.6.0 → 0.7.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/README.md +37 -79
- package/dist/adapters/remoteMcp/adapter.d.ts +3 -0
- package/dist/adapters/remoteMcp/adapter.d.ts.map +1 -1
- package/dist/adapters/remoteMcp/adapter.js +1 -1
- package/dist/adapters/remoteMcp/adapter.js.map +1 -1
- package/dist/adapters/simplifyqa/adapter.d.ts +23 -16
- package/dist/adapters/simplifyqa/adapter.d.ts.map +1 -1
- package/dist/adapters/simplifyqa/adapter.js +283 -61
- package/dist/adapters/simplifyqa/adapter.js.map +1 -1
- package/dist/adapters/simplifyqa/mapper.d.ts.map +1 -1
- package/dist/adapters/simplifyqa/mapper.js +22 -1
- package/dist/adapters/simplifyqa/mapper.js.map +1 -1
- package/dist/adapters/types.d.ts +18 -5
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/domain/types.d.ts +24 -1
- package/dist/domain/types.d.ts.map +1 -1
- package/dist/initAgents.d.ts.map +1 -1
- package/dist/initAgents.js +101 -15
- package/dist/initAgents.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +95 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/discovery.tools.d.ts.map +1 -1
- package/dist/tools/discovery.tools.js +36 -9
- package/dist/tools/discovery.tools.js.map +1 -1
- package/dist/tools/gapAnalysis.tools.d.ts +31 -0
- package/dist/tools/gapAnalysis.tools.d.ts.map +1 -0
- package/dist/tools/gapAnalysis.tools.js +287 -0
- package/dist/tools/gapAnalysis.tools.js.map +1 -0
- package/dist/tools/impactAnalysis.tools.d.ts +17 -0
- package/dist/tools/impactAnalysis.tools.d.ts.map +1 -0
- package/dist/tools/impactAnalysis.tools.js +352 -0
- package/dist/tools/impactAnalysis.tools.js.map +1 -0
- package/dist/tools/markdown.d.ts.map +1 -1
- package/dist/tools/markdown.js +6 -1
- package/dist/tools/markdown.js.map +1 -1
- package/dist/tools/objects.tools.d.ts.map +1 -1
- package/dist/tools/objects.tools.js +102 -16
- package/dist/tools/objects.tools.js.map +1 -1
- package/dist/tools/run.tools.d.ts.map +1 -1
- package/dist/tools/run.tools.js +41 -5
- package/dist/tools/run.tools.js.map +1 -1
- package/dist/tools/testcase.tools.d.ts.map +1 -1
- package/dist/tools/testcase.tools.js +9 -3
- package/dist/tools/testcase.tools.js.map +1 -1
- package/dist/tools/testdata.tools.d.ts.map +1 -1
- package/dist/tools/testdata.tools.js +43 -4
- package/dist/tools/testdata.tools.js.map +1 -1
- package/package.json +1 -1
|
@@ -514,10 +514,6 @@ export class SimplifyQaAdapter {
|
|
|
514
514
|
const rows = Array.isArray(body) ? body : Array.isArray(body?.data) ? body.data : [];
|
|
515
515
|
return rows;
|
|
516
516
|
}
|
|
517
|
-
/** For every FUNCTION step, resolve the referenced function's code + name via /tm/function search
|
|
518
|
-
* (batched by distinct functionId). Mutates `tc.steps[i].functionCall` in place with
|
|
519
|
-
* `testCaseCode`/`testCaseName`. Non-fatal on failure — the raw id still renders. Mirrors the
|
|
520
|
-
* Excel export builder in tcfn.service.js:2696-2703. */
|
|
521
517
|
async hydrateFunctionNames(tc) {
|
|
522
518
|
const ids = new Set();
|
|
523
519
|
for (const s of tc.steps) {
|
|
@@ -543,16 +539,33 @@ export class SimplifyQaAdapter {
|
|
|
543
539
|
continue;
|
|
544
540
|
byId.set(rid, { code: str(r, 'code'), name: str(r, 'name', 'title') ?? '' });
|
|
545
541
|
}
|
|
542
|
+
const versionNamesById = new Map();
|
|
543
|
+
await Promise.all([...ids].map(async (fnId) => {
|
|
544
|
+
try {
|
|
545
|
+
const versionRows = await this.fetchFunctionVersionRows(fnId);
|
|
546
|
+
const versions = toVersionEntries(versionRows);
|
|
547
|
+
versionNamesById.set(fnId, new Map(versions.map((v) => [v.id, v.name])));
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
// Non-fatal per function: that step's version falls back to the bare uuid.
|
|
551
|
+
}
|
|
552
|
+
}));
|
|
546
553
|
for (const s of tc.steps) {
|
|
547
554
|
if (s.stepType !== 'function' || !s.functionCall)
|
|
548
555
|
continue;
|
|
549
556
|
const hit = byId.get(s.functionCall.testCaseId);
|
|
550
|
-
if (
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
557
|
+
if (hit) {
|
|
558
|
+
if (hit.code)
|
|
559
|
+
s.functionCall.testCaseCode = hit.code;
|
|
560
|
+
if (hit.name)
|
|
561
|
+
s.functionCall.testCaseName = hit.name;
|
|
562
|
+
}
|
|
563
|
+
const versionId = s.functionCall.versionId;
|
|
564
|
+
if (versionId) {
|
|
565
|
+
const versionName = versionNamesById.get(s.functionCall.testCaseId)?.get(versionId);
|
|
566
|
+
if (versionName)
|
|
567
|
+
s.functionCall.versionName = versionName;
|
|
568
|
+
}
|
|
556
569
|
}
|
|
557
570
|
}
|
|
558
571
|
catch {
|
|
@@ -1023,11 +1036,27 @@ export class SimplifyQaAdapter {
|
|
|
1023
1036
|
* every node BEFORE that point is already persisted. */
|
|
1024
1037
|
async createModule(input) {
|
|
1025
1038
|
const root = await this.postOneModule(input.name, input.isRoot, input.projectId, input.description, input.parentId);
|
|
1039
|
+
if (!input.isRoot && input.parentId) {
|
|
1040
|
+
root.linkWarning = await this.checkChildLinked(input.parentId, root.id);
|
|
1041
|
+
}
|
|
1026
1042
|
if (input.children?.length) {
|
|
1027
1043
|
root.children = await this.createSubtree(input.children, root.id, input.projectId, input.name, root);
|
|
1028
1044
|
}
|
|
1029
1045
|
return root;
|
|
1030
1046
|
}
|
|
1047
|
+
async checkChildLinked(parentId, childId) {
|
|
1048
|
+
try {
|
|
1049
|
+
const parent = await this.getModule(parentId);
|
|
1050
|
+
const numChildId = Number.parseInt(childId, 10);
|
|
1051
|
+
if (parent.childIds?.includes(numChildId))
|
|
1052
|
+
return undefined;
|
|
1053
|
+
return `Created with parentId=${parentId}, but a fresh read of module ${parentId} does not yet list ` +
|
|
1054
|
+
`${childId} in its childIds — it may not appear under its parent in the Module screen tree until that catches up.`;
|
|
1055
|
+
}
|
|
1056
|
+
catch {
|
|
1057
|
+
return undefined;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1031
1060
|
/** Depth-first walk of a subtree. Stops at the first failure — everything already POSTed stays
|
|
1032
1061
|
* (no rollback endpoint upstream) and the failure is bubbled back on the root via `failedAt`. */
|
|
1033
1062
|
async createSubtree(children, parentId, projectId, parentPath, root) {
|
|
@@ -1044,6 +1073,7 @@ export class SimplifyQaAdapter {
|
|
|
1044
1073
|
root.failedAt = { path, error: e instanceof Error ? e.message : String(e) };
|
|
1045
1074
|
return out;
|
|
1046
1075
|
}
|
|
1076
|
+
saved.linkWarning = await this.checkChildLinked(parentId, saved.id);
|
|
1047
1077
|
if (child.children?.length) {
|
|
1048
1078
|
saved.children = await this.createSubtree(child.children, saved.id, projectId, path, root);
|
|
1049
1079
|
}
|
|
@@ -1065,23 +1095,56 @@ export class SimplifyQaAdapter {
|
|
|
1065
1095
|
const raw = await this.http.postJson('/pm/module', payload);
|
|
1066
1096
|
return toModuleSaved(raw, name);
|
|
1067
1097
|
}
|
|
1068
|
-
/** PUT /pm/module/:id — partial edit. Controller reads `projectId` off the body for its
|
|
1069
|
-
* `add_module` privilege check (module.controller.js:64), so it MUST be sent even when unchanged;
|
|
1070
|
-
* we default to the connected project. Only `name` / `description` are exposed — the controller
|
|
1071
|
-
* ignores everything else the validator accepts (isRoot, parentId, childIds). */
|
|
1072
1098
|
async updateModule(id, patch) {
|
|
1073
|
-
const projectId = patch.projectId
|
|
1074
|
-
? Number.parseInt(numericId(patch.projectId), 10)
|
|
1075
|
-
: this.requireProjectId();
|
|
1076
|
-
const body = { projectId };
|
|
1077
|
-
if (patch.name !== undefined)
|
|
1078
|
-
body['name'] = patch.name;
|
|
1079
|
-
if (patch.description !== undefined)
|
|
1080
|
-
body['description'] = patch.description;
|
|
1081
1099
|
const resolvedId = numericId(id);
|
|
1082
|
-
|
|
1100
|
+
let raw;
|
|
1101
|
+
if (patch.name !== undefined || patch.description !== undefined) {
|
|
1102
|
+
const projectId = patch.projectId
|
|
1103
|
+
? Number.parseInt(numericId(patch.projectId), 10)
|
|
1104
|
+
: this.requireProjectId();
|
|
1105
|
+
const body = { projectId };
|
|
1106
|
+
if (patch.name !== undefined)
|
|
1107
|
+
body['name'] = patch.name;
|
|
1108
|
+
if (patch.description !== undefined)
|
|
1109
|
+
body['description'] = patch.description;
|
|
1110
|
+
raw = await this.http.putJson(`/pm/module/${resolvedId}`, body);
|
|
1111
|
+
}
|
|
1112
|
+
if (patch.parentId !== undefined) {
|
|
1113
|
+
await this.reparentModule(resolvedId, patch.parentId);
|
|
1114
|
+
}
|
|
1083
1115
|
return toSavedRef(raw ?? { id: resolvedId }, false);
|
|
1084
1116
|
}
|
|
1117
|
+
async reparentModule(id, newParentId) {
|
|
1118
|
+
if (newParentId === '') {
|
|
1119
|
+
await this.http.putJson(`/pm/module/bulkupdate/${id}`, { isRoot: true });
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
const targetId = numericId(newParentId);
|
|
1123
|
+
if (targetId === id) {
|
|
1124
|
+
throw new Error(`Cannot set module ${id} as its own parent.`);
|
|
1125
|
+
}
|
|
1126
|
+
const all = await this.listModules();
|
|
1127
|
+
const childrenOf = new Map();
|
|
1128
|
+
for (const m of all)
|
|
1129
|
+
if (m.parentId)
|
|
1130
|
+
childrenOf.set(m.parentId, [...(childrenOf.get(m.parentId) ?? []), m.id]);
|
|
1131
|
+
const descendants = new Set();
|
|
1132
|
+
const stack = [...(childrenOf.get(id) ?? [])];
|
|
1133
|
+
while (stack.length) {
|
|
1134
|
+
const next = stack.pop();
|
|
1135
|
+
if (descendants.has(next))
|
|
1136
|
+
continue;
|
|
1137
|
+
descendants.add(next);
|
|
1138
|
+
stack.push(...(childrenOf.get(next) ?? []));
|
|
1139
|
+
}
|
|
1140
|
+
if (descendants.has(targetId)) {
|
|
1141
|
+
throw new Error(`Cannot move module ${id} under ${targetId} — it is already a descendant of ${id} (would detach the subtree).`);
|
|
1142
|
+
}
|
|
1143
|
+
if (!all.some((m) => m.id === targetId)) {
|
|
1144
|
+
throw new Error(`Parent module ${targetId} not found.`);
|
|
1145
|
+
}
|
|
1146
|
+
await this.http.putJson(`/pm/module/bulkupdate/${id}`, { parentId: Number.parseInt(targetId, 10), isRoot: false });
|
|
1147
|
+
}
|
|
1085
1148
|
/** PATCH /pm/module — two-phase per module.controller.js:94. `confirm:false` returns the per-id
|
|
1086
1149
|
* dependency footprint (test cases, functions, params, objects, defects, user stories). `confirm:true`
|
|
1087
1150
|
* triggers the actual delete: server drops only ids with EMPTY dependencies; ids with references are
|
|
@@ -1195,12 +1258,55 @@ export class SimplifyQaAdapter {
|
|
|
1195
1258
|
return withTotal(users, raw);
|
|
1196
1259
|
}
|
|
1197
1260
|
// ---------------------------------------------------------------------- Objects (read)
|
|
1198
|
-
async getObject(id) {
|
|
1261
|
+
async getObject(id, opts) {
|
|
1199
1262
|
const raw = await this.http.getJson(`/tm/object/${numericId(id)}`);
|
|
1200
1263
|
let doc = raw?.data ?? raw;
|
|
1201
1264
|
if (Array.isArray(doc))
|
|
1202
1265
|
doc = doc.length > 0 ? doc[0] : {};
|
|
1203
|
-
|
|
1266
|
+
const obj = toObject(doc && typeof doc === 'object' ? doc : {});
|
|
1267
|
+
if (opts?.enrichNames)
|
|
1268
|
+
await this.enrichObjectNames(obj);
|
|
1269
|
+
return obj;
|
|
1270
|
+
}
|
|
1271
|
+
async enrichObjectNames(obj) {
|
|
1272
|
+
if (obj.moduleId) {
|
|
1273
|
+
try {
|
|
1274
|
+
const modules = await this.listModules();
|
|
1275
|
+
const match = modules.find((m) => m.id === obj.moduleId);
|
|
1276
|
+
if (match)
|
|
1277
|
+
obj.moduleName = match.name;
|
|
1278
|
+
}
|
|
1279
|
+
catch { /* best-effort */ }
|
|
1280
|
+
}
|
|
1281
|
+
if (obj.parameterIds?.length) {
|
|
1282
|
+
const names = await this.resolveParameterNames(obj.parameterIds);
|
|
1283
|
+
const resolved = obj.parameterIds.map((pid) => names.get(pid)).filter((n) => !!n);
|
|
1284
|
+
if (resolved.length)
|
|
1285
|
+
obj.parameterNames = resolved;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
async resolveParameterNames(ids) {
|
|
1289
|
+
const map = new Map();
|
|
1290
|
+
const numIds = ids.map((i) => Number.parseInt(i, 10)).filter(Number.isFinite);
|
|
1291
|
+
if (!numIds.length)
|
|
1292
|
+
return map;
|
|
1293
|
+
try {
|
|
1294
|
+
const projectId = this.requireProjectId();
|
|
1295
|
+
const body = this.searchBody([
|
|
1296
|
+
{ column: 'projectId', value: projectId },
|
|
1297
|
+
{ column: 'id', value: numIds, regEx: false },
|
|
1298
|
+
]);
|
|
1299
|
+
const raw = await this.http.postJson('/tm/parameter/search', body);
|
|
1300
|
+
const rows = pickArray(raw, 'data', 'content');
|
|
1301
|
+
for (const row of rows) {
|
|
1302
|
+
const rid = str(row, 'id', '_id', 'parameterId');
|
|
1303
|
+
const name = str(row, 'name');
|
|
1304
|
+
if (rid && name)
|
|
1305
|
+
map.set(rid, name);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
catch { /* best-effort */ }
|
|
1309
|
+
return map;
|
|
1204
1310
|
}
|
|
1205
1311
|
// ---------------------------------------------------------------------- Deep links
|
|
1206
1312
|
productUrlFor(entity) {
|
|
@@ -2249,15 +2355,30 @@ export class SimplifyQaAdapter {
|
|
|
2249
2355
|
})().catch((e) => { this.actionCatalogCache = null; throw e; });
|
|
2250
2356
|
return this.actionCatalogCache;
|
|
2251
2357
|
}
|
|
2252
|
-
/** POST /tm/object/createObjectSeq — get-or-create the shared object row by locator match. The
|
|
2253
|
-
* backend does the dedup (`object.controller.js:268-370`) — same-locators reuses, same-disname-
|
|
2254
|
-
* different-locators creates a suffixed one. Returns the numeric object id.
|
|
2255
|
-
*
|
|
2256
|
-
* ponytail: minimum body — no thumbnail_url / pageScreenShot_url / parameterId. The recording
|
|
2257
|
-
* path enriches these; the tc_save path doesn't have screenshots to attach, and the object
|
|
2258
|
-
* works for execution without them. */
|
|
2259
2358
|
async resolveObjectId(objectRef, projectId, moduleId, technology, objectTemplateId) {
|
|
2260
|
-
const
|
|
2359
|
+
const name = (objectRef.name ?? '').trim();
|
|
2360
|
+
const incomingLocators = objectRef.locators ?? [];
|
|
2361
|
+
if (name) {
|
|
2362
|
+
try {
|
|
2363
|
+
const candidates = await this.searchObjects({ query: name, limit: 20 });
|
|
2364
|
+
const existing = candidates.find((c) => c.name.trim().toLowerCase() === name.toLowerCase());
|
|
2365
|
+
if (existing?.id) {
|
|
2366
|
+
const key = (l) => `${l.name}::${l.value}`;
|
|
2367
|
+
const existingKeys = new Set(existing.locators.map(key));
|
|
2368
|
+
const incomingKeys = new Set(incomingLocators.map(key));
|
|
2369
|
+
const sameLocators = existingKeys.size === incomingKeys.size
|
|
2370
|
+
&& [...existingKeys].every((k) => incomingKeys.has(k));
|
|
2371
|
+
if (!sameLocators && incomingLocators.length) {
|
|
2372
|
+
await this.updateObject(existing.id, { locators: incomingLocators });
|
|
2373
|
+
}
|
|
2374
|
+
return Number.parseInt(existing.id, 10);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
catch {
|
|
2378
|
+
// Lookup failed — fall through to create rather than block the save over this.
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
const attributes = incomingLocators.map((l) => ({
|
|
2261
2382
|
name: l.name,
|
|
2262
2383
|
value: l.value,
|
|
2263
2384
|
...(l.unique === true ? { unique: true } : {}),
|
|
@@ -2267,7 +2388,7 @@ export class SimplifyQaAdapter {
|
|
|
2267
2388
|
moduleId,
|
|
2268
2389
|
objecttemplateId: objectTemplateId,
|
|
2269
2390
|
technology,
|
|
2270
|
-
disname:
|
|
2391
|
+
disname: name,
|
|
2271
2392
|
attributes,
|
|
2272
2393
|
parameterId: [],
|
|
2273
2394
|
description: '',
|
|
@@ -2276,7 +2397,7 @@ export class SimplifyQaAdapter {
|
|
|
2276
2397
|
const flat = raw?.data ?? raw;
|
|
2277
2398
|
const id = str(flat, 'id', '_id', 'objectId');
|
|
2278
2399
|
if (!id)
|
|
2279
|
-
throw new Error(`createObjectSeq returned no id for object "${
|
|
2400
|
+
throw new Error(`createObjectSeq returned no id for object "${name}": ${JSON.stringify(raw)}`);
|
|
2280
2401
|
return Number.parseInt(id, 10);
|
|
2281
2402
|
}
|
|
2282
2403
|
/** For every automation step, resolve `actionId` (from action name + technology) and
|
|
@@ -2586,6 +2707,26 @@ export class SimplifyQaAdapter {
|
|
|
2586
2707
|
throw new Error('createObject: objectType is required (e.g. "WEB", "ANDROID") — it selects the object template ' +
|
|
2587
2708
|
'and cannot be changed after the object is created.');
|
|
2588
2709
|
}
|
|
2710
|
+
const wantedName = (o.name ?? '').trim();
|
|
2711
|
+
if (wantedName) {
|
|
2712
|
+
try {
|
|
2713
|
+
const candidates = await this.searchObjects({ query: wantedName, limit: 20 });
|
|
2714
|
+
const existing = candidates.find((c) => c.name.trim().toLowerCase() === wantedName.toLowerCase());
|
|
2715
|
+
if (existing?.id) {
|
|
2716
|
+
const key = (l) => `${l.name}::${l.value}`;
|
|
2717
|
+
const existingKeys = new Set(existing.locators.map(key));
|
|
2718
|
+
const incomingKeys = new Set((o.locators ?? []).map(key));
|
|
2719
|
+
const sameLocators = existingKeys.size === incomingKeys.size
|
|
2720
|
+
&& [...existingKeys].every((k) => incomingKeys.has(k));
|
|
2721
|
+
return sameLocators || !o.locators?.length
|
|
2722
|
+
? existing
|
|
2723
|
+
: await this.updateObject(existing.id, { locators: o.locators });
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
catch {
|
|
2727
|
+
// Lookup failed — fall through to create rather than block the save over this.
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2589
2730
|
const projectId = this.requireProjectId();
|
|
2590
2731
|
const objecttemplateId = await this.resolveObjectTemplateId(o.objectType);
|
|
2591
2732
|
const moduleId = await this.resolveDefaultModuleId();
|
|
@@ -2673,7 +2814,7 @@ export class SimplifyQaAdapter {
|
|
|
2673
2814
|
* attribute set is even valid for this object), so editing them here — outside the create flow — is
|
|
2674
2815
|
* out of scope; changing them means creating a new object, not "updating" this one.
|
|
2675
2816
|
*/
|
|
2676
|
-
async updateObject(id, changes) {
|
|
2817
|
+
async updateObject(id, changes, opts) {
|
|
2677
2818
|
const numId = numericId(id);
|
|
2678
2819
|
const raw = await this.http.getJson(`/tm/object/${numId}`);
|
|
2679
2820
|
const doc = (raw?.data && typeof raw.data === 'object' ? raw.data : raw) ?? {};
|
|
@@ -2695,12 +2836,41 @@ export class SimplifyQaAdapter {
|
|
|
2695
2836
|
else
|
|
2696
2837
|
attributes.push(merged);
|
|
2697
2838
|
}
|
|
2839
|
+
let moduleId = doc.moduleId;
|
|
2840
|
+
if (changes.moduleName !== undefined) {
|
|
2841
|
+
const modules = await this.listModules();
|
|
2842
|
+
const wanted = changes.moduleName.trim().toLowerCase();
|
|
2843
|
+
const match = modules.find((m) => m.name.toLowerCase() === wanted);
|
|
2844
|
+
if (!match) {
|
|
2845
|
+
const options = modules.map((m) => m.name).join(', ') || '(no modules configured in this project)';
|
|
2846
|
+
throw new Error(`Module "${changes.moduleName}" not found on object ${id}. Valid modules: ${options}`);
|
|
2847
|
+
}
|
|
2848
|
+
moduleId = Number.parseInt(match.id, 10);
|
|
2849
|
+
}
|
|
2850
|
+
let parameterId = doc.parameterId ?? [];
|
|
2851
|
+
if (changes.parameterNames !== undefined) {
|
|
2852
|
+
const projectId = this.requireProjectId();
|
|
2853
|
+
const resolved = [];
|
|
2854
|
+
const unresolved = [];
|
|
2855
|
+
for (const name of changes.parameterNames) {
|
|
2856
|
+
const pid = await this.findParameterId(projectId, name);
|
|
2857
|
+
if (pid != null)
|
|
2858
|
+
resolved.push(pid);
|
|
2859
|
+
else
|
|
2860
|
+
unresolved.push(name);
|
|
2861
|
+
}
|
|
2862
|
+
if (unresolved.length) {
|
|
2863
|
+
throw new Error(`Parameter(s) not found on object ${id}: ${unresolved.join(', ')}. Check the exact name ` +
|
|
2864
|
+
'(case-insensitive), or create the parameter first in the Parameters screen.');
|
|
2865
|
+
}
|
|
2866
|
+
parameterId = resolved;
|
|
2867
|
+
}
|
|
2698
2868
|
const body = {
|
|
2699
2869
|
disname: changes.name ?? doc.disname,
|
|
2700
|
-
description: doc.description,
|
|
2870
|
+
description: changes.description !== undefined ? changes.description : doc.description,
|
|
2701
2871
|
objecttemplateId: doc.objecttemplateId,
|
|
2702
|
-
parameterId
|
|
2703
|
-
moduleId
|
|
2872
|
+
parameterId,
|
|
2873
|
+
moduleId,
|
|
2704
2874
|
technology: doc.technology,
|
|
2705
2875
|
attributes,
|
|
2706
2876
|
projectId: this.requireProjectId(),
|
|
@@ -2708,7 +2878,10 @@ export class SimplifyQaAdapter {
|
|
|
2708
2878
|
};
|
|
2709
2879
|
const res = await this.http.putJson(`/tm/object/${numId}`, body);
|
|
2710
2880
|
const updated = (res?.data && typeof res.data === 'object' ? res.data : res) ?? doc;
|
|
2711
|
-
|
|
2881
|
+
const obj = toObject(updated);
|
|
2882
|
+
if (opts?.enrichNames)
|
|
2883
|
+
await this.enrichObjectNames(obj);
|
|
2884
|
+
return obj;
|
|
2712
2885
|
}
|
|
2713
2886
|
// ---------------------------------------------------------------------- Actions catalog
|
|
2714
2887
|
async actionsCatalog(platform) {
|
|
@@ -2762,8 +2935,9 @@ export class SimplifyQaAdapter {
|
|
|
2762
2935
|
const meta = await this.fetchParameterMeta(pid);
|
|
2763
2936
|
if (!meta)
|
|
2764
2937
|
continue; // parameter deleted — skip
|
|
2765
|
-
const
|
|
2766
|
-
|
|
2938
|
+
const isProtected = meta.protected || looksSensitive(meta.name);
|
|
2939
|
+
const seed = isProtected ? (rawValue ? '*****' : '(empty)') : rawValue;
|
|
2940
|
+
parameters.push({ paramId: pid, name: meta.name, seed, ...(isProtected ? { protected: true } : {}) });
|
|
2767
2941
|
}
|
|
2768
2942
|
}
|
|
2769
2943
|
return { tcId: q.tcId, versionId: versionId, environmentType: env, parameters };
|
|
@@ -2942,23 +3116,19 @@ export class SimplifyQaAdapter {
|
|
|
2942
3116
|
// Top-level execution doc (carries `type` = 'TC' or 'SUITE', and `executionItems[]` for a suite).
|
|
2943
3117
|
// One extra POST per getRun call; a suite short-circuits here so no /exec/report is needed.
|
|
2944
3118
|
if (Number.isFinite(runIdNum)) {
|
|
2945
|
-
const
|
|
2946
|
-
searchFields: [
|
|
2947
|
-
{ column: 'id', value: runIdNum, regEx: false },
|
|
2948
|
-
{ column: 'projectId', value: projectId, regEx: false },
|
|
2949
|
-
{ column: 'deleted', value: false },
|
|
2950
|
-
],
|
|
2951
|
-
startIndex: 0,
|
|
2952
|
-
limit: 1,
|
|
2953
|
-
};
|
|
2954
|
-
const execRaw = await this.http.postJson('/exec/execution/search', execBody);
|
|
2955
|
-
const execDoc = pickArray(execRaw, 'data', 'content', 'executions')[0];
|
|
3119
|
+
const execDoc = await this.fetchExecDoc(runIdNum, projectId);
|
|
2956
3120
|
if (execDoc && String(execDoc.type ?? '').toUpperCase() === 'SUITE') {
|
|
2957
3121
|
const items = Array.isArray(execDoc.executionItems) ? execDoc.executionItems : [];
|
|
2958
3122
|
const children = items.map((it) => {
|
|
3123
|
+
const testcaseId = str(it, 'testcaseId');
|
|
3124
|
+
const versionId = str(it, 'versionId');
|
|
3125
|
+
const stepId = str(it, 'stepId');
|
|
2959
3126
|
const child = {
|
|
2960
|
-
id:
|
|
3127
|
+
id: testcaseId ?? String(it?.id ?? ''),
|
|
2961
3128
|
state: mapRunState(str(it, 'status') ?? ''),
|
|
3129
|
+
...(testcaseId ? { testcaseId } : {}),
|
|
3130
|
+
...(versionId ? { versionId } : {}),
|
|
3131
|
+
...(stepId ? { stepId } : {}),
|
|
2962
3132
|
};
|
|
2963
3133
|
const name = str(it, 'name');
|
|
2964
3134
|
if (name)
|
|
@@ -2991,14 +3161,33 @@ export class SimplifyQaAdapter {
|
|
|
2991
3161
|
}
|
|
2992
3162
|
const executionRunId = str(chosen, 'id', '_id') ?? '';
|
|
2993
3163
|
const parentId = Number(chosen?.executionItemId ?? 0);
|
|
3164
|
+
const stepResults = await this.fetchStepResults(runIdNum, executionRunId, parentId, projectId);
|
|
3165
|
+
return {
|
|
3166
|
+
runId,
|
|
3167
|
+
state: mapRunState(str(chosen, 'status') ?? ''),
|
|
3168
|
+
stepResults,
|
|
3169
|
+
reportUrl: `${base}/user/reports/reports-page?type=TESTCASE&parentId=${parentId}&id=${runIdNum}&projectId=${projectId}`,
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
async fetchExecDoc(runIdNum, projectId) {
|
|
3173
|
+
const execBody = {
|
|
3174
|
+
searchFields: [
|
|
3175
|
+
{ column: 'id', value: runIdNum, regEx: false },
|
|
3176
|
+
{ column: 'projectId', value: projectId, regEx: false },
|
|
3177
|
+
{ column: 'deleted', value: false },
|
|
3178
|
+
],
|
|
3179
|
+
startIndex: 0,
|
|
3180
|
+
limit: 1,
|
|
3181
|
+
};
|
|
3182
|
+
const execRaw = await this.http.postJson('/exec/execution/search', execBody);
|
|
3183
|
+
return pickArray(execRaw, 'data', 'content', 'executions')[0];
|
|
3184
|
+
}
|
|
3185
|
+
async fetchStepResults(executionId, executionRunId, parentId, projectId) {
|
|
2994
3186
|
const stepsResp = await this.http.postJson('/reports/execution/getstepResult', {
|
|
2995
|
-
executionId
|
|
2996
|
-
executionRunId,
|
|
2997
|
-
parentId,
|
|
2998
|
-
projectId,
|
|
3187
|
+
executionId, executionRunId, parentId, projectId,
|
|
2999
3188
|
});
|
|
3000
3189
|
const rawSteps = Array.isArray(stepsResp?.data) ? stepsResp.data : [];
|
|
3001
|
-
|
|
3190
|
+
return rawSteps.map((s) => {
|
|
3002
3191
|
const step = {
|
|
3003
3192
|
n: typeof s?.seq === 'number' ? s.seq : 0,
|
|
3004
3193
|
description: String(s?.description ?? ''),
|
|
@@ -3028,11 +3217,44 @@ export class SimplifyQaAdapter {
|
|
|
3028
3217
|
}
|
|
3029
3218
|
return step;
|
|
3030
3219
|
});
|
|
3220
|
+
}
|
|
3221
|
+
async getSuiteChildRun(suiteRunId, child) {
|
|
3222
|
+
const projectId = this.requireProjectId();
|
|
3223
|
+
const runIdNum = Number.parseInt(numericId(suiteRunId), 10);
|
|
3224
|
+
const execDoc = await this.fetchExecDoc(runIdNum, projectId);
|
|
3225
|
+
if (!execDoc)
|
|
3226
|
+
throw new Error(`Suite run ${suiteRunId} not found.`);
|
|
3227
|
+
const suiteNum = Number(execDoc.executionTypeId ?? 0);
|
|
3228
|
+
const body = {
|
|
3229
|
+
type: 'SU',
|
|
3230
|
+
projectId,
|
|
3231
|
+
parentId: suiteNum,
|
|
3232
|
+
executionId: runIdNum,
|
|
3233
|
+
tcId: Number.parseInt(numericId(child.testcaseId), 10),
|
|
3234
|
+
...(child.versionId ? { tcVersionId: child.versionId } : {}),
|
|
3235
|
+
...(child.stepId ? { stepId: child.stepId } : {}),
|
|
3236
|
+
};
|
|
3237
|
+
const raw = await this.http.postJson('/reports/execution/getExecutionRuns', body);
|
|
3238
|
+
const runs = Array.isArray(raw?.data) ? raw.data : [];
|
|
3239
|
+
const base = (this.cfg.baseUrl ?? '').replace(/\/+$/, '');
|
|
3240
|
+
if (!runs.length) {
|
|
3241
|
+
return { runId: suiteRunId, state: 'queued' };
|
|
3242
|
+
}
|
|
3243
|
+
let chosen = runs[0];
|
|
3244
|
+
for (const r of runs) {
|
|
3245
|
+
if (typeof r?.status === 'string' && r.status.toUpperCase() === 'FAILED') {
|
|
3246
|
+
chosen = r;
|
|
3247
|
+
break;
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
const executionRunId = str(chosen, 'id', '_id') ?? '';
|
|
3251
|
+
const parentId = Number(chosen?.executionItemId ?? 0);
|
|
3252
|
+
const stepResults = await this.fetchStepResults(runIdNum, executionRunId, parentId, projectId);
|
|
3031
3253
|
return {
|
|
3032
|
-
runId,
|
|
3254
|
+
runId: suiteRunId,
|
|
3033
3255
|
state: mapRunState(str(chosen, 'status') ?? ''),
|
|
3034
3256
|
stepResults,
|
|
3035
|
-
reportUrl: `${base}/user/reports/reports-page?type=TESTCASE&parentId=${parentId}&id=${
|
|
3257
|
+
reportUrl: `${base}/user/reports/reports-page?type=TESTCASE&parentId=${parentId}&id=${runIdNum}&projectId=${projectId}`,
|
|
3036
3258
|
};
|
|
3037
3259
|
}
|
|
3038
3260
|
/** Most recent execution runId for a SUITE — same `/exec/execution/search` the /reports UI's
|