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.
- package/AGENTS.md +72 -0
- package/CHANGELOG.md +39 -1
- package/README.md +7 -3
- package/dist/cli.js +113 -54
- package/out/assertionRunner.js +537 -0
- package/out/cli/colors.js +129 -0
- package/out/cli/formatters/assertion.js +75 -0
- package/out/cli/formatters/index.js +23 -0
- package/out/cli/formatters/response.js +106 -0
- package/out/cli/formatters/summary.js +187 -0
- package/out/cli/redaction.js +237 -0
- package/out/cli/reporters/html.js +634 -0
- package/out/cli/reporters/index.js +22 -0
- package/out/cli/reporters/junit.js +211 -0
- package/out/cli.js +926 -0
- package/out/codeLensProvider.js +254 -0
- package/out/compareContentProvider.js +85 -0
- package/out/completionProvider.js +1886 -0
- package/out/contractDecorationProvider.js +243 -0
- package/out/coverageCalculator.js +756 -0
- package/out/coveragePanel.js +542 -0
- package/out/diagnosticProvider.js +980 -0
- package/out/environmentProvider.js +373 -0
- package/out/extension.js +1025 -0
- package/out/httpClient.js +269 -0
- package/out/jsonFileReader.js +320 -0
- package/out/nornapiParser.js +326 -0
- package/out/parser.js +725 -0
- package/out/responsePanel.js +4674 -0
- package/out/schemaGenerator.js +393 -0
- package/out/scriptRunner.js +419 -0
- package/out/sequenceRunner.js +3046 -0
- package/out/swaggerParser.js +339 -0
- package/out/test/extension.test.js +48 -0
- package/out/testProvider.js +658 -0
- package/out/validationCache.js +245 -0
- package/package.json +1 -1
|
@@ -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, '&')
|
|
57
|
+
.replace(/</g, '<')
|
|
58
|
+
.replace(/>/g, '>')
|
|
59
|
+
.replace(/"/g, '"')
|
|
60
|
+
.replace(/'/g, ''');
|
|
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
|