diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..4b5cea964 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +TEST_APPLICATION_ID= +TEST_CLIENT_ID= +TEST_CLIENT_SECRET= +TEST_SCOPE= +TEST_ACCESS_TOKEN= +TEST_REFRESH_TOKEN= +TEST_OAUTH_REDIRECT_PORT=3000 diff --git a/.eslintrc.js b/.eslintrc.js index e83461edf..1860126e4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,5 +23,9 @@ module.exports = { 'prettier', 'prettier/@typescript-eslint', 'plugin:prettier/recommended' - ] + ], + rules: { + '@typescript-eslint/explicit-function-return-type': ['off'], + '@typescript-eslint/camelcase': ['warn', { 'properties': 'always' }] + } }; diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 5ba7ad093..e754ccd86 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -25,6 +25,13 @@ jobs: npm install npm run lint npm test + npm run test:passthrough npm run build env: + TEST_ACCESS_TOKEN: ${{ secrets.TEST_ACCESS_TOKEN }} + TEST_APPLICATION_ID: ${{ secrets.TEST_APPLICATION_ID }} + TEST_CLIENT_ID: ${{ secrets.TEST_CLIENT_ID }} + TEST_CLIENT_SECRET: ${{ secrets.TEST_CLIENT_SECRET }} + TEST_REFRESH_TOKEN: ${{ secrets.TEST_REFRESH_TOKEN }} + TEST_SCOPE: ${{ secrets.TEST_SCOPE }} CI: true diff --git a/package-lock.json b/package-lock.json index 3b7954d5d..58ac37c82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -488,6 +488,22 @@ } } }, + "@scaleleap/config": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@scaleleap/config/-/config-1.2.3.tgz", + "integrity": "sha512-TCO7j8eQIYVznDAgc2FvUW2BM1ywBv1ba0dKjU0u+yfgzOn8vd+zoVvmQm/c/H1jbgVhUA2AiyUKUZu+WcwEiQ==", + "dev": true, + "requires": { + "browser-or-node": "1.2.1", + "dotenv": "8.1.0", + "env-var": "5.1.0" + } + }, + "@servie/events": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@servie/events/-/events-1.0.0.tgz", + "integrity": "sha512-sBSO19KzdrJCM3gdx6eIxV8M9Gxfgg6iDQmH5TIAGaUu+X9VDdsINXJOnoiZ1Kx3TrHdH4bt5UVglkjsEGBcvw==" + }, "@sindresorhus/fnv1a": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-1.2.0.tgz", @@ -587,10 +603,16 @@ "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", "dev": true }, + "@types/lodash": { + "version": "4.14.142", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.142.tgz", + "integrity": "sha512-ZhNS7c4D4WQ49Dr1FftQj7SwngRswOnPfxktmqSy5BettRCuum2q784jRwNTYfxH00r8+fEgRz6Da8j5DHNd1Q==", + "dev": true + }, "@types/node": { - "version": "12.11.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz", - "integrity": "sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A==", + "version": "12.7.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz", + "integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==", "dev": true }, "@types/pollyjs__adapter": { @@ -639,6 +661,11 @@ "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", "dev": true }, + "@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" + }, "@types/yargs": { "version": "13.0.3", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", @@ -1154,6 +1181,12 @@ } } }, + "browser-or-node": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.2.1.tgz", + "integrity": "sha512-sVIA0cysIED0nbmNOm7sZzKfgN1rpFmrqvLZaFWspaBAftfQcezlC81G6j6U2RJf4Lh66zFxrCeOsvkUXIcPWg==", + "dev": true + }, "browser-process-hrtime": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", @@ -1201,6 +1234,11 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/byte-length/-/byte-length-1.0.2.tgz", + "integrity": "sha512-ovBpjmsgd/teRmgcPh23d4gJvxDoXtAzEL9xTfMU8Yc2kqCDb7L9jAG0XHl1nzuGl+h3ebCIF1i62UFyA9V/2Q==" + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -1406,6 +1444,15 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, + "client-oauth2": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/client-oauth2/-/client-oauth2-4.2.5.tgz", + "integrity": "sha512-GAhVLveAbBkwcfEH/d5lTW9eCgcPR3Up93cx7v4qWTdLCa4O0m3ykNNn4aAVeWOiHfWL5skO+3u0F/gfAxZuPQ==", + "requires": { + "popsicle": "12.0.4", + "safe-buffer": "^5.1.1" + } + }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -1534,6 +1581,58 @@ "vary": "^1" } }, + "cross-env": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-6.0.3.tgz", + "integrity": "sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.0.tgz", + "integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.1.tgz", + "integrity": "sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "cross-fetch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.4.tgz", @@ -1752,6 +1851,12 @@ "webidl-conversions": "^4.0.2" } }, + "dotenv": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.1.0.tgz", + "integrity": "sha512-GUE3gqcDCaMltj2++g6bRQ5rBJWtkWTmqmD0fo1RnnMuUqHNCt2oTPeDnS9n6fKYvlhn7AeBkb38lymBtWBQdA==", + "dev": true + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1784,11 +1889,16 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "requires": { "once": "^1.4.0" } }, + "env-var": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/env-var/-/env-var-5.1.0.tgz", + "integrity": "sha512-X0qlMaP0ltgl4GzZBjbihb6MVUYUNDipQoF044ycHknbekr9LI/F0fahIRaC1WLgyw0ZTEu+G1/UxBsAvMaN+A==", + "dev": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3426,6 +3536,11 @@ "resolved": "https://registry.npmjs.org/io-ts-types/-/io-ts-types-0.5.1.tgz", "integrity": "sha512-wdlK7Gn9gBaI3BT44mpwU+Y54J3GTT5R3dkM7CYZoaPMKWDwp/jYZ1Bj8/RRfcwP2ElJjuGre06avjkHLvgl7w==" }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" + }, "ipaddr.js": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", @@ -4384,8 +4499,7 @@ "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash-es": { "version": "4.17.15", @@ -4441,8 +4555,15 @@ "make-error": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", - "dev": true + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==" + }, + "make-error-cause": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/make-error-cause/-/make-error-cause-2.2.1.tgz", + "integrity": "sha512-6wV0MRRpTnab6ctyQ41jusj9gkKfwoikD02ygJVgkjMFDSURcmby+WjXSyXrxmi6yldFJCR9lr3HrCcyPgSGJg==", + "requires": { + "make-error": "^1.3.5" + } }, "makeerror": { "version": "1.0.11", @@ -4882,7 +5003,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -5111,6 +5231,84 @@ "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", "dev": true }, + "popsicle": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/popsicle/-/popsicle-12.0.4.tgz", + "integrity": "sha512-UuxhAFa4RXBecC6ZK24sKra/9va1bTxnb3CQpFsm+VBW72sl+UtTAmZv7LZTvvDNnGusAqisN+a6xSN9xSQzZA==", + "requires": { + "popsicle-content-encoding": "^1.0.0", + "popsicle-cookie-jar": "^1.0.0", + "popsicle-redirects": "^1.0.0", + "popsicle-transport-http": "^1.0.0", + "popsicle-transport-xhr": "^1.0.0", + "popsicle-user-agent": "^1.0.0", + "servie": "^4.0.6", + "throwback": "^4.1.0", + "tough-cookie": "^3.0.1" + }, + "dependencies": { + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "popsicle-content-encoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/popsicle-content-encoding/-/popsicle-content-encoding-1.0.0.tgz", + "integrity": "sha512-4Df+vTfM8wCCJVTzPujiI6eOl3SiWQkcZg0AMrOkD1enMXsF3glIkFUZGvour1Sj7jOWCsNSEhBxpbbhclHhzw==" + }, + "popsicle-cookie-jar": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/popsicle-cookie-jar/-/popsicle-cookie-jar-1.0.0.tgz", + "integrity": "sha512-vrlOGvNVELko0+J8NpGC5lHWDGrk8LQJq9nwAMIVEVBfN1Lib3BLxAaLRGDTuUnvl45j5N9dT2H85PULz6IjjQ==", + "requires": { + "@types/tough-cookie": "^2.3.5", + "tough-cookie": "^3.0.1" + }, + "dependencies": { + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "popsicle-redirects": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/popsicle-redirects/-/popsicle-redirects-1.1.0.tgz", + "integrity": "sha512-XCpzVjVk7tty+IJnSdqWevmOr1n8HNDhL86v7mZ6T1JIIf2KGybxUk9mm7ZFOhWMkGB0e8XkacHip7BV8AQWQA==" + }, + "popsicle-transport-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/popsicle-transport-http/-/popsicle-transport-http-1.0.4.tgz", + "integrity": "sha512-GwqlU9dLZMlfqqA2ay/eDSAMPdBBdjpS1PqsDScEmrVCxPVaKlJVcNpHUXevaXv0bpaPM1DfEk1WGByd0RlzLg==", + "requires": { + "make-error-cause": "^2.2.0", + "pump": "^3.0.0" + } + }, + "popsicle-transport-xhr": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/popsicle-transport-xhr/-/popsicle-transport-xhr-1.0.2.tgz", + "integrity": "sha512-v9eAJnj1tydT4VmDdyKFE1z/+oL01vB7AS3LfSFMAYv33dzqlxtbApKALcYWBQotIqw3FoIqd2FiDR6qJsOxtA==" + }, + "popsicle-user-agent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/popsicle-user-agent/-/popsicle-user-agent-1.0.0.tgz", + "integrity": "sha512-epKaq3TTfTzXcxBxjpoKYMcTTcAX8Rykus6QZu77XNhJuRHSRxMd+JJrbX/3PFI0opFGSN0BabbAYCbGxbu0mA==" + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -5185,14 +5383,12 @@ "psl": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==", - "dev": true + "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -5201,8 +5397,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { "version": "6.5.2", @@ -5494,8 +5689,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -5582,6 +5776,15 @@ "send": "0.17.1" } }, + "servie": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/servie/-/servie-4.3.1.tgz", + "integrity": "sha512-4aFazTOWZbX2sXxe0cOoKK62s6Ty8Sz/9yiPr3JhP1poxBZviyU9/cSnD0lUwVmJlOjKk+yA8/uRgyMCgVnyBA==", + "requires": { + "@servie/events": "^1.0.0", + "byte-length": "^1.0.2" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -6072,6 +6275,11 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, + "throwback": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throwback/-/throwback-4.1.0.tgz", + "integrity": "sha512-dLFe8bU8SeH0xeqeKL7BNo8XoPC/o91nz9/ooeplZPiso+DZukhoyZcSz9TFnUNScm+cA9qjU1m1853M6sPOng==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -6519,8 +6727,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", diff --git a/package.json b/package.json index 94e485a0b..cf5b6ca7d 100644 --- a/package.json +++ b/package.json @@ -6,28 +6,34 @@ "scripts": { "build": "tsc", "lint": "eslint --ext .js,.ts src test", - "test": "jest" + "test": "jest", + "test:passthrough": "cross-env POLLY_MODE=passthrough npm test -- -t 'polly:passthrough'" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { + "client-oauth2": "4.2.5", "cross-fetch": "3.0.4", "fp-ts": "2.1.0", "io-ts": "2.0.1", - "io-ts-types": "0.5.1" + "io-ts-types": "0.5.1", + "lodash": "4.17.15" }, "devDependencies": { "@pollyjs/adapter-node-http": "2.6.3", "@pollyjs/core": "2.6.3", "@pollyjs/persister-fs": "2.6.3", + "@scaleleap/config": "1.2.3", "@types/jest": "24.0.19", - "@types/node": "12.11.1", + "@types/lodash": "4.14.142", + "@types/node": "12.7.12", "@types/pollyjs__adapter-node-http": "2.0.0", "@types/pollyjs__core": "2.6.1", "@types/pollyjs__persister-fs": "2.0.0", "@typescript-eslint/eslint-plugin": "2.4.0", "@typescript-eslint/parser": "2.4.0", + "cross-env": "6.0.3", "eslint": "6.5.1", "eslint-config-prettier": "6.4.0", "eslint-plugin-jest": "22.19.0", diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 000000000..41a50a108 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,3 @@ +import { name, version } from '../package.json' + +export const USER_AGENT = `${name}/${version}` diff --git a/src/o-auth-client.ts b/src/o-auth-client.ts new file mode 100644 index 000000000..a5e6e3f30 --- /dev/null +++ b/src/o-auth-client.ts @@ -0,0 +1,58 @@ +import ClientOAuth2 from 'client-oauth2' +import { Options } from 'client-oauth2' +import fetch, { Headers } from 'cross-fetch' +import { defaultsDeep } from 'lodash' +import { USER_AGENT } from './constants' + +const request: ClientOAuth2.Request = async ( + method, + url, + body, + headerRecord, +): ReturnType => { + const headers = new Headers() + headers.append('Accept-Encoding', 'application/json') // disable compression + headers.append('User-Agent', USER_AGENT) + Object.keys(headerRecord).map(key => headers.append(key, headerRecord[key] as string)) + + const req: RequestInit = { + method, + headers, + body, + } + + const res = await fetch(url, req) + + return { + status: res.status, + body: await res.text(), + } +} + +export class OAuthClient { + // https://advertising.amazon.com/API/docs/v2/guides/authorization + private readonly amazonOptions: Options = { + accessTokenUri: 'https://api.amazon.com/auth/o2/token', + authorizationUri: 'https://www.amazon.com/ap/oa', + scopes: ['cpc_advertising:campaign_management'], + } + + private readonly client = new ClientOAuth2( + defaultsDeep({}, this.opts, this.amazonOptions), + request, + ) + + public constructor(private readonly opts: Options) {} + + public get getUri() { + return this.client.code.getUri.bind(this) + } + + public get getToken() { + return this.client.code.getToken.bind(this) + } + + public createToken(accessToken: string, refreshToken: string) { + return this.client.createToken(accessToken, refreshToken, 'bearer', {}) + } +} diff --git a/test/__recordings__/RefreshToken_790648689/recording.har b/test/__recordings__/RefreshToken_790648689/recording.har new file mode 100644 index 000000000..32b572acd --- /dev/null +++ b/test/__recordings__/RefreshToken_790648689/recording.har @@ -0,0 +1,144 @@ +{ + "log": { + "_recordingName": "RefreshToken", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "2.6.3" + }, + "entries": [ + { + "_id": "0ef724e784c8a306a30aa3e06a92706f", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 650, + "cookies": [], + "headers": [ + { + "_fromType": "array", + "name": "accept-encoding", + "value": "application/json" + }, + { + "_fromType": "array", + "name": "user-agent", + "value": "amazon-advertising-api-nodejs-sdk-2/1.0.0" + }, + { + "_fromType": "array", + "name": "accept", + "value": "application/json, application/x-www-form-urlencoded" + }, + { + "_fromType": "array", + "name": "content-type", + "value": "application/x-www-form-urlencoded" + }, + { + "name": "authorization", + "value": "Basic x" + }, + { + "_fromType": "array", + "name": "content-length", + "value": "650" + }, + { + "_fromType": "array", + "name": "connection", + "value": "close" + }, + { + "name": "host", + "value": "api.amazon.com" + } + ], + "headersSize": 340, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "application/x-www-form-urlencoded", + "params": [], + "text": "refresh_token=x&grant_type=refresh_token" + }, + "queryString": [], + "url": "https://api.amazon.com/auth/o2/token" + }, + "response": { + "bodySize": 1318, + "content": { + "mimeType": "application/json;charset=UTF-8", + "size": 1318, + "text": "{\"access_token\":\"x\",\"expires_in\":3600,\"refresh_token\":\"x\",\"token_type\":\"bearer\"}" + }, + "cookies": [], + "headers": [ + { + "name": "server", + "value": "Server" + }, + { + "name": "date", + "value": "Wed, 16 Oct 2019 14:49:03 GMT" + }, + { + "name": "content-type", + "value": "application/json;charset=UTF-8" + }, + { + "name": "content-length", + "value": "1318" + }, + { + "name": "connection", + "value": "close" + }, + { + "name": "x-amzn-requestid", + "value": "a249376c-2051-47a4-bbee-77b302a7036d" + }, + { + "name": "x-amz-date", + "value": "Wed, 16 Oct 2019 14:49:03 GMT" + }, + { + "name": "cache-control", + "value": "no-cache, no-store, must-revalidate" + }, + { + "name": "pragma", + "value": "no-cache" + }, + { + "name": "vary", + "value": "Accept-Encoding,X-Amzn-CDN-Cache,X-Amzn-AX-Treatment,User-Agent" + }, + { + "name": "x-amz-rid", + "value": "EKA81PETZ52XMDDA39WX" + } + ], + "headersSize": 415, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2019-10-16T14:48:52.896Z", + "time": 202, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 202 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/test/config.ts b/test/config.ts new file mode 100644 index 000000000..c20ff9788 --- /dev/null +++ b/test/config.ts @@ -0,0 +1,22 @@ +import { BaseConfig } from '@scaleleap/config' +import { Polly } from '@pollyjs/core' + +const POLY_MODES: Polly['mode'][] = ['record', 'passthrough', 'replay', 'stopped'] + +export class Config extends BaseConfig { + public readonly TEST_APPLICATION_ID = this.get('TEST_APPLICATION_ID').asString() + public readonly TEST_CLIENT_ID = this.get('TEST_CLIENT_ID').asString() + public readonly TEST_CLIENT_SECRET = this.get('TEST_CLIENT_SECRET').asString() + public readonly TEST_SCOPE = this.get('TEST_SCOPE').asIntPositive() + public readonly TEST_ACCESS_TOKEN = this.get('TEST_ACCESS_TOKEN').asString() + public readonly TEST_REFRESH_TOKEN = this.get('TEST_REFRESH_TOKEN').asString() + + public readonly TEST_OAUTH_REDIRECT_PORT = this.get( + 'TEST_OAUTH_REDIRECT_PORT', + '3000', + ).asIntPositive() + + public readonly POLLY_MODE = this.get('POLLY_MODE', 'replay').asEnum(POLY_MODES) as Polly['mode'] +} + +export const config = new Config() diff --git a/test/o-auth-client.test.ts b/test/o-auth-client.test.ts new file mode 100644 index 000000000..acb1edf1c --- /dev/null +++ b/test/o-auth-client.test.ts @@ -0,0 +1,79 @@ +import { OAuthClient } from '../src/o-auth-client' +import { Token } from 'client-oauth2' +import { config } from './config' +import { PollyJS } from './pollyjs/polly' +import { parse, stringify } from 'querystring' + +const URI = 'https://example.com' +const PLACEHOLDER = 'x' +const pollyJs = new PollyJS('RefreshToken') +const server = pollyJs.getPollyServer() +const polly = pollyJs.getPollyInstance() + +server.post('https://api.amazon.com/auth/o2/token').on('beforeResponse', (req, res) => { + /* eslint-disable @typescript-eslint/camelcase */ + req.body = stringify( + Object.assign(parse(req.body), { + refresh_token: PLACEHOLDER, + }), + ) + + req.setHeader('authorization', `Basic ${PLACEHOLDER}`) + + res.body = JSON.stringify( + Object.assign(JSON.parse(res.body), { + access_token: PLACEHOLDER, + refresh_token: PLACEHOLDER, + }), + ) + /* eslint-enable @typescript-eslint/camelcase */ +}) + +describe(OAuthClient.name, () => { + let client: OAuthClient + + beforeEach(() => { + client = new OAuthClient({ + clientId: 'foo', + clientSecret: 'foo', + redirectUri: URI, + }) + }) + + it('should provide a correct uri', () => { + const uri = client.getUri() + expect(uri).toMatch(new RegExp('^https://www.amazon.com/')) + }) + + it('should have required methods', () => { + expect(client).toHaveProperty('getUri') + expect(client).toHaveProperty('getToken') + expect(client).toHaveProperty('createToken') + }) + + it('createToken returns a token object', () => { + const token = client.createToken(PLACEHOLDER, PLACEHOLDER) + expect(token).toBeInstanceOf(Token) + }) + + it('refresh an existing token polly:passthrough', async () => { + client = new OAuthClient({ + clientId: config.TEST_CLIENT_ID, + clientSecret: config.TEST_CLIENT_SECRET, + redirectUri: URI, + }) + + const token = client.createToken( + config.TEST_ACCESS_TOKEN || '', + config.TEST_REFRESH_TOKEN || '', + ) + + const res = await token.refresh() + + expect(res.accessToken).toBe(PLACEHOLDER) + expect(res.refreshToken).toBe(PLACEHOLDER) + expect(res.tokenType).toBe('bearer') + + await polly.stop() + }) +}) diff --git a/test/pollyjs/polly.ts b/test/pollyjs/polly.ts index 17a356fbd..6250237a5 100644 --- a/test/pollyjs/polly.ts +++ b/test/pollyjs/polly.ts @@ -2,6 +2,7 @@ import path from 'path' import { Polly, PollyServer } from '@pollyjs/core' import NodeHttpAdapter from '@pollyjs/adapter-node-http' import FSPersister from '@pollyjs/persister-fs' +import { config } from '../config' Polly.register(FSPersister) Polly.register(NodeHttpAdapter) @@ -13,6 +14,7 @@ export class PollyJS { constructor(recordingName: string) { this.recordingName = recordingName this.polly = new Polly(this.recordingName, { + mode: config.POLLY_MODE, adapters: ['node-http'], persister: 'fs', persisterOptions: {