@provartesting/provardx-cli 1.5.0-dev.2 → 1.5.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 +163 -12
- package/bin/mcp-start.js +74 -0
- 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/commands/provar/mcp/start.d.ts +2 -0
- package/lib/commands/provar/mcp/start.js +14 -1
- package/lib/commands/provar/mcp/start.js.map +1 -1
- package/lib/mcp/docs/NITROX_CATALOG_SOURCE.json +6 -0
- package/lib/mcp/docs/NITROX_COMPONENT_CATALOG.md +2001 -0
- package/lib/mcp/docs/PROVAR_TEST_STEP_REFERENCE.md +1430 -0
- package/lib/mcp/docs/PROVAR_TOOL_GUIDE.md +187 -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 +1 -73
- 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/guidePrompts.d.ts +4 -0
- package/lib/mcp/prompts/guidePrompts.js +334 -0
- package/lib/mcp/prompts/guidePrompts.js.map +1 -0
- package/lib/mcp/prompts/index.d.ts +2 -0
- package/lib/mcp/prompts/index.js +23 -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 +58 -3
- package/lib/mcp/security/pathPolicy.js.map +1 -1
- package/lib/mcp/server.d.ts +18 -0
- package/lib/mcp/server.js +232 -19
- package/lib/mcp/server.js.map +1 -1
- package/lib/mcp/tools/antTools.d.ts +15 -0
- package/lib/mcp/tools/antTools.js +369 -170
- package/lib/mcp/tools/antTools.js.map +1 -1
- package/lib/mcp/tools/automationTools.d.ts +18 -8
- package/lib/mcp/tools/automationTools.js +333 -176
- 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 +242 -0
- package/lib/mcp/tools/connectionTools.js.map +1 -0
- package/lib/mcp/tools/defectTools.d.ts +1 -1
- package/lib/mcp/tools/defectTools.js +61 -50
- package/lib/mcp/tools/defectTools.js.map +1 -1
- package/lib/mcp/tools/descHelper.d.ts +5 -0
- package/lib/mcp/tools/descHelper.js +14 -0
- package/lib/mcp/tools/descHelper.js.map +1 -0
- package/lib/mcp/tools/hierarchyValidate.d.ts +1 -1
- package/lib/mcp/tools/hierarchyValidate.js +127 -42
- package/lib/mcp/tools/hierarchyValidate.js.map +1 -1
- package/lib/mcp/tools/nitroXTools.d.ts +23 -0
- package/lib/mcp/tools/nitroXTools.js +863 -0
- package/lib/mcp/tools/nitroXTools.js.map +1 -0
- package/lib/mcp/tools/pageObjectGenerate.js +150 -57
- package/lib/mcp/tools/pageObjectGenerate.js.map +1 -1
- package/lib/mcp/tools/pageObjectValidate.js +143 -46
- package/lib/mcp/tools/pageObjectValidate.js.map +1 -1
- package/lib/mcp/tools/projectInspect.js +79 -32
- package/lib/mcp/tools/projectInspect.js.map +1 -1
- package/lib/mcp/tools/projectValidateFromPath.js +185 -58
- package/lib/mcp/tools/projectValidateFromPath.js.map +1 -1
- package/lib/mcp/tools/propertiesTools.d.ts +2 -0
- package/lib/mcp/tools/propertiesTools.js +358 -78
- package/lib/mcp/tools/propertiesTools.js.map +1 -1
- package/lib/mcp/tools/qualityHubApiTools.d.ts +3 -0
- package/lib/mcp/tools/qualityHubApiTools.js +139 -0
- package/lib/mcp/tools/qualityHubApiTools.js.map +1 -0
- package/lib/mcp/tools/qualityHubTools.js +292 -72
- package/lib/mcp/tools/qualityHubTools.js.map +1 -1
- package/lib/mcp/tools/rcaTools.d.ts +3 -2
- package/lib/mcp/tools/rcaTools.js +194 -56
- package/lib/mcp/tools/rcaTools.js.map +1 -1
- package/lib/mcp/tools/sfSpawn.d.ts +25 -3
- package/lib/mcp/tools/sfSpawn.js +154 -6
- package/lib/mcp/tools/sfSpawn.js.map +1 -1
- package/lib/mcp/tools/testCaseGenerate.js +285 -78
- package/lib/mcp/tools/testCaseGenerate.js.map +1 -1
- package/lib/mcp/tools/testCaseStepTools.d.ts +4 -0
- package/lib/mcp/tools/testCaseStepTools.js +244 -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 +381 -46
- package/lib/mcp/tools/testCaseValidate.js.map +1 -1
- package/lib/mcp/tools/testPlanTools.d.ts +1 -0
- package/lib/mcp/tools/testPlanTools.js +316 -59
- package/lib/mcp/tools/testPlanTools.js.map +1 -1
- package/lib/mcp/tools/testPlanValidate.js +114 -23
- package/lib/mcp/tools/testPlanValidate.js.map +1 -1
- package/lib/mcp/tools/testSuiteValidate.js +130 -15
- package/lib/mcp/tools/testSuiteValidate.js.map +1 -1
- package/lib/mcp/update/updateChecker.d.ts +14 -0
- package/lib/mcp/update/updateChecker.js +228 -0
- package/lib/mcp/update/updateChecker.js.map +1 -0
- package/lib/mcp/utils/detailLevel.d.ts +9 -0
- package/lib/mcp/utils/detailLevel.js +20 -0
- package/lib/mcp/utils/detailLevel.js.map +1 -0
- package/lib/mcp/utils/fieldMask.d.ts +17 -0
- package/lib/mcp/utils/fieldMask.js +75 -0
- package/lib/mcp/utils/fieldMask.js.map +1 -0
- package/lib/mcp/utils/tokenMeta.d.ts +40 -0
- package/lib/mcp/utils/tokenMeta.js +90 -0
- package/lib/mcp/utils/tokenMeta.js.map +1 -0
- package/lib/mcp/utils/validationDiff.d.ts +57 -0
- package/lib/mcp/utils/validationDiff.js +191 -0
- package/lib/mcp/utils/validationDiff.js.map +1 -0
- package/lib/mcp/utils/validationScore.d.ts +15 -0
- package/lib/mcp/utils/validationScore.js +31 -0
- package/lib/mcp/utils/validationScore.js.map +1 -0
- 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/projectValidation.d.ts +5 -2
- package/lib/services/projectValidation.js +83 -31
- package/lib/services/projectValidation.js.map +1 -1
- 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/messages/sf.provar.mcp.start.md +83 -48
- package/oclif.manifest.json +325 -28
- package/package.json +35 -12
|
@@ -12,6 +12,7 @@ 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 { desc } from './descHelper.js';
|
|
15
16
|
// ── Sub-schemas ───────────────────────────────────────────────────────────────
|
|
16
17
|
const FilesetSchema = z.object({
|
|
17
18
|
dir: z.string().describe('Directory path (relative or absolute) for the fileset'),
|
|
@@ -25,9 +26,7 @@ const FilesetSchema = z.object({
|
|
|
25
26
|
.describe('Specific .testcase or .testplan file names to include (e.g. ["Login.testcase"]). Omit to run everything in dir.'),
|
|
26
27
|
});
|
|
27
28
|
const PlanFeatureSchema = z.object({
|
|
28
|
-
name: z
|
|
29
|
-
.enum(['PDF', 'PIECHART', 'EMAIL', 'JUNIT'])
|
|
30
|
-
.describe('Feature name (PDF, PIECHART, EMAIL, JUNIT)'),
|
|
29
|
+
name: z.enum(['PDF', 'PIECHART', 'EMAIL', 'JUNIT']).describe('Feature name (PDF, PIECHART, EMAIL, JUNIT)'),
|
|
31
30
|
type: z.enum(['OUTPUT', 'NOTIFICATION']).describe('Feature type'),
|
|
32
31
|
enabled: z.boolean().describe('Whether this feature is enabled'),
|
|
33
32
|
});
|
|
@@ -56,148 +55,163 @@ const AttachmentPropertiesSchema = z.object({
|
|
|
56
55
|
});
|
|
57
56
|
// ── Generate tool ─────────────────────────────────────────────────────────────
|
|
58
57
|
export function registerAntGenerate(server, config) {
|
|
59
|
-
server.
|
|
60
|
-
'Generate
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
58
|
+
server.registerTool('provar_ant_generate', {
|
|
59
|
+
title: 'Generate ANT Build File',
|
|
60
|
+
description: desc([
|
|
61
|
+
'Generate a Provar ANT build.xml file.',
|
|
62
|
+
'Produces the standard <project> skeleton with Provar-Compile and Run-Test-Case tasks.',
|
|
63
|
+
'Supports targeting tests by project folder, plan folder, or specific .testcase files via filesets.',
|
|
64
|
+
'Returns XML content. Writes to disk only when dry_run=false.',
|
|
65
|
+
].join(' '), 'Generate a Provar ANT build.xml with Provar-Compile and Run-Test-Case tasks.'),
|
|
66
|
+
inputSchema: {
|
|
67
|
+
// ── Core paths ──────────────────────────────────────────────────────────
|
|
68
|
+
provar_home: z
|
|
69
|
+
.string()
|
|
70
|
+
.describe(desc('Absolute path to the Provar installation directory (e.g. "C:/Program Files/Provar/"). Used for provar.home property and ant taskdef classpaths.', 'string, absolute path to Provar installation')),
|
|
71
|
+
project_path: z
|
|
72
|
+
.string()
|
|
73
|
+
.default('..')
|
|
74
|
+
.describe(desc('Path to the Provar test project root. Defaults to ".." (parent of the ANT folder).', 'string, path to project root')),
|
|
75
|
+
results_path: z
|
|
76
|
+
.string()
|
|
77
|
+
.default('../ANT/Results')
|
|
78
|
+
.describe(desc('Path where test results are written. Defaults to "../ANT/Results".', 'string, path for test results')),
|
|
79
|
+
project_cache_path: z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe(desc('Path to the .provarCaches directory. Defaults to "../../.provarCaches" relative to the ANT folder.', 'string, optional; path to .provarCaches')),
|
|
83
|
+
license_path: z
|
|
84
|
+
.string()
|
|
85
|
+
.optional()
|
|
86
|
+
.describe(desc('Path to the Provar .licenses directory (e.g. "${env.PROVAR_HOME}/.licenses").', 'string, optional; path to .licenses dir')),
|
|
87
|
+
smtp_path: z
|
|
88
|
+
.string()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe(desc('Path to the Provar .smtp directory (e.g. "${env.PROVAR_HOME}/.smtp").', 'string, optional; path to .smtp dir')),
|
|
91
|
+
// ── Test selection ──────────────────────────────────────────────────────
|
|
92
|
+
filesets: z
|
|
93
|
+
.array(FilesetSchema)
|
|
94
|
+
.min(1)
|
|
95
|
+
.describe(desc('One or more filesets defining which tests to run. ' +
|
|
96
|
+
'To run all tests under a folder: { dir: "../tests" }. ' +
|
|
97
|
+
'To run a plan: { id: "testplan", dir: "../plans/MyPlan" }. ' +
|
|
98
|
+
'To run specific test cases: { dir: "../tests/Suite", includes: ["MyTest.testcase"] }.', 'array, min 1; filesets defining which tests to run')),
|
|
99
|
+
// ── Browser / environment ───────────────────────────────────────────────
|
|
100
|
+
web_browser: z
|
|
101
|
+
.enum(['Chrome', 'Chrome_Headless', 'Firefox', 'Edge', 'Edge_Legacy', 'Safari', 'IE'])
|
|
102
|
+
.default('Chrome')
|
|
103
|
+
.describe(desc('Web browser to use for test execution.', 'enum Chrome|Chrome_Headless|Firefox|Edge|Safari|IE')),
|
|
104
|
+
web_browser_configuration: z
|
|
105
|
+
.string()
|
|
106
|
+
.default('Full Screen')
|
|
107
|
+
.describe(desc('Browser window configuration (e.g. "Full Screen").', 'string, browser window config')),
|
|
108
|
+
web_browser_provider_name: z
|
|
109
|
+
.string()
|
|
110
|
+
.default('Desktop')
|
|
111
|
+
.describe(desc('Browser provider name (e.g. "Desktop").', 'string, browser provider name')),
|
|
112
|
+
web_browser_device_name: z
|
|
113
|
+
.string()
|
|
114
|
+
.default('Full Screen')
|
|
115
|
+
.describe(desc('Browser device name (e.g. "Full Screen").', 'string, browser device name')),
|
|
116
|
+
test_environment: z
|
|
117
|
+
.string()
|
|
118
|
+
.default('')
|
|
119
|
+
.describe(desc('Named test environment to use (must match a connection in the project). Empty string uses default.', 'string, optional; named test environment')),
|
|
120
|
+
// ── Cache / metadata ────────────────────────────────────────────────────
|
|
121
|
+
salesforce_metadata_cache: z
|
|
122
|
+
.enum(['Reuse', 'Refresh', 'Reload'])
|
|
123
|
+
.default('Reuse')
|
|
124
|
+
.describe(desc('Salesforce metadata cache strategy: Reuse (fastest, uses cached), Refresh (re-downloads), Reload (clears and re-downloads).', 'enum Reuse|Refresh|Reload')),
|
|
125
|
+
// ── Output / logging ────────────────────────────────────────────────────
|
|
126
|
+
results_path_disposition: z
|
|
127
|
+
.enum(['Increment', 'Replace', 'Reuse'])
|
|
128
|
+
.default('Increment')
|
|
129
|
+
.describe(desc('How to handle the results folder when it already exists: Increment (new subfolder), Replace (overwrite), Reuse (append).', 'enum Increment|Replace|Reuse')),
|
|
130
|
+
test_output_level: z
|
|
131
|
+
.enum(['BASIC', 'WARNING', 'DEBUG'])
|
|
132
|
+
.default('BASIC')
|
|
133
|
+
.describe(desc('Verbosity level for test output logs.', 'enum BASIC|WARNING|DEBUG')),
|
|
134
|
+
plugin_output_level: z
|
|
135
|
+
.enum(['BASIC', 'WARNING', 'DEBUG'])
|
|
136
|
+
.default('WARNING')
|
|
137
|
+
.describe(desc('Verbosity level for plugin output logs.', 'enum BASIC|WARNING|DEBUG')),
|
|
138
|
+
// ── Execution behaviour ─────────────────────────────────────────────────
|
|
139
|
+
stop_test_run_on_error: z
|
|
140
|
+
.boolean()
|
|
141
|
+
.default(false)
|
|
142
|
+
.describe(desc('Abort the entire test run when any test case fails.', 'bool, optional; abort on failure')),
|
|
143
|
+
exclude_callable_test_cases: z
|
|
144
|
+
.boolean()
|
|
145
|
+
.default(true)
|
|
146
|
+
.describe(desc('Skip test cases marked as callable (library/helper) when true.', 'bool, optional; skip callable tests')),
|
|
147
|
+
dont_fail_build: z
|
|
148
|
+
.boolean()
|
|
149
|
+
.optional()
|
|
150
|
+
.describe(desc('When true, the ANT build does not fail even if tests fail. Useful for CI pipelines that collect results separately.', 'bool, optional; skip build failure on test fail')),
|
|
151
|
+
invoke_test_run_monitor: z
|
|
152
|
+
.boolean()
|
|
153
|
+
.default(true)
|
|
154
|
+
.describe(desc('Enable the Provar test run monitor.', 'bool, optional; enable test run monitor')),
|
|
155
|
+
// ── Secrets / security ──────────────────────────────────────────────────
|
|
156
|
+
secrets_password: z
|
|
157
|
+
.string()
|
|
158
|
+
.default('${env.ProvarSecretsPassword}')
|
|
159
|
+
.describe(desc('Encryption key used to decrypt the Provar .secrets file (the password string itself, not a file path). Defaults to reading from the ProvarSecretsPassword environment variable.', 'string, NOT a file path; encryption key for .secrets')),
|
|
160
|
+
test_environment_secrets_password: z
|
|
161
|
+
.string()
|
|
162
|
+
.optional()
|
|
163
|
+
.describe(desc('Per-environment secrets password. Defaults to reading from the ProvarSecretsPassword_EnvName environment variable.', 'string, optional; per-environment secrets key')),
|
|
164
|
+
// ── Test Cycle ──────────────────────────────────────────────────────────
|
|
165
|
+
test_cycle_path: z
|
|
166
|
+
.string()
|
|
167
|
+
.optional()
|
|
168
|
+
.describe(desc('Path to a TestCycle folder (used with test cycle reporting).', 'string, optional; path to TestCycle folder')),
|
|
169
|
+
test_cycle_run_type: z
|
|
170
|
+
.enum(['ALL', 'FAILED', 'NEW'])
|
|
171
|
+
.optional()
|
|
172
|
+
.describe(desc('Which tests in the cycle to run (ALL, FAILED, NEW).', 'enum ALL|FAILED|NEW, optional')),
|
|
173
|
+
// ── Plan features ───────────────────────────────────────────────────────
|
|
174
|
+
plan_features: z
|
|
175
|
+
.array(PlanFeatureSchema)
|
|
176
|
+
.optional()
|
|
177
|
+
.describe(desc('Output and notification features to enable/disable (e.g. PDF, PIECHART, EMAIL). ' +
|
|
178
|
+
'Only meaningful when running by test plan.', 'array, optional; plan output/notification features')),
|
|
179
|
+
// ── Email / attachment reporting ────────────────────────────────────────
|
|
180
|
+
email_properties: EmailPropertiesSchema.optional().describe(desc('Email notification settings. Omit to exclude <emailProperties> from the XML.', 'object, optional; email notification settings')),
|
|
181
|
+
attachment_properties: AttachmentPropertiesSchema.optional().describe(desc('Attachment/report content settings. Omit to exclude <attachmentProperties> from the XML.', 'object, optional; attachment/report content settings')),
|
|
182
|
+
// ── File output ─────────────────────────────────────────────────────────
|
|
183
|
+
output_path: z
|
|
184
|
+
.string()
|
|
185
|
+
.optional()
|
|
186
|
+
.describe(desc('Where to write the build.xml file (returned in response). Required when dry_run=false.', 'string, optional; absolute path for build.xml output')),
|
|
187
|
+
overwrite: z
|
|
188
|
+
.boolean()
|
|
189
|
+
.default(false)
|
|
190
|
+
.describe(desc('Overwrite output_path if the file already exists.', 'bool, optional; overwrite if exists')),
|
|
191
|
+
dry_run: z
|
|
192
|
+
.boolean()
|
|
193
|
+
.default(true)
|
|
194
|
+
.describe(desc('true = return XML only (default); false = write to output_path.', 'bool, optional; true=return only, false=write')),
|
|
195
|
+
},
|
|
193
196
|
}, (input) => {
|
|
194
197
|
const requestId = makeRequestId();
|
|
195
|
-
log('info', '
|
|
198
|
+
log('info', 'provar_ant_generate', {
|
|
196
199
|
requestId,
|
|
197
200
|
output_path: input.output_path,
|
|
198
201
|
dry_run: input.dry_run,
|
|
199
202
|
});
|
|
200
203
|
try {
|
|
204
|
+
// Validate all path inputs before writing anything — these get embedded in the
|
|
205
|
+
// generated ANT build.xml and would be accessed by ANT at execution time.
|
|
206
|
+
assertPathAllowed(input.provar_home, config.allowedPaths);
|
|
207
|
+
assertPathAllowed(input.project_path, config.allowedPaths);
|
|
208
|
+
assertPathAllowed(input.results_path, config.allowedPaths);
|
|
209
|
+
if (input.license_path)
|
|
210
|
+
assertPathAllowed(input.license_path, config.allowedPaths);
|
|
211
|
+
if (input.smtp_path)
|
|
212
|
+
assertPathAllowed(input.smtp_path, config.allowedPaths);
|
|
213
|
+
if (input.project_cache_path)
|
|
214
|
+
assertPathAllowed(input.project_cache_path, config.allowedPaths);
|
|
201
215
|
const xmlContent = buildAntXml(input);
|
|
202
216
|
const filePath = input.output_path ? path.resolve(input.output_path) : undefined;
|
|
203
217
|
let written = false;
|
|
@@ -210,7 +224,7 @@ export function registerAntGenerate(server, config) {
|
|
|
210
224
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
211
225
|
fs.writeFileSync(filePath, xmlContent, 'utf-8');
|
|
212
226
|
written = true;
|
|
213
|
-
log('info', '
|
|
227
|
+
log('info', 'provar_ant_generate: wrote file', { requestId, filePath });
|
|
214
228
|
}
|
|
215
229
|
const result = {
|
|
216
230
|
requestId,
|
|
@@ -226,31 +240,35 @@ export function registerAntGenerate(server, config) {
|
|
|
226
240
|
}
|
|
227
241
|
catch (err) {
|
|
228
242
|
const error = err;
|
|
229
|
-
const errResult = makeError(error instanceof PathPolicyError ? error.code :
|
|
230
|
-
log('error', '
|
|
243
|
+
const errResult = makeError(error instanceof PathPolicyError ? error.code : error.code ?? 'GENERATE_ERROR', error.message, requestId, false);
|
|
244
|
+
log('error', 'provar_ant_generate failed', { requestId, error: error.message });
|
|
231
245
|
return { isError: true, content: [{ type: 'text', text: JSON.stringify(errResult) }] };
|
|
232
246
|
}
|
|
233
247
|
});
|
|
234
248
|
}
|
|
235
249
|
// ── Validate tool ─────────────────────────────────────────────────────────────
|
|
236
250
|
export function registerAntValidate(server, config) {
|
|
237
|
-
server.
|
|
238
|
-
'Validate
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
+
server.registerTool('provar_ant_validate', {
|
|
252
|
+
title: 'Validate ANT Build File',
|
|
253
|
+
description: desc([
|
|
254
|
+
'Validate a Provar ANT build.xml for structural correctness.',
|
|
255
|
+
'Checks XML well-formedness, required <taskdef> declarations, <Provar-Compile> step,',
|
|
256
|
+
'<Run-Test-Case> with required attributes (provarHome, projectPath, resultsPath),',
|
|
257
|
+
'and at least one <fileset> child. Returns is_valid, issues list, and a validity_score.',
|
|
258
|
+
].join(' '), 'Validate a Provar ANT build.xml for structural correctness.'),
|
|
259
|
+
inputSchema: {
|
|
260
|
+
content: z
|
|
261
|
+
.string()
|
|
262
|
+
.optional()
|
|
263
|
+
.describe(desc('XML content to validate directly', 'string, optional; inline XML')),
|
|
264
|
+
file_path: z
|
|
265
|
+
.string()
|
|
266
|
+
.optional()
|
|
267
|
+
.describe(desc('Path to the build.xml file to validate', 'string, optional; absolute path to build.xml')),
|
|
268
|
+
},
|
|
251
269
|
}, ({ content, file_path }) => {
|
|
252
270
|
const requestId = makeRequestId();
|
|
253
|
-
log('info', '
|
|
271
|
+
log('info', 'provar_ant_validate', { requestId, has_content: !!content, file_path });
|
|
254
272
|
try {
|
|
255
273
|
let source = content;
|
|
256
274
|
if (!source && file_path) {
|
|
@@ -276,7 +294,7 @@ export function registerAntValidate(server, config) {
|
|
|
276
294
|
catch (err) {
|
|
277
295
|
const error = err;
|
|
278
296
|
const errResult = makeError(error instanceof PathPolicyError ? error.code : 'VALIDATE_ERROR', error.message, requestId, false);
|
|
279
|
-
log('error', '
|
|
297
|
+
log('error', 'provar_ant_validate failed', { requestId, error: error.message });
|
|
280
298
|
return { isError: true, content: [{ type: 'text', text: JSON.stringify(errResult) }] };
|
|
281
299
|
}
|
|
282
300
|
});
|
|
@@ -393,13 +411,11 @@ function escapeXmlAttr(value) {
|
|
|
393
411
|
return value
|
|
394
412
|
.replace(/&/g, '&')
|
|
395
413
|
.replace(/"/g, '"')
|
|
414
|
+
.replace(/'/g, ''')
|
|
396
415
|
.replace(/</g, '<')
|
|
397
416
|
.replace(/>/g, '>');
|
|
398
417
|
}
|
|
399
|
-
const REQUIRED_TASKDEF_CLASSNAMES = [
|
|
400
|
-
'com.provar.testrunner.ant.CompileTask',
|
|
401
|
-
'com.provar.testrunner.ant.RunnerTask',
|
|
402
|
-
];
|
|
418
|
+
const REQUIRED_TASKDEF_CLASSNAMES = ['com.provar.testrunner.ant.CompileTask', 'com.provar.testrunner.ant.RunnerTask'];
|
|
403
419
|
const VALID_BROWSERS = ['Chrome', 'Chrome_Headless', 'Firefox', 'Edge', 'Edge_Legacy', 'Safari', 'IE'];
|
|
404
420
|
const VALID_CACHE = ['Reuse', 'Refresh', 'Reload'];
|
|
405
421
|
const VALID_OUTPUT_LEVELS = ['BASIC', 'WARNING', 'DEBUG'];
|
|
@@ -456,19 +472,43 @@ function validateProjectStructure(project, defaultTarget, issues) {
|
|
|
456
472
|
}
|
|
457
473
|
function validateRtcEnumAttrs(rtc, webBrowser, issues) {
|
|
458
474
|
if (webBrowser && !VALID_BROWSERS.includes(webBrowser)) {
|
|
459
|
-
issues.push({
|
|
475
|
+
issues.push({
|
|
476
|
+
rule_id: 'ANT_030',
|
|
477
|
+
severity: 'WARNING',
|
|
478
|
+
message: `webBrowser "${webBrowser}" is not a recognised value. Expected one of: ${VALID_BROWSERS.join(', ')}.`,
|
|
479
|
+
applies_to: 'Run-Test-Case',
|
|
480
|
+
suggestion: `Use one of the supported browser values: ${VALID_BROWSERS.join(', ')}.`,
|
|
481
|
+
});
|
|
460
482
|
}
|
|
461
483
|
const metadataCache = rtc['@_salesforceMetadataCache'];
|
|
462
484
|
if (metadataCache && !VALID_CACHE.includes(metadataCache)) {
|
|
463
|
-
issues.push({
|
|
485
|
+
issues.push({
|
|
486
|
+
rule_id: 'ANT_031',
|
|
487
|
+
severity: 'WARNING',
|
|
488
|
+
message: `salesforceMetadataCache "${metadataCache}" is not a recognised value. Expected one of: ${VALID_CACHE.join(', ')}.`,
|
|
489
|
+
applies_to: 'Run-Test-Case',
|
|
490
|
+
suggestion: `Use one of: ${VALID_CACHE.join(', ')}.`,
|
|
491
|
+
});
|
|
464
492
|
}
|
|
465
493
|
const testOutputLevel = rtc['@_testOutputlevel'];
|
|
466
494
|
if (testOutputLevel && !VALID_OUTPUT_LEVELS.includes(testOutputLevel)) {
|
|
467
|
-
issues.push({
|
|
495
|
+
issues.push({
|
|
496
|
+
rule_id: 'ANT_032',
|
|
497
|
+
severity: 'WARNING',
|
|
498
|
+
message: `testOutputlevel "${testOutputLevel}" is not a recognised value. Expected one of: ${VALID_OUTPUT_LEVELS.join(', ')}.`,
|
|
499
|
+
applies_to: 'Run-Test-Case',
|
|
500
|
+
suggestion: `Use one of: ${VALID_OUTPUT_LEVELS.join(', ')}.`,
|
|
501
|
+
});
|
|
468
502
|
}
|
|
469
503
|
const disposition = rtc['@_resultsPathDisposition'];
|
|
470
504
|
if (disposition && !VALID_DISPOSITIONS.includes(disposition)) {
|
|
471
|
-
issues.push({
|
|
505
|
+
issues.push({
|
|
506
|
+
rule_id: 'ANT_033',
|
|
507
|
+
severity: 'WARNING',
|
|
508
|
+
message: `resultsPathDisposition "${disposition}" is not a recognised value. Expected one of: ${VALID_DISPOSITIONS.join(', ')}.`,
|
|
509
|
+
applies_to: 'Run-Test-Case',
|
|
510
|
+
suggestion: `Use one of: ${VALID_DISPOSITIONS.join(', ')}.`,
|
|
511
|
+
});
|
|
472
512
|
}
|
|
473
513
|
}
|
|
474
514
|
function validateRunTestCase(rtc, issues) {
|
|
@@ -478,22 +518,52 @@ function validateRunTestCase(rtc, issues) {
|
|
|
478
518
|
const webBrowser = rtc['@_webBrowser'] ?? null;
|
|
479
519
|
const testEnvironment = rtc['@_testEnvironment'] ?? null;
|
|
480
520
|
if (!provarHome) {
|
|
481
|
-
issues.push({
|
|
521
|
+
issues.push({
|
|
522
|
+
rule_id: 'ANT_021',
|
|
523
|
+
severity: 'ERROR',
|
|
524
|
+
message: '<Run-Test-Case> missing required "provarHome" attribute.',
|
|
525
|
+
applies_to: 'Run-Test-Case',
|
|
526
|
+
suggestion: 'Add provarHome="${provar.home}" to <Run-Test-Case>.',
|
|
527
|
+
});
|
|
482
528
|
}
|
|
483
529
|
if (!projectPath) {
|
|
484
|
-
issues.push({
|
|
530
|
+
issues.push({
|
|
531
|
+
rule_id: 'ANT_022',
|
|
532
|
+
severity: 'ERROR',
|
|
533
|
+
message: '<Run-Test-Case> missing required "projectPath" attribute.',
|
|
534
|
+
applies_to: 'Run-Test-Case',
|
|
535
|
+
suggestion: 'Add projectPath="${testproject.home}" to <Run-Test-Case>.',
|
|
536
|
+
});
|
|
485
537
|
}
|
|
486
538
|
if (!resultsPath) {
|
|
487
|
-
issues.push({
|
|
539
|
+
issues.push({
|
|
540
|
+
rule_id: 'ANT_023',
|
|
541
|
+
severity: 'ERROR',
|
|
542
|
+
message: '<Run-Test-Case> missing required "resultsPath" attribute.',
|
|
543
|
+
applies_to: 'Run-Test-Case',
|
|
544
|
+
suggestion: 'Add resultsPath="${testproject.results}" to <Run-Test-Case>.',
|
|
545
|
+
});
|
|
488
546
|
}
|
|
489
547
|
validateRtcEnumAttrs(rtc, webBrowser, issues);
|
|
490
548
|
const filesets = rtc['fileset'] ?? [];
|
|
491
549
|
if (filesets.length === 0) {
|
|
492
|
-
issues.push({
|
|
550
|
+
issues.push({
|
|
551
|
+
rule_id: 'ANT_040',
|
|
552
|
+
severity: 'ERROR',
|
|
553
|
+
message: '<Run-Test-Case> has no <fileset> children — no tests will be selected.',
|
|
554
|
+
applies_to: 'Run-Test-Case',
|
|
555
|
+
suggestion: 'Add at least one <fileset dir="..."/> pointing to your tests or plans folder.',
|
|
556
|
+
});
|
|
493
557
|
}
|
|
494
558
|
for (const [i, fsEntry] of filesets.entries()) {
|
|
495
559
|
if (!fsEntry['@_dir']) {
|
|
496
|
-
issues.push({
|
|
560
|
+
issues.push({
|
|
561
|
+
rule_id: 'ANT_041',
|
|
562
|
+
severity: 'ERROR',
|
|
563
|
+
message: `<fileset> at index ${i} is missing required "dir" attribute.`,
|
|
564
|
+
applies_to: 'fileset',
|
|
565
|
+
suggestion: 'Add dir="..." to each <fileset> element.',
|
|
566
|
+
});
|
|
497
567
|
}
|
|
498
568
|
}
|
|
499
569
|
return { provarHome, projectPath, resultsPath, webBrowser, testEnvironment, filesetCount: filesets.length };
|
|
@@ -594,6 +664,135 @@ function finalizeAnt(issues, provarHome, projectPath, resultsPath, webBrowser, t
|
|
|
594
664
|
issues,
|
|
595
665
|
};
|
|
596
666
|
}
|
|
667
|
+
function extractFailureText(el) {
|
|
668
|
+
if (!el)
|
|
669
|
+
return undefined;
|
|
670
|
+
if (typeof el === 'string')
|
|
671
|
+
return el.trim() || undefined;
|
|
672
|
+
if (typeof el === 'object') {
|
|
673
|
+
const obj = el;
|
|
674
|
+
// Prefer CDATA body ('#text') — it has the specific error. Fall back to 'message' attribute.
|
|
675
|
+
const body = obj['#text']?.trim();
|
|
676
|
+
const msg = obj['message']?.trim();
|
|
677
|
+
if (body && msg && body !== msg)
|
|
678
|
+
return `${msg}: ${body}`;
|
|
679
|
+
return body ?? msg;
|
|
680
|
+
}
|
|
681
|
+
return undefined;
|
|
682
|
+
}
|
|
683
|
+
function extractStepsFromJUnit(parsed) {
|
|
684
|
+
const steps = [];
|
|
685
|
+
let idx = 0;
|
|
686
|
+
// Normalise to array of suites — handles both <testsuites> and bare <testsuite>
|
|
687
|
+
let suites = [];
|
|
688
|
+
if (parsed['testsuites']) {
|
|
689
|
+
const inner = parsed['testsuites']['testsuite'];
|
|
690
|
+
suites = Array.isArray(inner)
|
|
691
|
+
? inner
|
|
692
|
+
: inner
|
|
693
|
+
? [inner]
|
|
694
|
+
: [];
|
|
695
|
+
}
|
|
696
|
+
else if (parsed['testsuite']) {
|
|
697
|
+
const ts = parsed['testsuite'];
|
|
698
|
+
suites = Array.isArray(ts) ? ts : [ts];
|
|
699
|
+
}
|
|
700
|
+
for (const suite of suites) {
|
|
701
|
+
const rawTc = suite['testcase'];
|
|
702
|
+
if (!rawTc)
|
|
703
|
+
continue;
|
|
704
|
+
const testcases = Array.isArray(rawTc)
|
|
705
|
+
? rawTc
|
|
706
|
+
: [rawTc];
|
|
707
|
+
for (const tc of testcases) {
|
|
708
|
+
idx++;
|
|
709
|
+
// Provar JUnit: name = test case file name (no attribute prefix since attributeNamePrefix: '')
|
|
710
|
+
const title = tc['name'] ?? `Test ${idx}`;
|
|
711
|
+
const hasFailure = 'failure' in tc || 'error' in tc;
|
|
712
|
+
const hasSkipped = 'skipped' in tc;
|
|
713
|
+
let status = 'pass';
|
|
714
|
+
if (hasFailure)
|
|
715
|
+
status = 'fail';
|
|
716
|
+
else if (hasSkipped)
|
|
717
|
+
status = 'skip';
|
|
718
|
+
const errorMessage = extractFailureText(tc['failure'] ?? tc['error']);
|
|
719
|
+
const step = { testItemId: String(idx), title, status };
|
|
720
|
+
if (errorMessage)
|
|
721
|
+
step.errorMessage = errorMessage;
|
|
722
|
+
steps.push(step);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return steps;
|
|
726
|
+
}
|
|
727
|
+
function findXmlFiles(dir) {
|
|
728
|
+
const results = [];
|
|
729
|
+
try {
|
|
730
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
731
|
+
if (entry.isFile() && entry.name.endsWith('.xml') && !entry.name.startsWith('.')) {
|
|
732
|
+
results.push(path.join(dir, entry.name));
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
catch {
|
|
737
|
+
// ignore unreadable dirs
|
|
738
|
+
}
|
|
739
|
+
return results;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Scan a Provar results directory for JUnit XML files and return structured step results.
|
|
743
|
+
* Returns an empty steps array (+ optional warning) when no XML is found or parsing fails.
|
|
744
|
+
*/
|
|
745
|
+
export function parseJUnitResults(resultsDir) {
|
|
746
|
+
if (!fs.existsSync(resultsDir)) {
|
|
747
|
+
return { steps: [], warning: `Results directory not found: ${resultsDir}` };
|
|
748
|
+
}
|
|
749
|
+
const xmlFiles = findXmlFiles(resultsDir);
|
|
750
|
+
if (xmlFiles.length === 0) {
|
|
751
|
+
return {
|
|
752
|
+
steps: [],
|
|
753
|
+
warning: 'No JUnit XML files found in results directory — structured step output unavailable.',
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
const parser = new XMLParser({
|
|
757
|
+
ignoreAttributes: false,
|
|
758
|
+
attributeNamePrefix: '',
|
|
759
|
+
textNodeName: '#text',
|
|
760
|
+
allowBooleanAttributes: true,
|
|
761
|
+
parseAttributeValue: false,
|
|
762
|
+
isArray: (tagName) => ['testsuite', 'testcase'].includes(tagName),
|
|
763
|
+
});
|
|
764
|
+
const allSteps = [];
|
|
765
|
+
let parsedAny = false;
|
|
766
|
+
let parseFailures = 0;
|
|
767
|
+
for (const xmlFile of xmlFiles) {
|
|
768
|
+
try {
|
|
769
|
+
const content = fs.readFileSync(xmlFile, 'utf-8');
|
|
770
|
+
const parsed = parser.parse(content);
|
|
771
|
+
const steps = extractStepsFromJUnit(parsed);
|
|
772
|
+
allSteps.push(...steps);
|
|
773
|
+
parsedAny = true;
|
|
774
|
+
}
|
|
775
|
+
catch {
|
|
776
|
+
parseFailures++;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (!parsedAny) {
|
|
780
|
+
return {
|
|
781
|
+
steps: [],
|
|
782
|
+
warning: 'JUnit XML files found but could not be parsed — structured step output unavailable.',
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
if (allSteps.length === 0) {
|
|
786
|
+
return {
|
|
787
|
+
steps: [],
|
|
788
|
+
warning: 'JUnit XML found but no test steps could be extracted — files may not be standard JUnit format.',
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
const warning = parseFailures > 0
|
|
792
|
+
? `${parseFailures} JUnit XML file(s) could not be parsed — step data may be incomplete.`
|
|
793
|
+
: undefined;
|
|
794
|
+
return { steps: allSteps, warning };
|
|
795
|
+
}
|
|
597
796
|
// ── Registration ──────────────────────────────────────────────────────────────
|
|
598
797
|
export function registerAllAntTools(server, config) {
|
|
599
798
|
registerAntGenerate(server, config);
|