@msalaam/xray-qe-toolkit 1.4.0 → 1.5.0

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.
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Command: xqt validate [--file <path>] [--schema <schema>]
3
+ *
4
+ * Validates local project files against their JSON schemas.
5
+ * By default validates both tests.json and resources/business-rules.yaml.
6
+ *
7
+ * Schema options: tests | business-rules | all (default: all)
8
+ *
9
+ * Exit code 1 if any validation errors are found (useful in CI).
10
+ */
11
+
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ import { createRequire } from "node:module";
16
+ import logger, { setVerbose } from "../lib/logger.js";
17
+ import { loadConfig } from "../lib/config.js";
18
+
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = path.dirname(__filename);
21
+ const SCHEMA_DIR = path.join(__dirname, "..", "schema");
22
+
23
+ export default async function validate(opts = {}) {
24
+ if (opts.verbose) setVerbose(true);
25
+
26
+ logger.rocket("@msalaam/xray-qe-toolkit — Validate\n");
27
+
28
+ const cfg = loadConfig({ envPath: opts.env });
29
+
30
+ const schemaFilter = opts.schema || "all";
31
+
32
+ let totalErrors = 0;
33
+
34
+ // ── Require AJV ────────────────────────────────────────────────────────────
35
+ let Ajv;
36
+ try {
37
+ const require = createRequire(import.meta.url);
38
+ Ajv = require("ajv");
39
+ } catch {
40
+ logger.error("ajv is required for validation. Run: npm install ajv");
41
+ process.exit(1);
42
+ }
43
+
44
+ const ajv = new Ajv({ allErrors: true });
45
+
46
+ // ── 1. Validate tests.json ────────────────────────────────────────────────
47
+ if (schemaFilter === "all" || schemaFilter === "tests") {
48
+ const filePath = opts.file || cfg.testsPath;
49
+ const schemaPath = path.join(SCHEMA_DIR, "tests.schema.json");
50
+
51
+ console.log(`Validating ${filePath}...`);
52
+
53
+ if (!fs.existsSync(filePath)) {
54
+ logger.warn(` File not found: ${filePath}`);
55
+ } else if (!fs.existsSync(schemaPath)) {
56
+ logger.warn(` Schema not found: ${schemaPath}`);
57
+ } else {
58
+ const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8"));
59
+ const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
60
+ const validate = ajv.compile(schema);
61
+
62
+ if (validate(data)) {
63
+ logger.success(" tests.json: valid");
64
+ } else {
65
+ logger.error(` tests.json: ${validate.errors.length} error(s)`);
66
+ for (const err of validate.errors) {
67
+ const location = err.instancePath || "/";
68
+ console.log(` • ${location}: ${err.message}`);
69
+ if (err.params && Object.keys(err.params).length) {
70
+ console.log(` params: ${JSON.stringify(err.params)}`);
71
+ }
72
+ }
73
+ totalErrors += validate.errors.length;
74
+ }
75
+ }
76
+ }
77
+
78
+ // ── 2. Validate business-rules.yaml ───────────────────────────────────────
79
+ if (schemaFilter === "all" || schemaFilter === "business-rules") {
80
+ const yamlPath = path.join(
81
+ path.dirname(cfg.testsPath), // project root
82
+ cfg.resourcesPath || "resources",
83
+ "business-rules.yaml"
84
+ );
85
+ const schemaPath = path.join(SCHEMA_DIR, "business-rules.schema.json");
86
+
87
+ logger.blank();
88
+ console.log(`Validating ${yamlPath}...`);
89
+
90
+ if (!fs.existsSync(yamlPath)) {
91
+ logger.info(` Not found (${yamlPath}) — skipping`);
92
+ } else if (!fs.existsSync(schemaPath)) {
93
+ logger.warn(` Schema not found: ${schemaPath}`);
94
+ } else {
95
+ // Dynamically import js-yaml
96
+ let yaml;
97
+ try {
98
+ const { default: yamlModule } = await import("js-yaml");
99
+ yaml = yamlModule;
100
+ } catch {
101
+ logger.warn(" js-yaml not installed — skipping business-rules validation");
102
+ logger.info(" Run: npm install js-yaml");
103
+ }
104
+
105
+ if (yaml) {
106
+ const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8"));
107
+ const data = yaml.load(fs.readFileSync(yamlPath, "utf8"));
108
+
109
+ // Need a fresh Ajv instance — compiled schemas are keyed by $id
110
+ const ajv2 = new Ajv({ allErrors: true });
111
+ const validate = ajv2.compile(schema);
112
+
113
+ if (validate(data)) {
114
+ logger.success(" business-rules.yaml: valid");
115
+ } else {
116
+ logger.error(` business-rules.yaml: ${validate.errors.length} error(s)`);
117
+ for (const err of validate.errors) {
118
+ const location = err.instancePath || "/";
119
+ console.log(` • ${location}: ${err.message}`);
120
+ }
121
+ totalErrors += validate.errors.length;
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ // ── Summary ────────────────────────────────────────────────────────────────
128
+ logger.blank();
129
+ if (totalErrors === 0) {
130
+ logger.success("All files valid");
131
+ } else {
132
+ logger.error(`Validation failed with ${totalErrors} error(s)`);
133
+ process.exit(1);
134
+ }
135
+ logger.blank();
136
+ }
package/lib/config.js CHANGED
@@ -9,14 +9,25 @@ import { config as dotenvConfig } from "dotenv";
9
9
  import fs from "node:fs";
10
10
  import path from "node:path";
11
11
 
12
- const REQUIRED_VARS = [
13
- "XRAY_ID",
14
- "XRAY_SECRET",
15
- "JIRA_PROJECT_KEY",
16
- "JIRA_EMAIL",
17
- "JIRA_API_TOKEN",
18
- "JIRA_URL",
19
- ];
12
+ // ─── Region → endpoint mapping ─────────────────────────────────────────────────
13
+
14
+ const XRAY_REGIONS = {
15
+ us: {
16
+ graphql: "https://us.xray.cloud.getxray.app/api/v2/graphql",
17
+ rest: "https://us.xray.cloud.getxray.app/api/v2",
18
+ auth: "https://xray.cloud.getxray.app/api/v2/authenticate",
19
+ },
20
+ eu: {
21
+ graphql: "https://eu.xray.cloud.getxray.app/api/v2/graphql",
22
+ rest: "https://eu.xray.cloud.getxray.app/api/v2",
23
+ auth: "https://xray.cloud.getxray.app/api/v2/authenticate",
24
+ },
25
+ au: {
26
+ graphql: "https://au.xray.cloud.getxray.app/api/v2/graphql",
27
+ rest: "https://au.xray.cloud.getxray.app/api/v2",
28
+ auth: "https://xray.cloud.getxray.app/api/v2/authenticate",
29
+ },
30
+ };
20
31
 
21
32
  /**
22
33
  * Load configuration from .env file and optional .xrayrc JSON file.
@@ -49,18 +60,30 @@ export function loadConfig(opts = {}) {
49
60
  }
50
61
  }
51
62
 
63
+ // Resolve Xray region endpoints
64
+ const region = (process.env.XRAY_REGION || rcConfig.xrayRegion || "us").toLowerCase();
65
+ const regionEndpoints = XRAY_REGIONS[region] || XRAY_REGIONS.us;
66
+
67
+ // Valid environments list
68
+ const defaultEnvironments = ["IOP-DEV", "IOP-QA", "IOP-PROD"];
69
+
52
70
  const cfg = {
53
71
  // Xray Cloud
54
72
  xrayId: process.env.XRAY_ID || rcConfig.xrayId,
55
73
  xraySecret: process.env.XRAY_SECRET || rcConfig.xraySecret,
74
+ xrayRegion: region,
56
75
  xrayGraphqlUrl:
57
76
  process.env.XRAY_GRAPHQL_URL ||
58
77
  rcConfig.xrayGraphqlUrl ||
59
- "https://us.xray.cloud.getxray.app/api/v2/graphql",
78
+ regionEndpoints.graphql,
79
+ xrayRestUrl:
80
+ process.env.XRAY_REST_URL ||
81
+ rcConfig.xrayRestUrl ||
82
+ regionEndpoints.rest,
60
83
  xrayAuthUrl:
61
84
  process.env.XRAY_AUTH_URL ||
62
85
  rcConfig.xrayAuthUrl ||
63
- "https://xray.cloud.getxray.app/api/v2/authenticate",
86
+ regionEndpoints.auth,
64
87
 
65
88
  // JIRA
66
89
  jiraProjectKey: process.env.JIRA_PROJECT_KEY || rcConfig.jiraProjectKey,
@@ -68,12 +91,17 @@ export function loadConfig(opts = {}) {
68
91
  jiraApiToken: process.env.JIRA_API_TOKEN || rcConfig.jiraApiToken,
69
92
  jiraEmail: process.env.JIRA_EMAIL || rcConfig.jiraEmail,
70
93
 
94
+ // Test Plan & environments (per-repo config)
95
+ testPlanKey: process.env.XQT_TEST_PLAN_KEY || rcConfig.testPlanKey || null,
96
+ defaultEnvironment: process.env.XQT_DEFAULT_ENV || rcConfig.defaultEnvironment || "IOP-DEV",
97
+ environments: rcConfig.environments || defaultEnvironments,
98
+ folderRoot: rcConfig.folderRoot || null,
99
+
71
100
  // Paths (resolved relative to consuming project)
72
101
  testsPath: path.resolve(cwd, rcConfig.testsPath || "tests.json"),
73
102
  mappingPath: path.resolve(cwd, rcConfig.mappingPath || "xray-mapping.json"),
74
- collectionPath: path.resolve(cwd, rcConfig.collectionPath || "collection.postman.json"),
75
- knowledgePath: path.resolve(cwd, rcConfig.knowledgePath || "knowledge"),
76
- playwrightDir: path.resolve(cwd, rcConfig.playwrightDir || "playwright-tests"),
103
+ resourcesPath: path.resolve(cwd, rcConfig.resourcesPath || "resources"),
104
+ playwrightDir: path.resolve(cwd, rcConfig.playwrightDir || "tests"),
77
105
  };
78
106
 
79
107
  return Object.freeze(cfg);
@@ -106,3 +134,12 @@ export function validateConfig(cfg) {
106
134
  );
107
135
  }
108
136
  }
137
+
138
+ /**
139
+ * Get the list of valid test environments from config.
140
+ * @param {object} cfg
141
+ * @returns {string[]}
142
+ */
143
+ export function getEnvironments(cfg) {
144
+ return cfg.environments || ["IOP-DEV", "IOP-QA", "IOP-PROD"];
145
+ }
package/lib/index.js CHANGED
@@ -5,27 +5,66 @@
5
5
  * Consumers can also use the CLI (bin/cli.js) for interactive workflows.
6
6
  */
7
7
 
8
- export { loadConfig, validateConfig } from "./config.js";
8
+ export { loadConfig, validateConfig, getEnvironments } from "./config.js";
9
9
  export { default as logger, setVerbose } from "./logger.js";
10
10
  export {
11
+ // Auth
11
12
  authenticate,
13
+ // JIRA REST
12
14
  createIssue,
13
15
  updateIssue,
14
16
  getIssue,
17
+ // GraphQL — test queries
18
+ getTest,
19
+ getTests,
20
+ getTestPlan,
21
+ getTestPlans,
22
+ getTestExecution,
23
+ getTestRuns,
24
+ getFolder,
25
+ getProjectSettings,
26
+ // GraphQL — test mutations
27
+ setTestType,
15
28
  setTestTypeAutomated,
16
29
  addTestStep,
17
30
  removeAllTestSteps,
31
+ updateTestDefinition,
32
+ updateTestFolder,
33
+ // GraphQL — test plan mutations
34
+ createTestPlan,
35
+ addTestsToTestPlan,
36
+ removeTestsFromTestPlan,
37
+ // GraphQL — folder mutations
38
+ createFolder,
39
+ addTestsToFolder,
40
+ // GraphQL — execution mutations
41
+ addTestEnvironmentsToTestExecution,
42
+ addTestsToTestExecution,
43
+ // High-level helpers
44
+ createTestExecution,
45
+ // Linking
18
46
  linkIssues,
19
47
  linkMultiple,
48
+ // Xray REST v2 — import
20
49
  importResults,
50
+ importResultsXrayJson,
51
+ importResultsMultipart,
52
+ // Xray REST v2 — bulk test import
53
+ importTestsBulk,
54
+ checkImportJobStatus,
55
+ waitForImportJob,
56
+ // Xray REST v2 — attachments
57
+ addAttachment,
58
+ getAttachment,
59
+ // Utilities
21
60
  withRetry,
22
61
  toAdf,
23
62
  } from "./xrayClient.js";
24
63
  export {
25
64
  buildAndPush,
26
- createExecutionAndLink,
27
- linkToExistingExecution,
65
+ syncTestPlan,
66
+ syncFolders,
28
67
  loadMapping,
29
68
  saveMapping,
30
69
  } from "./testCaseBuilder.js";
31
- export { generate as generatePostmanCollection } from "./postmanGenerator.js";
70
+