@postman-cse/onboarding-bootstrap 2.9.8 → 2.9.9
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/dist/action.cjs +39 -9
- package/dist/cli.cjs +39 -9
- package/dist/index.cjs +39 -9
- package/package.json +1 -1
package/dist/action.cjs
CHANGED
|
@@ -261017,6 +261017,13 @@ function normalizeResponseKey(status) {
|
|
|
261017
261017
|
const raw = String(status);
|
|
261018
261018
|
return /^[1-5]xx$/i.test(raw) ? raw.toUpperCase() : raw;
|
|
261019
261019
|
}
|
|
261020
|
+
function responseBodyExpectation(method, status, content) {
|
|
261021
|
+
const normalizedStatus = normalizeResponseKey(status);
|
|
261022
|
+
if (method === "head" || normalizedStatus === "1XX" || /^1[0-9][0-9]$/.test(normalizedStatus) || normalizedStatus === "204" || normalizedStatus === "205" || normalizedStatus === "304") {
|
|
261023
|
+
return "forbidden";
|
|
261024
|
+
}
|
|
261025
|
+
return Object.keys(content).length > 0 ? "declared" : "unknown";
|
|
261026
|
+
}
|
|
261020
261027
|
function collectSecurityApiKeys(root, operation) {
|
|
261021
261028
|
const securitySchemes = asRecord5(asRecord5(root.components)?.securitySchemes);
|
|
261022
261029
|
const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
|
|
@@ -261447,6 +261454,13 @@ function collectRequestBody(root, operation, version, operationId, warnings) {
|
|
|
261447
261454
|
const body2 = resolveInternalRef(root, operation.requestBody);
|
|
261448
261455
|
if (!body2) return void 0;
|
|
261449
261456
|
const content = asRecord5(body2.content);
|
|
261457
|
+
for (const [contentType2, mediaObject] of Object.entries(content ?? {})) {
|
|
261458
|
+
if (asRecord5(mediaObject)?.schema === void 0) {
|
|
261459
|
+
warnings.push(
|
|
261460
|
+
`CONTRACT_REQUEST_SCHEMA_UNDOCUMENTED: request body ${contentType2} on ${operationId} declares no schema; generated request payload shape is not validated`
|
|
261461
|
+
);
|
|
261462
|
+
}
|
|
261463
|
+
}
|
|
261450
261464
|
const fieldRules = content ? requestBodyFieldRules(root, content, version, operationId, warnings) : void 0;
|
|
261451
261465
|
if (fieldRules) {
|
|
261452
261466
|
for (const [base, rule] of Object.entries(fieldRules)) {
|
|
@@ -262293,9 +262307,22 @@ function buildContractIndex(root) {
|
|
|
262293
262307
|
const linkExpressions = collectLinkExpressions(root, response, `${lowerMethod.toUpperCase()} ${path10}`, responseWarnings, version, linkTargetSchemas);
|
|
262294
262308
|
const responseContext = `${lowerMethod.toUpperCase()} ${path10} status ${status}`;
|
|
262295
262309
|
const content = responseContent(root, version, response, responseContext, responseWarnings);
|
|
262296
|
-
|
|
262310
|
+
const bodyExpectation = responseBodyExpectation(lowerMethod, status, content);
|
|
262311
|
+
if (bodyExpectation === "forbidden" && Object.keys(content).length > 0) {
|
|
262297
262312
|
responseWarnings.add(`CONTRACT_BODYLESS_STATUS_WITH_CONTENT: ${lowerMethod.toUpperCase()} ${path10} declares content for status ${status}, which RFC 9110 forbids on the wire`);
|
|
262298
262313
|
}
|
|
262314
|
+
if (bodyExpectation === "unknown") {
|
|
262315
|
+
responseWarnings.add(
|
|
262316
|
+
`CONTRACT_RESPONSE_BODY_UNDOCUMENTED: ${responseContext} declares no response content; body presence, media type, and shape are not validated`
|
|
262317
|
+
);
|
|
262318
|
+
}
|
|
262319
|
+
for (const [contentType2, mediaObject] of Object.entries(asRecord5(response.content) ?? {})) {
|
|
262320
|
+
if (asRecord5(mediaObject)?.schema === void 0) {
|
|
262321
|
+
responseWarnings.add(
|
|
262322
|
+
`CONTRACT_RESPONSE_SCHEMA_UNDOCUMENTED: response ${contentType2} on ${responseContext} declares no schema; body shape is not validated`
|
|
262323
|
+
);
|
|
262324
|
+
}
|
|
262325
|
+
}
|
|
262299
262326
|
for (const [contentType2, media] of Object.entries(content)) {
|
|
262300
262327
|
const base = contentType2.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
262301
262328
|
const schemaType = asRecord5(media.schema)?.type;
|
|
@@ -262307,7 +262334,7 @@ function buildContractIndex(root) {
|
|
|
262307
262334
|
const writeOnlyProperties = collectResponseWriteOnlyNames(root, response);
|
|
262308
262335
|
contractResponses[normalizeResponseKey(status)] = {
|
|
262309
262336
|
content,
|
|
262310
|
-
|
|
262337
|
+
bodyExpectation,
|
|
262311
262338
|
headers,
|
|
262312
262339
|
...linkExpressions.length > 0 ? { links: linkExpressions } : {},
|
|
262313
262340
|
...writeOnlyProperties.length > 0 ? { writeOnlyProperties } : {}
|
|
@@ -262583,7 +262610,12 @@ function createContractScript(operation, warnings = []) {
|
|
|
262583
262610
|
" return null;",
|
|
262584
262611
|
"}",
|
|
262585
262612
|
'function responseText() { return pm.response.text() || ""; }',
|
|
262586
|
-
'function isBodyless() { return pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
|
|
262613
|
+
'function isBodyless() { return pm.response.code < 200 || pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
|
|
262614
|
+
"function selectedBodyExpectation() {",
|
|
262615
|
+
' if (isBodyless()) return "forbidden";',
|
|
262616
|
+
' if (!selected) return "unknown";',
|
|
262617
|
+
' return selected.value.bodyExpectation || "unknown";',
|
|
262618
|
+
"}",
|
|
262587
262619
|
'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
|
|
262588
262620
|
'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
|
|
262589
262621
|
'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
|
|
@@ -262623,6 +262655,7 @@ function createContractScript(operation, warnings = []) {
|
|
|
262623
262655
|
" return matches[0];",
|
|
262624
262656
|
"}",
|
|
262625
262657
|
"var selected = selectedResponseContract();",
|
|
262658
|
+
"var bodyExpectation = selectedBodyExpectation();",
|
|
262626
262659
|
...skipped.length > 0 ? [
|
|
262627
262660
|
// A schema schemasafe could not compile is NOT silently ignored: it is
|
|
262628
262661
|
// surfaced here (and as a CONTRACT_SCHEMA_NOT_COMPILED warning at generation
|
|
@@ -262654,10 +262687,8 @@ function createContractScript(operation, warnings = []) {
|
|
|
262654
262687
|
"});",
|
|
262655
262688
|
"pm.test('Response body matches OpenAPI body contract', function () {",
|
|
262656
262689
|
" if (!selected) return;",
|
|
262657
|
-
|
|
262658
|
-
|
|
262659
|
-
' if (Object.keys(content).length === 0) { pm.expect(responseText().trim().length, "OpenAPI response defines no body but response body was not empty").to.equal(0); }',
|
|
262660
|
-
' else { pm.expect(responseText().trim().length, "OpenAPI response defines a body but response body was empty").to.be.above(0); }',
|
|
262690
|
+
' if (bodyExpectation === "forbidden") { pm.expect(responseText().length, "HTTP semantics forbid a response body for " + contract.method + " " + contract.path + " status " + pm.response.code).to.equal(0); return; }',
|
|
262691
|
+
' if (bodyExpectation === "declared") { pm.expect(responseText().length, "OpenAPI response declares content but response body was empty").to.be.above(0); }',
|
|
262661
262692
|
"});",
|
|
262662
262693
|
"pm.test('Content-Type matches OpenAPI response content', function () {",
|
|
262663
262694
|
" if (!selected || isBodyless()) return;",
|
|
@@ -263700,8 +263731,7 @@ function createContractScript(operation, warnings = []) {
|
|
|
263700
263731
|
' if (contract.method === "HEAD" || pm.response.code === 304) return;',
|
|
263701
263732
|
" var actualBytes = unescape(encodeURIComponent(responseText())).length;",
|
|
263702
263733
|
' if (Number(String(raw).trim()) !== actualBytes) pm.expect.fail("Content-Length must equal the response body byte length when Content-Encoding and Transfer-Encoding are absent (RFC 9110 8.6): " + raw + " !== " + actualBytes);',
|
|
263703
|
-
|
|
263704
|
-
' if (mustBeEmpty && Number(String(raw).trim()) !== 0) pm.expect.fail("OpenAPI defines no response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
|
|
263734
|
+
' if (bodyExpectation === "forbidden" && Number(String(raw).trim()) !== 0) pm.expect.fail("HTTP semantics forbid a carried response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
|
|
263705
263735
|
"});",
|
|
263706
263736
|
"pm.test('RFC SHOULD-level advisories are documented', function () {",
|
|
263707
263737
|
' pm.expect(rfcAdvisories, "SHOULD-level findings (advisory, non-failing): " + rfcAdvisories.join("; ")).to.be.an("array");',
|
package/dist/cli.cjs
CHANGED
|
@@ -259335,6 +259335,13 @@ function normalizeResponseKey(status) {
|
|
|
259335
259335
|
const raw = String(status);
|
|
259336
259336
|
return /^[1-5]xx$/i.test(raw) ? raw.toUpperCase() : raw;
|
|
259337
259337
|
}
|
|
259338
|
+
function responseBodyExpectation(method, status, content) {
|
|
259339
|
+
const normalizedStatus = normalizeResponseKey(status);
|
|
259340
|
+
if (method === "head" || normalizedStatus === "1XX" || /^1[0-9][0-9]$/.test(normalizedStatus) || normalizedStatus === "204" || normalizedStatus === "205" || normalizedStatus === "304") {
|
|
259341
|
+
return "forbidden";
|
|
259342
|
+
}
|
|
259343
|
+
return Object.keys(content).length > 0 ? "declared" : "unknown";
|
|
259344
|
+
}
|
|
259338
259345
|
function collectSecurityApiKeys(root, operation) {
|
|
259339
259346
|
const securitySchemes = asRecord5(asRecord5(root.components)?.securitySchemes);
|
|
259340
259347
|
const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
|
|
@@ -259765,6 +259772,13 @@ function collectRequestBody(root, operation, version, operationId, warnings) {
|
|
|
259765
259772
|
const body2 = resolveInternalRef(root, operation.requestBody);
|
|
259766
259773
|
if (!body2) return void 0;
|
|
259767
259774
|
const content = asRecord5(body2.content);
|
|
259775
|
+
for (const [contentType2, mediaObject] of Object.entries(content ?? {})) {
|
|
259776
|
+
if (asRecord5(mediaObject)?.schema === void 0) {
|
|
259777
|
+
warnings.push(
|
|
259778
|
+
`CONTRACT_REQUEST_SCHEMA_UNDOCUMENTED: request body ${contentType2} on ${operationId} declares no schema; generated request payload shape is not validated`
|
|
259779
|
+
);
|
|
259780
|
+
}
|
|
259781
|
+
}
|
|
259768
259782
|
const fieldRules = content ? requestBodyFieldRules(root, content, version, operationId, warnings) : void 0;
|
|
259769
259783
|
if (fieldRules) {
|
|
259770
259784
|
for (const [base, rule] of Object.entries(fieldRules)) {
|
|
@@ -260611,9 +260625,22 @@ function buildContractIndex(root) {
|
|
|
260611
260625
|
const linkExpressions = collectLinkExpressions(root, response, `${lowerMethod.toUpperCase()} ${path8}`, responseWarnings, version, linkTargetSchemas);
|
|
260612
260626
|
const responseContext = `${lowerMethod.toUpperCase()} ${path8} status ${status}`;
|
|
260613
260627
|
const content = responseContent(root, version, response, responseContext, responseWarnings);
|
|
260614
|
-
|
|
260628
|
+
const bodyExpectation = responseBodyExpectation(lowerMethod, status, content);
|
|
260629
|
+
if (bodyExpectation === "forbidden" && Object.keys(content).length > 0) {
|
|
260615
260630
|
responseWarnings.add(`CONTRACT_BODYLESS_STATUS_WITH_CONTENT: ${lowerMethod.toUpperCase()} ${path8} declares content for status ${status}, which RFC 9110 forbids on the wire`);
|
|
260616
260631
|
}
|
|
260632
|
+
if (bodyExpectation === "unknown") {
|
|
260633
|
+
responseWarnings.add(
|
|
260634
|
+
`CONTRACT_RESPONSE_BODY_UNDOCUMENTED: ${responseContext} declares no response content; body presence, media type, and shape are not validated`
|
|
260635
|
+
);
|
|
260636
|
+
}
|
|
260637
|
+
for (const [contentType2, mediaObject] of Object.entries(asRecord5(response.content) ?? {})) {
|
|
260638
|
+
if (asRecord5(mediaObject)?.schema === void 0) {
|
|
260639
|
+
responseWarnings.add(
|
|
260640
|
+
`CONTRACT_RESPONSE_SCHEMA_UNDOCUMENTED: response ${contentType2} on ${responseContext} declares no schema; body shape is not validated`
|
|
260641
|
+
);
|
|
260642
|
+
}
|
|
260643
|
+
}
|
|
260617
260644
|
for (const [contentType2, media] of Object.entries(content)) {
|
|
260618
260645
|
const base = contentType2.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
260619
260646
|
const schemaType = asRecord5(media.schema)?.type;
|
|
@@ -260625,7 +260652,7 @@ function buildContractIndex(root) {
|
|
|
260625
260652
|
const writeOnlyProperties = collectResponseWriteOnlyNames(root, response);
|
|
260626
260653
|
contractResponses[normalizeResponseKey(status)] = {
|
|
260627
260654
|
content,
|
|
260628
|
-
|
|
260655
|
+
bodyExpectation,
|
|
260629
260656
|
headers,
|
|
260630
260657
|
...linkExpressions.length > 0 ? { links: linkExpressions } : {},
|
|
260631
260658
|
...writeOnlyProperties.length > 0 ? { writeOnlyProperties } : {}
|
|
@@ -260901,7 +260928,12 @@ function createContractScript(operation, warnings = []) {
|
|
|
260901
260928
|
" return null;",
|
|
260902
260929
|
"}",
|
|
260903
260930
|
'function responseText() { return pm.response.text() || ""; }',
|
|
260904
|
-
'function isBodyless() { return pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
|
|
260931
|
+
'function isBodyless() { return pm.response.code < 200 || pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
|
|
260932
|
+
"function selectedBodyExpectation() {",
|
|
260933
|
+
' if (isBodyless()) return "forbidden";',
|
|
260934
|
+
' if (!selected) return "unknown";',
|
|
260935
|
+
' return selected.value.bodyExpectation || "unknown";',
|
|
260936
|
+
"}",
|
|
260905
260937
|
'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
|
|
260906
260938
|
'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
|
|
260907
260939
|
'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
|
|
@@ -260941,6 +260973,7 @@ function createContractScript(operation, warnings = []) {
|
|
|
260941
260973
|
" return matches[0];",
|
|
260942
260974
|
"}",
|
|
260943
260975
|
"var selected = selectedResponseContract();",
|
|
260976
|
+
"var bodyExpectation = selectedBodyExpectation();",
|
|
260944
260977
|
...skipped.length > 0 ? [
|
|
260945
260978
|
// A schema schemasafe could not compile is NOT silently ignored: it is
|
|
260946
260979
|
// surfaced here (and as a CONTRACT_SCHEMA_NOT_COMPILED warning at generation
|
|
@@ -260972,10 +261005,8 @@ function createContractScript(operation, warnings = []) {
|
|
|
260972
261005
|
"});",
|
|
260973
261006
|
"pm.test('Response body matches OpenAPI body contract', function () {",
|
|
260974
261007
|
" if (!selected) return;",
|
|
260975
|
-
|
|
260976
|
-
|
|
260977
|
-
' if (Object.keys(content).length === 0) { pm.expect(responseText().trim().length, "OpenAPI response defines no body but response body was not empty").to.equal(0); }',
|
|
260978
|
-
' else { pm.expect(responseText().trim().length, "OpenAPI response defines a body but response body was empty").to.be.above(0); }',
|
|
261008
|
+
' if (bodyExpectation === "forbidden") { pm.expect(responseText().length, "HTTP semantics forbid a response body for " + contract.method + " " + contract.path + " status " + pm.response.code).to.equal(0); return; }',
|
|
261009
|
+
' if (bodyExpectation === "declared") { pm.expect(responseText().length, "OpenAPI response declares content but response body was empty").to.be.above(0); }',
|
|
260979
261010
|
"});",
|
|
260980
261011
|
"pm.test('Content-Type matches OpenAPI response content', function () {",
|
|
260981
261012
|
" if (!selected || isBodyless()) return;",
|
|
@@ -262018,8 +262049,7 @@ function createContractScript(operation, warnings = []) {
|
|
|
262018
262049
|
' if (contract.method === "HEAD" || pm.response.code === 304) return;',
|
|
262019
262050
|
" var actualBytes = unescape(encodeURIComponent(responseText())).length;",
|
|
262020
262051
|
' if (Number(String(raw).trim()) !== actualBytes) pm.expect.fail("Content-Length must equal the response body byte length when Content-Encoding and Transfer-Encoding are absent (RFC 9110 8.6): " + raw + " !== " + actualBytes);',
|
|
262021
|
-
|
|
262022
|
-
' if (mustBeEmpty && Number(String(raw).trim()) !== 0) pm.expect.fail("OpenAPI defines no response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
|
|
262052
|
+
' if (bodyExpectation === "forbidden" && Number(String(raw).trim()) !== 0) pm.expect.fail("HTTP semantics forbid a carried response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
|
|
262023
262053
|
"});",
|
|
262024
262054
|
"pm.test('RFC SHOULD-level advisories are documented', function () {",
|
|
262025
262055
|
' pm.expect(rfcAdvisories, "SHOULD-level findings (advisory, non-failing): " + rfcAdvisories.join("; ")).to.be.an("array");',
|
package/dist/index.cjs
CHANGED
|
@@ -261042,6 +261042,13 @@ function normalizeResponseKey(status) {
|
|
|
261042
261042
|
const raw = String(status);
|
|
261043
261043
|
return /^[1-5]xx$/i.test(raw) ? raw.toUpperCase() : raw;
|
|
261044
261044
|
}
|
|
261045
|
+
function responseBodyExpectation(method, status, content) {
|
|
261046
|
+
const normalizedStatus = normalizeResponseKey(status);
|
|
261047
|
+
if (method === "head" || normalizedStatus === "1XX" || /^1[0-9][0-9]$/.test(normalizedStatus) || normalizedStatus === "204" || normalizedStatus === "205" || normalizedStatus === "304") {
|
|
261048
|
+
return "forbidden";
|
|
261049
|
+
}
|
|
261050
|
+
return Object.keys(content).length > 0 ? "declared" : "unknown";
|
|
261051
|
+
}
|
|
261045
261052
|
function collectSecurityApiKeys(root, operation) {
|
|
261046
261053
|
const securitySchemes = asRecord5(asRecord5(root.components)?.securitySchemes);
|
|
261047
261054
|
const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
|
|
@@ -261472,6 +261479,13 @@ function collectRequestBody(root, operation, version, operationId, warnings) {
|
|
|
261472
261479
|
const body2 = resolveInternalRef(root, operation.requestBody);
|
|
261473
261480
|
if (!body2) return void 0;
|
|
261474
261481
|
const content = asRecord5(body2.content);
|
|
261482
|
+
for (const [contentType2, mediaObject] of Object.entries(content ?? {})) {
|
|
261483
|
+
if (asRecord5(mediaObject)?.schema === void 0) {
|
|
261484
|
+
warnings.push(
|
|
261485
|
+
`CONTRACT_REQUEST_SCHEMA_UNDOCUMENTED: request body ${contentType2} on ${operationId} declares no schema; generated request payload shape is not validated`
|
|
261486
|
+
);
|
|
261487
|
+
}
|
|
261488
|
+
}
|
|
261475
261489
|
const fieldRules = content ? requestBodyFieldRules(root, content, version, operationId, warnings) : void 0;
|
|
261476
261490
|
if (fieldRules) {
|
|
261477
261491
|
for (const [base, rule] of Object.entries(fieldRules)) {
|
|
@@ -262318,9 +262332,22 @@ function buildContractIndex(root) {
|
|
|
262318
262332
|
const linkExpressions = collectLinkExpressions(root, response, `${lowerMethod.toUpperCase()} ${path10}`, responseWarnings, version, linkTargetSchemas);
|
|
262319
262333
|
const responseContext = `${lowerMethod.toUpperCase()} ${path10} status ${status}`;
|
|
262320
262334
|
const content = responseContent(root, version, response, responseContext, responseWarnings);
|
|
262321
|
-
|
|
262335
|
+
const bodyExpectation = responseBodyExpectation(lowerMethod, status, content);
|
|
262336
|
+
if (bodyExpectation === "forbidden" && Object.keys(content).length > 0) {
|
|
262322
262337
|
responseWarnings.add(`CONTRACT_BODYLESS_STATUS_WITH_CONTENT: ${lowerMethod.toUpperCase()} ${path10} declares content for status ${status}, which RFC 9110 forbids on the wire`);
|
|
262323
262338
|
}
|
|
262339
|
+
if (bodyExpectation === "unknown") {
|
|
262340
|
+
responseWarnings.add(
|
|
262341
|
+
`CONTRACT_RESPONSE_BODY_UNDOCUMENTED: ${responseContext} declares no response content; body presence, media type, and shape are not validated`
|
|
262342
|
+
);
|
|
262343
|
+
}
|
|
262344
|
+
for (const [contentType2, mediaObject] of Object.entries(asRecord5(response.content) ?? {})) {
|
|
262345
|
+
if (asRecord5(mediaObject)?.schema === void 0) {
|
|
262346
|
+
responseWarnings.add(
|
|
262347
|
+
`CONTRACT_RESPONSE_SCHEMA_UNDOCUMENTED: response ${contentType2} on ${responseContext} declares no schema; body shape is not validated`
|
|
262348
|
+
);
|
|
262349
|
+
}
|
|
262350
|
+
}
|
|
262324
262351
|
for (const [contentType2, media] of Object.entries(content)) {
|
|
262325
262352
|
const base = contentType2.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
262326
262353
|
const schemaType = asRecord5(media.schema)?.type;
|
|
@@ -262332,7 +262359,7 @@ function buildContractIndex(root) {
|
|
|
262332
262359
|
const writeOnlyProperties = collectResponseWriteOnlyNames(root, response);
|
|
262333
262360
|
contractResponses[normalizeResponseKey(status)] = {
|
|
262334
262361
|
content,
|
|
262335
|
-
|
|
262362
|
+
bodyExpectation,
|
|
262336
262363
|
headers,
|
|
262337
262364
|
...linkExpressions.length > 0 ? { links: linkExpressions } : {},
|
|
262338
262365
|
...writeOnlyProperties.length > 0 ? { writeOnlyProperties } : {}
|
|
@@ -262608,7 +262635,12 @@ function createContractScript(operation, warnings = []) {
|
|
|
262608
262635
|
" return null;",
|
|
262609
262636
|
"}",
|
|
262610
262637
|
'function responseText() { return pm.response.text() || ""; }',
|
|
262611
|
-
'function isBodyless() { return pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
|
|
262638
|
+
'function isBodyless() { return pm.response.code < 200 || pm.response.code === 204 || pm.response.code === 205 || pm.response.code === 304 || contract.method === "HEAD"; }',
|
|
262639
|
+
"function selectedBodyExpectation() {",
|
|
262640
|
+
' if (isBodyless()) return "forbidden";',
|
|
262641
|
+
' if (!selected) return "unknown";',
|
|
262642
|
+
' return selected.value.bodyExpectation || "unknown";',
|
|
262643
|
+
"}",
|
|
262612
262644
|
'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
|
|
262613
262645
|
'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
|
|
262614
262646
|
'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
|
|
@@ -262648,6 +262680,7 @@ function createContractScript(operation, warnings = []) {
|
|
|
262648
262680
|
" return matches[0];",
|
|
262649
262681
|
"}",
|
|
262650
262682
|
"var selected = selectedResponseContract();",
|
|
262683
|
+
"var bodyExpectation = selectedBodyExpectation();",
|
|
262651
262684
|
...skipped.length > 0 ? [
|
|
262652
262685
|
// A schema schemasafe could not compile is NOT silently ignored: it is
|
|
262653
262686
|
// surfaced here (and as a CONTRACT_SCHEMA_NOT_COMPILED warning at generation
|
|
@@ -262679,10 +262712,8 @@ function createContractScript(operation, warnings = []) {
|
|
|
262679
262712
|
"});",
|
|
262680
262713
|
"pm.test('Response body matches OpenAPI body contract', function () {",
|
|
262681
262714
|
" if (!selected) return;",
|
|
262682
|
-
|
|
262683
|
-
|
|
262684
|
-
' if (Object.keys(content).length === 0) { pm.expect(responseText().trim().length, "OpenAPI response defines no body but response body was not empty").to.equal(0); }',
|
|
262685
|
-
' else { pm.expect(responseText().trim().length, "OpenAPI response defines a body but response body was empty").to.be.above(0); }',
|
|
262715
|
+
' if (bodyExpectation === "forbidden") { pm.expect(responseText().length, "HTTP semantics forbid a response body for " + contract.method + " " + contract.path + " status " + pm.response.code).to.equal(0); return; }',
|
|
262716
|
+
' if (bodyExpectation === "declared") { pm.expect(responseText().length, "OpenAPI response declares content but response body was empty").to.be.above(0); }',
|
|
262686
262717
|
"});",
|
|
262687
262718
|
"pm.test('Content-Type matches OpenAPI response content', function () {",
|
|
262688
262719
|
" if (!selected || isBodyless()) return;",
|
|
@@ -263725,8 +263756,7 @@ function createContractScript(operation, warnings = []) {
|
|
|
263725
263756
|
' if (contract.method === "HEAD" || pm.response.code === 304) return;',
|
|
263726
263757
|
" var actualBytes = unescape(encodeURIComponent(responseText())).length;",
|
|
263727
263758
|
' if (Number(String(raw).trim()) !== actualBytes) pm.expect.fail("Content-Length must equal the response body byte length when Content-Encoding and Transfer-Encoding are absent (RFC 9110 8.6): " + raw + " !== " + actualBytes);',
|
|
263728
|
-
|
|
263729
|
-
' if (mustBeEmpty && Number(String(raw).trim()) !== 0) pm.expect.fail("OpenAPI defines no response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
|
|
263759
|
+
' if (bodyExpectation === "forbidden" && Number(String(raw).trim()) !== 0) pm.expect.fail("HTTP semantics forbid a carried response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
|
|
263730
263760
|
"});",
|
|
263731
263761
|
"pm.test('RFC SHOULD-level advisories are documented', function () {",
|
|
263732
263762
|
' pm.expect(rfcAdvisories, "SHOULD-level findings (advisory, non-failing): " + rfcAdvisories.join("; ")).to.be.an("array");',
|