@postman-cse/onboarding-bootstrap 2.4.0 → 2.6.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/README.md +1 -1
- package/dist/action.cjs +281 -5
- package/dist/cli.cjs +281 -5
- package/dist/index.cjs +281 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Provisions a [Postman workspace](https://learning.postman.com/docs/collaborating
|
|
|
6
6
|
|
|
7
7
|
Part of the [Postman API Onboarding suite](https://github.com/postman-cs/postman-api-onboarding-action).
|
|
8
8
|
|
|
9
|
-
> **Standards-grounded assertion generation.** Every collection this action generates ships with executable contract tests compiled from your spec. For OpenAPI: operation mapping, status-code, `Content-Type`, response-header, request/response body, parameter, security-credential, and `Content-Length` checks, with JSON Schema (draft-07 / 2020-12) body validation, dialect-exclusive keyword rejection, and RFC-checked formats (RFC 3339 timestamps, RFC 4122 UUIDs, RFC 3986 URIs, and more). Layered on top: RFC 9110 status-code, framing, and field-syntax requirements, RFC 9457 `application/problem+json` error bodies with RFC 8259 encoding and RFC 8288 `Link` checks, RFC 6265 `Set-Cookie`, RFC 6797 HSTS and security headers, WHATWG Fetch CORS, RFC 9651 structured fields, RFC 9209 typed `Proxy-Status`, RFC 9421 message signatures, RFC 9530 body digests, `Accept` negotiation, media-type conventions (NDJSON, multipart boundaries, HAL, JSON:API), authentication-scheme credential grammar, preconditions and RFC 6902 / RFC 7386 patch bodies, `Deprecation`/`Sunset` advisories, and OpenAPI links/servers resolution. Beyond REST: canonical proto3 JSON well-known-type validation for gRPC, version-aware SOAP 1.1/1.2 media-type and Fault-status checks, GraphQL-over-HTTP media-type/status discipline, static AsyncAPI WebSocket / Socket.IO / MQTT message validation, and static MCP server-manifest contract checks. The full test inventory and the standard behind each check: [Generated assertions](docs/generated-assertions.md) and [Multi-Protocol Contract Assertions](docs/MULTIPROTOCOL-ASSERTIONS.md).
|
|
9
|
+
> **Standards-grounded assertion generation.** Every collection this action generates ships with executable contract tests compiled from your spec. For OpenAPI: operation mapping, status-code, `Content-Type`, response-header, request/response body, parameter, security-credential, and `Content-Length` checks, with JSON Schema (draft-07 / 2020-12) body validation, dialect-exclusive keyword rejection, and RFC-checked formats (RFC 3339 timestamps, RFC 4122 UUIDs, RFC 3986 URIs, and more). Layered on top: RFC 9110 status-code, framing, and field-syntax requirements, RFC 9457 `application/problem+json` error bodies with RFC 8259 encoding and RFC 8288 `Link` checks, RFC 6265 `Set-Cookie`, RFC 6797 HSTS and security headers, WHATWG Fetch CORS, RFC 9651 structured fields, RFC 9209 typed `Proxy-Status`, RFC 9421 message signatures, RFC 9530 body digests, `Accept` negotiation, media-type conventions (NDJSON, multipart boundaries, HAL, JSON:API), authentication-scheme credential grammar, preconditions and RFC 6902 / RFC 7386 patch bodies, `Deprecation`/`Sunset` advisories, and OpenAPI links/servers resolution. Beyond REST: canonical proto3 JSON well-known-type validation for gRPC with per-streamed-message shape checks and metadata/trailer wire-conformance, version-aware SOAP 1.1/1.2 media-type and Fault-status checks, GraphQL-over-HTTP media-type/status discipline, static AsyncAPI WebSocket / Socket.IO / MQTT message validation, and static MCP server-manifest contract checks. The full test inventory and the standard behind each check: [Generated assertions](docs/generated-assertions.md) and [Multi-Protocol Contract Assertions](docs/MULTIPROTOCOL-ASSERTIONS.md).
|
|
10
10
|
|
|
11
11
|
## Which action should I use?
|
|
12
12
|
|
package/dist/action.cjs
CHANGED
|
@@ -296954,12 +296954,36 @@ function buildOperationScript(operation, index, warnings) {
|
|
|
296954
296954
|
' if (mediaType === "application/json" && gqlBody.data !== undefined && gqlBody.data !== null && pm.response.code !== 200) { pm.expect.fail("a GraphQL response carrying data over application/json must be HTTP 200; got " + pm.response.code); }',
|
|
296955
296955
|
"});"
|
|
296956
296956
|
);
|
|
296957
|
+
lines.push(
|
|
296958
|
+
`pm.test(${JSON.stringify(`[${label}] GraphQL response map follows the spec response format`)}, function () {`,
|
|
296959
|
+
' var extraKeys = Object.keys(gqlBody).filter(function (key) { return key !== "data" && key !== "errors" && key !== "extensions"; });',
|
|
296960
|
+
' if (extraKeys.length > 0) pm.expect.fail("the GraphQL response map must not contain entries other than data, errors, and extensions (GraphQL spec 7.1): " + extraKeys.join(", "));',
|
|
296961
|
+
' if (Object.prototype.hasOwnProperty.call(gqlBody, "extensions") && (typeof gqlBody.extensions !== "object" || gqlBody.extensions === null || Array.isArray(gqlBody.extensions))) pm.expect.fail("the GraphQL extensions entry must be a map (GraphQL spec 7.1)");',
|
|
296962
|
+
' if (!Object.prototype.hasOwnProperty.call(gqlBody, "data") && (!Array.isArray(gqlBody.errors) || gqlBody.errors.length === 0)) pm.expect.fail("a GraphQL request-error result (no data entry) must carry a non-empty errors list (GraphQL spec 7.1)");',
|
|
296963
|
+
"});"
|
|
296964
|
+
);
|
|
296957
296965
|
lines.push(
|
|
296958
296966
|
`pm.test(${JSON.stringify(`[${label}] GraphQL errors are well-formed and not a total failure`)}, function () {`,
|
|
296959
296967
|
" var errors = gqlBody.errors;",
|
|
296960
296968
|
" if (errors === undefined || errors === null) return;",
|
|
296961
296969
|
` pm.expect(errors, "GraphQL 'errors' must be an array when present").to.be.an("array");`,
|
|
296962
|
-
|
|
296970
|
+
" errors.forEach(function (err, errIndex) {",
|
|
296971
|
+
' pm.expect(err, "each GraphQL error must carry a message").to.be.an("object").that.has.property("message");',
|
|
296972
|
+
' if (typeof err.message !== "string") pm.expect.fail("errors[" + errIndex + "].message must be a string (GraphQL spec 7.1.2)");',
|
|
296973
|
+
" if (err.locations !== undefined && err.locations !== null) {",
|
|
296974
|
+
' if (!Array.isArray(err.locations)) { pm.expect.fail("errors[" + errIndex + "].locations must be a list (GraphQL spec 7.1.2)"); return; }',
|
|
296975
|
+
" err.locations.forEach(function (loc, locIndex) {",
|
|
296976
|
+
' var locLabel = "errors[" + errIndex + "].locations[" + locIndex + "]";',
|
|
296977
|
+
' if (!loc || typeof loc !== "object" || Array.isArray(loc)) { pm.expect.fail(locLabel + " must be a map with line and column (GraphQL spec 7.1.2)"); return; }',
|
|
296978
|
+
' if (!Number.isInteger(loc.line) || loc.line < 1 || !Number.isInteger(loc.column) || loc.column < 1) pm.expect.fail(locLabel + " line and column must be positive integers starting from 1 (GraphQL spec 7.1.2)");',
|
|
296979
|
+
" });",
|
|
296980
|
+
" }",
|
|
296981
|
+
" if (err.path !== undefined && err.path !== null) {",
|
|
296982
|
+
' if (!Array.isArray(err.path)) { pm.expect.fail("errors[" + errIndex + "].path must be a list of path segments (GraphQL spec 7.1.2)"); return; }',
|
|
296983
|
+
' err.path.forEach(function (segment, segIndex) { if (typeof segment !== "string" && !Number.isInteger(segment)) pm.expect.fail("errors[" + errIndex + "].path[" + segIndex + "] must be a string field name or an integer list index (GraphQL spec 7.1.2)"); });',
|
|
296984
|
+
" }",
|
|
296985
|
+
' if (err.extensions !== undefined && err.extensions !== null && (typeof err.extensions !== "object" || Array.isArray(err.extensions))) pm.expect.fail("errors[" + errIndex + "].extensions must be a map (GraphQL spec 7.1.2)");',
|
|
296986
|
+
" });",
|
|
296963
296987
|
" var hasData = gqlBody.data !== undefined && gqlBody.data !== null;",
|
|
296964
296988
|
' if (!hasData && errors.length > 0) { pm.expect.fail("GraphQL request returned errors and no data: " + JSON.stringify(errors)); }',
|
|
296965
296989
|
"});"
|
|
@@ -297618,7 +297642,17 @@ function createGrpcScript(spec) {
|
|
|
297618
297642
|
// reporters/cli/modules/grpc.ts:15-21.
|
|
297619
297643
|
'var GRPC_STATUS = { 0: "OK", 1: "CANCELLED", 2: "UNKNOWN", 3: "INVALID_ARGUMENT", 4: "DEADLINE_EXCEEDED", 5: "NOT_FOUND", 6: "ALREADY_EXISTS", 7: "PERMISSION_DENIED", 8: "RESOURCE_EXHAUSTED", 9: "FAILED_PRECONDITION", 10: "ABORTED", 11: "OUT_OF_RANGE", 12: "UNIMPLEMENTED", 13: "INTERNAL", 14: "UNAVAILABLE", 15: "DATA_LOSS", 16: "UNAUTHENTICATED" };',
|
|
297620
297644
|
'function grpcStatusName() { if (typeof pm.response.status === "string" && pm.response.status) return pm.response.status; return GRPC_STATUS[pm.response.code] || ("UNKNOWN(" + pm.response.code + ")"); }',
|
|
297621
|
-
|
|
297645
|
+
// Terminal message: pm.response.json() where the sandbox provides it,
|
|
297646
|
+
// falling back to the last received message in pm.response.messages (the
|
|
297647
|
+
// unified runtime's GRPCResponse exposes messages but no json()).
|
|
297648
|
+
"function grpcMessage() {",
|
|
297649
|
+
' try { if (pm.response && typeof pm.response.json === "function") { var viaJson = pm.response.json(); if (viaJson !== undefined && viaJson !== null) return viaJson; } } catch (error) { /* fall back to messages */ }',
|
|
297650
|
+
" try {",
|
|
297651
|
+
" var list = pm.response && pm.response.messages;",
|
|
297652
|
+
' if (list && typeof list.each === "function") { var last; list.each(function (member) { last = member && member.data !== undefined ? member.data : member; }); return last; }',
|
|
297653
|
+
" } catch (error) { return undefined; }",
|
|
297654
|
+
" return undefined;",
|
|
297655
|
+
"}",
|
|
297622
297656
|
"function jsonTypeOf(value) {",
|
|
297623
297657
|
' if (value === null || value === undefined) return "null";',
|
|
297624
297658
|
' if (Array.isArray(value)) return "array";',
|
|
@@ -297769,9 +297803,104 @@ function createGrpcScript(spec) {
|
|
|
297769
297803
|
' if (set.length > 1) pm.expect.fail("gRPC response at " + path + " sets multiple members of a oneof: " + set.join(", "));',
|
|
297770
297804
|
" });",
|
|
297771
297805
|
"}",
|
|
297806
|
+
// Wire-conformance helpers for response metadata/trailers ({ key, value }
|
|
297807
|
+
// PropertyLists; each/has/one guarded so the script stays inert on runtimes
|
|
297808
|
+
// or harnesses that do not surface them).
|
|
297809
|
+
'function grpcEachMeta(list, cb) { if (list && typeof list.each === "function") list.each(cb); }',
|
|
297810
|
+
'function grpcMetaOne(list, key) { try { if (list && typeof list.has === "function" && list.has(key) && typeof list.one === "function") return list.one(key); } catch (error) { /* absent */ } return null; }',
|
|
297811
|
+
"function grpcCheckMetaPairs(list, label) {",
|
|
297812
|
+
" grpcEachMeta(list, function (entry) {",
|
|
297813
|
+
" if (!entry || entry.disabled) return;",
|
|
297814
|
+
' var key = entry.key === undefined || entry.key === null ? "" : String(entry.key);',
|
|
297815
|
+
' var value = entry.value === undefined || entry.value === null ? "" : String(entry.value);',
|
|
297816
|
+
' if (!/^[0-9a-z_.-]+$/.test(key)) pm.expect.fail(label + " key " + JSON.stringify(key) + " must be a lowercase gRPC metadata key ([0-9a-z_.-])");',
|
|
297817
|
+
' if (/-bin$/.test(key)) { if (!/^[A-Za-z0-9+/=]*\\s*$/.test(value)) pm.expect.fail(label + " " + key + " must carry base64 data but was " + JSON.stringify(value)); }',
|
|
297818
|
+
' else if (!/^[\\x20-\\x7e]*$/.test(value)) pm.expect.fail(label + " " + key + " value must be printable ASCII (gRPC ASCII-Value)");',
|
|
297819
|
+
" });",
|
|
297820
|
+
"}",
|
|
297821
|
+
// grpc-status-details-bin carries a base64-encoded google.rpc.Status protobuf
|
|
297822
|
+
// (the gRPC richer-error model). The sandbox has no protobuf runtime, so the
|
|
297823
|
+
// script walks the protobuf wire format directly: base64 -> bytes -> tagged
|
|
297824
|
+
// fields (varint / 64-bit / length-delimited / 32-bit). Groups (wire types
|
|
297825
|
+
// 3/4) are proto2-deprecated and never emitted by google.rpc.Status.
|
|
297826
|
+
"function grpcB64Bytes(value) {",
|
|
297827
|
+
' var clean = String(value).replace(/\\s+/g, "").replace(/-/g, "+").replace(/_/g, "/");',
|
|
297828
|
+
" if (clean.length % 4 === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(clean)) return null;",
|
|
297829
|
+
' var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";',
|
|
297830
|
+
' var unpadded = clean.replace(/=+$/, ""); var bytes = []; var buffer = 0; var bits = 0;',
|
|
297831
|
+
" for (var i = 0; i < unpadded.length; i++) {",
|
|
297832
|
+
" var idx = alphabet.indexOf(unpadded.charAt(i));",
|
|
297833
|
+
" if (idx === -1) return null;",
|
|
297834
|
+
" buffer = (buffer << 6) | idx; bits += 6;",
|
|
297835
|
+
" if (bits >= 8) { bits -= 8; bytes.push((buffer >> bits) & 0xff); }",
|
|
297836
|
+
" }",
|
|
297837
|
+
" return bytes;",
|
|
297838
|
+
"}",
|
|
297839
|
+
"function grpcPbVarint(bytes, pos) {",
|
|
297840
|
+
" var value = 0; var shift = 0;",
|
|
297841
|
+
" while (pos < bytes.length) {",
|
|
297842
|
+
" var b = bytes[pos]; pos += 1;",
|
|
297843
|
+
" value += (b & 0x7f) * Math.pow(2, shift); shift += 7;",
|
|
297844
|
+
" if ((b & 0x80) === 0) return value > 9007199254740991 ? null : { value: value, pos: pos };",
|
|
297845
|
+
" if (shift > 63) return null;",
|
|
297846
|
+
" }",
|
|
297847
|
+
" return null;",
|
|
297848
|
+
"}",
|
|
297849
|
+
"function grpcPbFields(bytes) {",
|
|
297850
|
+
" var pos = 0; var fields = [];",
|
|
297851
|
+
" while (pos < bytes.length) {",
|
|
297852
|
+
" var tag = grpcPbVarint(bytes, pos); if (!tag) return null;",
|
|
297853
|
+
" pos = tag.pos;",
|
|
297854
|
+
" var fieldNumber = Math.floor(tag.value / 8); var wire = tag.value % 8;",
|
|
297855
|
+
" if (fieldNumber < 1) return null;",
|
|
297856
|
+
" if (wire === 0) { var varint = grpcPbVarint(bytes, pos); if (!varint) return null; fields.push({ field: fieldNumber, wire: 0, value: varint.value }); pos = varint.pos; }",
|
|
297857
|
+
" else if (wire === 1) { if (pos + 8 > bytes.length) return null; fields.push({ field: fieldNumber, wire: 1 }); pos += 8; }",
|
|
297858
|
+
" else if (wire === 2) { var len = grpcPbVarint(bytes, pos); if (!len) return null; pos = len.pos; if (pos + len.value > bytes.length) return null; fields.push({ field: fieldNumber, wire: 2, bytes: bytes.slice(pos, pos + len.value) }); pos += len.value; }",
|
|
297859
|
+
" else if (wire === 5) { if (pos + 4 > bytes.length) return null; fields.push({ field: fieldNumber, wire: 5 }); pos += 4; }",
|
|
297860
|
+
" else return null;",
|
|
297861
|
+
" }",
|
|
297862
|
+
" return fields;",
|
|
297863
|
+
"}",
|
|
297864
|
+
"function grpcCheckStatusDetailsBin(entry) {",
|
|
297865
|
+
' if (!entry || entry.value === undefined || entry.value === null || String(entry.value) === "") return;',
|
|
297866
|
+
" var detailBytes = grpcB64Bytes(entry.value);",
|
|
297867
|
+
' if (!detailBytes) { pm.expect.fail("grpc-status-details-bin trailer must carry base64-encoded bytes but was " + JSON.stringify(String(entry.value))); return; }',
|
|
297868
|
+
" var statusFields = grpcPbFields(detailBytes);",
|
|
297869
|
+
' if (!statusFields) { pm.expect.fail("grpc-status-details-bin does not decode as a protobuf wire message (google.rpc.Status)"); return; }',
|
|
297870
|
+
" statusFields.forEach(function (statusField) {",
|
|
297871
|
+
" if (statusField.field === 1) {",
|
|
297872
|
+
' if (statusField.wire !== 0) { pm.expect.fail("google.rpc.Status.code (field 1) in grpc-status-details-bin must be varint-encoded"); return; }',
|
|
297873
|
+
' if (statusField.value < 0 || statusField.value > 16) pm.expect.fail("google.rpc.Status.code in grpc-status-details-bin must be a canonical gRPC code 0-16 but was " + statusField.value);',
|
|
297874
|
+
' else if (statusField.value !== pm.response.code) pm.expect.fail("google.rpc.Status.code in grpc-status-details-bin (" + statusField.value + ") disagrees with the reported status code (" + pm.response.code + ")");',
|
|
297875
|
+
" } else if (statusField.field === 2) {",
|
|
297876
|
+
' if (statusField.wire !== 2) pm.expect.fail("google.rpc.Status.message (field 2) in grpc-status-details-bin must be length-delimited");',
|
|
297877
|
+
" } else if (statusField.field === 3) {",
|
|
297878
|
+
' if (statusField.wire !== 2) { pm.expect.fail("google.rpc.Status.details (field 3) in grpc-status-details-bin must be length-delimited google.protobuf.Any messages"); return; }',
|
|
297879
|
+
" var anyFields = grpcPbFields(statusField.bytes);",
|
|
297880
|
+
' if (!anyFields) { pm.expect.fail("google.rpc.Status.details entry in grpc-status-details-bin is not a decodable google.protobuf.Any message"); return; }',
|
|
297881
|
+
" var hasTypeUrl = anyFields.some(function (anyField) { return anyField.field === 1 && anyField.wire === 2 && anyField.bytes.length > 0; });",
|
|
297882
|
+
' if (!hasTypeUrl) pm.expect.fail("google.protobuf.Any in grpc-status-details-bin details must carry a non-empty type_url (field 1)");',
|
|
297883
|
+
" }",
|
|
297884
|
+
" });",
|
|
297885
|
+
"}",
|
|
297772
297886
|
`pm.test('gRPC status is OK for ' + grpcSpec.methodPath, function () {`,
|
|
297773
297887
|
' pm.expect(pm.response.code, "gRPC call for " + grpcSpec.methodPath + " returned " + grpcStatusName() + " (" + pm.response.code + ")").to.eql(0);',
|
|
297774
297888
|
"});",
|
|
297889
|
+
// Metadata/trailer wire conformance runs on every terminal status (error
|
|
297890
|
+
// statuses carry trailers too, so this is not gated on code === 0).
|
|
297891
|
+
`pm.test('gRPC response metadata and trailers conform to gRPC wire rules for ' + grpcSpec.methodPath, function () {`,
|
|
297892
|
+
' grpcCheckMetaPairs(pm.response.metadata, "gRPC response metadata");',
|
|
297893
|
+
' grpcCheckMetaPairs(pm.response.trailers, "gRPC response trailer");',
|
|
297894
|
+
' var contentType = grpcMetaOne(pm.response.metadata, "content-type");',
|
|
297895
|
+
' if (contentType && contentType.value !== undefined && contentType.value !== null && !/^application\\/grpc/.test(String(contentType.value))) pm.expect.fail("gRPC response content-type metadata must be application/grpc[+proto|+json] but was " + contentType.value);',
|
|
297896
|
+
' var echoedStatus = grpcMetaOne(pm.response.trailers, "grpc-status");',
|
|
297897
|
+
" if (echoedStatus) {",
|
|
297898
|
+
" var echoedCode = Number(echoedStatus.value);",
|
|
297899
|
+
' if (!Number.isInteger(echoedCode) || echoedCode < 0 || echoedCode > 16) pm.expect.fail("grpc-status trailer must be a canonical gRPC code 0-16 but was " + JSON.stringify(echoedStatus.value));',
|
|
297900
|
+
' else if (echoedCode !== pm.response.code) pm.expect.fail("grpc-status trailer (" + echoedCode + ") disagrees with the reported status code (" + pm.response.code + ")");',
|
|
297901
|
+
" }",
|
|
297902
|
+
' grpcCheckStatusDetailsBin(grpcMetaOne(pm.response.trailers, "grpc-status-details-bin"));',
|
|
297903
|
+
"});",
|
|
297775
297904
|
// Terminal-response-message expectation. unary and client-streaming RPCs
|
|
297776
297905
|
// return exactly one terminal response message; server/bidi streaming may
|
|
297777
297906
|
// legitimately return ZERO messages on an OK stream, so no minimum is
|
|
@@ -297793,6 +297922,26 @@ function createGrpcScript(spec) {
|
|
|
297793
297922
|
' if (grpcSpec.responseShape) grpcCheckShape(message, grpcSpec.responseShape, grpcSpec.responseType + ".");',
|
|
297794
297923
|
" if (grpcSpec.responseIsRpcStatus) grpcCheckRpcStatus(message, grpcSpec.responseType);",
|
|
297795
297924
|
"});"
|
|
297925
|
+
] : [],
|
|
297926
|
+
// Every streamed message is validated, not only the terminal one that
|
|
297927
|
+
// pm.response.json() returns; pm.response.messages is the full received
|
|
297928
|
+
// stream. Server/bidi only: for unary/client the single terminal message
|
|
297929
|
+
// is already covered above.
|
|
297930
|
+
...(spec.stream === "server" || spec.stream === "bidi") && (spec.hasResponseShape || spec.responseIsRpcStatus) ? [
|
|
297931
|
+
`pm.test('gRPC streamed response messages each match ' + grpcSpec.responseType, function () {`,
|
|
297932
|
+
" if (pm.response.code !== 0) return;",
|
|
297933
|
+
" var list = pm.response.messages;",
|
|
297934
|
+
' if (!list || typeof list.each !== "function") return;',
|
|
297935
|
+
" var messageIndex = 0;",
|
|
297936
|
+
" list.each(function (member) {",
|
|
297937
|
+
" var data = member && member.data !== undefined ? member.data : member;",
|
|
297938
|
+
' var label = grpcSpec.responseType + "[message " + messageIndex + "]";',
|
|
297939
|
+
" messageIndex += 1;",
|
|
297940
|
+
' if (jsonTypeOf(data) !== "object") { pm.expect.fail("gRPC streamed response " + label + " is not a decodable message object but " + jsonTypeOf(data)); return; }',
|
|
297941
|
+
' if (grpcSpec.responseShape) grpcCheckShape(data, grpcSpec.responseShape, label + ".");',
|
|
297942
|
+
" if (grpcSpec.responseIsRpcStatus) grpcCheckRpcStatus(data, label);",
|
|
297943
|
+
" });",
|
|
297944
|
+
"});"
|
|
297796
297945
|
] : []
|
|
297797
297946
|
];
|
|
297798
297947
|
}
|
|
@@ -303589,6 +303738,7 @@ function createSoapScript(operation, warnings = []) {
|
|
|
303589
303738
|
};
|
|
303590
303739
|
const responseRegex = operation.expectedResponseElement ? elementPresenceRegex(operation.expectedResponseElement) : "";
|
|
303591
303740
|
const mediaType = operation.soapVersion === "1.2" ? "application/soap+xml" : "text/xml";
|
|
303741
|
+
const envelopeNs = operation.soapVersion === "1.2" ? "http://www.w3.org/2003/05/soap-envelope" : "http://schemas.xmlsoap.org/soap/envelope/";
|
|
303592
303742
|
const faultStatusLine = operation.soapVersion === "1.2" ? ' if (faulted && code !== 500 && code !== 400) pm.expect.fail("SOAP 1.2 Faults ride HTTP 500, or 400 for env:Sender faults (SOAP 1.2 Part 2 HTTP binding); got HTTP " + code);' : ' if (faulted && code !== 500) pm.expect.fail("SOAP 1.1 Faults must ride HTTP 500 (WS-I Basic Profile R1126); got HTTP " + code);';
|
|
303593
303743
|
const lines = [
|
|
303594
303744
|
`var soap = JSON.parse(${JSON.stringify(JSON.stringify(meta))});`,
|
|
@@ -303611,6 +303761,11 @@ function createSoapScript(operation, warnings = []) {
|
|
|
303611
303761
|
' pm.expect(bodyText, "response body is not a SOAP envelope").to.match(matchTag("Envelope"));',
|
|
303612
303762
|
"});",
|
|
303613
303763
|
"",
|
|
303764
|
+
`pm.test('SOAP Envelope namespace matches SOAP ${operation.soapVersion}', function () {`,
|
|
303765
|
+
' if (!matchTag("Envelope").test(bodyText)) return;',
|
|
303766
|
+
` pm.expect(bodyText.indexOf(${JSON.stringify(envelopeNs)}) !== -1, "SOAP ${operation.soapVersion} envelopes must declare the ${envelopeNs} namespace").to.equal(true);`,
|
|
303767
|
+
"});",
|
|
303768
|
+
"",
|
|
303614
303769
|
"pm.test('SOAP Body element is present', function () {",
|
|
303615
303770
|
' pm.expect(bodyText, "SOAP envelope has no Body element").to.match(matchTag("Body"));',
|
|
303616
303771
|
"});",
|
|
@@ -303628,7 +303783,33 @@ function createSoapScript(operation, warnings = []) {
|
|
|
303628
303783
|
" var code = pm.response.code;",
|
|
303629
303784
|
faultStatusLine,
|
|
303630
303785
|
' if (!faulted && code === 500) pm.expect.fail("HTTP 500 from a SOAP endpoint must carry a SOAP Fault in the body");',
|
|
303631
|
-
"});"
|
|
303786
|
+
"});",
|
|
303787
|
+
// Fault well-formedness (diagnostic companion to the Fault-absence test: a
|
|
303788
|
+
// faulted run already fails, this pinpoints a malformed Fault). SOAP 1.1
|
|
303789
|
+
// section 4.4 requires faultcode + faultstring children; SOAP 1.2 Part 1
|
|
303790
|
+
// section 5.4 requires Code/Value + Reason/Text and pins the top-level
|
|
303791
|
+
// Value QName to the five defined fault codes.
|
|
303792
|
+
...operation.soapVersion === "1.2" ? [
|
|
303793
|
+
"",
|
|
303794
|
+
"pm.test('SOAP Fault is well-formed for SOAP 1.2', function () {",
|
|
303795
|
+
' if (!matchTag("Fault").test(bodyText)) return;',
|
|
303796
|
+
' if (!matchTag("Code").test(bodyText)) { pm.expect.fail("a SOAP 1.2 Fault must carry an env:Code child (SOAP 1.2 Part 1 section 5.4)"); return; }',
|
|
303797
|
+
' if (!matchTag("Value").test(bodyText)) { pm.expect.fail("a SOAP 1.2 Fault Code must carry an env:Value child (SOAP 1.2 Part 1 section 5.4.1)"); return; }',
|
|
303798
|
+
' if (!matchTag("Reason").test(bodyText)) pm.expect.fail("a SOAP 1.2 Fault must carry an env:Reason child (SOAP 1.2 Part 1 section 5.4)");',
|
|
303799
|
+
' else if (!matchTag("Text").test(bodyText)) pm.expect.fail("a SOAP 1.2 Fault Reason must carry an env:Text child (SOAP 1.2 Part 1 section 5.4.2)");',
|
|
303800
|
+
' var faultValue = (bodyText.match(/<(?:[A-Za-z_][\\w.-]*:)?Value[^>]*>([\\s\\S]*?)<\\//) || [])[1] || "";',
|
|
303801
|
+
' var faultLocal = faultValue.trim().split(":").pop();',
|
|
303802
|
+
' var faultCodes = ["VersionMismatch", "MustUnderstand", "DataEncodingUnknown", "Sender", "Receiver"];',
|
|
303803
|
+
' if (faultLocal && faultCodes.indexOf(faultLocal) === -1) pm.expect.fail("the SOAP 1.2 Fault Code Value must be one of " + faultCodes.join(", ") + " (SOAP 1.2 Part 1 table 4); got " + faultValue.trim());',
|
|
303804
|
+
"});"
|
|
303805
|
+
] : [
|
|
303806
|
+
"",
|
|
303807
|
+
"pm.test('SOAP Fault is well-formed for SOAP 1.1', function () {",
|
|
303808
|
+
' if (!matchTag("Fault").test(bodyText)) return;',
|
|
303809
|
+
' if (!matchTag("faultcode").test(bodyText)) pm.expect.fail("a SOAP 1.1 Fault must carry a faultcode child (SOAP 1.1 section 4.4)");',
|
|
303810
|
+
' if (!matchTag("faultstring").test(bodyText)) pm.expect.fail("a SOAP 1.1 Fault must carry a faultstring child (SOAP 1.1 section 4.4)");',
|
|
303811
|
+
"});"
|
|
303812
|
+
]
|
|
303632
303813
|
];
|
|
303633
303814
|
if (responseRegex) {
|
|
303634
303815
|
lines.push(
|
|
@@ -303805,7 +303986,8 @@ function wsBindingKeyValues(channel) {
|
|
|
303805
303986
|
const value = asRecord15(wsBinding.value()) ?? {};
|
|
303806
303987
|
return {
|
|
303807
303988
|
headers: bindingKeyValues(value.headers),
|
|
303808
|
-
queryParams: bindingKeyValues(value.query)
|
|
303989
|
+
queryParams: bindingKeyValues(value.query),
|
|
303990
|
+
binding: value
|
|
303809
303991
|
};
|
|
303810
303992
|
}
|
|
303811
303993
|
function collectMqttInfo(channel, servers, messagesRaw, documentJson) {
|
|
@@ -303879,6 +304061,8 @@ function messageDescriptor2(message, warnings, channelId, defaultContentType, re
|
|
|
303879
304061
|
const replySchema = replyByMessageId.get(message.id());
|
|
303880
304062
|
const ackSchema = xAckSchema ?? replySchema;
|
|
303881
304063
|
const ackSource = xAckSchema ? "x-ack" : replySchema ? "reply" : void 0;
|
|
304064
|
+
const correlationRaw = asRecord15(rawMessage.correlationId)?.location;
|
|
304065
|
+
const correlationLocation = typeof correlationRaw === "string" ? correlationRaw : void 0;
|
|
303882
304066
|
const examples = message.examples().all().filter((example) => example.hasPayload());
|
|
303883
304067
|
const hasExample = examples.length > 0;
|
|
303884
304068
|
let sample;
|
|
@@ -303900,6 +304084,7 @@ function messageDescriptor2(message, warnings, channelId, defaultContentType, re
|
|
|
303900
304084
|
payloadSchema,
|
|
303901
304085
|
ackSchema,
|
|
303902
304086
|
ackSource,
|
|
304087
|
+
correlationLocation,
|
|
303903
304088
|
sample,
|
|
303904
304089
|
hasExample,
|
|
303905
304090
|
contentKind,
|
|
@@ -303912,6 +304097,7 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
|
|
|
303912
304097
|
const servers = resolveServers(channel, document2);
|
|
303913
304098
|
const messagesRaw = channel.messages().all();
|
|
303914
304099
|
const transport = detectTransport(channel, servers, messagesRaw, documentJson, warnings);
|
|
304100
|
+
const parameterNames = Object.keys(asRecord15(channel.json().parameters) ?? {}).sort();
|
|
303915
304101
|
const serverUrl = servers.find((server) => server.url())?.url() || options.endpointUrl?.trim() || "";
|
|
303916
304102
|
if (!serverUrl) {
|
|
303917
304103
|
warnings.push(`ASYNCAPI_NO_SERVER_URL: channel ${channel.id()} has no resolvable server url; the generated request url is derived from the channel address only and must be completed before use`);
|
|
@@ -303931,6 +304117,7 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
|
|
|
303931
304117
|
headers: [],
|
|
303932
304118
|
queryParams: [],
|
|
303933
304119
|
mqtt: collectMqttInfo(channel, servers, messagesRaw, documentJson),
|
|
304120
|
+
parameterNames,
|
|
303934
304121
|
messages,
|
|
303935
304122
|
warnings
|
|
303936
304123
|
};
|
|
@@ -303945,11 +304132,12 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
|
|
|
303945
304132
|
queryParams: [],
|
|
303946
304133
|
socketioNamespace: address.startsWith("/") ? address : `/${address}`,
|
|
303947
304134
|
socketioPath: DEFAULT_SOCKETIO_PATH,
|
|
304135
|
+
parameterNames,
|
|
303948
304136
|
messages,
|
|
303949
304137
|
warnings
|
|
303950
304138
|
};
|
|
303951
304139
|
}
|
|
303952
|
-
const { headers, queryParams } = wsBindingKeyValues(channel);
|
|
304140
|
+
const { headers, queryParams, binding } = wsBindingKeyValues(channel);
|
|
303953
304141
|
return {
|
|
303954
304142
|
id: channel.id(),
|
|
303955
304143
|
address,
|
|
@@ -303957,6 +304145,8 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
|
|
|
303957
304145
|
url: joinUrl(serverUrl, address),
|
|
303958
304146
|
headers,
|
|
303959
304147
|
queryParams,
|
|
304148
|
+
parameterNames,
|
|
304149
|
+
wsBinding: binding,
|
|
303960
304150
|
messages,
|
|
303961
304151
|
warnings
|
|
303962
304152
|
};
|
|
@@ -304394,14 +304584,68 @@ function collectMessageNodeIds(node, ids, path10) {
|
|
|
304394
304584
|
if (record) collectMessageNodeIds(record, ids, `${path10}/${i}`);
|
|
304395
304585
|
});
|
|
304396
304586
|
}
|
|
304587
|
+
function validateChannelParameters(channel, warnings) {
|
|
304588
|
+
const declared = new Set(channel.parameterNames ?? []);
|
|
304589
|
+
const used = /* @__PURE__ */ new Set();
|
|
304590
|
+
for (const match of channel.address.matchAll(/\{([^{}]*)\}/g)) {
|
|
304591
|
+
const name = match[1] ?? "";
|
|
304592
|
+
if (!name) {
|
|
304593
|
+
warnings.push(`ASYNCAPI_CHANNEL_PARAMETER_INVALID: channel ${channel.id} address ${channel.address} contains an empty {} parameter expression`);
|
|
304594
|
+
continue;
|
|
304595
|
+
}
|
|
304596
|
+
used.add(name);
|
|
304597
|
+
if (!declared.has(name)) {
|
|
304598
|
+
warnings.push(`ASYNCAPI_CHANNEL_PARAMETER_UNDECLARED: channel ${channel.id} address parameter {${name}} has no entry in the channel parameters object; AsyncAPI requires every address parameter to be declared`);
|
|
304599
|
+
}
|
|
304600
|
+
}
|
|
304601
|
+
for (const name of declared) {
|
|
304602
|
+
if (!used.has(name)) {
|
|
304603
|
+
warnings.push(`ASYNCAPI_CHANNEL_PARAMETER_UNUSED: channel ${channel.id} declares parameter ${name} that never appears in the channel address ${channel.address}`);
|
|
304604
|
+
}
|
|
304605
|
+
}
|
|
304606
|
+
}
|
|
304607
|
+
var CORRELATION_LOCATION_GRAMMAR = /^\$message\.(header|payload)#(\/(?:[^~/]|~[01])*)+$/;
|
|
304608
|
+
function validateCorrelationLocation(channelId, message, warnings) {
|
|
304609
|
+
if (message.correlationLocation === void 0) return;
|
|
304610
|
+
if (!CORRELATION_LOCATION_GRAMMAR.test(message.correlationLocation)) {
|
|
304611
|
+
warnings.push(
|
|
304612
|
+
`ASYNCAPI_CORRELATION_LOCATION_INVALID: message ${message.id} on channel ${channelId} correlationId location ${JSON.stringify(message.correlationLocation)} is not a valid AsyncAPI runtime expression ($message.header#/<pointer> or $message.payload#/<pointer>)`
|
|
304613
|
+
);
|
|
304614
|
+
}
|
|
304615
|
+
}
|
|
304616
|
+
var SOCKETIO_RESERVED_EVENTS = /* @__PURE__ */ new Set(["connect", "connect_error", "disconnect", "disconnecting", "newListener", "removeListener"]);
|
|
304617
|
+
function validateWsBinding(channel, warnings) {
|
|
304618
|
+
const binding = channel.wsBinding;
|
|
304619
|
+
if (!binding) return;
|
|
304620
|
+
const method = binding.method;
|
|
304621
|
+
if (method !== void 0 && (typeof method !== "string" || method !== "GET" && method !== "POST")) {
|
|
304622
|
+
warnings.push(`ASYNCAPI_WS_BINDING_INVALID: channel ${channel.id} ws binding method must be "GET" or "POST" but was ${JSON.stringify(method)}`);
|
|
304623
|
+
}
|
|
304624
|
+
for (const key of ["query", "headers"]) {
|
|
304625
|
+
const schema = binding[key];
|
|
304626
|
+
if (schema === void 0) continue;
|
|
304627
|
+
const record = asRecord16(schema);
|
|
304628
|
+
if (!record || record.type !== void 0 && record.type !== "object") {
|
|
304629
|
+
warnings.push(`ASYNCAPI_WS_BINDING_INVALID: channel ${channel.id} ws binding ${key} must be a Schema Object of type object`);
|
|
304630
|
+
}
|
|
304631
|
+
}
|
|
304632
|
+
}
|
|
304397
304633
|
function instrumentAsyncApiCollection(collection, index) {
|
|
304398
304634
|
const warnings = [
|
|
304399
304635
|
...index.warnings,
|
|
304400
304636
|
...index.channels.flatMap((channel) => channel.warnings)
|
|
304401
304637
|
];
|
|
304402
304638
|
for (const channel of index.channels) {
|
|
304639
|
+
validateChannelParameters(channel, warnings);
|
|
304403
304640
|
if (channel.transport === "mqtt") validateMqttChannel(channel, warnings);
|
|
304641
|
+
if (channel.transport === "ws-raw") validateWsBinding(channel, warnings);
|
|
304404
304642
|
for (const message of channel.messages) {
|
|
304643
|
+
if (channel.transport === "socketio" && SOCKETIO_RESERVED_EVENTS.has(message.eventName)) {
|
|
304644
|
+
warnings.push(
|
|
304645
|
+
`ASYNCAPI_SOCKETIO_RESERVED_EVENT: message ${message.id} on channel ${channel.id} uses the reserved Socket.IO event name "${message.eventName}"; reserved lifecycle events cannot be emitted or received as application events`
|
|
304646
|
+
);
|
|
304647
|
+
}
|
|
304648
|
+
validateCorrelationLocation(channel.id, message, warnings);
|
|
304405
304649
|
validateMessage(index, channel.id, message, warnings);
|
|
304406
304650
|
}
|
|
304407
304651
|
}
|
|
@@ -304579,6 +304823,7 @@ function toolDescriptor(tool, warnings) {
|
|
|
304579
304823
|
description: typeof tool.description === "string" ? tool.description : void 0,
|
|
304580
304824
|
inputSchema,
|
|
304581
304825
|
outputSchema: asRecord17(tool.outputSchema) ?? void 0,
|
|
304826
|
+
annotations: asRecord17(tool.annotations) ?? void 0,
|
|
304582
304827
|
sampleArguments: inputSchema ? sampleFromSchema2(inputSchema, 0) : {},
|
|
304583
304828
|
warnings: toolWarnings
|
|
304584
304829
|
};
|
|
@@ -304779,10 +305024,41 @@ function validateServer(server, warnings) {
|
|
|
304779
305024
|
}
|
|
304780
305025
|
}
|
|
304781
305026
|
}
|
|
305027
|
+
var TOOL_ANNOTATION_BOOLEAN_HINTS = ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"];
|
|
305028
|
+
function validateToolAnnotations(tool, warnings) {
|
|
305029
|
+
if (!tool.annotations) return;
|
|
305030
|
+
for (const hint of TOOL_ANNOTATION_BOOLEAN_HINTS) {
|
|
305031
|
+
const value = tool.annotations[hint];
|
|
305032
|
+
if (value !== void 0 && typeof value !== "boolean") {
|
|
305033
|
+
warnings.push(`MCP_TOOL_ANNOTATION_INVALID: tool ${tool.name} annotations.${hint} must be a boolean (MCP ToolAnnotations); got ${JSON.stringify(value)}`);
|
|
305034
|
+
}
|
|
305035
|
+
}
|
|
305036
|
+
if (tool.annotations.title !== void 0 && typeof tool.annotations.title !== "string") {
|
|
305037
|
+
warnings.push(`MCP_TOOL_ANNOTATION_INVALID: tool ${tool.name} annotations.title must be a string (MCP ToolAnnotations); got ${JSON.stringify(tool.annotations.title)}`);
|
|
305038
|
+
}
|
|
305039
|
+
}
|
|
305040
|
+
function validateToolOutputSchema(index, tool, warnings) {
|
|
305041
|
+
if (!tool.outputSchema) return;
|
|
305042
|
+
const declaredType = tool.outputSchema.type;
|
|
305043
|
+
if (declaredType !== void 0 && declaredType !== "object") {
|
|
305044
|
+
warnings.push(`MCP_TOOL_OUTPUT_SCHEMA_INVALID: tool ${tool.name} outputSchema type is ${JSON.stringify(declaredType)}; the MCP specification requires tool output schemas to describe the structuredContent object`);
|
|
305045
|
+
}
|
|
305046
|
+
const packed = packSchema(index.documentJson, tool.outputSchema, "3.0", "response");
|
|
305047
|
+
if (packed.unsupported) {
|
|
305048
|
+
const code = isSchemaGraphOverflow(packed) ? "MCP_SCHEMA_NOT_COMPILED" : "MCP_TOOL_OUTPUT_SCHEMA_NOT_VALIDATED";
|
|
305049
|
+
warnings.push(`${code}: tool ${tool.name} outputSchema is not validated (${packed.unsupported})`);
|
|
305050
|
+
return;
|
|
305051
|
+
}
|
|
305052
|
+
if (!compileSchemaValidator(packed.schema)) {
|
|
305053
|
+
warnings.push(`MCP_TOOL_OUTPUT_SCHEMA_NOT_VALIDATED: tool ${tool.name} outputSchema could not be compiled to a validator`);
|
|
305054
|
+
}
|
|
305055
|
+
}
|
|
304782
305056
|
function validateTool(index, tool, warnings) {
|
|
304783
305057
|
if (!TOOL_NAME_RE.test(tool.name)) {
|
|
304784
305058
|
warnings.push(`MCP_TOOL_NAME_UNCONVENTIONAL: tool name "${tool.name}" is outside the conventional [A-Za-z0-9_./-]{1,128} identifier set`);
|
|
304785
305059
|
}
|
|
305060
|
+
validateToolAnnotations(tool, warnings);
|
|
305061
|
+
validateToolOutputSchema(index, tool, warnings);
|
|
304786
305062
|
if (!tool.inputSchema) return;
|
|
304787
305063
|
const declaredType = tool.inputSchema.type;
|
|
304788
305064
|
if (declaredType !== void 0 && declaredType !== "object") {
|