diff --git a/package-lock.json b/package-lock.json index e8dfe1f..48e9dbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "object-hash": "3.0.0" }, "devDependencies": { + "@jest/globals": "29.5.0", "@netcracker/qubership-apihub-graphapi": "1.0.8", "@netcracker/qubership-apihub-npm-gitflow": "3.1.0", "@types/jest": "29.5.2", @@ -39,39 +40,25 @@ "vite-plugin-target": "0.1.1" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", - "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "dev": true, "license": "MIT", "engines": { @@ -79,22 +66,22 @@ } }, "node_modules/@babel/core": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", - "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.0", - "@babel/generator": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.0", - "@babel/parser": "^7.26.0", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.26.0", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -120,16 +107,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", - "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.5", - "@babel/types": "^7.26.5", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -137,14 +124,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -163,30 +150,40 @@ "semver": "bin/semver.js" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -196,9 +193,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, "license": "MIT", "engines": { @@ -206,9 +203,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -216,9 +213,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -226,9 +223,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -236,27 +233,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz", - "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.5" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -363,13 +360,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -489,13 +486,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", - "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -505,58 +502,48 @@ } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.5.tgz", - "integrity": "sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.5", - "@babel/parser": "^7.26.5", - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.5", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz", - "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1058,16 +1045,16 @@ } }, "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -1223,34 +1210,31 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -1277,9 +1261,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -3082,9 +3066,9 @@ } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, "license": "MIT", "dependencies": { @@ -3105,7 +3089,7 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "node_modules/babel-preset-jest": { @@ -5320,6 +5304,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runtime/node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jest-snapshot": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", @@ -6202,9 +6202,9 @@ } }, "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 0f98c12..704a193 100644 --- a/package.json +++ b/package.json @@ -41,15 +41,16 @@ ], "dependencies": { "@netcracker/qubership-apihub-json-crawl": "1.0.4", - "object-hash": "3.0.0", - "fast-equals": "4.0.3" + "fast-equals": "4.0.3", + "object-hash": "3.0.0" }, "devDependencies": { + "@jest/globals": "29.5.0", "@netcracker/qubership-apihub-graphapi": "feature-performance-optimization", "@netcracker/qubership-apihub-npm-gitflow": "3.1.0", - "@types/object-hash": "3.0.6", "@types/jest": "29.5.2", "@types/js-yaml": "4.0.5", + "@types/object-hash": "3.0.6", "@typescript-eslint/eslint-plugin": "6.0.0", "@typescript-eslint/parser": "6.0.0", "eslint": "8.48.0", @@ -57,8 +58,8 @@ "jest": "29.5.0", "jest-extended": "4.0.2", "js-yaml": "4.1.0", - "openapi-types": "12.1.3", "json-schema-merge-allof": "0.8.1", + "openapi-types": "12.1.3", "rimraf": "5.0.5", "ts-jest": "29.1.0", "ts-loader": "9.4.3", diff --git a/src/hash.ts b/src/hash.ts index c528f82..5992096 100644 --- a/src/hash.ts +++ b/src/hash.ts @@ -1,101 +1,60 @@ -import { - BEFORE_SECOND_DATA_LEVEL as SECOND_DATA_LEVELS, - CURRENT_DATA_LEVEL, - HashOptions, - InternalHashOptions, - NormalizationRule, -} from './types' +import { HashOptions, InternalHashOptions, NormalizationRule } from './types' import { resolveSpec, SPEC_TYPE_GRAPH_API } from './spec-type' -import { CrawlRules, isObject, syncClone, SyncCloneHook, syncCrawl, SyncCrawlHook } from '@netcracker/qubership-apihub-json-crawl' +import { isObject, syncCrawl, SyncCrawlHook } from '@netcracker/qubership-apihub-json-crawl' import { RULES } from './rules' -import objectHash, { NotUndefined } from 'object-hash' +import { cryptoMd5, objectToString } from './utils' -const calculateHash: (object: NotUndefined) => string = (object) => { - const res = objectHash(object, { - unorderedArrays: true, - unorderedObjects: true, - algorithm: 'md5', - }) - return res -} +export type ObjPath = (string | number)[] -const createHashObject: (object: NotUndefined, rules: CrawlRules) => NotUndefined = (object, rules) => { - const creatorHook = createHashObjectCreatorHook() - return syncClone(object, creatorHook, { state: { dataLevel: 0 }, rules }) as NotUndefined -} +//todo only for tests +const optionalFieldFlag = Symbol('optional-fields') + +const createHashObjectCreatorHook: (options: HashOptions) => HashScannerCrawlHook = (options) => { + const { semanticHashProperty, hashProperty } = options -const createHashObjectCreatorHook: () => HashObjectCreatorCrawlHook = () => { const cycleGuard: Set = new Set() - const creatorHook: HashObjectCreatorCrawlHook = ({ key, value, rules, state }) => { + const optionalFieldStates = new Map() + + const generateHash = (value: any, flag: symbol, optionalFields?: ObjPath): string => { + return cryptoMd5(objectToString(value, flag, optionalFields)) + } + + const hashHook: HashScannerCrawlHook = ({ key, value, rules, state }) => { if (typeof key === 'symbol') { - return { done: true } + return { done: true, state: { ...state, fieldsForOptionalHash: [] } } } if (!rules) { + state.fieldsForOptionalHash.push(key) return { done: true } } - let ignoreKey = true - switch (rules.hashStrategy) { - case CURRENT_DATA_LEVEL: { - ignoreKey = state.dataLevel > 0 - break - } - case SECOND_DATA_LEVELS: { - ignoreKey = state.dataLevel > 1 - break - } - } + const ignoreKey = rules.excludeFromSemanticHash + ignoreKey && state.fieldsForOptionalHash.push(key) if (!isObject(value)) { - return { done: ignoreKey, value } + return { done: true, value } } - if (cycleGuard.has(value)) { - return { - done: ignoreKey, - value: rules.newDataLayer ? value : undefined, - state: { - ...state, - dataLevel: key !== undefined && rules.newDataLayer ? state.dataLevel + 1 : state.dataLevel, - }, - } + const nestedFieldsForOptionalHash = optionalFieldStates.get(value) || [] + if (!optionalFieldStates.has(value)) { + optionalFieldStates.set(value, nestedFieldsForOptionalHash) } - cycleGuard.add(value) + + const done = !(!cycleGuard.has(value) && cycleGuard.add(value)) return { - done: ignoreKey, + done, value, - state: { - ...state, - dataLevel: key !== undefined && rules.newDataLayer ? state.dataLevel + 1 : state.dataLevel, - }, - } - } - return creatorHook -} - -const createHashScannerHook: (options: HashOptions) => HashScannerCrawlHook = (options) => { - const flag = options.hashFlag ?? Symbol('should-never-happen') - const cycleGuard: Set = new Set() - const hashHook: HashScannerCrawlHook = ({ key, value, rules }) => { - if (typeof key === 'symbol') { - return { done: true } - } - - if (!isObject(value)) { - return { done: true } - } + state: { ...state, fieldsForOptionalHash: nestedFieldsForOptionalHash }, + exitHook: () => { + if (hashProperty) { + value[hashProperty] = generateHash(value, hashProperty) + } - if (cycleGuard.has(value)) { - return { done: true } - } - cycleGuard.add(value) - return { - value, exitHook: rules?.hashOwner ? () => { - let hash: string | undefined = undefined - value[flag] = () => { - if (!hash) { - hash = calculateHash(createHashObject(value, rules)) - } - return hash + // even if the optionalFields are empty, it is necessary to calculate the hash again, + // since its children may have optional fields and their hash will be different. + if (semanticHashProperty) { + value[semanticHashProperty] = generateHash(value, semanticHashProperty, nestedFieldsForOptionalHash) } - } : undefined, + + value[optionalFieldFlag] = nestedFieldsForOptionalHash + }, } } @@ -104,39 +63,33 @@ const createHashScannerHook: (options: HashOptions) => HashScannerCrawlHook = (o type HashScannerCrawlHook = SyncCrawlHook -export interface HashScannerCrawlState {} - -type HashObjectCreatorCrawlHook = SyncCloneHook - -export interface HashObjectCreatorState { - dataLevel: number +export interface HashScannerCrawlState { + fieldsForOptionalHash: ObjPath } - export const hash = (value: unknown, options?: HashOptions) => { const internalOptions = { ...options, } satisfies InternalHashOptions - const flag = options?.hashFlag - if (!flag) { + const semanticHashProperty = options?.semanticHashProperty + const hashProperty = options?.hashProperty + if (!semanticHashProperty && !hashProperty) { return value } const spec = resolveSpec(value) - if (spec.type === SPEC_TYPE_GRAPH_API){ - return value //cause not implemented - } syncCrawl( value, - [createHashScannerHook(internalOptions)], - { rules: RULES[spec.type] }, + [createHashObjectCreatorHook(internalOptions)], + { state: { fieldsForOptionalHash: [] }, rules: RULES[spec.type] }, ) return value } export const deHash = (value: unknown, options?: HashOptions) => { - const flag = options?.hashFlag - if (!flag) { + const semanticHashProperty = options?.semanticHashProperty + const hashProperty = options?.hashProperty + if (!semanticHashProperty && !hashProperty) { return value } const cycleGuard: Set = new Set() @@ -148,7 +101,10 @@ export const deHash = (value: unknown, options?: HashOptions) => { return { done: true } } cycleGuard.add(value) - flag in value && delete value[flag] + if (semanticHashProperty && semanticHashProperty in value) {delete value[semanticHashProperty]} + if (hashProperty && hashProperty in value) { delete value[hashProperty] } + //todo del after tests + if (optionalFieldFlag in value) { delete value[optionalFieldFlag] } return { value } }) return value diff --git a/src/normalize.ts b/src/normalize.ts index 763c336..df82e4b 100644 --- a/src/normalize.ts +++ b/src/normalize.ts @@ -26,14 +26,14 @@ export const normalize = (value: unknown, options: NormalizeOptions = {}) => { if (optionsWithDefaults.unify) { spec = unify(spec, optionsWithDefaults) } if (optionsWithDefaults.mergeAllOf && !optionsWithDefaults.unify && !optionsWithDefaults.allowNotValidSyntheticChanges) { spec = cleanUpSynthetic(spec, optionsWithDefaults) } if (optionsWithDefaults.removeOasExtensions) { spec = removeOasExtensions(spec, optionsWithDefaults) } - if (optionsWithDefaults.hashFlag) { spec = hash(spec, optionsWithDefaults) } + if (optionsWithDefaults.semanticHashProperty || optionsWithDefaults.hashProperty) { spec = hash(spec, optionsWithDefaults) } return spec } export const denormalize = (value: unknown, options: DenormalizeOptions = {}) => { const optionsWithDefaults = createOptionsWithDefaults(options) let spec = value - if (optionsWithDefaults.hashFlag) { spec = deHash(spec, optionsWithDefaults) } + if (optionsWithDefaults.semanticHashProperty || optionsWithDefaults.hashProperty) { spec = deHash(spec, optionsWithDefaults) } if (optionsWithDefaults.mergeAllOf && !optionsWithDefaults.unify && !optionsWithDefaults.allowNotValidSyntheticChanges) { spec = deCleanUpSynthetic(spec, optionsWithDefaults) } if (optionsWithDefaults.unify) { spec = deUnify(spec, optionsWithDefaults) } //if in future we found way to denormalize following operation it should be in this order diff --git a/src/rules/jsonschema.ts b/src/rules/jsonschema.ts index c8a0022..955e123 100644 --- a/src/rules/jsonschema.ts +++ b/src/rules/jsonschema.ts @@ -1,6 +1,4 @@ import { - BEFORE_SECOND_DATA_LEVEL, - CURRENT_DATA_LEVEL, NormalizationRules, OriginLeafs, ReferenceHandler, @@ -202,13 +200,11 @@ const versionSpecific: Record NormalizationR '/contentMediaType': { validate: checkType(TYPE_STRING), merge: resolvers.last, - hashStrategy: CURRENT_DATA_LEVEL, }, '/const': { validate: checkType(...TYPE_JSON_ANY), merge: resolvers.equal, '/**': { validate: checkType(...TYPE_JSON_ANY) }, - hashStrategy: CURRENT_DATA_LEVEL, }, '/propertyNames': () => { const common = self() @@ -216,7 +212,6 @@ const versionSpecific: Record NormalizationR ...common, //maybe better to remove propertyNames at all? unify: insertIntoArrayByInstruction(concatArrays(common.unify), replaceValue(jsonSchemaTypeInfer, excludeNotAllowedTypes([JSON_SCHEMA_NODE_TYPE_STRING]))), - hashStrategy: CURRENT_DATA_LEVEL, } }, '/contains': self, @@ -224,43 +219,37 @@ const versionSpecific: Record NormalizationR '/*': ({ value }) => (Array.isArray(value) ? { validate: checkType(TYPE_ARRAY), + excludeFromSemanticHash: true, '/*': { validate: checkType(TYPE_STRING), + excludeFromSemanticHash: true, }, } : self() ), validate: checkType(TYPE_OBJECT), merge: resolvers.dependenciesMergeResolver, - hashStrategy: CURRENT_DATA_LEVEL, }, '/defs': { '/*': self, validate: checkType(TYPE_OBJECT), merge: resolvers.mergeObjects, - hashStrategy: CURRENT_DATA_LEVEL, }, '/additionalProperties': () => ({ ...self(), merge: resolvers.additionalPropertiesMergeResolver, - hashStrategy: BEFORE_SECOND_DATA_LEVEL, - newDataLayer: true, }), '/additionalItems': () => ({ ...self(), merge: resolvers.additionalItemsMergeResolver, - hashStrategy: CURRENT_DATA_LEVEL, - newDataLayer: true, }), '/exclusiveMaximum': { validate: checkType(TYPE_NUMBER), merge: resolvers.minValue, //todo how it works for allOf - hashStrategy: CURRENT_DATA_LEVEL, }, '/exclusiveMinimum': { validate: checkType(TYPE_NUMBER), merge: resolvers.maxValue, //todo how it works for allOf - hashStrategy: CURRENT_DATA_LEVEL, }, validate: checkType(TYPE_OBJECT, TYPE_BOOLEAN), }), @@ -269,17 +258,14 @@ const versionSpecific: Record NormalizationR '/readOnly': { validate: checkType(TYPE_BOOLEAN), merge: resolvers.or, - hashStrategy: CURRENT_DATA_LEVEL, }, '/writeOnly': { validate: checkType(TYPE_BOOLEAN), merge: resolvers.or, - hashStrategy: CURRENT_DATA_LEVEL, }, '/deprecated': { merge: resolvers.or, validate: checkType(TYPE_BOOLEAN), - hashStrategy: CURRENT_DATA_LEVEL, }, }), } @@ -300,91 +286,77 @@ export const jsonSchemaRules: ( '/*': { validate: [checkType(TYPE_STRING), checkContains(...JSON_SCHEMA_NODE_TYPES)] }, }), merge: resolvers.mergeTypes, - hashStrategy: BEFORE_SECOND_DATA_LEVEL, }), '/title': { validate: checkType(TYPE_STRING), merge: resolvers.last, + excludeFromSemanticHash: true, }, '/description': { validate: checkType(TYPE_STRING), merge: resolvers.last, + excludeFromSemanticHash: true, }, '/format': { validate: checkType(TYPE_STRING), merge: resolvers.last, - hashStrategy: CURRENT_DATA_LEVEL, }, '/default': { validate: checkType(...TYPE_JSON_ANY), merge: resolvers.last, '/**': { validate: checkType(...TYPE_JSON_ANY) }, - hashStrategy: CURRENT_DATA_LEVEL, }, '/multipleOf': { validate: checkType(TYPE_NUMBER), merge: resolvers.mergeMultipleOf, - hashStrategy: CURRENT_DATA_LEVEL, }, '/maximum': { validate: checkType(TYPE_NUMBER), merge: resolvers.minValue, - hashStrategy: CURRENT_DATA_LEVEL, }, '/exclusiveMaximum': { validate: checkType(TYPE_BOOLEAN), merge: resolvers.or, - hashStrategy: CURRENT_DATA_LEVEL, }, '/minimum': { validate: checkType(TYPE_NUMBER), merge: resolvers.maxValue, - hashStrategy: CURRENT_DATA_LEVEL, }, '/exclusiveMinimum': { validate: checkType(TYPE_BOOLEAN), merge: resolvers.or, - hashStrategy: CURRENT_DATA_LEVEL, }, '/maxLength': { validate: checkType(TYPE_NUMBER), merge: resolvers.minValue, - hashStrategy: CURRENT_DATA_LEVEL, }, '/minLength': { validate: checkType(TYPE_NUMBER), merge: resolvers.maxValue, - hashStrategy: CURRENT_DATA_LEVEL, }, '/pattern': { validate: checkType(TYPE_STRING), merge: resolvers.mergePattern, - hashStrategy: CURRENT_DATA_LEVEL, }, '/maxItems': { validate: checkType(TYPE_NUMBER), merge: resolvers.minValue, - hashStrategy: CURRENT_DATA_LEVEL, }, '/minItems': { validate: checkType(TYPE_NUMBER), merge: resolvers.maxValue, - hashStrategy: CURRENT_DATA_LEVEL, }, '/uniqueItems': { validate: checkType(TYPE_BOOLEAN), merge: resolvers.or, - hashStrategy: CURRENT_DATA_LEVEL, }, '/maxProperties': { validate: checkType(TYPE_NUMBER), merge: resolvers.minValue, - hashStrategy: CURRENT_DATA_LEVEL, }, '/minProperties': { validate: checkType(TYPE_NUMBER), merge: resolvers.maxValue, - hashStrategy: CURRENT_DATA_LEVEL, }, '/items': ({ value }) => ({ ...(Array.isArray(value) @@ -392,17 +364,13 @@ export const jsonSchemaRules: ( validate: [checkType(TYPE_ARRAY)], '/*': { ...self(), - hashStrategy: BEFORE_SECOND_DATA_LEVEL, - newDataLayer: true, }, } : { ...self(), - newDataLayer: true, } ), merge: resolvers.itemsMergeResolver, - hashStrategy: CURRENT_DATA_LEVEL, }), deprecation: { deprecationResolver: ctx => JSON_SCHEMA_DEPRECATION_RESOLVER(ctx), @@ -413,19 +381,17 @@ export const jsonSchemaRules: ( ? { validate: [] } : { ...self(), - newDataLayer: true, + excludeFromSemanticHash: true }), merge: resolvers.additionalItemsMergeResolver, - hashStrategy: CURRENT_DATA_LEVEL, + excludeFromSemanticHash: true }), '/required': { validate: checkType(TYPE_ARRAY), merge: resolvers.mergeStringSets, '/*': { validate: checkType(TYPE_STRING), - hashStrategy: BEFORE_SECOND_DATA_LEVEL, }, - hashStrategy: CURRENT_DATA_LEVEL, }, '/enum': { validate: checkType(TYPE_ARRAY), @@ -433,52 +399,39 @@ export const jsonSchemaRules: ( unify: unifyJsonSchemaEnums, '/**': { validate: checkType(...TYPE_JSON_ANY), - hashStrategy: CURRENT_DATA_LEVEL, }, - hashStrategy: CURRENT_DATA_LEVEL, }, '/properties': { '/*': () => ({ ...self(), - newDataLayer: true, - hashStrategy: BEFORE_SECOND_DATA_LEVEL, }), validate: checkType(TYPE_OBJECT), merge: resolvers.propertiesMergeResolver, - hashStrategy: CURRENT_DATA_LEVEL, }, '/additionalProperties': ({ value }) => ({ ...(typeof value === 'boolean' ? { validate: [] } : self()), merge: resolvers.additionalPropertiesMergeResolver, - hashStrategy: BEFORE_SECOND_DATA_LEVEL, - newDataLayer: true, }), '/patternProperties': { '/*': () => ({ - ...self(), - newDataLayer: true, - hashStrategy: BEFORE_SECOND_DATA_LEVEL, + ...self() }), validate: checkType(TYPE_OBJECT), merge: resolvers.propertiesMergeResolver, - hashStrategy: CURRENT_DATA_LEVEL, }, '/oneOf': { validate: checkType(TYPE_ARRAY), merge: resolvers.mergeCombination, - '/*': () => ({ ...self(), hashStrategy: BEFORE_SECOND_DATA_LEVEL }), - hashStrategy: BEFORE_SECOND_DATA_LEVEL, + '/*': () => self(), }, '/anyOf': { validate: checkType(TYPE_ARRAY), merge: resolvers.mergeCombination, '/*': self, - hashStrategy: BEFORE_SECOND_DATA_LEVEL, }, '/not': () => ({ ...self(), merge: resolvers.mergeNot, - hashStrategy: BEFORE_SECOND_DATA_LEVEL, }), //TODO NOT BY SPECIFICATION. ONLY IN 06 VERSION. NC SPECIFIC EXCLUSION '/examples': { @@ -486,27 +439,26 @@ export const jsonSchemaRules: ( merge: resolvers.last, '/**': { validate: checkType(...TYPE_JSON_ANY), + excludeFromSemanticHash: true, }, + excludeFromSemanticHash: true, }, '/definitions': { '/*': self, validate: checkType(TYPE_OBJECT), merge: resolvers.mergeObjects, - hashStrategy: BEFORE_SECOND_DATA_LEVEL, }, '/allOf': { validate: checkType(TYPE_ARRAY), //actually this contains only dead allOf. Cause all other should be already resolved merge: resolvers.concatArrays, '/*': self, - hashStrategy: BEFORE_SECOND_DATA_LEVEL, }, '/$ref': { validate: checkType(TYPE_STRING), //actually this contains only dead refs. Cause all other should be already resolved merge: resolvers.concatString, //why anyOf? - hashStrategy: BEFORE_SECOND_DATA_LEVEL, }, '/**': { referenceHandler: notAllowedReferenceHandler }, //4.3.2. Boolean JSON Schemas - not supported. Cause not tested @@ -535,7 +487,5 @@ export const jsonSchemaRules: ( cleanUpSyntheticJsonSchemaTypes, ], mandatoryUnify: [forwardOnlyCleanUpSyntheticJsonSchemaTypes], - ...versionSpecific[version](self), - hashStrategy: CURRENT_DATA_LEVEL, - hashOwner: true, + ...versionSpecific[version](self) }) diff --git a/src/rules/openapi.ts b/src/rules/openapi.ts index 6aa9b7a..eafaa57 100644 --- a/src/rules/openapi.ts +++ b/src/rules/openapi.ts @@ -1,12 +1,4 @@ -import { - BEFORE_SECOND_DATA_LEVEL, - CURRENT_DATA_LEVEL, - InternalUnifyOptions, - NormalizationRules, - ReferenceHandler, - UnifyContext, - UnifyFunction, -} from '../types' +import { InternalUnifyOptions, NormalizationRules, ReferenceHandler, UnifyContext, UnifyFunction } from '../types' import { OpenApiSpecVersion, SPEC_TYPE_JSON_SCHEMA_04, @@ -296,13 +288,19 @@ const openApiExternalDocsRules: NormalizationRules = { '/description': { validate: checkType(TYPE_STRING) }, '/url': { validate: checkType(TYPE_STRING) }, ...openApiExtensionRules, + excludeFromSemanticHash: true, + '/**': { excludeFromSemanticHash: true }, }, } const openApiExampleRules: NormalizationRules = { '/example': { validate: checkType(...TYPE_JSON_ANY), - '/**': { validate: checkType(...TYPE_JSON_ANY) }, + excludeFromSemanticHash: true, + '/**': { + validate: checkType(...TYPE_JSON_ANY), + excludeFromSemanticHash: true, + }, }, } @@ -310,16 +308,21 @@ const openApiExamplesRules = (version: OpenApiSpecVersion): NormalizationRules = '/examples': { validate: checkType(TYPE_OBJECT), merge: resolvers.last, + excludeFromSemanticHash: true, '/*': { ...openApiExtensionRulesFunction({ validate: checkType(...TYPE_JSON_ANY), + excludeFromSemanticHash: true, }), referenceHandler: referenceObjectRuleFunction({ version, allowedOverrides: [OPEN_API_PROPERTY_DESCRIPTION, OPEN_API_PROPERTY_SUMMARY], }), }, - '/**': { validate: checkType(...TYPE_JSON_ANY) }, + '/**': { + validate: checkType(...TYPE_JSON_ANY), + excludeFromSemanticHash: true, + }, }, }) @@ -430,49 +433,43 @@ const customFor30JsonSchemaRulesFactory = (): NormalizationRules => { '/type': { validate: [checkType(TYPE_STRING), checkContains(...OPEN_API_30_JSON_SCHEMA_NODE_TYPES)], merge: resolvers.mergeTypes, - hashStrategy: BEFORE_SECOND_DATA_LEVEL, }, '/items': () => ({ ...customFor30JsonSchemaRules, merge: resolvers.itemsMergeResolver, - hashStrategy: CURRENT_DATA_LEVEL, - newDataLayer: true, }), '/additionalItems': { validate: () => false, - hashStrategy: BEFORE_SECOND_DATA_LEVEL, - newDataLayer: true, + excludeFromSemanticHash: true, }, '/patternProperties': { validate: () => false, - hashStrategy: BEFORE_SECOND_DATA_LEVEL, - newDataLayer: true, + excludeFromSemanticHash: true, }, '/readOnly': { validate: checkType(TYPE_BOOLEAN), merge: resolvers.or, - hashStrategy: CURRENT_DATA_LEVEL, }, '/writeOnly': { validate: checkType(TYPE_BOOLEAN), merge: resolvers.or, - hashStrategy: CURRENT_DATA_LEVEL, }, '/deprecated': { validate: checkType(TYPE_BOOLEAN), merge: resolvers.or, - hashStrategy: CURRENT_DATA_LEVEL, }, '/nullable': { validate: checkType(TYPE_BOOLEAN), merge: resolvers.or, //todo need check - hashStrategy: CURRENT_DATA_LEVEL, }, '/example': { validate: checkType(...TYPE_JSON_ANY), merge: resolvers.last, - '/**': { validate: checkType(...TYPE_JSON_ANY) }, - hashStrategy: CURRENT_DATA_LEVEL, + excludeFromSemanticHash: true, + '/**': { + validate: checkType(...TYPE_JSON_ANY), + excludeFromSemanticHash: true, + }, }, unify: insertIntoArrayByInstruction( concatArrays(core.unify, extension.unify), @@ -543,7 +540,10 @@ const openApiHeadersRules = (version: OpenApiSpecVersion): NormalizationRules => descriptionCalculator: ctx => `[Deprecated] header${nonEmptyString(calculateHeaderName(ctx.paths, ctx.key))}${nonEmptyString(calculateHeaderPlace(ctx.paths, ctx.suffix))}`, inlineDescriptionSuffixCalculator: (ctx) => `in header '${ctx.key.toString()}' ${ctx.suffix}`, }, - '/description': { validate: checkType(TYPE_STRING) }, + '/description': { + validate: checkType(TYPE_STRING), + excludeFromSemanticHash: true, + }, '/required': { validate: checkType(TYPE_BOOLEAN) }, '/deprecated': { validate: checkType(TYPE_BOOLEAN) }, '/allowEmptyValue': { validate: checkType(TYPE_BOOLEAN) }, @@ -577,43 +577,37 @@ const openApiParametersRules = (version: OpenApiSpecVersion): NormalizationRules }, '/name': { validate: checkType(TYPE_STRING), - hashStrategy: CURRENT_DATA_LEVEL, }, '/in': { validate: checkType(TYPE_STRING), - hashStrategy: CURRENT_DATA_LEVEL, }, - '/description': { validate: checkType(TYPE_STRING) }, + '/description': { + validate: checkType(TYPE_STRING), + excludeFromSemanticHash: true, + }, '/required': { validate: checkType(TYPE_BOOLEAN), - hashStrategy: CURRENT_DATA_LEVEL, }, '/deprecated': { validate: checkType(TYPE_BOOLEAN), - hashStrategy: CURRENT_DATA_LEVEL, }, '/allowEmptyValue': { validate: checkType(TYPE_BOOLEAN), - hashStrategy: CURRENT_DATA_LEVEL, }, '/style': { validate: checkType(TYPE_STRING), - hashStrategy: CURRENT_DATA_LEVEL, }, '/explode': { validate: checkType(TYPE_BOOLEAN), - hashStrategy: CURRENT_DATA_LEVEL, }, '/allowReserved': { validate: checkType(TYPE_BOOLEAN), - hashStrategy: CURRENT_DATA_LEVEL, }, '/content': openApiMediaTypesRules(version), ...openApiExampleRules, ...openApiExamplesRules(version), '/schema': () => ({ ...openApiJsonSchemaRules(version), - newDataLayer: true, }), ...openApiExtensionRules, referenceHandler: referenceObjectRuleFunction({ version, allowedOverrides: [OPEN_API_PROPERTY_DESCRIPTION] }), @@ -622,14 +616,15 @@ const openApiParametersRules = (version: OpenApiSpecVersion): NormalizationRules valueDefaults(OPEN_API_PARAMETER_DEFAULTS), valueReplaces(OPEN_API_PARAMETER_REPLACES), ], - hashStrategy: BEFORE_SECOND_DATA_LEVEL, - hashOwner: true, }, validate: checkType(TYPE_ARRAY), }) const openApiRequestRules = (version: OpenApiSpecVersion): NormalizationRules => ({ - '/description': { validate: checkType(TYPE_STRING) }, + '/description': { + validate: checkType(TYPE_STRING), + excludeFromSemanticHash: true, + }, '/required': { validate: checkType(TYPE_BOOLEAN) }, '/content': openApiMediaTypesRules(version), ...openApiExtensionRules, @@ -645,7 +640,10 @@ const openApiRequestRules = (version: OpenApiSpecVersion): NormalizationRules => const openApiResponsesRules = (version: OpenApiSpecVersion): NormalizationRules => ({ ...openApiExtensionRulesFunction({ - '/description': { validate: checkType(TYPE_STRING) }, + '/description': { + validate: checkType(TYPE_STRING), + excludeFromSemanticHash: true, + }, '/headers': openApiHeadersRules(version), '/content': openApiMediaTypesRules(version), '/links': openApiLinksRules(version), diff --git a/src/types.ts b/src/types.ts index 38f4cba..3e58128 100644 --- a/src/types.ts +++ b/src/types.ts @@ -74,7 +74,8 @@ export interface InternalMergeOptions extends Omit, Omit readonly validate?: ValidateFunction[] | ValidateFunction @@ -228,9 +222,7 @@ export interface NormalizationRule { readonly unify?: UnifyFunction[] | UnifyFunction readonly mandatoryUnify?: UnifyFunction[] | UnifyFunction readonly resolvedReferenceNamePropertyKey?: PropertyKey - readonly hashStrategy?: InclusionStrategy - readonly hashOwner?: boolean - readonly newDataLayer?: boolean + readonly excludeFromSemanticHash?: boolean readonly deprecation?: DeprecationPolicy readonly isExtension?: boolean readonly referenceHandler?: ReferenceHandler @@ -299,4 +291,3 @@ export interface ValueWithOrigins { } export type Hash = string -export type DeferredHash = () => Hash diff --git a/src/utils.ts b/src/utils.ts index 14bfb3a..ecd3773 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,11 +1,19 @@ -import { isArray, isObject, JSON_ROOT_KEY, SyncCloneHook, SyncCrawlHook, type JsonPath } from '@netcracker/qubership-apihub-json-crawl' +import { + isArray, + isObject, + JSON_ROOT_KEY, + type JsonPath, + SyncCloneHook, + SyncCrawlHook, +} from '@netcracker/qubership-apihub-json-crawl' import { ChainItem, DEFAULT_TYPE_FLAG_PURE, DEFAULT_TYPE_FLAG_SYNTHETIC, DefaultMetaRecord, - Jso, OriginLeafs, + Jso, + OriginLeafs, PureRefNode, type RawJsonSchema, RefNode, @@ -13,6 +21,9 @@ import { } from './types' import { JSON_SCHEMA_PROPERTY_REF } from './rules/jsonschema.const' import { resolveOrigins, setOriginsForArray } from './origins' +import crypto from 'crypto' +import { ObjPath } from './hash' +import objectHash from 'object-hash' export class MapArray extends Map> { public add(key: K, value: V): this { @@ -383,4 +394,142 @@ export const removeDuplicatesWithMergeOrigins = (array: T[], originFlag: symb setOriginsForArray(uniqueItems, originFlag, itemOrigins) return uniqueItems -} \ No newline at end of file +} + +export function cryptoMd5(str: string): string{ + //Have error: crypto.createHash is not a function. crypto for Node.js, need user browserify? + //return crypto.createHash('md5').update(str).digest('hex') + // todo fix it + return objectHash.MD5(str) +} + +type HashData = string | number | boolean | null | undefined | object + +// TODO: move to separate file, add proper attribution and description of changes +export function objectToString(object: HashData, symbol: symbol, excludeKeys?: ObjPath): string { + const context: any[] = [] + let result = '' + + const write = (str: string): void => { + result += str + } + + const isNativeFunction = (f: any): boolean => { + if (typeof f !== 'function') { + return false + } + const exp = /^function\s+\w*\s*\(\s*\)\s*{\s+\[native code\]\s+}$/i + return exp.exec(Function.prototype.toString.call(f)) != null + } + + const hasher = { + dispatch(value: HashData): void { + let type: string = typeof value + if (value === null) { + type = 'null' + } + const handler = (this as any)['_' + type] + if (handler) { + handler.call(this, value) + } else { + write(`[${type}]`) + } + }, + + _object(obj: Record): void { + if (obj === null) { + return write('null') + } + + //TODO: remove circular objects handling, should not be required + // since we are only interested in shallow hash calculation and use pre-computed hash for nested objects + if (context.includes(obj)) { + return write(`[CIRCULAR:${context.indexOf(obj)}]`) + } + context.push(obj) + + const pattern = /\[object (.*)\]/i + const objType = (pattern.exec(Object.prototype.toString.call(obj)) || [])[1]?.toLowerCase() || 'object' + + // Special typed objects + if (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(obj)) { + write('buffer:') + return write(obj.toString()) + } + + if (objType !== 'object' && objType !== 'function' && objType !== 'asyncfunction') { + const handler = (this as any)['_' + objType] + if (handler) {return handler.call(this, obj)} + return write(`[${objType}]`) + } + + let keys = Object.keys(obj).sort() + if (!isNativeFunction(obj)) { + keys.unshift('prototype', '__proto__', 'constructor') + } + if (excludeKeys) { + keys = keys.filter((key: string) => !excludeKeys.includes(key)) + } + write(`object:${keys.length}:`) + for (const key of keys) { + this.dispatch(key) + write(':') + const value = obj[key] + if(isObject(value)){ + const replacedValue = obj[key]?.[symbol] + this.dispatch(replacedValue ? replacedValue : obj[key]) + }else { + this.dispatch(value) + } + write(',') + } + }, + + _array(arr: any[]): void { + write(`array:${arr.length}:`) + + // the elements of the array should already have a hash calculated, + // if it doesn't exist, it means it didn't pass according to the rules. + // todo check for looping + const entries = arr.map((entry) => entry[symbol] ?? {}) + entries.sort() + for (const e of entries) { + this.dispatch(e) + } + }, + _boolean(b: boolean): void { + write(`bool:${b}`) + }, + _string(s: string): void { + write(`string:${s.length}:${s}`) + }, + // eslint-disable-next-line @typescript-eslint/ban-types + _function(fn: Function): void { + write('fn:') + this.dispatch('[native]') + }, + _number(n: number): void { + write(`number:${n}`) + }, + _bigint(bi: bigint): void { + write(`bigint:${bi.toString()}`) + }, + _null(): void { + write('Null') + }, + _undefined(): void { + write('Undefined') + }, + _map(map: Map): void { + write('map:') + this._array(Array.from(map.entries())) + }, + _set(set: Set): void { + write('set:') + this._array(Array.from(set.values())) + }, + } + + hasher.dispatch(object) + return result +} diff --git a/test/helpers/index.ts b/test/helpers/index.ts index 477b855..dcf98be 100644 --- a/test/helpers/index.ts +++ b/test/helpers/index.ts @@ -197,7 +197,8 @@ export const TEST_SYNTHETIC_ALL_OF_FLAG = Symbol('test-synthetic-allOf') export const TEST_ORIGINS_FLAG = Symbol('test-origin') export const TEST_ORIGINS_FOR_DEFAULTS: OriginLeafs = [{ parent: undefined, value: 'test-origins-defaults' }] export const TEST_DEFAULTS_FLAG = Symbol('test-defaults') -export const TEST_HASH_FLAG = Symbol('test-hash') +export const TEST_HASH_PROPERTY = Symbol('test-hash') +export const TEST_SEMANTIC_HASH_PROPERTY = Symbol('test-semantic-hash') export const isSymbol = (value: unknown): value is symbol => { return typeof value === 'symbol' @@ -218,25 +219,33 @@ export const resolveValueByPath = (obj: unknown, path: JsonPath): unknown | unde return value } -export function resolveHashesByPath(data1: unknown, data2: unknown, path1: JsonPath, path2: JsonPath = path1): [Hash, Hash] { - const hash1 = resolveValueByPath(data1, [...path1, TEST_HASH_FLAG]) - const hash2 = resolveValueByPath(data2, [...path2, TEST_HASH_FLAG]) - if (!hash1 || !hash2 || typeof hash1 !== 'function' || typeof hash2 !== 'function') { +export function resolveHashPropertyValuesByPath(hashProperty: PropertyKey, data1: unknown, data2: unknown, path1: JsonPath, path2: JsonPath = path1 ): [Hash, Hash] { + const hash1 = resolveValueByPath(data1, [...path1, hashProperty]) as string + const hash2 = resolveValueByPath(data2, [...path2, hashProperty]) as string + if (!hash1 || !hash2) { throw new Error(`No hash found, path1: ${path1}, path2: ${path2}`) } - return [hash1(), hash2()] + return [hash1, hash2] } -export function checkHashesEqualByPath(data1: unknown, data2: unknown, path1: JsonPath, path2: JsonPath = path1) { - const [hash1, hash2] = resolveHashesByPath(data1, data2, path1, path2) +export function checkHashPropertyValuesEqualByPath(hashProperty: PropertyKey, data1: unknown, data2: unknown, path1: JsonPath, path2: JsonPath = path1 ) { + const [hash1, hash2] = resolveHashPropertyValuesByPath(hashProperty, data1, data2, path1, path2 ) expect(hash1).toEqual(hash2) } -export function checkHashesNotEqualByPath(data1: unknown, data2: unknown, path1: JsonPath, path2: JsonPath = path1) { - const [hash1, hash2] = resolveHashesByPath(data1, data2, path1, path2) +export function checkHashPropertyValuesNotEqualByPath(hashProperty: PropertyKey, data1: unknown, data2: unknown, path1: JsonPath, path2: JsonPath = path1 ) { + const [hash1, hash2] = resolveHashPropertyValuesByPath(hashProperty, data1, data2, path1, path2 ) expect(hash1).not.toEqual(hash2) } +export function checkSemanticHashesEqualByPath(data1: unknown, data2: unknown, path1: JsonPath, path2: JsonPath = path1 ) { + checkHashPropertyValuesEqualByPath(TEST_SEMANTIC_HASH_PROPERTY, data1, data2, path1, path2 ) +} + +export function checkSemanticHashesNotEqualByPath(data1: unknown, data2: unknown, path1: JsonPath, path2: JsonPath = path1 ) { + checkHashPropertyValuesNotEqualByPath(TEST_SEMANTIC_HASH_PROPERTY, data1, data2, path1, path2 ) +} + export function createSimpleOriginsMetaRecord(key: PropertyKey) { return { [key]: [{ @@ -255,8 +264,8 @@ export function countUniqueHashes(spec: unknown): number { if (!isObject(value)) { return { done: true } } - if (TEST_HASH_FLAG in value && value.type === 'object') { - const hash = value[TEST_HASH_FLAG]() + if (TEST_SEMANTIC_HASH_PROPERTY in value && value.type === 'object') { + const hash = value[TEST_SEMANTIC_HASH_PROPERTY] hashesSet.add(hash) hashesMap.set(value, hash) } diff --git a/test/oas/bugs.test.ts b/test/oas/bugs.test.ts index 7d451d1..9d5dd74 100644 --- a/test/oas/bugs.test.ts +++ b/test/oas/bugs.test.ts @@ -1,5 +1,12 @@ -import { TEST_DEFAULTS_FLAG, TEST_HASH_FLAG, TEST_SYNTHETIC_ALL_OF_FLAG } from '../helpers/index' -import { denormalize, JSON_SCHEMA_PROPERTY_DEPRECATED, normalize, OriginLeafs, resolveOrigins } from '../../src' +import { TEST_DEFAULTS_FLAG, TEST_SEMANTIC_HASH_PROPERTY, TEST_SYNTHETIC_ALL_OF_FLAG } from '../helpers/index' +import { + denormalize, + HashOptions, + JSON_SCHEMA_PROPERTY_DEPRECATED, + normalize, + OriginLeafs, + resolveOrigins, +} from '../../src' import outOfMemoryCauseTooManyCombinations from '../resources/out-of-memory-cause-too-many-combinations.json' import bugWithWrongOrigins from '../resources/bug-with-wrong-origins.json' import bugWithSpearedArray from '../resources/bug-with-speared-array.json' @@ -176,7 +183,7 @@ describe('Bugs', () => { it('make denormalize inside custom symbols', () => { const options = { defaultsFlag: TEST_DEFAULTS_FLAG, - hashFlag: TEST_HASH_FLAG, + semanticHashProperty: TEST_SEMANTIC_HASH_PROPERTY, inlineRefsFlag: TEST_INLINE_REFS_FLAG, originsFlag: TEST_ORIGINS_FLAG, syntheticTitleFlag: TEST_SYNTHETIC_TITLE_FLAG, @@ -196,14 +203,14 @@ describe('Bugs', () => { } }, options) as any expect(normalized).toHaveProperty(['components', 'schemas', 'From', TEST_INLINE_REFS_FLAG]) - expect(normalized).toHaveProperty(['components', 'schemas', 'From', TEST_HASH_FLAG]) + expect(normalized).toHaveProperty(['components', 'schemas', 'From', TEST_SEMANTIC_HASH_PROPERTY]) const customSymbol = Symbol('custom') normalized.components.schemas[customSymbol] = normalized.components.schemas.From delete normalized.components.schemas.To delete normalized.components.schemas.From const result = denormalize(normalized, options) expect(result).not.toHaveProperty(['components', 'schemas', customSymbol, TEST_INLINE_REFS_FLAG]) - expect(result).not.toHaveProperty(['components', 'schemas', customSymbol, TEST_HASH_FLAG]) + expect(result).not.toHaveProperty(['components', 'schemas', customSymbol, TEST_SEMANTIC_HASH_PROPERTY]) }) // todo: inline-refs symbol can't be in schema @@ -233,7 +240,7 @@ describe('Bugs', () => { allowNotValidSyntheticChanges: true, syntheticTitleFlag: TEST_SYNTHETIC_TITLE_FLAG, originsFlag: TEST_ORIGINS_FLAG, - hashFlag: TEST_HASH_FLAG, + semanticHashProperty: TEST_SEMANTIC_HASH_PROPERTY, inlineRefsFlag: TEST_INLINE_REFS_FLAG, }) as any expect(result.paths['customer'].get.parameters[0].schema).not.toHaveProperty([TEST_INLINE_REFS_FLAG]) @@ -247,7 +254,7 @@ describe('Bugs', () => { allowNotValidSyntheticChanges: true, syntheticTitleFlag: TEST_SYNTHETIC_TITLE_FLAG, originsFlag: TEST_ORIGINS_FLAG, - hashFlag: TEST_HASH_FLAG, + semanticHashProperty: TEST_SEMANTIC_HASH_PROPERTY, inlineRefsFlag: TEST_INLINE_REFS_FLAG, }) as any expect(result).toHaveProperty(['paths', '/datasets/events', 'get', 'parameters', 'length'], 10) /*1 and 3 index will be missing*/ diff --git a/test/oas/e2e.test.ts b/test/oas/e2e.test.ts index cf3c7bd..3e85ed1 100644 --- a/test/oas/e2e.test.ts +++ b/test/oas/e2e.test.ts @@ -7,7 +7,7 @@ import { normalize, NormalizeOptions, } from '../../src' -import { TEST_HASH_FLAG, TEST_INLINE_REFS_FLAG, TEST_ORIGINS_FLAG, TEST_SYNTHETIC_TITLE_FLAG } from '../helpers' +import { TEST_INLINE_REFS_FLAG, TEST_ORIGINS_FLAG, TEST_SEMANTIC_HASH_PROPERTY, TEST_SYNTHETIC_TITLE_FLAG } from '../helpers' describe('e2e', () => { it('resolve type after all', () => { @@ -229,7 +229,7 @@ describe('e2e', () => { liftCombiners: true, syntheticTitleFlag: TEST_SYNTHETIC_TITLE_FLAG, validate: true, - hashFlag: TEST_HASH_FLAG, + semanticHashProperty: TEST_SEMANTIC_HASH_PROPERTY, inlineRefsFlag: TEST_INLINE_REFS_FLAG } it('pure schema', () => { @@ -1099,6 +1099,6 @@ describe('e2e', () => { validate: true, unify: true, }) - expect(result).toHaveProperty(['paths', '/path1', 'post', 'responses', 200, 'content', 'application/json', 'schema', 'properties', 'id', 'xml', 'name'], 'xml-id') + expect(result).toHaveProperty(['paths', '/path1', 'post', 'responses', 200, 'content', 'application/json', 'schema', 'properties', 'id', 'xml', 'name'], 'xml-id') }) }) diff --git a/test/oas/hash.test.ts b/test/oas/hash.test.ts index 2cd79e0..d0538f9 100644 --- a/test/oas/hash.test.ts +++ b/test/oas/hash.test.ts @@ -1,11 +1,14 @@ +import { describe, expect, it } from '@jest/globals' + import { normalize, NormalizeOptions, setJsoProperty } from '../../src' import { - checkHashesEqualByPath, - checkHashesNotEqualByPath, + checkSemanticHashesEqualByPath, + checkSemanticHashesNotEqualByPath, countUniqueHashes, createOas, createOasWithParameters, - TEST_HASH_FLAG, + TEST_SEMANTIC_HASH_PROPERTY, + TEST_HASH_PROPERTY, TEST_ORIGINS_FLAG, TEST_PARAMETER_NAME, TEST_SCHEMA_NAME, @@ -14,11 +17,12 @@ import { import petstore from '../resources/petstore.json' const DEFAULT_OPTIONS: NormalizeOptions = { - hashFlag: TEST_HASH_FLAG, + semanticHashProperty: TEST_SEMANTIC_HASH_PROPERTY, + hashProperty: TEST_HASH_PROPERTY, } -describe('hash', () => { - it('title does not affect hash', () => { +describe('semantic hash', () => { + it('title does not affect semantic hash', () => { const data1 = createOas({ title: 'Some Schema 1', type: 'string', @@ -30,154 +34,154 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('type affects hash', () => { + it('type affects semantic hash', () => { const data1 = createOas({ type: 'string' }) const data2 = createOas({ type: 'number' }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('format affects hash', () => { + it('format affects semantic hash', () => { const data1 = createOas({ format: 'uri' }) const data2 = createOas({ format: 'ip' }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('default affects hash', () => { + it('default affects semantic hash', () => { const data1 = createOas({ default: 'first sample' }) const data2 = createOas({ default: 'last sample' }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('multipleOf affects hash', () => { + it('multipleOf affects semantic hash', () => { const data1 = createOas({ multipleOf: 2 }) const data2 = createOas({ multipleOf: 3 }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('maximum affects hash', () => { + it('maximum affects semantic hash', () => { const data1 = createOas({ maximum: 2 }) const data2 = createOas({ maximum: 3 }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('exclusiveMaximum affects hash', () => { + it('exclusiveMaximum affects semantic hash', () => { const data1 = createOas({ exclusiveMaximum: true }) const data2 = createOas({ exclusiveMaximum: false }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('minimum affects hash', () => { + it('minimum affects semantic hash', () => { const data1 = createOas({ minimum: 2 }) const data2 = createOas({ minimum: 3 }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('exclusiveMinimum affects hash', () => { + it('exclusiveMinimum affects semantic hash', () => { const data1 = createOas({ exclusiveMinimum: true }) const data2 = createOas({ exclusiveMinimum: false }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('maxLength affects hash', () => { + it('maxLength affects semantic hash', () => { const data1 = createOas({ maxLength: 5 }) const data2 = createOas({ maxLength: 10 }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('minLength affects hash', () => { + it('minLength affects semantic hash', () => { const data1 = createOas({ minLength: 5 }) const data2 = createOas({ minLength: 10 }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('pattern affects hash', () => { + it('pattern affects semantic hash', () => { const data1 = createOas({ pattern: 'qwe' }) const data2 = createOas({ pattern: 'asd' }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('maxItems affects hash', () => { + it('maxItems affects semantic hash', () => { const data1 = createOas({ maxItems: 10 }) const data2 = createOas({ maxItems: 5 }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('minItems affects hash', () => { + it('minItems affects semantic hash', () => { const data1 = createOas({ minItems: 10 }) const data2 = createOas({ minItems: 5 }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('uniqueItems affects hash', () => { + it('uniqueItems affects semantic hash', () => { const data1 = createOas({ uniqueItems: true }) const data2 = createOas({ uniqueItems: false }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('maxProperties affects hash', () => { + it('maxProperties affects semantic hash', () => { const data1 = createOas({ maxProperties: 10 }) const data2 = createOas({ maxProperties: 5 }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('minProperties affects hash', () => { + it('minProperties affects semantic hash', () => { const data1 = createOas({ minProperties: 10 }) const data2 = createOas({ minProperties: 5 }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('item type in items array affects hash (openapi 3.1.0)', () => { + it('item type in items array affects semantic hash (openapi 3.1.0)', () => { const data1 = createOas({ type: 'object', items: [{ @@ -194,10 +198,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('item title in items array does not affect hash (openapi 3.1.0)', () => { + it('item title in items array does not affect semantic hash (openapi 3.1.0)', () => { const data1 = createOas({ type: 'object', items: [{ @@ -216,10 +220,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('item type in items object affects hash (openapi 3.1.0)', () => { + it('item type in items object affects semantic hash (openapi 3.1.0)', () => { const data1 = createOas({ type: 'object', items: { @@ -236,10 +240,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('item title in items object does not affect hash (openapi 3.1.0)', () => { + it('item title in items object does not affect semantic hash (openapi 3.1.0)', () => { const data1 = createOas({ type: 'object', items: { @@ -258,10 +262,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('other item fields do not affect hash (openapi 3.1.0)', () => { + it('other item fields affect semantic hash (openapi 3.1.0)', () => { const data1 = createOas({ type: 'object', items: { @@ -282,10 +286,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('item type in items object affects hash (openapi 3.0.0)', () => { + it('item type in items object affects semantic hash (openapi 3.0.0)', () => { const data1 = createOas({ type: 'object', items: { @@ -302,10 +306,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('item title in items object does not affect hash (openapi 3.0.0)', () => { + it('item title in items object does not affect semantic hash (openapi 3.0.0)', () => { const data1 = createOas({ type: 'object', items: { @@ -324,10 +328,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('other item fields do not affect hash (openapi 3.0.0)', () => { + it('other item fields affect semantic hash (openapi 3.0.0)', () => { const data1 = createOas({ type: 'object', items: { @@ -348,10 +352,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('additionalItems type affects hash (openapi 3.1.0)', () => { + it('additionalItems type affects semantic hash (openapi 3.1.0)', () => { const data1 = createOas({ type: 'object', additionalItems: { @@ -368,10 +372,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('additionalItems title does not affect hash (openapi 3.1.0)', () => { + it('additionalItems title does not affect semantic hash (openapi 3.1.0)', () => { const data1 = createOas({ type: 'object', additionalItems: { @@ -390,10 +394,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('boolean additionalItems affects hash (openapi 3.1.0)', () => { + it('boolean additionalItems affects semantic hash (openapi 3.1.0)', () => { const data1 = createOas({ type: 'object', additionalItems: true, @@ -406,10 +410,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('none of the additionalItems properties affect hash (openapi 3.0.0)', () => { + it('none of the additionalItems properties affects semantic hash (openapi 3.0.0)', () => { const data1 = createOas({ type: 'object', additionalItems: { @@ -430,46 +434,46 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('required affects hash', () => { + it('required affects semantic hash', () => { const data1 = createOas({ required: ['prop2', 'prop1'] }) const data2 = createOas({ required: ['prop3'] }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('required order does not affect hash', () => { + it('required order does not affect semantic hash', () => { const data1 = createOas({ required: ['prop1', 'prop2', 'prop3'] }) const data2 = createOas({ required: ['prop3', 'prop2', 'prop1'] }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('enum affects hash', () => { + it('enum affects semantic hash', () => { const data1 = createOas({ enum: ['Husky', 'Retriever'] }) const data2 = createOas({ enum: ['Dingo'] }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('enum order does not affect hash', () => { + it('enum order does not affect semantic hash', () => { const data1 = createOas({ enum: ['Husky', 'Retriever', 'Dingo', 'Shepherd'] }) const data2 = createOas({ enum: ['Shepherd', 'Dingo', 'Retriever', 'Husky'] }) const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('property type affects hash', () => { + it('property type affects semantic hash', () => { const data1 = createOas({ type: 'object', properties: { @@ -490,10 +494,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('property title does not affect hash', () => { + it('property title does not affect semantic hash', () => { const data1 = createOas({ type: 'object', properties: { @@ -516,10 +520,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('other property fields do not affect hash', () => { + it('other property fields affect semantic hash', () => { const data1 = createOas({ type: 'object', properties: { @@ -548,11 +552,11 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME, 'properties', 'foo']) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME, 'properties', 'foo']) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('hashes in cycled specs are calculated correctly', () => { + it('semantic hashes in cycled specs are calculated correctly', () => { const data1 = createOas({ type: 'object', properties: { @@ -574,7 +578,7 @@ describe('hash', () => { cycle: { type: 'object', properties: { - foo3: { type: 'string' }, + foo2: { type: 'string' }, recursive: { $ref: `#/components/schemas/${TEST_SCHEMA_NAME}/properties/cycle`, }, @@ -585,14 +589,14 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result1, + checkSemanticHashesEqualByPath(result1, result1, ['components', 'schemas', TEST_SCHEMA_NAME, 'properties', 'cycle'], ['components', 'schemas', TEST_SCHEMA_NAME, 'properties', 'cycle', 'properties', 'recursive'], ) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('additionalProperties type affects hash', () => { + it('additionalProperties type affects semantic hash', () => { const data1 = createOas({ type: 'object', additionalProperties: { @@ -609,10 +613,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('additionalProperties title does not affect hash', () => { + it('additionalProperties title does not affect semantic hash', () => { const data1 = createOas({ type: 'object', additionalProperties: { @@ -631,10 +635,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('other additionalProperties fields do not affect hash', () => { + it('other additionalProperties fields affect semantic hash', () => { const data1 = createOas({ type: 'object', additionalProperties: { @@ -653,10 +657,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('boolean additionalProperties affects hash', () => { + it('boolean additionalProperties affects semantic hash', () => { const data1 = createOas({ type: 'object', additionalProperties: true, @@ -669,10 +673,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('patternProperties type affects hash (openapi 3.1.0)', () => { + it('patternProperties type affects semantic hash (openapi 3.1.0)', () => { const data1 = createOas({ type: 'object', patternProperties: { @@ -689,10 +693,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('patternProperties title does not affect hash (openapi 3.1.0)', () => { + it('patternProperties title does not affect semantic hash (openapi 3.1.0)', () => { const data1 = createOas({ type: 'object', patternProperties: { @@ -715,10 +719,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('none of the patternProperties properties affect hash (openapi 3.0.0)', () => { + it('none of the patternProperties properties affect semantic hash (openapi 3.0.0)', () => { const data1 = createOas({ type: 'object', patternProperties: { @@ -741,10 +745,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('oneOf order does not affect hash', () => { + it('oneOf order does not affect semantic hash', () => { const data1 = createOas({ oneOf: [ { type: 'integer' }, @@ -762,10 +766,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('oneOf title does not affect hash', () => { + it('oneOf title does not affect semantic hash', () => { const data1 = createOas({ oneOf: [ { @@ -783,10 +787,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('oneOf type affects hash', () => { + it('oneOf type affects semantic hash', () => { const data1 = createOas({ oneOf: [ { @@ -804,10 +808,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('oneOf readOnly field affects hash', () => { + it('oneOf readOnly field affects semantic hash', () => { const data1 = createOas({ oneOf: [ { @@ -825,10 +829,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('anyOf order does not affect hash', () => { + it('anyOf order does not affect semantic hash', () => { const data1 = createOas({ anyOf: [ { type: 'string' }, @@ -846,10 +850,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('allOf order does not affect hash', () => { + it('allOf order does not affect semantic hash', () => { const data1 = createOas({ allOf: [ { type: 'string' }, @@ -867,10 +871,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('allOf title does not affect hash', () => { + it('allOf title does not affect semantic hash', () => { const data1 = createOas({ allOf: [ { @@ -888,10 +892,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('allOf type affects hash', () => { + it('allOf type affects semantic hash', () => { const data1 = createOas({ allOf: [ { @@ -909,10 +913,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('allOf readOnly field affects hash', () => { + it('allOf readOnly field affects semantic hash', () => { const data1 = createOas({ allOf: [ { @@ -930,10 +934,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('not type affects hash', () => { + it('not type affects semantic hash', () => { const data1 = createOas({ type: 'object', not: { @@ -950,10 +954,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('not title does not affect hash', () => { + it('not title does not affect semantic hash', () => { const data1 = createOas({ type: 'object', not: { @@ -972,10 +976,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('definitions type affects hash', () => { + it('definitions type affects semantic hash', () => { const data1 = createOas({ type: 'object', definitions: { @@ -996,10 +1000,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('definitions title does not affect hash', () => { + it('definitions title does not affect semantic hash', () => { const data1 = createOas({ type: 'object', definitions: { @@ -1020,10 +1024,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('$ref affects hash', () => { + it('$ref affects semantic hash', () => { const data1 = createOas({ $ref: '#/components/schemas/Test1', }) @@ -1034,10 +1038,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('annotation severity fields do not affect hashes', () => { + it('annotation severity fields do not affect semantic hashes', () => { const data1 = createOas({ description: 'description 1', anyOf: [ @@ -1068,10 +1072,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('parameter name affects hash', () => { + it('parameter name affects semantic hash', () => { const data1 = createOasWithParameters({ name: 'Cookie 1', in: 'cookie', @@ -1083,10 +1087,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'parameters', TEST_PARAMETER_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'parameters', TEST_PARAMETER_NAME]) }) - it('parameter\'s "in" field affects hash', () => { + it('parameter\'s "in" field affects semantic hash', () => { const data1 = createOasWithParameters({ name: 'Cookie', in: 'cookie', @@ -1098,10 +1102,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'parameters', TEST_PARAMETER_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'parameters', TEST_PARAMETER_NAME]) }) - it('parameter\'s fields with annotation severity do not affect hashes', () => { + it('parameter\'s fields with annotation severity do not affect semantic hashes', () => { const data1 = createOasWithParameters({ name: 'Cookie', in: 'cookie', @@ -1121,10 +1125,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'parameters', TEST_PARAMETER_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'parameters', TEST_PARAMETER_NAME]) }) - it('parameter\'s schema type affects hash', () => { + it('parameter\'s schema type affects semantic hash', () => { const data1 = createOasWithParameters({ name: 'Cookie', in: 'cookie', @@ -1142,10 +1146,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesNotEqualByPath(result1, result2, ['components', 'parameters', TEST_PARAMETER_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'parameters', TEST_PARAMETER_NAME]) }) - it('parameter\'s schema title does not affect hash', () => { + it('parameter\'s schema title does not affect semantic hash', () => { const data1 = createOasWithParameters({ name: 'Cookie', in: 'cookie', @@ -1163,10 +1167,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'parameters', TEST_PARAMETER_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'parameters', TEST_PARAMETER_NAME]) }) - it('other parameter\'s schema fields do not affect hash', () => { + it('other parameter\'s schema fields affect semantic hash', () => { const data1 = createOasWithParameters({ name: 'Cookie', in: 'cookie', @@ -1188,10 +1192,10 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'parameters', TEST_PARAMETER_NAME]) + checkSemanticHashesNotEqualByPath(result1, result2, ['components', 'parameters', TEST_PARAMETER_NAME]) }) - it('symbol in array does not break hash calculation', () => { + it('symbol in array does not break semantic hash calculation', () => { const requiredArray1 = ['prop1'] const requiredArray2 = ['prop1'] setJsoProperty(requiredArray1, TEST_ORIGINS_FLAG, { type: [{ value: 'type' }] }) @@ -1201,14 +1205,14 @@ describe('hash', () => { const result1 = normalize(data1, DEFAULT_OPTIONS) const result2 = normalize(data2, DEFAULT_OPTIONS) - checkHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) + checkSemanticHashesEqualByPath(result1, result2, ['components', 'schemas', TEST_SCHEMA_NAME]) }) - it('no hash collisions', () => { + it('no semantic hash collisions', () => { const customOptions = { ...DEFAULT_OPTIONS } const result = normalize(petstore, customOptions) const uniqueHashesCount = countUniqueHashes(result) - expect(uniqueHashesCount).toEqual(14) + expect(uniqueHashesCount).toEqual(16) }) }) diff --git a/test/performance.test.ts b/test/performance.test.ts index dfd0f5a..0c676f4 100644 --- a/test/performance.test.ts +++ b/test/performance.test.ts @@ -3,7 +3,7 @@ import { } from '../src' import { readFileSync } from 'fs' import { load } from 'js-yaml' -import { buildGraphApi, TEST_HASH_FLAG, TEST_ORIGINS_FLAG, TEST_SYNTHETIC_TITLE_FLAG } from './helpers' +import { buildGraphApi, TEST_ORIGINS_FLAG, TEST_SEMANTIC_HASH_PROPERTY, TEST_SYNTHETIC_TITLE_FLAG } from './helpers' const SPECS = [ load(readFileSync(`./test/resources/performance/openapi_large_x6.yaml`).toString()), @@ -24,7 +24,7 @@ describe('performance', () => { allowNotValidSyntheticChanges: true, syntheticTitleFlag: TEST_SYNTHETIC_TITLE_FLAG, originsFlag: TEST_ORIGINS_FLAG, - hashFlag: TEST_HASH_FLAG, + semanticHashProperty: TEST_SEMANTIC_HASH_PROPERTY, }) const to = performance.now() - from }) diff --git a/test/performance.ts b/test/performance.ts index 4b05725..23c1b49 100644 --- a/test/performance.ts +++ b/test/performance.ts @@ -3,7 +3,7 @@ import { } from '../src' import { readFileSync } from 'fs' import { load } from 'js-yaml' -import { buildGraphApi, TEST_HASH_FLAG, TEST_ORIGINS_FLAG, TEST_SYNTHETIC_TITLE_FLAG } from './helpers' +import { buildGraphApi, TEST_ORIGINS_FLAG, TEST_SEMANTIC_HASH_PROPERTY, TEST_SYNTHETIC_TITLE_FLAG } from './helpers' const SPECS = [ load(readFileSync(`./test/resources/performance/openapi_large_x6.yaml`).toString()), @@ -21,7 +21,7 @@ for (let j = 0; j < 2; j++) { allowNotValidSyntheticChanges: true, syntheticTitleFlag: TEST_SYNTHETIC_TITLE_FLAG, originsFlag: TEST_ORIGINS_FLAG, - hashFlag: TEST_HASH_FLAG, + semanticHashProperty: TEST_SEMANTIC_HASH_PROPERTY, }) const to = performance.now() - from console.log(j, i, to)