@postman-cse/onboarding-bootstrap 2.2.0 → 2.3.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.
package/dist/action.cjs CHANGED
@@ -298324,7 +298324,11 @@ function securityCheckFor(schemeName, scheme) {
298324
298324
  if (scheme?.type === "http") {
298325
298325
  const httpScheme = String(scheme.scheme || "").toLowerCase();
298326
298326
  if (httpScheme === "basic") return { scheme: schemeName, kind, checkable: true, prefix: "Basic " };
298327
- if (httpScheme === "bearer") return { scheme: schemeName, kind, checkable: true, prefix: "Bearer " };
298327
+ if (httpScheme === "bearer") {
298328
+ const check = { scheme: schemeName, kind, checkable: true, prefix: "Bearer " };
298329
+ if (typeof scheme.bearerFormat === "string" && scheme.bearerFormat) check.bearerFormat = scheme.bearerFormat;
298330
+ return check;
298331
+ }
298328
298332
  if (httpScheme) return { scheme: schemeName, kind, checkable: true, prefix: `${httpScheme.charAt(0).toUpperCase()}${httpScheme.slice(1)} ` };
298329
298333
  return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
298330
298334
  }
@@ -298463,6 +298467,7 @@ function collectParameterChecks(root, pathItem, operation, version2, operationId
298463
298467
  if (defaultSerialization) warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: parameter ${location2}:${name} schema on ${operationId} skipped (${packed.unsupported})`);
298464
298468
  continue;
298465
298469
  }
298470
+ validateParameterExamples(root, param, packed, `${location2}:${name} of ${operationId}`, warnings);
298466
298471
  if (location2 === "path") {
298467
298472
  const containingSegment = pathTemplate.split("/").find((segment) => segment.includes(`{${name}}`));
298468
298473
  if (containingSegment !== void 0 && containingSegment !== `{${name}}`) {
@@ -298479,12 +298484,32 @@ function collectParameterChecks(root, pathItem, operation, version2, operationId
298479
298484
  }
298480
298485
  const scalarSchema = packedScalarSchema(packed);
298481
298486
  if (scalarSchema !== void 0) {
298482
- if (!defaultSerialization) continue;
298487
+ const decodablePathStyle = location2 === "path" && (style === "label" || style === "matrix") && !explode ? style : void 0;
298488
+ if (!defaultSerialization && !decodablePathStyle) continue;
298489
+ if (!defaultSerialization) warnings.push(...noteWarnings);
298483
298490
  const check2 = { in: location2, name, required: param.required === true, schema: scalarSchema };
298491
+ if (decodablePathStyle) check2.pathStyle = decodablePathStyle;
298484
298492
  if (location2 === "query" && param.allowEmptyValue === true) check2.allowEmptyValue = true;
298485
298493
  checks.push(check2);
298486
298494
  continue;
298487
298495
  }
298496
+ if (location2 === "query" && style === "deepObject" && explode) {
298497
+ const objectSchema = asRecord5(packed.schema);
298498
+ const properties = objectSchema ? asRecord5(objectSchema.properties) : null;
298499
+ const allScalar = properties !== null && Object.keys(properties).length > 0 && Object.values(properties).every((prop) => {
298500
+ const record = asRecord5(prop);
298501
+ if (!record) return false;
298502
+ const types2 = Array.isArray(record.type) ? record.type : [record.type];
298503
+ return types2.every((entry) => typeof entry === "string" && SCALAR_SCHEMA_TYPES.has(entry));
298504
+ });
298505
+ if (allScalar) {
298506
+ warnings.push(...noteWarnings);
298507
+ const check2 = { in: "query", name, required: param.required === true, schema: packed.schema, decode: "deepObject" };
298508
+ if (param.allowEmptyValue === true) check2.allowEmptyValue = true;
298509
+ checks.push(check2);
298510
+ continue;
298511
+ }
298512
+ }
298488
298513
  if (location2 !== "query" && location2 !== "header") continue;
298489
298514
  const items = packedArrayItemsSchema(packed);
298490
298515
  if (items === void 0) continue;
@@ -298795,6 +298820,185 @@ function responseHeaders(root, version2, response, context, warnings) {
298795
298820
  }
298796
298821
  return entries;
298797
298822
  }
298823
+ var IANA_HTTP_AUTH_SCHEMES = /* @__PURE__ */ new Set([
298824
+ "basic",
298825
+ "bearer",
298826
+ "concealed",
298827
+ "digest",
298828
+ "dpop",
298829
+ "gnap",
298830
+ "hoba",
298831
+ "mutual",
298832
+ "negotiate",
298833
+ "oauth",
298834
+ "privatetoken",
298835
+ "scram-sha-1",
298836
+ "scram-sha-256",
298837
+ "vapid"
298838
+ ]);
298839
+ function httpsUrlLint(value, label, schemeName) {
298840
+ if (typeof value !== "string" || !value) return void 0;
298841
+ try {
298842
+ const parsed = new URL(value);
298843
+ if (parsed.protocol !== "https:") return `CONTRACT_SECURITY_SCHEME_URL: security scheme ${schemeName} ${label} ${value} is not an HTTPS URL`;
298844
+ } catch {
298845
+ return `CONTRACT_SECURITY_SCHEME_URL: security scheme ${schemeName} ${label} ${value} is not a valid URL`;
298846
+ }
298847
+ return void 0;
298848
+ }
298849
+ function collectSecurityStaticLints(root, operation) {
298850
+ const securitySchemes = asRecord5(asRecord5(root.components)?.securitySchemes);
298851
+ const requirements = operation.security === void 0 ? asArray3(root.security) : asArray3(operation.security);
298852
+ const warnings = /* @__PURE__ */ new Set();
298853
+ for (const requirement of requirements.map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry))) {
298854
+ for (const [schemeName, requiredScopes] of Object.entries(requirement)) {
298855
+ let scheme;
298856
+ try {
298857
+ scheme = resolveInternalRef(root, securitySchemes?.[schemeName]);
298858
+ } catch {
298859
+ scheme = null;
298860
+ }
298861
+ if (!scheme) continue;
298862
+ if (scheme.type === "http") {
298863
+ const httpScheme = String(scheme.scheme || "").toLowerCase();
298864
+ if (httpScheme && !IANA_HTTP_AUTH_SCHEMES.has(httpScheme)) {
298865
+ warnings.add(`CONTRACT_UNKNOWN_HTTP_AUTH_SCHEME: security scheme ${schemeName} uses "${httpScheme}", which is not in the IANA HTTP Authentication Scheme registry`);
298866
+ }
298867
+ }
298868
+ if (scheme.type === "apiKey" && String(scheme.in) === "query") {
298869
+ warnings.add(`CONTRACT_CREDENTIALS_IN_QUERY: security scheme ${schemeName} sends credentials in the query string, which leaks into logs and referrers`);
298870
+ }
298871
+ if (scheme.type === "openIdConnect") {
298872
+ const urlWarning = httpsUrlLint(scheme.openIdConnectUrl, "openIdConnectUrl", schemeName);
298873
+ if (urlWarning) warnings.add(urlWarning);
298874
+ else if (typeof scheme.openIdConnectUrl === "string" && !scheme.openIdConnectUrl.endsWith("/.well-known/openid-configuration")) {
298875
+ warnings.add(`CONTRACT_SECURITY_SCHEME_URL: security scheme ${schemeName} openIdConnectUrl does not end in /.well-known/openid-configuration`);
298876
+ }
298877
+ }
298878
+ if (scheme.type === "oauth2") {
298879
+ const flows = asRecord5(scheme.flows) ?? {};
298880
+ const declaredScopes = /* @__PURE__ */ new Set();
298881
+ for (const [flowName, rawFlow] of Object.entries(flows)) {
298882
+ const flow = asRecord5(rawFlow);
298883
+ if (!flow) continue;
298884
+ for (const scope of Object.keys(asRecord5(flow.scopes) ?? {})) declaredScopes.add(scope);
298885
+ const urlFields = [["refreshUrl", flow.refreshUrl]];
298886
+ if (flowName === "implicit" || flowName === "authorizationCode") urlFields.push(["authorizationUrl", flow.authorizationUrl]);
298887
+ if (flowName === "password" || flowName === "clientCredentials" || flowName === "authorizationCode") urlFields.push(["tokenUrl", flow.tokenUrl]);
298888
+ for (const [label, value] of urlFields) {
298889
+ const urlWarning = httpsUrlLint(value, `${flowName} ${label}`, schemeName);
298890
+ if (urlWarning) warnings.add(urlWarning);
298891
+ }
298892
+ }
298893
+ for (const scope of asArray3(requiredScopes).filter((entry) => typeof entry === "string")) {
298894
+ if (!declaredScopes.has(scope)) {
298895
+ warnings.add(`CONTRACT_OAUTH2_UNDECLARED_SCOPE: operation requires scope "${scope}" of ${schemeName}, which no flow of the scheme declares`);
298896
+ }
298897
+ }
298898
+ }
298899
+ }
298900
+ }
298901
+ return [...warnings];
298902
+ }
298903
+ function collectSecurityResponseLints(root, operation, responses, operationId) {
298904
+ const warnings = [];
298905
+ const requirements = operation.security === void 0 ? asArray3(root.security) : asArray3(operation.security);
298906
+ const requirementRecords = requirements.map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry));
298907
+ const secured = requirementRecords.length > 0 && requirementRecords.every((entry) => Object.keys(entry).length > 0);
298908
+ const statusKeys = new Set(Object.keys(responses));
298909
+ const hasCatchAll = statusKeys.has("default") || statusKeys.has("4XX");
298910
+ if (secured && !statusKeys.has("401") && !hasCatchAll) {
298911
+ warnings.push(`CONTRACT_SECURITY_RESPONSES_INCOMPLETE: ${operationId} requires authentication but documents no 401 (or 4XX/default) response`);
298912
+ }
298913
+ const usesScopes = requirementRecords.some((entry) => Object.values(entry).some((scopes) => Array.isArray(scopes) && scopes.length > 0));
298914
+ if (secured && usesScopes && !statusKeys.has("403") && !hasCatchAll) {
298915
+ warnings.push(`CONTRACT_SECURITY_RESPONSES_INCOMPLETE: ${operationId} requires scopes but documents no 403 (or 4XX/default) response`);
298916
+ }
298917
+ if (requirementRecords.length === 0) {
298918
+ for (const status of ["401", "403"]) {
298919
+ if (statusKeys.has(status)) warnings.push(`CONTRACT_UNSECURED_AUTH_RESPONSES: ${operationId} documents a ${status} response but declares no security requirement`);
298920
+ }
298921
+ }
298922
+ return warnings;
298923
+ }
298924
+ function collectLinkExpressions(root, response, operationId, warnings) {
298925
+ const links = asRecord5(response.links);
298926
+ if (!links) return [];
298927
+ const expressions = [];
298928
+ let unevaluated = false;
298929
+ for (const [linkName, rawLink] of Object.entries(links)) {
298930
+ let link;
298931
+ try {
298932
+ link = resolveInternalRef(root, rawLink);
298933
+ } catch {
298934
+ link = null;
298935
+ }
298936
+ if (!link) {
298937
+ unevaluated = true;
298938
+ continue;
298939
+ }
298940
+ const values = Object.values(asRecord5(link.parameters) ?? {});
298941
+ if (link.requestBody !== void 0) values.push(link.requestBody);
298942
+ if (values.length === 0) unevaluated = true;
298943
+ for (const value of values) {
298944
+ if (typeof value !== "string" || !value.startsWith("$")) continue;
298945
+ const bodyMatch = value.match(/^\$response\.body#(\/.*)$/);
298946
+ if (bodyMatch) {
298947
+ expressions.push({ link: linkName, kind: "body", pointer: bodyMatch[1] });
298948
+ continue;
298949
+ }
298950
+ const headerMatch = value.match(/^\$response\.header\.([!#$%&'*+.^_`|~0-9A-Za-z-]+)$/);
298951
+ if (headerMatch) {
298952
+ expressions.push({ link: linkName, kind: "header", header: headerMatch[1] });
298953
+ continue;
298954
+ }
298955
+ unevaluated = true;
298956
+ }
298957
+ }
298958
+ if (expressions.length === 0) {
298959
+ if (unevaluated) warnings.add(`CONTRACT_LINKS_NOT_VALIDATED: response links are not validated for ${operationId}`);
298960
+ } else if (unevaluated) {
298961
+ warnings.add(`CONTRACT_LINKS_PARTIALLY_VALIDATED: some link expressions for ${operationId} are not runtime-evaluable and are skipped`);
298962
+ }
298963
+ return expressions;
298964
+ }
298965
+ function escapeRegExpLiteral(value) {
298966
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
298967
+ }
298968
+ function serverAdvisoryPatterns(root, pathItem, operation) {
298969
+ const serverLists = [asArray3(operation.servers), asArray3(pathItem.servers), asArray3(root.servers)];
298970
+ const servers = serverLists.find((list) => list.length > 0) ?? [];
298971
+ const patterns = [];
298972
+ for (const rawServer of servers) {
298973
+ const server = asRecord5(rawServer);
298974
+ if (!server) continue;
298975
+ const url = typeof server.url === "string" ? server.url.trim() : "";
298976
+ if (!url || url === "/") continue;
298977
+ const variables = asRecord5(server.variables);
298978
+ const pattern = url.split(/(\{[^}]+\})/).map((part) => {
298979
+ const varMatch = part.match(/^\{([^}]+)\}$/);
298980
+ if (!varMatch) return escapeRegExpLiteral(part);
298981
+ const variable = asRecord5(variables?.[varMatch[1]]);
298982
+ const enumValues = asArray3(variable?.enum).filter((entry) => typeof entry === "string");
298983
+ if (enumValues.length > 0) return `(${enumValues.map(escapeRegExpLiteral).join("|")})`;
298984
+ return "[^/]*";
298985
+ }).join("");
298986
+ patterns.push(`^${pattern}`);
298987
+ }
298988
+ return patterns.length > 0 ? patterns : void 0;
298989
+ }
298990
+ function validateParameterExamples(root, param, packed, context, warnings) {
298991
+ if (packed.schema === void 0 || packed.unsupported) return;
298992
+ const candidates = exampleCandidates(root, param);
298993
+ if (candidates.length === 0) return;
298994
+ const validate3 = compileSchemaValidator(packed.schema);
298995
+ if (!validate3) return;
298996
+ for (const candidate of candidates) {
298997
+ if (!validate3(candidate.value)) {
298998
+ warnings.push(`CONTRACT_EXAMPLE_SCHEMA_MISMATCH: ${candidate.label} for parameter ${context} does not match its schema`);
298999
+ }
299000
+ }
299001
+ }
298798
299002
  function buildContractIndex(root) {
298799
299003
  if (root.swagger === "2.0") throw new Error("CONTRACT_UNSUPPORTED_OPENAPI_VERSION: Dynamic contract tests require OpenAPI 3.0 or 3.1 (found swagger 2.0)");
298800
299004
  if (!("openapi" in root)) throw new Error("CONTRACT_UNSUPPORTED_OPENAPI_VERSION: Dynamic contract tests require OpenAPI 3.0 or 3.1 (missing openapi)");
@@ -298813,6 +299017,9 @@ function buildContractIndex(root) {
298813
299017
  const operation = resolveInternalRef(root, rawOperation);
298814
299018
  if (!operation) continue;
298815
299019
  if (operation.callbacks) warnings.push(`CONTRACT_CALLBACKS_NOT_VALIDATED: callbacks are not validated for ${lowerMethod.toUpperCase()} ${path10}`);
299020
+ if (operation.requestBody !== void 0 && ["get", "head", "delete"].includes(lowerMethod)) {
299021
+ warnings.push(`CONTRACT_METHOD_BODY_SEMANTICS: ${lowerMethod.toUpperCase()} ${path10} declares a request body; RFC 9110 defines no request-body semantics for ${lowerMethod.toUpperCase()}`);
299022
+ }
298816
299023
  const responses = asRecord5(operation.responses);
298817
299024
  if (!responses || Object.keys(responses).length === 0) {
298818
299025
  throw new Error(`CONTRACT_OPERATION_NO_RESPONSES: ${lowerMethod.toUpperCase()} ${path10} must define at least one response`);
@@ -298822,11 +299029,15 @@ function buildContractIndex(root) {
298822
299029
  for (const [status, rawResponse] of Object.entries(responses)) {
298823
299030
  const response = resolveInternalRef(root, rawResponse);
298824
299031
  if (!response) continue;
298825
- if (asRecord5(response.links)) {
298826
- responseWarnings.add(`CONTRACT_LINKS_NOT_VALIDATED: response links are not validated for ${lowerMethod.toUpperCase()} ${path10}`);
299032
+ if (status !== "default" && !/^[1-5]XX$/.test(status) && !/^[1-5][0-9][0-9]$/.test(status)) {
299033
+ responseWarnings.add(`CONTRACT_INVALID_STATUS_CODE: ${lowerMethod.toUpperCase()} ${path10} declares response status "${status}" outside RFC 9110's 100-599, 1XX-5XX, or default forms`);
298827
299034
  }
299035
+ const linkExpressions = collectLinkExpressions(root, response, `${lowerMethod.toUpperCase()} ${path10}`, responseWarnings);
298828
299036
  const responseContext = `${lowerMethod.toUpperCase()} ${path10} status ${status}`;
298829
299037
  const content = responseContent(root, version2, response, responseContext, responseWarnings);
299038
+ if ((status === "204" || status === "205" || status === "304") && Object.keys(content).length > 0) {
299039
+ responseWarnings.add(`CONTRACT_BODYLESS_STATUS_WITH_CONTENT: ${lowerMethod.toUpperCase()} ${path10} declares content for status ${status}, which RFC 9110 forbids on the wire`);
299040
+ }
298830
299041
  for (const [contentType2, media] of Object.entries(content)) {
298831
299042
  const base = contentType2.toLowerCase().split(";")[0]?.trim() ?? "";
298832
299043
  const schemaType = asRecord5(media.schema)?.type;
@@ -298838,7 +299049,8 @@ function buildContractIndex(root) {
298838
299049
  contractResponses[normalizeResponseKey(status)] = {
298839
299050
  content,
298840
299051
  hasBody: Object.keys(content).length > 0,
298841
- headers
299052
+ headers,
299053
+ ...linkExpressions.length > 0 ? { links: linkExpressions } : {}
298842
299054
  };
298843
299055
  }
298844
299056
  const candidates = [...new Set([
@@ -298851,11 +299063,13 @@ function buildContractIndex(root) {
298851
299063
  opWarnings.push(...collectSecuritySchemeWarnings(root, operation));
298852
299064
  const parameterChecks = collectParameterChecks(root, pathItem, operation, version2, operationId, path10, opWarnings);
298853
299065
  const checkedKeys = new Set((parameterChecks ?? []).map((check) => `${check.in}:${check.name.toLowerCase()}`));
298854
- const decodedKeys = new Set((parameterChecks ?? []).filter((check) => check.decode).map((check) => `${check.in}:${check.name.toLowerCase()}`));
299066
+ const decodedKeys = new Set((parameterChecks ?? []).filter((check) => check.decode || check.pathStyle).map((check) => `${check.in}:${check.name.toLowerCase()}`));
298855
299067
  opWarnings.push(...collectSerializationWarnings(root, pathItem, operation, decodedKeys));
298856
299068
  if (operation.deprecated === true) {
298857
299069
  opWarnings.push(`CONTRACT_OPERATION_DEPRECATED: ${lowerMethod.toUpperCase()} ${path10} is marked deprecated in the OpenAPI document`);
298858
299070
  }
299071
+ opWarnings.push(...collectSecurityStaticLints(root, operation));
299072
+ opWarnings.push(...collectSecurityResponseLints(root, operation, responses, operationId));
298859
299073
  const requiredParameters = collectParameters(root, pathItem, operation);
298860
299074
  for (const parameter of requiredParameters.filter((entry) => entry.securityDerived)) {
298861
299075
  opWarnings.push(`CONTRACT_SECURITY_NOT_VALIDATED: security parameter ${parameter.in}:${parameter.name} is not statically required in generated requests`);
@@ -298885,6 +299099,8 @@ function buildContractIndex(root) {
298885
299099
  requestBody: collectRequestBody(root, operation, version2, operationId, opWarnings),
298886
299100
  security: collectSecurityRuntimeChecks(root, operation),
298887
299101
  pathMethods: Object.keys(pathItem).filter((key) => HTTP_METHODS.has(key)).map((key) => key.toUpperCase()),
299102
+ deprecated: operation.deprecated === true || void 0,
299103
+ servers: serverAdvisoryPatterns(root, pathItem, operation),
298888
299104
  warnings: opWarnings
298889
299105
  });
298890
299106
  }
@@ -299061,7 +299277,7 @@ function buildValidatorAssignments(operation, warnings, skipped) {
299061
299277
  return lines;
299062
299278
  }
299063
299279
  function createContractScript(operation, warnings = []) {
299064
- const contract = { method: operation.method, path: operation.path, responses: operation.responses, security: operation.security, parameters: operation.parameterChecks, pathMethods: operation.pathMethods };
299280
+ const contract = { method: operation.method, path: operation.path, responses: operation.responses, security: operation.security, parameters: operation.parameterChecks, pathMethods: operation.pathMethods, deprecated: operation.deprecated, servers: operation.servers };
299065
299281
  const skipped = [];
299066
299282
  const validatorLines = buildValidatorAssignments(operation, warnings, skipped);
299067
299283
  return [
@@ -299180,6 +299396,13 @@ function createContractScript(operation, warnings = []) {
299180
299396
  ' if (!challenge) pm.expect.fail("RFC 9110 requires WWW-Authenticate on 401 responses");',
299181
299397
  ' var wantsBearer = (contract.security || []).some(function (alternative) { return alternative.some(function (check) { return check.prefix && check.prefix.toLowerCase().indexOf("bearer") === 0; }); });',
299182
299398
  ' if (wantsBearer && challenge && challenge.toLowerCase().indexOf("bearer") === -1) pm.expect.fail("RFC 6750 expects a Bearer challenge on 401 for bearer-secured operations; got: " + challenge);',
299399
+ ' if (challenge && /\\bbasic\\b/i.test(challenge) && !/realm\\s*=/i.test(challenge)) pm.expect.fail("RFC 7617 requires a realm parameter on Basic challenges: " + challenge);',
299400
+ ' if (challenge && /\\bdigest\\b/i.test(challenge) && (!/realm\\s*=/i.test(challenge) || !/nonce\\s*=/i.test(challenge))) pm.expect.fail("RFC 7616 requires realm and nonce on Digest challenges: " + challenge);',
299401
+ " }",
299402
+ " if (code === 401 || code === 403) {",
299403
+ ' var authChallenge = respHeader("WWW-Authenticate");',
299404
+ ' var bearerError = authChallenge && /\\bbearer\\b/i.test(authChallenge) ? authChallenge.match(/\\berror\\s*=\\s*"?([A-Za-z0-9_]+)"?/i) : null;',
299405
+ ' if (bearerError && ["invalid_request", "invalid_token", "insufficient_scope"].indexOf(bearerError[1]) === -1) pm.expect.fail("RFC 6750 Bearer error code must be invalid_request, invalid_token, or insufficient_scope; got " + bearerError[1]);',
299183
299406
  " }",
299184
299407
  " if (code === 405) {",
299185
299408
  ' var allow = respHeader("Allow");',
@@ -299188,11 +299411,6 @@ function createContractScript(operation, warnings = []) {
299188
299411
  ' (contract.pathMethods || []).forEach(function (method) { if (allowed.indexOf(method) === -1) pm.expect.fail("Allow on 405 must list every method the OpenAPI path declares (RFC 9110); missing " + method + " in: " + allow); });',
299189
299412
  " }",
299190
299413
  ' if (code === 304 && responseText().trim().length > 0) pm.expect.fail("RFC 9110 forbids content in a 304 response");',
299191
- " if (code === 206) {",
299192
- ' var range = respHeader("Content-Range");',
299193
- ' if (!range) pm.expect.fail("RFC 9110 requires Content-Range on a single-part 206 response");',
299194
- ' if (range && !/^\\S+ (\\d+-\\d+|\\*)\\/(\\d+|\\*)$/.test(range)) pm.expect.fail("Content-Range is not a valid RFC 9110 range: " + range);',
299195
- " }",
299196
299414
  ' var retryAfter = respHeader("Retry-After");',
299197
299415
  " if (retryAfter && (code === 429 || code === 503 || (code >= 300 && code < 400))) {",
299198
299416
  ' if (!/^\\d+$/.test(retryAfter.trim()) && isNaN(Date.parse(retryAfter))) pm.expect.fail("Retry-After must be delay-seconds or an HTTP-date (RFC 9110): " + retryAfter);',
@@ -299210,6 +299428,7 @@ function createContractScript(operation, warnings = []) {
299210
299428
  ' try { problem = pm.response.json(); } catch (error) { pm.expect.fail("application/problem+json body is not valid JSON (RFC 9457): " + error); }',
299211
299429
  ' if (!problem || typeof problem !== "object" || Array.isArray(problem)) pm.expect.fail("problem details must be a JSON object (RFC 9457)");',
299212
299430
  ' ["type", "title", "detail", "instance"].forEach(function (member) { if (problem[member] !== undefined && typeof problem[member] !== "string") pm.expect.fail("RFC 9457 " + member + " member must be a string; got " + typeof problem[member]); });',
299431
+ ' ["type", "instance"].forEach(function (member) { if (typeof problem[member] === "string" && /\\s/.test(problem[member].trim())) pm.expect.fail("RFC 9457 " + member + " member must be a URI-reference (RFC 3986): " + problem[member]); });',
299213
299432
  " if (problem.status !== undefined) {",
299214
299433
  ' if (typeof problem.status !== "number") pm.expect.fail("RFC 9457 status member must be a number; got " + typeof problem.status);',
299215
299434
  ' else if (problem.status !== pm.response.code) pm.expect.fail("RFC 9457 status member (" + problem.status + ") must match the HTTP status code (" + pm.response.code + ")");',
@@ -299227,6 +299446,359 @@ function createContractScript(operation, warnings = []) {
299227
299446
  " });",
299228
299447
  " }",
299229
299448
  "});",
299449
+ "var rfcAdvisories = [];",
299450
+ "function rfcAdvise(message) { if (rfcAdvisories.indexOf(message) === -1) rfcAdvisories.push(message); }",
299451
+ 'function rfcRespHeader(name) { return pm.response.headers.get(name) || ""; }',
299452
+ "function rfcHeaderAll(name) { var out = []; pm.response.headers.each(function (header) { if (header && String(header.key).toLowerCase() === String(name).toLowerCase()) out.push(String(header.value)); }); return out; }",
299453
+ "function rfcIsHttpDate(value) { return /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), [0-3][0-9] (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]{4} [0-2][0-9]:[0-5][0-9]:[0-5][0-9] GMT$/.test(String(value).trim()) && !isNaN(Date.parse(value)); }",
299454
+ "function rfcIsToken(value) { return /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(String(value)); }",
299455
+ 'function rfcIsEntityTag(value) { return /^(W\\/)?"[\\x21\\x23-\\x7e\\x80-\\xff]*"$/.test(String(value).trim()); }',
299456
+ "function rfcIsFieldContent(value) { return /^[\\t \\x21-\\x7e\\x80-\\xff]*$/.test(String(value)); }",
299457
+ 'function rfcTokenList(value) { var parts = String(value).split(","); for (var i = 0; i < parts.length; i += 1) { if (!rfcIsToken(parts[i].trim())) return false; } return true; }',
299458
+ `function rfcSplitList(value) { var out = []; var current = ""; var inQuote = false; for (var i = 0; i < value.length; i += 1) { var ch = value.charAt(i); if (ch === "\\\\" && inQuote) { current += ch + (value.charAt(i + 1) || ""); i += 1; continue; } if (ch === '"') inQuote = !inQuote; if (ch === "," && !inQuote) { out.push(current); current = ""; continue; } current += ch; } out.push(current); return out; }`,
299459
+ 'function rfcBase64Decode(value) { var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var clean = String(value).replace(/=+$/, ""); if (clean.length === 0 || /[^A-Za-z0-9+\\/]/.test(clean)) return null; var bits = 0, buffer = 0, out = ""; for (var i = 0; i < clean.length; i += 1) { buffer = (buffer << 6) | alphabet.indexOf(clean.charAt(i)); bits += 6; if (bits >= 8) { bits -= 8; out += String.fromCharCode((buffer >> bits) & 255); } } return out; }',
299460
+ "function rfcSfParse(input, kind) {",
299461
+ " var s = String(input), i = 0;",
299462
+ ' function ws() { while (i < s.length && (s.charAt(i) === " " || s.charAt(i) === "\\t")) i += 1; }',
299463
+ " function key() { if (!/[a-z*]/.test(s.charAt(i))) return null; var start = i; i += 1; while (i < s.length && /[a-z0-9_.*-]/.test(s.charAt(i))) i += 1; return s.slice(start, i); }",
299464
+ " function bareItem() {",
299465
+ " var ch = s.charAt(i);",
299466
+ ` if (ch === '"') { i += 1; while (i < s.length) { var c = s.charAt(i); if (c === "\\\\") { if (!/["\\\\]/.test(s.charAt(i + 1))) return null; i += 2; continue; } if (c === '"') { i += 1; return true; } if (c < " " || c > "~") return null; i += 1; } return null; }`,
299467
+ ' if (ch === ":") { i += 1; var start = i; while (i < s.length && s.charAt(i) !== ":") i += 1; if (s.charAt(i) !== ":") return null; var body = s.slice(start, i); i += 1; return /^[A-Za-z0-9+\\/=]*$/.test(body) ? true : null; }',
299468
+ ' if (ch === "?") { i += 1; if (s.charAt(i) !== "0" && s.charAt(i) !== "1") return null; i += 1; return true; }',
299469
+ ' if (ch === "@") { i += 1; if (s.charAt(i) === "-") i += 1; if (!/[0-9]/.test(s.charAt(i))) return null; while (i < s.length && /[0-9]/.test(s.charAt(i))) i += 1; return true; }',
299470
+ ' if (/[-0-9]/.test(ch)) { if (ch === "-") i += 1; if (!/[0-9]/.test(s.charAt(i))) return null; while (i < s.length && /[0-9]/.test(s.charAt(i))) i += 1; if (s.charAt(i) === ".") { i += 1; if (!/[0-9]/.test(s.charAt(i))) return null; while (i < s.length && /[0-9]/.test(s.charAt(i))) i += 1; } return true; }',
299471
+ " if (/[A-Za-z*]/.test(ch)) { i += 1; while (i < s.length && /[!#$%&'*+.^_`|~:\\/0-9A-Za-z-]/.test(s.charAt(i))) i += 1; return true; }",
299472
+ " return null;",
299473
+ " }",
299474
+ ' function params() { while (s.charAt(i) === ";") { i += 1; ws(); if (key() === null) return null; if (s.charAt(i) === "=") { i += 1; if (bareItem() === null) return null; } } return true; }',
299475
+ ' function item() { if (s.charAt(i) === "(") { i += 1; ws(); while (s.charAt(i) !== ")") { if (i >= s.length) return null; if (bareItem() === null || params() === null) return null; ws(); } i += 1; return params(); } if (bareItem() === null) return null; return params(); }',
299476
+ " ws();",
299477
+ ' if (kind === "item") { if (item() === null) return false; ws(); return i === s.length; }',
299478
+ " if (i === s.length) return true;",
299479
+ " while (i < s.length) {",
299480
+ ' if (kind === "dict") { if (key() === null) return false; if (s.charAt(i) === "=") { i += 1; if (item() === null) return false; } else if (params() === null) return false; }',
299481
+ " else if (item() === null) return false;",
299482
+ " ws();",
299483
+ " if (i === s.length) return true;",
299484
+ ' if (s.charAt(i) !== ",") return false;',
299485
+ " i += 1; ws();",
299486
+ " if (i === s.length) return false;",
299487
+ " }",
299488
+ " return true;",
299489
+ "}",
299490
+ "pm.test('Response header fields satisfy RFC 9110 field syntax', function () {",
299491
+ " pm.response.headers.each(function (header) {",
299492
+ " if (!header) return;",
299493
+ ' if (!rfcIsToken(String(header.key))) pm.expect.fail("Response header name is not a valid RFC 9110 token: " + header.key);',
299494
+ ' if (!rfcIsFieldContent(String(header.value))) pm.expect.fail("Response header value contains characters forbidden by RFC 9110 field-content: " + header.key);',
299495
+ " });",
299496
+ ' ["content-type", "content-length", "etag", "location", "date", "age", "expires", "last-modified", "retry-after"].forEach(function (name) {',
299497
+ " var values = rfcHeaderAll(name);",
299498
+ ' for (var i = 1; i < values.length; i += 1) { if (values[i] !== values[0]) pm.expect.fail("Singleton response header " + name + " appears " + values.length + " times with differing values (RFC 9110)"); }',
299499
+ " });",
299500
+ "});",
299501
+ "pm.test('Response header values satisfy their RFC grammars', function () {",
299502
+ ' var date = rfcRespHeader("Date");',
299503
+ ' if (date && !rfcIsHttpDate(date)) pm.expect.fail("Date must be an IMF-fixdate (RFC 9110): " + date);',
299504
+ ' if (!date) rfcAdvise("RFC 9110: origin servers SHOULD send a Date header");',
299505
+ ' var etag = rfcRespHeader("ETag");',
299506
+ ' if (etag && !rfcIsEntityTag(etag)) pm.expect.fail("ETag is not a valid entity-tag (RFC 9110): " + etag);',
299507
+ ' var lastModified = rfcRespHeader("Last-Modified");',
299508
+ " if (lastModified) {",
299509
+ ' if (!rfcIsHttpDate(lastModified)) pm.expect.fail("Last-Modified must be a valid HTTP-date (RFC 9110): " + lastModified);',
299510
+ ' else if (date && rfcIsHttpDate(date) && Date.parse(lastModified) > Date.parse(date)) pm.expect.fail("Last-Modified must not be later than Date (RFC 9110): " + lastModified + " > " + date);',
299511
+ " }",
299512
+ ' var vary = rfcRespHeader("Vary");',
299513
+ " if (vary) {",
299514
+ ' var varyMembers = vary.split(",").map(function (entry) { return entry.trim(); });',
299515
+ ' if (varyMembers.indexOf("*") !== -1 && varyMembers.length > 1) pm.expect.fail("Vary: * must not be combined with other members (RFC 9110): " + vary);',
299516
+ ' varyMembers.forEach(function (member) { if (member !== "*" && !rfcIsToken(member)) pm.expect.fail("Vary member is not a field-name token (RFC 9110): " + member); });',
299517
+ " }",
299518
+ ' var contentLocation = rfcRespHeader("Content-Location");',
299519
+ ' if (contentLocation && (/\\s/.test(contentLocation.trim()) || contentLocation.trim().length === 0)) pm.expect.fail("Content-Location must be a valid URI-reference (RFC 9110): " + contentLocation);',
299520
+ ' var acceptRanges = rfcRespHeader("Accept-Ranges");',
299521
+ ' if (acceptRanges && !rfcTokenList(acceptRanges)) pm.expect.fail("Accept-Ranges must be a list of range-unit tokens (RFC 9110): " + acceptRanges);',
299522
+ ' var contentLanguage = rfcRespHeader("Content-Language");',
299523
+ ' if (contentLanguage) contentLanguage.split(",").forEach(function (tag) { if (!/^[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})*$/.test(tag.trim())) pm.expect.fail("Content-Language carries a malformed BCP 47 language-tag (RFC 5646): " + tag.trim()); });',
299524
+ ' var allow = rfcRespHeader("Allow");',
299525
+ ' if (allow && allow.trim() && !rfcTokenList(allow)) pm.expect.fail("Allow must be a comma-separated list of method tokens (RFC 9110): " + allow);',
299526
+ ' if (allow && contract.method === "OPTIONS" && pm.response.code >= 200 && pm.response.code < 300) {',
299527
+ ' var optionsAllowed = allow.split(",").map(function (entry) { return entry.trim().toUpperCase(); });',
299528
+ ' (contract.pathMethods || []).forEach(function (method) { if (optionsAllowed.indexOf(method) === -1) pm.expect.fail("Allow on an OPTIONS response must list every method the OpenAPI path declares (RFC 9110); missing " + method + " in: " + allow); });',
299529
+ " }",
299530
+ ' var age = rfcRespHeader("Age");',
299531
+ ' if (age && !/^[0-9]+$/.test(age.trim())) pm.expect.fail("Age must be a non-negative integer of delta-seconds (RFC 9111): " + age);',
299532
+ ' var expires = rfcRespHeader("Expires");',
299533
+ ' if (expires && !rfcIsHttpDate(expires)) rfcAdvise("RFC 9111: Expires is not a valid HTTP-date and will be treated as already expired: " + expires);',
299534
+ ' if (rfcHeaderAll("warning").length > 0) rfcAdvise("RFC 9111 obsoleted the Warning header; the server still emits it");',
299535
+ ' var cacheControl = rfcRespHeader("Cache-Control");',
299536
+ " if (cacheControl) {",
299537
+ " var seenDirectives = {};",
299538
+ " rfcSplitList(cacheControl).forEach(function (entry) {",
299539
+ " var directive = entry.trim();",
299540
+ ' if (!directive) { pm.expect.fail("Cache-Control contains an empty directive (RFC 9111): " + cacheControl); return; }',
299541
+ ' var eq = directive.indexOf("=");',
299542
+ " var name = (eq === -1 ? directive : directive.slice(0, eq)).trim().toLowerCase();",
299543
+ " var argument = eq === -1 ? undefined : directive.slice(eq + 1).trim();",
299544
+ ' if (!rfcIsToken(name)) pm.expect.fail("Cache-Control directive name is not a token (RFC 9111): " + directive);',
299545
+ " seenDirectives[name] = argument === undefined ? true : argument;",
299546
+ ' if (["max-age", "s-maxage", "stale-while-revalidate", "stale-if-error"].indexOf(name) !== -1 && (argument === undefined || !/^"?[0-9]+"?$/.test(argument))) pm.expect.fail("Cache-Control " + name + " requires a delta-seconds argument (RFC 9111): " + directive);',
299547
+ " });",
299548
+ ' if (seenDirectives["no-store"] && seenDirectives["max-age"] !== undefined) pm.expect.fail("Cache-Control combines no-store with max-age; the directives contradict (RFC 9111): " + cacheControl);',
299549
+ " }",
299550
+ ' var acceptPatch = rfcRespHeader("Accept-Patch");',
299551
+ ' if (acceptPatch) acceptPatch.split(",").forEach(function (entry) { var parts = mediaParts(entry); if (!parts.type || !parts.subtype) pm.expect.fail("Accept-Patch must be a list of media types (RFC 5789): " + entry.trim()); });',
299552
+ ' var deprecation = rfcRespHeader("Deprecation");',
299553
+ ' if (deprecation && !/^@-?[0-9]+$/.test(deprecation.trim())) pm.expect.fail("Deprecation must be an RFC 9745 Date structured field (@unix-timestamp): " + deprecation);',
299554
+ ' var sunset = rfcRespHeader("Sunset");',
299555
+ " if (sunset) {",
299556
+ ' if (!rfcIsHttpDate(sunset)) pm.expect.fail("Sunset must be a valid HTTP-date (RFC 8594): " + sunset);',
299557
+ ' else if (Date.parse(sunset) < Date.now()) rfcAdvise("RFC 8594: Sunset date is already in the past: " + sunset);',
299558
+ " }",
299559
+ ' var preferenceApplied = rfcRespHeader("Preference-Applied");',
299560
+ " if (preferenceApplied) {",
299561
+ ' var requestPrefer = requestHeader("Prefer").toLowerCase();',
299562
+ " rfcSplitList(preferenceApplied).forEach(function (entry) {",
299563
+ ' var token = entry.split("=")[0].trim().toLowerCase();',
299564
+ ' if (token && requestPrefer.indexOf(token) === -1) pm.expect.fail("Preference-Applied echoes a preference the request never sent (RFC 7240): " + entry.trim());',
299565
+ " });",
299566
+ " }",
299567
+ "});",
299568
+ "pm.test('Response satisfies RFC 9110 message framing requirements', function () {",
299569
+ " var code = pm.response.code;",
299570
+ ' if ((code === 204 || code < 200) && rfcRespHeader("Content-Length")) pm.expect.fail("RFC 9110 forbids Content-Length on 1xx and 204 responses");',
299571
+ ' if ([301, 302, 303, 307, 308].indexOf(code) !== -1 && !rfcRespHeader("Location")) pm.expect.fail("RFC 9110 expects Location on a " + code + " redirect response");',
299572
+ " if (code === 416) {",
299573
+ ' var unsatisfiedRange = rfcRespHeader("Content-Range");',
299574
+ ' if (!unsatisfiedRange) pm.expect.fail("RFC 9110 requires Content-Range (unsatisfied-range form) on 416 responses");',
299575
+ ' else if (!/^\\S+ \\*\\/[0-9]+$/.test(unsatisfiedRange.trim())) pm.expect.fail("Content-Range on 416 must use the unsatisfied-range form <unit> */<complete-length> (RFC 9110): " + unsatisfiedRange);',
299576
+ " }",
299577
+ " if (code === 206) {",
299578
+ ' var contentRange = rfcRespHeader("Content-Range");',
299579
+ ' var responseMedia = mediaParts(rfcRespHeader("Content-Type"));',
299580
+ ' var isByteranges = responseMedia.type === "multipart" && responseMedia.subtype === "byteranges";',
299581
+ ' if (!contentRange && !isByteranges) pm.expect.fail("RFC 9110 requires Content-Range on a single-part 206 response (or multipart/byteranges for multi-range)");',
299582
+ ' if (contentRange && isByteranges) pm.expect.fail("RFC 9110 forbids Content-Range on a multipart/byteranges 206 response");',
299583
+ " if (contentRange) {",
299584
+ " var rangeParts = contentRange.trim().match(/^(\\S+) (?:([0-9]+)-([0-9]+)|\\*)\\/([0-9]+|\\*)$/);",
299585
+ ' if (!rangeParts) pm.expect.fail("Content-Range is not a valid RFC 9110 range: " + contentRange);',
299586
+ " else {",
299587
+ ' if (rangeParts[1] !== "bytes") rfcAdvise("RFC 9110: 206 Content-Range uses a non-bytes range unit: " + rangeParts[1]);',
299588
+ " if (rangeParts[2] !== undefined) {",
299589
+ ' if (Number(rangeParts[2]) > Number(rangeParts[3])) pm.expect.fail("Content-Range first-byte-pos must be <= last-byte-pos (RFC 9110): " + contentRange);',
299590
+ ' if (rangeParts[4] !== "*" && Number(rangeParts[3]) >= Number(rangeParts[4])) pm.expect.fail("Content-Range last-byte-pos must be < complete-length (RFC 9110): " + contentRange);',
299591
+ " }",
299592
+ " }",
299593
+ " }",
299594
+ " }",
299595
+ ' if (code === 407 && !rfcRespHeader("Proxy-Authenticate")) pm.expect.fail("RFC 9110 requires Proxy-Authenticate on 407 responses");',
299596
+ ' if (code === 415 && contract.method === "PATCH" && !rfcRespHeader("Accept-Patch")) rfcAdvise("RFC 5789: a 415 response to PATCH SHOULD carry Accept-Patch");',
299597
+ "});",
299598
+ "pm.test('Response media type is acceptable under the request Accept header', function () {",
299599
+ " if (pm.response.code < 200 || pm.response.code >= 300 || isBodyless()) return;",
299600
+ ' var accept = requestHeader("Accept");',
299601
+ ' if (!accept || accept.indexOf("{{") !== -1) return;',
299602
+ ' var actual = mediaParts(rfcRespHeader("Content-Type"));',
299603
+ " if (!actual.type) return;",
299604
+ " var acceptable = false;",
299605
+ " var jsonSoftened = false;",
299606
+ " rfcSplitList(accept).forEach(function (entry) {",
299607
+ " var range = mediaParts(entry);",
299608
+ ' var qMatch = entry.match(/;\\s*q\\s*=\\s*"?([0-9.]+)"?/i);',
299609
+ " if (qMatch && Number(qMatch[1]) <= 0) return;",
299610
+ ' if ((range.type === "*" && range.subtype === "*") || (range.type === actual.type && (range.subtype === "*" || range.subtype === actual.subtype))) acceptable = true;',
299611
+ ' if (range.type === actual.type && range.subtype === "json" && isJsonSubtype(actual.subtype)) jsonSoftened = true;',
299612
+ " });",
299613
+ ' if (!acceptable && jsonSoftened) { rfcAdvise("Content negotiation: response " + actual.raw + " is a +json type while the request only accepted application/json"); return; }',
299614
+ ' if (!acceptable) pm.expect.fail("Response Content-Type " + actual.raw + " is not acceptable under the request Accept header (RFC 9110): " + accept);',
299615
+ "});",
299616
+ "pm.test('Response body satisfies its media type RFC conventions', function () {",
299617
+ ' var contentTypeValue = rfcRespHeader("Content-Type");',
299618
+ " var media = mediaParts(contentTypeValue);",
299619
+ " var text = responseText();",
299620
+ ' if (pm.response.code === 406 && !text.trim()) rfcAdvise("RFC 9110: a 406 response SHOULD include a list of available representations");',
299621
+ " if (!text) return;",
299622
+ " if (isJsonSubtype(media.subtype)) {",
299623
+ ' if (text.charCodeAt(0) === 65279) pm.expect.fail("RFC 8259 forbids a byte order mark at the start of JSON text");',
299624
+ ' var charsetParam = contentTypeValue.match(/charset\\s*=\\s*"?([^";\\s]+)"?/i);',
299625
+ ' if (charsetParam) rfcAdvise("RFC 8259 defines no charset parameter for JSON media types; got charset=" + charsetParam[1]);',
299626
+ " }",
299627
+ ' if (media.raw === "application/x-ndjson" || media.raw === "application/jsonl" || media.raw === "application/x-jsonlines") {',
299628
+ ' text.split(/\\r?\\n/).forEach(function (line, lineNumber) { if (!line.trim()) return; try { JSON.parse(line); } catch (error) { pm.expect.fail("NDJSON line " + (lineNumber + 1) + " is not valid JSON: " + error); } });',
299629
+ " }",
299630
+ ' if (media.raw === "text/event-stream") {',
299631
+ ' text.split(/\\r?\\n/).forEach(function (line) { if (!line || line.charAt(0) === ":") return; var field = line.split(":")[0]; if (["data", "event", "id", "retry"].indexOf(field) === -1) pm.expect.fail("SSE line does not start with a known field or comment: " + line); if (field === "retry" && !/^retry:\\s*[0-9]+\\s*$/.test(line)) pm.expect.fail("SSE retry field must be an integer: " + line); });',
299632
+ " }",
299633
+ ' if (media.type === "multipart") {',
299634
+ ' var boundary = contentTypeValue.match(/;\\s*boundary=(?:"([^"]*)"|([^;]*))/i);',
299635
+ ' var boundaryValue = boundary ? (boundary[1] !== undefined ? boundary[1] : boundary[2].trim()) : "";',
299636
+ ' if (!boundaryValue) pm.expect.fail("multipart responses must carry a boundary parameter (RFC 2046): " + contentTypeValue);',
299637
+ " else {",
299638
+ ' if (boundaryValue.length > 70) pm.expect.fail("multipart boundary must be 1-70 characters (RFC 2046): " + boundaryValue);',
299639
+ ` if (!/^[0-9A-Za-z'()+_,\\-.\\/:=? ]*[0-9A-Za-z'()+_,\\-.\\/:=?]$/.test(boundaryValue)) pm.expect.fail("multipart boundary contains characters outside RFC 2046 bchars or ends with a space: " + boundaryValue);`,
299640
+ " }",
299641
+ " }",
299642
+ ' if (media.raw === "application/hal+json") {',
299643
+ " var hal; try { hal = JSON.parse(text); } catch (error) { hal = null; }",
299644
+ ' if (hal && typeof hal === "object" && !Array.isArray(hal)) {',
299645
+ " var halLinks = hal._links;",
299646
+ ' if (halLinks !== undefined && (typeof halLinks !== "object" || Array.isArray(halLinks) || halLinks === null)) pm.expect.fail("HAL _links must be an object of link relations");',
299647
+ ' if (halLinks) Object.keys(halLinks).forEach(function (rel) { var linkValue = halLinks[rel]; (Array.isArray(linkValue) ? linkValue : [linkValue]).forEach(function (linkObject) { if (!linkObject || typeof linkObject !== "object" || typeof linkObject.href !== "string") pm.expect.fail("HAL link relation " + rel + " must be a Link Object (or array of them) with a string href"); }); });',
299648
+ " var halEmbedded = hal._embedded;",
299649
+ ' if (halEmbedded !== undefined && (typeof halEmbedded !== "object" || Array.isArray(halEmbedded) || halEmbedded === null)) pm.expect.fail("HAL _embedded must be an object of resource names");',
299650
+ " }",
299651
+ " }",
299652
+ ' if (media.raw === "application/vnd.api+json") {',
299653
+ " var jsonApi; try { jsonApi = JSON.parse(text); } catch (error) { jsonApi = null; }",
299654
+ ' if (jsonApi && typeof jsonApi === "object" && !Array.isArray(jsonApi)) {',
299655
+ ' if (jsonApi.data === undefined && jsonApi.errors === undefined && jsonApi.meta === undefined) pm.expect.fail("JSON:API documents must contain at least one of data, errors, meta");',
299656
+ ' if (jsonApi.data !== undefined && jsonApi.errors !== undefined) pm.expect.fail("JSON:API forbids data and errors in the same document");',
299657
+ " }",
299658
+ " }",
299659
+ ' if (media.subtype === "problem+xml") {',
299660
+ ' if (text.indexOf("urn:ietf:rfc:7807") === -1) rfcAdvise("application/problem+xml body does not reference the urn:ietf:rfc:7807 namespace");',
299661
+ " var xmlStatus = text.match(/<status[^>]*>\\s*([0-9]+)\\s*<\\/status>/);",
299662
+ ' if (xmlStatus && Number(xmlStatus[1]) !== pm.response.code) pm.expect.fail("RFC 9457 status member (" + xmlStatus[1] + ") must match the HTTP status code (" + pm.response.code + ")");',
299663
+ " }",
299664
+ "});",
299665
+ "pm.test('Structured field response headers parse per RFC 8941', function () {",
299666
+ ' [["Cache-Status", "list"], ["Proxy-Status", "list"], ["Priority", "dict"], ["RateLimit", "dict"], ["RateLimit-Policy", "dict"], ["Signature", "dict"], ["Signature-Input", "dict"]].forEach(function (pair) {',
299667
+ ' var value = rfcHeaderAll(pair[0]).join(", ");',
299668
+ " if (!value) return;",
299669
+ ' if (!rfcSfParse(value, pair[1])) pm.expect.fail(pair[0] + " is not a valid RFC 8941 structured field (" + pair[1] + "): " + value);',
299670
+ " });",
299671
+ "});",
299672
+ "pm.test('Content-Digest and Repr-Digest match the response body (RFC 9530)', function () {",
299673
+ " var cryptoLib = null;",
299674
+ ' try { cryptoLib = require("crypto-js"); } catch (error) { cryptoLib = null; }',
299675
+ ' ["Content-Digest", "Repr-Digest"].forEach(function (name) {',
299676
+ " var value = rfcRespHeader(name);",
299677
+ " if (!value) return;",
299678
+ ' if (!rfcSfParse(value, "dict")) { pm.expect.fail(name + " is not a valid RFC 8941 dictionary (RFC 9530): " + value); return; }',
299679
+ ' if (!cryptoLib || rfcRespHeader("Content-Encoding")) return;',
299680
+ ' var media = mediaParts(rfcRespHeader("Content-Type"));',
299681
+ ' if (media.type !== "text" && !isJsonSubtype(media.subtype) && !/xml$/.test(media.subtype)) return;',
299682
+ " rfcSplitList(value).forEach(function (entry) {",
299683
+ " var match = entry.trim().match(/^(sha-256|sha-512)=:([A-Za-z0-9+\\/=]+):$/);",
299684
+ " if (!match) return;",
299685
+ ' var computed = match[1] === "sha-256" ? cryptoLib.SHA256(responseText()) : cryptoLib.SHA512(responseText());',
299686
+ " var encoded = cryptoLib.enc.Base64.stringify(computed);",
299687
+ ' if (encoded !== match[2]) pm.expect.fail(name + " " + match[1] + " does not match the response body (RFC 9530): computed " + encoded + " but header carries " + match[2]);',
299688
+ " });",
299689
+ " });",
299690
+ "});",
299691
+ "pm.test('Request credentials are well-formed per their authentication scheme RFCs', function () {",
299692
+ ' var authorization = requestHeader("Authorization");',
299693
+ ' if (authorization && authorization.indexOf("{{") === -1) {',
299694
+ " var schemeMatch = authorization.match(/^(\\S+)(?:\\s+([\\s\\S]*))?$/);",
299695
+ ' var authScheme = schemeMatch ? schemeMatch[1].toLowerCase() : "";',
299696
+ ' var authParams = schemeMatch && schemeMatch[2] !== undefined ? schemeMatch[2].trim() : "";',
299697
+ ' if (authScheme === "basic") {',
299698
+ " var decoded = rfcBase64Decode(authParams);",
299699
+ ' if (decoded === null) pm.expect.fail("Basic credentials must be base64 (RFC 7617)");',
299700
+ ' else if (decoded.indexOf(":") === -1) pm.expect.fail("Basic credentials must decode to user-id:password (RFC 7617)");',
299701
+ " }",
299702
+ ' if (authScheme === "bearer" && !/^[A-Za-z0-9\\-._~+\\/]+=*$/.test(authParams)) pm.expect.fail("Bearer token does not match the b64token grammar (RFC 6750)");',
299703
+ ' if (authScheme === "digest") {',
299704
+ ' rfcSplitList(authParams).forEach(function (entry) { var param = entry.trim(); if (param && !/^[!#$%&\'*+.^_`|~0-9A-Za-z-]+\\s*=\\s*("([^"\\\\]|\\\\.)*"|[!#$%&\'*+.^_`|~0-9A-Za-z-]+)$/.test(param)) pm.expect.fail("Digest auth-param is malformed (RFC 7616): " + param); });',
299705
+ ' var digestResponse = authParams.match(/\\bresponse\\s*=\\s*"?([^",\\s]+)"?/i);',
299706
+ ' if (digestResponse && !/^[0-9a-fA-F]+$/.test(digestResponse[1])) pm.expect.fail("Digest response parameter must be hex (RFC 7616): " + digestResponse[1]);',
299707
+ " }",
299708
+ " }",
299709
+ ' var wantsJwt = (contract.security || []).some(function (alternative) { return alternative.some(function (check) { return check.prefix === "Bearer " && String(check.bearerFormat || "").toUpperCase() === "JWT"; }); });',
299710
+ ' if (wantsJwt && authorization && authorization.indexOf("{{") === -1 && authorization.toLowerCase().indexOf("bearer ") === 0) {',
299711
+ " var jwtToken = authorization.slice(7).trim();",
299712
+ ' var jwtSegments = jwtToken.split(".");',
299713
+ ' if (jwtSegments.length !== 3) pm.expect.fail("bearerFormat JWT tokens must have three base64url segments (RFC 7519); got " + jwtSegments.length);',
299714
+ ' else if (!jwtSegments.every(function (segment) { return /^[A-Za-z0-9_-]+$/.test(segment); })) pm.expect.fail("JWT segments must be base64url (RFC 7515)");',
299715
+ " else {",
299716
+ ' var jwtDecode = function (segment) { var padded = segment.replace(/-/g, "+").replace(/_/g, "/"); while (padded.length % 4 !== 0) padded += "="; return rfcBase64Decode(padded); };',
299717
+ " var jwtHeader = null; var jwtPayload = null;",
299718
+ ' try { jwtHeader = JSON.parse(jwtDecode(jwtSegments[0])); } catch (error) { pm.expect.fail("JWT header segment does not decode to JSON (RFC 7515)"); }',
299719
+ ' try { jwtPayload = JSON.parse(jwtDecode(jwtSegments[1])); } catch (error) { pm.expect.fail("JWT payload segment does not decode to JSON (RFC 7519)"); }',
299720
+ ' if (jwtHeader && typeof jwtHeader.alg !== "string") pm.expect.fail("JWT header must carry a string alg member (RFC 7515)");',
299721
+ " if (jwtPayload) {",
299722
+ ' ["exp", "nbf", "iat"].forEach(function (claim) { if (jwtPayload[claim] !== undefined && typeof jwtPayload[claim] !== "number") pm.expect.fail("JWT " + claim + " claim must be numeric (RFC 7519)"); });',
299723
+ ' if (typeof jwtPayload.exp === "number" && jwtPayload.exp * 1000 < Date.now()) rfcAdvise("RFC 7519: the outgoing JWT exp claim is already in the past");',
299724
+ " }",
299725
+ " }",
299726
+ " }",
299727
+ ' if (hasQueryParam("access_token")) rfcAdvise("RFC 6750: bearer tokens SHOULD NOT travel in the query string");',
299728
+ " (contract.security || []).forEach(function (alternative) {",
299729
+ " alternative.forEach(function (check) {",
299730
+ " if (!check.checkable || !check.name) return;",
299731
+ ' if (check.in === "query") { if (hasQueryParam(check.name)) rfcAdvise("Security scheme " + check.scheme + " sends credentials in the query string"); return; }',
299732
+ ' if (check.in !== "header" || String(check.name).toLowerCase() === "authorization") return;',
299733
+ " var apiKeyValue = requestHeader(check.name);",
299734
+ ' if (!apiKeyValue || apiKeyValue.indexOf("{{") !== -1) return;',
299735
+ ' if (apiKeyValue !== apiKeyValue.trim()) pm.expect.fail("API key header " + check.name + " carries leading or trailing whitespace");',
299736
+ ' if (!rfcIsFieldContent(apiKeyValue)) pm.expect.fail("API key header " + check.name + " contains characters forbidden by RFC 9110 field-content");',
299737
+ " });",
299738
+ " });",
299739
+ "});",
299740
+ "pm.test('Request preconditions, preferences, and patch bodies follow their RFCs', function () {",
299741
+ ' ["If-Match", "If-None-Match"].forEach(function (name) {',
299742
+ " var value = requestHeader(name);",
299743
+ ' if (!value || value.indexOf("{{") !== -1 || value.trim() === "*") return;',
299744
+ ' rfcSplitList(value).forEach(function (entry) { if (entry.trim() && !rfcIsEntityTag(entry.trim())) pm.expect.fail(name + " must be * or a list of entity-tags (RFC 9110): " + entry.trim()); });',
299745
+ " });",
299746
+ ' var prefer = requestHeader("Prefer");',
299747
+ ' if (prefer && prefer.indexOf("{{") === -1) {',
299748
+ ' rfcSplitList(prefer).forEach(function (entry) { var token = entry.split("=")[0].split(";")[0].trim(); if (token && !rfcIsToken(token)) pm.expect.fail("Prefer preference name must be a token (RFC 7240): " + entry.trim()); });',
299749
+ " }",
299750
+ ' var requestContentType = mediaBase(requestHeader("Content-Type"));',
299751
+ " var body = pm.request.body;",
299752
+ ' var raw = body && body.mode === "raw" && typeof body.raw === "string" ? body.raw : "";',
299753
+ ' if (!raw.trim() || raw.indexOf("{{") !== -1 || /"<[^"<>]*>"/.test(raw)) return;',
299754
+ ' if (requestContentType === "application/json-patch+json") {',
299755
+ ' var patch; try { patch = JSON.parse(raw); } catch (error) { if (/<[A-Za-z][A-Za-z0-9_ -]*>/.test(raw)) return; pm.expect.fail("application/json-patch+json request body is not valid JSON (RFC 6902): " + error); return; }',
299756
+ ' if (!Array.isArray(patch)) { pm.expect.fail("A JSON Patch document must be an array of operations (RFC 6902)"); return; }',
299757
+ " patch.forEach(function (operation, operationIndex) {",
299758
+ ' if (!operation || typeof operation !== "object" || Array.isArray(operation)) { pm.expect.fail("JSON Patch operation " + operationIndex + " must be an object (RFC 6902)"); return; }',
299759
+ ' if (["add", "remove", "replace", "move", "copy", "test"].indexOf(operation.op) === -1) pm.expect.fail("JSON Patch operation " + operationIndex + " has an invalid op (RFC 6902): " + operation.op);',
299760
+ " var pointerPattern = /^(\\/([^\\/~]|~[01])*)*$/;",
299761
+ ' if (typeof operation.path !== "string" || !pointerPattern.test(operation.path)) pm.expect.fail("JSON Patch operation " + operationIndex + " path must be an RFC 6901 JSON Pointer");',
299762
+ ' if (["add", "replace", "test"].indexOf(operation.op) !== -1 && operation.value === undefined) pm.expect.fail("JSON Patch " + operation.op + " operation " + operationIndex + " requires a value member (RFC 6902)");',
299763
+ ' if (["move", "copy"].indexOf(operation.op) !== -1 && (typeof operation.from !== "string" || !pointerPattern.test(operation.from))) pm.expect.fail("JSON Patch " + operation.op + " operation " + operationIndex + " requires an RFC 6901 from pointer (RFC 6902)");',
299764
+ " });",
299765
+ " }",
299766
+ ' if (requestContentType === "application/merge-patch+json") {',
299767
+ ' try { JSON.parse(raw); } catch (error) { if (/<[A-Za-z][A-Za-z0-9_ -]*>/.test(raw)) return; pm.expect.fail("application/merge-patch+json request body must be valid JSON (RFC 7386): " + error); }',
299768
+ " }",
299769
+ "});",
299770
+ "pm.test('Deprecated operation signals deprecation in the response', function () {",
299771
+ " if (!contract.deprecated) return;",
299772
+ ' if (!rfcRespHeader("Deprecation") && !rfcRespHeader("Sunset")) rfcAdvise("RFC 9745: the OpenAPI document deprecates this operation but the response carries neither Deprecation nor Sunset");',
299773
+ "});",
299774
+ "pm.test('OpenAPI link expressions resolve against the response', function () {",
299775
+ " if (!selected || !selected.value.links || selected.value.links.length === 0) return;",
299776
+ " var linkBody = null; var linkBodyParsed = false;",
299777
+ " selected.value.links.forEach(function (expression) {",
299778
+ ' if (expression.kind === "header") {',
299779
+ ' if (!pm.response.headers.get(expression.header)) pm.expect.fail("OpenAPI link " + expression.link + " references response header " + expression.header + " which is absent");',
299780
+ " return;",
299781
+ " }",
299782
+ " if (!linkBodyParsed) { linkBodyParsed = true; try { linkBody = JSON.parse(responseText()); } catch (error) { linkBody = null; } }",
299783
+ ' if (linkBody === null) { pm.expect.fail("OpenAPI link " + expression.link + " references the response body but the body is not JSON"); return; }',
299784
+ " var target = linkBody;",
299785
+ ' var tokens = String(expression.pointer).split("/").slice(1).map(function (token) { return token.replace(/~1/g, "/").replace(/~0/g, "~"); });',
299786
+ " for (var t = 0; t < tokens.length; t += 1) {",
299787
+ ' if (target !== null && typeof target === "object") target = Array.isArray(target) ? target[Number(tokens[t])] : target[tokens[t]];',
299788
+ " else { target = undefined; break; }",
299789
+ " }",
299790
+ ' if (target === undefined) pm.expect.fail("OpenAPI link " + expression.link + " expression $response.body#" + expression.pointer + " does not resolve in the response body");',
299791
+ " });",
299792
+ "});",
299793
+ "pm.test('Request URL conforms to an OpenAPI servers entry', function () {",
299794
+ " if (!contract.servers || contract.servers.length === 0) return;",
299795
+ ' var requestUrl = "";',
299796
+ ' try { requestUrl = String(pm.request.url.toString()); } catch (ignored) { requestUrl = ""; }',
299797
+ ' if (!requestUrl || requestUrl.indexOf("{{") !== -1) return;',
299798
+ ' var pathOnly = requestUrl.replace(/^[a-z][a-z0-9+.-]*:\\/\\/[^\\/]+/i, "");',
299799
+ ' var matched = contract.servers.some(function (pattern) { try { var serverPattern = new RegExp(pattern, "i"); return serverPattern.test(requestUrl) || serverPattern.test(pathOnly); } catch (ignored) { return true; } });',
299800
+ ' if (!matched) rfcAdvise("Request URL does not match any OpenAPI servers entry: " + requestUrl);',
299801
+ "});",
299230
299802
  ...operation.security ? [
299231
299803
  "pm.test('Request carries credentials required by OpenAPI security', function () {",
299232
299804
  " function satisfied(check) {",
@@ -299266,6 +299838,12 @@ function createContractScript(operation, warnings = []) {
299266
299838
  ' if (param.allowEmptyValue && entries.length === 1 && entries[0] === "") return;',
299267
299839
  " if (entries.some(isPlaceholder)) return;",
299268
299840
  " value = entries.map(function (entry) { return coerceBySchema(decodeComponent(entry), param.items); });",
299841
+ ' } else if (param.decode === "deepObject") {',
299842
+ " var deepValue = {}; var deepFound = false; var deepPlaceholder = false;",
299843
+ ' pm.request.url.query.each(function (queryParam) { if (!queryParam || queryParam.disabled === true) return; var deepMatch = String(queryParam.key).match(/^([^\\[]+)\\[([^\\]]+)\\]$/); if (!deepMatch || deepMatch[1].toLowerCase() !== String(param.name).toLowerCase()) return; deepFound = true; var deepRaw = queryParam.value === null || queryParam.value === undefined ? "" : String(queryParam.value); if (isPlaceholder(deepRaw)) { deepPlaceholder = true; return; } deepValue[deepMatch[2]] = coerceBySchema(decodeComponent(deepRaw), (param.schema && param.schema.properties && param.schema.properties[deepMatch[2]]) || {}); });',
299844
+ ' if (!deepFound) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
299845
+ " if (deepPlaceholder) return;",
299846
+ " value = deepValue;",
299269
299847
  " } else if (param.decode) {",
299270
299848
  ' var joined = param.in === "query" ? queryValue(String(param.name).toLowerCase()) : headerValue(param.name);',
299271
299849
  ' if (joined === undefined) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
@@ -299282,6 +299860,8 @@ function createContractScript(operation, warnings = []) {
299282
299860
  ' } else if (param.in === "path") {',
299283
299861
  " value = pathParamValue(param.name);",
299284
299862
  " if (value === undefined) return;",
299863
+ ' if (param.pathStyle === "label") { if (String(value).charAt(0) !== ".") return; value = String(value).slice(1); }',
299864
+ ' if (param.pathStyle === "matrix") { var matrixPrefix = ";" + param.name + "="; if (String(value).indexOf(matrixPrefix) !== 0) return; value = String(value).slice(matrixPrefix.length); }',
299285
299865
  ' if (isPlaceholder(value) || value.charAt(0) === ":" || value.charAt(0) === "{") return;',
299286
299866
  " value = coerceBySchema(value, param.schema);",
299287
299867
  ' } else if (param.in === "cookie") {',
@@ -299333,6 +299913,9 @@ function createContractScript(operation, warnings = []) {
299333
299913
  ' if (pm.response.headers.get("Content-Encoding")) return;',
299334
299914
  " var mustBeEmpty = isBodyless() || (selected && Object.keys(selected.value.content || {}).length === 0);",
299335
299915
  ' 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);',
299916
+ "});",
299917
+ "pm.test('RFC SHOULD-level advisories are documented', function () {",
299918
+ ' pm.expect(rfcAdvisories, "SHOULD-level findings (advisory, non-failing): " + rfcAdvisories.join("; ")).to.be.an("array");',
299336
299919
  "});"
299337
299920
  ];
299338
299921
  }
@@ -299402,6 +299985,9 @@ function assertStaticRequestShape(operation, request) {
299402
299985
  }
299403
299986
  }
299404
299987
  const warnings = collectStaticBodyWarnings(operation, request, contentType2);
299988
+ if (["GET", "HEAD", "DELETE"].includes(operation.method) && hasRequestBody(request)) {
299989
+ warnings.push(`CONTRACT_METHOD_BODY_SEMANTICS: ${operation.id} sends a request body with ${operation.method}; RFC 9110 defines no request-body semantics for this method`);
299990
+ }
299405
299991
  if (operation.requestBody && !operation.requestBody.required && operation.requestBody.contentTypes.length > 0 && hasRequestBody(request) && contentType2) {
299406
299992
  const actual = contentType2.toLowerCase().split(";")[0]?.trim() ?? "";
299407
299993
  const matches = operation.requestBody.contentTypes.some((expected) => mediaTypeMatchesPattern(expected.toLowerCase(), actual));