norn-cli 1.3.17 → 1.3.19

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,211 @@
1
+ "use strict";
2
+ /**
3
+ * JUnit XML reporter for CI/CD integration
4
+ *
5
+ * Generates JUnit XML format that's compatible with:
6
+ * - Azure DevOps (ADO)
7
+ * - Jenkins
8
+ * - GitHub Actions
9
+ * - GitLab CI
10
+ * - CircleCI
11
+ * - Most CI/CD platforms
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.generateJUnitReport = generateJUnitReport;
48
+ exports.generateJUnitReportFromResponse = generateJUnitReportFromResponse;
49
+ const fs = __importStar(require("fs"));
50
+ const redaction_1 = require("../redaction");
51
+ /**
52
+ * Escape XML special characters
53
+ */
54
+ function escapeXml(text) {
55
+ return text
56
+ .replace(/&/g, '&amp;')
57
+ .replace(/</g, '&lt;')
58
+ .replace(/>/g, '&gt;')
59
+ .replace(/"/g, '&quot;')
60
+ .replace(/'/g, '&apos;');
61
+ }
62
+ /**
63
+ * Format duration as seconds with 3 decimal places
64
+ */
65
+ function formatDurationSeconds(ms) {
66
+ return (ms / 1000).toFixed(3);
67
+ }
68
+ /**
69
+ * Generate JUnit XML report from sequence results
70
+ */
71
+ function generateJUnitReport(results, options) {
72
+ const { outputPath, redaction, suiteName = 'Norn Tests' } = options;
73
+ const totalTests = results.reduce((sum, r) => sum + Math.max(1, r.assertionResults?.length || 0), 0);
74
+ const totalFailures = results.reduce((sum, r) => sum + (r.assertionResults?.filter(a => !a.passed).length || (r.success ? 0 : 1)), 0);
75
+ const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
76
+ const totalErrors = results.filter(r => r.errors.length > 0 && r.success === false).length;
77
+ let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
78
+ xml += `<testsuites name="${escapeXml(suiteName)}" tests="${totalTests}" failures="${totalFailures}" errors="${totalErrors}" time="${formatDurationSeconds(totalDuration)}">\n`;
79
+ for (const result of results) {
80
+ xml += generateTestSuite(result, redaction);
81
+ }
82
+ xml += '</testsuites>\n';
83
+ fs.writeFileSync(outputPath, xml, 'utf-8');
84
+ }
85
+ /**
86
+ * Generate a testsuite element for a sequence
87
+ */
88
+ function generateTestSuite(result, redaction) {
89
+ const assertions = result.assertionResults || [];
90
+ const testCount = Math.max(1, assertions.length);
91
+ const failures = assertions.filter(a => !a.passed).length + (result.success ? 0 : (assertions.length === 0 ? 1 : 0));
92
+ let xml = ` <testsuite name="${escapeXml(result.name)}" tests="${testCount}" failures="${failures}" errors="0" time="${formatDurationSeconds(result.duration)}">\n`;
93
+ if (assertions.length > 0) {
94
+ // Create a testcase for each assertion
95
+ for (const assertion of assertions) {
96
+ xml += generateTestCase(result.name, assertion, redaction);
97
+ }
98
+ }
99
+ else {
100
+ // No assertions - create a single testcase for the sequence
101
+ xml += generateSequenceTestCase(result, redaction);
102
+ }
103
+ // Add system-out with request/response details
104
+ xml += generateSystemOut(result, redaction);
105
+ // Add system-err with errors
106
+ if (result.errors.length > 0) {
107
+ xml += ` <system-err><![CDATA[${result.errors.map(e => (0, redaction_1.redactString)(e, redaction)).join('\n')}]]></system-err>\n`;
108
+ }
109
+ xml += ' </testsuite>\n';
110
+ return xml;
111
+ }
112
+ /**
113
+ * Generate a testcase element for an assertion
114
+ */
115
+ function generateTestCase(sequenceName, assertion, redaction) {
116
+ const testName = assertion.message || assertion.expression;
117
+ const className = sequenceName;
118
+ let xml = ` <testcase name="${escapeXml(testName)}" classname="${escapeXml(className)}">\n`;
119
+ if (!assertion.passed) {
120
+ const message = assertion.error || `Expected ${assertion.rightExpression || assertion.rightValue}, got ${JSON.stringify(assertion.leftValue)}`;
121
+ xml += ` <failure message="${escapeXml((0, redaction_1.redactString)(message, redaction))}" type="AssertionError">\n`;
122
+ xml += `<![CDATA[Expression: ${assertion.expression}\n`;
123
+ xml += `Expected: ${JSON.stringify(assertion.rightValue)}\n`;
124
+ xml += `Actual: ${JSON.stringify(assertion.leftValue)}]]>\n`;
125
+ xml += ' </failure>\n';
126
+ }
127
+ xml += ' </testcase>\n';
128
+ return xml;
129
+ }
130
+ /**
131
+ * Generate a testcase element for a sequence without assertions
132
+ */
133
+ function generateSequenceTestCase(result, redaction) {
134
+ let xml = ` <testcase name="${escapeXml(result.name)}" classname="${escapeXml(result.name)}" time="${formatDurationSeconds(result.duration)}">\n`;
135
+ if (!result.success) {
136
+ const errorMessages = result.errors.length > 0
137
+ ? result.errors.map(e => (0, redaction_1.redactString)(e, redaction)).join('; ')
138
+ : 'Sequence failed';
139
+ xml += ` <failure message="${escapeXml(errorMessages)}" type="SequenceError">\n`;
140
+ xml += `<![CDATA[${errorMessages}]]>\n`;
141
+ xml += ' </failure>\n';
142
+ }
143
+ xml += ' </testcase>\n';
144
+ return xml;
145
+ }
146
+ /**
147
+ * Generate system-out with request/response details
148
+ */
149
+ function generateSystemOut(result, redaction) {
150
+ const lines = [];
151
+ for (const step of result.steps) {
152
+ if (step.type === 'request' && step.response) {
153
+ const url = step.requestUrl ? (0, redaction_1.redactUrl)(step.requestUrl, redaction) : 'unknown';
154
+ lines.push(`[${step.requestMethod || 'REQUEST'}] ${url}`);
155
+ lines.push(` Status: ${step.response.status} ${step.response.statusText}`);
156
+ lines.push(` Duration: ${step.response.duration}ms`);
157
+ if (step.response.body) {
158
+ const bodyStr = typeof step.response.body === 'object'
159
+ ? JSON.stringify((0, redaction_1.redactBody)(step.response.body, redaction), null, 2)
160
+ : (0, redaction_1.redactString)(String(step.response.body), redaction);
161
+ // Truncate long bodies
162
+ if (bodyStr.length > 1000) {
163
+ lines.push(` Body: ${bodyStr.substring(0, 1000)}... (truncated)`);
164
+ }
165
+ else {
166
+ lines.push(` Body: ${bodyStr}`);
167
+ }
168
+ }
169
+ lines.push('');
170
+ }
171
+ else if (step.type === 'print' && step.print) {
172
+ lines.push(`[PRINT] ${step.print.title}`);
173
+ if (step.print.body) {
174
+ lines.push(` ${step.print.body}`);
175
+ }
176
+ }
177
+ }
178
+ if (lines.length === 0) {
179
+ return '';
180
+ }
181
+ return ` <system-out><![CDATA[${lines.join('\n')}]]></system-out>\n`;
182
+ }
183
+ /**
184
+ * Generate JUnit report from a single HTTP response (for non-sequence runs)
185
+ */
186
+ function generateJUnitReportFromResponse(response, testName, options) {
187
+ const { outputPath, redaction, suiteName = 'Norn Tests' } = options;
188
+ const isSuccess = response.status >= 200 && response.status < 300;
189
+ const failures = isSuccess ? 0 : 1;
190
+ let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
191
+ xml += `<testsuites name="${escapeXml(suiteName)}" tests="1" failures="${failures}" errors="0" time="${formatDurationSeconds(response.duration)}">\n`;
192
+ xml += ` <testsuite name="${escapeXml(testName)}" tests="1" failures="${failures}" errors="0" time="${formatDurationSeconds(response.duration)}">\n`;
193
+ xml += ` <testcase name="${escapeXml(testName)}" classname="${escapeXml(testName)}" time="${formatDurationSeconds(response.duration)}">\n`;
194
+ if (!isSuccess) {
195
+ xml += ` <failure message="HTTP ${response.status} ${response.statusText}" type="HttpError">\n`;
196
+ xml += `<![CDATA[Status: ${response.status} ${response.statusText}`;
197
+ if (response.body) {
198
+ const bodyStr = typeof response.body === 'object'
199
+ ? JSON.stringify((0, redaction_1.redactBody)(response.body, redaction), null, 2)
200
+ : (0, redaction_1.redactString)(String(response.body), redaction);
201
+ xml += `\nResponse: ${bodyStr}`;
202
+ }
203
+ xml += ']]>\n';
204
+ xml += ' </failure>\n';
205
+ }
206
+ xml += ' </testcase>\n';
207
+ xml += ' </testsuite>\n';
208
+ xml += '</testsuites>\n';
209
+ fs.writeFileSync(outputPath, xml, 'utf-8');
210
+ }
211
+ //# sourceMappingURL=junit.js.map