diff --git a/.DS_Store b/.DS_Store index 46ed0dcba04..4d9f1524b8f 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/package-lock.json b/package-lock.json index a10a8e11221..56aa155c8c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -290,9 +290,9 @@ "dev": true }, "@types/socket.io": { - "version": "1.4.31", - "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-1.4.31.tgz", - "integrity": "sha512-HnO78rKiAx+tCEKYFurJvKLNAluN9B0V+yLnkJEY9BVwmCXHA7oKeLCymqP/uAgx0t+un/JM+GJOTwNQJ+ODvQ==", + "version": "1.4.32", + "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-1.4.32.tgz", + "integrity": "sha512-xl2fYn5eZRp1jBpv62DEtJLLGroz0ZTi7jq9ZMIGKVgNgS0SbLP36tG7TrUpZTTi+Y2CWvMgemgErZLPGi+CQA==", "dev": true, "requires": { "@types/node": "7.0.48" @@ -311,7 +311,8 @@ "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=" + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true }, "abstract-logging": { "version": "1.0.0", @@ -542,7 +543,8 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=" + "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=", + "dev": true }, "archy": { "version": "1.0.0", @@ -554,6 +556,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "dev": true, "requires": { "delegates": "1.0.0", "readable-stream": "2.3.3" @@ -664,12 +667,14 @@ "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true }, "assert-plus": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true }, "assertion-error": { "version": "1.0.2", @@ -706,7 +711,8 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true }, "atob": { "version": "2.0.3", @@ -752,12 +758,14 @@ "aws-sign2": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true }, "aws4": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true }, "axios": { "version": "0.17.1", @@ -1815,6 +1823,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, "optional": true, "requires": { "tweetnacl": "0.14.5" @@ -1840,27 +1849,12 @@ "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", "dev": true }, - "binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", - "requires": { - "buffers": "0.1.1", - "chainsaw": "0.1.0" - } - }, "binary-extensions": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", "dev": true }, - "bindings": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.0.tgz", - "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==", - "optional": true - }, "bl": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", @@ -1874,15 +1868,6 @@ "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=" }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "optional": true, - "requires": { - "inherits": "2.0.3" - } - }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", @@ -1909,6 +1894,7 @@ "version": "2.10.1", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, "requires": { "hoek": "2.16.3" } @@ -2010,24 +1996,6 @@ "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", "dev": true }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" - }, - "buffermaker": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffermaker/-/buffermaker-1.2.0.tgz", - "integrity": "sha1-u3MlLsCIK3Y56bVWuCnav8LK4bo=", - "requires": { - "long": "1.1.2" - } - }, - "buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" - }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", @@ -2155,7 +2123,8 @@ "caseless": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "dev": true }, "center-align": { "version": "0.1.3", @@ -2193,14 +2162,6 @@ "check-error": "1.0.2" } }, - "chainsaw": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", - "requires": { - "traverse": "0.3.9" - } - }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -2505,7 +2466,8 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true }, "collection-visit": { "version": "1.0.0", @@ -2560,6 +2522,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "dev": true, "requires": { "delayed-stream": "1.0.0" } @@ -2803,7 +2766,8 @@ "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true }, "content-disposition": { "version": "0.5.2", @@ -3386,6 +3350,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, "requires": { "boom": "2.10.1" } @@ -3473,6 +3438,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, "requires": { "assert-plus": "1.0.0" }, @@ -3480,7 +3446,8 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true } } }, @@ -3696,12 +3663,14 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true }, "depd": { "version": "1.1.1", @@ -3886,6 +3855,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, "optional": true, "requires": { "jsbn": "0.1.1" @@ -3925,32 +3895,23 @@ } }, "engine.io": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.4.tgz", - "integrity": "sha1-PQIRtwpVLOhB/8fahiezAamkFi4=", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.5.tgz", + "integrity": "sha512-D06ivJkYxyRrcEe0bTpNnBQNgP9d3xog+qZlLbui8EsMr/DouQpf5o9FzJnWYHEYE0YsFHllUv2R1dkgYZXHcA==", "requires": { - "accepts": "1.3.3", + "accepts": "1.3.4", "base64id": "1.0.0", "cookie": "0.3.1", - "debug": "2.6.9", + "debug": "3.1.0", "engine.io-parser": "2.1.1", - "uws": "0.14.5", + "uws": "9.14.0", "ws": "3.3.2" }, "dependencies": { - "accepts": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", - "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", - "requires": { - "mime-types": "2.1.17", - "negotiator": "0.6.1" - } - }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { "ms": "2.0.0" } @@ -4487,7 +4448,8 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true }, "fancy-log": { "version": "1.3.0", @@ -4860,12 +4822,14 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true }, "form-data": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true, "requires": { "asynckit": "0.4.0", "combined-stream": "1.0.5", @@ -5957,21 +5921,11 @@ } } }, - "fstream": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.2" - } - }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, "requires": { "aproba": "1.2.0", "console-control-strings": "1.1.0", @@ -5987,6 +5941,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "requires": { "number-is-nan": "1.0.1" } @@ -5995,6 +5950,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -6015,12 +5971,14 @@ "generate-function": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=" + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true }, "generate-object-property": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, "requires": { "is-property": "1.0.2" } @@ -6071,6 +6029,7 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, "requires": { "assert-plus": "1.0.0" }, @@ -6078,7 +6037,8 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true } } }, @@ -7739,6 +7699,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "dev": true, "requires": { "chalk": "1.1.3", "commander": "2.12.1", @@ -7749,7 +7710,8 @@ "commander": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.1.tgz", - "integrity": "sha512-PCNLExLlI5HiPdaJs4pMXwOTHkSCpNQ1QJH9ykZLKtKEyKu3p9HgmH5l97vM8c0IUz6d54l+xEu2GG9yuYrFzA==" + "integrity": "sha512-PCNLExLlI5HiPdaJs4pMXwOTHkSCpNQ1QJH9ykZLKtKEyKu3p9HgmH5l97vM8c0IUz6d54l+xEu2GG9yuYrFzA==", + "dev": true } } }, @@ -7797,7 +7759,8 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true }, "has-value": { "version": "1.0.0", @@ -7835,6 +7798,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, "requires": { "boom": "2.10.1", "cryptiles": "2.0.5", @@ -7980,7 +7944,8 @@ "hoek": { "version": "2.16.3", "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true }, "home-or-tmp": { "version": "1.0.0", @@ -8021,6 +7986,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, "requires": { "assert-plus": "0.2.0", "jsprim": "1.4.1", @@ -8310,6 +8276,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz", "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==", + "dev": true, "requires": { "generate-function": "2.0.0", "generate-object-property": "1.2.0", @@ -8403,7 +8370,8 @@ "is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true }, "is-redirect": { "version": "1.0.0", @@ -8449,7 +8417,8 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true }, "is-unc-path": { "version": "0.1.2", @@ -8486,7 +8455,8 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "isobject": { "version": "3.0.1", @@ -8497,7 +8467,8 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true }, "istanbul": { "version": "0.4.5", @@ -8575,6 +8546,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, "optional": true }, "jscodeshift": { @@ -8699,7 +8671,8 @@ "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true }, "json-schema-traverse": { "version": "0.3.1", @@ -8722,7 +8695,8 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true }, "json3": { "version": "3.3.2", @@ -8758,12 +8732,14 @@ "jsonpointer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -8774,46 +8750,8 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, - "kafka-node": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/kafka-node/-/kafka-node-2.4.1.tgz", - "integrity": "sha512-9J5uutin1HKiS0oJo/ScIVC/i+jn24ivYArV2EOerfJX8oRQ/sdgPaQMCOtfluuxf7gy2ExIh5Dyk703SWtXUQ==", - "requires": { - "async": "2.6.0", - "binary": "0.3.0", - "bl": "1.2.1", - "buffer-crc32": "0.2.13", - "buffermaker": "1.2.0", - "debug": "2.6.7", - "lodash": "4.17.4", - "minimatch": "3.0.4", - "nested-error-stacks": "2.0.0", - "node-zookeeper-client": "0.2.2", - "optional": "0.1.4", - "retry": "0.10.1", - "snappy": "6.0.1", - "uuid": "3.1.0" - }, - "dependencies": { - "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "requires": { - "lodash": "4.17.4" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "1.1.8" - } + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true } } }, @@ -9446,11 +9384,6 @@ "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", "dev": true }, - "long": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/long/-/long-1.1.2.tgz", - "integrity": "sha1-6u9ZUcp1UdlpJrgtokLbnWso+1M=" - }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", @@ -10025,14 +9958,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, - "nested-error-stacks": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.0.0.tgz", - "integrity": "sha1-mLL/rvtGEPo5NvHnFDXTBwDeKEA=", - "requires": { - "inherits": "2.0.3" - } - }, "next-tick": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-0.2.2.tgz", @@ -10052,73 +9977,6 @@ "is-stream": "1.1.0" } }, - "node-gyp": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz", - "integrity": "sha1-m/vlRWIoYoSDjnUOrAUpWFP6HGA=", - "optional": true, - "requires": { - "fstream": "1.0.11", - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "npmlog": "4.1.2", - "osenv": "0.1.5", - "request": "2.79.0", - "rimraf": "2.6.2", - "semver": "5.3.0", - "tar": "2.2.1", - "which": "1.3.0" - }, - "dependencies": { - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "optional": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.3.3", - "path-is-absolute": "1.0.1" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "1.1.8" - } - }, - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", - "optional": true - } - } - }, - "node-zookeeper-client": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/node-zookeeper-client/-/node-zookeeper-client-0.2.2.tgz", - "integrity": "sha1-CXvaAZme749gLOBotjJgAGnb9oU=", - "requires": { - "async": "0.2.10", - "underscore": "1.4.4" - }, - "dependencies": { - "async": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", - "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=" - } - } - }, "nodemon": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.12.1.tgz", @@ -10197,6 +10055,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, "requires": { "abbrev": "1.0.9" } @@ -10234,6 +10093,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", + "dev": true, "requires": { "are-we-there-yet": "1.1.4", "console-control-strings": "1.1.0", @@ -11826,7 +11686,8 @@ "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true }, "object-assign": { "version": "4.1.1", @@ -12087,16 +11948,6 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "optional": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, "output-file-sync": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz", @@ -12615,7 +12466,8 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true }, "q": { "version": "1.5.1", @@ -13095,6 +12947,7 @@ "version": "2.79.0", "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", + "dev": true, "requires": { "aws-sign2": "0.6.0", "aws4": "1.6.0", @@ -13121,7 +12974,8 @@ "qs": { "version": "6.3.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", - "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=" + "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=", + "dev": true } } }, @@ -13170,11 +13024,6 @@ "signal-exit": "3.0.2" } }, - "retry": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -13192,6 +13041,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", + "dev": true, "requires": { "glob": "7.1.2" }, @@ -13200,6 +13050,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "dev": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -13213,6 +13064,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "dev": true, "requires": { "brace-expansion": "1.1.8" } @@ -13398,7 +13250,8 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "set-getter": { "version": "0.1.0", @@ -13597,21 +13450,11 @@ } } }, - "snappy": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/snappy/-/snappy-6.0.1.tgz", - "integrity": "sha512-wrbLPjpDgDOA/VTQk/okf/qRhnWLueejiiZYMhvM9zK8NzPyLD14hIoItXya4q76u58OuUGduANks6DS8jOaJg==", - "optional": true, - "requires": { - "bindings": "1.3.0", - "nan": "2.8.0", - "node-gyp": "3.6.2" - } - }, "sntp": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, "requires": { "hoek": "2.16.3" } @@ -13622,10 +13465,10 @@ "integrity": "sha1-waRZDO/4fs8TxyZS8Eb3FrKeYBQ=", "requires": { "debug": "2.6.7", - "engine.io": "3.1.4", + "engine.io": "3.1.5", "socket.io-adapter": "1.1.1", "socket.io-client": "2.0.4", - "socket.io-parser": "3.1.2" + "socket.io-parser": "3.1.3" } }, "socket.io-adapter": { @@ -13649,19 +13492,29 @@ "object-component": "0.0.3", "parseqs": "0.0.5", "parseuri": "0.0.5", - "socket.io-parser": "3.1.2", + "socket.io-parser": "3.1.3", "to-array": "0.1.4" } }, "socket.io-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.2.tgz", - "integrity": "sha1-28IoIVH8T6675Aru3Ady66YZ9/I=", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.3.tgz", + "integrity": "sha512-g0a2HPqLguqAczs3dMECuA1RgoGFPyvDqcbaDEdCWY9g59kdUAz3YRmaJBNKXflrHNwB7Q12Gkf/0CZXfdHR7g==", "requires": { "component-emitter": "1.2.1", - "debug": "2.6.7", + "debug": "3.1.0", "has-binary2": "1.0.2", "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } } }, "sort-keys": { @@ -13794,6 +13647,7 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dev": true, "requires": { "asn1": "0.2.3", "assert-plus": "1.0.0", @@ -13808,7 +13662,8 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true } } }, @@ -13957,7 +13812,8 @@ "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true }, "strip-ansi": { "version": "3.0.1", @@ -14127,17 +13983,6 @@ "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=", "dev": true }, - "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", - "optional": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, "temp": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", @@ -14437,15 +14282,11 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "dev": true, "requires": { "punycode": "1.4.1" } }, - "traverse": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", - "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" - }, "tree-kill": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz", @@ -14572,12 +14413,14 @@ "tunnel-agent": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=" + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, "optional": true }, "type-check": { @@ -14650,11 +14493,6 @@ "integrity": "sha1-7Mo6A+VrmvFzhbqsgSrIO5lKli8=", "dev": true }, - "underscore": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", - "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" - }, "union-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", @@ -14885,12 +14723,13 @@ "uuid": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "dev": true }, "uws": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/uws/-/uws-0.14.5.tgz", - "integrity": "sha1-Z6rzPEaypYel9mZtAPdpEyjxSdw=", + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/uws/-/uws-9.14.0.tgz", + "integrity": "sha512-HNMztPP5A1sKuVFmdZ6BPVpBQd5bUjNC8EFMFiICK+oho/OQsAJy5hnIx4btMHiOk8j04f/DbIlqnEZ9d72dqg==", "optional": true }, "v8flags": { @@ -14932,6 +14771,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, "requires": { "assert-plus": "1.0.0", "core-util-is": "1.0.2", @@ -14941,7 +14781,8 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true } } }, @@ -15114,6 +14955,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, "requires": { "isexe": "2.0.0" } @@ -15128,6 +14970,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "integrity": "sha1-Vx4PGwYEY268DfwhsDObvjE0FxA=", + "dev": true, "requires": { "string-width": "1.0.2" }, @@ -15136,6 +14979,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "requires": { "number-is-nan": "1.0.1" } @@ -15144,6 +14988,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", diff --git a/package.json b/package.json index e1aafed2bc8..8ce49c230d9 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "reflect-metadata": "^0.1.10", "rxjs": "^5.5.6", "rxjs-grpc": "^0.1.6", - "socket.io": "^2.0.3", + "socket.io": "^2.0.4", "trouter": "^1.0.0" }, "devDependencies": { @@ -69,7 +69,7 @@ "@types/redis": "^0.12.36", "@types/reflect-metadata": "0.0.5", "@types/sinon": "^1.16.36", - "@types/socket.io": "^1.4.29", + "@types/socket.io": "^1.4.32", "awesome-typescript-loader": "^3.0.0-beta.18", "body-parser": "^1.17.2", "chai": "^3.5.0", diff --git a/src/common/interfaces/websockets/web-socket-adapter.interface.ts b/src/common/interfaces/websockets/web-socket-adapter.interface.ts index eab43f957d2..088c3bb5fb4 100644 --- a/src/common/interfaces/websockets/web-socket-adapter.interface.ts +++ b/src/common/interfaces/websockets/web-socket-adapter.interface.ts @@ -1,17 +1,15 @@ import { Observable } from 'rxjs/Observable'; export interface WebSocketAdapter { - create(port: number); - createWithNamespace?(port: number, namespace: string, server?); + create(port: number, options?: any & { namespace?: string; server?: any }); bindClientConnect(server, callback: (...args) => void); bindClientDisconnect?(client, callback: (...args) => void); bindMessageHandlers( client, handler: { - message: string; - callback: (...args) => Observable | Promise | void; + message: any; + callback: (...args) => Observable | Promise | any; }[], process: (data) => Observable, ); - bindMiddleware?(server, middleware: (socket, next) => void); } diff --git a/src/common/test/interceptors/files.interceptor.spec.ts b/src/common/test/interceptors/files.interceptor.spec.ts index 44e1bd2d2f2..57fb4da1ecc 100644 --- a/src/common/test/interceptors/files.interceptor.spec.ts +++ b/src/common/test/interceptors/files.interceptor.spec.ts @@ -29,5 +29,17 @@ describe('FilesInterceptor', () => { expect(arraySpy.called).to.be.true; expect(arraySpy.calledWith(fieldName, maxCount)).to.be.true; }); + it('should transform exception', async () => { + const fieldName = 'file'; + const target = new (FilesInterceptor(fieldName)); + const err = {}; + const callback = (req, res, next) => next(err); + + (target as any).upload = { + array: () => callback, + }; + const req = {}; + expect(target.intercept(req, null, stream$)).to.eventually.throw(); + }); }); }); diff --git a/src/core/errors/exceptions/circular-dependency.exception.ts b/src/core/errors/exceptions/circular-dependency.exception.ts new file mode 100644 index 00000000000..f158a06a9e0 --- /dev/null +++ b/src/core/errors/exceptions/circular-dependency.exception.ts @@ -0,0 +1,9 @@ +import { RuntimeException } from './runtime.exception'; + +export class CircularDependencyException extends RuntimeException { + constructor(context: string) { + super( + `A circular dependency has been detected inside ${context}. Please, make sure that each side of a bidirectional relationships are decorated with "forwardRef()".`, + ); + } +} diff --git a/src/core/scanner.ts b/src/core/scanner.ts index 6285da5732b..9f6800e9fdb 100644 --- a/src/core/scanner.ts +++ b/src/core/scanner.ts @@ -13,8 +13,9 @@ import { Type } from '@nestjs/common/interfaces/type.interface'; import { MetadataScanner } from '../core/metadata-scanner'; import { DynamicModule } from '@nestjs/common'; import { ApplicationConfig } from './application-config'; -import { isNil } from '@nestjs/common/utils/shared.utils'; +import { isNil, isUndefined } from '@nestjs/common/utils/shared.utils'; import { APP_INTERCEPTOR, APP_PIPE, APP_GUARD, APP_FILTER } from './constants'; +import { CircularDependencyException } from './errors/exceptions/circular-dependency.exception'; interface ApplicationProviderWrapper { moduleToken: string; @@ -58,14 +59,14 @@ export class DependenciesScanner { const modules = this.container.getModules(); modules.forEach(({ metatype }, token) => { - this.reflectRelatedModules(metatype, token); + this.reflectRelatedModules(metatype, token, metatype.name); this.reflectComponents(metatype, token); this.reflectControllers(metatype, token); this.reflectExports(metatype, token); }); } - public reflectRelatedModules(module: Type, token: string) { + public reflectRelatedModules(module: Type, token: string, context: string) { const modules = [ ...this.reflectMetadata(module, metadata.MODULES), ...this.container.getDynamicMetadataByToken( @@ -77,7 +78,7 @@ export class DependenciesScanner { metadata.IMPORTS as 'imports', ), ]; - modules.map(related => this.storeRelatedModule(related, token)); + modules.map(related => this.storeRelatedModule(related, token, context)); } public reflectComponents(module: Type, token: string) { @@ -189,8 +190,11 @@ export class DependenciesScanner { return descriptor ? Reflect.getMetadata(key, descriptor.value) : undefined; } - public storeRelatedModule(related: any, token: string) { - if (related.forwardRef) { + public storeRelatedModule(related: any, token: string, context: string) { + if (isUndefined(related)) { + throw new CircularDependencyException(context); + } + if (related && related.forwardRef) { return this.container.addRelatedModule(related.forwardRef(), token); } this.container.addRelatedModule(related, token); diff --git a/src/core/test/scanner.spec.ts b/src/core/test/scanner.spec.ts index 031ab97f0b1..3346ceb2f74 100644 --- a/src/core/test/scanner.spec.ts +++ b/src/core/test/scanner.spec.ts @@ -156,7 +156,7 @@ describe('DependenciesScanner', () => { const module = { forwardRef: sinon.stub().returns({}) }; sinon.stub(container, 'addRelatedModule').returns({}); - scanner.storeRelatedModule(module as any, [] as any); + scanner.storeRelatedModule(module as any, [] as any, 'test'); expect(module.forwardRef.called).to.be.true; }); }); diff --git a/src/microservices/.DS_Store b/src/microservices/.DS_Store new file mode 100644 index 00000000000..a2eb533abf5 Binary files /dev/null and b/src/microservices/.DS_Store differ diff --git a/src/microservices/client/client-grpc.ts b/src/microservices/client/client-grpc.ts index 57ba8e69f3e..b429605d4b7 100644 --- a/src/microservices/client/client-grpc.ts +++ b/src/microservices/client/client-grpc.ts @@ -8,6 +8,7 @@ import { ClientGrpc } from './../interfaces'; import { Observable } from 'rxjs/Observable'; import { InvalidGrpcServiceException } from '../exceptions/invalid-grpc-service.exception'; import { InvalidGrpcPackageException } from '../exceptions/invalid-grpc-package.exception'; +import { InvalidProtoDefinitionException } from '../exceptions/invalid-proto-definition.exception'; export interface GrpcService { [name: string]: (...args: any[]) => Observable; @@ -84,9 +85,7 @@ export class ClientGrpcProxy extends ClientProxy implements ClientGrpc { } public createClient(): any { - const grpcContext = grpc.load( - this.getOptionsProp(this.options, 'protoPath'), - ); + const grpcContext = this.loadProto(); const packageName = this.getOptionsProp( this.options, 'package', @@ -98,6 +97,18 @@ export class ClientGrpcProxy extends ClientProxy implements ClientGrpc { return grpcPackage; } + public loadProto(): grpc.GrpcObject { + try { + const context = grpc.load( + this.getOptionsProp(this.options, 'protoPath'), + ); + return context; + } + catch (e) { + throw new InvalidProtoDefinitionException(); + } + } + public lookupPackage(root: any, packageName: string) { /** Reference: https://github.com/kondi/rxjs-grpc */ let pkg = root; diff --git a/src/microservices/client/client-mqtt.ts b/src/microservices/client/client-mqtt.ts index 1b8a51a250e..1a25f380827 100644 --- a/src/microservices/client/client-mqtt.ts +++ b/src/microservices/client/client-mqtt.ts @@ -23,7 +23,7 @@ export class ClientMqtt extends ClientProxy { this.getOptionsProp(this.options, 'url') || MQTT_DEFAULT_URL; } - protected async publish( + protected publish( partialPacket: ReadPacket, callback: (packet: WritePacket) => any, ) { diff --git a/src/microservices/client/client-nats.ts b/src/microservices/client/client-nats.ts index 1b7fc7624cb..88fab077cf5 100644 --- a/src/microservices/client/client-nats.ts +++ b/src/microservices/client/client-nats.ts @@ -3,8 +3,12 @@ import { ClientProxy } from './client-proxy'; import { Logger } from '@nestjs/common/services/logger.service'; import { ClientOptions } from '../interfaces/client-metadata.interface'; import { NATS_DEFAULT_URL, ERROR_EVENT, CONNECT_EVENT } from './../constants'; -import { WritePacket, NatsOptions } from './../interfaces'; -import { ReadPacket, PacketId } from 'src/microservices'; +import { + WritePacket, + NatsOptions, + ReadPacket, + PacketId, +} from './../interfaces'; export class ClientNats extends ClientProxy { private readonly logger = new Logger(ClientProxy.name); @@ -28,28 +32,30 @@ export class ClientNats extends ClientProxy { const pattern = JSON.stringify(partialPacket.pattern); const responseChannel = this.getResPatternName(pattern); - const subscriptionId = this.natsClient.subscribe( - responseChannel, - (message: WritePacket & PacketId) => { - if (message.id !== packet.id) { - return void 0; - } - const { err, response, isDisposed } = message; - if (isDisposed || err) { - callback({ - err, - response: null, - isDisposed: true, - }); - return this.natsClient.unsubscribe(subscriptionId); - } + const subscriptionHandler = (message: WritePacket & PacketId) => { + if (message.id !== packet.id) { + return void 0; + } + const { err, response, isDisposed } = message; + if (isDisposed || err) { callback({ err, - response, + response: null, + isDisposed: true, }); - }, + return this.natsClient.unsubscribe(subscriptionId); + } + callback({ + err, + response, + }); + }; + const subscriptionId = this.natsClient.subscribe( + responseChannel, + subscriptionHandler, ); this.natsClient.publish(this.getAckPatternName(pattern), packet as any); + return subscriptionHandler; } public getAckPatternName(pattern: string): string { diff --git a/src/microservices/client/client-redis.ts b/src/microservices/client/client-redis.ts index f8cd3a40742..a6c3249190e 100644 --- a/src/microservices/client/client-redis.ts +++ b/src/microservices/client/client-redis.ts @@ -25,7 +25,8 @@ export class ClientRedis extends ClientProxy { constructor(private readonly options: ClientOptions) { super(); - this.url = this.getOptionsProp(options, 'url') || REDIS_DEFAULT_URL; + this.url = + this.getOptionsProp(options, 'url') || REDIS_DEFAULT_URL; } protected async publish( @@ -62,12 +63,16 @@ export class ClientRedis extends ClientProxy { }; this.subClient.on(MESSAGE_EVENT, responseCallback); this.subClient.subscribe(responseChannel); - await new Promise(resolve => - this.subClient.on( - SUBSCRIBE, - channel => channel === responseChannel && resolve(), - ), - ); + await new Promise(resolve => { + const handler = channel => { + if (channel && channel !== responseChannel) { + return void 0; + } + this.subClient.removeListener(SUBSCRIBE, handler); + resolve(); + }; + this.subClient.on(SUBSCRIBE, handler); + }); this.pubClient.publish( this.getAckPatternName(pattern), diff --git a/src/microservices/client/client-tcp.ts b/src/microservices/client/client-tcp.ts index 55959c5032c..ed83f93c85d 100644 --- a/src/microservices/client/client-tcp.ts +++ b/src/microservices/client/client-tcp.ts @@ -14,8 +14,7 @@ import { ERROR_EVENT, CLOSE_EVENT, } from './../constants'; -import { WritePacket } from './../interfaces'; -import { ReadPacket, PacketId } from 'src/microservices'; +import { WritePacket, ReadPacket, PacketId } from './../interfaces'; export class ClientTCP extends ClientProxy { private readonly logger = new Logger(ClientTCP.name); diff --git a/src/microservices/context/rpc-context-creator.ts b/src/microservices/context/rpc-context-creator.ts index 0e704ea994d..7f5e08e491c 100644 --- a/src/microservices/context/rpc-context-creator.ts +++ b/src/microservices/context/rpc-context-creator.ts @@ -29,7 +29,7 @@ export class RpcContextCreator { instance: Controller, callback: (data) => Observable, module, - ): (data) => Promise> { + ): (...args) => Promise> { const exceptionHandler = this.exceptionFiltersContext.create( instance, callback, diff --git a/src/microservices/exceptions/invalid-proto-definition.exception.ts b/src/microservices/exceptions/invalid-proto-definition.exception.ts new file mode 100644 index 00000000000..eca98cc495f --- /dev/null +++ b/src/microservices/exceptions/invalid-proto-definition.exception.ts @@ -0,0 +1,7 @@ +import { RuntimeException } from '@nestjs/core/errors/exceptions/runtime.exception'; + +export class InvalidProtoDefinitionException extends RuntimeException { + constructor() { + super('Invalid .proto definition (file not found?)'); + } +} diff --git a/src/microservices/server/server-grpc.ts b/src/microservices/server/server-grpc.ts index eb8df346d4f..e4e59ecf4c6 100644 --- a/src/microservices/server/server-grpc.ts +++ b/src/microservices/server/server-grpc.ts @@ -26,12 +26,12 @@ export class ServerGrpc extends Server implements CustomTransportStrategy { } public async start(callback?: () => void) { - await this.bindEvents(this.grpcClient); + await this.bindEvents(); this.grpcClient.start(); callback(); } - public async bindEvents(client: any) { + public async bindEvents() { const grpcContext = grpc.load( this.getOptionsProp(this.options, 'protoPath'), ); diff --git a/src/microservices/server/server-tcp.ts b/src/microservices/server/server-tcp.ts index 850df5f2413..1ac8b13d4ae 100644 --- a/src/microservices/server/server-tcp.ts +++ b/src/microservices/server/server-tcp.ts @@ -21,10 +21,10 @@ export class ServerTCP extends Server implements CustomTransportStrategy { private isExplicitlyTerminated = false; private retryAttemptsCount = 0; - constructor(private readonly config: MicroserviceOptions) { + constructor(private readonly options: MicroserviceOptions) { super(); this.port = - this.getOptionsProp(config, 'port') || + this.getOptionsProp(options, 'port') || TCP_DEFAULT_PORT; this.init(); } @@ -71,12 +71,12 @@ export class ServerTCP extends Server implements CustomTransportStrategy { if ( this.isExplicitlyTerminated || !this.getOptionsProp( - this.config, + this.options, 'retryAttempts', ) || this.retryAttemptsCount >= this.getOptionsProp( - this.config, + this.options, 'retryAttempts', ) ) { @@ -86,7 +86,7 @@ export class ServerTCP extends Server implements CustomTransportStrategy { return setTimeout( () => this.server.listen(this.port), this.getOptionsProp( - this.config, + this.options, 'retryDelay', ) || 0, ); diff --git a/src/microservices/test/client/client-grpc.spec.ts b/src/microservices/test/client/client-grpc.spec.ts new file mode 100644 index 00000000000..af142520d1f --- /dev/null +++ b/src/microservices/test/client/client-grpc.spec.ts @@ -0,0 +1,136 @@ +import * as sinon from 'sinon'; +import { expect } from 'chai'; +import { ClientGrpcProxy } from '../../client/client-grpc'; +import { join } from 'path'; +import { InvalidGrpcServiceException } from '../../exceptions/invalid-grpc-service.exception'; +import { Observable } from 'rxjs/Observable'; +import { InvalidGrpcPackageException } from '../../exceptions/invalid-grpc-package.exception'; + +class GrpcService { + test = null; +} + +describe('ClientGrpcProxy', () => { + let client: ClientGrpcProxy; + + beforeEach(() => { + client = new ClientGrpcProxy({ + options: { + protoPath: join(__dirname, './test.proto'), + package: 'test', + } + }); + }); + + describe('getService', () => { + describe('when "grpcClient[name]" is nil', () => { + it('should throw "InvalidGrpcServiceException"', () => { + (client as any).grpcClient = {}; + expect(() => client.getService('test')).to.throw(InvalidGrpcServiceException); + }); + }); + describe('when "grpcClient[name]" is not nil', () => { + it('should create grpcService', () => { + (client as any).grpcClient = { + 'test': GrpcService, + }; + expect(() => client.getService('test')).to.not.throw(InvalidGrpcServiceException); + }); + }); + }); + + describe('createServiceMethod', () => { + const methodName = 'test'; + describe('when method is a response stream', () => { + it('should call "createStreamServiceMethod"', () => { + const cln = { [methodName]: { responseStream: true } }; + const spy = sinon.spy(client, 'createStreamServiceMethod'); + client.createServiceMethod(cln, methodName); + + expect(spy.called).to.be.true; + }); + }); + describe('when method is not a response stream', () => { + it('should call "createUnaryServiceMethod"', () => { + const cln = { [methodName]: { responseStream: false } }; + const spy = sinon.spy(client, 'createUnaryServiceMethod'); + client.createServiceMethod(cln, methodName); + + expect(spy.called).to.be.true; + }); + }); + }); + + describe('createStreamServiceMethod', () => { + it('should return observable', () => { + const fn = client.createStreamServiceMethod({}, 'method'); + expect(fn()).to.be.instanceof(Observable); + }); + describe('on subscribe', () => { + const methodName = 'm'; + const obj = { [methodName]: () => ({ on: (type, fn) => fn() }) }; + + let stream$: Observable; + + beforeEach(() => { + stream$ = client.createStreamServiceMethod(obj, methodName)(); + }); + + it('should call native method', () => { + const spy = sinon.spy(obj, methodName); + stream$.subscribe(() => ({}), () => ({})); + + expect(spy.called).to.be.true; + }); + }); + }); + + describe('createUnaryServiceMethod', () => { + it('should return observable', () => { + const fn = client.createUnaryServiceMethod({}, 'method'); + expect(fn()).to.be.instanceof(Observable); + }); + describe('on subscribe', () => { + const methodName = 'm'; + const obj = { [methodName]: (callback) => callback(null, {}) }; + + let stream$: Observable; + + beforeEach(() => { + stream$ = client.createUnaryServiceMethod(obj, methodName)(); + }); + + it('should call native method', () => { + const spy = sinon.spy(obj, methodName); + stream$.subscribe(() => ({}), () => ({})); + + expect(spy.called).to.be.true; + }); + }); + }); + + describe('createClient', () => { + describe('when package does not exist', () => { + it('should throw "InvalidGrpcPackageException"', () => { + sinon.stub(client, 'lookupPackage').callsFake(() => null); + expect(() => client.createClient()).to.throw(InvalidGrpcPackageException); + }); + }); + }); + + describe('close', () => { + it('should call "close" method', () => { + const grpcClient = { close: sinon.spy() }; + (client as any).grpcClient = grpcClient; + + client.close(); + expect(grpcClient.close.called).to.be.true; + }); + }); + + describe('publish', () => { + it('should throw exception', () => { + expect(client['publish'](null, null)).to.eventually.throws(Error); + }); + }); +}); diff --git a/src/microservices/test/client/client-mqtt.spec.ts b/src/microservices/test/client/client-mqtt.spec.ts new file mode 100644 index 00000000000..81cf118139a --- /dev/null +++ b/src/microservices/test/client/client-mqtt.spec.ts @@ -0,0 +1,238 @@ +import * as sinon from 'sinon'; +import { expect } from 'chai'; +import { ClientMqtt } from '../../client/client-mqtt'; +import { ERROR_EVENT, CONNECT_EVENT, MESSAGE_EVENT } from '../../constants'; + +describe('ClientMqtt', () => { + const test = 'test'; + const client = new ClientMqtt({}); + + describe('getAckPatternName', () => { + it(`should append _ack to string`, () => { + const expectedResult = test + '_ack'; + expect(client.getAckPatternName(test)).to.equal(expectedResult); + }); + }); + describe('getResPatternName', () => { + it(`should append _res to string`, () => { + const expectedResult = test + '_res'; + expect(client.getResPatternName(test)).to.equal(expectedResult); + }); + }); + describe('publish', () => { + const pattern = 'test'; + const msg = { pattern, data: 'data' }; + let subscribeSpy: sinon.SinonSpy, + publishSpy: sinon.SinonSpy, + onSpy: sinon.SinonSpy, + removeListenerSpy: sinon.SinonSpy, + unsubscribeSpy: sinon.SinonSpy, + initSpy: sinon.SinonSpy, + mqttClient; + + beforeEach(() => { + subscribeSpy = sinon.spy(); + publishSpy = sinon.spy(); + onSpy = sinon.spy(); + removeListenerSpy = sinon.spy(); + unsubscribeSpy = sinon.spy(); + + mqttClient = { + subscribe: subscribeSpy, + on: (type, handler) => (type === 'subscribe' ? handler() : onSpy()), + removeListener: removeListenerSpy, + unsubscribe: unsubscribeSpy, + publish: publishSpy, + addListener: () => ({}), + }; + (client as any).mqttClient = mqttClient; + initSpy = sinon.spy(client, 'init'); + }); + afterEach(() => { + initSpy.restore(); + }); + it('should not call "init()" when mqtt client is not null', () => { + client['publish'](msg, () => {}); + expect(initSpy.called).to.be.false; + }); + it('should call "init()" when mqtt client is null', () => { + (client as any).mqttClient = null; + client['publish'](msg, () => {}); + expect(initSpy.called).to.be.true; + }); + it('should subscribe to response pattern name', () => { + client['publish'](msg, () => {}); + expect(subscribeSpy.calledWith(`"${pattern}"_res`)).to.be.true; + }); + it('should publish stringified message to acknowledge pattern name', async () => { + await client['publish'](msg, () => {}); + expect(publishSpy.calledWith(`"${pattern}"_ack`, JSON.stringify(msg))).to + .be.true; + }); + it('should listen on messages', () => { + client['publish'](msg, () => {}); + expect(onSpy.called).to.be.true; + }); + describe('responseCallback', () => { + let callback: sinon.SinonSpy, subscription, assignStub: sinon.SinonStub; + const responseMessage = { + err: null, + response: 'test', + id: '1', + }; + + describe('not disposed', () => { + beforeEach(async () => { + callback = sinon.spy(); + assignStub = sinon + .stub(client, 'assignPacketId') + .callsFake(packet => + Object.assign(packet, { id: responseMessage.id }), + ); + subscription = await client['publish'](msg, callback); + subscription(null, new Buffer(JSON.stringify(responseMessage))); + }); + afterEach(() => { + assignStub.restore(); + }); + it('should call callback with expected arguments', () => { + expect( + callback.calledWith({ + err: null, + response: responseMessage.response, + }), + ).to.be.true; + }); + it('should not unsubscribe to response pattern name', () => { + expect(unsubscribeSpy.calledWith(`"${pattern}"_res`)).to.be.false; + }); + it('should not remove listener', () => { + expect(removeListenerSpy.called).to.be.false; + }); + }); + describe('disposed and "id" is correct', () => { + let assignStub: sinon.SinonStub; + + const channel = 'channel'; + const id = '1'; + + beforeEach(async () => { + callback = sinon.spy(); + assignStub = sinon + .stub(client, 'assignPacketId') + .callsFake(packet => + Object.assign(packet, { id }), + ); + subscription = await client['publish'](msg, callback); + subscription(channel, JSON.stringify({ isDisposed: true, id })); + }); + + afterEach(() => assignStub.restore()); + + it('should call callback with dispose param', () => { + expect(callback.called).to.be.true; + expect(callback.calledWith({ + isDisposed: true, + response: null, + err: undefined, + })).to.be.true; + }); + it('should unsubscribe to response pattern name', () => { + expect(unsubscribeSpy.calledWith(channel)).to.be.true; + }); + it('should remove listener', () => { + expect(removeListenerSpy.called).to.be.true; + }); + }); + describe('disposed and "id" is incorrect', () => { + let assignStub: sinon.SinonStub; + + const channel = 'channel'; + const id = '1'; + + beforeEach(async () => { + callback = sinon.spy(); + assignStub = sinon + .stub(client, 'assignPacketId') + .callsFake(packet => + Object.assign(packet, { id }), + ); + subscription = await client['publish'](msg, callback); + subscription(channel, JSON.stringify({ isDisposed: true })); + }); + + afterEach(() => assignStub.restore()); + + it('should not call callback', () => { + expect(callback.called).to.be.false; + }); + it('should not unsubscribe to response pattern name', () => { + expect(unsubscribeSpy.called).to.be.false; + }); + }); + }); + }); + describe('close', () => { + let endSpy: sinon.SinonSpy; + beforeEach(() => { + endSpy = sinon.spy(); + (client as any).mqttClient = { end: endSpy }; + }); + it('should close "pub" when it is not null', () => { + client.close(); + expect(endSpy.called).to.be.true; + }); + it('should not close "pub" when it is null', () => { + (client as any).mqttClient = null; + client.close(); + expect(endSpy.called).to.be.false; + }); + }); + describe('init', () => { + let createClientSpy: sinon.SinonSpy; + let handleErrorsSpy: sinon.SinonSpy; + + beforeEach(() => { + createClientSpy = sinon.spy(client, 'createClient'); + handleErrorsSpy = sinon.spy(client, 'handleError'); + client.init(sinon.spy()); + }); + afterEach(() => { + createClientSpy.restore(); + handleErrorsSpy.restore(); + }); + it('should call "createClient" once', () => { + expect(createClientSpy.called).to.be.true; + }); + it('should call "handleError" once', () => { + expect(handleErrorsSpy.called).to.be.true; + }); + }); + describe('handleError', () => { + it('should bind error event handler and call callback with error', () => { + const callback = sinon.spy(); + const removeListenerSpy = sinon.spy(); + + const addListener = (name, fn) => { + const err = { code: 'ECONNREFUSED' }; + fn(err); + + expect(name).to.be.eql(ERROR_EVENT); + expect(callback.called).to.be.true; + expect(callback.calledWith(err, null)).to.be.true; + }; + const onCallback = (name, fn) => { + fn(); + expect(name).to.be.eql(CONNECT_EVENT); + expect(removeListenerSpy.called).to.be.true; + }; + + const stream = { + addListener, + on: onCallback, + removeListener: removeListenerSpy, + }; + client.handleError(stream as any, callback); + }); + }); +}); diff --git a/src/microservices/test/client/client-nats.spec.ts b/src/microservices/test/client/client-nats.spec.ts new file mode 100644 index 00000000000..ba73074369e --- /dev/null +++ b/src/microservices/test/client/client-nats.spec.ts @@ -0,0 +1,232 @@ +import * as sinon from 'sinon'; +import { expect } from 'chai'; +import { ClientNats } from '../../client/client-nats'; +import { ERROR_EVENT, CONNECT_EVENT, MESSAGE_EVENT } from '../../constants'; + +describe('ClientNats', () => { + const test = 'test'; + const client = new ClientNats({}); + + describe('getAckPatternName', () => { + it(`should append _ack to string`, () => { + const expectedResult = test + '_ack'; + expect(client.getAckPatternName(test)).to.equal(expectedResult); + }); + }); + describe('getResPatternName', () => { + it(`should append _res to string`, () => { + const expectedResult = test + '_res'; + expect(client.getResPatternName(test)).to.equal(expectedResult); + }); + }); + describe('publish', () => { + const pattern = 'test'; + const msg = { pattern, data: 'data' }; + const id = 3; + const subscriptionId = 10; + + let subscribeSpy: sinon.SinonSpy, + publishSpy: sinon.SinonSpy, + onSpy: sinon.SinonSpy, + removeListenerSpy: sinon.SinonSpy, + unsubscribeSpy: sinon.SinonSpy, + initSpy: sinon.SinonSpy, + natsClient, + createClient: sinon.SinonStub; + + beforeEach(() => { + subscribeSpy = sinon.spy(() => subscriptionId); + publishSpy = sinon.spy(); + onSpy = sinon.spy(); + removeListenerSpy = sinon.spy(); + unsubscribeSpy = sinon.spy(); + + natsClient = { + subscribe: subscribeSpy, + on: (type, handler) => (type === 'subscribe' ? handler() : onSpy()), + removeListener: removeListenerSpy, + unsubscribe: unsubscribeSpy, + addListener: () => ({}), + publish: publishSpy, + }; + (client as any).natsClient = natsClient; + + initSpy = sinon.stub(client, 'init').callsFake(() => { + (client as any).natsClient = natsClient; + }); + createClient = sinon.stub(client, 'createClient').callsFake(() => client); + }); + afterEach(() => { + initSpy.restore(); + createClient.restore(); + }); + it('should not call "init()" when natsClient is not null', async () => { + await client['publish'](msg, () => {}); + expect(initSpy.called).to.be.false; + }); + it('should call "init()" when natsClient is null', async () => { + (client as any).natsClient = null; + await client['publish'](msg, () => {}); + expect(initSpy.called).to.be.true; + }); + it('should subscribe to response pattern name', async () => { + await client['publish'](msg, () => {}); + expect(subscribeSpy.calledWith(`"${pattern}"_res`)).to.be.true; + }); + it('should publish stringified message to acknowledge pattern name', async () => { + await client['publish'](msg, () => {}); + // expect(publishSpy.getCall(0).args).to.be.true; + expect(publishSpy.getCall(0).args[0]).to.be.eql(`"${pattern}"_ack`); + }); + describe('responseCallback', () => { + let callback: sinon.SinonSpy, subscription, assignStub: sinon.SinonStub; + const responseMessage = { + err: null, + response: 'test', + id: '1', + }; + + describe('not disposed', () => { + beforeEach(async () => { + callback = sinon.spy(); + assignStub = sinon + .stub(client, 'assignPacketId') + .callsFake(packet => + Object.assign(packet, { id: responseMessage.id }), + ); + subscription = await client['publish'](msg, callback); + subscription(responseMessage); + }); + afterEach(() => { + assignStub.restore(); + }); + it('should call callback with expected arguments', () => { + expect( + callback.calledWith({ + err: null, + response: responseMessage.response, + }), + ).to.be.true; + }); + it('should not unsubscribe to response pattern name', () => { + expect(unsubscribeSpy.calledWith(`"${pattern}"_res`)).to.be.false; + }); + }); + describe('disposed and "id" is correct', () => { + let assignStub: sinon.SinonStub; + + const channel = 'channel'; + const id = '1'; + + beforeEach(async () => { + callback = sinon.spy(); + assignStub = sinon + .stub(client, 'assignPacketId') + .callsFake(packet => Object.assign(packet, { id })); + subscription = await client['publish'](msg, callback); + subscription({ isDisposed: true, id }); + }); + + afterEach(() => assignStub.restore()); + + it('should call callback with dispose param', () => { + expect(callback.called).to.be.true; + expect( + callback.calledWith({ + isDisposed: true, + response: null, + err: undefined, + }), + ).to.be.true; + }); + it('should unsubscribe to response pattern name', () => { + expect(unsubscribeSpy.calledWith(subscriptionId)).to.be.true; + }); + }); + describe('disposed and "id" is incorrect', () => { + let assignStub: sinon.SinonStub; + + const channel = 'channel'; + const id = '1'; + + beforeEach(async () => { + callback = sinon.spy(); + assignStub = sinon + .stub(client, 'assignPacketId') + .callsFake(packet => Object.assign(packet, { id })); + subscription = await client['publish'](msg, callback); + subscription({ isDisposed: true }); + }); + + afterEach(() => assignStub.restore()); + + it('should not call callback', () => { + expect(callback.called).to.be.false; + }); + it('should not unsubscribe to response pattern name', () => { + expect(unsubscribeSpy.called).to.be.false; + }); + }); + }); + }); + describe('close', () => { + let natsClose: sinon.SinonSpy; + let natsClient; + beforeEach(() => { + natsClose = sinon.spy(); + natsClient = { close: natsClose }; + (client as any).natsClient = natsClient; + }); + it('should close "natsClient" when it is not null', () => { + client.close(); + expect(natsClose.called).to.be.true; + }); + }); + describe('init', () => { + let createClientSpy: sinon.SinonSpy; + let handleErrorsSpy: sinon.SinonSpy; + + beforeEach(async () => { + createClientSpy = sinon.spy(client, 'createClient'); + handleErrorsSpy = sinon.spy(client, 'handleError'); + await client.init(sinon.spy()); + }); + afterEach(() => { + createClientSpy.restore(); + handleErrorsSpy.restore(); + }); + it('should call "createClient"', () => { + expect(createClientSpy.called).to.be.true; + }); + it('should call "handleError"', () => { + expect(handleErrorsSpy.called).to.be.true; + }); + }); + describe('handleError', () => { + it('should bind error event handler and call callback with error', () => { + const callback = sinon.spy(); + const removeListenerSpy = sinon.spy(); + + const addListener = (name, fn) => { + const err = { code: 'ECONNREFUSED' }; + fn(err); + + expect(name).to.be.eql(ERROR_EVENT); + expect(callback.called).to.be.true; + expect(callback.calledWith(err, null)).to.be.true; + }; + const onCallback = (name, fn) => { + fn(); + expect(name).to.be.eql(CONNECT_EVENT); + expect(removeListenerSpy.called).to.be.true; + }; + + const stream = { + addListener, + on: onCallback, + removeListener: removeListenerSpy, + }; + client.handleError(stream as any, callback); + }); + }); +}); diff --git a/src/microservices/test/client/client-proxy-factory.spec.ts b/src/microservices/test/client/client-proxy-factory.spec.ts index b5f8c697d2c..3d35199121e 100644 --- a/src/microservices/test/client/client-proxy-factory.spec.ts +++ b/src/microservices/test/client/client-proxy-factory.spec.ts @@ -3,6 +3,10 @@ import { ClientProxyFactory } from '../../client/client-proxy-factory'; import { ClientTCP } from '../../client/client-tcp'; import { Transport } from '../../enums/transport.enum'; import { ClientRedis } from '../../client/client-redis'; +import { ClientNats } from '../../client/client-nats'; +import { ClientMqtt } from '../../client/client-mqtt'; +import { ClientGrpcProxy } from '../../client/client-grpc'; +import { join } from 'path'; describe('ClientProxyFactory', () => { describe('create', () => { @@ -15,5 +19,26 @@ describe('ClientProxyFactory', () => { const proxy = ClientProxyFactory.create({ transport: Transport.REDIS }); expect(proxy instanceof ClientRedis).to.be.true; }); + + it(`should create nats client`, () => { + const proxy = ClientProxyFactory.create({ transport: Transport.NATS }); + expect(proxy instanceof ClientNats).to.be.true; + }); + + it(`should create mqtt client`, () => { + const proxy = ClientProxyFactory.create({ transport: Transport.MQTT }); + expect(proxy instanceof ClientMqtt).to.be.true; + }); + + it(`should create grpc client`, () => { + const proxy = ClientProxyFactory.create({ + transport: Transport.GRPC, + options: { + protoPath: join(__dirname, './test.proto'), + package: 'test' + }, + }); + expect(proxy instanceof ClientGrpcProxy).to.be.true; + }); }); }); diff --git a/src/microservices/test/client/client-proxy.spec.ts b/src/microservices/test/client/client-proxy.spec.ts index a94668158eb..afc30a9593a 100644 --- a/src/microservices/test/client/client-proxy.spec.ts +++ b/src/microservices/test/client/client-proxy.spec.ts @@ -5,7 +5,7 @@ import { Observable } from 'rxjs'; import { ErrorObservable } from 'rxjs/observable/ErrorObservable'; class TestClientProxy extends ClientProxy { - public sendMessage(pattern, callback) {} + public publish(pattern, callback) {} } describe('ClientProxy', () => { @@ -16,15 +16,15 @@ describe('ClientProxy', () => { const stream$ = client.send({}, ''); expect(stream$ instanceof Observable).to.be.true; }); - it(`should call "sendMessage" on subscribe`, () => { + it(`should call "publish" on subscribe`, () => { const pattern = { test: 3 }; const data = 'test'; - const sendMessageSpy = sinon.spy(); + const publishSpy = sinon.spy(); const stream$ = client.send(pattern, data); - client.sendMessage = sendMessageSpy; + client.publish = publishSpy; stream$.subscribe(); - expect(sendMessageSpy.calledOnce).to.be.true; + expect(publishSpy.calledOnce).to.be.true; }); it('should return ErrorObservable', () => { const err$ = client.send(null, null); @@ -54,19 +54,19 @@ describe('ClientProxy', () => { it(`"error" when first parameter is not null or undefined`, () => { const err = 'test'; - fn(err); + fn({ err }); expect(error.calledWith(err)).to.be.true; }); it(`"next" when first parameter is null or undefined`, () => { const data = 'test'; - fn(null, data); + fn({ response: data }); expect(next.calledWith(data)).to.be.true; }); it(`"complete" when third parameter is true`, () => { const data = 'test'; - fn(null, data, true); + fn({ data, isDisposed: true }); expect(complete.called).to.be.true; }); }); diff --git a/src/microservices/test/client/client-redis.spec.ts b/src/microservices/test/client/client-redis.spec.ts index 5bb3e99044a..112cb08b2ea 100644 --- a/src/microservices/test/client/client-redis.spec.ts +++ b/src/microservices/test/client/client-redis.spec.ts @@ -1,7 +1,7 @@ import * as sinon from 'sinon'; import { expect } from 'chai'; import { ClientRedis } from '../../client/client-redis'; -import { ERROR_EVENT, CONNECT_EVENT } from '../../constants'; +import { ERROR_EVENT, CONNECT_EVENT, MESSAGE_EVENT } from '../../constants'; describe('ClientRedis', () => { const test = 'test'; @@ -19,9 +19,9 @@ describe('ClientRedis', () => { expect(client.getResPatternName(test)).to.equal(expectedResult); }); }); - describe('sendMessage', () => { + describe('publish', () => { const pattern = 'test'; - const msg = { pattern }; + const msg = { pattern, data: 'data' }; let subscribeSpy: sinon.SinonSpy, publishSpy: sinon.SinonSpy, onSpy: sinon.SinonSpy, @@ -40,7 +40,7 @@ describe('ClientRedis', () => { sub = { subscribe: subscribeSpy, - on: onSpy, + on: (type, handler) => (type === 'subscribe' ? handler() : onSpy()), removeListener: removeListenerSpy, unsubscribe: unsubscribeSpy, addListener: () => ({}), @@ -53,75 +53,126 @@ describe('ClientRedis', () => { afterEach(() => { initSpy.restore(); }); - it('should not call "init()" when pub and sub are null', () => { - client['sendMessage'](msg, () => {}); + it('should not call "init()" when pub and sub are not null', () => { + client['publish'](msg, () => {}); expect(initSpy.called).to.be.false; }); it('should call "init()" when pub and sub are null', () => { (client as any).subClient = null; (client as any).pubClient = null; - client['sendMessage'](msg, () => {}); + client['publish'](msg, () => {}); expect(initSpy.called).to.be.true; }); it('should subscribe to response pattern name', () => { - client['sendMessage'](msg, () => {}); + client['publish'](msg, () => {}); expect(subscribeSpy.calledWith(`"${pattern}"_res`)).to.be.true; }); - it('should publish stringified message to acknowledge pattern name', () => { - client['sendMessage'](msg, () => {}); + it('should publish stringified message to acknowledge pattern name', async () => { + await client['publish'](msg, () => {}); expect(publishSpy.calledWith(`"${pattern}"_ack`, JSON.stringify(msg))).to .be.true; }); it('should listen on messages', () => { - client['sendMessage'](msg, () => {}); + client['publish'](msg, () => {}); expect(onSpy.called).to.be.true; }); describe('responseCallback', () => { - let callback, subscription; + let callback: sinon.SinonSpy, subscription, assignStub: sinon.SinonStub; const responseMessage = { err: null, response: 'test', + id: '1', }; describe('not disposed', () => { - beforeEach(() => { + beforeEach(async () => { callback = sinon.spy(); -<<<<<<< HEAD - subscription = client['sendMessage'](msg, callback); - subscription(null, JSON.stringify(resMsg)); -======= - subscription = client['sendSingleMessage'](msg, callback); + assignStub = sinon + .stub(client, 'assignPacketId') + .callsFake(packet => + Object.assign(packet, { id: responseMessage.id }), + ); + subscription = await client['publish'](msg, callback); subscription(null, JSON.stringify(responseMessage)); ->>>>>>> master + }); + afterEach(() => { + assignStub.restore(); }); it('should call callback with expected arguments', () => { - expect(callback.calledWith(null, responseMessage.response)).to.be - .true; + expect( + callback.calledWith({ + err: null, + response: responseMessage.response, + }), + ).to.be.true; }); it('should not unsubscribe to response pattern name', () => { expect(unsubscribeSpy.calledWith(`"${pattern}"_res`)).to.be.false; }); it('should not remove listener', () => { - expect(removeListenerSpy.called).to.be.false; + expect(removeListenerSpy.getCall(0).args[0]).to.not.be.eql(MESSAGE_EVENT); }); }); - describe('disposed', () => { - beforeEach(() => { + describe('disposed and "id" is correct', () => { + let assignStub: sinon.SinonStub; + + const channel = 'channel'; + const id = '1'; + + beforeEach(async () => { callback = sinon.spy(); - subscription = client['sendMessage'](msg, callback); - subscription(null, JSON.stringify({ disposed: true })); + assignStub = sinon + .stub(client, 'assignPacketId') + .callsFake(packet => + Object.assign(packet, { id }), + ); + subscription = await client['publish'](msg, callback); + subscription(channel, JSON.stringify({ isDisposed: true, id })); }); + + afterEach(() => assignStub.restore()); + it('should call callback with dispose param', () => { expect(callback.called).to.be.true; - expect(callback.calledWith(undefined, null, true)).to.be.true; + expect(callback.calledWith({ + isDisposed: true, + response: null, + err: undefined, + })).to.be.true; }); it('should unsubscribe to response pattern name', () => { - expect(unsubscribeSpy.calledWith(`"${pattern}"_res`)).to.be.true; + expect(unsubscribeSpy.calledWith(channel)).to.be.true; }); it('should remove listener', () => { expect(removeListenerSpy.called).to.be.true; }); }); + describe('disposed and "id" is incorrect', () => { + let assignStub: sinon.SinonStub; + + const channel = 'channel'; + const id = '1'; + + beforeEach(async () => { + callback = sinon.spy(); + assignStub = sinon + .stub(client, 'assignPacketId') + .callsFake(packet => + Object.assign(packet, { id }), + ); + subscription = await client['publish'](msg, callback); + subscription(channel, JSON.stringify({ isDisposed: true })); + }); + + afterEach(() => assignStub.restore()); + + it('should not call callback', () => { + expect(callback.called).to.be.false; + }); + it('should not unsubscribe to response pattern name', () => { + expect(unsubscribeSpy.called).to.be.false; + }); + }); }); }); describe('close', () => { @@ -183,7 +234,7 @@ describe('ClientRedis', () => { const addListener = (name, fn) => { const err = { code: 'ECONNREFUSED' }; fn(err); - + expect(name).to.be.eql(ERROR_EVENT); expect(callback.called).to.be.true; expect(callback.calledWith(err, null)).to.be.true; @@ -220,26 +271,29 @@ describe('ClientRedis', () => { }); describe('when "retryAttempts" does not exist', () => { it('should return undefined', () => { - (client as any).metadata.retryAttempts = undefined; + (client as any).options.options = {}; + (client as any).options.options.retryAttempts = undefined; const result = client.createRetryStrategy({} as any); expect(result).to.be.undefined; }); }); describe('when "attempts" count is max', () => { it('should return undefined', () => { - (client as any).metadata.retryAttempts = 3; + (client as any).options.options = {}; + (client as any).options.options.retryAttempts = 3; const result = client.createRetryStrategy({ attempt: 4 } as any); expect(result).to.be.undefined; }); }); describe('otherwise', () => { it('should return delay (ms)', () => { + (client as any).options.options = {}; (client as any).isExplicitlyTerminated = false; - (client as any).metadata.retryAttempts = 3; - (client as any).metadata.retryDelay = 3; + (client as any).options.options.retryAttempts = 3; + (client as any).options.options.retryDelay = 3; const result = client.createRetryStrategy({ attempt: 2 } as any); - expect(result).to.be.eql((client as any).metadata.retryDelay); + expect(result).to.be.eql((client as any).options.options.retryDelay); }); - }) + }); }); }); diff --git a/src/microservices/test/client/client-tcp.spec.ts b/src/microservices/test/client/client-tcp.spec.ts index 6a1ebeae896..8cca25ceb2f 100644 --- a/src/microservices/test/client/client-tcp.spec.ts +++ b/src/microservices/test/client/client-tcp.spec.ts @@ -7,28 +7,30 @@ describe('ClientTCP', () => { const client = new ClientTCP({}); let socket: { connect: sinon.SinonSpy; - sendMessage: sinon.SinonSpy; + publish: sinon.SinonSpy; _socket: { removeListener: sinon.SinonSpy; once: sinon.SinonStub; - }, + }; on: sinon.SinonStub; end: sinon.SinonSpy; + sendMessage: sinon.SinonSpy; }; let createSocketStub: sinon.SinonStub; beforeEach(() => { const onFakeCallback = (event, callback) => event !== 'error' && event !== 'close' && callback({}); - + socket = { connect: sinon.spy(), - sendMessage: sinon.spy(), + publish: sinon.spy(), on: sinon.stub().callsFake(onFakeCallback), _socket: { removeListener: sinon.spy(), once: sinon.stub().callsFake(onFakeCallback), }, + sendMessage: sinon.spy(), end: sinon.spy(), }; createSocketStub = sinon @@ -38,26 +40,26 @@ describe('ClientTCP', () => { afterEach(() => { createSocketStub.restore(); }); - describe('sendMessage', () => { + describe('publish', () => { let msg; beforeEach(() => { msg = { test: 3 }; }); it('should connect to server when is not connected', done => { - client['sendMessage'](msg, () => ({})).then(() => { + client['publish'](msg, () => ({})).then(() => { expect(socket.connect.calledOnce).to.be.true; done(); }); }); it('should not connect to server when is already connected', () => { (client as any).isConnected = true; - client['sendMessage'](msg, () => ({})); + client['publish'](msg, () => ({})); expect(socket.connect.called).to.be.false; }); describe('after connection', () => { it('should send message', done => { (client as any).isConnected = false; - client['sendMessage'](msg, () => ({})).then(() => { + client['publish'](msg, () => ({})).then(() => { expect(socket.sendMessage.called).to.be.true; expect(socket.sendMessage.calledWith(msg)).to.be.true; done(); @@ -65,7 +67,7 @@ describe('ClientTCP', () => { }); it('should listen on messages', done => { (client as any).isConnected = false; - client['sendMessage'](msg, () => ({})).then(() => { + client['publish'](msg, () => ({})).then(() => { expect(socket.on.called).to.be.true; done(); }); @@ -78,15 +80,22 @@ describe('ClientTCP', () => { const context = () => ({}); beforeEach(() => { callback = sinon.spy(); - client.handleResponse(socket, callback, { disposed: true }, context); + client.handleResponse(socket, callback, { isDisposed: true }, context); }); it('should remove listener', () => { expect(socket._socket.removeListener.called).to.be.true; - expect(socket._socket.removeListener.calledWith(MESSAGE_EVENT, context)).to.be.true; + expect(socket._socket.removeListener.calledWith(MESSAGE_EVENT, context)) + .to.be.true; }); it('should emit disposed callback', () => { expect(callback.called).to.be.true; - expect(callback.calledWith(undefined, null, true)).to.be.true; + expect( + callback.calledWith({ + err: undefined, + response: null, + isDisposed: true, + }), + ).to.be.true; }); }); describe('when not disposed', () => { @@ -102,7 +111,12 @@ describe('ClientTCP', () => { }); it('should call callback with error and response data', () => { expect(callback.called).to.be.true; - expect(callback.calledWith(buffer.err, buffer.response)).to.be.true; + expect( + callback.calledWith({ + err: buffer.err, + response: buffer.response, + }), + ).to.be.true; }); }); }); @@ -127,7 +141,7 @@ describe('ClientTCP', () => { const callback = sinon.spy(); const err = { code: 'ECONNREFUSED' }; client.handleError(err, callback); - + expect(callback.called).to.be.true; expect(callback.calledWith(err, null)).to.be.true; }); @@ -135,7 +149,7 @@ describe('ClientTCP', () => { const callback = sinon.spy(); const err = {}; client.handleError(err, callback); - + expect(callback.called).to.be.false; }); }); diff --git a/src/microservices/test/client/test.proto b/src/microservices/test/client/test.proto new file mode 100644 index 00000000000..ba8c9202048 --- /dev/null +++ b/src/microservices/test/client/test.proto @@ -0,0 +1,3 @@ +syntax = "proto3"; + +package test; \ No newline at end of file diff --git a/src/microservices/test/context/rpc-context-creator.spec.ts b/src/microservices/test/context/rpc-context-creator.spec.ts index dd2cd6ebcfd..787c1860555 100644 --- a/src/microservices/test/context/rpc-context-creator.spec.ts +++ b/src/microservices/test/context/rpc-context-creator.spec.ts @@ -55,6 +55,8 @@ describe('RpcContextCreator', () => { exceptionFiltersContext = new ExceptionFiltersContext( new ApplicationConfig() as any, ); + sinon.stub(rpcProxy, 'create').callsFake(a => a); + pipesCreator = new PipesContextCreator(); pipesConsumer = new PipesConsumer(); guardsContextCreator = new GuardsContextCreator(new NestContainer()); diff --git a/src/microservices/test/server/server-factory.spec.ts b/src/microservices/test/server/server-factory.spec.ts index 1581b229e30..d1d946f2df1 100644 --- a/src/microservices/test/server/server-factory.spec.ts +++ b/src/microservices/test/server/server-factory.spec.ts @@ -3,17 +3,49 @@ import { ServerFactory } from '../../server/server-factory'; import { ServerTCP } from '../../server/server-tcp'; import { ServerRedis } from '../../server/server-redis'; import { Transport } from '../../enums/transport.enum'; +import { ServerMqtt } from '../../server/server-mqtt'; +import { ServerNats } from '../../server/server-nats'; +import { ServerGrpc } from '../../server/server-grpc'; describe('ServerFactory', () => { describe('create', () => { it(`should return tcp server by default`, () => { expect(ServerFactory.create({}) instanceof ServerTCP).to.be.true; }); - it(`should return redis server if transport is set to redis`, () => { + + it(`should return redis server`, () => { + expect( + ServerFactory.create({ transport: Transport.REDIS }) instanceof + ServerRedis, + ).to.be.true; + }); + + it(`should return redis server`, () => { expect( ServerFactory.create({ transport: Transport.REDIS }) instanceof ServerRedis, ).to.be.true; }); + + it(`should return mqtt server`, () => { + expect( + ServerFactory.create({ transport: Transport.MQTT }) instanceof + ServerMqtt, + ).to.be.true; + }); + + it(`should return nats server`, () => { + expect( + ServerFactory.create({ transport: Transport.NATS }) instanceof + ServerNats, + ).to.be.true; + }); + + it(`should return grpc server`, () => { + expect( + ServerFactory.create({ transport: Transport.GRPC }) instanceof + ServerGrpc, + ).to.be.true; + }); }); }); diff --git a/src/microservices/test/server/server-grpc.spec.ts b/src/microservices/test/server/server-grpc.spec.ts new file mode 100644 index 00000000000..10077719d85 --- /dev/null +++ b/src/microservices/test/server/server-grpc.spec.ts @@ -0,0 +1,201 @@ +import { ServerGrpc } from '../../server/server-grpc'; +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import { Observable } from 'rxjs/Observable'; +import { join } from 'path'; +import { InvalidGrpcPackageException } from '../../exceptions/invalid-grpc-package.exception'; + +describe('ServerGrpc', () => { + let server: ServerGrpc; + beforeEach(() => { + server = new ServerGrpc({ + options: { + protoPath: join(__dirname, './test.proto'), + package: 'test', + }, + } as any); + }); + + describe('listen', () => { + let callback: sinon.SinonSpy; + let bindEventsStub: sinon.SinonStub; + + beforeEach(() => { + callback = sinon.spy(); + bindEventsStub = sinon.stub(server, 'bindEvents').callsFake(() => ({})); + }); + + it('should call "bindEvents"', async () => { + await server.listen(callback); + expect(bindEventsStub.called).to.be.true; + }); + it('should call "client.start"', async () => { + const client = { start: sinon.spy() }; + sinon.stub(server, 'createClient').callsFake(() => client); + + await server.listen(callback); + expect(client.start.called).to.be.true; + }); + it('should call callback', async () => { + await server.listen(callback); + expect(callback.called).to.be.true; + }); + }); + + describe('bindEvents', () => { + describe('when package does not exist', () => { + it('should throw "InvalidGrpcPackageException"', () => { + sinon.stub(server, 'lookupPackage').callsFake(() => null); + expect(server.bindEvents()).to.eventually.throws( + InvalidGrpcPackageException, + ); + }); + }); + describe('when package exist', () => { + it('should call "addService"', async () => { + const serviceNames = ['test', 'test2']; + sinon.stub(server, 'lookupPackage').callsFake(() => ({ + test: true, + test2: true, + })); + sinon.stub(server, 'getServiceNames').callsFake(() => serviceNames); + + (server as any).grpcClient = { addService: sinon.spy() }; + + await server.bindEvents(); + expect((server as any).grpcClient.addService.calledTwice).to.be.true; + }); + }); + }); + + describe('getServiceNames', () => { + it('should return filtered object keys', () => { + const obj = { + key: { service: true }, + key2: { service: true }, + key3: { service: false }, + }; + const expected = ['key', 'key2']; + expect(server.getServiceNames(obj)).to.be.eql(expected); + }); + }); + + describe('createService', () => { + it('should call "createServiceMethod"', async () => { + const handlers = { + test: null, + test2: () => ({}), + }; + sinon + .stub(server, 'createPattern') + .onFirstCall() + .returns('test') + .onSecondCall() + .returns('test2'); + + const spy = sinon + .stub(server, 'createServiceMethod') + .callsFake(() => ({})); + + (server as any).messageHandlers = handlers; + await server.createService( + { + prototype: { test: true, test2: true }, + }, + 'name', + ); + expect(spy.calledOnce).to.be.true; + }); + }); + + describe('createPattern', () => { + it('should return pattern', () => { + const service = 'test'; + const method = 'method'; + expect(server.createPattern(service, method)).to.be.eql( + JSON.stringify({ + service, + rpc: method, + }), + ); + }); + }); + + describe('createServiceMethod', () => { + describe('when method is a response stream', () => { + it('should call "createStreamServiceMethod"', () => { + const cln = sinon.spy(); + const spy = sinon.spy(server, 'createStreamServiceMethod'); + server.createServiceMethod(cln, { responseStream: true } as any); + + expect(spy.called).to.be.true; + }); + }); + describe('when method is not a response stream', () => { + it('should call "createUnaryServiceMethod"', () => { + const cln = sinon.spy(); + const spy = sinon.spy(server, 'createUnaryServiceMethod'); + server.createServiceMethod(cln, { responseStream: false } as any); + + expect(spy.called).to.be.true; + }); + }); + }); + + describe('createStreamServiceMethod', () => { + it('should return function', () => { + const fn = server.createStreamServiceMethod(sinon.spy()); + expect(fn).to.be.a('function'); + }); + describe('on call', () => { + it('should call native method', async () => { + const call = { write: sinon.spy(), end: sinon.spy() }; + const callback = sinon.spy(); + const native = sinon.spy(); + + await server.createStreamServiceMethod(native)(call, callback); + expect(native.called).to.be.true; + }); + }); + }); + + describe('createUnaryServiceMethod', () => { + it('should return observable', () => { + const fn = server.createUnaryServiceMethod(sinon.spy()); + expect(fn).to.be.a('function'); + }); + describe('on call', () => { + it('should call native & callback methods', async () => { + const call = { write: sinon.spy(), end: sinon.spy() }; + const callback = sinon.spy(); + const native = sinon.spy(); + + await server.createUnaryServiceMethod(native)(call, callback); + expect(native.called).to.be.true; + expect(callback.called).to.be.true; + }); + }); + }); + + describe('close', () => { + it('should call "forceShutdown"', () => { + const grpcClient = { forceShutdown: sinon.spy() }; + (server as any).grpcClient = grpcClient; + server.close(); + expect(grpcClient.forceShutdown.called).to.be.true; + }); + }); + + describe('deserialize', () => { + it(`should return parsed json`, () => { + const obj = { test: 'test' }; + expect(server.deserialize(obj)).to.deep.equal( + JSON.parse(JSON.stringify(obj)), + ); + }); + it(`should not parse argument if it is not an object`, () => { + const content = 'test'; + expect(server.deserialize(content)).to.equal(content); + }); + }); +}); diff --git a/src/microservices/test/server/server-mqtt.spec.ts b/src/microservices/test/server/server-mqtt.spec.ts new file mode 100644 index 00000000000..6c0553ac196 --- /dev/null +++ b/src/microservices/test/server/server-mqtt.spec.ts @@ -0,0 +1,178 @@ +import * as sinon from 'sinon'; +import { expect } from 'chai'; +import { NO_PATTERN_MESSAGE } from '../../constants'; +import { ServerMqtt } from '../../server/server-mqtt'; +import { Observable } from 'rxjs/Observable'; + +describe('ServerMqtt', () => { + let server: ServerMqtt; + beforeEach(() => { + server = new ServerMqtt({}); + }); + describe('listen', () => { + let createMqttClient; + let onSpy: sinon.SinonSpy; + let client; + + beforeEach(() => { + onSpy = sinon.spy(); + client = { + on: onSpy, + }; + createMqttClient = sinon + .stub(server, 'createMqttClient') + .callsFake(() => client); + + server.listen(null); + }); + it('should bind "error" event to handler', () => { + expect(onSpy.getCall(0).args[0]).to.be.equal('error'); + }); + it('should bind "message" event to handler', () => { + expect(onSpy.getCall(1).args[0]).to.be.equal('message'); + }); + it('should bind "connect" event to handler', () => { + expect(onSpy.getCall(2).args[0]).to.be.equal('connect'); + }); + }); + describe('close', () => { + const mqttClient = { end: sinon.spy() }; + beforeEach(() => { + (server as any).mqttClient = mqttClient; + }); + it('should end mqttClient', () => { + server.close(); + expect(mqttClient.end.called).to.be.true; + }); + }); + describe('bindEvents', () => { + let onSpy: sinon.SinonSpy, subscribeSpy: sinon.SinonSpy, mqttClient; + + beforeEach(() => { + onSpy = sinon.spy(); + subscribeSpy = sinon.spy(); + mqttClient = { + on: onSpy, + subscribe: subscribeSpy, + }; + }); + it('should subscribe each acknowledge patterns', () => { + const pattern = 'test'; + const handler = sinon.spy(); + (server as any).messageHandlers = { + [pattern]: handler, + }; + server.bindEvents(mqttClient); + + const expectedPattern = 'test_ack'; + expect(subscribeSpy.calledWith(expectedPattern)).to.be.true; + }); + }); + describe('getMessageHandler', () => { + it(`should return function`, () => { + expect( + typeof server.getMessageHandler((server as any).mqttClient), + ).to.be.eql('function'); + }); + describe('handler', () => { + it('should call "handleMessage"', async () => { + const spy = sinon.spy(server, 'handleMessage'); + (await server.getMessageHandler((server as any).mqttClient))(null); + expect(spy.called).to.be.true; + }); + }); + }); + describe('handleMessage', () => { + let getPublisherSpy: sinon.SinonSpy; + + const channel = 'test'; + const data = 'test'; + const id = '3'; + + beforeEach(() => { + getPublisherSpy = sinon.spy(); + sinon.stub(server, 'getPublisher').callsFake(() => getPublisherSpy); + }); + it(`should publish NO_PATTERN_MESSAGE if pattern not exists in messageHandlers object`, () => { + server.handleMessage( + channel, + new Buffer(JSON.stringify({ id, pattern: '', data })), + null, + ); + expect( + getPublisherSpy.calledWith({ + id, + status: 'error', + err: NO_PATTERN_MESSAGE, + }), + ).to.be.true; + }); + it(`should call handler with expected arguments`, () => { + const handler = sinon.spy(); + (server as any).messageHandlers = { + [channel]: handler, + }; + + server.handleMessage( + channel, + new Buffer(JSON.stringify({ pattern: '', data, id: '2' })), + null, + ); + expect(handler.calledWith(data)).to.be.true; + }); + }); + describe('getPublisher', () => { + let publisherSpy: sinon.SinonSpy; + let pub, publisher; + + const id = '1'; + const pattern = 'test'; + + beforeEach(() => { + publisherSpy = sinon.spy(); + pub = { + publish: publisherSpy, + }; + publisher = server.getPublisher(pub, pattern, id); + }); + it(`should return function`, () => { + expect(typeof server.getPublisher(null, null, id)).to.be.eql('function'); + }); + it(`should call "publish" with expected arguments`, () => { + const respond = 'test'; + publisher({ respond, id }); + expect( + publisherSpy.calledWith( + `${pattern}_res`, + JSON.stringify({ respond, id }), + ), + ).to.be.true; + }); + }); + describe('getAckPatternName', () => { + const test = 'test'; + it(`should append _ack to string`, () => { + const expectedResult = test + '_ack'; + expect(server.getAckQueueName(test)).to.equal(expectedResult); + }); + }); + describe('getResPatternName', () => { + const test = 'test'; + it(`should append _res to string`, () => { + const expectedResult = test + '_res'; + expect(server.getResQueueName(test)).to.equal(expectedResult); + }); + }); + describe('deserialize', () => { + it(`should return parsed json`, () => { + const obj = { test: 'test' }; + expect(server.deserialize(obj)).to.deep.equal( + JSON.parse(JSON.stringify(obj)), + ); + }); + it(`should not parse argument if it is not an object`, () => { + const content = 'test'; + expect(server.deserialize(content)).to.equal(content); + }); + }); +}); diff --git a/src/microservices/test/server/server-nats.spec.ts b/src/microservices/test/server/server-nats.spec.ts new file mode 100644 index 00000000000..5c4a0ab1975 --- /dev/null +++ b/src/microservices/test/server/server-nats.spec.ts @@ -0,0 +1,155 @@ +import * as sinon from 'sinon'; +import { expect } from 'chai'; +import { NO_PATTERN_MESSAGE } from '../../constants'; +import { ServerNats } from '../../server/server-nats'; +import { Observable } from 'rxjs/Observable'; + +describe('ServerNats', () => { + let server: ServerNats; + beforeEach(() => { + server = new ServerNats({}); + }); + describe('listen', () => { + let createNatsClient; + let onSpy: sinon.SinonSpy; + let client; + + beforeEach(() => { + onSpy = sinon.spy(); + client = { + on: onSpy, + }; + createNatsClient = sinon + .stub(server, 'createNatsClient') + .callsFake(() => client); + + server.listen(null); + }); + it('should bind "error" event to handler', () => { + expect(onSpy.getCall(0).args[0]).to.be.equal('error'); + }); + it('should bind "connect" event to handler', () => { + expect(onSpy.getCall(1).args[0]).to.be.equal('connect'); + }); + }); + describe('close', () => { + const natsClient = { close: sinon.spy() }; + beforeEach(() => { + (server as any).natsClient = natsClient; + }); + it('should close natsClient', () => { + server.close(); + expect(natsClient.close.called).to.be.true; + }); + }); + describe('bindEvents', () => { + let onSpy: sinon.SinonSpy, subscribeSpy: sinon.SinonSpy, natsClient; + + beforeEach(() => { + onSpy = sinon.spy(); + subscribeSpy = sinon.spy(); + natsClient = { + on: onSpy, + subscribe: subscribeSpy, + }; + }); + it('should subscribe each acknowledge patterns', () => { + const pattern = 'test'; + const handler = sinon.spy(); + (server as any).messageHandlers = { + [pattern]: handler, + }; + server.bindEvents(natsClient); + + const expectedPattern = 'test_ack'; + expect(subscribeSpy.calledWith(expectedPattern)).to.be.true; + }); + }); + describe('getMessageHandler', () => { + it(`should return function`, () => { + expect( + typeof server.getMessageHandler(null, (server as any).natsClient), + ).to.be.eql('function'); + }); + describe('handler', () => { + it('should call "handleMessage"', async () => { + const spy = sinon.spy(server, 'handleMessage'); + (await server.getMessageHandler(null, (server as any).natsClient))(null); + expect(spy.called).to.be.true; + }) + }); + }); + describe('handleMessage', () => { + let getPublisherSpy: sinon.SinonSpy; + + const channel = 'test'; + const data = 'test'; + const id = '3'; + + beforeEach(() => { + getPublisherSpy = sinon.spy(); + sinon.stub(server, 'getPublisher').callsFake(() => getPublisherSpy); + }); + it(`should publish NO_PATTERN_MESSAGE if pattern not exists in messageHandlers object`, () => { + server.handleMessage(channel, { id, pattern: '', data: '' }, null); + expect( + getPublisherSpy.calledWith({ + id, + status: 'error', + err: NO_PATTERN_MESSAGE, + }), + ).to.be.true; + }); + it(`should call handler with expected arguments`, () => { + const handler = sinon.spy(); + (server as any).messageHandlers = { + [channel]: handler, + }; + + server.handleMessage(channel, { pattern: '', data, id: '2' }, null); + expect(handler.calledWith(data)).to.be.true; + }); + }); + describe('getPublisher', () => { + let publisherSpy: sinon.SinonSpy; + let pub, publisher; + + const id = '1'; + const pattern = 'test'; + + beforeEach(() => { + publisherSpy = sinon.spy(); + pub = { + publish: publisherSpy, + }; + publisher = server.getPublisher(pub, pattern, id); + }); + it(`should return function`, () => { + expect(typeof server.getPublisher(null, null, id)).to.be.eql('function'); + }); + it(`should call "publish" with expected arguments`, () => { + const respond = 'test'; + publisher({ respond, id }); + expect( + publisherSpy.calledWith( + `${pattern}_res`, + { respond, id }, + ), + ).to.be.true; + }); + }); + describe('getAckPatternName', () => { + const test = 'test'; + it(`should append _ack to string`, () => { + const expectedResult = test + '_ack'; + expect(server.getAckQueueName(test)).to.equal(expectedResult); + }); + }); + describe('getResPatternName', () => { + const test = 'test'; + it(`should append _res to string`, () => { + const expectedResult = test + '_res'; + expect(server.getResQueueName(test)).to.equal(expectedResult); + }); + }); +}); diff --git a/src/microservices/test/server/server-redis.spec.ts b/src/microservices/test/server/server-redis.spec.ts index 500fb672225..f0140c81513 100644 --- a/src/microservices/test/server/server-redis.spec.ts +++ b/src/microservices/test/server/server-redis.spec.ts @@ -83,18 +83,21 @@ describe('ServerRedis', () => { }); describe('handleMessage', () => { let getPublisherSpy: sinon.SinonSpy; + const channel = 'test'; const data = 'test'; + const id = '3'; beforeEach(() => { getPublisherSpy = sinon.spy(); sinon.stub(server, 'getPublisher').callsFake(() => getPublisherSpy); - sinon.stub(server, 'tryParse').callsFake(() => ({ data })); + sinon.stub(server, 'deserialize').callsFake(() => ({ id, data })); }); it(`should publish NO_PATTERN_MESSAGE if pattern not exists in messageHandlers object`, () => { - server.handleMessage(channel, {}, null); + server.handleMessage(channel, JSON.stringify({ id }), null); expect( getPublisherSpy.calledWith({ + id, status: 'error', err: NO_PATTERN_MESSAGE, }), @@ -113,6 +116,8 @@ describe('ServerRedis', () => { describe('getPublisher', () => { let publisherSpy: sinon.SinonSpy; let pub, publisher; + + const id = '1'; const pattern = 'test'; beforeEach(() => { @@ -120,28 +125,28 @@ describe('ServerRedis', () => { pub = { publish: publisherSpy, }; - publisher = server.getPublisher(pub, pattern); + publisher = server.getPublisher(pub, pattern, id); }); it(`should return function`, () => { - expect(typeof server.getPublisher(null, null)).to.be.eql('function'); + expect(typeof server.getPublisher(null, null, id)).to.be.eql('function'); }); it(`should call "publish" with expected arguments`, () => { const respond = 'test'; - publisher(respond); - expect(publisherSpy.calledWith(`${pattern}_res`, JSON.stringify(respond))) + publisher({ respond, id }); + expect(publisherSpy.calledWith(`${pattern}_res`, JSON.stringify({ respond, id }))) .to.be.true; }); }); - describe('tryParse', () => { + describe('deserialize', () => { it(`should return parsed json`, () => { const obj = { test: 'test' }; - expect(server.tryParse(obj)).to.deep.equal( + expect(server.deserialize(obj)).to.deep.equal( JSON.parse(JSON.stringify(obj)), ); }); it(`should not parse argument if it is not an object`, () => { const content = 'test'; - expect(server.tryParse(content)).to.equal(content); + expect(server.deserialize(content)).to.equal(content); }); }); describe('getAckPatternName', () => { @@ -176,25 +181,28 @@ describe('ServerRedis', () => { }); describe('when "retryAttempts" does not exist', () => { it('should return undefined', () => { - (server as any).config.retryAttempts = undefined; + (server as any).options.options = {}; + (server as any).options.options.retryAttempts = undefined; const result = server.createRetryStrategy({} as any); expect(result).to.be.undefined; }); }); describe('when "attempts" count is max', () => { it('should return undefined', () => { - (server as any).config.retryAttempts = 3; + (server as any).options.options = {}; + (server as any).options.options.retryAttempts = 3; const result = server.createRetryStrategy({ attempt: 4 } as any); expect(result).to.be.undefined; }); }); describe('otherwise', () => { it('should return delay (ms)', () => { + (server as any).options.options = {}; (server as any).isExplicitlyTerminated = false; - (server as any).config.retryAttempts = 3; - (server as any).config.retryDelay = 3; + (server as any).options.options.retryAttempts = 3; + (server as any).options.options.retryDelay = 3; const result = server.createRetryStrategy({ attempt: 2 } as any); - expect(result).to.be.eql((server as any).config.retryDelay); + expect(result).to.be.eql((server as any).options.options.retryDelay); }); }) }); diff --git a/src/microservices/test/server/server-tcp.spec.ts b/src/microservices/test/server/server-tcp.spec.ts index 1d78d1f292c..c8f67b012cb 100644 --- a/src/microservices/test/server/server-tcp.spec.ts +++ b/src/microservices/test/server/server-tcp.spec.ts @@ -49,6 +49,7 @@ describe('ServerTCP', () => { const msg = { pattern: 'test', data: 'tests', + id: '3', }; beforeEach(() => { socket = { @@ -59,6 +60,7 @@ describe('ServerTCP', () => { server.handleMessage(socket, msg); expect( socket.sendMessage.calledWith({ + id: msg.id, status: 'error', err: NO_PATTERN_MESSAGE, }), @@ -83,14 +85,14 @@ describe('ServerTCP', () => { }); describe('when "retryAttempts" does not exist', () => { it('should return undefined', () => { - (server as any).config.retryAttempts = undefined; + (server as any).options.retryAttempts = undefined; const result = server.handleClose(); expect(result).to.be.undefined; }); }); describe('when "retryAttemptsCount" count is max', () => { it('should return undefined', () => { - (server as any).config.retryAttempts = 3; + (server as any).options.retryAttempts = 3; (server as any).retryAttemptsCount = 3; const result = server.handleClose(); expect(result).to.be.undefined; @@ -98,10 +100,11 @@ describe('ServerTCP', () => { }); describe('otherwise', () => { it('should return delay (ms)', () => { + (server as any).options.options = {}; (server as any).isExplicitlyTerminated = false; - (server as any).config.retryAttempts = 3; + (server as any).options.options.retryAttempts = 3; (server as any).retryAttemptsCount = 2; - (server as any).config.retryDelay = 3; + (server as any).options.options.retryDelay = 3; const result = server.handleClose(); expect(result).to.be.not.undefined; }); diff --git a/src/microservices/test/server/server.spec.ts b/src/microservices/test/server/server.spec.ts index ae382b29c09..33609240ae0 100644 --- a/src/microservices/test/server/server.spec.ts +++ b/src/microservices/test/server/server.spec.ts @@ -42,7 +42,7 @@ describe('Server', () => { .true; }); it('should send "complete" event', () => { - expect(sendSpy.calledWith({ disposed: true })).to.be.true; + expect(sendSpy.calledWith({ isDisposed: true })).to.be.true; }); }); describe('emits response', () => { @@ -54,7 +54,7 @@ describe('Server', () => { .true; }); it('should send "complete" event', () => { - expect(sendSpy.calledWith({ disposed: true })).to.be.true; + expect(sendSpy.calledWith({ isDisposed: true })).to.be.true; }); }); }); diff --git a/src/microservices/test/server/test.proto b/src/microservices/test/server/test.proto new file mode 100644 index 00000000000..ba8c9202048 --- /dev/null +++ b/src/microservices/test/server/test.proto @@ -0,0 +1,3 @@ +syntax = "proto3"; + +package test; \ No newline at end of file diff --git a/src/websockets/adapters/io-adapter.ts b/src/websockets/adapters/io-adapter.ts index eacb124b588..755716442da 100644 --- a/src/websockets/adapters/io-adapter.ts +++ b/src/websockets/adapters/io-adapter.ts @@ -6,25 +6,28 @@ import { WebSocketAdapter } from '@nestjs/common'; import { Observable } from 'rxjs/Observable'; import { switchMap, filter, tap } from 'rxjs/operators'; import { fromEvent } from 'rxjs/observable/fromEvent'; +import { isFunction } from '@nestjs/common/utils/shared.utils'; export class IoAdapter implements WebSocketAdapter { constructor(private readonly httpServer: Server | null = null) {} - public create(port: number): any { - return this.createIOServer(port); - } - - public createWithNamespace(port: number, namespace: string, server?: any): any { - return server + public create(port: number, options?: any & { namespace?: string, server?: any}): any { + if (!options) { + return this.createIOServer(port); + } + const { namespace, server, ...opt } = options; + return server && isFunction(server.of) ? server.of(namespace) - : this.createIOServer(port).of(namespace); + : namespace + ? this.createIOServer(port, opt).of(namespace) + : this.createIOServer(port, opt); } - public createIOServer(port: number): any { + public createIOServer(port: number, options?: any): any { if (this.httpServer && port === 0) { - return io.listen(this.httpServer); + return io(this.httpServer, options); } - return io(port); + return io(port, options); } public bindClientConnect(server, callback: (...args) => void) { diff --git a/src/websockets/constants.ts b/src/websockets/constants.ts index d1cd0598b66..2498d201ad5 100644 --- a/src/websockets/constants.ts +++ b/src/websockets/constants.ts @@ -5,6 +5,7 @@ export const GATEWAY_METADATA = '__isGateway'; export const NAMESPACE_METADATA = 'namespace'; export const PORT_METADATA = 'port'; export const GATEWAY_MIDDLEWARES = '__gatewayMiddlewares'; +export const GATEWAY_OPTIONS = '__gatewayOptions'; export const CONNECTION_EVENT = 'connection'; export const DISCONNECT_EVENT = 'disconnect'; diff --git a/src/websockets/gateway-metadata-explorer.ts b/src/websockets/gateway-metadata-explorer.ts index ceb020aab0c..e3bdb1198e8 100644 --- a/src/websockets/gateway-metadata-explorer.ts +++ b/src/websockets/gateway-metadata-explorer.ts @@ -30,7 +30,6 @@ export class GatewayMetadataExplorer { MESSAGE_MAPPING_METADATA, callback, ); - if (isUndefined(isMessageMapping)) { return null; } @@ -59,6 +58,6 @@ export class GatewayMetadataExplorer { } export interface MessageMappingProperties { - message: string; - callback: (...args) => Observable | Promise | void; + message: any; + callback: (...args) => Observable | Promise | any; } diff --git a/src/websockets/interfaces/gateway-metadata.interface.ts b/src/websockets/interfaces/gateway-metadata.interface.ts index 2958279e1aa..18c6d5c1fad 100644 --- a/src/websockets/interfaces/gateway-metadata.interface.ts +++ b/src/websockets/interfaces/gateway-metadata.interface.ts @@ -1,8 +1,11 @@ -import { Type } from '@nestjs/common/interfaces/type.interface'; -import { GatewayMiddleware } from './gateway-middleware.interface'; - export interface GatewayMetadata { - port?: number; namespace?: string; - middlewares?: Type[]; + path?: string; + serveClient?: boolean; + adapter?: any; + origins?: string; + parser?: any; + pingTimeout?: number; + pingInterval?: number; + transports?: string[]; } diff --git a/src/websockets/middlewares-injector.ts b/src/websockets/middlewares-injector.ts index 1879bf8e635..26767676151 100644 --- a/src/websockets/middlewares-injector.ts +++ b/src/websockets/middlewares-injector.ts @@ -24,7 +24,7 @@ export class MiddlewaresInjector { ) {} public inject(server, instance: NestGateway, module: string) { - const adapter = this.config.getIoAdapter(); + const adapter: any = this.config.getIoAdapter(); if (!adapter.bindMiddleware) { return; } @@ -49,7 +49,7 @@ export class MiddlewaresInjector { components: Map>, tokens: any[], ) { - const adapter = this.config.getIoAdapter(); + const adapter: any = this.config.getIoAdapter(); iterate(tokens) .map(token => this.bindMiddleware(token.name, components)) .filter(middleware => !isNil(middleware)) diff --git a/src/websockets/socket-server-provider.ts b/src/websockets/socket-server-provider.ts index f888710f96c..bd072d35f9a 100644 --- a/src/websockets/socket-server-provider.ts +++ b/src/websockets/socket-server-provider.ts @@ -11,54 +11,54 @@ export class SocketServerProvider { ) {} public scanForSocketServer( - namespace: string, + options: any, port: number, ): ObservableSocketServer { const observableServer = this.socketsContainer.getServerByPort(port); return observableServer - ? this.createWithNamespace(namespace, port, observableServer) - : this.createSocketServer(namespace, port); + ? this.createWithNamespace(options, port, observableServer) + : this.createSocketServer(options, port); } private createSocketServer( - namespace: string, + options: any, port: number, ): ObservableSocketServer { + const { namespace, server, ...opt } = options; const adapter = this.applicationConfig.getIoAdapter(); - const server = adapter.create(port); - const observableSocket = ObservableSocket.create(server); + const ioServer = adapter.create(port, opt); + const observableSocket = ObservableSocket.create(ioServer); this.socketsContainer.addServer(null, port, observableSocket); - return this.createWithNamespace(namespace, port, observableSocket); + return this.createWithNamespace(options, port, observableSocket); } private createWithNamespace( - namespace: string, + options: any, port: number, observableSocket: ObservableSocketServer, ): ObservableSocketServer { - const adapter = this.applicationConfig.getIoAdapter(); - if (!namespace || !adapter.createWithNamespace) { + const { namespace } = options; + if (!namespace) { return observableSocket; } const namespaceServer = this.getServerOfNamespace( - namespace, + options, port, observableSocket.server, ); const observableNamespaceSocket = ObservableSocket.create(namespaceServer); this.socketsContainer.addServer(namespace, port, observableNamespaceSocket); - return observableNamespaceSocket; } - private getServerOfNamespace(namespace: string, port: number, server) { + private getServerOfNamespace(options: any, port: number, server) { const adapter = this.applicationConfig.getIoAdapter(); - return adapter.createWithNamespace( - port, - this.validateNamespace(namespace), + return adapter.create(port, { + ...options, + namespace: this.validateNamespace(options.namespace || ''), server, - ); + }); } private validateNamespace(namespace: string): string { diff --git a/src/websockets/test/container.spec.ts b/src/websockets/test/container.spec.ts index 1708cbc5bb0..c986568c2fc 100644 --- a/src/websockets/test/container.spec.ts +++ b/src/websockets/test/container.spec.ts @@ -31,4 +31,19 @@ describe('SocketsContainer', () => { expect(setSpy.calledWith({ namespace, port }, server)); }); }); + describe('getAllServers', () => { + it('should return "observableServers"', () => { + const collection = ['test']; + (instance as any).observableServers = collection; + expect(instance.getAllServers()).to.be.eq(collection); + }); + }); + describe('clear', () => { + it('should clear servers collection', () => { + const collection = { clear: sinon.spy() }; + (instance as any).observableServers = collection; + instance.clear(); + expect(collection.clear.called).to.be.true; + }); + }) }); diff --git a/src/websockets/test/context/ws-context-creator.spec.ts b/src/websockets/test/context/ws-context-creator.spec.ts index 442b9b1753c..1d5c9f9e67b 100644 --- a/src/websockets/test/context/ws-context-creator.spec.ts +++ b/src/websockets/test/context/ws-context-creator.spec.ts @@ -51,6 +51,8 @@ describe('WsContextCreator', () => { beforeEach(() => { wsProxy = new WsProxy(); + sinon.stub(wsProxy, 'create').callsFake(a => a); + exceptionFiltersContext = new ExceptionFiltersContext(); pipesCreator = new PipesContextCreator(); pipesConsumer = new PipesConsumer(); diff --git a/src/websockets/test/gateway-metadata-explorer.spec.ts b/src/websockets/test/gateway-metadata-explorer.spec.ts index 7f80c86e8fe..c95b3636742 100644 --- a/src/websockets/test/gateway-metadata-explorer.spec.ts +++ b/src/websockets/test/gateway-metadata-explorer.spec.ts @@ -22,10 +22,10 @@ describe('GatewayMetadataExplorer', () => { constructor() {} - @SubscribeMessage({ value: message }) + @SubscribeMessage(message) public test() {} - @SubscribeMessage({ value: secMessage }) + @SubscribeMessage(secMessage) public testSec() {} public noMessage() {} diff --git a/src/websockets/test/socket-server-provider.spec.ts b/src/websockets/test/socket-server-provider.spec.ts index 9b25f145c2d..964b6c85f7e 100644 --- a/src/websockets/test/socket-server-provider.spec.ts +++ b/src/websockets/test/socket-server-provider.spec.ts @@ -32,7 +32,7 @@ describe('SocketServerProvider', () => { const server = { test: 'test' }; mockContainer.expects('getServerByPort').returns(server); - const result = instance.scanForSocketServer(null, port); + const result = instance.scanForSocketServer({ namespace: null }, port); expect(createSocketServerSpy.called).to.be.false; expect(result).to.eq(server); @@ -40,7 +40,7 @@ describe('SocketServerProvider', () => { it(`should call "createSocketServer" when server is not stored already`, () => { mockContainer.expects('getServerByPort').returns(null); - instance.scanForSocketServer(namespace, port); + instance.scanForSocketServer({ namespace }, port); expect(createSocketServerSpy.called).to.be.true; }); }); diff --git a/src/websockets/test/utils/socket-gateway.decorator.spec.ts b/src/websockets/test/utils/socket-gateway.decorator.spec.ts index 6b5fff8c8be..b19cf7981dd 100644 --- a/src/websockets/test/utils/socket-gateway.decorator.spec.ts +++ b/src/websockets/test/utils/socket-gateway.decorator.spec.ts @@ -1,18 +1,43 @@ import 'reflect-metadata'; import { expect } from 'chai'; import { WebSocketGateway } from '../../utils/socket-gateway.decorator'; +import { GATEWAY_METADATA, GATEWAY_OPTIONS } from '../../constants'; describe('@WebSocketGateway', () => { - @WebSocketGateway({ port: 80, namespace: '/' }) + @WebSocketGateway(80, { namespace: '/' }) class TestGateway {} it('should decorate transport with expected metadata', () => { const isGateway = Reflect.getMetadata('__isGateway', TestGateway); const port = Reflect.getMetadata('port', TestGateway); - const namespace = Reflect.getMetadata('namespace', TestGateway); + const { namespace } = Reflect.getMetadata(GATEWAY_OPTIONS, TestGateway); expect(isGateway).to.be.eql(true); expect(port).to.be.eql(80); expect(namespace).to.be.eql('/'); }); + + @WebSocketGateway() + class TestGateway2 {} + + it('should decorate transport with port: 0', () => { + const isGateway = Reflect.getMetadata('__isGateway', TestGateway2); + const port = Reflect.getMetadata('port', TestGateway2); + + expect(isGateway).to.be.eql(true); + expect(port).to.be.eql(0); + }); + + @WebSocketGateway({ namespace: '/' }) + class TestGateway3 {} + + it('should decorate transport with expected options', () => { + const isGateway = Reflect.getMetadata('__isGateway', TestGateway3); + const port = Reflect.getMetadata('port', TestGateway3); + const { namespace } = Reflect.getMetadata(GATEWAY_OPTIONS, TestGateway3); + + expect(isGateway).to.be.eql(true); + expect(port).to.be.eql(0); + expect(namespace).to.be.eql('/'); + }); }); diff --git a/src/websockets/test/utils/subscribe-message.decorator.spec.ts b/src/websockets/test/utils/subscribe-message.decorator.spec.ts index 99aa65e68c8..2ba4874cdce 100644 --- a/src/websockets/test/utils/subscribe-message.decorator.spec.ts +++ b/src/websockets/test/utils/subscribe-message.decorator.spec.ts @@ -4,7 +4,7 @@ import { SubscribeMessage } from '../../utils/subscribe-message.decorator'; describe('@SubscribeMessage', () => { class TestGateway { - @SubscribeMessage({ value: 'filter' }) + @SubscribeMessage('filter') static fn() {} } diff --git a/src/websockets/test/web-sockets-controller.spec.ts b/src/websockets/test/web-sockets-controller.spec.ts index aeb7e48d92c..f7710ba4433 100644 --- a/src/websockets/test/web-sockets-controller.spec.ts +++ b/src/websockets/test/web-sockets-controller.spec.ts @@ -11,6 +11,7 @@ import { WsContextCreator } from '../context/ws-context-creator'; import { Observable } from 'rxjs/Observable'; import { IoAdapter } from '../index'; import { of } from 'rxjs/observable/of'; +import { PORT_METADATA } from '../constants'; describe('WebSocketsController', () => { let instance: WebSocketsController; @@ -20,7 +21,7 @@ describe('WebSocketsController', () => { const port = 90, namespace = '/'; - @WebSocketGateway({ port, namespace }) + @WebSocketGateway(port, { namespace }) class Test {} beforeEach(() => { @@ -37,7 +38,7 @@ describe('WebSocketsController', () => { describe('hookGatewayIntoServer', () => { let subscribeObservableServer: sinon.SinonSpy; - @WebSocketGateway({ port: 'test' } as any) + @WebSocketGateway('test') class InvalidGateway {} @WebSocketGateway() @@ -48,6 +49,7 @@ describe('WebSocketsController', () => { (instance as any).subscribeObservableServer = subscribeObservableServer; }); it('should throws "InvalidSocketPortException" when port is not a number', () => { + Reflect.defineMetadata(PORT_METADATA, 'test', InvalidGateway); expect(() => instance.hookGatewayIntoServer( new InvalidGateway(), @@ -59,12 +61,12 @@ describe('WebSocketsController', () => { it('should call "subscribeObservableServer" with default values when metadata is empty', () => { const gateway = new DefaultGateway(); instance.hookGatewayIntoServer(gateway, DefaultGateway, ''); - expect(subscribeObservableServer.calledWith(gateway, '', 0)).to.be.true; + expect(subscribeObservableServer.calledWith(gateway, {}, 0, '')).to.be.true; }); it('should call "subscribeObservableServer" when metadata is valid', () => { const gateway = new Test(); instance.hookGatewayIntoServer(gateway, Test, ''); - expect(subscribeObservableServer.calledWith(gateway, namespace, port)).to + expect(subscribeObservableServer.calledWith(gateway, { namespace }, port, '')).to .be.true; }); }); diff --git a/src/websockets/utils/gateway-server.decorator.ts b/src/websockets/utils/gateway-server.decorator.ts index f68cebcc7dc..8bf273988c0 100644 --- a/src/websockets/utils/gateway-server.decorator.ts +++ b/src/websockets/utils/gateway-server.decorator.ts @@ -2,7 +2,7 @@ import 'reflect-metadata'; import { GATEWAY_SERVER_METADATA } from '../constants'; /** - * Attaches the native Web Socket Server to the given property. + * Attaches a native Web Socket Server to the given property. */ export const WebSocketServer = (): PropertyDecorator => { return (target: object, propertyKey: string | symbol) => { diff --git a/src/websockets/utils/socket-gateway.decorator.ts b/src/websockets/utils/socket-gateway.decorator.ts index 5d3d7b12566..01cbb4e6504 100644 --- a/src/websockets/utils/socket-gateway.decorator.ts +++ b/src/websockets/utils/socket-gateway.decorator.ts @@ -2,26 +2,32 @@ import 'reflect-metadata'; import { GatewayMetadata } from '../interfaces'; import { PORT_METADATA, - NAMESPACE_METADATA, GATEWAY_METADATA, + GATEWAY_OPTIONS, GATEWAY_MIDDLEWARES, } from '../constants'; /** - * Defines the Gateway. The gateway can inject dependencies through constructor. - * Those dependencies should belongs to the same module. Gateway is listening on the specified port. + * Defines the Gateway. The gateway is able to inject dependencies through constructor. + * Those dependencies should belong to the same module. Gateway is listening on the specified port. */ -export const WebSocketGateway = ( - metadataOrPort?: GatewayMetadata | number, -): ClassDecorator => { - if (Number.isInteger(metadataOrPort as number)) { - metadataOrPort = { port: metadataOrPort } as any; - } - const metadata: GatewayMetadata = (metadataOrPort as GatewayMetadata) || {}; +export function WebSocketGateway(port?: number); +export function WebSocketGateway(options?: GatewayMetadata | any); +export function WebSocketGateway(port?: number, options?: GatewayMetadata | any); +export function WebSocketGateway( + portOrOptions?: number | GatewayMetadata | any, + options?: GatewayMetadata | any, +): ClassDecorator { + const isPortInt = Number.isInteger(portOrOptions as number); + let [port, opt] = isPortInt + ? [portOrOptions, options] + : [0, portOrOptions]; + + opt = opt || {}; return (target: object) => { Reflect.defineMetadata(GATEWAY_METADATA, true, target); - Reflect.defineMetadata(NAMESPACE_METADATA, metadata.namespace, target); - Reflect.defineMetadata(PORT_METADATA, metadata.port, target); - Reflect.defineMetadata(GATEWAY_MIDDLEWARES, metadata.middlewares, target); + Reflect.defineMetadata(PORT_METADATA, port, target); + Reflect.defineMetadata(GATEWAY_OPTIONS, opt, target); + Reflect.defineMetadata(GATEWAY_MIDDLEWARES, opt.middlewares, target); }; -}; +} diff --git a/src/websockets/utils/subscribe-message.decorator.ts b/src/websockets/utils/subscribe-message.decorator.ts index b06e7bc85d1..bd05424cfb4 100644 --- a/src/websockets/utils/subscribe-message.decorator.ts +++ b/src/websockets/utils/subscribe-message.decorator.ts @@ -3,17 +3,14 @@ import { MESSAGE_MAPPING_METADATA, MESSAGE_METADATA } from '../constants'; import { isObject, isUndefined } from '@nestjs/common/utils/shared.utils'; /** - * Subscribes to the messages, which fulfils chosen pattern. + * Subscribes to messages which fulfils chosen pattern. */ export const SubscribeMessage = ( - message?: { value: string } | string, + message: any, ): MethodDecorator => { - let metadata = isObject(message) ? message.value : message; - metadata = isUndefined(metadata) ? '' : metadata; - return (target, key, descriptor: PropertyDescriptor) => { Reflect.defineMetadata(MESSAGE_MAPPING_METADATA, true, descriptor.value); - Reflect.defineMetadata(MESSAGE_METADATA, metadata, descriptor.value); + Reflect.defineMetadata(MESSAGE_METADATA, message, descriptor.value); return descriptor; }; }; diff --git a/src/websockets/web-sockets-controller.ts b/src/websockets/web-sockets-controller.ts index a55568b01c2..ee9043f5032 100644 --- a/src/websockets/web-sockets-controller.ts +++ b/src/websockets/web-sockets-controller.ts @@ -9,7 +9,10 @@ import { } from './gateway-metadata-explorer'; import { Subject } from 'rxjs/Subject'; import { SocketServerProvider } from './socket-server-provider'; -import { NAMESPACE_METADATA, PORT_METADATA } from './constants'; +import { + PORT_METADATA, + GATEWAY_OPTIONS, +} from './constants'; import { Type } from '@nestjs/common/interfaces/type.interface'; import { MetadataScanner } from '@nestjs/core/metadata-scanner'; import { NestContainer } from '@nestjs/core/injector/container'; @@ -42,18 +45,18 @@ export class WebSocketsController { metatype: Type, module: string, ) { - const namespace = Reflect.getMetadata(NAMESPACE_METADATA, metatype) || ''; + const options = Reflect.getMetadata(GATEWAY_OPTIONS, metatype) || {}; const port = Reflect.getMetadata(PORT_METADATA, metatype) || 0; - + if (!Number.isInteger(port)) { throw new InvalidSocketPortException(port, metatype); } - this.subscribeObservableServer(instance, namespace, port, module); + this.subscribeObservableServer(instance, options, port, module); } public subscribeObservableServer( instance: NestGateway, - namespace: string, + options: any, port: number, module: string, ) { @@ -65,10 +68,9 @@ export class WebSocketsController { }), ); const observableServer = this.socketServerProvider.scanForSocketServer( - namespace, + options, port, ); - this.injectMiddlewares(observableServer, instance, module); this.hookServerToProperties(instance, observableServer.server); this.subscribeEvents(instance, messageHandlers, observableServer);