@provartesting/provardx-cli 1.5.0-beta → 1.5.0-beta.10
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 +137 -13
- package/lib/commands/provar/auth/clear.d.ts +7 -0
- package/lib/commands/provar/auth/clear.js +36 -0
- package/lib/commands/provar/auth/clear.js.map +1 -0
- package/lib/commands/provar/auth/login.d.ts +10 -0
- package/lib/commands/provar/auth/login.js +90 -0
- package/lib/commands/provar/auth/login.js.map +1 -0
- package/lib/commands/provar/auth/rotate.d.ts +7 -0
- package/lib/commands/provar/auth/rotate.js +42 -0
- package/lib/commands/provar/auth/rotate.js.map +1 -0
- package/lib/commands/provar/auth/status.d.ts +7 -0
- package/lib/commands/provar/auth/status.js +107 -0
- package/lib/commands/provar/auth/status.js.map +1 -0
- package/lib/mcp/docs/PROVAR_TEST_STEP_REFERENCE.md +1430 -0
- package/lib/mcp/licensing/algasClient.js +14 -5
- package/lib/mcp/licensing/algasClient.js.map +1 -1
- package/lib/mcp/licensing/ideDetection.d.ts +0 -12
- package/lib/mcp/licensing/ideDetection.js +0 -72
- package/lib/mcp/licensing/ideDetection.js.map +1 -1
- package/lib/mcp/licensing/licenseCache.js +7 -1
- package/lib/mcp/licensing/licenseCache.js.map +1 -1
- package/lib/mcp/licensing/licenseValidator.d.ts +3 -3
- package/lib/mcp/licensing/licenseValidator.js +11 -4
- package/lib/mcp/licensing/licenseValidator.js.map +1 -1
- package/lib/mcp/prompts/index.d.ts +2 -0
- package/lib/mcp/prompts/index.js +19 -0
- package/lib/mcp/prompts/index.js.map +1 -0
- package/lib/mcp/prompts/loopPrompts.d.ts +6 -0
- package/lib/mcp/prompts/loopPrompts.js +435 -0
- package/lib/mcp/prompts/loopPrompts.js.map +1 -0
- package/lib/mcp/prompts/migrationPrompts.d.ts +4 -0
- package/lib/mcp/prompts/migrationPrompts.js +207 -0
- package/lib/mcp/prompts/migrationPrompts.js.map +1 -0
- package/lib/mcp/rules/provar_best_practices_rules.json +256 -544
- package/lib/mcp/security/pathPolicy.d.ts +5 -0
- package/lib/mcp/security/pathPolicy.js +30 -2
- package/lib/mcp/security/pathPolicy.js.map +1 -1
- package/lib/mcp/server.js +51 -4
- package/lib/mcp/server.js.map +1 -1
- package/lib/mcp/tools/antTools.d.ts +15 -0
- package/lib/mcp/tools/antTools.js +216 -50
- package/lib/mcp/tools/antTools.js.map +1 -1
- package/lib/mcp/tools/automationTools.d.ts +39 -5
- package/lib/mcp/tools/automationTools.js +341 -55
- package/lib/mcp/tools/automationTools.js.map +1 -1
- package/lib/mcp/tools/bestPracticesEngine.js +161 -23
- package/lib/mcp/tools/bestPracticesEngine.js.map +1 -1
- package/lib/mcp/tools/connectionTools.d.ts +4 -0
- package/lib/mcp/tools/connectionTools.js +168 -0
- package/lib/mcp/tools/connectionTools.js.map +1 -0
- package/lib/mcp/tools/nitroXTools.d.ts +22 -0
- package/lib/mcp/tools/nitroXTools.js +750 -0
- package/lib/mcp/tools/nitroXTools.js.map +1 -0
- package/lib/mcp/tools/pageObjectGenerate.js +103 -35
- package/lib/mcp/tools/pageObjectGenerate.js.map +1 -1
- package/lib/mcp/tools/propertiesTools.d.ts +2 -0
- package/lib/mcp/tools/propertiesTools.js +277 -39
- package/lib/mcp/tools/propertiesTools.js.map +1 -1
- package/lib/mcp/tools/qualityHubApiTools.d.ts +3 -0
- package/lib/mcp/tools/qualityHubApiTools.js +134 -0
- package/lib/mcp/tools/qualityHubApiTools.js.map +1 -0
- package/lib/mcp/tools/qualityHubTools.js +139 -20
- package/lib/mcp/tools/qualityHubTools.js.map +1 -1
- package/lib/mcp/tools/rcaTools.d.ts +3 -2
- package/lib/mcp/tools/rcaTools.js +145 -20
- package/lib/mcp/tools/rcaTools.js.map +1 -1
- package/lib/mcp/tools/sfSpawn.d.ts +1 -1
- package/lib/mcp/tools/sfSpawn.js +8 -2
- package/lib/mcp/tools/sfSpawn.js.map +1 -1
- package/lib/mcp/tools/testCaseGenerate.js +88 -59
- package/lib/mcp/tools/testCaseGenerate.js.map +1 -1
- package/lib/mcp/tools/testCaseStepTools.d.ts +4 -0
- package/lib/mcp/tools/testCaseStepTools.js +221 -0
- package/lib/mcp/tools/testCaseStepTools.js.map +1 -0
- package/lib/mcp/tools/testCaseValidate.d.ts +11 -0
- package/lib/mcp/tools/testCaseValidate.js +146 -19
- package/lib/mcp/tools/testCaseValidate.js.map +1 -1
- package/lib/services/auth/credentials.d.ts +21 -0
- package/lib/services/auth/credentials.js +75 -0
- package/lib/services/auth/credentials.js.map +1 -0
- package/lib/services/auth/loginFlow.d.ts +68 -0
- package/lib/services/auth/loginFlow.js +216 -0
- package/lib/services/auth/loginFlow.js.map +1 -0
- package/lib/services/qualityHub/client.d.ts +161 -0
- package/lib/services/qualityHub/client.js +226 -0
- package/lib/services/qualityHub/client.js.map +1 -0
- package/messages/sf.provar.auth.clear.md +16 -0
- package/messages/sf.provar.auth.login.md +31 -0
- package/messages/sf.provar.auth.rotate.md +23 -0
- package/messages/sf.provar.auth.status.md +16 -0
- package/oclif.manifest.json +241 -28
- package/package.json +8 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testCaseGenerate.js","sourceRoot":"","sources":["../../../src/mcp/tools/testCaseGenerate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,8BAA8B;AAC9B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,iFAAiF;AACjF,iFAAiF;AACjF,iDAAiD;AAEjD,MAAM,iBAAiB,GAA2B;IAChD,SAAS,
|
|
1
|
+
{"version":3,"file":"testCaseGenerate.js","sourceRoot":"","sources":["../../../src/mcp/tools/testCaseGenerate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { ServerConfig } from '../server.js';
|
|
3
|
+
export declare function registerTestCaseStepEdit(server: McpServer, config: ServerConfig): void;
|
|
4
|
+
export declare function registerAllTestCaseStepTools(server: McpServer, config: ServerConfig): void;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024 Provar Limited.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* Licensed under the BSD 3-Clause license.
|
|
5
|
+
* For full license text, see LICENSE.md file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
/* eslint-disable camelcase */
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
|
|
12
|
+
import { assertPathAllowed, PathPolicyError } from '../security/pathPolicy.js';
|
|
13
|
+
import { makeError, makeRequestId } from '../schemas/common.js';
|
|
14
|
+
import { log } from '../logging/logger.js';
|
|
15
|
+
import { validateTestCase } from './testCaseValidate.js';
|
|
16
|
+
// ── XML parse / build config ──────────────────────────────────────────────────
|
|
17
|
+
const PARSER_OPTIONS = {
|
|
18
|
+
ignoreAttributes: false,
|
|
19
|
+
attributeNamePrefix: '@_',
|
|
20
|
+
parseAttributeValue: false,
|
|
21
|
+
isArray: (tagName) => tagName === 'apiCall',
|
|
22
|
+
};
|
|
23
|
+
const BUILDER_OPTIONS = {
|
|
24
|
+
ignoreAttributes: false,
|
|
25
|
+
attributeNamePrefix: '@_',
|
|
26
|
+
format: true,
|
|
27
|
+
indentBy: ' ',
|
|
28
|
+
suppressEmptyNode: false,
|
|
29
|
+
};
|
|
30
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
31
|
+
function parseTestCaseXml(xmlContent) {
|
|
32
|
+
const parser = new XMLParser(PARSER_OPTIONS);
|
|
33
|
+
return parser.parse(xmlContent);
|
|
34
|
+
}
|
|
35
|
+
function buildTestCaseXml(parsed) {
|
|
36
|
+
const builder = new XMLBuilder(BUILDER_OPTIONS);
|
|
37
|
+
return '<?xml version="1.0" encoding="UTF-8"?>\n' + builder.build(parsed);
|
|
38
|
+
}
|
|
39
|
+
function getApiCalls(parsed) {
|
|
40
|
+
const tc = parsed['testCase'];
|
|
41
|
+
if (!tc || typeof tc !== 'object')
|
|
42
|
+
return null;
|
|
43
|
+
const steps = tc['steps'];
|
|
44
|
+
if (!steps || typeof steps !== 'object')
|
|
45
|
+
return null;
|
|
46
|
+
const calls = steps['apiCall'];
|
|
47
|
+
if (!Array.isArray(calls))
|
|
48
|
+
return null;
|
|
49
|
+
return calls;
|
|
50
|
+
}
|
|
51
|
+
function collectAllTestItemIds(parsed) {
|
|
52
|
+
const calls = getApiCalls(parsed);
|
|
53
|
+
if (!calls)
|
|
54
|
+
return [];
|
|
55
|
+
return calls.map((c) => c['@_testItemId']).filter((id) => typeof id === 'string');
|
|
56
|
+
}
|
|
57
|
+
function parseNewStep(stepXml) {
|
|
58
|
+
try {
|
|
59
|
+
const fragParser = new XMLParser(PARSER_OPTIONS);
|
|
60
|
+
const fragDoc = fragParser.parse(`<root>${stepXml}</root>`);
|
|
61
|
+
const rootEl = fragDoc['root'];
|
|
62
|
+
const callEl = rootEl?.['apiCall'];
|
|
63
|
+
if (!callEl)
|
|
64
|
+
return { error: 'step_xml must contain exactly one <apiCall> element' };
|
|
65
|
+
const calls = Array.isArray(callEl) ? callEl : [callEl];
|
|
66
|
+
if (calls.length !== 1)
|
|
67
|
+
return { error: 'step_xml must contain exactly one <apiCall> element' };
|
|
68
|
+
return { step: calls[0] };
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
return { error: e.message };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// ── Tool registration ─────────────────────────────────────────────────────────
|
|
75
|
+
export function registerTestCaseStepEdit(server, config) {
|
|
76
|
+
server.tool('provar.testcase.step.edit', [
|
|
77
|
+
'Add or remove a single step (apiCall) in a Provar XML test case file.',
|
|
78
|
+
'Uses write-to-temp-then-rename to minimise partial-write risk.',
|
|
79
|
+
'Prerequisites: the test case must exist and be valid XML.',
|
|
80
|
+
'For mode=remove: supply test_item_id of the step to remove.',
|
|
81
|
+
'For mode=add: supply test_item_id of the anchor step, position (before|after, default after),',
|
|
82
|
+
'and step_xml (the <apiCall ...>...</apiCall> XML fragment for the new step; must contain exactly one <apiCall>).',
|
|
83
|
+
'A backup is written to <test_case_path>.bak before any mutation and restored automatically if',
|
|
84
|
+
'the post-edit validation fails.',
|
|
85
|
+
'Returns STEP_NOT_FOUND (with all_test_item_ids list) when the target step is absent.',
|
|
86
|
+
'Returns INVALID_STEP_XML when step_xml cannot be parsed or contains ≠1 <apiCall> elements.',
|
|
87
|
+
'Returns INVALID_XML_AFTER_EDIT (backup restored) when the mutated file fails validation.',
|
|
88
|
+
].join(' '), {
|
|
89
|
+
test_case_path: z.string().describe('Absolute path to the .testcase XML file; must be within --allowed-paths'),
|
|
90
|
+
mode: z.enum(['remove', 'add']).describe('"remove" to delete a step; "add" to insert a new step'),
|
|
91
|
+
test_item_id: z
|
|
92
|
+
.string()
|
|
93
|
+
.describe('For mode=remove: testItemId of the step to delete. For mode=add: testItemId of the anchor step.'),
|
|
94
|
+
position: z
|
|
95
|
+
.enum(['before', 'after'])
|
|
96
|
+
.optional()
|
|
97
|
+
.default('after')
|
|
98
|
+
.describe('Where to insert relative to the anchor step (mode=add only; default: after)'),
|
|
99
|
+
step_xml: z
|
|
100
|
+
.string()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe('The <apiCall ...>...</apiCall> XML fragment for the new step (mode=add only). Must be well-formed XML.'),
|
|
103
|
+
validate_after_edit: z
|
|
104
|
+
.boolean()
|
|
105
|
+
.optional()
|
|
106
|
+
.default(true)
|
|
107
|
+
.describe('Run provar.testcase.validate after the mutation; restores backup on failure (default: true)'),
|
|
108
|
+
}, (input) => {
|
|
109
|
+
const requestId = makeRequestId();
|
|
110
|
+
log('info', 'provar.testcase.step.edit', { requestId, mode: input.mode, test_item_id: input.test_item_id });
|
|
111
|
+
try {
|
|
112
|
+
const resolvedPath = path.resolve(input.test_case_path);
|
|
113
|
+
const bakPath = resolvedPath + '.bak';
|
|
114
|
+
// Path policy — validate both the target file and its backup path
|
|
115
|
+
assertPathAllowed(resolvedPath, config.allowedPaths);
|
|
116
|
+
assertPathAllowed(bakPath, config.allowedPaths);
|
|
117
|
+
// Validate step_xml up-front before touching the file
|
|
118
|
+
let newStep = null;
|
|
119
|
+
if (input.mode === 'add') {
|
|
120
|
+
if (!input.step_xml) {
|
|
121
|
+
const err = makeError('MISSING_INPUT', 'step_xml is required for mode=add', requestId);
|
|
122
|
+
return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
|
|
123
|
+
}
|
|
124
|
+
const parsed_step = parseNewStep(input.step_xml);
|
|
125
|
+
if ('error' in parsed_step) {
|
|
126
|
+
const err = makeError('INVALID_STEP_XML', `step_xml parse error: ${parsed_step.error}`, requestId);
|
|
127
|
+
return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
|
|
128
|
+
}
|
|
129
|
+
newStep = parsed_step.step;
|
|
130
|
+
}
|
|
131
|
+
// Read the test case file
|
|
132
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
133
|
+
const err = makeError('FILE_NOT_FOUND', `Test case not found: ${resolvedPath}`, requestId);
|
|
134
|
+
return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
|
|
135
|
+
}
|
|
136
|
+
const original = fs.readFileSync(resolvedPath, 'utf-8');
|
|
137
|
+
// Parse
|
|
138
|
+
let parsed;
|
|
139
|
+
try {
|
|
140
|
+
parsed = parseTestCaseXml(original);
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
const err = makeError('INVALID_XML', `Cannot parse test case: ${e.message}`, requestId);
|
|
144
|
+
return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
|
|
145
|
+
}
|
|
146
|
+
const apiCalls = getApiCalls(parsed);
|
|
147
|
+
if (!apiCalls) {
|
|
148
|
+
const err = makeError('INVALID_XML', 'Test case XML does not contain a <testCase><steps><apiCall> structure', requestId);
|
|
149
|
+
return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
|
|
150
|
+
}
|
|
151
|
+
// Find target step
|
|
152
|
+
const targetIndex = apiCalls.findIndex((c) => String(c['@_testItemId']) === input.test_item_id);
|
|
153
|
+
if (targetIndex === -1) {
|
|
154
|
+
const allIds = collectAllTestItemIds(parsed);
|
|
155
|
+
const err = makeError('STEP_NOT_FOUND', `Step with testItemId "${input.test_item_id}" not found in ${resolvedPath}`, requestId, false, { all_test_item_ids: allIds });
|
|
156
|
+
return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
|
|
157
|
+
}
|
|
158
|
+
// Mutate the parsed tree
|
|
159
|
+
if (input.mode === 'remove') {
|
|
160
|
+
apiCalls.splice(targetIndex, 1);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// mode=add
|
|
164
|
+
const insertAt = input.position === 'before' ? targetIndex : targetIndex + 1;
|
|
165
|
+
apiCalls.splice(insertAt, 0, newStep);
|
|
166
|
+
}
|
|
167
|
+
// Rebuild XML
|
|
168
|
+
const mutatedXml = buildTestCaseXml(parsed);
|
|
169
|
+
// Write backup, then write mutated file via temp→rename to minimise partial-write risk
|
|
170
|
+
const tmpPath = resolvedPath + '.tmp';
|
|
171
|
+
fs.writeFileSync(bakPath, original, 'utf-8');
|
|
172
|
+
fs.writeFileSync(tmpPath, mutatedXml, 'utf-8');
|
|
173
|
+
fs.renameSync(tmpPath, resolvedPath);
|
|
174
|
+
// Validate if requested
|
|
175
|
+
let validation;
|
|
176
|
+
if (input.validate_after_edit) {
|
|
177
|
+
try {
|
|
178
|
+
validation = validateTestCase(mutatedXml, path.basename(resolvedPath, '.testcase'));
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// treat thrown validation errors as failures
|
|
182
|
+
validation = null;
|
|
183
|
+
}
|
|
184
|
+
if (!validation || !validation.is_valid) {
|
|
185
|
+
// Restore from backup
|
|
186
|
+
fs.writeFileSync(resolvedPath, original, 'utf-8');
|
|
187
|
+
fs.unlinkSync(bakPath);
|
|
188
|
+
const err = makeError('INVALID_XML_AFTER_EDIT', `Validation failed after ${input.mode}; original file restored from backup`, requestId, false, { validation_issues: validation?.issues ?? ['Validation threw an unexpected error'] });
|
|
189
|
+
return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Success — delete backup
|
|
193
|
+
try {
|
|
194
|
+
fs.unlinkSync(bakPath);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// non-fatal
|
|
198
|
+
}
|
|
199
|
+
const result = {
|
|
200
|
+
requestId,
|
|
201
|
+
success: true,
|
|
202
|
+
test_item_id: input.test_item_id,
|
|
203
|
+
mode: input.mode,
|
|
204
|
+
...(input.validate_after_edit && validation ? { validation } : {}),
|
|
205
|
+
};
|
|
206
|
+
return {
|
|
207
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
const error = err;
|
|
212
|
+
const errResult = makeError(error instanceof PathPolicyError ? error.code : 'STEP_EDIT_ERROR', error.message, requestId);
|
|
213
|
+
log('error', 'provar.testcase.step.edit failed', { requestId, error: error.message });
|
|
214
|
+
return { isError: true, content: [{ type: 'text', text: JSON.stringify(errResult) }] };
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
export function registerAllTestCaseStepTools(server, config) {
|
|
219
|
+
registerTestCaseStepEdit(server, config);
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=testCaseStepTools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testCaseStepTools.js","sourceRoot":"","sources":["../../../src/mcp/tools/testCaseStepTools.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,17 @@ export interface TestCaseValidationResult {
|
|
|
15
15
|
/** Violations from the Best Practices Engine (same rules as the Quality Hub API). */
|
|
16
16
|
best_practices_violations?: Array<import('./bestPracticesEngine.js').BPViolation>;
|
|
17
17
|
best_practices_rules_evaluated?: number;
|
|
18
|
+
/** Which ruleset produced this result. Always present. */
|
|
19
|
+
validation_source: 'quality_hub' | 'local' | 'local_fallback';
|
|
20
|
+
/** Set when falling back to local — explains why and what to do. */
|
|
21
|
+
validation_warning?: string;
|
|
18
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Reads a test case file from disk, validates it, and returns the result.
|
|
25
|
+
* Used by Wave 2 (testCaseGenerate) and Wave 3 (testCaseStepEdit) to validate
|
|
26
|
+
* after mutations without spawning a separate MCP tool call.
|
|
27
|
+
* Throws on path-policy violation or missing file.
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateTestCaseXml(filePath: string, config: ServerConfig): TestCaseValidationResult;
|
|
19
30
|
/** Pure function — exported for unit testing */
|
|
20
31
|
export declare function validateTestCase(xmlContent: string, testName?: string): TestCaseValidationResult;
|
|
@@ -12,13 +12,24 @@ import { XMLParser } from 'fast-xml-parser';
|
|
|
12
12
|
import { assertPathAllowed, PathPolicyError } from '../security/pathPolicy.js';
|
|
13
13
|
import { makeError, makeRequestId } from '../schemas/common.js';
|
|
14
14
|
import { log } from '../logging/logger.js';
|
|
15
|
+
import { resolveApiKey } from '../../services/auth/credentials.js';
|
|
16
|
+
import { qualityHubClient, getQualityHubBaseUrl, QualityHubAuthError, QualityHubRateLimitError, REQUEST_ACCESS_URL, } from '../../services/qualityHub/client.js';
|
|
15
17
|
import { runBestPractices } from './bestPracticesEngine.js';
|
|
18
|
+
const ONBOARDING_MESSAGE = 'Quality Hub validation unavailable — running local validation only (structural rules, no quality scoring).\n' +
|
|
19
|
+
'To enable Quality Hub (170 rules): run sf provar auth login\n' +
|
|
20
|
+
'For CI/CD: set the PROVAR_API_KEY environment variable.\n' +
|
|
21
|
+
`No account? Request access at: ${REQUEST_ACCESS_URL}`;
|
|
22
|
+
const AUTH_WARNING = 'Quality Hub API key is invalid or expired. Running local validation only.\n' +
|
|
23
|
+
`Run sf provar auth login to get a new key, or request access at: ${REQUEST_ACCESS_URL}`;
|
|
24
|
+
const RATE_LIMIT_WARNING = 'Quality Hub API rate limit reached. Running local validation only. Try again shortly.';
|
|
25
|
+
const UNREACHABLE_WARNING = 'Quality Hub API unreachable. Running local validation only (structural rules, no quality scoring).\n' +
|
|
26
|
+
'For CI/CD: set PROVAR_QUALITY_HUB_URL and PROVAR_API_KEY environment variables.';
|
|
16
27
|
export function registerTestCaseValidate(server, config) {
|
|
17
|
-
server.tool('provar.testcase.validate', 'Validate a Provar XML test case for structural correctness and quality. Checks XML declaration, root element, required attributes (guid UUID v4, testItemId integer), <steps> presence, and applies best-practice rules (
|
|
28
|
+
server.tool('provar.testcase.validate', 'Validate a Provar XML test case for structural correctness and quality. Checks XML declaration, root element, required attributes (guid UUID v4, testItemId integer), <steps> presence, and applies best-practice rules. When a Provar API key is configured (via sf provar auth login or PROVAR_API_KEY env var), calls the Quality Hub API for full 170-rule scoring. Falls back to local validation if no key is set or the API is unavailable. Returns validity_score (schema compliance), quality_score (best practices, 0–100), and validation_source indicating which ruleset was applied.', {
|
|
18
29
|
content: z.string().optional().describe('XML content to validate directly (alias: xml)'),
|
|
19
30
|
xml: z.string().optional().describe('XML content to validate — API-compatible alias for content'),
|
|
20
31
|
file_path: z.string().optional().describe('Path to .xml test case file'),
|
|
21
|
-
}, ({ content, xml, file_path }) => {
|
|
32
|
+
}, async ({ content, xml, file_path }) => {
|
|
22
33
|
const requestId = makeRequestId();
|
|
23
34
|
log('info', 'provar.testcase.validate', { requestId, has_content: !!(content ?? xml), file_path });
|
|
24
35
|
try {
|
|
@@ -37,8 +48,62 @@ export function registerTestCaseValidate(server, config) {
|
|
|
37
48
|
const err = makeError('MISSING_INPUT', 'Provide either content or file_path.', requestId);
|
|
38
49
|
return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
|
|
39
50
|
}
|
|
40
|
-
const
|
|
41
|
-
|
|
51
|
+
const apiKey = resolveApiKey();
|
|
52
|
+
if (apiKey) {
|
|
53
|
+
const baseUrl = getQualityHubBaseUrl();
|
|
54
|
+
try {
|
|
55
|
+
const apiResult = await qualityHubClient.validateTestCaseViaApi(source, apiKey, baseUrl);
|
|
56
|
+
const localMeta = validateTestCase(source);
|
|
57
|
+
const result = {
|
|
58
|
+
requestId,
|
|
59
|
+
...apiResult,
|
|
60
|
+
step_count: localMeta.step_count,
|
|
61
|
+
error_count: apiResult.issues.filter((i) => i.severity === 'ERROR').length,
|
|
62
|
+
warning_count: apiResult.issues.filter((i) => i.severity === 'WARNING').length,
|
|
63
|
+
test_case_id: localMeta.test_case_id,
|
|
64
|
+
test_case_name: localMeta.test_case_name,
|
|
65
|
+
validation_source: 'quality_hub',
|
|
66
|
+
};
|
|
67
|
+
log('info', 'provar.testcase.validate: quality_hub', { requestId });
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
70
|
+
structuredContent: result,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (apiErr) {
|
|
74
|
+
// API failed — determine the warning and fall through to local validation
|
|
75
|
+
let warning;
|
|
76
|
+
if (apiErr instanceof QualityHubAuthError) {
|
|
77
|
+
warning = AUTH_WARNING;
|
|
78
|
+
log('warn', 'provar.testcase.validate: auth error, falling back', { requestId });
|
|
79
|
+
}
|
|
80
|
+
else if (apiErr instanceof QualityHubRateLimitError) {
|
|
81
|
+
warning = RATE_LIMIT_WARNING;
|
|
82
|
+
log('warn', 'provar.testcase.validate: rate limited, falling back', { requestId });
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
warning = UNREACHABLE_WARNING;
|
|
86
|
+
log('warn', 'provar.testcase.validate: api unreachable, falling back', { requestId });
|
|
87
|
+
}
|
|
88
|
+
const localResult = {
|
|
89
|
+
requestId,
|
|
90
|
+
...validateTestCase(source),
|
|
91
|
+
validation_source: 'local_fallback',
|
|
92
|
+
validation_warning: warning,
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
content: [{ type: 'text', text: JSON.stringify(localResult) }],
|
|
96
|
+
structuredContent: localResult,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// No API key configured — run local validation with onboarding message
|
|
101
|
+
const result = {
|
|
102
|
+
requestId,
|
|
103
|
+
...validateTestCase(source),
|
|
104
|
+
validation_source: 'local',
|
|
105
|
+
validation_warning: ONBOARDING_MESSAGE,
|
|
106
|
+
};
|
|
42
107
|
return {
|
|
43
108
|
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
44
109
|
structuredContent: result,
|
|
@@ -54,13 +119,28 @@ export function registerTestCaseValidate(server, config) {
|
|
|
54
119
|
}
|
|
55
120
|
// ── Validator (ported from quality-hub-agents/lambda/src/validator/handler.py) ──
|
|
56
121
|
const UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
122
|
+
/**
|
|
123
|
+
* Reads a test case file from disk, validates it, and returns the result.
|
|
124
|
+
* Used by Wave 2 (testCaseGenerate) and Wave 3 (testCaseStepEdit) to validate
|
|
125
|
+
* after mutations without spawning a separate MCP tool call.
|
|
126
|
+
* Throws on path-policy violation or missing file.
|
|
127
|
+
*/
|
|
128
|
+
export function validateTestCaseXml(filePath, config) {
|
|
129
|
+
assertPathAllowed(filePath, config.allowedPaths);
|
|
130
|
+
const resolved = path.resolve(filePath);
|
|
131
|
+
if (!fs.existsSync(resolved)) {
|
|
132
|
+
throw Object.assign(new Error(`File not found: ${resolved}`), { code: 'TESTCASE_FILE_NOT_FOUND' });
|
|
133
|
+
}
|
|
134
|
+
return validateTestCase(fs.readFileSync(resolved, 'utf-8'));
|
|
135
|
+
}
|
|
57
136
|
/** Pure function — exported for unit testing */
|
|
58
137
|
export function validateTestCase(xmlContent, testName) {
|
|
59
138
|
const issues = [];
|
|
60
139
|
// TC_001: XML declaration
|
|
61
140
|
if (!xmlContent.trimStart().startsWith('<?xml')) {
|
|
62
141
|
issues.push({
|
|
63
|
-
rule_id: 'TC_001',
|
|
142
|
+
rule_id: 'TC_001',
|
|
143
|
+
severity: 'ERROR',
|
|
64
144
|
message: 'Missing XML declaration. File must start with <?xml version="1.0" encoding="UTF-8"?>.',
|
|
65
145
|
applies_to: 'document',
|
|
66
146
|
suggestion: 'Add XML declaration as the first line.',
|
|
@@ -79,7 +159,8 @@ export function validateTestCase(xmlContent, testName) {
|
|
|
79
159
|
catch (e) {
|
|
80
160
|
const parseError = e;
|
|
81
161
|
issues.push({
|
|
82
|
-
rule_id: 'TC_002',
|
|
162
|
+
rule_id: 'TC_002',
|
|
163
|
+
severity: 'ERROR',
|
|
83
164
|
message: `XML parse error: ${parseError.message}`,
|
|
84
165
|
applies_to: 'document',
|
|
85
166
|
suggestion: 'Fix XML syntax errors.',
|
|
@@ -89,7 +170,8 @@ export function validateTestCase(xmlContent, testName) {
|
|
|
89
170
|
// TC_003: Root element
|
|
90
171
|
if (!('testCase' in parsed)) {
|
|
91
172
|
issues.push({
|
|
92
|
-
rule_id: 'TC_003',
|
|
173
|
+
rule_id: 'TC_003',
|
|
174
|
+
severity: 'ERROR',
|
|
93
175
|
message: 'Root element must be <testCase>.',
|
|
94
176
|
applies_to: 'document',
|
|
95
177
|
suggestion: 'Ensure root element is <testCase>.',
|
|
@@ -105,7 +187,8 @@ export function validateTestCase(xmlContent, testName) {
|
|
|
105
187
|
const tcGuid = tc['@_guid'];
|
|
106
188
|
if (!tcId) {
|
|
107
189
|
issues.push({
|
|
108
|
-
rule_id: 'TC_010',
|
|
190
|
+
rule_id: 'TC_010',
|
|
191
|
+
severity: 'ERROR',
|
|
109
192
|
message: 'testCase missing required id attribute.',
|
|
110
193
|
applies_to: 'testCase',
|
|
111
194
|
suggestion: 'Add id attribute to testCase element.',
|
|
@@ -113,7 +196,8 @@ export function validateTestCase(xmlContent, testName) {
|
|
|
113
196
|
}
|
|
114
197
|
if (!tcGuid) {
|
|
115
198
|
issues.push({
|
|
116
|
-
rule_id: 'TC_011',
|
|
199
|
+
rule_id: 'TC_011',
|
|
200
|
+
severity: 'ERROR',
|
|
117
201
|
message: 'testCase missing required guid attribute.',
|
|
118
202
|
applies_to: 'testCase',
|
|
119
203
|
suggestion: 'Add guid attribute (UUID v4) to testCase element.',
|
|
@@ -121,10 +205,11 @@ export function validateTestCase(xmlContent, testName) {
|
|
|
121
205
|
}
|
|
122
206
|
else if (!UUID_V4_RE.test(tcGuid)) {
|
|
123
207
|
issues.push({
|
|
124
|
-
rule_id: 'TC_012',
|
|
208
|
+
rule_id: 'TC_012',
|
|
209
|
+
severity: 'ERROR',
|
|
125
210
|
message: `testCase guid "${tcGuid}" is not a valid UUID v4.`,
|
|
126
211
|
applies_to: 'testCase',
|
|
127
|
-
suggestion: '
|
|
212
|
+
suggestion: 'Replace with a valid UUID v4 — e.g. crypto.randomUUID(). The 4th segment must begin with 8, 9, a, or b.',
|
|
128
213
|
});
|
|
129
214
|
}
|
|
130
215
|
// TC_013 (registryId) is intentionally not checked here — registryId is a
|
|
@@ -134,13 +219,24 @@ export function validateTestCase(xmlContent, testName) {
|
|
|
134
219
|
// TC_020: <steps> element
|
|
135
220
|
if (!('steps' in tc)) {
|
|
136
221
|
issues.push({
|
|
137
|
-
rule_id: 'TC_020',
|
|
222
|
+
rule_id: 'TC_020',
|
|
223
|
+
severity: 'ERROR',
|
|
138
224
|
message: 'testCase missing <steps> element.',
|
|
139
225
|
applies_to: 'testCase',
|
|
140
226
|
suggestion: 'Wrap all step elements in a <steps> element.',
|
|
141
227
|
});
|
|
142
228
|
return finalize(issues, tcId, tcName, 0, xmlContent, testName);
|
|
143
229
|
}
|
|
230
|
+
// DATA-001: <dataTable> binding is silently ignored in standalone CLI execution
|
|
231
|
+
if ('dataTable' in tc && tc['dataTable'] != null) {
|
|
232
|
+
issues.push({
|
|
233
|
+
rule_id: 'DATA-001',
|
|
234
|
+
severity: 'WARNING',
|
|
235
|
+
message: 'testCase declares a <dataTable> but CLI standalone execution does not bind CSV column variables — steps using <value class="variable"> references will resolve to null.',
|
|
236
|
+
applies_to: 'testCase',
|
|
237
|
+
suggestion: 'Use SetValues (Test scope) steps to bind data for standalone CLI execution, or add this test case to a test plan.',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
144
240
|
// Same self-closing guard for <steps/> → fast-xml-parser yields ''
|
|
145
241
|
const rawSteps = tc['steps'];
|
|
146
242
|
const steps = rawSteps !== null && typeof rawSteps === 'object' ? rawSteps : {};
|
|
@@ -161,7 +257,8 @@ function validateApiCall(call, issues) {
|
|
|
161
257
|
const label = apiId ? ` "${apiId}"` : '';
|
|
162
258
|
if (!callGuid) {
|
|
163
259
|
issues.push({
|
|
164
|
-
rule_id: 'TC_030',
|
|
260
|
+
rule_id: 'TC_030',
|
|
261
|
+
severity: 'ERROR',
|
|
165
262
|
message: `apiCall${label} missing guid attribute.`,
|
|
166
263
|
applies_to: 'apiCall',
|
|
167
264
|
suggestion: 'Add a UUID v4 guid to each apiCall.',
|
|
@@ -169,15 +266,17 @@ function validateApiCall(call, issues) {
|
|
|
169
266
|
}
|
|
170
267
|
else if (!UUID_V4_RE.test(callGuid)) {
|
|
171
268
|
issues.push({
|
|
172
|
-
rule_id: 'TC_031',
|
|
269
|
+
rule_id: 'TC_031',
|
|
270
|
+
severity: 'ERROR',
|
|
173
271
|
message: `apiCall${label} guid "${callGuid}" is not a valid UUID v4.`,
|
|
174
272
|
applies_to: 'apiCall',
|
|
175
|
-
suggestion: '
|
|
273
|
+
suggestion: 'Replace with a valid UUID v4 — e.g. crypto.randomUUID(). The 4th segment must begin with 8, 9, a, or b.',
|
|
176
274
|
});
|
|
177
275
|
}
|
|
178
276
|
if (!apiId) {
|
|
179
277
|
issues.push({
|
|
180
|
-
rule_id: 'TC_032',
|
|
278
|
+
rule_id: 'TC_032',
|
|
279
|
+
severity: 'ERROR',
|
|
181
280
|
message: 'apiCall missing apiId attribute.',
|
|
182
281
|
applies_to: 'apiCall',
|
|
183
282
|
suggestion: 'Add apiId attribute (e.g., UiConnect, ApexSoqlQuery).',
|
|
@@ -185,7 +284,8 @@ function validateApiCall(call, issues) {
|
|
|
185
284
|
}
|
|
186
285
|
if (!name) {
|
|
187
286
|
issues.push({
|
|
188
|
-
rule_id: 'TC_033',
|
|
287
|
+
rule_id: 'TC_033',
|
|
288
|
+
severity: 'WARNING',
|
|
189
289
|
message: `apiCall${label} missing name attribute.`,
|
|
190
290
|
applies_to: 'apiCall',
|
|
191
291
|
suggestion: 'Add a descriptive name attribute.',
|
|
@@ -193,7 +293,8 @@ function validateApiCall(call, issues) {
|
|
|
193
293
|
}
|
|
194
294
|
if (!testItemId) {
|
|
195
295
|
issues.push({
|
|
196
|
-
rule_id: 'TC_034',
|
|
296
|
+
rule_id: 'TC_034',
|
|
297
|
+
severity: 'ERROR',
|
|
197
298
|
message: `apiCall${label} missing testItemId attribute.`,
|
|
198
299
|
applies_to: 'apiCall',
|
|
199
300
|
suggestion: 'Add sequential testItemId (1, 2, 3...).',
|
|
@@ -201,12 +302,35 @@ function validateApiCall(call, issues) {
|
|
|
201
302
|
}
|
|
202
303
|
else if (!/^\d+$/.test(testItemId)) {
|
|
203
304
|
issues.push({
|
|
204
|
-
rule_id: 'TC_035',
|
|
305
|
+
rule_id: 'TC_035',
|
|
306
|
+
severity: 'ERROR',
|
|
205
307
|
message: `apiCall${label} testItemId "${testItemId}" must be a whole number.`,
|
|
206
308
|
applies_to: 'apiCall',
|
|
207
309
|
suggestion: 'Use sequential integers for testItemId.',
|
|
208
310
|
});
|
|
209
311
|
}
|
|
312
|
+
// ASSERT-001: AssertValues using UI namedValues format instead of variable format
|
|
313
|
+
if (apiId?.includes('AssertValues')) {
|
|
314
|
+
const rawArgs = call['arguments'];
|
|
315
|
+
if (rawArgs) {
|
|
316
|
+
const argRaw = rawArgs['argument'];
|
|
317
|
+
const argList = !argRaw
|
|
318
|
+
? []
|
|
319
|
+
: Array.isArray(argRaw)
|
|
320
|
+
? argRaw
|
|
321
|
+
: [argRaw];
|
|
322
|
+
const hasValuesArg = argList.some((a) => a['@_id'] === 'values');
|
|
323
|
+
if (hasValuesArg) {
|
|
324
|
+
issues.push({
|
|
325
|
+
rule_id: 'ASSERT-001',
|
|
326
|
+
severity: 'WARNING',
|
|
327
|
+
message: `AssertValues step "${name ?? '(unnamed)'}" uses namedValues format (argument id="values") — designed for UI element attribute assertions. For Apex/SOQL result or variable comparisons this silently passes as null=null.`,
|
|
328
|
+
applies_to: 'apiCall',
|
|
329
|
+
suggestion: 'Use separate expectedValue, actualValue, and comparisonType arguments for variable or Apex result comparisons.',
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
210
334
|
}
|
|
211
335
|
function finalize(issues, testCaseId, testCaseName, stepCount, xmlContent, testName) {
|
|
212
336
|
const errorCount = issues.filter((i) => i.severity === 'ERROR').length;
|
|
@@ -227,6 +351,9 @@ function finalize(issues, testCaseId, testCaseName, stepCount, xmlContent, testN
|
|
|
227
351
|
issues,
|
|
228
352
|
best_practices_violations: bp.violations,
|
|
229
353
|
best_practices_rules_evaluated: bp.rules_evaluated,
|
|
354
|
+
// validation_source is set by the caller (MCP tool handler or direct callers).
|
|
355
|
+
// Default to 'local' here so the pure validateTestCase() function is self-contained.
|
|
356
|
+
validation_source: 'local',
|
|
230
357
|
};
|
|
231
358
|
}
|
|
232
359
|
//# sourceMappingURL=testCaseValidate.js.map
|